diff options
Diffstat (limited to 'Source/WebCore/storage/StorageTracker.cpp')
-rw-r--r-- | Source/WebCore/storage/StorageTracker.cpp | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/Source/WebCore/storage/StorageTracker.cpp b/Source/WebCore/storage/StorageTracker.cpp new file mode 100644 index 0000000..e18b4b8 --- /dev/null +++ b/Source/WebCore/storage/StorageTracker.cpp @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2011 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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 "StorageTracker.h" + +#if ENABLE(DOM_STORAGE) + +#include "DatabaseThread.h" +#include "FileSystem.h" +#include "LocalStorageTask.h" +#include "LocalStorageThread.h" +#include "Logging.h" +#include "PageGroup.h" +#include "SQLiteFileSystem.h" +#include "SQLiteStatement.h" +#include "SecurityOrigin.h" +#include "StorageTrackerClient.h" +#include "TextEncoding.h" +#include <wtf/MainThread.h> +#include <wtf/StdLibExtras.h> +#include <wtf/Vector.h> +#include <wtf/text/CString.h> + +namespace WebCore { + +static StorageTracker* storageTracker = 0; + +void StorageTracker::initializeTracker(const String& storagePath) +{ + ASSERT(isMainThread()); + ASSERT(!storageTracker); + + if (!storageTracker) + storageTracker = new StorageTracker(storagePath); + + // Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead. + // FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding? + UTF8Encoding(); + + SQLiteFileSystem::registerSQLiteVFS(); + storageTracker->setIsActive(true); + storageTracker->m_thread->start(); + storageTracker->importOriginIdentifiers(); +} + +StorageTracker& StorageTracker::tracker() +{ + if (!storageTracker) + storageTracker = new StorageTracker(""); + + return *storageTracker; +} + +StorageTracker::StorageTracker(const String& storagePath) + : m_client(0) + , m_thread(LocalStorageThread::create()) + , m_isActive(false) +{ + setStorageDirectoryPath(storagePath); +} + +void StorageTracker::setStorageDirectoryPath(const String& path) +{ + MutexLocker lockDatabase(m_databaseGuard); + ASSERT(!m_database.isOpen()); + + m_storageDirectoryPath = path.threadsafeCopy(); +} + +String StorageTracker::trackerDatabasePath() +{ + ASSERT(!m_databaseGuard.tryLock()); + return SQLiteFileSystem::appendDatabaseFileNameToPath(m_storageDirectoryPath, "StorageTracker.db"); +} + +void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist) +{ + ASSERT(m_isActive); + ASSERT(!isMainThread()); + ASSERT(!m_databaseGuard.tryLock()); + + if (m_database.isOpen()) + return; + + String databasePath = trackerDatabasePath(); + + if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) { + if (createIfDoesNotExist) + LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data()); + return; + } + + if (!m_database.open(databasePath)) { + 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, path TEXT);")) + LOG_ERROR("Failed to create Origins table."); + } +} + +void StorageTracker::importOriginIdentifiers() +{ + if (!m_isActive) + return; + + ASSERT(isMainThread()); + ASSERT(m_thread); + + m_thread->scheduleTask(LocalStorageTask::createOriginIdentifiersImport()); +} + +void StorageTracker::syncImportOriginIdentifiers() +{ + ASSERT(m_isActive); + + ASSERT(!isMainThread()); + + { + MutexLocker lockDatabase(m_databaseGuard); + + // Don't force creation of StorageTracker's db just because a tracker + // was initialized. It will be created if local storage dbs are found + // by syncFileSystemAndTrackerDatabse() or the next time a local storage + // db is created by StorageAreaSync. + openTrackerDatabase(false); + + if (m_database.isOpen()) { + SQLiteStatement statement(m_database, "SELECT origin FROM Origins"); + if (statement.prepare() != SQLResultOk) { + LOG_ERROR("Failed to prepare statement."); + return; + } + + int result; + + { + MutexLocker lockOrigins(m_originSetGuard); + while ((result = statement.step()) == SQLResultRow) + m_originSet.add(statement.getColumnText(0).threadsafeCopy()); + } + + if (result != SQLResultDone) { + LOG_ERROR("Failed to read in all origins from the database."); + return; + } + } + } + + syncFileSystemAndTrackerDatabase(); + + { + MutexLocker lockClient(m_clientGuard); + if (m_client) { + MutexLocker lockOrigins(m_originSetGuard); + OriginSet::const_iterator end = m_originSet.end(); + for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it) + m_client->dispatchDidModifyOrigin(*it); + } + } +} + +void StorageTracker::syncFileSystemAndTrackerDatabase() +{ + ASSERT(!isMainThread()); + ASSERT(m_isActive); + + m_databaseGuard.lock(); + DEFINE_STATIC_LOCAL(const String, fileMatchPattern, ("*.localstorage")); + DEFINE_STATIC_LOCAL(const String, fileExt, (".localstorage")); + DEFINE_STATIC_LOCAL(const unsigned, fileExtLength, (fileExt.length())); + m_databaseGuard.unlock(); + + Vector<String> paths; + { + MutexLocker lock(m_databaseGuard); + paths = listDirectory(m_storageDirectoryPath, fileMatchPattern); + } + + // Use a copy of m_originSet to find expired entries and to schedule their + // deletions from disk and from m_originSet. + OriginSet originSetCopy; + { + MutexLocker lock(m_originSetGuard); + OriginSet::const_iterator end = m_originSet.end(); + for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it) + originSetCopy.add((*it).threadsafeCopy()); + } + + // Add missing StorageTracker records. + OriginSet foundOrigins; + Vector<String>::const_iterator end = paths.end(); + for (Vector<String>::const_iterator it = paths.begin(); it != end; ++it) { + String path = *it; + if (path.endsWith(fileExt, true) && path.length() > fileExtLength) { + String file = pathGetFileName(path); + String originIdentifier = file.substring(0, file.length() - fileExtLength); + if (!originSetCopy.contains(originIdentifier)) + syncSetOriginDetails(originIdentifier, path); + + foundOrigins.add(originIdentifier); + } + } + + // Delete stale StorageTracker records. + OriginSet::const_iterator setEnd = originSetCopy.end(); + for (OriginSet::const_iterator it = originSetCopy.begin(); it != setEnd; ++it) { + if (!foundOrigins.contains(*it)) + syncDeleteOrigin(*it); + } +} + +void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile) +{ + if (!m_isActive) + return; + + { + MutexLocker lockOrigins(m_originSetGuard); + + if (m_originSet.contains(originIdentifier)) + return; + + m_originSet.add(originIdentifier); + } + + OwnPtr<LocalStorageTask> task = LocalStorageTask::createSetOriginDetails(originIdentifier.threadsafeCopy(), databaseFile); + + if (isMainThread()) { + ASSERT(m_thread); + m_thread->scheduleTask(task.release()); + } else + callOnMainThread(scheduleTask, reinterpret_cast<void*>(task.leakPtr())); +} + +void StorageTracker::scheduleTask(void* taskIn) +{ + ASSERT(isMainThread()); + ASSERT(StorageTracker::tracker().m_thread); + + OwnPtr<LocalStorageTask> task = adoptPtr(reinterpret_cast<LocalStorageTask*>(taskIn)); + + StorageTracker::tracker().m_thread->scheduleTask(task.release()); +} + +void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile) +{ + ASSERT(!isMainThread()); + + MutexLocker lockDatabase(m_databaseGuard); + + openTrackerDatabase(true); + + if (!m_database.isOpen()) + return; + + SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)"); + if (statement.prepare() != SQLResultOk) { + LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data()); + return; + } + + statement.bindText(1, originIdentifier); + statement.bindText(2, databaseFile); + + if (statement.step() != SQLResultDone) + LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data()); + + { + MutexLocker lockOrigins(m_originSetGuard); + if (!m_originSet.contains(originIdentifier)) + m_originSet.add(originIdentifier); + } + + { + MutexLocker lockClient(m_clientGuard); + if (m_client) + m_client->dispatchDidModifyOrigin(originIdentifier); + } +} + +void StorageTracker::origins(Vector<RefPtr<SecurityOrigin> >& result) +{ + ASSERT(m_isActive); + + if (!m_isActive) + return; + + MutexLocker lockOrigins(m_originSetGuard); + + OriginSet::const_iterator end = m_originSet.end(); + for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it) + result.append(SecurityOrigin::createFromDatabaseIdentifier(*it)); +} + +void StorageTracker::deleteAllOrigins() +{ + ASSERT(m_isActive); + ASSERT(isMainThread()); + ASSERT(m_thread); + + if (!m_isActive) + return; + + { + MutexLocker lockOrigins(m_originSetGuard); + willDeleteAllOrigins(); + m_originSet.clear(); + } + + PageGroup::clearLocalStorageForAllOrigins(); + + m_thread->scheduleTask(LocalStorageTask::createDeleteAllOrigins()); +} + +void StorageTracker::syncDeleteAllOrigins() +{ + ASSERT(!isMainThread()); + + MutexLocker lockDatabase(m_databaseGuard); + + openTrackerDatabase(false); + if (!m_database.isOpen()) + return; + + SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins"); + if (statement.prepare() != SQLResultOk) { + LOG_ERROR("Failed to prepare statement."); + return; + } + + int result; + while ((result = statement.step()) == SQLResultRow) { + if (!canDeleteOrigin(statement.getColumnText(0))) + continue; + + SQLiteFileSystem::deleteDatabaseFile(statement.getColumnText(1)); + + { + MutexLocker lockClient(m_clientGuard); + if (m_client) + m_client->dispatchDidModifyOrigin(statement.getColumnText(0)); + } + } + + if (result != SQLResultDone) + LOG_ERROR("Failed to read in all origins from the database."); + + if (m_database.isOpen()) + m_database.close(); + + SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath()); + SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath); +} + +void StorageTracker::deleteOrigin(const String& originIdentifier) +{ + deleteOrigin(SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get()); +} + +void StorageTracker::deleteOrigin(SecurityOrigin* origin) +{ + ASSERT(m_isActive); + ASSERT(isMainThread()); + ASSERT(m_thread); + + if (!m_isActive) + return; + + // Before deleting database, we need to clear in-memory local storage data + // in StorageArea, and to close the StorageArea db. It's possible for an + // item to be added immediately after closing the db and cause StorageAreaSync + // to reopen the db before the db is deleted by a StorageTracker thread. + // In this case, reopening the db in StorageAreaSync will cancel a pending + // StorageTracker db deletion. + PageGroup::clearLocalStorageForOrigin(origin); + + String originId = origin->databaseIdentifier(); + + { + MutexLocker lockOrigins(m_originSetGuard); + willDeleteOrigin(originId); + m_originSet.remove(originId); + } + + m_thread->scheduleTask(LocalStorageTask::createDeleteOrigin(originId)); +} + +void StorageTracker::syncDeleteOrigin(const String& originIdentifier) +{ + ASSERT(!isMainThread()); + + MutexLocker lockDatabase(m_databaseGuard); + + if (!canDeleteOrigin(originIdentifier)) { + LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data()); + return; + } + + openTrackerDatabase(false); + if (!m_database.isOpen()) + return; + + // Get origin's db file path, delete entry in tracker's db, then delete db file. + SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?"); + if (pathStatement.prepare() != SQLResultOk) { + LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data()); + return; + } + pathStatement.bindText(1, originIdentifier); + int result = pathStatement.step(); + if (result != SQLResultRow) { + LOG_ERROR("Unable to find origin '%s' in Origins table", originIdentifier.ascii().data()); + return; + } + + String path = pathStatement.getColumnText(0); + + ASSERT(!path.isEmpty()); + + SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?"); + if (deleteStatement.prepare() != SQLResultOk) { + LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data()); + return; + } + deleteStatement.bindText(1, originIdentifier); + if (!deleteStatement.executeCommand()) { + LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data()); + return; + } + + SQLiteFileSystem::deleteDatabaseFile(path); + + bool shouldDeleteTrackerFiles = false; + { + MutexLocker originLock(m_originSetGuard); + m_originSet.remove(originIdentifier); + shouldDeleteTrackerFiles = m_originSet.isEmpty(); + } + + if (shouldDeleteTrackerFiles) { + m_database.close(); + SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath()); + SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath); + } + + { + MutexLocker lockClient(m_clientGuard); + if (m_client) + m_client->dispatchDidModifyOrigin(originIdentifier); + } +} + +void StorageTracker::willDeleteAllOrigins() +{ + ASSERT(!m_originSetGuard.tryLock()); + + OriginSet::const_iterator end = m_originSet.end(); + for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it) + m_originsBeingDeleted.add((*it).threadsafeCopy()); +} + +void StorageTracker::willDeleteOrigin(const String& originIdentifier) +{ + ASSERT(isMainThread()); + ASSERT(!m_originSetGuard.tryLock()); + + m_originsBeingDeleted.add(originIdentifier); +} + +bool StorageTracker::canDeleteOrigin(const String& originIdentifier) +{ + ASSERT(!m_databaseGuard.tryLock()); + MutexLocker lockOrigins(m_originSetGuard); + return m_originsBeingDeleted.contains(originIdentifier); +} + +void StorageTracker::cancelDeletingOrigin(const String& originIdentifier) +{ + if (!m_isActive) + return; + + MutexLocker lockDatabase(m_databaseGuard); + MutexLocker lockOrigins(m_originSetGuard); + if (!m_originsBeingDeleted.isEmpty()) + m_originsBeingDeleted.remove(originIdentifier); +} + +void StorageTracker::setClient(StorageTrackerClient* client) +{ + MutexLocker lockClient(m_clientGuard); + m_client = client; +} + +void StorageTracker::syncLocalStorage() +{ + PageGroup::syncLocalStorage(); +} + +bool StorageTracker::isActive() +{ + return m_isActive; +} + +void StorageTracker::setIsActive(bool flag) +{ + m_isActive = flag; +} + +} // namespace WebCore + +#endif // ENABLE(DOM_STORAGE) |