From c60802dd50f86c37e0596d41c3ef6fc2c8804da4 Mon Sep 17 00:00:00 2001 From: Andrei Popescu Date: Tue, 21 Jul 2009 13:10:06 +0100 Subject: Implements a mechanism that limit the growth of the application cache --- WebCore/loader/EmptyClients.h | 4 + WebCore/loader/appcache/ApplicationCache.cpp | 9 +- WebCore/loader/appcache/ApplicationCache.h | 7 + WebCore/loader/appcache/ApplicationCacheGroup.cpp | 112 +++++++--- WebCore/loader/appcache/ApplicationCacheGroup.h | 9 + .../loader/appcache/ApplicationCacheResource.cpp | 23 ++ WebCore/loader/appcache/ApplicationCacheResource.h | 2 + .../loader/appcache/ApplicationCacheStorage.cpp | 240 +++++++++++++++++++-- WebCore/loader/appcache/ApplicationCacheStorage.h | 30 ++- 9 files changed, 381 insertions(+), 55 deletions(-) (limited to 'WebCore/loader') diff --git a/WebCore/loader/EmptyClients.h b/WebCore/loader/EmptyClients.h index f6916ef..f93a878 100644 --- a/WebCore/loader/EmptyClients.h +++ b/WebCore/loader/EmptyClients.h @@ -133,6 +133,10 @@ public: virtual void exceededDatabaseQuota(Frame*, const String&) { } #endif +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + virtual void reachedMaxAppCacheSize(int64_t) { } +#endif + virtual void runOpenPanel(Frame*, PassRefPtr) { } virtual void formStateDidChange(const Node*) { } diff --git a/WebCore/loader/appcache/ApplicationCache.cpp b/WebCore/loader/appcache/ApplicationCache.cpp index 42f5b6a..3c29d68 100644 --- a/WebCore/loader/appcache/ApplicationCache.cpp +++ b/WebCore/loader/appcache/ApplicationCache.cpp @@ -39,6 +39,7 @@ namespace WebCore { ApplicationCache::ApplicationCache() : m_group(0) , m_manifest(0) + , m_estimatedSizeInStorage(0) , m_storageID(0) { } @@ -86,7 +87,9 @@ void ApplicationCache::addResource(PassRefPtr resource // Add the resource to the storage. cacheStorage().store(resource.get(), this); } - + + m_estimatedSizeInStorage += resource->estimatedSizeInStorage(); + m_resources.set(url, resource); } @@ -100,7 +103,9 @@ unsigned ApplicationCache::removeResource(const String& url) unsigned type = it->second->type(); m_resources.remove(it); - + + m_estimatedSizeInStorage -= it->second->estimatedSizeInStorage(); + return type; } diff --git a/WebCore/loader/appcache/ApplicationCache.h b/WebCore/loader/appcache/ApplicationCache.h index afdab27..4566471 100644 --- a/WebCore/loader/appcache/ApplicationCache.h +++ b/WebCore/loader/appcache/ApplicationCache.h @@ -93,6 +93,8 @@ public: static bool requestIsHTTPOrHTTPSGet(const ResourceRequest&); + int64_t estimatedSizeInStorage() const { return m_estimatedSizeInStorage; } + private: ApplicationCache(); @@ -106,6 +108,11 @@ private: // While an update is in progress, changes in dynamic entries are queued for later execution. Vector > m_pendingDynamicEntryActions; + // The total size of the resources belonging to this Application Cache instance. + // This is an estimation of the size this Application Cache occupies in the + // database file. + int64_t m_estimatedSizeInStorage; + unsigned m_storageID; }; diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/WebCore/loader/appcache/ApplicationCacheGroup.cpp index 9f2f6a4..735e3a3 100644 --- a/WebCore/loader/appcache/ApplicationCacheGroup.cpp +++ b/WebCore/loader/appcache/ApplicationCacheGroup.cpp @@ -31,6 +31,7 @@ #include "ApplicationCache.h" #include "ApplicationCacheResource.h" #include "ApplicationCacheStorage.h" +#include "ChromeClient.h" #include "DocumentLoader.h" #include "DOMApplicationCache.h" #include "DOMWindow.h" @@ -53,6 +54,7 @@ ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCop , m_isObsolete(false) , m_completionType(None) , m_isCopy(isCopy) + , m_calledReachedMaxAppCacheSize(false) { } @@ -650,6 +652,15 @@ void ApplicationCacheGroup::didFinishLoadingManifest() startLoadingEntry(); } +void ApplicationCacheGroup::didReachMaxAppCacheSize() +{ + ASSERT(m_frame); + ASSERT(m_cacheBeingUpdated); + m_frame->page()->chrome()->client()->reachedMaxAppCacheSize(cacheStorage().spaceNeeded(m_cacheBeingUpdated->estimatedSizeInStorage())); + m_calledReachedMaxAppCacheSize = true; + checkIfLoadIsComplete(); +} + void ApplicationCacheGroup::cacheUpdateFailed() { stopLoading(); @@ -728,7 +739,15 @@ void ApplicationCacheGroup::checkIfLoadIsComplete() // FIXME: Fetch the resource from manifest URL again, and check whether it is identical to the one used for update (in case the application was upgraded server-side in the meanwhile). () ASSERT(m_cacheBeingUpdated); - m_cacheBeingUpdated->setManifestResource(m_manifestResource.release()); + if (m_manifestResource) + m_cacheBeingUpdated->setManifestResource(m_manifestResource.release()); + else { + // We can get here as a result of retrying the Complete step, following + // a failure of the cache storage to save the newest cache due to hitting + // the maximum size. In such a case, m_manifestResource may be 0, as + // the manifest was already set on the newest cache object. + ASSERT(cacheStorage().isMaximumSizeReached() && m_calledReachedMaxAppCacheSize); + } RefPtr oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? 0 : m_newestCache; @@ -739,29 +758,42 @@ void ApplicationCacheGroup::checkIfLoadIsComplete() cacheStorage().remove(oldNewestCache.get()); // Fire the success events. postListenerTask(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener, m_associatedDocumentLoaders); - } else { - // Run the "cache failure steps" - // Fire the error events to all pending master entries, as well any other cache hosts - // currently associated with a cache in this group. - postListenerTask(&DOMApplicationCache::callErrorListener, m_associatedDocumentLoaders); - // Disassociate the pending master entries from the failed new cache. Note that - // all other loaders in the m_associatedDocumentLoaders are still associated with - // some other cache in this group. They are not associated with the failed new cache. - - // Need to copy loaders, because the cache group may be destroyed at the end of iteration. - Vector loaders; - copyToVector(m_pendingMasterResourceLoaders, loaders); - size_t count = loaders.size(); - for (size_t i = 0; i != count; ++i) - disassociateDocumentLoader(loaders[i]); // This can delete this group. - - // Reinstate the oldNewestCache, if there was one. - if (oldNewestCache) { - // This will discard the failed new cache. - setNewestCache(oldNewestCache.release()); - } else { - // We must have been deleted by the last call to disassociateDocumentLoader(). - return; + } else { + if (cacheStorage().isMaximumSizeReached() && !m_calledReachedMaxAppCacheSize) { + // We ran out of space. All the changes in the cache storage have + // been rolled back. We roll back to the previous state in here, + // as well, call the chrome client asynchronously and retry to + // save the new cache. + // Save a reference to the new cache. + m_cacheBeingUpdated = m_newestCache.release(); + if (oldNewestCache) { + // Reinstate the oldNewestCache. + setNewestCache(oldNewestCache.release()); + } + scheduleReachedMaxAppCacheSizeCallback(); + return; + } else { + // Run the "cache failure steps" + // Fire the error events to all pending master entries, as well any other cache hosts + // currently associated with a cache in this group. + postListenerTask(&DOMApplicationCache::callErrorListener, m_associatedDocumentLoaders); + // Disassociate the pending master entries from the failed new cache. Note that + // all other loaders in the m_associatedDocumentLoaders are still associated with + // some other cache in this group. They are not associated with the failed new cache. + // Need to copy loaders, because the cache group may be destroyed at the end of iteration. + Vector loaders; + copyToVector(m_pendingMasterResourceLoaders, loaders); + size_t count = loaders.size(); + for (size_t i = 0; i != count; ++i) + disassociateDocumentLoader(loaders[i]); // This can delete this group. + // Reinstate the oldNewestCache, if there was one. + if (oldNewestCache) { + // This will discard the failed new cache. + setNewestCache(oldNewestCache.release()); + } else { + // We must have been deleted by the last call to disassociateDocumentLoader(). + return; + } } } break; @@ -773,6 +805,7 @@ void ApplicationCacheGroup::checkIfLoadIsComplete() m_completionType = None; m_updateStatus = Idle; m_frame = 0; + m_calledReachedMaxAppCacheSize = false; } void ApplicationCacheGroup::startLoadingEntry() @@ -855,7 +888,34 @@ void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loa ASSERT(!m_associatedDocumentLoaders.contains(loader)); m_associatedDocumentLoaders.add(loader); } - + +class ChromeClientCallbackTimer: public TimerBase { +public: + ChromeClientCallbackTimer(ApplicationCacheGroup* cacheGroup) + : m_cacheGroup(cacheGroup) + { + } + +private: + virtual void fired() + { + m_cacheGroup->didReachMaxAppCacheSize(); + delete this; + } + // Note that there is no need to use a RefPtr here. The ApplicationCacheGroup instance is guaranteed + // to be alive when the timer fires since invoking the ChromeClient callback is part of its normal + // update machinery and nothing can yet cause it to get deleted. + ApplicationCacheGroup* m_cacheGroup; +}; + +void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback() +{ + ASSERT(isMainThread()); + ChromeClientCallbackTimer* timer = new ChromeClientCallbackTimer(this); + timer->startOneShot(0); + // The timer will delete itself once it fires. +} + class CallCacheListenerTask : public ScriptExecutionContext::Task { typedef void (DOMApplicationCache::*ListenerFunction)(); public: @@ -908,7 +968,7 @@ void ApplicationCacheGroup::clearStorageID() for (HashSet::const_iterator it = m_caches.begin(); it != end; ++it) (*it)->clearStorageID(); } - + } diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.h b/WebCore/loader/appcache/ApplicationCacheGroup.h index 063fb3b..281dadf 100644 --- a/WebCore/loader/appcache/ApplicationCacheGroup.h +++ b/WebCore/loader/appcache/ApplicationCacheGroup.h @@ -95,6 +95,7 @@ private: static void postListenerTask(ListenerFunction, const HashSet&); static void postListenerTask(ListenerFunction, const Vector >& loaders); static void postListenerTask(ListenerFunction, DocumentLoader*); + void scheduleReachedMaxAppCacheSizeCallback(); PassRefPtr createResourceHandle(const KURL&, ApplicationCacheResource* newestCachedResource); @@ -106,6 +107,7 @@ private: void didReceiveManifestResponse(const ResourceResponse&); void didReceiveManifestData(const char*, int); void didFinishLoadingManifest(); + void didReachMaxAppCacheSize(); void startLoadingEntry(); void deliverDelayedMainResources(); @@ -163,12 +165,19 @@ private: // Whether this cache group is a copy that's only used for transferring the cache to another file. bool m_isCopy; + + // This flag is set immediately after the ChromeClient::reachedMaxAppCacheSize() callback is invoked as a result of the storage layer failing to save a cache + // due to reaching the maximum size of the application cache database file. This flag is used by ApplicationCacheGroup::checkIfLoadIsComplete() to decide + // the course of action in case of this failure (i.e. call the ChromeClient callback or run the failure steps). + bool m_calledReachedMaxAppCacheSize; RefPtr m_currentHandle; RefPtr m_currentResource; RefPtr m_manifestResource; RefPtr m_manifestHandle; + + friend class ChromeClientCallbackTimer; }; } // namespace WebCore diff --git a/WebCore/loader/appcache/ApplicationCacheResource.cpp b/WebCore/loader/appcache/ApplicationCacheResource.cpp index 7c1241b..90e65ad 100644 --- a/WebCore/loader/appcache/ApplicationCacheResource.cpp +++ b/WebCore/loader/appcache/ApplicationCacheResource.cpp @@ -35,6 +35,7 @@ ApplicationCacheResource::ApplicationCacheResource(const KURL& url, const Resour : SubstituteResource(url, response, data) , m_type(type) , m_storageID(0) + , m_estimatedSizeInStorage(0) { } @@ -44,6 +45,28 @@ void ApplicationCacheResource::addType(unsigned type) m_type |= type; } +int64_t ApplicationCacheResource::estimatedSizeInStorage() +{ + if (m_estimatedSizeInStorage) + return m_estimatedSizeInStorage; + + if (data()) + m_estimatedSizeInStorage = data()->size(); + + HTTPHeaderMap::const_iterator end = response().httpHeaderFields().end(); + for (HTTPHeaderMap::const_iterator it = response().httpHeaderFields().begin(); it != end; ++it) + m_estimatedSizeInStorage += (it->first.length() + it->second.length() + 2) * sizeof(UChar); + + m_estimatedSizeInStorage += url().string().length() * sizeof(UChar); + m_estimatedSizeInStorage += sizeof(int); // response().m_httpStatusCode + m_estimatedSizeInStorage += response().url().string().length() * sizeof(UChar); + m_estimatedSizeInStorage += sizeof(unsigned); // dataId + m_estimatedSizeInStorage += response().mimeType().length() * sizeof(UChar); + m_estimatedSizeInStorage += response().textEncodingName().length() * sizeof(UChar); + + return m_estimatedSizeInStorage; +} + #ifndef NDEBUG void ApplicationCacheResource::dumpType(unsigned type) { diff --git a/WebCore/loader/appcache/ApplicationCacheResource.h b/WebCore/loader/appcache/ApplicationCacheResource.h index 28d8280..53fdd1d 100644 --- a/WebCore/loader/appcache/ApplicationCacheResource.h +++ b/WebCore/loader/appcache/ApplicationCacheResource.h @@ -54,6 +54,7 @@ public: void setStorageID(unsigned storageID) { m_storageID = storageID; } unsigned storageID() const { return m_storageID; } void clearStorageID() { m_storageID = 0; } + int64_t estimatedSizeInStorage(); #ifndef NDEBUG static void dumpType(unsigned type); @@ -64,6 +65,7 @@ private: unsigned m_type; unsigned m_storageID; + int64_t m_estimatedSizeInStorage; }; } // namespace WebCore diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/WebCore/loader/appcache/ApplicationCacheStorage.cpp index 1c59581..a1dad15 100644 --- a/WebCore/loader/appcache/ApplicationCacheStorage.cpp +++ b/WebCore/loader/appcache/ApplicationCacheStorage.cpp @@ -43,16 +43,17 @@ using namespace std; namespace WebCore { -class ResourceStorageIDJournal { +template +class StorageIDJournal { public: - ~ResourceStorageIDJournal() + ~StorageIDJournal() { size_t size = m_records.size(); for (size_t i = 0; i < size; ++i) m_records[i].restore(); } - void add(ApplicationCacheResource* resource, unsigned storageID) + void add(T* resource, unsigned storageID) { m_records.append(Record(resource, storageID)); } @@ -66,7 +67,7 @@ private: class Record { public: Record() : m_resource(0), m_storageID(0) { } - Record(ApplicationCacheResource* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { } + Record(T* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { } void restore() { @@ -74,7 +75,7 @@ private: } private: - ApplicationCacheResource* m_resource; + T* m_resource; unsigned m_storageID; }; @@ -223,6 +224,8 @@ ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url // a matching URL. unsigned newestCacheID = static_cast(statement.getColumnInt64(2)); RefPtr cache = loadCache(newestCacheID); + if (!cache) + continue; ApplicationCacheResource* resource = cache->resourceForURL(url); if (!resource) @@ -353,6 +356,56 @@ const String& ApplicationCacheStorage::cacheDirectory() const return m_cacheDirectory; } +void ApplicationCacheStorage::setMaximumSize(int64_t size) +{ + m_maximumSize = size; +} + +int64_t ApplicationCacheStorage::maximumSize() const +{ + return m_maximumSize; +} + +bool ApplicationCacheStorage::isMaximumSizeReached() const +{ + return m_isMaximumSizeReached; +} + +int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave) +{ + int64_t spaceNeeded = 0; + int64_t currentSize = 0; + if (!getFileSize(m_cacheFile, currentSize)) + return 0; + + // Determine the amount of free space we have available. + int64_t totalAvailableSize = 0; + if (m_maximumSize < currentSize) { + // The max size is smaller than the actual size of the app cache file. + // This can happen if the client previously imposed a larger max size + // value and the app cache file has already grown beyond the current + // max size value. + // The amount of free space is just the amount of free space inside + // the database file. Note that this is always 0 if SQLite is compiled + // with AUTO_VACUUM = 1. + totalAvailableSize = m_database.freeSpaceSize(); + } else { + // The max size is the same or larger than the current size. + // The amount of free space available is the amount of free space + // inside the database file plus the amount we can grow until we hit + // the max size. + totalAvailableSize = (m_maximumSize - currentSize) + m_database.freeSpaceSize(); + } + + // The space needed to be freed in order to accomodate the failed cache is + // the size of the failed cache minus any already available free space. + spaceNeeded = cacheToSave - totalAvailableSize; + // The space needed value must be positive (or else the total already + // available free space would be larger than the size of the failed cache and + // saving of the cache should have never failed). + ASSERT(spaceNeeded); + return spaceNeeded; +} bool ApplicationCacheStorage::executeSQLCommand(const String& sql) { @@ -366,7 +419,7 @@ bool ApplicationCacheStorage::executeSQLCommand(const String& sql) return result; } -static const int schemaVersion = 3; +static const int schemaVersion = 4; void ApplicationCacheStorage::verifySchemaVersion() { @@ -400,13 +453,13 @@ void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist) // The cache directory should never be null, but if it for some weird reason is we bail out. if (m_cacheDirectory.isNull()) return; - - String applicationCachePath = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); - if (!createIfDoesNotExist && !fileExists(applicationCachePath)) + + m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); + if (!createIfDoesNotExist && !fileExists(m_cacheFile)) return; makeAllDirectories(m_cacheDirectory); - m_database.open(applicationCachePath); + m_database.open(m_cacheFile); if (!m_database.isOpen()) return; @@ -416,7 +469,7 @@ void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist) // Create tables executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, " "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER)"); - executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER, size INTEGER)"); executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)"); executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, " "cache INTEGER NOT NULL ON CONFLICT FAIL)"); @@ -456,9 +509,10 @@ bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement) return result; } -bool ApplicationCacheStorage::store(ApplicationCacheGroup* group) +bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageIDJournal* journal) { ASSERT(group->storageID() == 0); + ASSERT(journal); SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL) VALUES (?, ?)"); if (statement.prepare() != SQLResultOk) @@ -471,6 +525,7 @@ bool ApplicationCacheStorage::store(ApplicationCacheGroup* group) return false; group->setStorageID(static_cast(m_database.lastInsertRowID())); + journal->add(group, 0); return true; } @@ -480,11 +535,12 @@ bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJo ASSERT(cache->group()->storageID() != 0); ASSERT(storageIDJournal); - SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup) VALUES (?)"); + SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup, size) VALUES (?, ?)"); if (statement.prepare() != SQLResultOk) return false; statement.bindInt64(1, cache->group()->storageID()); + statement.bindInt64(2, cache->estimatedSizeInStorage()); if (!executeStatement(statement)) return false; @@ -581,6 +637,9 @@ bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned if (resourceStatement.prepare() != SQLResultOk) return false; + // The same ApplicationCacheResource are used in ApplicationCacheResource::size() + // to calculate the approximate size of an ApplicationCacheResource object. If + // you change the code below, please also change ApplicationCacheResource::size(). resourceStatement.bindText(1, resource->url()); resourceStatement.bindInt64(2, resource->response().httpStatusCode()); resourceStatement.bindText(3, resource->response().url()); @@ -629,33 +688,56 @@ bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resourc return executeStatement(entryStatement); } -void ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache) +bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache) { ASSERT(cache->storageID()); openDatabase(true); + m_isMaximumSizeReached = false; + m_database.setMaximumSize(m_maximumSize); + SQLiteTransaction storeResourceTransaction(m_database); storeResourceTransaction.begin(); - if (!store(resource, cache->storageID())) - return; + if (!store(resource, cache->storageID())) { + checkForMaxSizeReached(); + return false; + } + + // A resource was added to the cache. Update the total data size for the cache. + SQLiteStatement sizeUpdateStatement(m_database, "UPDATE Caches SET size=size+? WHERE id=?"); + if (sizeUpdateStatement.prepare() != SQLResultOk) + return false; + + sizeUpdateStatement.bindInt64(1, resource->estimatedSizeInStorage()); + sizeUpdateStatement.bindInt64(2, cache->storageID()); + + if (!executeStatement(sizeUpdateStatement)) + return false; storeResourceTransaction.commit(); + return true; } bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group) { openDatabase(true); - + + m_isMaximumSizeReached = false; + m_database.setMaximumSize(m_maximumSize); + SQLiteTransaction storeCacheTransaction(m_database); storeCacheTransaction.begin(); - + + GroupStorageIDJournal groupStorageIDJournal; if (!group->storageID()) { // Store the group - if (!store(group)) + if (!store(group, &groupStorageIDJournal)) { + checkForMaxSizeReached(); return false; + } } ASSERT(group->newestCache()); @@ -665,11 +747,13 @@ bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group) // Log the storageID changes to the in-memory resource objects. The journal // object will roll them back automatically in case a database operation // fails and this method returns early. - ResourceStorageIDJournal storageIDJournal; + ResourceStorageIDJournal resourceStorageIDJournal; // Store the newest cache - if (!store(group->newestCache(), &storageIDJournal)) + if (!store(group->newestCache(), &resourceStorageIDJournal)) { + checkForMaxSizeReached(); return false; + } // Update the newest cache in the group. @@ -683,7 +767,8 @@ bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group) if (!executeStatement(statement)) return false; - storageIDJournal.commit(); + groupStorageIDJournal.commit(); + resourceStorageIDJournal.commit(); storeCacheTransaction.commit(); return true; } @@ -879,7 +964,116 @@ bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, App return copyStorage.storeNewestCache(groupCopy.get()); } - + +bool ApplicationCacheStorage::manifestURLs(Vector* urls) +{ + ASSERT(urls); + openDatabase(false); + if (!m_database.isOpen()) + return false; + + SQLiteStatement selectURLs(m_database, "SELECT manifestURL FROM CacheGroups"); + + if (selectURLs.prepare() != SQLResultOk) + return false; + + while (selectURLs.step() == SQLResultRow) + urls->append(selectURLs.getColumnText(0)); + + return true; +} + +bool ApplicationCacheStorage::cacheGroupSize(const String& manifestURL, int64_t* size) +{ + ASSERT(size); + openDatabase(false); + if (!m_database.isOpen()) + return false; + + SQLiteStatement statement(m_database, "SELECT sum(Caches.size) FROM Caches INNER JOIN CacheGroups ON Caches.cacheGroup=CacheGroups.id WHERE CacheGroups.manifestURL=?"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindText(1, manifestURL); + + int result = statement.step(); + if (result == SQLResultDone) + return false; + + if (result != SQLResultRow) { + LOG_ERROR("Could not get the size of the cache group, error \"%s\"", m_database.lastErrorMsg()); + return false; + } + + *size = statement.getColumnInt64(0); + return true; +} + +bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL) +{ + SQLiteTransaction deleteTransaction(m_database); + // Check to see if the group is in memory. + ApplicationCacheGroup* group = m_cachesInMemory.get(manifestURL); + if (group) + cacheGroupMadeObsolete(group); + else { + // The cache group is not in memory, so remove it from the disk. + openDatabase(false); + if (!m_database.isOpen()) + return false; + + SQLiteStatement idStatement(m_database, "SELECT id FROM CacheGroups WHERE manifestURL=?"); + if (idStatement.prepare() != SQLResultOk) + return false; + + idStatement.bindText(1, manifestURL); + + int result = idStatement.step(); + if (result == SQLResultDone) + return false; + + if (result != SQLResultRow) { + LOG_ERROR("Could not load cache group id, error \"%s\"", m_database.lastErrorMsg()); + return false; + } + + int64_t groupId = idStatement.getColumnInt64(0); + + SQLiteStatement cacheStatement(m_database, "DELETE FROM Caches WHERE cacheGroup=?"); + if (cacheStatement.prepare() != SQLResultOk) + return false; + + SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?"); + if (groupStatement.prepare() != SQLResultOk) + return false; + + cacheStatement.bindInt64(1, groupId); + executeStatement(cacheStatement); + groupStatement.bindInt64(1, groupId); + executeStatement(groupStatement); + } + + deleteTransaction.commit(); + return true; +} + +void ApplicationCacheStorage::vacuumDatabaseFile() +{ + m_database.runVacuumCommand(); +} + +void ApplicationCacheStorage::checkForMaxSizeReached() +{ + if (m_database.lastError() == SQLResultFull) + m_isMaximumSizeReached = true; +} + +ApplicationCacheStorage::ApplicationCacheStorage() + : m_maximumSize(INT_MAX) + , m_isMaximumSizeReached(false) +{ +} + ApplicationCacheStorage& cacheStorage() { DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ()); diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.h b/WebCore/loader/appcache/ApplicationCacheStorage.h index b13b596..c6d687e 100644 --- a/WebCore/loader/appcache/ApplicationCacheStorage.h +++ b/WebCore/loader/appcache/ApplicationCacheStorage.h @@ -40,13 +40,19 @@ class ApplicationCache; class ApplicationCacheGroup; class ApplicationCacheResource; class KURL; -class ResourceStorageIDJournal; - +template +class StorageIDJournal; + class ApplicationCacheStorage { public: void setCacheDirectory(const String&); const String& cacheDirectory() const; + void setMaximumSize(int64_t size); + int64_t maximumSize() const; + bool isMaximumSizeReached() const; + int64_t spaceNeeded(int64_t cacheToSave); + ApplicationCacheGroup* cacheGroupForURL(const KURL&); // Cache to load a main resource from. ApplicationCacheGroup* fallbackCacheGroupForURL(const KURL&); // Cache that has a fallback entry to load a main resource from if normal loading fails. @@ -55,7 +61,7 @@ public: void cacheGroupMadeObsolete(ApplicationCacheGroup*); bool storeNewestCache(ApplicationCacheGroup*); // Updates the cache group, but doesn't remove old cache. - void store(ApplicationCacheResource*, ApplicationCache*); + bool store(ApplicationCacheResource*, ApplicationCache*); bool storeUpdatedType(ApplicationCacheResource*, ApplicationCache*); // Removes the group if the cache to be removed is the newest one (so, storeNewestCache() needs to be called beforehand when updating). @@ -65,11 +71,19 @@ public: static bool storeCopyOfCache(const String& cacheDirectory, ApplicationCache*); + bool manifestURLs(Vector* urls); + bool cacheGroupSize(const String& manifestURL, int64_t* size); + bool deleteCacheGroup(const String& manifestURL); + void vacuumDatabaseFile(); private: + ApplicationCacheStorage(); PassRefPtr loadCache(unsigned storageID); ApplicationCacheGroup* loadCacheGroup(const KURL& manifestURL); - bool store(ApplicationCacheGroup*); + typedef StorageIDJournal ResourceStorageIDJournal; + typedef StorageIDJournal GroupStorageIDJournal; + + bool store(ApplicationCacheGroup*, GroupStorageIDJournal*); bool store(ApplicationCache*, ResourceStorageIDJournal*); bool store(ApplicationCacheResource*, unsigned cacheStorageID); @@ -81,8 +95,14 @@ private: bool executeStatement(SQLiteStatement&); bool executeSQLCommand(const String&); + + void checkForMaxSizeReached(); String m_cacheDirectory; + String m_cacheFile; + + int64_t m_maximumSize; + bool m_isMaximumSizeReached; SQLiteDatabase m_database; @@ -92,6 +112,8 @@ private: typedef HashMap CacheGroupMap; CacheGroupMap m_cachesInMemory; // Excludes obsolete cache groups. + + friend ApplicationCacheStorage& cacheStorage(); }; ApplicationCacheStorage& cacheStorage(); -- cgit v1.1