summaryrefslogtreecommitdiffstats
path: root/WebCore/loader
diff options
context:
space:
mode:
authorAndrei Popescu <andreip@google.com>2009-07-21 13:10:06 +0100
committerAndrei Popescu <andreip@google.com>2009-07-21 13:31:30 +0100
commitc60802dd50f86c37e0596d41c3ef6fc2c8804da4 (patch)
treeef54137cbf064976e5f146c125691c40fb1136d3 /WebCore/loader
parentce39e03a248f9bee3e746c15e7961b3e40a871ed (diff)
downloadexternal_webkit-c60802dd50f86c37e0596d41c3ef6fc2c8804da4.zip
external_webkit-c60802dd50f86c37e0596d41c3ef6fc2c8804da4.tar.gz
external_webkit-c60802dd50f86c37e0596d41c3ef6fc2c8804da4.tar.bz2
Implements a mechanism that limit the growth of the application cache
Diffstat (limited to 'WebCore/loader')
-rw-r--r--WebCore/loader/EmptyClients.h4
-rw-r--r--WebCore/loader/appcache/ApplicationCache.cpp9
-rw-r--r--WebCore/loader/appcache/ApplicationCache.h7
-rw-r--r--WebCore/loader/appcache/ApplicationCacheGroup.cpp112
-rw-r--r--WebCore/loader/appcache/ApplicationCacheGroup.h9
-rw-r--r--WebCore/loader/appcache/ApplicationCacheResource.cpp23
-rw-r--r--WebCore/loader/appcache/ApplicationCacheResource.h2
-rw-r--r--WebCore/loader/appcache/ApplicationCacheStorage.cpp240
-rw-r--r--WebCore/loader/appcache/ApplicationCacheStorage.h30
9 files changed, 381 insertions, 55 deletions
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<FileChooser>) { }
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<ApplicationCacheResource> 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<std::pair<KURL, bool> > 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). (<rdar://problem/6467625>)
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<ApplicationCache> 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<DocumentLoader*> 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<DocumentLoader*> 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<ApplicationCache*>::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<DocumentLoader*>&);
static void postListenerTask(ListenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders);
static void postListenerTask(ListenerFunction, DocumentLoader*);
+ void scheduleReachedMaxAppCacheSizeCallback();
PassRefPtr<ResourceHandle> 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<ResourceHandle> m_currentHandle;
RefPtr<ApplicationCacheResource> m_currentResource;
RefPtr<ApplicationCacheResource> m_manifestResource;
RefPtr<ResourceHandle> 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 T>
+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<unsigned>(statement.getColumnInt64(2));
RefPtr<ApplicationCache> 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<unsigned>(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<KURL>* 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 T>
+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<KURL>* urls);
+ bool cacheGroupSize(const String& manifestURL, int64_t* size);
+ bool deleteCacheGroup(const String& manifestURL);
+ void vacuumDatabaseFile();
private:
+ ApplicationCacheStorage();
PassRefPtr<ApplicationCache> loadCache(unsigned storageID);
ApplicationCacheGroup* loadCacheGroup(const KURL& manifestURL);
- bool store(ApplicationCacheGroup*);
+ typedef StorageIDJournal<ApplicationCacheResource> ResourceStorageIDJournal;
+ typedef StorageIDJournal<ApplicationCacheGroup> 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<String, ApplicationCacheGroup*> CacheGroupMap;
CacheGroupMap m_cachesInMemory; // Excludes obsolete cache groups.
+
+ friend ApplicationCacheStorage& cacheStorage();
};
ApplicationCacheStorage& cacheStorage();