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"

#include "ChromeClient.h"
#include "Database.h"
#include "DatabaseTrackerClient.h"
#include "Document.h"
#include "FileSystem.h"
#include "Logging.h"
#include "OriginQuotaManager.h"
#include "Page.h"
#include "SecurityOrigin.h"
#include "SecurityOriginHash.h"
#include "SQLiteStatement.h"

using namespace std;

namespace WebCore {

OriginQuotaManager& DatabaseTracker::originQuotaManager()
{
    populateOrigins();
    ASSERT(m_quotaManager);
    return *m_quotaManager;
}

DatabaseTracker& DatabaseTracker::tracker()
{
    static DatabaseTracker tracker;
    return tracker;
}

DatabaseTracker::DatabaseTracker()
    : m_client(0)
    , m_proposedDatabase(0)
#ifndef NDEBUG
    , m_thread(currentThread())
#endif
{
}

void DatabaseTracker::setDatabaseDirectoryPath(const String& path)
{
    ASSERT(currentThread() == m_thread);
    ASSERT(!m_database.isOpen());
    m_databaseDirectoryPath = path;
}

const String& DatabaseTracker::databaseDirectoryPath() const
{
    ASSERT(currentThread() == m_thread);
    return m_databaseDirectoryPath;
}

String DatabaseTracker::trackerDatabasePath() const
{
    ASSERT(currentThread() == m_thread);
    if (m_databaseDirectoryPath.isEmpty())
        return String();
    return pathByAppendingComponent(m_databaseDirectoryPath, "Databases.db");
}

void DatabaseTracker::openTrackerDatabase(bool createIfDoesNotExist)
{
    ASSERT(currentThread() == m_thread);

    if (m_database.isOpen())
        return;

    String databasePath = trackerDatabasePath();
    if (databasePath.isEmpty())
        return;

    if (!createIfDoesNotExist && !fileExists(databasePath))
        return;

    makeAllDirectories(m_databaseDirectoryPath);
    if (!m_database.open(databasePath)) {
        // FIXME: What do do here?
        return;
    }
    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
        }
    }
    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
        }
    }
}

bool DatabaseTracker::canEstablishDatabase(Document* document, const String& name, const String& displayName, unsigned long estimatedSize)
{
    ASSERT(currentThread() == m_thread);

    // Populate the origins before we establish a database; this guarantees that quotaForOrigin
    // can run on the database thread later.
    populateOrigins();

    SecurityOrigin* origin = document->securityOrigin();

    // Since we're imminently opening a database within this Document's origin, make sure this origin is being tracked by the QuotaTracker
    // by fetching it's current usage now
    unsigned long long usage = usageForOrigin(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.
    unsigned long long requirement = usage + max(1UL, estimatedSize);
    if (requirement < usage)
        return false; // If the estimated size is so big it causes an overflow, don't allow creation.
    if (requirement <= quotaForOrigin(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.
    Page* page = document->page();
    if (!page)
        return false;
    pair<SecurityOrigin*, DatabaseDetails> details(origin, DatabaseDetails(name, displayName, estimatedSize, 0));
    m_proposedDatabase = &details;
    page->chrome()->client()->exceededDatabaseQuota(document->frame(), name);
    m_proposedDatabase = 0;

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

bool DatabaseTracker::hasEntryForOrigin(SecurityOrigin* origin)
{
    ASSERT(currentThread() == m_thread);
    populateOrigins();
    MutexLocker lockQuotaMap(m_quotaMapGuard);
    return m_quotaMap->contains(origin);
}

bool DatabaseTracker::hasEntryForDatabase(SecurityOrigin* origin, const String& databaseIdentifier)
{
    ASSERT(currentThread() == m_thread);
    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->stringIdentifier());
    statement.bindText(2, databaseIdentifier);

    return statement.step() == SQLResultRow;
}

String DatabaseTracker::originPath(SecurityOrigin* origin) const
{
    ASSERT(currentThread() == m_thread);
    if (m_databaseDirectoryPath.isEmpty())
        return String();
    return pathByAppendingComponent(m_databaseDirectoryPath, origin->stringIdentifier());
}

String DatabaseTracker::fullPathForDatabase(SecurityOrigin* origin, const String& name, bool createIfNotExists)
{
    ASSERT(currentThread() == m_thread);

    if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name)
        return String();

    String originIdentifier = origin->stringIdentifier();
    String originPath = this->originPath(origin);
    
    // Make sure the path for this SecurityOrigin exists
    if (createIfNotExists && !makeAllDirectories(originPath))
        return String();
    
    // See if we have a path for this database yet
    openTrackerDatabase(false);
    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 pathByAppendingComponent(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", origin->stringIdentifier().ascii().data(), name.ascii().data());
        return String();
    }
    statement.finalize();
    
    SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';");

    // FIXME: More informative error handling here, even though these steps should never fail
    if (sequenceStatement.prepare() != SQLResultOk)
        return String();
    result = sequenceStatement.step();

    // This has a range of 2^63 and starts at 0 for every time a user resets Safari -
    // I can't imagine it'd over overflow
    int64_t seq = 0;
    if (result == SQLResultRow) {
        seq = sequenceStatement.getColumnInt64(0);
    } else if (result != SQLResultDone)
        return String();
    sequenceStatement.finalize();

    String filename;
    do {
        ++seq;
        filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq));
    } while (fileExists(filename));

    if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
        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
    {
        Locker<OriginQuotaManager> locker(originQuotaManager());
        if (originQuotaManager().tracksOrigin(origin))
            originQuotaManager().addDatabase(origin, name, filename);
    }
    
    return filename;
}

void DatabaseTracker::populateOrigins()
{
    if (m_quotaMap)
        return;

    ASSERT(currentThread() == m_thread);

    m_quotaMap.set(new QuotaMap);
    m_quotaManager.set(new OriginQuotaManager);

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

    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::createFromIdentifier(statement.getColumnText(0));
        m_quotaMap->set(origin.get(), statement.getColumnInt64(1));
    }

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

void DatabaseTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
{
    ASSERT(currentThread() == m_thread);
    populateOrigins();
    MutexLocker lockQuotaMap(m_quotaMapGuard);
    copyKeysToVector(*m_quotaMap, result);
}

bool DatabaseTracker::databaseNamesForOrigin(SecurityOrigin* origin, Vector<String>& resultVector)
{
    ASSERT(currentThread() == m_thread);
    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->stringIdentifier());

    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->stringIdentifier().ascii().data());
        return false;
    }

    return true;
}

DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, SecurityOrigin* origin)
{
    ASSERT(currentThread() == m_thread);

    if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name)
        return m_proposedDatabase->second;

    String originIdentifier = origin->stringIdentifier();

    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();
    }
    
    return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin));
}

void DatabaseTracker::setDatabaseDetails(SecurityOrigin* origin, const String& name, const String& displayName, unsigned long estimatedSize)
{
    ASSERT(currentThread() == m_thread);

    String originIdentifier = origin->stringIdentifier();
    int64_t guid = 0;
    
    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)
{
    ASSERT(currentThread() == m_thread);
    String path = fullPathForDatabase(origin, name, false);
    if (path.isEmpty())
        return 0;
        
    long long size;
    return getFileSize(path, size) ? size : 0;
}

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

    MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);

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

    RefPtr<SecurityOrigin> origin(database->securityOriginCopy());
    String name(database->stringIdentifier());

    DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
    if (!nameMap) {
        nameMap = new DatabaseNameMap;
        m_openDatabaseMap->set(origin, nameMap);
    }

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

    databaseSet->add(database);

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

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

    MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);

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

    RefPtr<SecurityOrigin> origin(database->securityOriginCopy());
    String name(database->stringIdentifier());

    DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
    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(origin);
    delete nameMap;
}

unsigned long long DatabaseTracker::usageForOrigin(SecurityOrigin* origin)
{
    ASSERT(currentThread() == m_thread);
    Locker<OriginQuotaManager> locker(originQuotaManager());

    // 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;
    databaseNamesForOrigin(origin, names);

    for (unsigned i = 0; i < names.size(); ++i)
        originQuotaManager().addDatabase(origin, names[i], fullPathForDatabase(origin, names[i], false));
    
    if (!originQuotaManager().tracksOrigin(origin))
        return 0;
    return originQuotaManager().diskUsage(origin);
}

unsigned long long DatabaseTracker::quotaForOrigin(SecurityOrigin* origin)
{
    ASSERT(currentThread() == m_thread || m_quotaMap);
    populateOrigins();
    MutexLocker lockQuotaMap(m_quotaMapGuard);
    return m_quotaMap->get(origin);
}

