| /* |
| * 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 |