2 * Copyright (C) 2008, 2009, 2010, 2011 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "ApplicationCacheStorage.h"
29 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
31 #include "ApplicationCache.h"
32 #include "ApplicationCacheGroup.h"
33 #include "ApplicationCacheHost.h"
34 #include "ApplicationCacheResource.h"
35 #include "FileSystem.h"
37 #include "NotImplemented.h"
38 #include "SQLiteStatement.h"
39 #include "SQLiteTransaction.h"
40 #include "SecurityOrigin.h"
42 #include <wtf/text/CString.h>
43 #include <wtf/StdLibExtras.h>
44 #include <wtf/StringExtras.h>
45 #include <wtf/text/StringBuilder.h>
51 static const char flatFileSubdirectory[] = "ApplicationCache";
54 class StorageIDJournal {
58 size_t size = m_records.size();
59 for (size_t i = 0; i < size; ++i)
60 m_records[i].restore();
63 void add(T* resource, unsigned storageID)
65 m_records.append(Record(resource, storageID));
76 Record() : m_resource(0), m_storageID(0) { }
77 Record(T* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { }
81 m_resource->setStorageID(m_storageID);
89 Vector<Record> m_records;
92 static unsigned urlHostHash(const KURL& url)
94 unsigned hostStart = url.hostStart();
95 unsigned hostEnd = url.hostEnd();
97 return AlreadyHashed::avoidDeletedValue(StringHasher::computeHash(url.string().characters() + hostStart, hostEnd - hostStart));
100 ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL)
103 if (!m_database.isOpen())
106 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?");
107 if (statement.prepare() != SQLResultOk)
110 statement.bindText(1, manifestURL);
112 int result = statement.step();
113 if (result == SQLResultDone)
116 if (result != SQLResultRow) {
117 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
121 unsigned newestCacheStorageID = static_cast<unsigned>(statement.getColumnInt64(2));
123 RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID);
127 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
129 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
130 group->setNewestCache(cache.release());
135 ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL)
137 ASSERT(!manifestURL.hasFragmentIdentifier());
139 std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0);
141 if (!result.second) {
142 ASSERT(result.first->second);
143 return result.first->second;
146 // Look up the group in the database
147 ApplicationCacheGroup* group = loadCacheGroup(manifestURL);
149 // If the group was not found we need to create it
151 group = new ApplicationCacheGroup(manifestURL);
152 m_cacheHostSet.add(urlHostHash(manifestURL));
155 result.first->second = group;
160 ApplicationCacheGroup* ApplicationCacheStorage::findInMemoryCacheGroup(const KURL& manifestURL) const
162 return m_cachesInMemory.get(manifestURL);
165 void ApplicationCacheStorage::loadManifestHostHashes()
167 static bool hasLoadedHashes = false;
172 // We set this flag to true before the database has been opened
173 // to avoid trying to open the database over and over if it doesn't exist.
174 hasLoadedHashes = true;
177 if (!m_database.isOpen())
180 // Fetch the host hashes.
181 SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups");
182 if (statement.prepare() != SQLResultOk)
185 while (statement.step() == SQLResultRow)
186 m_cacheHostSet.add(static_cast<unsigned>(statement.getColumnInt64(0)));
189 ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url)
191 ASSERT(!url.hasFragmentIdentifier());
193 loadManifestHostHashes();
195 // Hash the host name and see if there's a manifest with the same host.
196 if (!m_cacheHostSet.contains(urlHostHash(url)))
199 // Check if a cache already exists in memory.
200 CacheGroupMap::const_iterator end = m_cachesInMemory.end();
201 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) {
202 ApplicationCacheGroup* group = it->second;
204 ASSERT(!group->isObsolete());
206 if (!protocolHostAndPortAreEqual(url, group->manifestURL()))
209 if (ApplicationCache* cache = group->newestCache()) {
210 ApplicationCacheResource* resource = cache->resourceForURL(url);
213 if (resource->type() & ApplicationCacheResource::Foreign)
219 if (!m_database.isOpen())
222 // Check the database. Look for all cache groups with a newest cache.
223 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL");
224 if (statement.prepare() != SQLResultOk)
228 while ((result = statement.step()) == SQLResultRow) {
229 KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1));
231 if (m_cachesInMemory.contains(manifestURL))
234 if (!protocolHostAndPortAreEqual(url, manifestURL))
237 // We found a cache group that matches. Now check if the newest cache has a resource with
239 unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2));
240 RefPtr<ApplicationCache> cache = loadCache(newestCacheID);
244 ApplicationCacheResource* resource = cache->resourceForURL(url);
247 if (resource->type() & ApplicationCacheResource::Foreign)
250 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
252 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
253 group->setNewestCache(cache.release());
255 m_cachesInMemory.set(group->manifestURL(), group);
260 if (result != SQLResultDone)
261 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
266 ApplicationCacheGroup* ApplicationCacheStorage::fallbackCacheGroupForURL(const KURL& url)
268 ASSERT(!url.hasFragmentIdentifier());
270 // Check if an appropriate cache already exists in memory.
271 CacheGroupMap::const_iterator end = m_cachesInMemory.end();
272 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) {
273 ApplicationCacheGroup* group = it->second;
275 ASSERT(!group->isObsolete());
277 if (ApplicationCache* cache = group->newestCache()) {
279 if (cache->isURLInOnlineWhitelist(url))
281 if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL))
283 if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign)
289 if (!m_database.isOpen())
292 // Check the database. Look for all cache groups with a newest cache.
293 SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL");
294 if (statement.prepare() != SQLResultOk)
298 while ((result = statement.step()) == SQLResultRow) {
299 KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1));
301 if (m_cachesInMemory.contains(manifestURL))
304 // Fallback namespaces always have the same origin as manifest URL, so we can avoid loading caches that cannot match.
305 if (!protocolHostAndPortAreEqual(url, manifestURL))
308 // We found a cache group that matches. Now check if the newest cache has a resource with
309 // a matching fallback namespace.
310 unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2));
311 RefPtr<ApplicationCache> cache = loadCache(newestCacheID);
314 if (cache->isURLInOnlineWhitelist(url))
316 if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL))
318 if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign)
321 ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
323 group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
324 group->setNewestCache(cache.release());
326 m_cachesInMemory.set(group->manifestURL(), group);
331 if (result != SQLResultDone)
332 LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
337 void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group)
339 if (group->isObsolete()) {
340 ASSERT(!group->storageID());
341 ASSERT(m_cachesInMemory.get(group->manifestURL()) != group);
345 ASSERT(m_cachesInMemory.get(group->manifestURL()) == group);
347 m_cachesInMemory.remove(group->manifestURL());
349 // If the cache group is half-created, we don't want it in the saved set (as it is not stored in database).
350 if (!group->storageID())
351 m_cacheHostSet.remove(urlHostHash(group->manifestURL()));
354 void ApplicationCacheStorage::cacheGroupMadeObsolete(ApplicationCacheGroup* group)
356 ASSERT(m_cachesInMemory.get(group->manifestURL()) == group);
357 ASSERT(m_cacheHostSet.contains(urlHostHash(group->manifestURL())));
359 if (ApplicationCache* newestCache = group->newestCache())
362 m_cachesInMemory.remove(group->manifestURL());
363 m_cacheHostSet.remove(urlHostHash(group->manifestURL()));
366 void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory)
368 ASSERT(m_cacheDirectory.isNull());
369 ASSERT(!cacheDirectory.isNull());
371 m_cacheDirectory = cacheDirectory;
374 const String& ApplicationCacheStorage::cacheDirectory() const
376 return m_cacheDirectory;
379 void ApplicationCacheStorage::setMaximumSize(int64_t size)
381 m_maximumSize = size;
384 int64_t ApplicationCacheStorage::maximumSize() const
386 return m_maximumSize;
389 bool ApplicationCacheStorage::isMaximumSizeReached() const
391 return m_isMaximumSizeReached;
394 int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave)
396 int64_t spaceNeeded = 0;
397 long long fileSize = 0;
398 if (!getFileSize(m_cacheFile, fileSize))
401 int64_t currentSize = fileSize + flatFileAreaSize();
403 // Determine the amount of free space we have available.
404 int64_t totalAvailableSize = 0;
405 if (m_maximumSize < currentSize) {
406 // The max size is smaller than the actual size of the app cache file.
407 // This can happen if the client previously imposed a larger max size
408 // value and the app cache file has already grown beyond the current
410 // The amount of free space is just the amount of free space inside
411 // the database file. Note that this is always 0 if SQLite is compiled
412 // with AUTO_VACUUM = 1.
413 totalAvailableSize = m_database.freeSpaceSize();
415 // The max size is the same or larger than the current size.
416 // The amount of free space available is the amount of free space
417 // inside the database file plus the amount we can grow until we hit
419 totalAvailableSize = (m_maximumSize - currentSize) + m_database.freeSpaceSize();
422 // The space needed to be freed in order to accommodate the failed cache is
423 // the size of the failed cache minus any already available free space.
424 spaceNeeded = cacheToSave - totalAvailableSize;
425 // The space needed value must be positive (or else the total already
426 // available free space would be larger than the size of the failed cache and
427 // saving of the cache should have never failed).
432 void ApplicationCacheStorage::setDefaultOriginQuota(int64_t quota)
434 m_defaultOriginQuota = quota;
437 bool ApplicationCacheStorage::calculateQuotaForOrigin(const SecurityOrigin* origin, int64_t& quota)
439 // If an Origin record doesn't exist, then the COUNT will be 0 and quota will be 0.
440 // Using the count to determine if a record existed or not is a safe way to determine
441 // if a quota of 0 is real, from the record, or from null.
442 SQLiteStatement statement(m_database, "SELECT COUNT(quota), quota FROM Origins WHERE origin=?");
443 if (statement.prepare() != SQLResultOk)
446 statement.bindText(1, origin->databaseIdentifier());
447 int result = statement.step();
449 // Return the quota, or if it was null the default.
450 if (result == SQLResultRow) {
451 bool wasNoRecord = statement.getColumnInt64(0) == 0;
452 quota = wasNoRecord ? m_defaultOriginQuota : statement.getColumnInt64(1);
456 LOG_ERROR("Could not get the quota of an origin, error \"%s\"", m_database.lastErrorMsg());
460 bool ApplicationCacheStorage::calculateUsageForOrigin(const SecurityOrigin* origin, int64_t& usage)
462 // If an Origins record doesn't exist, then the SUM will be null,
463 // which will become 0, as expected, when converting to a number.
464 SQLiteStatement statement(m_database, "SELECT SUM(Caches.size)"
466 " INNER JOIN Origins ON CacheGroups.origin = Origins.origin"
467 " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup"
468 " WHERE Origins.origin=?");
469 if (statement.prepare() != SQLResultOk)
472 statement.bindText(1, origin->databaseIdentifier());
473 int result = statement.step();
475 if (result == SQLResultRow) {
476 usage = statement.getColumnInt64(0);
480 LOG_ERROR("Could not get the quota of an origin, error \"%s\"", m_database.lastErrorMsg());
484 bool ApplicationCacheStorage::calculateRemainingSizeForOriginExcludingCache(const SecurityOrigin* origin, ApplicationCache* cache, int64_t& remainingSize)
487 if (!m_database.isOpen())
490 // Remaining size = total origin quota - size of all caches with origin excluding the provided cache.
491 // Keep track of the number of caches so we can tell if the result was a calculation or not.
493 int64_t excludingCacheIdentifier = cache ? cache->storageID() : 0;
494 if (excludingCacheIdentifier != 0) {
495 query = "SELECT COUNT(Caches.size), Origins.quota - SUM(Caches.size)"
497 " INNER JOIN Origins ON CacheGroups.origin = Origins.origin"
498 " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup"
499 " WHERE Origins.origin=?"
502 query = "SELECT COUNT(Caches.size), Origins.quota - SUM(Caches.size)"
504 " INNER JOIN Origins ON CacheGroups.origin = Origins.origin"
505 " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup"
506 " WHERE Origins.origin=?";
509 SQLiteStatement statement(m_database, query);
510 if (statement.prepare() != SQLResultOk)
513 statement.bindText(1, origin->databaseIdentifier());
514 if (excludingCacheIdentifier != 0)
515 statement.bindInt64(2, excludingCacheIdentifier);
516 int result = statement.step();
518 // If the count was 0 that then we have to query the origin table directly
519 // for its quota. Otherwise we can use the calculated value.
520 if (result == SQLResultRow) {
521 int64_t numberOfCaches = statement.getColumnInt64(0);
522 if (numberOfCaches == 0)
523 calculateQuotaForOrigin(origin, remainingSize);
525 remainingSize = statement.getColumnInt64(1);
529 LOG_ERROR("Could not get the remaining size of an origin's quota, error \"%s\"", m_database.lastErrorMsg());
533 bool ApplicationCacheStorage::storeUpdatedQuotaForOrigin(const SecurityOrigin* origin, int64_t quota)
536 if (!m_database.isOpen())
539 if (!ensureOriginRecord(origin))
542 SQLiteStatement updateStatement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
543 if (updateStatement.prepare() != SQLResultOk)
546 updateStatement.bindInt64(1, quota);
547 updateStatement.bindText(2, origin->databaseIdentifier());
549 return executeStatement(updateStatement);
552 bool ApplicationCacheStorage::executeSQLCommand(const String& sql)
554 ASSERT(m_database.isOpen());
556 bool result = m_database.executeCommand(sql);
558 LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"",
559 sql.utf8().data(), m_database.lastErrorMsg());
564 // Update the schemaVersion when the schema of any the Application Cache
565 // SQLite tables changes. This allows the database to be rebuilt when
566 // a new, incompatible change has been introduced to the database schema.
567 static const int schemaVersion = 7;
569 void ApplicationCacheStorage::verifySchemaVersion()
571 int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0);
572 if (version == schemaVersion)
577 // Update user version.
578 SQLiteTransaction setDatabaseVersion(m_database);
579 setDatabaseVersion.begin();
581 char userVersionSQL[32];
582 int unusedNumBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion);
583 ASSERT_UNUSED(unusedNumBytes, static_cast<int>(sizeof(userVersionSQL)) >= unusedNumBytes);
585 SQLiteStatement statement(m_database, userVersionSQL);
586 if (statement.prepare() != SQLResultOk)
589 executeStatement(statement);
590 setDatabaseVersion.commit();
593 void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
595 if (m_database.isOpen())
598 // The cache directory should never be null, but if it for some weird reason is we bail out.
599 if (m_cacheDirectory.isNull())
602 m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db");
603 if (!createIfDoesNotExist && !fileExists(m_cacheFile))
606 makeAllDirectories(m_cacheDirectory);
607 m_database.open(m_cacheFile);
609 if (!m_database.isOpen())
612 verifySchemaVersion();
615 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, "
616 "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER, origin TEXT)");
617 executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER, size INTEGER)");
618 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
619 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheAllowsAllNetworkRequests (wildcard INTEGER NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
620 executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, "
621 "cache INTEGER NOT NULL ON CONFLICT FAIL)");
622 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)");
623 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, "
624 "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)");
625 executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB, path TEXT)");
626 executeSQLCommand("CREATE TABLE IF NOT EXISTS DeletedCacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT)");
627 executeSQLCommand("CREATE TABLE IF NOT EXISTS Origins (origin TEXT UNIQUE ON CONFLICT IGNORE, quota INTEGER NOT NULL ON CONFLICT FAIL)");
629 // When a cache is deleted, all its entries and its whitelist should be deleted.
630 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches"
631 " FOR EACH ROW BEGIN"
632 " DELETE FROM CacheEntries WHERE cache = OLD.id;"
633 " DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;"
634 " DELETE FROM CacheAllowsAllNetworkRequests WHERE cache = OLD.id;"
635 " DELETE FROM FallbackURLs WHERE cache = OLD.id;"
638 // When a cache entry is deleted, its resource should also be deleted.
639 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheEntryDeleted AFTER DELETE ON CacheEntries"
640 " FOR EACH ROW BEGIN"
641 " DELETE FROM CacheResources WHERE id = OLD.resource;"
644 // When a cache resource is deleted, its data blob should also be deleted.
645 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources"
646 " FOR EACH ROW BEGIN"
647 " DELETE FROM CacheResourceData WHERE id = OLD.data;"
650 // When a cache resource is deleted, if it contains a non-empty path, that path should
651 // be added to the DeletedCacheResources table so the flat file at that path can
652 // be deleted at a later time.
653 executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDataDeleted AFTER DELETE ON CacheResourceData"
655 " WHEN OLD.path NOT NULL BEGIN"
656 " INSERT INTO DeletedCacheResources (path) values (OLD.path);"
660 bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement)
662 bool result = statement.executeCommand();
664 LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"",
665 statement.query().utf8().data(), m_database.lastErrorMsg());
670 bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageIDJournal* journal)
672 ASSERT(group->storageID() == 0);
675 SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL, origin) VALUES (?, ?, ?)");
676 if (statement.prepare() != SQLResultOk)
679 statement.bindInt64(1, urlHostHash(group->manifestURL()));
680 statement.bindText(2, group->manifestURL());
681 statement.bindText(3, group->origin()->databaseIdentifier());
683 if (!executeStatement(statement))
686 unsigned groupStorageID = static_cast<unsigned>(m_database.lastInsertRowID());
688 if (!ensureOriginRecord(group->origin()))
691 group->setStorageID(groupStorageID);
692 journal->add(group, 0);
696 bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJournal* storageIDJournal)
698 ASSERT(cache->storageID() == 0);
699 ASSERT(cache->group()->storageID() != 0);
700 ASSERT(storageIDJournal);
702 SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup, size) VALUES (?, ?)");
703 if (statement.prepare() != SQLResultOk)
706 statement.bindInt64(1, cache->group()->storageID());
707 statement.bindInt64(2, cache->estimatedSizeInStorage());
709 if (!executeStatement(statement))
712 unsigned cacheStorageID = static_cast<unsigned>(m_database.lastInsertRowID());
714 // Store all resources
716 ApplicationCache::ResourceMap::const_iterator end = cache->end();
717 for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
718 unsigned oldStorageID = it->second->storageID();
719 if (!store(it->second.get(), cacheStorageID))
722 // Storing the resource succeeded. Log its old storageID in case
723 // it needs to be restored later.
724 storageIDJournal->add(it->second.get(), oldStorageID);
728 // Store the online whitelist
729 const Vector<KURL>& onlineWhitelist = cache->onlineWhitelist();
731 size_t whitelistSize = onlineWhitelist.size();
732 for (size_t i = 0; i < whitelistSize; ++i) {
733 SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)");
736 statement.bindText(1, onlineWhitelist[i]);
737 statement.bindInt64(2, cacheStorageID);
739 if (!executeStatement(statement))
744 // Store online whitelist wildcard flag.
746 SQLiteStatement statement(m_database, "INSERT INTO CacheAllowsAllNetworkRequests (wildcard, cache) VALUES (?, ?)");
749 statement.bindInt64(1, cache->allowsAllNetworkRequests());
750 statement.bindInt64(2, cacheStorageID);
752 if (!executeStatement(statement))
756 // Store fallback URLs.
757 const FallbackURLVector& fallbackURLs = cache->fallbackURLs();
759 size_t fallbackCount = fallbackURLs.size();
760 for (size_t i = 0; i < fallbackCount; ++i) {
761 SQLiteStatement statement(m_database, "INSERT INTO FallbackURLs (namespace, fallbackURL, cache) VALUES (?, ?, ?)");
764 statement.bindText(1, fallbackURLs[i].first);
765 statement.bindText(2, fallbackURLs[i].second);
766 statement.bindInt64(3, cacheStorageID);
768 if (!executeStatement(statement))
773 cache->setStorageID(cacheStorageID);
777 bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID)
779 ASSERT(cacheStorageID);
780 ASSERT(!resource->storageID());
784 // openDatabase(true) could still fail, for example when cacheStorage is full or no longer available.
785 if (!m_database.isOpen())
788 // First, insert the data
789 SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data, path) VALUES (?, ?)");
790 if (dataStatement.prepare() != SQLResultOk)
795 if (!resource->path().isEmpty())
796 dataStatement.bindText(2, pathGetFileName(resource->path()));
797 else if (shouldStoreResourceAsFlatFile(resource)) {
798 // First, check to see if creating the flat file would violate the maximum total quota. We don't need
799 // to check the per-origin quota here, as it was already checked in storeNewestCache().
800 if (m_database.totalSize() + flatFileAreaSize() + resource->data()->size() > m_maximumSize) {
801 m_isMaximumSizeReached = true;
805 String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
806 makeAllDirectories(flatFileDirectory);
810 String fileName = resource->response().suggestedFilename();
811 size_t dotIndex = fileName.reverseFind('.');
812 if (dotIndex != notFound && dotIndex < (fileName.length() - 1))
813 extension = fileName.substring(dotIndex);
816 if (!writeDataToUniqueFileInDirectory(resource->data(), flatFileDirectory, path, extension))
819 fullPath = pathByAppendingComponent(flatFileDirectory, path);
820 resource->setPath(fullPath);
821 dataStatement.bindText(2, path);
823 if (resource->data()->size())
824 dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size());
827 if (!dataStatement.executeCommand()) {
828 // Clean up the file which we may have written to:
829 if (!fullPath.isEmpty())
830 deleteFile(fullPath);
835 unsigned dataId = static_cast<unsigned>(m_database.lastInsertRowID());
837 // Then, insert the resource
839 // Serialize the headers
840 StringBuilder stringBuilder;
842 HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end();
843 for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) {
844 stringBuilder.append(it->first);
845 stringBuilder.append((UChar)':');
846 stringBuilder.append(it->second);
847 stringBuilder.append((UChar)'\n');
850 String headers = stringBuilder.toString();
852 SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)");
853 if (resourceStatement.prepare() != SQLResultOk)
856 // The same ApplicationCacheResource are used in ApplicationCacheResource::size()
857 // to calculate the approximate size of an ApplicationCacheResource object. If
858 // you change the code below, please also change ApplicationCacheResource::size().
859 resourceStatement.bindText(1, resource->url());
860 resourceStatement.bindInt64(2, resource->response().httpStatusCode());
861 resourceStatement.bindText(3, resource->response().url());
862 resourceStatement.bindText(4, headers);
863 resourceStatement.bindInt64(5, dataId);
864 resourceStatement.bindText(6, resource->response().mimeType());
865 resourceStatement.bindText(7, resource->response().textEncodingName());
867 if (!executeStatement(resourceStatement))
870 unsigned resourceId = static_cast<unsigned>(m_database.lastInsertRowID());
872 // Finally, insert the cache entry
873 SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)");
874 if (entryStatement.prepare() != SQLResultOk)
877 entryStatement.bindInt64(1, cacheStorageID);
878 entryStatement.bindInt64(2, resource->type());
879 entryStatement.bindInt64(3, resourceId);
881 if (!executeStatement(entryStatement))
884 // Did we successfully write the resource data to a file? If so,
885 // release the resource's data and free up a potentially large amount
887 if (!fullPath.isEmpty())
888 resource->data()->clear();
890 resource->setStorageID(resourceId);
894 bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resource, ApplicationCache* cache)
896 ASSERT_UNUSED(cache, cache->storageID());
897 ASSERT(resource->storageID());
899 // First, insert the data
900 SQLiteStatement entryStatement(m_database, "UPDATE CacheEntries SET type=? WHERE resource=?");
901 if (entryStatement.prepare() != SQLResultOk)
904 entryStatement.bindInt64(1, resource->type());
905 entryStatement.bindInt64(2, resource->storageID());
907 return executeStatement(entryStatement);
910 bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache)
912 ASSERT(cache->storageID());
916 if (!m_database.isOpen())
919 m_isMaximumSizeReached = false;
920 m_database.setMaximumSize(m_maximumSize - flatFileAreaSize());
922 SQLiteTransaction storeResourceTransaction(m_database);
923 storeResourceTransaction.begin();
925 if (!store(resource, cache->storageID())) {
926 checkForMaxSizeReached();
930 // A resource was added to the cache. Update the total data size for the cache.
931 SQLiteStatement sizeUpdateStatement(m_database, "UPDATE Caches SET size=size+? WHERE id=?");
932 if (sizeUpdateStatement.prepare() != SQLResultOk)
935 sizeUpdateStatement.bindInt64(1, resource->estimatedSizeInStorage());
936 sizeUpdateStatement.bindInt64(2, cache->storageID());
938 if (!executeStatement(sizeUpdateStatement))
941 storeResourceTransaction.commit();
945 bool ApplicationCacheStorage::ensureOriginRecord(const SecurityOrigin* origin)
947 SQLiteStatement insertOriginStatement(m_database, "INSERT INTO Origins (origin, quota) VALUES (?, ?)");
948 if (insertOriginStatement.prepare() != SQLResultOk)
951 insertOriginStatement.bindText(1, origin->databaseIdentifier());
952 insertOriginStatement.bindInt64(2, m_defaultOriginQuota);
953 if (!executeStatement(insertOriginStatement))
959 bool ApplicationCacheStorage::checkOriginQuota(ApplicationCacheGroup* group, ApplicationCache* oldCache, ApplicationCache* newCache, int64_t& totalSpaceNeeded)
961 // Check if the oldCache with the newCache would reach the per-origin quota.
962 int64_t remainingSpaceInOrigin;
963 const SecurityOrigin* origin = group->origin();
964 if (calculateRemainingSizeForOriginExcludingCache(origin, oldCache, remainingSpaceInOrigin)) {
965 if (remainingSpaceInOrigin < newCache->estimatedSizeInStorage()) {
967 if (calculateQuotaForOrigin(origin, quota)) {
968 totalSpaceNeeded = quota - remainingSpaceInOrigin + newCache->estimatedSizeInStorage();
972 ASSERT_NOT_REACHED();
973 totalSpaceNeeded = 0;
981 bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group, ApplicationCache* oldCache, FailureReason& failureReason)
985 if (!m_database.isOpen())
988 m_isMaximumSizeReached = false;
989 m_database.setMaximumSize(m_maximumSize - flatFileAreaSize());
991 SQLiteTransaction storeCacheTransaction(m_database);
993 storeCacheTransaction.begin();
995 // Check if this would reach the per-origin quota.
996 int64_t totalSpaceNeededIgnored;
997 if (!checkOriginQuota(group, oldCache, group->newestCache(), totalSpaceNeededIgnored)) {
998 failureReason = OriginQuotaReached;
1002 GroupStorageIDJournal groupStorageIDJournal;
1003 if (!group->storageID()) {
1005 if (!store(group, &groupStorageIDJournal)) {
1006 checkForMaxSizeReached();
1007 failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure;
1012 ASSERT(group->newestCache());
1013 ASSERT(!group->isObsolete());
1014 ASSERT(!group->newestCache()->storageID());
1016 // Log the storageID changes to the in-memory resource objects. The journal
1017 // object will roll them back automatically in case a database operation
1018 // fails and this method returns early.
1019 ResourceStorageIDJournal resourceStorageIDJournal;
1021 // Store the newest cache
1022 if (!store(group->newestCache(), &resourceStorageIDJournal)) {
1023 checkForMaxSizeReached();
1024 failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure;
1028 // Update the newest cache in the group.
1030 SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?");
1031 if (statement.prepare() != SQLResultOk) {
1032 failureReason = DiskOrOperationFailure;
1036 statement.bindInt64(1, group->newestCache()->storageID());
1037 statement.bindInt64(2, group->storageID());
1039 if (!executeStatement(statement)) {
1040 failureReason = DiskOrOperationFailure;
1044 groupStorageIDJournal.commit();
1045 resourceStorageIDJournal.commit();
1046 storeCacheTransaction.commit();
1050 bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
1052 // Ignore the reason for failing, just attempt the store.
1053 FailureReason ignoredFailureReason;
1054 return storeNewestCache(group, 0, ignoredFailureReason);
1057 static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response)
1059 size_t pos = find(header, headerLength, ':');
1060 ASSERT(pos != notFound);
1062 AtomicString headerName = AtomicString(header, pos);
1063 String headerValue = String(header + pos + 1, headerLength - pos - 1);
1065 response.setHTTPHeaderField(headerName, headerValue);
1068 static inline void parseHeaders(const String& headers, ResourceResponse& response)
1070 unsigned startPos = 0;
1072 while ((endPos = headers.find('\n', startPos)) != notFound) {
1073 ASSERT(startPos != endPos);
1075 parseHeader(headers.characters() + startPos, endPos - startPos, response);
1077 startPos = endPos + 1;
1080 if (startPos != headers.length())
1081 parseHeader(headers.characters(), headers.length(), response);
1084 PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID)
1086 SQLiteStatement cacheStatement(m_database,
1087 "SELECT url, statusCode, type, mimeType, textEncodingName, headers, CacheResourceData.data, CacheResourceData.path FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id "
1088 "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?");
1089 if (cacheStatement.prepare() != SQLResultOk) {
1090 LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg());
1094 cacheStatement.bindInt64(1, storageID);
1096 RefPtr<ApplicationCache> cache = ApplicationCache::create();
1098 String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
1101 while ((result = cacheStatement.step()) == SQLResultRow) {
1102 KURL url(ParsedURLString, cacheStatement.getColumnText(0));
1104 int httpStatusCode = cacheStatement.getColumnInt(1);
1106 unsigned type = static_cast<unsigned>(cacheStatement.getColumnInt64(2));
1109 cacheStatement.getColumnBlobAsVector(6, blob);
1111 RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob);
1113 String path = cacheStatement.getColumnText(7);
1116 size = data->size();
1118 path = pathByAppendingComponent(flatFileDirectory, path);
1119 getFileSize(path, size);
1122 String mimeType = cacheStatement.getColumnText(3);
1123 String textEncodingName = cacheStatement.getColumnText(4);
1125 ResourceResponse response(url, mimeType, size, textEncodingName, "");
1126 response.setHTTPStatusCode(httpStatusCode);
1128 String headers = cacheStatement.getColumnText(5);
1129 parseHeaders(headers, response);
1131 RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release(), path);
1133 if (type & ApplicationCacheResource::Manifest)
1134 cache->setManifestResource(resource.release());
1136 cache->addResource(resource.release());
1139 if (result != SQLResultDone)
1140 LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg());
1142 // Load the online whitelist
1143 SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?");
1144 if (whitelistStatement.prepare() != SQLResultOk)
1146 whitelistStatement.bindInt64(1, storageID);
1148 Vector<KURL> whitelist;
1149 while ((result = whitelistStatement.step()) == SQLResultRow)
1150 whitelist.append(KURL(ParsedURLString, whitelistStatement.getColumnText(0)));
1152 if (result != SQLResultDone)
1153 LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg());
1155 cache->setOnlineWhitelist(whitelist);
1157 // Load online whitelist wildcard flag.
1158 SQLiteStatement whitelistWildcardStatement(m_database, "SELECT wildcard FROM CacheAllowsAllNetworkRequests WHERE cache=?");
1159 if (whitelistWildcardStatement.prepare() != SQLResultOk)
1161 whitelistWildcardStatement.bindInt64(1, storageID);
1163 result = whitelistWildcardStatement.step();
1164 if (result != SQLResultRow)
1165 LOG_ERROR("Could not load cache online whitelist wildcard flag, error \"%s\"", m_database.lastErrorMsg());
1167 cache->setAllowsAllNetworkRequests(whitelistWildcardStatement.getColumnInt64(0));
1169 if (whitelistWildcardStatement.step() != SQLResultDone)
1170 LOG_ERROR("Too many rows for online whitelist wildcard flag");
1172 // Load fallback URLs.
1173 SQLiteStatement fallbackStatement(m_database, "SELECT namespace, fallbackURL FROM FallbackURLs WHERE cache=?");
1174 if (fallbackStatement.prepare() != SQLResultOk)
1176 fallbackStatement.bindInt64(1, storageID);
1178 FallbackURLVector fallbackURLs;
1179 while ((result = fallbackStatement.step()) == SQLResultRow)
1180 fallbackURLs.append(make_pair(KURL(ParsedURLString, fallbackStatement.getColumnText(0)), KURL(ParsedURLString, fallbackStatement.getColumnText(1))));
1182 if (result != SQLResultDone)
1183 LOG_ERROR("Could not load fallback URLs, error \"%s\"", m_database.lastErrorMsg());
1185 cache->setFallbackURLs(fallbackURLs);
1187 cache->setStorageID(storageID);
1189 return cache.release();
1192 void ApplicationCacheStorage::remove(ApplicationCache* cache)
1194 if (!cache->storageID())
1197 openDatabase(false);
1198 if (!m_database.isOpen())
1201 ASSERT(cache->group());
1202 ASSERT(cache->group()->storageID());
1204 // All associated data will be deleted by database triggers.
1205 SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?");
1206 if (statement.prepare() != SQLResultOk)
1209 statement.bindInt64(1, cache->storageID());
1210 executeStatement(statement);
1212 cache->clearStorageID();
1214 if (cache->group()->newestCache() == cache) {
1215 // Currently, there are no triggers on the cache group, which is why the cache had to be removed separately above.
1216 SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?");
1217 if (groupStatement.prepare() != SQLResultOk)
1220 groupStatement.bindInt64(1, cache->group()->storageID());
1221 executeStatement(groupStatement);
1223 cache->group()->clearStorageID();
1226 checkForDeletedResources();
1229 void ApplicationCacheStorage::empty()
1231 openDatabase(false);
1233 if (!m_database.isOpen())
1236 // Clear cache groups, caches, cache resources, and origins.
1237 executeSQLCommand("DELETE FROM CacheGroups");
1238 executeSQLCommand("DELETE FROM Caches");
1239 executeSQLCommand("DELETE FROM Origins");
1241 // Clear the storage IDs for the caches in memory.
1242 // The caches will still work, but cached resources will not be saved to disk
1243 // until a cache update process has been initiated.
1244 CacheGroupMap::const_iterator end = m_cachesInMemory.end();
1245 for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it)
1246 it->second->clearStorageID();
1248 checkForDeletedResources();
1251 void ApplicationCacheStorage::deleteTables()
1254 m_database.clearAllTables();
1257 bool ApplicationCacheStorage::shouldStoreResourceAsFlatFile(ApplicationCacheResource* resource)
1259 return resource->response().mimeType().startsWith("audio/", false)
1260 || resource->response().mimeType().startsWith("video/", false);
1263 bool ApplicationCacheStorage::writeDataToUniqueFileInDirectory(SharedBuffer* data, const String& directory, String& path, const String& fileExtension)
1268 path = encodeForFileName(createCanonicalUUIDString()) + fileExtension;
1269 // Guard against the above function being called on a platform which does not implement
1270 // createCanonicalUUIDString().
1271 ASSERT(!path.isEmpty());
1275 fullPath = pathByAppendingComponent(directory, path);
1276 } while (directoryName(fullPath) != directory || fileExists(fullPath));
1278 PlatformFileHandle handle = openFile(fullPath, OpenForWrite);
1282 int64_t writtenBytes = writeToFile(handle, data->data(), data->size());
1285 if (writtenBytes != static_cast<int64_t>(data->size())) {
1286 deleteFile(fullPath);
1293 bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost* cacheHost)
1295 ApplicationCache* cache = cacheHost->applicationCache();
1299 // Create a new cache.
1300 RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create();
1302 cacheCopy->setOnlineWhitelist(cache->onlineWhitelist());
1303 cacheCopy->setFallbackURLs(cache->fallbackURLs());
1305 // Traverse the cache and add copies of all resources.
1306 ApplicationCache::ResourceMap::const_iterator end = cache->end();
1307 for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
1308 ApplicationCacheResource* resource = it->second.get();
1310 RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data(), resource->path());
1312 cacheCopy->addResource(resourceCopy.release());
1315 // Now create a new cache group.
1316 OwnPtr<ApplicationCacheGroup> groupCopy(adoptPtr(new ApplicationCacheGroup(cache->group()->manifestURL(), true)));
1318 groupCopy->setNewestCache(cacheCopy);
1320 ApplicationCacheStorage copyStorage;
1321 copyStorage.setCacheDirectory(cacheDirectory);
1323 // Empty the cache in case something was there before.
1324 copyStorage.empty();
1326 return copyStorage.storeNewestCache(groupCopy.get());
1329 bool ApplicationCacheStorage::manifestURLs(Vector<KURL>* urls)
1332 openDatabase(false);
1333 if (!m_database.isOpen())
1336 SQLiteStatement selectURLs(m_database, "SELECT manifestURL FROM CacheGroups");
1338 if (selectURLs.prepare() != SQLResultOk)
1341 while (selectURLs.step() == SQLResultRow)
1342 urls->append(KURL(ParsedURLString, selectURLs.getColumnText(0)));
1347 bool ApplicationCacheStorage::cacheGroupSize(const String& manifestURL, int64_t* size)
1350 openDatabase(false);
1351 if (!m_database.isOpen())
1354 SQLiteStatement statement(m_database, "SELECT sum(Caches.size) FROM Caches INNER JOIN CacheGroups ON Caches.cacheGroup=CacheGroups.id WHERE CacheGroups.manifestURL=?");
1355 if (statement.prepare() != SQLResultOk)
1358 statement.bindText(1, manifestURL);
1360 int result = statement.step();
1361 if (result == SQLResultDone)
1364 if (result != SQLResultRow) {
1365 LOG_ERROR("Could not get the size of the cache group, error \"%s\"", m_database.lastErrorMsg());
1369 *size = statement.getColumnInt64(0);
1373 bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL)
1375 SQLiteTransaction deleteTransaction(m_database);
1376 // Check to see if the group is in memory.
1377 ApplicationCacheGroup* group = m_cachesInMemory.get(manifestURL);
1379 cacheGroupMadeObsolete(group);
1381 // The cache group is not in memory, so remove it from the disk.
1382 openDatabase(false);
1383 if (!m_database.isOpen())
1386 SQLiteStatement idStatement(m_database, "SELECT id FROM CacheGroups WHERE manifestURL=?");
1387 if (idStatement.prepare() != SQLResultOk)
1390 idStatement.bindText(1, manifestURL);
1392 int result = idStatement.step();
1393 if (result == SQLResultDone)
1396 if (result != SQLResultRow) {
1397 LOG_ERROR("Could not load cache group id, error \"%s\"", m_database.lastErrorMsg());
1401 int64_t groupId = idStatement.getColumnInt64(0);
1403 SQLiteStatement cacheStatement(m_database, "DELETE FROM Caches WHERE cacheGroup=?");
1404 if (cacheStatement.prepare() != SQLResultOk)
1407 SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?");
1408 if (groupStatement.prepare() != SQLResultOk)
1411 cacheStatement.bindInt64(1, groupId);
1412 executeStatement(cacheStatement);
1413 groupStatement.bindInt64(1, groupId);
1414 executeStatement(groupStatement);
1417 deleteTransaction.commit();
1419 checkForDeletedResources();
1424 void ApplicationCacheStorage::vacuumDatabaseFile()
1426 openDatabase(false);
1427 if (!m_database.isOpen())
1430 m_database.runVacuumCommand();
1433 void ApplicationCacheStorage::checkForMaxSizeReached()
1435 if (m_database.lastError() == SQLResultFull)
1436 m_isMaximumSizeReached = true;
1439 void ApplicationCacheStorage::checkForDeletedResources()
1441 openDatabase(false);
1442 if (!m_database.isOpen())
1445 // Select only the paths in DeletedCacheResources that do not also appear in CacheResourceData:
1446 SQLiteStatement selectPaths(m_database, "SELECT DeletedCacheResources.path "
1447 "FROM DeletedCacheResources "
1448 "LEFT JOIN CacheResourceData "
1449 "ON DeletedCacheResources.path = CacheResourceData.path "
1450 "WHERE (SELECT DeletedCacheResources.path == CacheResourceData.path) IS NULL");
1452 if (selectPaths.prepare() != SQLResultOk)
1455 if (selectPaths.step() != SQLResultRow)
1459 String path = selectPaths.getColumnText(0);
1463 String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
1464 String fullPath = pathByAppendingComponent(flatFileDirectory, path);
1466 // Don't exit the flatFileDirectory! This should only happen if the "path" entry contains a directory
1467 // component, but protect against it regardless.
1468 if (directoryName(fullPath) != flatFileDirectory)
1471 deleteFile(fullPath);
1472 } while (selectPaths.step() == SQLResultRow);
1474 executeSQLCommand("DELETE FROM DeletedCacheResources");
1477 long long ApplicationCacheStorage::flatFileAreaSize()
1479 openDatabase(false);
1480 if (!m_database.isOpen())
1483 SQLiteStatement selectPaths(m_database, "SELECT path FROM CacheResourceData WHERE path NOT NULL");
1485 if (selectPaths.prepare() != SQLResultOk) {
1486 LOG_ERROR("Could not load flat file cache resource data, error \"%s\"", m_database.lastErrorMsg());
1490 long long totalSize = 0;
1491 String flatFileDirectory = pathByAppendingComponent(m_cacheDirectory, flatFileSubdirectory);
1492 while (selectPaths.step() == SQLResultRow) {
1493 String path = selectPaths.getColumnText(0);
1494 String fullPath = pathByAppendingComponent(flatFileDirectory, path);
1495 long long pathSize = 0;
1496 if (!getFileSize(fullPath, pathSize))
1498 totalSize += pathSize;
1504 void ApplicationCacheStorage::getOriginsWithCache(HashSet<RefPtr<SecurityOrigin>, SecurityOriginHash>& origins)
1507 if (!manifestURLs(&urls)) {
1508 LOG_ERROR("Failed to retrieve ApplicationCache manifest URLs");
1512 // Multiple manifest URLs might share the same SecurityOrigin, so we might be creating extra, wasted origins here.
1513 // The current schema doesn't allow for a more efficient way of building this list.
1514 size_t count = urls.size();
1515 for (size_t i = 0; i < count; ++i) {
1516 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(urls[i]);
1517 origins.add(origin);
1521 void ApplicationCacheStorage::deleteAllEntries()
1524 vacuumDatabaseFile();
1527 ApplicationCacheStorage::ApplicationCacheStorage()
1528 : m_maximumSize(ApplicationCacheStorage::noQuota())
1529 , m_isMaximumSizeReached(false)
1530 , m_defaultOriginQuota(ApplicationCacheStorage::noQuota())
1534 ApplicationCacheStorage& cacheStorage()
1536 DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ());
1541 } // namespace WebCore
1543 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)