void DatabaseTracker::setQuota(SecurityOrigin* origin, unsigned long long quota)
{
    ASSERT(currentThread() == m_thread);
    if (quotaForOrigin(origin) == quota)
        return;

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

    {
        MutexLocker lockQuotaMap(m_quotaMapGuard);

        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->stringIdentifier().ascii().data());
            } else {
                statement.bindText(1, origin->stringIdentifier());
                statement.bindInt64(2, quota);

                if (statement.step() != SQLResultDone)
                    LOG_ERROR("Unable to establish origin %s in the tracker", origin->stringIdentifier().ascii().data());
            }
        } 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->stringIdentifier());

                error = !statement.executeCommand();
            }

            if (error)
                LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin->stringIdentifier().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, quota);
    }

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

bool DatabaseTracker::addDatabase(SecurityOrigin* origin, const String& name, const String& path)
{
    ASSERT(currentThread() == m_thread);
    openTrackerDatabase(true);
    if (!m_database.isOpen())
        return false;
        
    // New database should never be added until the origin has been established
    ASSERT(hasEntryForOrigin(origin));

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

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

    statement.bindText(1, origin->stringIdentifier());
    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->stringIdentifier().ascii().data(), m_database.lastErrorMsg());
        return false;
    }
    
    if (m_client)
        m_client->dispatchDidModifyOrigin(origin);
    
    return true;
}

void DatabaseTracker::deleteAllDatabases()
{
    ASSERT(currentThread() == m_thread);

    Vector<RefPtr<SecurityOrigin> > originsCopy;
    origins(originsCopy);

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

void DatabaseTracker::deleteOrigin(SecurityOrigin* origin)
{
    ASSERT(currentThread() == m_thread);
    openTrackerDatabase(false);
    if (!m_database.isOpen())
        return;

    Vector<String> databaseNames;
    if (!databaseNamesForOrigin(origin, databaseNames)) {
        LOG_ERROR("Unable to retrieve list of database names for origin %s", origin->stringIdentifier().ascii().data());
        return;
    }
    
    for (unsigned i = 0; i < databaseNames.size(); ++i) {
        if (!deleteDatabaseFile(origin, databaseNames[i])) {
            LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin->stringIdentifier().ascii().data());
            return;
        }
    }
    
    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->stringIdentifier().ascii().data());
        return;
    }
        
    statement.bindText(1, origin->stringIdentifier());
    
    if (!statement.executeCommand()) {
        LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->stringIdentifier().ascii().data());
        return;
    }
    
    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->stringIdentifier().ascii().data());
        return;
    }

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

    deleteEmptyDirectory(originPath(origin));

    RefPtr<SecurityOrigin> originPossiblyLastReference = origin;
    {
        MutexLocker lockQuotaMap(m_quotaMapGuard);
        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();
            deleteFile(trackerDatabasePath());
            deleteEmptyDirectory(m_databaseDirectoryPath);
        }
    }

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

void DatabaseTracker::deleteDatabase(SecurityOrigin* origin, const String& name)
{
    ASSERT(currentThread() == m_thread);
    openTrackerDatabase(false);
    if (!m_database.isOpen())
        return;

    if (!deleteDatabaseFile(origin, name)) {
        LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin->stringIdentifier().ascii().data());
        return;
    }
    
    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->stringIdentifier().ascii().data());
        return;
    }
        
    statement.bindText(1, origin->stringIdentifier());
    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->stringIdentifier().ascii().data());
        return;
    }
    
    {
        Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
        originQuotaManager().removeDatabase(origin, name);
    }
    
    if (m_client) {
        m_client->dispatchDidModifyOrigin(origin);
        m_client->dispatchDidModifyDatabase(origin, name);
    }
}

bool DatabaseTracker::deleteDatabaseFile(SecurityOrigin* origin, const String& name)
{
    ASSERT(currentThread() == m_thread);
    String fullPath = fullPathForDatabase(origin, name, false);
    if (fullPath.isEmpty())
        return true;

    Vector<RefPtr<Database> > deletedDatabases;

    // 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) {
            // 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, lets 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();

    return deleteFile(fullPath);
}

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

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

static Vector<pair<SecurityOrigin*, String> >& notificationQueue()
{
    static Vector<pair<SecurityOrigin*, String> > queue;
    return queue;
}

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

    notificationQueue().append(pair<SecurityOrigin*, String>(origin, name.copy()));
    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());

    Vector<pair<SecurityOrigin*, String> > 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, notifications[i].second);
}


} // namespace WebCore