📄 applicationcachestorage.cpp
字号:
/* * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */#include "config.h"#include "ApplicationCacheStorage.h"#if ENABLE(OFFLINE_WEB_APPLICATIONS)#include "ApplicationCache.h"#include "ApplicationCacheGroup.h"#include "ApplicationCacheResource.h"#include "CString.h"#include "FileSystem.h"#include "KURL.h"#include "SQLiteStatement.h"#include "SQLiteTransaction.h"#include <wtf/StdLibExtras.h>#include <wtf/StringExtras.h>using namespace std;namespace WebCore {static unsigned urlHostHash(const KURL& url){ unsigned hostStart = url.hostStart(); unsigned hostEnd = url.hostEnd(); return AlreadyHashed::avoidDeletedValue(StringImpl::computeHash(url.string().characters() + hostStart, hostEnd - hostStart));}ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL){ openDatabase(false); if (!m_database.isOpen()) return 0; SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?"); if (statement.prepare() != SQLResultOk) return 0; statement.bindText(1, manifestURL); int result = statement.step(); if (result == SQLResultDone) return 0; if (result != SQLResultRow) { LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); return 0; } unsigned newestCacheStorageID = static_cast<unsigned>(statement.getColumnInt64(2)); RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID); if (!cache) return 0; ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); group->setNewestCache(cache.release()); return group;} ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL){ std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0); if (!result.second) { ASSERT(result.first->second); return result.first->second; } // Look up the group in the database ApplicationCacheGroup* group = loadCacheGroup(manifestURL); // If the group was not found we need to create it if (!group) { group = new ApplicationCacheGroup(manifestURL); m_cacheHostSet.add(urlHostHash(manifestURL)); } result.first->second = group; return group;}void ApplicationCacheStorage::loadManifestHostHashes(){ static bool hasLoadedHashes = false; if (hasLoadedHashes) return; // We set this flag to true before the database has been opened // to avoid trying to open the database over and over if it doesn't exist. hasLoadedHashes = true; openDatabase(false); if (!m_database.isOpen()) return; // Fetch the host hashes. SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups"); if (statement.prepare() != SQLResultOk) return; int result; while ((result = statement.step()) == SQLResultRow) m_cacheHostSet.add(static_cast<unsigned>(statement.getColumnInt64(0)));} ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url){ loadManifestHostHashes(); // Hash the host name and see if there's a manifest with the same host. if (!m_cacheHostSet.contains(urlHostHash(url))) return 0; // Check if a cache already exists in memory. CacheGroupMap::const_iterator end = m_cachesInMemory.end(); for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) { ApplicationCacheGroup* group = it->second; ASSERT(!group->isObsolete()); if (!protocolHostAndPortAreEqual(url, group->manifestURL())) continue; if (ApplicationCache* cache = group->newestCache()) { ApplicationCacheResource* resource = cache->resourceForURL(url); if (!resource) continue; if (resource->type() & ApplicationCacheResource::Foreign) continue; return group; } } if (!m_database.isOpen()) return 0; // Check the database. Look for all cache groups with a newest cache. SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL"); if (statement.prepare() != SQLResultOk) return 0; int result; while ((result = statement.step()) == SQLResultRow) { KURL manifestURL = KURL(statement.getColumnText(1)); if (m_cachesInMemory.contains(manifestURL)) continue; if (!protocolHostAndPortAreEqual(url, manifestURL)) continue; // We found a cache group that matches. Now check if the newest cache has a resource with // a matching URL. unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2)); RefPtr<ApplicationCache> cache = loadCache(newestCacheID); ApplicationCacheResource* resource = cache->resourceForURL(url); if (!resource) continue; if (resource->type() & ApplicationCacheResource::Foreign) continue; ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); group->setNewestCache(cache.release()); m_cachesInMemory.set(group->manifestURL(), group); return group; } if (result != SQLResultDone) LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); return 0;}ApplicationCacheGroup* ApplicationCacheStorage::fallbackCacheGroupForURL(const KURL& url){ // Check if an appropriate cache already exists in memory. CacheGroupMap::const_iterator end = m_cachesInMemory.end(); for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) { ApplicationCacheGroup* group = it->second; ASSERT(!group->isObsolete()); if (ApplicationCache* cache = group->newestCache()) { KURL fallbackURL; if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL)) continue; if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign) continue; return group; } } if (!m_database.isOpen()) return 0; // Check the database. Look for all cache groups with a newest cache. SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL"); if (statement.prepare() != SQLResultOk) return 0; int result; while ((result = statement.step()) == SQLResultRow) { KURL manifestURL = KURL(statement.getColumnText(1)); if (m_cachesInMemory.contains(manifestURL)) continue; // Fallback namespaces always have the same origin as manifest URL, so we can avoid loading caches that cannot match. if (!protocolHostAndPortAreEqual(url, manifestURL)) continue; // We found a cache group that matches. Now check if the newest cache has a resource with // a matching fallback namespace. unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2)); RefPtr<ApplicationCache> cache = loadCache(newestCacheID); KURL fallbackURL; if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL)) continue; if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign) continue; ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); group->setNewestCache(cache.release()); m_cachesInMemory.set(group->manifestURL(), group); return group; } if (result != SQLResultDone) LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); return 0;}void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group){ if (group->isObsolete()) { ASSERT(!group->storageID()); ASSERT(m_cachesInMemory.get(group->manifestURL()) != group); return; } ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); m_cachesInMemory.remove(group->manifestURL()); // If the cache group is half-created, we don't want it in the saved set (as it is not stored in database). if (!group->storageID()) m_cacheHostSet.remove(urlHostHash(group->manifestURL()));}void ApplicationCacheStorage::cacheGroupMadeObsolete(ApplicationCacheGroup* group){ ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); ASSERT(m_cacheHostSet.contains(urlHostHash(group->manifestURL()))); if (ApplicationCache* newestCache = group->newestCache()) remove(newestCache); m_cachesInMemory.remove(group->manifestURL()); m_cacheHostSet.remove(urlHostHash(group->manifestURL()));}void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory){ ASSERT(m_cacheDirectory.isNull()); ASSERT(!cacheDirectory.isNull()); m_cacheDirectory = cacheDirectory;}const String& ApplicationCacheStorage::cacheDirectory() const{ return m_cacheDirectory;}bool ApplicationCacheStorage::executeSQLCommand(const String& sql){ ASSERT(m_database.isOpen()); bool result = m_database.executeCommand(sql); if (!result) LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", sql.utf8().data(), m_database.lastErrorMsg()); return result;}static const int schemaVersion = 3; void ApplicationCacheStorage::verifySchemaVersion(){ int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0); if (version == schemaVersion) return; m_database.clearAllTables(); // Update user version. SQLiteTransaction setDatabaseVersion(m_database); setDatabaseVersion.begin(); char userVersionSQL[32]; int unusedNumBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion); ASSERT_UNUSED(unusedNumBytes, static_cast<int>(sizeof(userVersionSQL)) >= unusedNumBytes); SQLiteStatement statement(m_database, userVersionSQL); if (statement.prepare() != SQLResultOk) return; executeStatement(statement); setDatabaseVersion.commit();} void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist){ if (m_database.isOpen()) return; // 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)) return; makeAllDirectories(m_cacheDirectory); m_database.open(applicationCachePath); if (!m_database.isOpen()) return; verifySchemaVersion(); // 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 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)"); executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)"); executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, " "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)"); executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB)"); // When a cache is deleted, all its entries and its whitelist should be deleted. executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches" " FOR EACH ROW BEGIN" " DELETE FROM CacheEntries WHERE cache = OLD.id;" " DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;" " DELETE FROM FallbackURLs WHERE cache = OLD.id;" " END"); // When a cache entry is deleted, its resource should also be deleted. executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheEntryDeleted AFTER DELETE ON CacheEntries" " FOR EACH ROW BEGIN" " DELETE FROM CacheResources WHERE id = OLD.resource;" " END"); // When a cache resource is deleted, its data blob should also be deleted. executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources" " FOR EACH ROW BEGIN" " DELETE FROM CacheResourceData WHERE id = OLD.data;" " END");}bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement){ bool result = statement.executeCommand(); if (!result) LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", statement.query().utf8().data(), m_database.lastErrorMsg()); return result;} bool ApplicationCacheStorage::store(ApplicationCacheGroup* group){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -