DatabaseTracker.cpp   [plain text]


/*
 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "DatabaseTracker.h"

#if ENABLE(DATABASE)

#include "Chrome.h"
#include "ChromeClient.h"
#include "Database.h"
#include "DatabaseThread.h"
#include "DatabaseTrackerClient.h"
#include "FileSystem.h"
#include "Logging.h"
#include "OriginQuotaManager.h"
#include "Page.h"
#include "ScriptExecutionContext.h"
#include "SecurityOrigin.h"
#include "SecurityOriginHash.h"
#include "SQLiteFileSystem.h"
#include "SQLiteStatement.h"
#include <wtf/MainThread.h>
#include <wtf/StdLibExtras.h>

#include "WebCoreThread.h"

using namespace std;

static WebCore::OriginQuotaManager& originQuotaManager()
{
    DEFINE_STATIC_LOCAL(WebCore::OriginQuotaManager, quotaManager, ());
    return quotaManager;
}

namespace WebCore {

static DatabaseTracker* staticTracker = 0;

void DatabaseTracker::initializeTracker(const String& databasePath)
{
    ASSERT(!staticTracker);
    if (staticTracker)
        return;

    staticTracker = new DatabaseTracker(databasePath);
}

DatabaseTracker& DatabaseTracker::tracker()
{
    if (!staticTracker)
        staticTracker = new DatabaseTracker("");

    return *staticTracker;
}

DatabaseTracker::DatabaseTracker(const String& databasePath)
    : m_client(0)
{
    setDatabaseDirectoryPath(databasePath);
    
    SQLiteFileSystem::registerSQLiteVFS();

    MutexLocker lockDatabase(m_databaseGuard);
    populateOrigins();
}

void DatabaseTracker::setDatabaseDirectoryPath(const String& path)
{
    MutexLocker lockDatabase(m_databaseGuard);
    ASSERT(!m_database.isOpen());
    m_databaseDirectoryPath = path.threadsafeCopy();
}

String DatabaseTracker::databaseDirectoryPath() const
{
    return m_databaseDirectoryPath.threadsafeCopy();
}

String DatabaseTracker::trackerDatabasePath() const
{
    return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath, "Databases.db");
}

void DatabaseTracker::openTrackerDatabase(bool createIfDoesNotExist)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(WebThreadIsLockedOrDisabled());

    if (m_database.isOpen())
        return;

    String databasePath = trackerDatabasePath();
    if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist))
        return;

    if (!m_database.open(databasePath)) {
        // FIXME: What do do here?
        LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
        return;
    }
    m_database.disableThreadingChecks();
    if (!m_database.tableExists("Origins")) {
        if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) {
            // FIXME: and here
            LOG_ERROR("Failed to create Origins table");
        }
    }
    if (!m_database.tableExists("Databases")) {
        if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) {
            // FIXME: and here
            LOG_ERROR("Failed to create Databases table");
        }
    }
}

bool DatabaseTracker::canEstablishDatabase(ScriptExecutionContext* context, const String& name, const String& displayName, unsigned long estimatedSize)
{
    SecurityOrigin* origin = context->securityOrigin();
    ProposedDatabase details;

    unsigned long long requirement;
    unsigned long long tempUsage;
    {
        MutexLocker lockDatabase(m_databaseGuard);
        Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());

        if (!canCreateDatabase(origin, name))
            return false;

        recordCreatingDatabase(origin, name);

        // Since we're imminently opening a database within this context's origin, make sure this origin is being tracked by the QuotaTracker
        // by fetching its current usage now.
        unsigned long long usage = usageForOriginNoLock(origin);

        // If a database already exists, ignore the passed-in estimated size and say it's OK.
        if (hasEntryForDatabase(origin, name))
            return true;

        // If the database will fit, allow its creation.
        requirement = usage + max(1UL, estimatedSize);
        tempUsage = usage;
        if (requirement < usage) {
            doneCreatingDatabase(origin, name);
            return false; // If the estimated size is so big it causes an overflow, don't allow creation.
        }
        if (requirement <= quotaForOriginNoLock(origin))
            return true;

        // Give the chrome client a chance to increase the quota.
        // Temporarily make the details of the proposed database available, so the client can get at them.
        // FIXME: We should really just pass the details into this call, rather than using m_proposedDatabases.
        details = ProposedDatabase(origin->threadsafeCopy(), DatabaseDetails(name.threadsafeCopy(), displayName.threadsafeCopy(), estimatedSize, 0));
        m_proposedDatabases.add(&details);
    }
    // Drop all locks before calling out; we don't know what they'll do.
    context->databaseExceededQuota(name);

    MutexLocker lockDatabase(m_databaseGuard);

    m_proposedDatabases.remove(&details);

    // If the database will fit now, allow its creation.
    if (requirement <= quotaForOriginNoLock(origin))
        return true;

    doneCreatingDatabase(origin, name);

    return false;
}

bool DatabaseTracker::hasEntryForOriginNoLock(SecurityOrigin* origin)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(m_quotaMap);
    return m_quotaMap->contains(origin);
}

bool DatabaseTracker::hasEntryForOrigin(SecurityOrigin* origin)
{
    MutexLocker lockDatabase(m_databaseGuard);
    return hasEntryForOriginNoLock(origin);
}

bool DatabaseTracker::hasEntryForDatabase(SecurityOrigin* origin, const String& databaseIdentifier)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(WebThreadIsLockedOrDisabled());
    openTrackerDatabase(false);
    if (!m_database.isOpen())
        return false;
    SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");

    if (statement.prepare() != SQLResultOk)
        return false;

    statement.bindText(1, origin->databaseIdentifier());
    statement.bindText(2, databaseIdentifier);

    return statement.step() == SQLResultRow;
}

unsigned long long DatabaseTracker::getMaxSizeForDatabase(const Database* database)
{
    ASSERT(currentThread() == database->scriptExecutionContext()->databaseThread()->getThreadID());
    // The maximum size for a database is the full quota for its origin, minus the current usage within the origin,
    // plus the current usage of the given database
    MutexLocker lockDatabase(m_databaseGuard);
    Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
    SecurityOrigin* origin = database->securityOrigin();
    return quotaForOriginNoLock(origin) - originQuotaManager().diskUsage(origin) + SQLiteFileSystem::getDatabaseFileSize(database->fileName());
}

void DatabaseTracker::databaseChanged(Database* database)
{
    Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
    originQuotaManager().markDatabase(database);
}

String DatabaseTracker::originPath(SecurityOrigin* origin) const
{
    return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.threadsafeCopy(), origin->databaseIdentifier());
}

String DatabaseTracker::fullPathForDatabaseNoLock(SecurityOrigin* origin, const String& name, bool createIfNotExists)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(!originQuotaManager().tryLock());
    ASSERT(WebThreadIsLockedOrDisabled());

    for (HashSet<ProposedDatabase*>::iterator iter = m_proposedDatabases.begin(); iter != m_proposedDatabases.end(); ++iter)
        if ((*iter)->second.name() == name && (*iter)->first->equal(origin))
            return String();

    String originIdentifier = origin->databaseIdentifier();
    String originPath = this->originPath(origin);

    // Make sure the path for this SecurityOrigin exists
    if (createIfNotExists && !SQLiteFileSystem::ensureDatabaseDirectoryExists(originPath))
        return String();

    // See if we have a path for this database yet
    if (!m_database.isOpen())
        return String();
    SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");

    if (statement.prepare() != SQLResultOk)
        return String();

    statement.bindText(1, originIdentifier);
    statement.bindText(2, name);

    int result = statement.step();

    if (result == SQLResultRow)
        return SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, statement.getColumnText(0));
    if (!createIfNotExists)
        return String();

    if (result != SQLResultDone) {
        LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", originIdentifier.ascii().data(), name.ascii().data());
        return String();
    }
    statement.finalize();

    String fileName = SQLiteFileSystem::getFileNameForNewDatabase(originPath, name, originIdentifier, &m_database);
    if (!addDatabase(origin, name, fileName))
        return String();

    // If this origin's quota is being tracked (open handle to a database in this origin), add this new database
    // to the quota manager now
    String fullFilePath = SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, fileName);
    if (originQuotaManager().tracksOrigin(origin))
        originQuotaManager().addDatabase(origin, name, fullFilePath);

    return fullFilePath;
}

String DatabaseTracker::fullPathForDatabase(SecurityOrigin* origin, const String& name, bool createIfNotExists)
{
    MutexLocker lockDatabase(m_databaseGuard);
    Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());

    return fullPathForDatabaseNoLock(origin, name, createIfNotExists).threadsafeCopy();
}

void DatabaseTracker::populateOrigins()
{
    ASSERT(!m_databaseGuard.tryLock());
    if (m_quotaMap)
        return;

    m_quotaMap.set(new QuotaMap);

    openTrackerDatabase(false);
    if (!m_database.isOpen())
        return;

    SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");

    if (statement.prepare() != SQLResultOk) {
        LOG_ERROR("Failed to prepare statement.");
        return;
    }

    int result;
    while ((result = statement.step()) == SQLResultRow) {
        RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromDatabaseIdentifier(statement.getColumnText(0));
        m_quotaMap->set(origin.get()->threadsafeCopy(), statement.getColumnInt64(1));
    }

    if (result != SQLResultDone)
        LOG_ERROR("Failed to read in all origins from the database.");
}

void DatabaseTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
{
    MutexLocker lockDatabase(m_databaseGuard);
    ASSERT(m_quotaMap);
    copyKeysToVector(*m_quotaMap, result);
}

bool DatabaseTracker::databaseNamesForOriginNoLock(SecurityOrigin* origin, Vector<String>& resultVector)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(WebThreadIsLockedOrDisabled());
    openTrackerDatabase(false);
    if (!m_database.isOpen())
        return false;

    SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");

    if (statement.prepare() != SQLResultOk)
        return false;

    statement.bindText(1, origin->databaseIdentifier());

    int result;
    while ((result = statement.step()) == SQLResultRow)
        resultVector.append(statement.getColumnText(0));

    if (result != SQLResultDone) {
        LOG_ERROR("Failed to retrieve all database names for origin %s", origin->databaseIdentifier().ascii().data());
        return false;
    }

    return true;
}

bool DatabaseTracker::databaseNamesForOrigin(SecurityOrigin* origin, Vector<String>& resultVector)
{
    Vector<String> temp;
    {
        MutexLocker lockDatabase(m_databaseGuard);
        if (!databaseNamesForOriginNoLock(origin, temp))
          return false;
    }

    for (Vector<String>::iterator iter = temp.begin(); iter != temp.end(); ++iter)
        resultVector.append(iter->threadsafeCopy());
    return true;
}

DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, SecurityOrigin* origin)
{
    String originIdentifier = origin->databaseIdentifier();
    String displayName;
    int64_t expectedUsage;

    {
        MutexLocker lockDatabase(m_databaseGuard);

        for (HashSet<ProposedDatabase*>::iterator iter = m_proposedDatabases.begin(); iter != m_proposedDatabases.end(); ++iter)
            if ((*iter)->second.name() == name && (*iter)->first->equal(origin)) {
                ASSERT((*iter)->second.thread() == currentThread() || ((isMainThread() || pthread_main_np()) && WebThreadIsLockedOrDisabled()));
                return (*iter)->second;
            }

        openTrackerDatabase(false);
        if (!m_database.isOpen())
            return DatabaseDetails();
        SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
        if (statement.prepare() != SQLResultOk)
            return DatabaseDetails();

        statement.bindText(1, originIdentifier);
        statement.bindText(2, name);

        int result = statement.step();
        if (result == SQLResultDone)
            return DatabaseDetails();

        if (result != SQLResultRow) {
            LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data());
            return DatabaseDetails();
        }
        displayName = statement.getColumnText(0);
        expectedUsage = statement.getColumnInt64(1);
    }

    return DatabaseDetails(name, displayName, expectedUsage, usageForDatabase(name, origin));
}

void DatabaseTracker::setDatabaseDetails(SecurityOrigin* origin, const String& name, const String& displayName, unsigned long estimatedSize)
{
    String originIdentifier = origin->databaseIdentifier();
    int64_t guid = 0;

    MutexLocker lockDatabase(m_databaseGuard);

    openTrackerDatabase(true);
    if (!m_database.isOpen())
        return;
    SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
    if (statement.prepare() != SQLResultOk)
        return;

    statement.bindText(1, originIdentifier);
    statement.bindText(2, name);

    int result = statement.step();
    if (result == SQLResultRow)
        guid = statement.getColumnInt64(0);
    statement.finalize();

    if (guid == 0) {
        if (result != SQLResultDone)
            LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data());
        else {
            // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker
            // But since the tracker file is an external resource not under complete control of our code, it's somewhat invalid to make this an ASSERT case
            // So we'll print an error instead
            LOG_ERROR("Could not retrieve guid for database %s in origin %s from the tracker database - it is invalid to set database details on a database that doesn't already exist in the tracker",
                       name.ascii().data(), originIdentifier.ascii().data());
        }
        return;
    }

    SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
    if (updateStatement.prepare() != SQLResultOk)
        return;

    updateStatement.bindText(1, displayName);
    updateStatement.bindInt64(2, estimatedSize);
    updateStatement.bindInt64(3, guid);

    if (updateStatement.step() != SQLResultDone) {
        LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
        return;
    }

    if (m_client)
        m_client->dispatchDidModifyDatabase(origin, name);
}

unsigned long long DatabaseTracker::usageForDatabase(const String& name, SecurityOrigin* origin)
{
    String path = fullPathForDatabase(origin, name, false);
    if (path.isEmpty())
        return 0;

    return SQLiteFileSystem::getDatabaseFileSize(path);
}

void DatabaseTracker::addOpenDatabase(Database* database)
{
    if (!database)
        return;

    {
        MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);

        if (!m_openDatabaseMap)
            m_openDatabaseMap.set(new DatabaseOriginMap);

        String name(database->stringIdentifier());
        DatabaseNameMap* nameMap = m_openDatabaseMap->get(database->securityOrigin());
        if (!nameMap) {
            nameMap = new DatabaseNameMap;
            m_openDatabaseMap->set(database->securityOrigin()->threadsafeCopy(), nameMap);
        }

        DatabaseSet* databaseSet = nameMap->get(name);
        if (!databaseSet) {
            databaseSet = new DatabaseSet;
            nameMap->set(name.threadsafeCopy(), databaseSet);
        }

        databaseSet->add(database);

        LOG(StorageAPI, "Added open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
    }

    MutexLocker lockDatabase(m_databaseGuard);
    doneCreatingDatabase(database->securityOrigin(), database->stringIdentifier());
}

void DatabaseTracker::removeOpenDatabase(Database* database)
{
    if (!database)
        return;

    {
        MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);

        if (!m_openDatabaseMap) {
            ASSERT_NOT_REACHED();
            return;
        }

        String name(database->stringIdentifier());
        DatabaseNameMap* nameMap = m_openDatabaseMap->get(database->securityOrigin());
        if (!nameMap) {
            ASSERT_NOT_REACHED();
            return;
        }

        DatabaseSet* databaseSet = nameMap->get(name);
        if (!databaseSet) {
            ASSERT_NOT_REACHED();
            return;
        }

        databaseSet->remove(database);

        LOG(StorageAPI, "Removed open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);

        if (!databaseSet->isEmpty())
            return;

        nameMap->remove(name);
        delete databaseSet;

        if (!nameMap->isEmpty())
            return;

        m_openDatabaseMap->remove(database->securityOrigin());
        delete nameMap;
    }

    Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
    originQuotaManager().removeOrigin(database->securityOrigin());
}

void DatabaseTracker::getOpenDatabases(SecurityOrigin* origin, const String& name, HashSet<RefPtr<Database> >* databases)
{
    MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
    if (!m_openDatabaseMap)
        return;

    DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
    if (!nameMap)
        return;

    DatabaseSet* databaseSet = nameMap->get(name);
    if (!databaseSet)
        return;

    for (DatabaseSet::iterator it = databaseSet->begin(); it != databaseSet->end(); ++it)
        databases->add(*it);
}

unsigned long long DatabaseTracker::usageForOriginNoLock(SecurityOrigin* origin)
{
    ASSERT(!originQuotaManager().tryLock());
    ASSERT(WebThreadIsLockedOrDisabled());

    // Use the OriginQuotaManager mechanism to calculate the usage
    if (originQuotaManager().tracksOrigin(origin))
        return originQuotaManager().diskUsage(origin);

    // If the OriginQuotaManager doesn't track this origin already, prime it to do so
    originQuotaManager().trackOrigin(origin);

    Vector<String> names;
    databaseNamesForOriginNoLock(origin, names);

    for (unsigned i = 0; i < names.size(); ++i)
        originQuotaManager().addDatabase(origin, names[i], fullPathForDatabaseNoLock(origin, names[i], false));

    if (!originQuotaManager().tracksOrigin(origin))
        return 0;
    return originQuotaManager().diskUsage(origin);
}

unsigned long long DatabaseTracker::usageForOrigin(SecurityOrigin* origin)
{
    MutexLocker lockDatabase(m_databaseGuard);
    Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
    return usageForOriginNoLock(origin);
}

unsigned long long DatabaseTracker::quotaForOriginNoLock(SecurityOrigin* origin)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(m_quotaMap);
    return m_quotaMap->get(origin);
}

unsigned long long DatabaseTracker::quotaForOrigin(SecurityOrigin* origin)
{
    MutexLocker lockDatabase(m_databaseGuard);
    return quotaForOriginNoLock(origin);
}

void DatabaseTracker::setQuota(SecurityOrigin* origin, unsigned long long quota)
{
    MutexLocker lockDatabase(m_databaseGuard);

    if (quotaForOriginNoLock(origin) == quota)
        return;

    openTrackerDatabase(true);
    if (!m_database.isOpen())
        return;
    
    bool insertedNewOrigin = false;

    if (!m_quotaMap->contains(origin)) {
        SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
        if (statement.prepare() != SQLResultOk) {
            LOG_ERROR("Unable to establish origin %s in the tracker", origin->databaseIdentifier().ascii().data());
        } else {
            statement.bindText(1, origin->databaseIdentifier());
            statement.bindInt64(2, quota);

            if (statement.step() != SQLResultDone)
                LOG_ERROR("Unable to establish origin %s in the tracker", origin->databaseIdentifier().ascii().data());
            else
                insertedNewOrigin = true;
        }
    } else {
        SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
        bool error = statement.prepare() != SQLResultOk;
        if (!error) {
            statement.bindInt64(1, quota);
            statement.bindText(2, origin->databaseIdentifier());

            error = !statement.executeCommand();
        }

        if (error)
            LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin->databaseIdentifier().ascii().data());
    }

    // FIXME: Is it really OK to update the quota in memory if we failed to update it on disk?
    m_quotaMap->set(origin->threadsafeCopy(), quota);

    if (m_client)
    {
        if (insertedNewOrigin)
            m_client->dispatchDidAddNewOrigin(origin);
        m_client->dispatchDidModifyOrigin(origin);
    }
}

bool DatabaseTracker::addDatabase(SecurityOrigin* origin, const String& name, const String& path)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(m_quotaMap);
    ASSERT(WebThreadIsLockedOrDisabled());
    openTrackerDatabase(true);
    if (!m_database.isOpen())
        return false;

    // New database should never be added until the origin has been established
    ASSERT(hasEntryForOriginNoLock(origin));

    SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");

    if (statement.prepare() != SQLResultOk)
        return false;

    statement.bindText(1, origin->databaseIdentifier());
    statement.bindText(2, name);
    statement.bindText(3, path);

    if (!statement.executeCommand()) {
        LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin->databaseIdentifier().ascii().data(), m_database.lastErrorMsg());
        return false;
    }

    if (m_client)
        m_client->dispatchDidModifyOrigin(origin);

    return true;
}

void DatabaseTracker::deleteAllDatabases()
{
    Vector<RefPtr<SecurityOrigin> > originsCopy;
    origins(originsCopy);

    for (unsigned i = 0; i < originsCopy.size(); ++i)
        deleteOrigin(originsCopy[i].get());
}

// It is the caller's responsibility to make sure that nobody is trying to create, delete, open, or close databases in this origin while the deletion is
// taking place.
bool DatabaseTracker::deleteOrigin(SecurityOrigin* origin)
{
    Vector<String> databaseNames;
    {
        MutexLocker lockDatabase(m_databaseGuard);
        openTrackerDatabase(false);
        if (!m_database.isOpen())
            return false;

        if (!databaseNamesForOriginNoLock(origin, databaseNames)) {
            LOG_ERROR("Unable to retrieve list of database names for origin %s", origin->databaseIdentifier().ascii().data());
            return false;
        }
        if (!canDeleteOrigin(origin)) {
            LOG_ERROR("Tried to delete an origin (%s) while either creating database in it or already deleting it", origin->databaseIdentifier().ascii().data());
            ASSERT(false);
            return false;
        }
        recordDeletingOrigin(origin);
    }

    // We drop the lock here because holding locks during a call to deleteDatabaseFile will deadlock.
    for (unsigned i = 0; i < databaseNames.size(); ++i) {
        if (!deleteDatabaseFile(origin, databaseNames[i])) {
            // Even if the file can't be deleted, we want to try and delete the rest, don't return early here.
            LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin->databaseIdentifier().ascii().data());
        }
    }

    {
        MutexLocker lockDatabase(m_databaseGuard);
        doneDeletingOrigin(origin);

        SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
        if (statement.prepare() != SQLResultOk) {
            LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
            return false;
        }

        statement.bindText(1, origin->databaseIdentifier());

        if (!statement.executeCommand()) {
            LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
            return false;
        }

        SQLiteStatement originStatement(m_database, "DELETE FROM Origins WHERE origin=?");
        if (originStatement.prepare() != SQLResultOk) {
            LOG_ERROR("Unable to prepare deletion of origin %s from tracker", origin->databaseIdentifier().ascii().data());
            return false;
        }

        originStatement.bindText(1, origin->databaseIdentifier());

        if (!originStatement.executeCommand()) {
            LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
            return false;
        }

        SQLiteFileSystem::deleteEmptyDatabaseDirectory(originPath(origin));

        RefPtr<SecurityOrigin> originPossiblyLastReference = origin;
        m_quotaMap->remove(origin);

        {
            Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
            originQuotaManager().removeOrigin(origin);
        }

        // If we removed the last origin, do some additional deletion.
        if (m_quotaMap->isEmpty()) {
            if (m_database.isOpen())
                m_database.close();
           SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
           SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_databaseDirectoryPath);
        }

        if (m_client) {
            m_client->dispatchDidModifyOrigin(origin);
            m_client->dispatchDidDeleteDatabaseOrigin();
            for (unsigned i = 0; i < databaseNames.size(); ++i)
                m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
        }
    }
    return true;
}

bool DatabaseTracker::canCreateDatabase(SecurityOrigin *origin, const String& name)
{
    ASSERT(!m_databaseGuard.tryLock());
    // Can't create a database while someone else is deleting it; there's a risk of leaving untracked database debris on the disk.
    return !deletingDatabase(origin, name) && !deletingOrigin(origin);
}

void DatabaseTracker::recordCreatingDatabase(SecurityOrigin *origin, const String& name)
{
    ASSERT(!m_databaseGuard.tryLock());
    NameCountMap* nameMap = m_beingCreated.get(origin);
    if (!nameMap) {
        nameMap = new NameCountMap();
        m_beingCreated.set(origin->threadsafeCopy(), nameMap);
    }
    long count = nameMap->get(name);
    nameMap->set(name.threadsafeCopy(), count + 1);
}

void DatabaseTracker::doneCreatingDatabase(SecurityOrigin *origin, const String& name)
{
    ASSERT(!m_databaseGuard.tryLock());
    NameCountMap* nameMap = m_beingCreated.get(origin);
    if (nameMap) {
        long count = nameMap->get(name);
        ASSERT(count > 0);
        if (count <= 1) {
            nameMap->remove(name);
            if (nameMap->isEmpty()) {
                m_beingCreated.remove(origin);
                delete nameMap;
            }
        } else
            nameMap->set(name, count - 1);
    } else
        ASSERT(false);
}

bool DatabaseTracker::creatingDatabase(SecurityOrigin *origin, const String& name)
{
    ASSERT(!m_databaseGuard.tryLock());
    NameCountMap* nameMap = m_beingCreated.get(origin);
    return nameMap && nameMap->get(name);
}

bool DatabaseTracker::canDeleteDatabase(SecurityOrigin *origin, const String& name)
{
    ASSERT(!m_databaseGuard.tryLock());
    return !creatingDatabase(origin, name) && !deletingDatabase(origin, name);
}

void DatabaseTracker::recordDeletingDatabase(SecurityOrigin *origin, const String& name)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(canDeleteDatabase(origin, name));
    NameSet* nameSet = m_beingDeleted.get(origin);
    if (!nameSet) {
        nameSet = new NameSet();
        m_beingDeleted.set(origin->threadsafeCopy(), nameSet);
    }
    ASSERT(!nameSet->contains(name));
    nameSet->add(name.threadsafeCopy());
}

void DatabaseTracker::doneDeletingDatabase(SecurityOrigin *origin, const String& name)
{
    ASSERT(!m_databaseGuard.tryLock());
    NameSet* nameSet = m_beingDeleted.get(origin);
    if (nameSet) {
        ASSERT(nameSet->contains(name));
        nameSet->remove(name);
        if (nameSet->isEmpty()) {
            m_beingDeleted.remove(origin);
            delete nameSet;
        }
    } else {
        ASSERT(false);
    }
}

bool DatabaseTracker::deletingDatabase(SecurityOrigin *origin, const String& name)
{
    ASSERT(!m_databaseGuard.tryLock());
    NameSet* nameSet = m_beingDeleted.get(origin);
    return nameSet && nameSet->contains(name);
}

bool DatabaseTracker::canDeleteOrigin(SecurityOrigin *origin)
{
    ASSERT(!m_databaseGuard.tryLock());
    return !(deletingOrigin(origin) || m_beingCreated.get(origin));
}

bool DatabaseTracker::deletingOrigin(SecurityOrigin *origin)
{
    ASSERT(!m_databaseGuard.tryLock());
    return m_originsBeingDeleted.contains(origin);
}

void DatabaseTracker::recordDeletingOrigin(SecurityOrigin *origin)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(!deletingOrigin(origin));
    m_originsBeingDeleted.add(origin->threadsafeCopy());
}

void DatabaseTracker::doneDeletingOrigin(SecurityOrigin *origin)
{
    ASSERT(!m_databaseGuard.tryLock());
    ASSERT(deletingOrigin(origin));
    m_originsBeingDeleted.remove(origin);
}

bool DatabaseTracker::deleteDatabase(SecurityOrigin* origin, const String& name)
{
    {
        MutexLocker lockDatabase(m_databaseGuard);
        openTrackerDatabase(false);
        if (!m_database.isOpen())
            return false;

        if (!canDeleteDatabase(origin, name)) {
            ASSERT(FALSE);
            return false;
        }
        recordDeletingDatabase(origin, name);
    }

    // We drop the lock here because holding locks during a call to deleteDatabaseFile will deadlock.
    if (!deleteDatabaseFile(origin, name)) {
        LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin->databaseIdentifier().ascii().data());
        MutexLocker lockDatabase(m_databaseGuard);
        doneDeletingDatabase(origin, name);
        return false;
    }

    MutexLocker lockDatabase(m_databaseGuard);

    SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
    if (statement.prepare() != SQLResultOk) {
        LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin->databaseIdentifier().ascii().data());
        doneDeletingDatabase(origin, name);
        return false;
    }

    statement.bindText(1, origin->databaseIdentifier());
    statement.bindText(2, name);

    if (!statement.executeCommand()) {
        LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin->databaseIdentifier().ascii().data());
        doneDeletingDatabase(origin, name);
        return false;
    }

    {
        Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
        originQuotaManager().removeDatabase(origin, name);
    }

    if (m_client) {
        m_client->dispatchDidModifyOrigin(origin);
        m_client->dispatchDidModifyDatabase(origin, name);
        m_client->dispatchDidDeleteDatabase();
    }
    doneDeletingDatabase(origin, name);
    
    return true;
}

// deleteDatabaseFile has to release locks between looking up the list of databases to close and closing them.  While this is in progress, the caller
// is responsible for making sure no new databases are opened in the file to be deleted.
bool DatabaseTracker::deleteDatabaseFile(SecurityOrigin* origin, const String& name)
{
    String fullPath = fullPathForDatabase(origin, name, false);
    if (fullPath.isEmpty())
        return true;

#ifndef NDEBUG
    {
        MutexLocker lockDatabase(m_databaseGuard);
        ASSERT(deletingDatabase(origin, name) || deletingOrigin(origin));
    }
#endif

    Vector<RefPtr<Database> > deletedDatabases;

    // Make sure not to hold the any locks when calling
    // Database::markAsDeletedAndClose(), since that can cause a deadlock
    // during the synchronous DatabaseThread call it triggers.
    {
        MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
        if (m_openDatabaseMap) {
            // There are some open databases, lets check if they are for this origin.
            DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
            if (nameMap && nameMap->size()) {
                // There are some open databases for this origin, let's check
                // if they are this database by name.
                DatabaseSet* databaseSet = nameMap->get(name);
                if (databaseSet && databaseSet->size()) {
                    // We have some database open with this name. Mark them as deleted.
                    DatabaseSet::const_iterator end = databaseSet->end();
                    for (DatabaseSet::const_iterator it = databaseSet->begin(); it != end; ++it)
                        deletedDatabases.append(*it);
                }
            }
        }
    }

    for (unsigned i = 0; i < deletedDatabases.size(); ++i)
        deletedDatabases[i]->markAsDeletedAndClose();

    // On the phone, other background processes may still be accessing this database.  Deleting the database directly
    // would nuke the POSIX file locks, potentially causing Safari/WebApp to corrupt the new db if it's running in the background.
    // We'll instead truncate the database file to 0 bytes.  If another process is operating on this same database file after
    // the truncation, it should get an error since the database file is no longer valid.  When Safari is launched
    // next time, it'll go through the database files and clean up any zero-bytes ones.
    SQLiteDatabase database;
    if (database.open(fullPath))
        return SQLiteFileSystem::truncateDatabaseFile(database.sqlite3Handle());
    return false;
}
    
void DatabaseTracker::originsDidChange()
{
    // This is called when origins have been added or deleted by another app.
    ASSERT(WebThreadIsLockedOrDisabled());
    
    // Keep track of the origins that have been added or deleted.
    Vector<RefPtr<SecurityOrigin> > changedOrigins;
    
    {
        MutexLocker lockDatabase(m_databaseGuard);

        openTrackerDatabase(false);
        if (!m_database.isOpen())
            return;

        if (!m_quotaMap)
            return;
        
        // Collect all the origins into a HashSet.
        typedef HashSet<RefPtr<SecurityOrigin>, SecurityOriginHash> OriginSet;
        OriginSet origins;
        QuotaMap::const_iterator quotaMapEnd = m_quotaMap->end();
        for (QuotaMap::const_iterator quotaMapIt = m_quotaMap->begin(); quotaMapIt != quotaMapEnd; ++quotaMapIt)
            origins.add(quotaMapIt->first);
        
        // Get the up-to-date list of origins.
        SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
        if (statement.prepare() != SQLResultOk)
            return;
        
        int result;
        while ((result = statement.step()) == SQLResultRow) {
            RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromDatabaseIdentifier(statement.getColumnText(0));
            // Check if the origin exists in the current quota map.  If so, remove it from the HashSet now that
            // the origin is accounted for.  If not, add the origin to the quota map.
            if (m_quotaMap->contains(origin)) {
                ASSERT(origins.contains(origin));
                origins.remove(origin);
            } else {
                m_quotaMap->set(origin, statement.getColumnInt64(1));
                changedOrigins.append(origin);
            }
        }
        
        if (result != SQLResultDone)
            LOG_ERROR("Failed to read in all origins from the database");
        else {
            // Whatever is left in the origins HashSet are no longer tracked by the database.  Remove them from
            // the quota map and the origin quota manager.
            OriginSet::const_iterator originEnd = origins.end();
            for (OriginSet::const_iterator originIt = origins.begin(); originIt != originEnd; ++originIt) {
                RefPtr<SecurityOrigin> origin = *originIt;
                
                // Only delete origin if it's empty.
                Vector<String> databaseNames;
                if (!databaseNamesForOriginNoLock(origin.get(), databaseNames) || !databaseNames.isEmpty())
                    continue;
                
                m_quotaMap->remove(origin);

                {
                    Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
                    originQuotaManager().removeOrigin(origin.get());
                }
                
                changedOrigins.append(origin);
            }
        }
        
        // If we removed the last origin, do some additional deletion.
        if (m_quotaMap->isEmpty() && m_database.isOpen())
            m_database.close();
    }
    
    if (!m_client)
        return;
    
    for (size_t i = 0; i < changedOrigins.size(); ++i)
        m_client->dispatchDidModifyOrigin(changedOrigins[i].get());
}

void DatabaseTracker::removeDeletedOpenedDatabases()
{
    // This is called when another app has deleted a database.  Go through all opened databases in this
    // tracker and close any that's no longer being tracked in the database.
    ASSERT(WebThreadIsLockedOrDisabled());
    
    {
        // Acquire the lock before calling openTrackerDatabase.
        MutexLocker lockDatabase(m_databaseGuard);
        openTrackerDatabase(false);
    }

    if (!m_database.isOpen())
        return;
    
    // Keep track of which opened databases have been deleted.
    Vector<RefPtr<Database> > deletedDatabases;
    typedef HashMap<RefPtr<SecurityOrigin>, Vector<String> > DeletedDatabaseMap;
    DeletedDatabaseMap deletedDatabaseMap;
    
    // Make sure not to hold the m_openDatabaseMapGuard mutex when calling
    // Database::markAsDeletedAndClose(), since that can cause a deadlock
    // during the synchronous DatabaseThread call it triggers.
    {
        MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
        if (m_openDatabaseMap) {
            DatabaseOriginMap::const_iterator originMapEnd = m_openDatabaseMap->end();
            for (DatabaseOriginMap::const_iterator originMapIt = m_openDatabaseMap->begin(); originMapIt != originMapEnd; ++originMapIt) {
                RefPtr<SecurityOrigin> origin = originMapIt->first;
                DatabaseNameMap* databaseNameMap = originMapIt->second;
                Vector<String> deletedDatabaseNamesForThisOrigin;

                // Loop through all opened databases in this origin.  Get the current database file path of each database and see if
                // it still matches the path stored in the opened database object.
                DatabaseNameMap::const_iterator dbNameMapEnd = databaseNameMap->end();
                for (DatabaseNameMap::const_iterator dbNameMapIt = databaseNameMap->begin(); dbNameMapIt != dbNameMapEnd; ++dbNameMapIt) {
                    String databaseName = dbNameMapIt->first;
                    String databaseFileName;
                    SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
                    if (statement.prepare() == SQLResultOk) {
                        statement.bindText(1, origin->databaseIdentifier());
                        statement.bindText(2, databaseName);
                        if (statement.step() == SQLResultRow)
                            databaseFileName = statement.getColumnText(0);
                        statement.finalize();
                    }
                    
                    bool foundDeletedDatabase = false;
                    DatabaseSet* databaseSet = dbNameMapIt->second;
                    DatabaseSet::const_iterator dbEnd = databaseSet->end();
                    for (DatabaseSet::const_iterator dbIt = databaseSet->begin(); dbIt != dbEnd; ++dbIt) {
                        Database* db = *dbIt;
                        
                        // We are done if this database has already been marked as deleted.
                        if (db->deleted())
                            continue;
                        
                        // If this database has been deleted or if its database file no longer matches the current version, this database is no longer valid and it should be marked as deleted.
                        if (databaseFileName.isNull() || databaseFileName != pathGetFileName(db->fileName())) {
                            deletedDatabases.append(db);
                            foundDeletedDatabase = true;
                        }
                    }
                    
                    // If the database no longer exists, we should remember to remove it from the OriginQuotaManager later.
                    if (foundDeletedDatabase && databaseFileName.isNull())
                        deletedDatabaseNamesForThisOrigin.append(databaseName);
                }
                
                if (!deletedDatabaseNamesForThisOrigin.isEmpty())
                    deletedDatabaseMap.set(origin, deletedDatabaseNamesForThisOrigin);
            }
        }
    }
    
    for (unsigned i = 0; i < deletedDatabases.size(); ++i)
        deletedDatabases[i]->markAsDeletedAndClose();
    
    DeletedDatabaseMap::const_iterator end = deletedDatabaseMap.end();
    for (DeletedDatabaseMap::const_iterator it = deletedDatabaseMap.begin(); it != end; ++it) {
        SecurityOrigin* origin = it->first.get();
        if (m_client)
            m_client->dispatchDidModifyOrigin(origin);
        
        const Vector<String>& databaseNames = it->second;
        for (unsigned i = 0; i < databaseNames.size(); ++i) {
            {
                Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
                originQuotaManager().removeDatabase(origin, databaseNames[i]);
            }
            
            if (m_client)
                m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
        }        
    }
}
    
static bool isZeroByteFile(const String& path)
{
    long long size = 0;
    return getFileSize(path, size) && !size;
}
    
bool DatabaseTracker::deleteDatabaseFileIfEmpty(const String& path)
{
    if (!isZeroByteFile(path))
        return false;
    
    SQLiteDatabase database;
    if (!database.open(path))
        return false;
    
    // Specify that we want the exclusive locking mode, so after the next read,
    // we'll be holding the lock to this database file.
    SQLiteStatement lockStatement(database, "PRAGMA locking_mode=EXCLUSIVE;");
    if (lockStatement.prepare() != SQLResultOk)
        return false;
    int result = lockStatement.step();
    if (result != SQLResultRow && result != SQLResultDone)
        return false;
    lockStatement.finalize();

    // Every sqlite database has a sqlite_master table that contains the schema for the database.
    // http://www.sqlite.org/faq.html#q7
    SQLiteStatement readStatement(database, "SELECT * FROM sqlite_master LIMIT 1;");    
    if (readStatement.prepare() != SQLResultOk)
        return false;
    // We shouldn't expect any result.
    if (readStatement.step() != SQLResultDone)
        return false;
    readStatement.finalize();
    
    // At this point, we hold the exclusive lock to this file.  Double-check again to make sure
    // it's still zero bytes.
    if (!isZeroByteFile(path))
        return false;
    
    return SQLiteFileSystem::deleteDatabaseFile(path);
}

Mutex& DatabaseTracker::openDatabaseMutex()
{
    DEFINE_STATIC_LOCAL(Mutex, mutex, ());
    return mutex;
}

void DatabaseTracker::emptyDatabaseFilesRemovalTaskWillBeScheduled()
{
    // Lock the database from opening any database until we are done with scanning the file system for
    // zero byte database files to remove.
    openDatabaseMutex().lock();
}

void DatabaseTracker::emptyDatabaseFilesRemovalTaskDidFinish()
{
    openDatabaseMutex().unlock();
}

void DatabaseTracker::setDatabasesPaused(bool paused)
{
    ASSERT(WebThreadIsLockedOrDisabled() || WebThreadIsCurrent());

    MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
    if (!m_openDatabaseMap)
        return;

    // This walking is - sadly - the only reliable way to get at each open database thread.
    // This will be cleaner once <rdar://problem/5680441> or some other DB thread consolidation takes place.
    HashSet<DatabaseThread*> handledThreads;

    DatabaseOriginMap::iterator i = m_openDatabaseMap.get()->begin();
    DatabaseOriginMap::iterator end = m_openDatabaseMap.get()->end();
    
    for (; i != end; ++i) {
        DatabaseNameMap* databaseNameMap = i->second;
        DatabaseNameMap::iterator j = databaseNameMap->begin();
        DatabaseNameMap::iterator dbNameMapEnd = databaseNameMap->end();
        for (; j != dbNameMapEnd; ++j) {
            DatabaseSet* databaseSet = j->second;
            DatabaseSet::iterator k = databaseSet->begin();
            DatabaseSet::iterator dbSetEnd = databaseSet->end();
            for (; k != dbSetEnd; ++k) {
                // If the database is not open it could be in the process of shutting down but
                // has not infomed this thread yet.
                if (!(*k)->opened())
                    continue;
                ScriptExecutionContext* context = (*k)->scriptExecutionContext();
                if (context) {
                    DatabaseThread* thread = context->databaseThread();
                    if (!handledThreads.contains(thread)) {
                        handledThreads.add(thread);
                        thread->setPaused(paused);
                    }
                }
            }
        }
    }
}


void DatabaseTracker::setClient(DatabaseTrackerClient* client)
{
    m_client = client;
}

static Mutex& notificationMutex()
{
    DEFINE_STATIC_LOCAL(Mutex, mutex, ());
    return mutex;
}

typedef Vector<pair<RefPtr<SecurityOrigin>, String> > NotificationQueue;

static NotificationQueue& notificationQueue()
{
    DEFINE_STATIC_LOCAL(NotificationQueue, queue, ());
    return queue;
}

void DatabaseTracker::scheduleNotifyDatabaseChanged(SecurityOrigin* origin, const String& name)
{
    MutexLocker locker(notificationMutex());

    notificationQueue().append(pair<RefPtr<SecurityOrigin>, String>(origin->threadsafeCopy(), name.crossThreadString()));
    scheduleForNotification();
}

static bool notificationScheduled = false;

void DatabaseTracker::scheduleForNotification()
{
    ASSERT(!notificationMutex().tryLock());

    if (!notificationScheduled) {
        callOnMainThread(DatabaseTracker::notifyDatabasesChanged, 0);
        notificationScheduled = true;
    }
}

void DatabaseTracker::notifyDatabasesChanged(void*)
{
    // Note that if DatabaseTracker ever becomes non-singleton, we'll have to amend this notification
    // mechanism to include which tracker the notification goes out on as well.
    DatabaseTracker& theTracker(tracker());

    NotificationQueue notifications;
    {
        MutexLocker locker(notificationMutex());

        notifications.swap(notificationQueue());

        notificationScheduled = false;
    }

    if (!theTracker.m_client)
        return;

    for (unsigned i = 0; i < notifications.size(); ++i)
        theTracker.m_client->dispatchDidModifyDatabase(notifications[i].first.get(), notifications[i].second);
}


} // namespace WebCore
#endif