summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authormichaeln@chromium.org <michaeln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-29 03:09:28 +0000
committermichaeln@chromium.org <michaeln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-29 03:09:28 +0000
commit5ac84ca4f92eea80f31b5c7a58e41e35657a69ab (patch)
tree8ddff436018b9628878fc5b0871060dd5dba93c2 /webkit
parent79ab45adb441cf439515b0a80c6e546b5cb5c2cd (diff)
downloadchromium_src-5ac84ca4f92eea80f31b5c7a58e41e35657a69ab.zip
chromium_src-5ac84ca4f92eea80f31b5c7a58e41e35657a69ab.tar.gz
chromium_src-5ac84ca4f92eea80f31b5c7a58e41e35657a69ab.tar.bz2
AppCacheDatabase and SQL based AppCacheStorageImpl.
Still nothing is being written to disk with this CL, in-memory SQLite and DiskCaches are being utilized. Responses are not yet being removed from the DiskCasche when the should be. Once that's done (in the next CL), we'll start saving things on disk. BUG=none TEST=appcache_database_unittest.cc, appcache_storage_impl_unittest.cc Review URL: http://codereview.chromium.org/501033 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@35328 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/appcache/appcache.cc78
-rw-r--r--webkit/appcache/appcache.h18
-rw-r--r--webkit/appcache/appcache_database.cc879
-rw-r--r--webkit/appcache/appcache_database.h159
-rw-r--r--webkit/appcache/appcache_database_unittest.cc439
-rw-r--r--webkit/appcache/appcache_group_unittest.cc5
-rw-r--r--webkit/appcache/appcache_host.cc2
-rw-r--r--webkit/appcache/appcache_host.h5
-rw-r--r--webkit/appcache/appcache_storage_impl.cc856
-rw-r--r--webkit/appcache/appcache_storage_impl.h92
-rw-r--r--webkit/appcache/appcache_storage_impl_unittest.cc1007
-rw-r--r--webkit/appcache/appcache_thread.h10
-rw-r--r--webkit/appcache/appcache_working_set.cc13
-rw-r--r--webkit/appcache/appcache_working_set.h15
-rw-r--r--webkit/appcache/mock_appcache_storage.cc12
-rw-r--r--webkit/tools/test_shell/simple_appcache_system.cc11
-rw-r--r--webkit/tools/test_shell/simple_appcache_system.h33
-rw-r--r--webkit/tools/test_shell/test_shell.gyp2
-rw-r--r--webkit/webkit.gyp4
19 files changed, 3609 insertions, 31 deletions
diff --git a/webkit/appcache/appcache.cc b/webkit/appcache/appcache.cc
index 38d3228..ec00cc4 100644
--- a/webkit/appcache/appcache.cc
+++ b/webkit/appcache/appcache.cc
@@ -79,6 +79,84 @@ void AppCache::InitializeWithManifest(Manifest* manifest) {
SortByLength);
}
+void AppCache::InitializeWithDatabaseRecords(
+ const AppCacheDatabase::CacheRecord& cache_record,
+ const std::vector<AppCacheDatabase::EntryRecord>& entries,
+ const std::vector<AppCacheDatabase::FallbackNameSpaceRecord>& fallbacks,
+ const std::vector<AppCacheDatabase::OnlineWhiteListRecord>& whitelists) {
+ DCHECK(cache_id_ == cache_record.cache_id);
+ online_whitelist_all_ = cache_record.online_wildcard;
+ update_time_ = cache_record.update_time;
+
+ for (size_t i = 0; i < entries.size(); ++i) {
+ const AppCacheDatabase::EntryRecord& entry = entries.at(i);
+ AddEntry(entry.url, AppCacheEntry(entry.flags, entry.response_id));
+ }
+
+ for (size_t i = 0; i < fallbacks.size(); ++i) {
+ const AppCacheDatabase::FallbackNameSpaceRecord& fallback = fallbacks.at(i);
+ fallback_namespaces_.push_back(
+ FallbackNamespace(fallback.namespace_url, fallback.fallback_entry_url));
+ }
+
+ // Sort the fallback namespaces by url string length, longest to shortest,
+ // since longer matches trump when matching a url to a namespace.
+ std::sort(fallback_namespaces_.begin(), fallback_namespaces_.end(),
+ SortByLength);
+
+ if (!online_whitelist_all_) {
+ for (size_t i = 0; i < whitelists.size(); ++i) {
+ online_whitelist_namespaces_.push_back(whitelists.at(i).namespace_url);
+ }
+ }
+}
+
+void AppCache::ToDatabaseRecords(
+ const AppCacheGroup* group,
+ AppCacheDatabase::CacheRecord* cache_record,
+ std::vector<AppCacheDatabase::EntryRecord>* entries,
+ std::vector<AppCacheDatabase::FallbackNameSpaceRecord>* fallbacks,
+ std::vector<AppCacheDatabase::OnlineWhiteListRecord>* whitelists) {
+ DCHECK(group && cache_record && entries && fallbacks && whitelists);
+ DCHECK(entries->empty() && fallbacks->empty() && whitelists->empty());
+
+ cache_record->cache_id = cache_id_;
+ cache_record->group_id = group->group_id();
+ cache_record->online_wildcard = online_whitelist_all_;
+ cache_record->update_time = update_time_;
+
+ for (EntryMap::const_iterator iter = entries_.begin();
+ iter != entries_.end(); ++iter) {
+ entries->push_back(AppCacheDatabase::EntryRecord());
+ AppCacheDatabase::EntryRecord& record = entries->back();
+ record.url = iter->first;
+ record.cache_id = cache_id_;
+ record.flags = iter->second.types();
+ record.response_id = iter->second.response_id();
+ }
+
+ GURL origin = group->manifest_url().GetOrigin();
+
+ for (size_t i = 0; i < fallback_namespaces_.size(); ++i) {
+ fallbacks->push_back(AppCacheDatabase::FallbackNameSpaceRecord());
+ AppCacheDatabase::FallbackNameSpaceRecord& record = fallbacks->back();
+ record.cache_id = cache_id_;
+ record.origin = origin;
+ record.namespace_url = fallback_namespaces_[i].first;
+ record.fallback_entry_url = fallback_namespaces_[i].second;
+ }
+
+ if (!online_whitelist_all_) {
+ for (size_t i = 0; i < online_whitelist_namespaces_.size(); ++i) {
+ whitelists->push_back(AppCacheDatabase::OnlineWhiteListRecord());
+ AppCacheDatabase::OnlineWhiteListRecord& record = whitelists->back();
+ record.cache_id = cache_id_;
+ record.namespace_url = online_whitelist_namespaces_[i];
+ }
+ }
+}
+
+
bool AppCache::FindResponseForRequest(const GURL& url,
AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
GURL* found_fallback_namespace, bool* found_network_namespace) {
diff --git a/webkit/appcache/appcache.h b/webkit/appcache/appcache.h
index 6e43a85..c4446f9 100644
--- a/webkit/appcache/appcache.h
+++ b/webkit/appcache/appcache.h
@@ -14,6 +14,7 @@
#include "base/time.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
+#include "webkit/appcache/appcache_database.h"
#include "webkit/appcache/appcache_entry.h"
#include "webkit/appcache/manifest_parser.h"
@@ -75,6 +76,22 @@ class AppCache : public base::RefCounted<AppCache> {
// Do not use the manifest after this call.
void InitializeWithManifest(Manifest* manifest);
+ // Initializes the cache with the information in the database records.
+ void InitializeWithDatabaseRecords(
+ const AppCacheDatabase::CacheRecord& cache_record,
+ const std::vector<AppCacheDatabase::EntryRecord>& entries,
+ const std::vector<AppCacheDatabase::FallbackNameSpaceRecord>& fallbacks,
+ const std::vector<AppCacheDatabase::OnlineWhiteListRecord>& whitelists);
+
+ // Returns the database records to be stored in the AppCacheDatabase
+ // to represent this cache.
+ void ToDatabaseRecords(
+ const AppCacheGroup* group,
+ AppCacheDatabase::CacheRecord* cache_record,
+ std::vector<AppCacheDatabase::EntryRecord>* entries,
+ std::vector<AppCacheDatabase::FallbackNameSpaceRecord>* fallbacks,
+ std::vector<AppCacheDatabase::OnlineWhiteListRecord>* whitelists);
+
bool FindResponseForRequest(const GURL& url,
AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
GURL* found_fallback_namespace, bool* found_network_namespace);
@@ -82,6 +99,7 @@ class AppCache : public base::RefCounted<AppCache> {
private:
friend class AppCacheGroup;
friend class AppCacheHost;
+ friend class AppCacheStorageImplTest;
friend class AppCacheUpdateJobTest;
friend class base::RefCounted<AppCache>;
diff --git a/webkit/appcache/appcache_database.cc b/webkit/appcache/appcache_database.cc
new file mode 100644
index 0000000..256def1
--- /dev/null
+++ b/webkit/appcache/appcache_database.cc
@@ -0,0 +1,879 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/appcache/appcache_database.h"
+
+#include "app/sql/connection.h"
+#include "app/sql/meta_table.h"
+#include "app/sql/statement.h"
+#include "app/sql/transaction.h"
+#include "base/auto_reset.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "webkit/appcache/appcache_entry.h"
+
+// Schema -------------------------------------------------------------------
+namespace {
+
+const int kCurrentVersion = 0;
+const int kCompatibleVersion = 0;
+
+const char* kGroupsTable = "Groups";
+const char* kCachesTable = "Caches";
+const char* kEntriesTable = "Entries";
+const char* kFallbackNameSpacesTable = "FallbackNameSpaces";
+const char* kOnlineWhiteListsTable = "OnlineWhiteLists";
+
+const struct {
+ const char* table_name;
+ const char* columns;
+} kTables[] = {
+ { kGroupsTable,
+ "(group_id INTEGER PRIMARY KEY,"
+ " origin TEXT,"
+ " manifest_url TEXT)" },
+
+ { kCachesTable,
+ "(cache_id INTEGER PRIMARY KEY,"
+ " group_id INTEGER,"
+ " online_wildcard INTEGER CHECK(online_wildcard IN (0, 1)),"
+ " update_time INTEGER)" },
+
+ { kEntriesTable,
+ "(cache_id INTEGER,"
+ " url TEXT,"
+ " flags INTEGER,"
+ " response_id INTEGER)" },
+
+ { kFallbackNameSpacesTable,
+ "(cache_id INTEGER,"
+ " origin TEXT," // intentionally not normalized
+ " namespace_url TEXT,"
+ " fallback_entry_url TEXT)" },
+
+ { kOnlineWhiteListsTable,
+ "(cache_id INTEGER,"
+ " namespace_url TEXT)" },
+};
+
+const struct {
+ const char* index_name;
+ const char* table_name;
+ const char* columns;
+ bool unique;
+} kIndexes[] = {
+ { "GroupsOriginIndex",
+ kGroupsTable,
+ "(origin)",
+ false },
+
+ { "GroupsManifestIndex",
+ kGroupsTable,
+ "(manifest_url)",
+ true },
+
+ { "CachesGroupIndex",
+ kCachesTable,
+ "(group_id)",
+ false },
+
+ { "EntriesCacheIndex",
+ kEntriesTable,
+ "(cache_id)",
+ false },
+
+ { "EntriesCacheAndUrlIndex",
+ kEntriesTable,
+ "(cache_id, url)",
+ true },
+
+ { "FallbackNameSpacesCacheIndex",
+ kFallbackNameSpacesTable,
+ "(cache_id)",
+ false },
+
+ { "FallbackNameSpacesOriginIndex",
+ kFallbackNameSpacesTable,
+ "(origin)",
+ false },
+
+ { "FallbackNameSpacesCacheAndUrlIndex",
+ kFallbackNameSpacesTable,
+ "(cache_id, namespace_url)",
+ true },
+
+ { "OnlineWhiteListCacheIndex",
+ kOnlineWhiteListsTable,
+ "(cache_id)",
+ false },
+};
+
+const int kTableCount = ARRAYSIZE_UNSAFE(kTables);
+const int kIndexCount = ARRAYSIZE_UNSAFE(kIndexes);
+
+} // anon namespace
+
+
+// AppCacheDatabase ----------------------------------------------------------
+namespace appcache {
+
+AppCacheDatabase::AppCacheDatabase(const FilePath& path)
+ : db_file_path_(path), has_open_error_(false), is_recreating_(false) {
+}
+
+AppCacheDatabase::~AppCacheDatabase() {
+}
+
+void AppCacheDatabase::CloseConnection() {
+ // We can't close the connection for an in-memory database w/o
+ // losing all of the data, so we don't do that.
+ if (!db_file_path_.empty()) {
+ meta_table_.reset();
+ db_.reset();
+ }
+}
+
+bool AppCacheDatabase::FindOriginsWithGroups(std::set<GURL>* origins) {
+ DCHECK(origins && origins->empty());
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT DISTINCT(origin) FROM Groups";
+
+ sql::Statement statement;
+ if (!PrepareUniqueStatement(kSql, &statement))
+ return false;
+
+ while (statement.Step())
+ origins->insert(GURL(statement.ColumnString(0)));
+
+ return statement.Succeeded();
+}
+
+bool AppCacheDatabase::FindLastStorageIds(
+ int64* last_group_id, int64* last_cache_id, int64* last_response_id) {
+ DCHECK(last_group_id && last_cache_id && last_response_id);
+
+ *last_group_id = 0;
+ *last_cache_id = 0;
+ *last_response_id = 0;
+
+ if (!LazyOpen(false))
+ return false;
+
+ sql::Statement statement;
+
+ const char* kSql1 = "SELECT MAX(group_id) FROM Groups";
+ if (!PrepareUniqueStatement(kSql1, &statement) ||
+ !statement.Step()) {
+ return false;
+ }
+ int64 group_id = statement.ColumnInt64(0);
+
+ const char* kSql2 = "SELECT MAX(cache_id) FROM Caches";
+ if (!PrepareUniqueStatement(kSql2, &statement) ||
+ !statement.Step()) {
+ return false;
+ }
+ int64 cache_id = statement.ColumnInt64(0);
+
+ // TODO(michaeln): SELECT MAX(responseId) FROM Where/How ?
+ int64 response_id = 0;
+
+ *last_group_id = group_id;
+ *last_cache_id = cache_id;
+ *last_response_id = response_id;
+ return true;
+}
+
+bool AppCacheDatabase::FindGroup(int64 group_id, GroupRecord* record) {
+ DCHECK(record);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT group_id, origin, manifest_url FROM Groups WHERE group_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, group_id);
+ if (!statement.Step() || !statement.Succeeded())
+ return false;
+
+ ReadGroupRecord(statement, record);
+ DCHECK(record->group_id == group_id);
+ return true;
+}
+
+bool AppCacheDatabase::FindGroupForManifestUrl(
+ const GURL& manifest_url, GroupRecord* record) {
+ DCHECK(record);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT group_id, origin, manifest_url FROM Groups"
+ " WHERE manifest_url = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindString(0, manifest_url.spec());
+ if (!statement.Step() || !statement.Succeeded())
+ return false;
+
+ ReadGroupRecord(statement, record);
+ DCHECK(record->manifest_url == manifest_url);
+ return true;
+}
+
+bool AppCacheDatabase::FindGroupsForOrigin(
+ const GURL& origin, std::vector<GroupRecord>* records) {
+ DCHECK(records && records->empty());
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT group_id, origin, manifest_url FROM Groups WHERE origin = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindString(0, origin.spec());
+ while (statement.Step()) {
+ records->push_back(GroupRecord());
+ ReadGroupRecord(statement, &records->back());
+ DCHECK(records->back().origin == origin);
+ }
+
+ return statement.Succeeded();
+}
+
+bool AppCacheDatabase::FindGroupForCache(int64 cache_id, GroupRecord* record) {
+ DCHECK(record);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT g.group_id, g.origin, g.manifest_url FROM Groups g, Caches c"
+ " WHERE c.cache_id = ? AND c.group_id = g.group_id";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ if (!statement.Step() || !statement.Succeeded())
+ return false;
+
+ ReadGroupRecord(statement, record);
+ return true;
+}
+
+bool AppCacheDatabase::InsertGroup(const GroupRecord* record) {
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql =
+ "INSERT INTO Groups (group_id, origin, manifest_url)"
+ " VALUES(?, ?, ?)";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, record->group_id);
+ statement.BindString(1, record->origin.spec());
+ statement.BindString(2, record->manifest_url.spec());
+ return statement.Run();
+}
+
+bool AppCacheDatabase::DeleteGroup(int64 group_id) {
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "DELETE FROM Groups WHERE group_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, group_id);
+ return statement.Run();
+}
+
+bool AppCacheDatabase::FindCache(int64 cache_id, CacheRecord* record) {
+ DCHECK(record);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT cache_id, group_id, online_wildcard, update_time"
+ " FROM Caches WHERE cache_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ if (!statement.Step() || !statement.Succeeded())
+ return false;
+
+ ReadCacheRecord(statement, record);
+ return true;
+}
+
+bool AppCacheDatabase::FindCacheForGroup(int64 group_id, CacheRecord* record) {
+ DCHECK(record);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT cache_id, group_id, online_wildcard, update_time"
+ " FROM Caches WHERE group_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, group_id);
+ if (!statement.Step() || !statement.Succeeded())
+ return false;
+
+ ReadCacheRecord(statement, record);
+ return true;
+}
+
+bool AppCacheDatabase::InsertCache(const CacheRecord* record) {
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql =
+ "INSERT INTO Caches (cache_id, group_id, online_wildcard,"
+ " update_time)"
+ " VALUES(?, ?, ?, ?)";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, record->cache_id);
+ statement.BindInt64(1, record->group_id);
+ statement.BindBool(2, record->online_wildcard);
+
+ // There are no convinient methods to convert TimeTicks to or
+ // from microseconds directly, so we compute TimeDelta's
+ // as an intermediary step.
+ statement.BindInt64(3,
+ (record->update_time - base::TimeTicks()).InMicroseconds());
+
+ return statement.Run();
+}
+
+bool AppCacheDatabase::DeleteCache(int64 cache_id) {
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "DELETE FROM Caches WHERE cache_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ return statement.Run();
+}
+
+bool AppCacheDatabase::FindEntriesForCache(
+ int64 cache_id, std::vector<EntryRecord>* records) {
+ DCHECK(records && records->empty());
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT cache_id, url, flags, response_id FROM Entries"
+ " WHERE cache_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ while (statement.Step()) {
+ records->push_back(EntryRecord());
+ ReadEntryRecord(statement, &records->back());
+ DCHECK(records->back().cache_id == cache_id);
+ }
+
+ return statement.Succeeded();
+}
+
+bool AppCacheDatabase::FindEntriesForUrl(
+ const GURL& url, std::vector<EntryRecord>* records) {
+ DCHECK(records && records->empty());
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT cache_id, url, flags, response_id FROM Entries"
+ " WHERE url = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindString(0, url.spec());
+ while (statement.Step()) {
+ records->push_back(EntryRecord());
+ ReadEntryRecord(statement, &records->back());
+ DCHECK(records->back().url == url);
+ }
+
+ return statement.Succeeded();
+}
+
+
+bool AppCacheDatabase::FindEntry(
+ int64 cache_id, const GURL& url, EntryRecord* record) {
+ DCHECK(record);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT cache_id, url, flags, response_id FROM Entries"
+ " WHERE cache_id = ? AND url = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ statement.BindString(1, url.spec());
+ if (!statement.Step() || !statement.Succeeded())
+ return false;
+
+ ReadEntryRecord(statement, record);
+ DCHECK(record->cache_id == cache_id);
+ DCHECK(record->url == url);
+ return true;
+}
+
+bool AppCacheDatabase::InsertEntry(const EntryRecord* record) {
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql =
+ "INSERT INTO Entries (cache_id, url, flags, response_id)"
+ " VALUES(?, ?, ?, ?)";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, record->cache_id);
+ statement.BindString(1, record->url.spec());
+ statement.BindInt(2, record->flags);
+ statement.BindInt64(3, record->response_id);
+ return statement.Run();
+}
+
+bool AppCacheDatabase::InsertEntryRecords(
+ const std::vector<EntryRecord>& records) {
+ std::vector<EntryRecord>::const_iterator iter = records.begin();
+ while (iter != records.end()) {
+ if (!InsertEntry(&(*iter)))
+ return false;
+ ++iter;
+ }
+ return true;
+}
+
+bool AppCacheDatabase::DeleteEntriesForCache(int64 cache_id) {
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "DELETE FROM Entries WHERE cache_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ return statement.Run();
+}
+
+bool AppCacheDatabase::AddEntryFlags(
+ const GURL& entry_url, int64 cache_id, int additional_flags) {
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "UPDATE Entries SET flags = flags | ? WHERE cache_id = ? AND url = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt(0, additional_flags);
+ statement.BindInt64(1, cache_id);
+ statement.BindString(2, entry_url.spec());
+ return statement.Run() && db_->GetLastChangeCount();
+}
+
+bool AppCacheDatabase::FindFallbackNameSpacesForOrigin(
+ const GURL& origin, std::vector<FallbackNameSpaceRecord>* records) {
+ DCHECK(records && records->empty());
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT cache_id, origin, namespace_url, fallback_entry_url"
+ " FROM FallbackNameSpaces WHERE origin = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindString(0, origin.spec());
+ while (statement.Step()) {
+ records->push_back(FallbackNameSpaceRecord());
+ ReadFallbackNameSpaceRecord(statement, &records->back());
+ DCHECK(records->back().origin == origin);
+ }
+ return statement.Succeeded();
+}
+
+bool AppCacheDatabase::FindFallbackNameSpacesForCache(
+ int64 cache_id, std::vector<FallbackNameSpaceRecord>* records) {
+ DCHECK(records && records->empty());
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT cache_id, origin, namespace_url, fallback_entry_url"
+ " FROM FallbackNameSpaces WHERE cache_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ while (statement.Step()) {
+ records->push_back(FallbackNameSpaceRecord());
+ ReadFallbackNameSpaceRecord(statement, &records->back());
+ DCHECK(records->back().cache_id == cache_id);
+ }
+ return statement.Succeeded();
+}
+
+bool AppCacheDatabase::InsertFallbackNameSpace(
+ const FallbackNameSpaceRecord* record) {
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql =
+ "INSERT INTO FallbackNameSpaces"
+ " (cache_id, origin, namespace_url, fallback_entry_url)"
+ " VALUES (?, ?, ?, ?)";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, record->cache_id);
+ statement.BindString(1, record->origin.spec());
+ statement.BindString(2, record->namespace_url.spec());
+ statement.BindString(3, record->fallback_entry_url.spec());
+ return statement.Run();
+}
+
+bool AppCacheDatabase::InsertFallbackNameSpaceRecords(
+ const std::vector<FallbackNameSpaceRecord>& records) {
+ std::vector<FallbackNameSpaceRecord>::const_iterator iter = records.begin();
+ while (iter != records.end()) {
+ if (!InsertFallbackNameSpace(&(*iter)))
+ return false;
+ ++iter;
+ }
+ return true;
+}
+
+bool AppCacheDatabase::DeleteFallbackNameSpacesForCache(int64 cache_id) {
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "DELETE FROM FallbackNameSpaces WHERE cache_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ return statement.Run();
+}
+
+bool AppCacheDatabase::FindOnlineWhiteListForCache(
+ int64 cache_id, std::vector<OnlineWhiteListRecord>* records) {
+ DCHECK(records && records->empty());
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT cache_id, namespace_url FROM OnlineWhiteLists"
+ " WHERE cache_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ while (statement.Step()) {
+ records->push_back(OnlineWhiteListRecord());
+ this->ReadOnlineWhiteListRecord(statement, &records->back());
+ DCHECK(records->back().cache_id == cache_id);
+ }
+ return statement.Succeeded();
+}
+
+bool AppCacheDatabase::InsertOnlineWhiteList(
+ const OnlineWhiteListRecord* record) {
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql =
+ "INSERT INTO OnlineWhiteLists (cache_id, namespace_url) VALUES (?, ?)";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, record->cache_id);
+ statement.BindString(1, record->namespace_url.spec());
+ return statement.Run();
+}
+
+bool AppCacheDatabase::InsertOnlineWhiteListRecords(
+ const std::vector<OnlineWhiteListRecord>& records) {
+ std::vector<OnlineWhiteListRecord>::const_iterator iter = records.begin();
+ while (iter != records.end()) {
+ if (!InsertOnlineWhiteList(&(*iter)))
+ return false;
+ ++iter;
+ }
+ return true;
+}
+
+bool AppCacheDatabase::DeleteOnlineWhiteListForCache(int64 cache_id) {
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "DELETE FROM OnlineWhiteLists WHERE cache_id = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, cache_id);
+ return statement.Run();
+}
+
+bool AppCacheDatabase::PrepareUniqueStatement(
+ const char* sql, sql::Statement* statement) {
+ DCHECK(sql && statement);
+ statement->Assign(db_->GetUniqueStatement(sql));
+ if (!statement->is_valid()) {
+ NOTREACHED() << db_->GetErrorMessage();
+ return false;
+ }
+ return true;
+}
+
+bool AppCacheDatabase::PrepareCachedStatement(
+ const sql::StatementID& id, const char* sql, sql::Statement* statement) {
+ DCHECK(sql && statement);
+ statement->Assign(db_->GetCachedStatement(id, sql));
+ if (!statement->is_valid()) {
+ NOTREACHED() << db_->GetErrorMessage();
+ return false;
+ }
+ return true;
+}
+
+void AppCacheDatabase::ReadGroupRecord(
+ const sql::Statement& statement, GroupRecord* record) {
+ record->group_id = statement.ColumnInt64(0);
+ record->origin = GURL(statement.ColumnString(1));
+ record->manifest_url = GURL(statement.ColumnString(2));
+}
+
+void AppCacheDatabase::ReadCacheRecord(
+ const sql::Statement& statement, CacheRecord* record) {
+ record->cache_id = statement.ColumnInt64(0);
+ record->group_id = statement.ColumnInt64(1);
+ record->online_wildcard = statement.ColumnBool(2);
+
+ // There are no convinient methods to convert TimeTicks to or
+ // from microseconds directly, so we compute TimeDelta's
+ // as an intermediary step.
+ record->update_time = base::TimeTicks() +
+ base::TimeDelta::FromMicroseconds(statement.ColumnInt64(3));
+}
+
+void AppCacheDatabase::ReadEntryRecord(
+ const sql::Statement& statement, EntryRecord* record) {
+ record->cache_id = statement.ColumnInt64(0);
+ record->url = GURL(statement.ColumnString(1));
+ record->flags = statement.ColumnInt(2);
+ record->response_id = statement.ColumnInt64(3);
+}
+
+void AppCacheDatabase::ReadFallbackNameSpaceRecord(
+ const sql::Statement& statement, FallbackNameSpaceRecord* record) {
+ record->cache_id = statement.ColumnInt64(0);
+ record->origin = GURL(statement.ColumnString(1));
+ record->namespace_url = GURL(statement.ColumnString(2));
+ record->fallback_entry_url = GURL(statement.ColumnString(3));
+}
+
+void AppCacheDatabase::ReadOnlineWhiteListRecord(
+ const sql::Statement& statement, OnlineWhiteListRecord* record) {
+ record->cache_id = statement.ColumnInt64(0);
+ record->namespace_url = GURL(statement.ColumnString(1));
+}
+
+bool AppCacheDatabase::LazyOpen(bool create_if_needed) {
+ if (db_.get())
+ return true;
+
+ // If we tried and failed once, don't try again in the same session
+ // to avoid creating an incoherent mess on disk.
+ if (has_open_error_)
+ return false;
+
+ // Avoid creating a database at all if we can.
+ bool use_in_memory_db = db_file_path_.empty();
+ if (!create_if_needed &&
+ (use_in_memory_db || !file_util::PathExists(db_file_path_))) {
+ return false;
+ }
+
+ db_.reset(new sql::Connection);
+ bool opened = use_in_memory_db ? db_->OpenInMemory()
+ : db_->Open(db_file_path_);
+ if (opened)
+ db_->Preload();
+ if (!opened || !EnsureDatabaseVersion()) {
+ has_open_error_ = true;
+ meta_table_.reset();
+ db_.reset();
+ return false;
+ }
+
+ return true;
+}
+
+bool AppCacheDatabase::EnsureDatabaseVersion() {
+ bool did_create_meta_table = !sql::MetaTable::DoesTableExist(db_.get());
+
+ meta_table_.reset(new sql::MetaTable);
+ if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
+ return false;
+
+ if (did_create_meta_table)
+ return CreateSchema();
+
+ if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
+ DCHECK(!did_create_meta_table);
+ LOG(WARNING) << "AppCache database is too new.";
+ return false;
+ }
+
+ if (meta_table_->GetVersionNumber() < kCurrentVersion)
+ return UpgradeSchema();
+
+#ifndef NDEBUG
+ for (int i = 0; i < kTableCount; ++i) {
+ DCHECK(db_->DoesTableExist(kTables[i].table_name));
+ }
+#endif
+
+ return true;
+}
+
+bool AppCacheDatabase::CreateSchema() {
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+
+ for (int i = 0; i < kTableCount; ++i) {
+ std::string sql("CREATE TABLE ");
+ sql += kTables[i].table_name;
+ sql += kTables[i].columns;
+ if (!db_->Execute(sql.c_str()))
+ return false;
+ }
+
+ for (int i = 0; i < kIndexCount; ++i) {
+ std::string sql;
+ if (kIndexes[i].unique)
+ sql += "CREATE UNIQUE INDEX ";
+ else
+ sql += "CREATE INDEX ";
+ sql += kIndexes[i].index_name;
+ sql += " ON ";
+ sql += kIndexes[i].table_name;
+ sql += kIndexes[i].columns;
+ if (!db_->Execute(sql.c_str()))
+ return false;
+ }
+
+ return transaction.Commit();
+}
+
+bool AppCacheDatabase::UpgradeSchema() {
+ DCHECK(false); // We don't have any upgrades yet since we're at version 0
+
+ // Upgrade logic goes here
+
+ // If there is no upgrade path for the version on disk to the current
+ // version, nuke everything and start over.
+ return DeleteExistingAndCreateNewDatabase();
+}
+
+bool AppCacheDatabase::DeleteExistingAndCreateNewDatabase() {
+ DCHECK(!db_file_path_.empty());
+ DCHECK(file_util::PathExists(db_file_path_));
+
+ meta_table_.reset();
+ db_.reset();
+
+ FilePath directory = db_file_path_.DirName();
+ if (!file_util::Delete(directory, true) ||
+ !file_util::CreateDirectory(directory)) {
+ return false;
+ }
+
+ // Make sure the steps above actually deleted things.
+ if (file_util::PathExists(db_file_path_))
+ return false;
+
+ // So we can't go recursive.
+ if (is_recreating_)
+ return false;
+
+ AutoReset auto_reset(&is_recreating_, true);
+ return LazyOpen(true);
+}
+
+} // namespace appcache
diff --git a/webkit/appcache/appcache_database.h b/webkit/appcache/appcache_database.h
new file mode 100644
index 0000000..89ff701
--- /dev/null
+++ b/webkit/appcache/appcache_database.h
@@ -0,0 +1,159 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_APPCACHE_APPCACHE_DATABASE_H_
+#define WEBKIT_APPCACHE_APPCACHE_DATABASE_H_
+
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace sql {
+class Connection;
+class MetaTable;
+class Statement;
+class StatementID;
+}
+
+namespace appcache {
+
+class AppCacheDatabase {
+ public:
+ struct GroupRecord {
+ int64 group_id;
+ GURL origin;
+ GURL manifest_url;
+ };
+
+ struct CacheRecord {
+ int64 cache_id;
+ int64 group_id;
+ bool online_wildcard;
+ base::TimeTicks update_time;
+ };
+
+ struct EntryRecord {
+ int64 cache_id;
+ GURL url;
+ int flags;
+ int64 response_id;
+ };
+
+ struct FallbackNameSpaceRecord {
+ int64 cache_id;
+ GURL origin; // intentionally not normalized
+ GURL namespace_url;
+ GURL fallback_entry_url;
+ };
+
+ struct OnlineWhiteListRecord {
+ int64 cache_id;
+ GURL namespace_url;
+ };
+
+ explicit AppCacheDatabase(const FilePath& path);
+ ~AppCacheDatabase();
+
+ void CloseConnection();
+ bool FindOriginsWithGroups(std::set<GURL>* origins);
+ bool FindLastStorageIds(
+ int64* last_group_id, int64* last_cache_id, int64* last_response_id);
+
+ bool FindGroup(int64 group_id, GroupRecord* record);
+ bool FindGroupForManifestUrl(const GURL& manifest_url, GroupRecord* record);
+ bool FindGroupsForOrigin(
+ const GURL& origin, std::vector<GroupRecord>* records);
+ bool FindGroupForCache(int64 cache_id, GroupRecord* record);
+ bool InsertGroup(const GroupRecord* record);
+ bool DeleteGroup(int64 group_id);
+
+ bool FindCache(int64 cache_id, CacheRecord* record);
+ bool FindCacheForGroup(int64 group_id, CacheRecord* record);
+ bool InsertCache(const CacheRecord* record);
+ bool DeleteCache(int64 cache_id);
+
+ bool FindEntriesForCache(
+ int64 cache_id, std::vector<EntryRecord>* records);
+ bool FindEntriesForUrl(
+ const GURL& url, std::vector<EntryRecord>* records);
+ bool FindEntry(int64 cache_id, const GURL& url, EntryRecord* record);
+ bool InsertEntry(const EntryRecord* record);
+ bool InsertEntryRecords(
+ const std::vector<EntryRecord>& records);
+ bool DeleteEntriesForCache(int64 cache_id);
+ bool AddEntryFlags(const GURL& entry_url, int64 cache_id,
+ int additional_flags);
+
+ bool FindFallbackNameSpacesForOrigin(
+ const GURL& origin, std::vector<FallbackNameSpaceRecord>* records);
+ bool FindFallbackNameSpacesForCache(
+ int64 cache_id, std::vector<FallbackNameSpaceRecord>* records);
+ bool InsertFallbackNameSpace(const FallbackNameSpaceRecord* record);
+ bool InsertFallbackNameSpaceRecords(
+ const std::vector<FallbackNameSpaceRecord>& records);
+ bool DeleteFallbackNameSpacesForCache(int64 cache_id);
+
+ bool FindOnlineWhiteListForCache(
+ int64 cache_id, std::vector<OnlineWhiteListRecord>* records);
+ bool InsertOnlineWhiteList(const OnlineWhiteListRecord* record);
+ bool InsertOnlineWhiteListRecords(
+ const std::vector<OnlineWhiteListRecord>& records);
+ bool DeleteOnlineWhiteListForCache(int64 cache_id);
+
+ // So our callers can wrap operations in transactions.
+ sql::Connection* db_connection() {
+ LazyOpen(true);
+ return db_.get();
+ }
+
+ private:
+ bool PrepareUniqueStatement(const char* sql, sql::Statement* statement);
+ bool PrepareCachedStatement(
+ const sql::StatementID& id, const char* sql, sql::Statement* statement);
+
+ // Record retrieval helpers
+ void ReadGroupRecord(const sql::Statement& statement, GroupRecord* record);
+ void ReadCacheRecord(const sql::Statement& statement, CacheRecord* record);
+ void ReadEntryRecord(const sql::Statement& statement, EntryRecord* record);
+ void ReadFallbackNameSpaceRecord(
+ const sql::Statement& statement, FallbackNameSpaceRecord* record);
+ void ReadOnlineWhiteListRecord(
+ const sql::Statement& statement, OnlineWhiteListRecord* record);
+
+ // Database creation
+ bool LazyOpen(bool create_if_needed);
+ bool EnsureDatabaseVersion();
+ bool CreateSchema();
+ bool UpgradeSchema();
+
+ // Deletes the existing database file and the entire directory containing
+ // the database file including the disk cache in which response headers
+ // and bodies are stored, and then creates a new database file.
+ bool DeleteExistingAndCreateNewDatabase();
+
+ FilePath db_file_path_;
+ scoped_ptr<sql::Connection> db_;
+ scoped_ptr<sql::MetaTable> meta_table_;
+ bool has_open_error_;
+ bool is_recreating_;
+
+ FRIEND_TEST(AppCacheDatabaseTest, CacheRecords);
+ FRIEND_TEST(AppCacheDatabaseTest, EntryRecords);
+ FRIEND_TEST(AppCacheDatabaseTest, FallbackNameSpaceRecords);
+ FRIEND_TEST(AppCacheDatabaseTest, GroupRecords);
+ FRIEND_TEST(AppCacheDatabaseTest, LazyOpen);
+ FRIEND_TEST(AppCacheDatabaseTest, OnlineWhiteListRecords);
+ FRIEND_TEST(AppCacheDatabaseTest, ReCreate);
+ DISALLOW_COPY_AND_ASSIGN(AppCacheDatabase);
+};
+
+} // namespace appcache
+
+#endif // WEBKIT_APPCACHE_APPCACHE_DATABASE_H_
diff --git a/webkit/appcache/appcache_database_unittest.cc b/webkit/appcache/appcache_database_unittest.cc
new file mode 100644
index 0000000..62ff4ca
--- /dev/null
+++ b/webkit/appcache/appcache_database_unittest.cc
@@ -0,0 +1,439 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "app/sql/connection.h"
+#include "base/file_util.h"
+#include "base/scoped_temp_dir.h"
+#include "webkit/appcache/appcache_database.h"
+#include "webkit/appcache/appcache_entry.h"
+
+namespace {
+
+const base::TimeTicks kZeroTimeTicks;
+
+class TestErrorDelegate : public sql::ErrorDelegate {
+ public:
+ virtual ~TestErrorDelegate() { }
+ virtual int OnError(
+ int error, sql::Connection* connection, sql::Statement* stmt) {
+ return error;
+ }
+};
+
+} // namespace
+
+namespace appcache {
+
+class AppCacheDatabaseTest {};
+
+TEST(AppCacheDatabaseTest, LazyOpen) {
+ // Use an empty file path to use an in-memory sqlite database.
+ const FilePath kEmptyPath;
+ AppCacheDatabase db(kEmptyPath);
+
+ EXPECT_FALSE(db.LazyOpen(false));
+ EXPECT_TRUE(db.LazyOpen(true));
+
+ int64 group_id, cache_id, response_id;
+ group_id = cache_id = response_id = 0;
+ EXPECT_TRUE(db.FindLastStorageIds(&group_id, &cache_id, &response_id));
+ EXPECT_EQ(0, group_id);
+ EXPECT_EQ(0, cache_id);
+ EXPECT_EQ(0, response_id);
+
+ std::set<GURL> origins;
+ EXPECT_TRUE(db.FindOriginsWithGroups(&origins));
+ EXPECT_TRUE(origins.empty());
+}
+
+TEST(AppCacheDatabaseTest, ReCreate) {
+ // Real files on disk for this test.
+ ScopedTempDir temp_dir;
+ EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
+ const FilePath kDbFile = temp_dir.path().AppendASCII("appcache.db");
+ const FilePath kNestedDir = temp_dir.path().AppendASCII("nested");
+ const FilePath kOtherFile = kNestedDir.AppendASCII("other_file");
+ EXPECT_TRUE(file_util::CreateDirectory(kNestedDir));
+ EXPECT_EQ(3, file_util::WriteFile(kOtherFile, "foo", 3));
+
+ AppCacheDatabase db(kDbFile);
+ EXPECT_FALSE(db.LazyOpen(false));
+ EXPECT_TRUE(db.LazyOpen(true));
+
+ EXPECT_TRUE(file_util::PathExists(kDbFile));
+ EXPECT_TRUE(file_util::DirectoryExists(kNestedDir));
+ EXPECT_TRUE(file_util::PathExists(kOtherFile));
+
+ EXPECT_TRUE(db.DeleteExistingAndCreateNewDatabase());
+
+ EXPECT_TRUE(file_util::PathExists(kDbFile));
+ EXPECT_FALSE(file_util::DirectoryExists(kNestedDir));
+ EXPECT_FALSE(file_util::PathExists(kOtherFile));
+}
+
+TEST(AppCacheDatabaseTest, EntryRecords) {
+ const FilePath kEmptyPath;
+ AppCacheDatabase db(kEmptyPath);
+ EXPECT_TRUE(db.LazyOpen(true));
+
+ // Set an error delegate that will make all operations return false on error.
+ scoped_refptr<TestErrorDelegate> error_delegate(new TestErrorDelegate);
+ db.db_->set_error_delegate(error_delegate);
+
+ AppCacheDatabase::EntryRecord entry;
+
+ entry.cache_id = 1;
+ entry.url = GURL("http://blah/1");
+ entry.flags = AppCacheEntry::MASTER;
+ entry.response_id = 1;
+ EXPECT_TRUE(db.InsertEntry(&entry));
+ EXPECT_FALSE(db.InsertEntry(&entry));
+
+ entry.cache_id = 2;
+ entry.url = GURL("http://blah/2");
+ entry.flags = AppCacheEntry::EXPLICIT;
+ entry.response_id = 2;
+ EXPECT_TRUE(db.InsertEntry(&entry));
+
+ entry.cache_id = 2;
+ entry.url = GURL("http://blah/3");
+ entry.flags = AppCacheEntry::MANIFEST;
+ entry.response_id = 3;
+ EXPECT_TRUE(db.InsertEntry(&entry));
+
+ std::vector<AppCacheDatabase::EntryRecord> found;
+
+ EXPECT_TRUE(db.FindEntriesForCache(1, &found));
+ EXPECT_EQ(1U, found.size());
+ EXPECT_EQ(1, found[0].cache_id);
+ EXPECT_EQ(GURL("http://blah/1"), found[0].url);
+ EXPECT_EQ(AppCacheEntry::MASTER, found[0].flags);
+ EXPECT_EQ(1, found[0].response_id);
+ found.clear();
+
+ EXPECT_TRUE(db.AddEntryFlags(GURL("http://blah/1"), 1,
+ AppCacheEntry::FOREIGN));
+ EXPECT_TRUE(db.FindEntriesForCache(1, &found));
+ EXPECT_EQ(1U, found.size());
+ EXPECT_EQ(AppCacheEntry::MASTER | AppCacheEntry::FOREIGN, found[0].flags);
+ found.clear();
+
+ EXPECT_TRUE(db.FindEntriesForCache(2, &found));
+ EXPECT_EQ(2U, found.size());
+ EXPECT_EQ(2, found[0].cache_id);
+ EXPECT_EQ(GURL("http://blah/2"), found[0].url);
+ EXPECT_EQ(AppCacheEntry::EXPLICIT, found[0].flags);
+ EXPECT_EQ(2, found[0].response_id);
+ EXPECT_EQ(2, found[1].cache_id);
+ EXPECT_EQ(GURL("http://blah/3"), found[1].url);
+ EXPECT_EQ(AppCacheEntry::MANIFEST, found[1].flags);
+ EXPECT_EQ(3, found[1].response_id);
+ found.clear();
+
+ EXPECT_TRUE(db.DeleteEntriesForCache(2));
+ EXPECT_TRUE(db.FindEntriesForCache(2, &found));
+ EXPECT_TRUE(found.empty());
+ found.clear();
+
+ EXPECT_TRUE(db.DeleteEntriesForCache(1));
+ EXPECT_FALSE(db.AddEntryFlags(GURL("http://blah/1"), 1,
+ AppCacheEntry::FOREIGN));
+}
+
+TEST(AppCacheDatabaseTest, CacheRecords) {
+ const FilePath kEmptyPath;
+ AppCacheDatabase db(kEmptyPath);
+ EXPECT_TRUE(db.LazyOpen(true));
+
+ scoped_refptr<TestErrorDelegate> error_delegate(new TestErrorDelegate);
+ db.db_->set_error_delegate(error_delegate);
+
+ const AppCacheDatabase::CacheRecord kZeroRecord = {0};
+ AppCacheDatabase::CacheRecord record = kZeroRecord;
+ EXPECT_FALSE(db.FindCache(1, &record));
+
+ record.cache_id = 1;
+ record.group_id = 1;
+ record.online_wildcard = true;
+ record.update_time = kZeroTimeTicks;
+ EXPECT_TRUE(db.InsertCache(&record));
+ EXPECT_FALSE(db.InsertCache(&record));
+
+ record = kZeroRecord;
+ EXPECT_TRUE(db.FindCache(1, &record));
+ EXPECT_EQ(1, record.cache_id);
+ EXPECT_EQ(1, record.group_id);
+ EXPECT_TRUE(record.online_wildcard);
+ EXPECT_TRUE(kZeroTimeTicks == record.update_time);
+
+ record = kZeroRecord;
+ EXPECT_TRUE(db.FindCacheForGroup(1, &record));
+ EXPECT_EQ(1, record.cache_id);
+ EXPECT_EQ(1, record.group_id);
+ EXPECT_TRUE(record.online_wildcard);
+ EXPECT_TRUE(kZeroTimeTicks == record.update_time);
+
+ EXPECT_TRUE(db.DeleteCache(1));
+ EXPECT_FALSE(db.FindCache(1, &record));
+ EXPECT_FALSE(db.FindCacheForGroup(1, &record));
+
+ EXPECT_TRUE(db.DeleteCache(1));
+}
+
+TEST(AppCacheDatabaseTest, GroupRecords) {
+ const FilePath kEmptyPath;
+ AppCacheDatabase db(kEmptyPath);
+ EXPECT_TRUE(db.LazyOpen(true));
+
+ scoped_refptr<TestErrorDelegate> error_delegate(new TestErrorDelegate);
+ db.db_->set_error_delegate(error_delegate);
+
+ const GURL kManifestUrl("http://blah/manifest");
+ const GURL kOrigin(kManifestUrl.GetOrigin());
+
+ const AppCacheDatabase::GroupRecord kZeroRecord = {0, GURL(), GURL()};
+ AppCacheDatabase::GroupRecord record = kZeroRecord;
+ std::vector<AppCacheDatabase::GroupRecord> records;
+
+ // Behavior with an empty table
+ EXPECT_FALSE(db.FindGroup(1, &record));
+ EXPECT_FALSE(db.FindGroupForManifestUrl(kManifestUrl, &record));
+ EXPECT_TRUE(db.DeleteGroup(1));
+ EXPECT_TRUE(db.FindGroupsForOrigin(kOrigin, &records));
+ EXPECT_TRUE(records.empty());
+ EXPECT_FALSE(db.FindGroupForCache(1, &record));
+
+ record.group_id = 1;
+ record.manifest_url = kManifestUrl;
+ record.origin = kOrigin;
+ EXPECT_TRUE(db.InsertGroup(&record));
+ EXPECT_FALSE(db.InsertGroup(&record));
+
+ record.group_id = 2;
+ EXPECT_FALSE(db.InsertGroup(&record));
+
+ record = kZeroRecord;
+ EXPECT_TRUE(db.FindGroup(1, &record));
+ EXPECT_EQ(1, record.group_id);
+ EXPECT_EQ(kManifestUrl, record.manifest_url);
+ EXPECT_EQ(kOrigin, record.origin);
+
+ record = kZeroRecord;
+ EXPECT_TRUE(db.FindGroupForManifestUrl(kManifestUrl, &record));
+ EXPECT_EQ(1, record.group_id);
+ EXPECT_EQ(kManifestUrl, record.manifest_url);
+ EXPECT_EQ(kOrigin, record.origin);
+
+ record.group_id = 2;
+ record.manifest_url = kOrigin;
+ record.origin = kOrigin;
+ EXPECT_TRUE(db.InsertGroup(&record));
+
+ record = kZeroRecord;
+ EXPECT_TRUE(db.FindGroupForManifestUrl(kOrigin, &record));
+ EXPECT_EQ(2, record.group_id);
+ EXPECT_EQ(kOrigin, record.manifest_url);
+ EXPECT_EQ(kOrigin, record.origin);
+
+ EXPECT_TRUE(db.FindGroupsForOrigin(kOrigin, &records));
+ EXPECT_EQ(2U, records.size());
+ EXPECT_EQ(1, records[0].group_id);
+ EXPECT_EQ(kManifestUrl, records[0].manifest_url);
+ EXPECT_EQ(kOrigin, records[0].origin);
+ EXPECT_EQ(2, records[1].group_id);
+ EXPECT_EQ(kOrigin, records[1].manifest_url);
+ EXPECT_EQ(kOrigin, records[1].origin);
+
+ EXPECT_TRUE(db.DeleteGroup(1));
+
+ records.clear();
+ EXPECT_TRUE(db.FindGroupsForOrigin(kOrigin, &records));
+ EXPECT_EQ(1U, records.size());
+ EXPECT_EQ(2, records[0].group_id);
+ EXPECT_EQ(kOrigin, records[0].manifest_url);
+ EXPECT_EQ(kOrigin, records[0].origin);
+
+ std::set<GURL> origins;
+ EXPECT_TRUE(db.FindOriginsWithGroups(&origins));
+ EXPECT_EQ(1U, origins.size());
+ EXPECT_EQ(kOrigin, *(origins.begin()));
+
+ const GURL kManifest2("http://blah2/manifest");
+ const GURL kOrigin2(kManifest2.GetOrigin());
+ record.group_id = 1;
+ record.manifest_url = kManifest2;
+ record.origin = kOrigin2;
+ EXPECT_TRUE(db.InsertGroup(&record));
+
+ origins.clear();
+ EXPECT_TRUE(db.FindOriginsWithGroups(&origins));
+ EXPECT_EQ(2U, origins.size());
+ EXPECT_TRUE(origins.end() != origins.find(kOrigin));
+ EXPECT_TRUE(origins.end() != origins.find(kOrigin2));
+
+ AppCacheDatabase::CacheRecord cache_record;
+ cache_record.cache_id = 1;
+ cache_record.group_id = 1;
+ cache_record.online_wildcard = true;
+ cache_record.update_time = kZeroTimeTicks;
+ EXPECT_TRUE(db.InsertCache(&cache_record));
+
+ record = kZeroRecord;
+ EXPECT_TRUE(db.FindGroupForCache(1, &record));
+ EXPECT_EQ(1, record.group_id);
+ EXPECT_EQ(kManifest2, record.manifest_url);
+ EXPECT_EQ(kOrigin2, record.origin);
+}
+
+TEST(AppCacheDatabaseTest, FallbackNameSpaceRecords) {
+ const FilePath kEmptyPath;
+ AppCacheDatabase db(kEmptyPath);
+ EXPECT_TRUE(db.LazyOpen(true));
+
+ scoped_refptr<TestErrorDelegate> error_delegate(new TestErrorDelegate);
+ db.db_->set_error_delegate(error_delegate);
+
+ const GURL kFooNameSpace1("http://foo/namespace1");
+ const GURL kFooNameSpace2("http://foo/namespace2");
+ const GURL kFooFallbackEntry("http://foo/entry");
+ const GURL kFooOrigin(kFooNameSpace1.GetOrigin());
+ const GURL kBarNameSpace1("http://bar/namespace1");
+ const GURL kBarNameSpace2("http://bar/namespace2");
+ const GURL kBarFallbackEntry("http://bar/entry");
+ const GURL kBarOrigin(kBarNameSpace1.GetOrigin());
+
+ const AppCacheDatabase::FallbackNameSpaceRecord kZeroRecord =
+ { 0, GURL(), GURL(), GURL() };
+ AppCacheDatabase::FallbackNameSpaceRecord record = kZeroRecord;
+ std::vector<AppCacheDatabase::FallbackNameSpaceRecord> records;
+
+ // Behavior with an empty table
+ EXPECT_TRUE(db.FindFallbackNameSpacesForCache(1, &records));
+ EXPECT_TRUE(records.empty());
+ EXPECT_TRUE(db.FindFallbackNameSpacesForOrigin(kFooOrigin, &records));
+ EXPECT_TRUE(records.empty());
+ EXPECT_TRUE(db.DeleteFallbackNameSpacesForCache(1));
+
+ // Two records for two differenent caches in the Foo origin.
+ record.cache_id = 1;
+ record.origin = kFooOrigin;
+ record.namespace_url = kFooNameSpace1;
+ record.fallback_entry_url = kFooFallbackEntry;
+ EXPECT_TRUE(db.InsertFallbackNameSpace(&record));
+ EXPECT_FALSE(db.InsertFallbackNameSpace(&record));
+
+ record.cache_id = 2;
+ record.origin = kFooOrigin;
+ record.namespace_url = kFooNameSpace2;
+ record.fallback_entry_url = kFooFallbackEntry;
+ EXPECT_TRUE(db.InsertFallbackNameSpace(&record));
+
+ records.clear();
+ EXPECT_TRUE(db.FindFallbackNameSpacesForCache(1, &records));
+ EXPECT_EQ(1U, records.size());
+ EXPECT_EQ(1, records[0].cache_id);
+ EXPECT_EQ(kFooOrigin, records[0].origin);
+ EXPECT_EQ(kFooNameSpace1, records[0].namespace_url);
+ EXPECT_EQ(kFooFallbackEntry, records[0].fallback_entry_url);
+
+ records.clear();
+ EXPECT_TRUE(db.FindFallbackNameSpacesForCache(2, &records));
+ EXPECT_EQ(1U, records.size());
+ EXPECT_EQ(2, records[0].cache_id);
+ EXPECT_EQ(kFooOrigin, records[0].origin);
+ EXPECT_EQ(kFooNameSpace2, records[0].namespace_url);
+ EXPECT_EQ(kFooFallbackEntry, records[0].fallback_entry_url);
+
+ records.clear();
+ EXPECT_TRUE(db.FindFallbackNameSpacesForOrigin(kFooOrigin, &records));
+ EXPECT_EQ(2U, records.size());
+ EXPECT_EQ(1, records[0].cache_id);
+ EXPECT_EQ(kFooOrigin, records[0].origin);
+ EXPECT_EQ(kFooNameSpace1, records[0].namespace_url);
+ EXPECT_EQ(kFooFallbackEntry, records[0].fallback_entry_url);
+ EXPECT_EQ(2, records[1].cache_id);
+ EXPECT_EQ(kFooOrigin, records[1].origin);
+ EXPECT_EQ(kFooNameSpace2, records[1].namespace_url);
+ EXPECT_EQ(kFooFallbackEntry, records[1].fallback_entry_url);
+
+ EXPECT_TRUE(db.DeleteFallbackNameSpacesForCache(1));
+ records.clear();
+ EXPECT_TRUE(db.FindFallbackNameSpacesForOrigin(kFooOrigin, &records));
+ EXPECT_EQ(1U, records.size());
+ EXPECT_EQ(2, records[0].cache_id);
+ EXPECT_EQ(kFooOrigin, records[0].origin);
+ EXPECT_EQ(kFooNameSpace2, records[0].namespace_url);
+ EXPECT_EQ(kFooFallbackEntry, records[0].fallback_entry_url);
+
+ // Two more records for the same cache in the Bar origin.
+ record.cache_id = 3;
+ record.origin = kBarOrigin;
+ record.namespace_url = kBarNameSpace1;
+ record.fallback_entry_url = kBarFallbackEntry;
+ EXPECT_TRUE(db.InsertFallbackNameSpace(&record));
+
+ record.cache_id = 3;
+ record.origin = kBarOrigin;
+ record.namespace_url = kBarNameSpace2;
+ record.fallback_entry_url = kBarFallbackEntry;
+ EXPECT_TRUE(db.InsertFallbackNameSpace(&record));
+
+ records.clear();
+ EXPECT_TRUE(db.FindFallbackNameSpacesForCache(3, &records));
+ EXPECT_EQ(2U, records.size());
+ records.clear();
+ EXPECT_TRUE(db.FindFallbackNameSpacesForOrigin(kBarOrigin, &records));
+ EXPECT_EQ(2U, records.size());
+}
+
+TEST(AppCacheDatabaseTest, OnlineWhiteListRecords) {
+ const FilePath kEmptyPath;
+ AppCacheDatabase db(kEmptyPath);
+ EXPECT_TRUE(db.LazyOpen(true));
+
+ scoped_refptr<TestErrorDelegate> error_delegate(new TestErrorDelegate);
+ db.db_->set_error_delegate(error_delegate);
+
+ const GURL kFooNameSpace1("http://foo/namespace1");
+ const GURL kFooNameSpace2("http://foo/namespace2");
+ const GURL kBarNameSpace1("http://bar/namespace1");
+
+ const AppCacheDatabase::OnlineWhiteListRecord kZeroRecord = { 0, GURL() };
+ AppCacheDatabase::OnlineWhiteListRecord record = kZeroRecord;
+ std::vector<AppCacheDatabase::OnlineWhiteListRecord> records;
+
+ // Behavior with an empty table
+ EXPECT_TRUE(db.FindOnlineWhiteListForCache(1, &records));
+ EXPECT_TRUE(records.empty());
+ EXPECT_TRUE(db.DeleteOnlineWhiteListForCache(1));
+
+ record.cache_id = 1;
+ record.namespace_url = kFooNameSpace1;
+ EXPECT_TRUE(db.InsertOnlineWhiteList(&record));
+ record.namespace_url = kFooNameSpace2;
+ EXPECT_TRUE(db.InsertOnlineWhiteList(&record));
+ records.clear();
+ EXPECT_TRUE(db.FindOnlineWhiteListForCache(1, &records));
+ EXPECT_EQ(2U, records.size());
+ EXPECT_EQ(1, records[0].cache_id);
+ EXPECT_EQ(kFooNameSpace1, records[0].namespace_url);
+ EXPECT_EQ(1, records[1].cache_id);
+ EXPECT_EQ(kFooNameSpace2, records[1].namespace_url);
+
+ record.cache_id = 2;
+ record.namespace_url = kBarNameSpace1;
+ EXPECT_TRUE(db.InsertOnlineWhiteList(&record));
+ records.clear();
+ EXPECT_TRUE(db.FindOnlineWhiteListForCache(2, &records));
+ EXPECT_EQ(1U, records.size());
+
+ EXPECT_TRUE(db.DeleteOnlineWhiteListForCache(1));
+ records.clear();
+ EXPECT_TRUE(db.FindOnlineWhiteListForCache(1, &records));
+ EXPECT_TRUE(records.empty());
+}
+
+} // namespace appcache
diff --git a/webkit/appcache/appcache_group_unittest.cc b/webkit/appcache/appcache_group_unittest.cc
index 5986021..180cc3a 100644
--- a/webkit/appcache/appcache_group_unittest.cc
+++ b/webkit/appcache/appcache_group_unittest.cc
@@ -77,7 +77,7 @@ class AppCacheGroupTest : public testing::Test {
TEST(AppCacheGroupTest, AddRemoveCache) {
MockAppCacheService service;
scoped_refptr<AppCacheGroup> group =
- new AppCacheGroup(&service, GURL::EmptyGURL(), 111);
+ new AppCacheGroup(&service, GURL("http://foo.com"), 111);
base::TimeTicks ticks = base::TimeTicks::Now();
@@ -146,7 +146,8 @@ TEST(AppCacheGroupTest, AddRemoveCache) {
TEST(AppCacheGroupTest, CleanupUnusedGroup) {
MockAppCacheService service;
TestAppCacheFrontend frontend;
- AppCacheGroup* group = new AppCacheGroup(&service, GURL::EmptyGURL(), 111);
+ AppCacheGroup* group =
+ new AppCacheGroup(&service, GURL("http://foo.com"), 111);
AppCacheHost host1(1, &frontend, &service);
AppCacheHost host2(2, &frontend, &service);
diff --git a/webkit/appcache/appcache_host.cc b/webkit/appcache/appcache_host.cc
index 6a62a44..489d062 100644
--- a/webkit/appcache/appcache_host.cc
+++ b/webkit/appcache/appcache_host.cc
@@ -300,6 +300,7 @@ void AppCacheHost::FinishCacheSelection(
void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) {
DCHECK(!group_being_updated_);
group_being_updated_ = group;
+ newest_cache_of_group_being_updated_ = group->newest_complete_cache();
group->AddUpdateObserver(this);
}
@@ -311,6 +312,7 @@ void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) {
SetSwappableCache(group);
group_being_updated_ = NULL;
+ newest_cache_of_group_being_updated_ = NULL;
}
void AppCacheHost::SetSwappableCache(AppCacheGroup* group) {
diff --git a/webkit/appcache/appcache_host.h b/webkit/appcache/appcache_host.h
index 13acdb0..3029cd3 100644
--- a/webkit/appcache/appcache_host.h
+++ b/webkit/appcache/appcache_host.h
@@ -135,6 +135,11 @@ class AppCacheHost : public AppCacheStorage::Delegate,
// Keep a reference to the group being updated until the update completes.
scoped_refptr<AppCacheGroup> group_being_updated_;
+ // Similarly, keep a reference to the newest cache of the group until the
+ // update completes. When adding a new master entry to a cache that is not
+ // in use in any other host, this reference keeps the cache in memory.
+ scoped_refptr<AppCache> newest_cache_of_group_being_updated_;
+
// Keep a reference to the cache of the main resource so it survives frame
// navigations.
scoped_refptr<AppCache> main_resource_cache_;
diff --git a/webkit/appcache/appcache_storage_impl.cc b/webkit/appcache/appcache_storage_impl.cc
index 3fb8280..04809eb 100644
--- a/webkit/appcache/appcache_storage_impl.cc
+++ b/webkit/appcache/appcache_storage_impl.cc
@@ -4,12 +4,862 @@
#include "webkit/appcache/appcache_storage_impl.h"
+#include "app/sql/connection.h"
+#include "app/sql/transaction.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "webkit/appcache/appcache.h"
+#include "webkit/appcache/appcache_database.h"
+#include "webkit/appcache/appcache_entry.h"
+#include "webkit/appcache/appcache_group.h"
+#include "webkit/appcache/appcache_response.h"
+#include "webkit/appcache/appcache_thread.h"
+
namespace appcache {
+static const char kAppCacheDatabaseName[] = "Index";
+static const char kDiskCacheDirectoryName[] = "Cache";
+static const int kMaxDiskCacheSize = 10 * 1024 * 1024;
+
+// DatabaseTask -----------------------------------------
+
+class AppCacheStorageImpl::DatabaseTask
+ : public base::RefCountedThreadSafe<DatabaseTask> {
+ public:
+ explicit DatabaseTask(AppCacheStorageImpl* storage)
+ : storage_(storage), database_(storage->database_) {}
+
+ void AddDelegate(DelegateReference* delegate_reference) {
+ delegates_.push_back(delegate_reference);
+ }
+
+ // Schedules a task to be Run() on the DB thread. Tasks
+ // are run in the order in which they are scheduled.
+ void Schedule();
+
+ // Called on the DB thread.
+ virtual void Run() = 0;
+
+ // Called on the IO thread after Run() has completed.
+ virtual void RunCompleted() {}
+
+ // Once scheduled a task cannot be cancelled, but the
+ // call to RunCompleted may be. This method should only be
+ // called on the IO thread. This is used by AppCacheStorageImpl
+ // to cancel the completion calls when AppCacheStorageImpl is
+ // destructed. This method may be overriden to release or delete
+ // additional data associated with the task that is not DB thread
+ // safe. If overriden, this base class method must be called from
+ // within the override.
+ virtual void CancelCompletion();
+
+ protected:
+ AppCacheStorageImpl* storage_;
+ AppCacheDatabase* database_;
+ DelegateReferenceVector delegates_;
+
+ private:
+ void CallRun();
+ void CallRunCompleted();
+};
+
+void AppCacheStorageImpl::DatabaseTask::Schedule() {
+ DCHECK(storage_);
+ DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io()));
+ storage_->scheduled_database_tasks_.push_back(this);
+ AppCacheThread::PostTask(AppCacheThread::db(), FROM_HERE,
+ NewRunnableMethod(this, &DatabaseTask::CallRun));
+}
+
+void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
+ DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io()));
+ delegates_.clear();
+ storage_ = NULL;
+}
+
+void AppCacheStorageImpl::DatabaseTask::CallRun() {
+ DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::db()));
+ Run();
+ AppCacheThread::PostTask(AppCacheThread::io(), FROM_HERE,
+ NewRunnableMethod(this, &DatabaseTask::CallRunCompleted));
+}
+
+void AppCacheStorageImpl::DatabaseTask::CallRunCompleted() {
+ if (storage_) {
+ DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io()));
+ DCHECK(storage_->scheduled_database_tasks_.front() == this);
+ storage_->scheduled_database_tasks_.pop_front();
+ RunCompleted();
+ }
+}
+
+// InitTask -------
+
+class AppCacheStorageImpl::InitTask : public DatabaseTask {
+ public:
+ explicit InitTask(AppCacheStorageImpl* storage)
+ : DatabaseTask(storage), last_group_id_(0),
+ last_cache_id_(0), last_response_id_(0) {}
+
+ virtual void Run();
+ virtual void RunCompleted();
+
+ int64 last_group_id_;
+ int64 last_cache_id_;
+ int64 last_response_id_;
+ std::set<GURL> origins_with_groups_;
+};
+
+void AppCacheStorageImpl::InitTask::Run() {
+ database_->FindLastStorageIds(
+ &last_group_id_, &last_cache_id_, &last_response_id_);
+ database_->FindOriginsWithGroups(&origins_with_groups_);
+}
+
+void AppCacheStorageImpl::InitTask::RunCompleted() {
+ storage_->last_group_id_ = last_group_id_;
+ storage_->last_cache_id_ = last_cache_id_;
+ storage_->last_response_id_ = last_response_id_;
+ storage_->origins_with_groups_.swap(origins_with_groups_);
+}
+
+// CloseConnectionTask -------
+
+class AppCacheStorageImpl::CloseConnectionTask : public DatabaseTask {
+ public:
+ explicit CloseConnectionTask(AppCacheStorageImpl* storage)
+ : DatabaseTask(storage) {}
+
+ virtual void Run() { database_->CloseConnection(); }
+};
+
+// StoreOrLoadTask -------
+
+class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
+ protected:
+ explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
+ : DatabaseTask(storage) {}
+
+ bool FindRelatedCacheRecords(int64 cache_id);
+ void CreateCacheAndGroupFromRecords(
+ scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);
+
+ AppCacheDatabase::GroupRecord group_record_;
+ AppCacheDatabase::CacheRecord cache_record_;
+ std::vector<AppCacheDatabase::EntryRecord> entry_records_;
+ std::vector<AppCacheDatabase::FallbackNameSpaceRecord>
+ fallback_namespace_records_;
+ std::vector<AppCacheDatabase::OnlineWhiteListRecord>
+ online_whitelist_records_;
+};
+
+bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
+ int64 cache_id) {
+ return database_->FindEntriesForCache(cache_id, &entry_records_) &&
+ database_->FindFallbackNameSpacesForCache(
+ cache_id, &fallback_namespace_records_) &&
+ database_->FindOnlineWhiteListForCache(
+ cache_id, &online_whitelist_records_);
+}
+
+void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
+ scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) {
+ DCHECK(storage_ && cache && group);
+
+ (*cache) = new AppCache(storage_->service_, cache_record_.cache_id);
+ cache->get()->InitializeWithDatabaseRecords(
+ cache_record_, entry_records_, fallback_namespace_records_,
+ online_whitelist_records_);
+ cache->get()->set_complete(true);
+
+ (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url);
+ if (group->get()) {
+ DCHECK(group_record_.group_id == group->get()->group_id());
+ group->get()->AddCache(cache->get());
+ } else {
+ (*group) = new AppCacheGroup(
+ storage_->service_, group_record_.manifest_url,
+ group_record_.group_id);
+ group->get()->AddCache(cache->get());
+ }
+ DCHECK(group->get()->newest_complete_cache() == cache->get());
+
+ // We have to update foriegn entries if MarkEntryAsForeignTasks
+ // are in flight.
+ std::vector<GURL> urls;
+ storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls);
+ for (std::vector<GURL>::iterator iter = urls.begin();
+ iter != urls.end(); ++iter) {
+ DCHECK(cache->get()->GetEntry(*iter));
+ cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN);
+ }
+}
+
+// CacheLoadTask -------
+
+class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
+ public:
+ CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage)
+ : StoreOrLoadTask(storage), cache_id_(cache_id),
+ success_(false) {}
+
+ virtual void Run();
+ virtual void RunCompleted();
+
+ int64 cache_id_;
+ bool success_;
+};
+
+void AppCacheStorageImpl::CacheLoadTask::Run() {
+ success_ =
+ database_->FindCache(cache_id_, &cache_record_) &&
+ database_->FindGroup(cache_record_.group_id, &group_record_) &&
+ FindRelatedCacheRecords(cache_id_);
+}
+
+void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
+ storage_->pending_cache_loads_.erase(cache_id_);
+ scoped_refptr<AppCache> cache;
+ scoped_refptr<AppCacheGroup> group;
+ if (success_) {
+ DCHECK(cache_record_.cache_id == cache_id_);
+ DCHECK(!storage_->working_set_.GetCache(cache_record_.cache_id));
+ CreateCacheAndGroupFromRecords(&cache, &group);
+ }
+ FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache, cache_id_));
+}
+
+// GroupLoadTask -------
+
+class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
+ public:
+ GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
+ : StoreOrLoadTask(storage), manifest_url_(manifest_url),
+ success_(false) {}
+
+ virtual void Run();
+ virtual void RunCompleted();
+
+ GURL manifest_url_;
+ bool success_;
+};
+
+void AppCacheStorageImpl::GroupLoadTask::Run() {
+ success_ =
+ database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
+ database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
+ FindRelatedCacheRecords(cache_record_.cache_id);
+}
+
+void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
+ storage_->pending_group_loads_.erase(manifest_url_);
+ scoped_refptr<AppCacheGroup> group;
+ scoped_refptr<AppCache> cache;
+ if (success_) {
+ DCHECK(group_record_.manifest_url == manifest_url_);
+ DCHECK(!storage_->working_set_.GetGroup(manifest_url_));
+ DCHECK(!storage_->working_set_.GetCache(cache_record_.cache_id));
+ CreateCacheAndGroupFromRecords(&cache, &group);
+ } else {
+ group = new AppCacheGroup(
+ storage_->service_, manifest_url_,
+ storage_->NewGroupId());
+ }
+ FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group, manifest_url_));
+}
+
+// StoreGroupAndCacheTask -------
+
+class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
+ public:
+ StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
+ AppCache* newest_cache);
+
+ virtual void Run();
+ virtual void RunCompleted();
+ virtual void CancelCompletion();
+
+ scoped_refptr<AppCacheGroup> group_;
+ scoped_refptr<AppCache> cache_;
+ bool success_;
+};
+
+AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
+ AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache)
+ : StoreOrLoadTask(storage), group_(group), cache_(newest_cache),
+ success_(false) {
+ group_record_.group_id = group->group_id();
+ group_record_.manifest_url = group->manifest_url();
+ group_record_.origin = group_record_.manifest_url.GetOrigin();
+ newest_cache->ToDatabaseRecords(
+ group,
+ &cache_record_, &entry_records_, &fallback_namespace_records_,
+ &online_whitelist_records_);
+}
+
+void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
+ DCHECK(!success_);
+ sql::Connection* connection = database_->db_connection();
+ if (!connection)
+ return;
+
+ sql::Transaction transaction(connection);
+ if (!transaction.Begin())
+ return;
+
+ AppCacheDatabase::GroupRecord existing_group;
+ success_ = database_->FindGroup(group_record_.group_id, &existing_group);
+ if (!success_) {
+ success_ = database_->InsertGroup(&group_record_);
+ } else {
+ DCHECK(group_record_.group_id == existing_group.group_id);
+ DCHECK(group_record_.manifest_url == existing_group.manifest_url);
+ DCHECK(group_record_.origin == existing_group.origin);
+
+ AppCacheDatabase::CacheRecord cache;
+ if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
+ success_ =
+ database_->DeleteCache(cache.cache_id) &&
+ database_->DeleteEntriesForCache(cache.cache_id) &&
+ database_->DeleteFallbackNameSpacesForCache(cache.cache_id) &&
+ database_->DeleteOnlineWhiteListForCache(cache.cache_id);
+ // TODO(michaeln): schedule to purge unused responses from the disk cache
+ } else {
+ NOTREACHED() << "A existing group without a cache is unexpected";
+ }
+ }
+
+ success_ =
+ success_ &&
+ database_->InsertCache(&cache_record_) &&
+ database_->InsertEntryRecords(entry_records_) &&
+ database_->InsertFallbackNameSpaceRecords(fallback_namespace_records_)&&
+ database_->InsertOnlineWhiteListRecords(online_whitelist_records_) &&
+ transaction.Commit();
+}
+
+void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
+ if (success_) {
+ storage_->origins_with_groups_.insert(group_->manifest_url().GetOrigin());
+ if (cache_ != group_->newest_complete_cache())
+ group_->AddCache(cache_);
+ }
+ FOR_EACH_DELEGATE(delegates_, OnGroupAndNewestCacheStored(group_, success_));
+ group_ = NULL;
+ cache_ = NULL;
+}
+
+void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
+ // Overriden to safely drop our reference to the group and cache
+ // which are not thread safe refcounted.
+ DatabaseTask::CancelCompletion();
+ group_ = NULL;
+ cache_ = NULL;
+}
+
+// FindMainResponseTask -------
+
+class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
+ public:
+ FindMainResponseTask(AppCacheStorageImpl* storage, const GURL& url,
+ const AppCacheWorkingSet::GroupMap* groups_in_use)
+ : DatabaseTask(storage), url_(url), cache_id_(kNoCacheId) {
+ if (groups_in_use) {
+ for (AppCacheWorkingSet::GroupMap::const_iterator it =
+ groups_in_use->begin();
+ it != groups_in_use->end(); ++it) {
+ AppCacheGroup* group = it->second;
+ AppCache* cache = group->newest_complete_cache();
+ if (group->is_obsolete() || !cache)
+ continue;
+ cache_ids_in_use_.insert(cache->cache_id());
+ }
+ }
+ }
+
+ virtual void Run();
+ virtual void RunCompleted();
+
+ GURL url_;
+ std::set<int64> cache_ids_in_use_;
+ AppCacheEntry entry_;
+ AppCacheEntry fallback_entry_;
+ int64 cache_id_;
+ GURL manifest_url_;
+};
+
+namespace {
+
+bool SortByLength(
+ const AppCacheDatabase::FallbackNameSpaceRecord& lhs,
+ const AppCacheDatabase::FallbackNameSpaceRecord& rhs) {
+ return lhs.namespace_url.spec().length() > rhs.namespace_url.spec().length();
+}
+
+}
+
+void AppCacheStorageImpl::FindMainResponseTask::Run() {
+ // We have a bias for hits from caches that are in use.
+
+ // TODO(michaeln): The heuristics around choosing amoungst
+ // multiple candidates is under specified, and just plain
+ // not fully understood. Refine these over time. In particular,
+ // * prefer candidates from newer caches
+ // * take into account the cache associated with the document
+ // that initiated the navigation
+ // * take into account the cache associated with the document
+ // currently residing in the frame being navigated
+
+ // First look for an exact match. We don't worry about whether
+ // the containing cache is in-use in this loop because the
+ // storage class's FindResponseForMainRequest method does that
+ // as a pre-optimization.
+ std::vector<AppCacheDatabase::EntryRecord> entries;
+ if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
+ std::vector<AppCacheDatabase::EntryRecord>::iterator iter;
+ for (iter = entries.begin(); iter < entries.end(); ++iter) {
+ if (iter->flags & AppCacheEntry::FOREIGN)
+ continue;
+
+ AppCacheDatabase::GroupRecord group_record;
+ if (!database_->FindGroupForCache(iter->cache_id, &group_record)) {
+ NOTREACHED() << "A cache without a group is not expected.";
+ continue;
+ }
+ entry_ = AppCacheEntry(iter->flags, iter->response_id);
+ cache_id_ = iter->cache_id;
+ manifest_url_ = group_record.manifest_url;
+ return;
+ }
+ }
+
+ // No exact matches, look at the fallback namespaces for this origin.
+ std::vector<AppCacheDatabase::FallbackNameSpaceRecord> fallbacks;
+ if (!database_->FindFallbackNameSpacesForOrigin(url_.GetOrigin(), &fallbacks)
+ || fallbacks.empty()) {
+ return;
+ }
+
+ // Sort by namespace url string length, longest to shortest,
+ // since longer matches trump when matching a url to a namespace.
+ std::sort(fallbacks.begin(), fallbacks.end(), SortByLength);
+
+ bool has_candidate = false;
+ GURL candidate_fallback_namespace;
+ std::vector<AppCacheDatabase::FallbackNameSpaceRecord>::iterator iter;
+ for (iter = fallbacks.begin(); iter < fallbacks.end(); ++iter) {
+ if (has_candidate &&
+ (candidate_fallback_namespace.spec().length() >
+ iter->namespace_url.spec().length())) {
+ break; // Stop iterating since longer namespace prefix matches win.
+ }
+
+ if (StartsWithASCII(url_.spec(), iter->namespace_url.spec(), true)) {
+ bool is_cache_in_use = cache_ids_in_use_.find(iter->cache_id) !=
+ cache_ids_in_use_.end();
+
+ bool take_new_candidate = !has_candidate || is_cache_in_use;
+
+ AppCacheDatabase::EntryRecord entry_record;
+ if (take_new_candidate &&
+ database_->FindEntry(iter->cache_id, iter->fallback_entry_url,
+ &entry_record)) {
+ AppCacheDatabase::GroupRecord group_record;
+ if (!database_->FindGroupForCache(iter->cache_id, &group_record)) {
+ NOTREACHED() << "A cache without a group is not expected.";
+ continue;
+ }
+ cache_id_ = iter->cache_id;
+ manifest_url_ = group_record.manifest_url;
+ fallback_entry_ = AppCacheEntry(
+ entry_record.flags, entry_record.response_id);
+ if (is_cache_in_use)
+ break; // Stop iterating since we favor hits from in-use caches.
+ candidate_fallback_namespace = iter->namespace_url;
+ has_candidate = true;
+ }
+ }
+ }
+}
+
+void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
+ FOR_EACH_DELEGATE(delegates_,
+ OnMainResponseFound(url_, entry_, fallback_entry_,
+ cache_id_, manifest_url_));
+}
+
+// MarkEntryAsForeignTask -------
+
+class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
+ public:
+ MarkEntryAsForeignTask(
+ AppCacheStorageImpl* storage, const GURL& url, int64 cache_id)
+ : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
+
+ virtual void Run();
+ virtual void RunCompleted();
+
+ int64 cache_id_;
+ GURL entry_url_;
+};
+
+void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
+ database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
+}
+
+void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
+ DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ &&
+ storage_->pending_foreign_markings_.front().second == cache_id_);
+ storage_->pending_foreign_markings_.pop_front();
+}
+
+// MakeGroupObsoleteTask -------
+
+class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
+ public:
+ MakeGroupObsoleteTask(AppCacheStorageImpl* storage, AppCacheGroup* group);
+
+ virtual void Run();
+ virtual void RunCompleted();
+ virtual void CancelCompletion();
+
+ scoped_refptr<AppCacheGroup> group_;
+ int64 group_id_;
+ bool success_;
+ std::set<GURL> origins_with_groups_;
+};
+
+AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
+ AppCacheStorageImpl* storage, AppCacheGroup* group)
+ : DatabaseTask(storage), group_(group), group_id_(group->group_id()),
+ success_(false) {
+}
+
+void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
+ DCHECK(!success_);
+ sql::Connection* connection = database_->db_connection();
+ if (!connection)
+ return;
+
+ sql::Transaction transaction(connection);
+ if (!transaction.Begin())
+ return;
+
+ AppCacheDatabase::GroupRecord group_record;
+ if (!database_->FindGroup(group_id_, &group_record)) {
+ // This group doesn't exists in the database, nothing todo here.
+ success_ = true;
+ return;
+ }
+
+ AppCacheDatabase::CacheRecord cache_record;
+ if (database_->FindCacheForGroup(group_id_, &cache_record)) {
+ success_ =
+ database_->DeleteGroup(group_id_) &&
+ database_->DeleteCache(cache_record.cache_id) &&
+ database_->DeleteEntriesForCache(cache_record.cache_id) &&
+ database_->DeleteFallbackNameSpacesForCache(cache_record.cache_id) &&
+ database_->DeleteOnlineWhiteListForCache(cache_record.cache_id);
+ } else {
+ NOTREACHED() << "A existing group without a cache is unexpected";
+ success_ = database_->DeleteGroup(group_id_);
+ }
+
+ success_ = success_ &&
+ database_->FindOriginsWithGroups(&origins_with_groups_) &&
+ transaction.Commit();
+
+ // TODO(michaeln): schedule to purge unused responses from the disk cache
+}
+
+void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
+ if (success_) {
+ storage_->origins_with_groups_.swap(origins_with_groups_);
+ group_->set_obsolete(true);
+ }
+ FOR_EACH_DELEGATE(delegates_, OnGroupMadeObsolete(group_, success_));
+ group_ = NULL;
+}
+
+void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
+ // Overriden to safely drop our reference to the group
+ // which is not thread safe refcounted.
+ DatabaseTask::CancelCompletion();
+ group_ = NULL;
+}
+
+
+// AppCacheStorageImpl ---------------------------------------------------
+
+AppCacheStorageImpl::AppCacheStorageImpl(AppCacheService* service)
+ : AppCacheStorage(service), is_incognito_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
+}
+
+AppCacheStorageImpl::~AppCacheStorageImpl() {
+ STLDeleteElements(&pending_simple_tasks_);
+
+ std::for_each(scheduled_database_tasks_.begin(),
+ scheduled_database_tasks_.end(),
+ std::mem_fun(&DatabaseTask::CancelCompletion));
+
+ if (database_)
+ AppCacheThread::DeleteSoon(AppCacheThread::db(), FROM_HERE, database_);
+}
+
void AppCacheStorageImpl::Initialize(const FilePath& cache_directory) {
- is_incognito_ = cache_directory.empty();
- cache_directory_ = cache_directory;
- // TODO(michaeln): retrieve last_ids from storage
+ // TODO(michaeln): until purging of responses is addressed in some way,
+ // always use incognito mode which doesn't put anything to disk.
+ // Uncomment the following line when responses are dealt with.
+ // cache_directory_ = cache_directory;
+ is_incognito_ = cache_directory_.empty();
+
+ FilePath db_file_path;
+ if (!is_incognito_)
+ db_file_path = cache_directory.AppendASCII(kAppCacheDatabaseName);
+ database_ = new AppCacheDatabase(db_file_path);
+
+ scoped_refptr<InitTask> task = new InitTask(this);
+ task->Schedule();
+}
+
+void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) {
+ DCHECK(delegate);
+ AppCache* cache = working_set_.GetCache(id);
+ if (cache) {
+ delegate->OnCacheLoaded(cache, id);
+ return;
+ }
+ scoped_refptr<CacheLoadTask> task = GetPendingCacheLoadTask(id);
+ if (task) {
+ task->AddDelegate(GetOrCreateDelegateReference(delegate));
+ return;
+ }
+ task = new CacheLoadTask(id, this);
+ task->AddDelegate(GetOrCreateDelegateReference(delegate));
+ task->Schedule();
+ pending_cache_loads_[id] = task;
+}
+
+void AppCacheStorageImpl::LoadOrCreateGroup(
+ const GURL& manifest_url, Delegate* delegate) {
+ DCHECK(delegate);
+ AppCacheGroup* group = working_set_.GetGroup(manifest_url);
+ if (group) {
+ delegate->OnGroupLoaded(group, manifest_url);
+ return;
+ }
+
+ scoped_refptr<GroupLoadTask> task = GetPendingGroupLoadTask(manifest_url);
+ if (task) {
+ task->AddDelegate(GetOrCreateDelegateReference(delegate));
+ return;
+ }
+
+ if (origins_with_groups_.find(manifest_url.GetOrigin()) ==
+ origins_with_groups_.end()) {
+ // No need to query the database, return NULL immediately.
+ scoped_refptr<AppCacheGroup> group = new AppCacheGroup(
+ service_, manifest_url, NewGroupId());
+ delegate->OnGroupLoaded(group, manifest_url);
+ return;
+ }
+
+ task = new GroupLoadTask(manifest_url, this);
+ task->AddDelegate(GetOrCreateDelegateReference(delegate));
+ task->Schedule();
+ pending_group_loads_[manifest_url] = task.get();
+}
+
+void AppCacheStorageImpl::StoreGroupAndNewestCache(
+ AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
+ // TODO(michaeln): distinguish between a simple update of an existing
+ // cache that just adds new master entry(s), and the insertion of a
+ // whole new cache. The StoreGroupAndCacheTask as written will handle
+ // the simple update case in a very heavy weight way (delete all and
+ // the reinsert all over again).
+ DCHECK(group && delegate);
+ DCHECK(newest_cache && newest_cache->is_complete());
+ scoped_refptr<StoreGroupAndCacheTask> task =
+ new StoreGroupAndCacheTask(this, group, newest_cache);
+ task->AddDelegate(GetOrCreateDelegateReference(delegate));
+ task->Schedule();
+}
+
+void AppCacheStorageImpl::FindResponseForMainRequest(
+ const GURL& url, Delegate* delegate) {
+ DCHECK(delegate);
+
+ const GURL* url_ptr = &url;
+ GURL url_no_ref;
+ if (url.has_ref()) {
+ GURL::Replacements replacements;
+ replacements.ClearRef();
+ url_no_ref = url.ReplaceComponents(replacements);
+ url_ptr = &url_no_ref;
+ }
+
+ // First look in our working set for a direct hit without having to query
+ // the database.
+ const AppCacheWorkingSet::GroupMap* groups_in_use =
+ working_set()->GetGroupsInOrigin(url_ptr->GetOrigin());
+ if (groups_in_use) {
+ for (AppCacheWorkingSet::GroupMap::const_iterator it =
+ groups_in_use->begin();
+ it != groups_in_use->end(); ++it) {
+ AppCacheGroup* group = it->second;
+ AppCache* cache = group->newest_complete_cache();
+ if (group->is_obsolete() || !cache)
+ continue;
+
+ AppCacheEntry* entry = cache->GetEntry(*url_ptr);
+ if (entry && !entry->IsForeign()) {
+ ScheduleSimpleTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
+ url, *entry, make_scoped_refptr(group), make_scoped_refptr(cache),
+ make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
+ return;
+ }
+ }
+ }
+
+ if (origins_with_groups_.find(url.GetOrigin()) ==
+ origins_with_groups_.end()) {
+ // No need to query the database, return async'ly but without going thru
+ // the DB thread.
+ scoped_refptr<AppCacheGroup> no_group;
+ scoped_refptr<AppCache> no_cache;
+ ScheduleSimpleTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
+ url, AppCacheEntry(), no_group, no_cache,
+ make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
+ return;
+ }
+
+ // We have to query the database, schedule a database task to do so.
+ scoped_refptr<FindMainResponseTask> task =
+ new FindMainResponseTask(this, *url_ptr, groups_in_use);
+ task->AddDelegate(GetOrCreateDelegateReference(delegate));
+ task->Schedule();
+}
+
+void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
+ const GURL& url, AppCacheEntry found_entry,
+ scoped_refptr<AppCacheGroup> group, scoped_refptr<AppCache> cache,
+ scoped_refptr<DelegateReference> delegate_ref) {
+ if (delegate_ref->delegate) {
+ delegate_ref->delegate->OnMainResponseFound(
+ url, found_entry, AppCacheEntry(),
+ cache.get() ? cache->cache_id() : kNoCacheId,
+ group.get() ? group->manifest_url() : GURL::EmptyGURL());
+ }
+}
+
+void AppCacheStorageImpl::FindResponseForSubRequest(
+ AppCache* cache, const GURL& url,
+ AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
+ bool* found_network_namespace) {
+ DCHECK(cache && cache->is_complete());
+ GURL fallback_namespace_not_used;
+ cache->FindResponseForRequest(
+ url, found_entry, found_fallback_entry,
+ &fallback_namespace_not_used, found_network_namespace);
+}
+
+void AppCacheStorageImpl::MarkEntryAsForeign(
+ const GURL& entry_url, int64 cache_id) {
+ AppCache* cache = working_set_.GetCache(cache_id);
+ if (cache) {
+ AppCacheEntry* entry = cache->GetEntry(entry_url);
+ DCHECK(entry);
+ if (entry)
+ entry->add_types(AppCacheEntry::FOREIGN);
+ }
+ scoped_refptr<MarkEntryAsForeignTask> task =
+ new MarkEntryAsForeignTask(this, entry_url, cache_id);
+ task->Schedule();
+ pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
+}
+
+void AppCacheStorageImpl::MakeGroupObsolete(
+ AppCacheGroup* group, Delegate* delegate) {
+ DCHECK(group && delegate);
+ scoped_refptr<MakeGroupObsoleteTask> task =
+ new MakeGroupObsoleteTask(this, group);
+ task->AddDelegate(GetOrCreateDelegateReference(delegate));
+ task->Schedule();
+}
+
+AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
+ const GURL& manifest_url, int64 response_id) {
+ return new AppCacheResponseReader(response_id, disk_cache());
+}
+
+AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
+ const GURL& manifest_url) {
+ return new AppCacheResponseWriter(NewResponseId(), disk_cache());
+}
+
+void AppCacheStorageImpl::DoomResponses(
+ const GURL& manifest_url, const std::vector<int64>& response_ids) {
+ // TODO(michaeln): do something here when deleting responses
+}
+
+AppCacheStorageImpl::CacheLoadTask*
+AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id) {
+ PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id);
+ if (found != pending_cache_loads_.end())
+ return found->second;
+ return NULL;
+}
+
+AppCacheStorageImpl::GroupLoadTask*
+AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) {
+ PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url);
+ if (found != pending_group_loads_.end())
+ return found->second;
+ return NULL;
+}
+
+void AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
+ int64 cache_id, std::vector<GURL>* urls) {
+ PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin();
+ while (iter != pending_foreign_markings_.end()) {
+ if (iter->second == cache_id)
+ urls->push_back(iter->first);
+ ++iter;
+ }
+}
+
+void AppCacheStorageImpl::ScheduleSimpleTask(Task* task) {
+ pending_simple_tasks_.push_back(task);
+ MessageLoop::current()->PostTask(FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &AppCacheStorageImpl::RunOnePendingSimpleTask));
+}
+
+void AppCacheStorageImpl::RunOnePendingSimpleTask() {
+ DCHECK(!pending_simple_tasks_.empty());
+ Task* task = pending_simple_tasks_.front();
+ pending_simple_tasks_.pop_front();
+ task->Run();
+ delete task;
+}
+
+disk_cache::Backend* AppCacheStorageImpl::disk_cache() {
+ if (!disk_cache_.get()) {
+ if (is_incognito_) {
+ disk_cache_.reset(
+ disk_cache::CreateInMemoryCacheBackend(kMaxDiskCacheSize));
+ } else {
+ // TODO(michaeln): create a disk backed backend
+ disk_cache_.reset(
+ disk_cache::CreateInMemoryCacheBackend(kMaxDiskCacheSize));
+ }
+ }
+ return disk_cache_.get();
}
} // namespace appcache
diff --git a/webkit/appcache/appcache_storage_impl.h b/webkit/appcache/appcache_storage_impl.h
index ef75d1e..9debc1c 100644
--- a/webkit/appcache/appcache_storage_impl.h
+++ b/webkit/appcache/appcache_storage_impl.h
@@ -5,22 +5,102 @@
#ifndef WEBKIT_APPCACHE_APPCACHE_STORAGE_IMPL_H_
#define WEBKIT_APPCACHE_APPCACHE_STORAGE_IMPL_H_
+#include <deque>
+#include <map>
+#include <set>
+#include <vector>
+
#include "base/file_path.h"
-#include "webkit/appcache/mock_appcache_storage.h"
+#include "net/disk_cache/disk_cache.h"
+#include "webkit/appcache/appcache_database.h"
+#include "webkit/appcache/appcache_storage.h"
namespace appcache {
-// TODO(michaeln): write me, for now we derive from 'mock' storage.
-class AppCacheStorageImpl : public MockAppCacheStorage {
+class AppCacheStorageImpl : public AppCacheStorage {
public:
- explicit AppCacheStorageImpl(AppCacheService* service)
- : MockAppCacheStorage(service), is_incognito_(false) {}
+ explicit AppCacheStorageImpl(AppCacheService* service);
+ virtual ~AppCacheStorageImpl();
void Initialize(const FilePath& cache_directory);
+ // AppCacheStorage methods
+ virtual void LoadCache(int64 id, Delegate* delegate);
+ virtual void LoadOrCreateGroup(const GURL& manifest_url, Delegate* delegate);
+ virtual void StoreGroupAndNewestCache(
+ AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate);
+ virtual void FindResponseForMainRequest(const GURL& url, Delegate* delegate);
+ virtual void FindResponseForSubRequest(
+ AppCache* cache, const GURL& url,
+ AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
+ bool* found_network_namespace);
+ virtual void MarkEntryAsForeign(const GURL& entry_url, int64 cache_id);
+ virtual void MakeGroupObsolete(AppCacheGroup* group, Delegate* delegate);
+ virtual AppCacheResponseReader* CreateResponseReader(
+ const GURL& manifest_url, int64 response_id);
+ virtual AppCacheResponseWriter* CreateResponseWriter(
+ const GURL& manifest_url);
+ virtual void DoomResponses(
+ const GURL& manifest_url, const std::vector<int64>& response_ids);
+
private:
- bool is_incognito_;
+ friend class AppCacheStorageImplTest;
+
+ // A handful of tasks used to perform database operations on the
+ // background database thread.
+ class DatabaseTask;
+ class InitTask;
+ class CloseConnectionTask;
+ class StoreOrLoadTask;
+ class CacheLoadTask;
+ class GroupLoadTask;
+ class StoreGroupAndCacheTask;
+ class FindMainResponseTask;
+ class MarkEntryAsForeignTask;
+ class MakeGroupObsoleteTask;
+
+ typedef std::deque<DatabaseTask*> DatabaseTaskQueue;
+ typedef std::map<int64, CacheLoadTask*> PendingCacheLoads;
+ typedef std::map<GURL, GroupLoadTask*> PendingGroupLoads;
+ typedef std::deque<std::pair<GURL, int64> > PendingForeignMarkings;
+
+ CacheLoadTask* GetPendingCacheLoadTask(int64 cache_id);
+ GroupLoadTask* GetPendingGroupLoadTask(const GURL& manifest_url);
+ void GetPendingForeignMarkingsForCache(
+ int64 cache_id, std::vector<GURL>* urls);
+
+ void ScheduleSimpleTask(Task* task);
+ void RunOnePendingSimpleTask();
+
+ // Sometimes we can respond without having to query the database.
+ void DeliverShortCircuitedFindMainResponse(
+ const GURL& url, AppCacheEntry found_entry,
+ scoped_refptr<AppCacheGroup> group, scoped_refptr<AppCache> newest_cache,
+ scoped_refptr<DelegateReference> delegate_ref);
+
+ disk_cache::Backend* disk_cache();
+
+ // The directory in which we place files in the file system.
FilePath cache_directory_;
+ bool is_incognito_;
+
+ // Structures to keep track of DatabaseTasks that are in-flight.
+ DatabaseTaskQueue scheduled_database_tasks_;
+ PendingCacheLoads pending_cache_loads_;
+ PendingGroupLoads pending_group_loads_;
+ PendingForeignMarkings pending_foreign_markings_;
+
+ // Created on the IO thread, but only used on the DB thread.
+ AppCacheDatabase* database_;
+
+ // TODO(michaeln): use a disk_cache per group (manifest or group_id).
+ scoped_ptr<disk_cache::Backend> disk_cache_;
+
+ // Used to short-circuit certain operations without having to schedule
+ // any tasks on the background database thread.
+ std::set<GURL> origins_with_groups_;
+ std::deque<Task*> pending_simple_tasks_;
+ ScopedRunnableMethodFactory<AppCacheStorageImpl> method_factory_;
};
} // namespace appcache
diff --git a/webkit/appcache/appcache_storage_impl_unittest.cc b/webkit/appcache/appcache_storage_impl_unittest.cc
new file mode 100644
index 0000000..1db4aa8
--- /dev/null
+++ b/webkit/appcache/appcache_storage_impl_unittest.cc
@@ -0,0 +1,1007 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/message_loop.h"
+#include "base/thread.h"
+#include "base/waitable_event.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/appcache/appcache.h"
+#include "webkit/appcache/appcache_database.h"
+#include "webkit/appcache/appcache_entry.h"
+#include "webkit/appcache/appcache_group.h"
+#include "webkit/appcache/appcache_service.h"
+#include "webkit/appcache/appcache_storage_impl.h"
+#include "webkit/tools/test_shell/simple_appcache_system.h"
+
+namespace appcache {
+
+namespace {
+
+const base::TimeTicks kZeroTimeTicks;
+const GURL kManifestUrl("http://blah/manifest");
+const GURL kManifestUrl2("http://blah/manifest2");
+const GURL kEntryUrl("http://blah/entry");
+const GURL kEntryUrl2("http://blah/entry2");
+const GURL kFallbackNamespace("http://blah/fallback_namespace/");
+const GURL kFallbackNamespace2("http://blah/fallback_namespace/longer");
+const GURL kFallbackTestUrl("http://blah/fallback_namespace/longer/test");
+const GURL kOnlineNamespace("http://blah/online_namespace");
+
+// For the duration of this test case, we hijack the AppCacheThread API
+// calls and implement them in terms of the io and db threads created here.
+
+scoped_ptr<base::Thread> io_thread;
+scoped_ptr<base::Thread> db_thread;
+
+class TestThreadProvider : public SimpleAppCacheSystem::ThreadProvider {
+ public:
+ virtual bool PostTask(
+ int id,
+ const tracked_objects::Location& from_here,
+ Task* task) {
+ GetMessageLoop(id)->PostTask(from_here, task);
+ return true;
+ }
+
+ virtual bool CurrentlyOn(int id) {
+ return MessageLoop::current() == GetMessageLoop(id);
+ }
+
+ MessageLoop* GetMessageLoop(int id) {
+ DCHECK(io_thread.get() && db_thread.get());
+ if (id == SimpleAppCacheSystem::IO_THREAD_ID)
+ return io_thread->message_loop();
+ if (id == SimpleAppCacheSystem::DB_THREAD_ID)
+ return db_thread->message_loop();
+ NOTREACHED() << "Invalid AppCacheThreadID value";
+ return NULL;
+ }
+};
+
+TestThreadProvider thread_provider;
+
+} // namespace
+
+class AppCacheStorageImplTest : public testing::Test {
+ public:
+ class MockStorageDelegate : public AppCacheStorage::Delegate {
+ public:
+ explicit MockStorageDelegate(AppCacheStorageImplTest* test)
+ : loaded_cache_id_(0), stored_group_success_(false),
+ obsoleted_success_(false), found_cache_id_(kNoCacheId),
+ test_(test) {
+ }
+
+ void OnCacheLoaded(AppCache* cache, int64 cache_id) {
+ loaded_cache_ = cache;
+ loaded_cache_id_ = cache_id;
+ test_->ScheduleNextTask();
+ }
+
+ void OnGroupLoaded(AppCacheGroup* group, const GURL& manifest_url) {
+ loaded_group_ = group;
+ loaded_manifest_url_ = manifest_url;
+ loaded_groups_newest_cache_ = group ? group->newest_complete_cache()
+ : NULL;
+ test_->ScheduleNextTask();
+ }
+
+ void OnGroupAndNewestCacheStored(AppCacheGroup* group, bool success) {
+ stored_group_ = group;
+ stored_group_success_ = success;
+ test_->ScheduleNextTask();
+ }
+
+ void OnGroupMadeObsolete(AppCacheGroup* group, bool success) {
+ obsoleted_group_ = group;
+ obsoleted_success_ = success;
+ test_->ScheduleNextTask();
+ }
+
+ void OnMainResponseFound(const GURL& url, const AppCacheEntry& entry,
+ const AppCacheEntry& fallback_entry,
+ int64 cache_id, const GURL& manifest_url) {
+ found_url_ = url;
+ found_entry_ = entry;
+ found_fallback_entry_ = fallback_entry;
+ found_cache_id_ = cache_id;
+ found_manifest_url_ = manifest_url;
+ test_->ScheduleNextTask();
+ }
+
+ scoped_refptr<AppCache> loaded_cache_;
+ int64 loaded_cache_id_;
+ scoped_refptr<AppCacheGroup> loaded_group_;
+ GURL loaded_manifest_url_;
+ scoped_refptr<AppCache> loaded_groups_newest_cache_;
+ scoped_refptr<AppCacheGroup> stored_group_;
+ bool stored_group_success_;
+ scoped_refptr<AppCacheGroup> obsoleted_group_;
+ bool obsoleted_success_;
+ GURL found_url_;
+ AppCacheEntry found_entry_;
+ AppCacheEntry found_fallback_entry_;
+ int64 found_cache_id_;
+ GURL found_manifest_url_;
+ AppCacheStorageImplTest* test_;
+ };
+
+ // Helper class run a test on our io_thread. The io_thread
+ // is spun up once and reused for all tests.
+ template <class Method>
+ class WrapperTask : public Task {
+ public:
+ WrapperTask(AppCacheStorageImplTest* test, Method method)
+ : test_(test), method_(method) {
+ }
+
+ virtual void Run() {
+ test_->SetUpTest();
+
+ // Ensure InitTask execution prior to conducting a test.
+ test_->FlushDbThreadTasks();
+
+ // We also have to wait for InitTask completion call to be performed
+ // on the IO thread prior to running the test. Its guaranteed to be
+ // queued by this time.
+ MessageLoop::current()->PostTask(FROM_HERE,
+ NewRunnableFunction(&RunMethod, test_, method_));
+ }
+
+ static void RunMethod(AppCacheStorageImplTest* test, Method method) {
+ (test->*method)();
+ }
+
+ private:
+ AppCacheStorageImplTest* test_;
+ Method method_;
+ };
+
+
+ static void SetUpTestCase() {
+ io_thread.reset(new base::Thread("AppCacheTest.IOThread"));
+ base::Thread::Options options(MessageLoop::TYPE_IO, 0);
+ ASSERT_TRUE(io_thread->StartWithOptions(options));
+
+ db_thread.reset(new base::Thread("AppCacheTest::DBThread"));
+ ASSERT_TRUE(db_thread->Start());
+
+ SimpleAppCacheSystem::set_thread_provider(&thread_provider);
+ }
+
+ static void TearDownTestCase() {
+ SimpleAppCacheSystem::set_thread_provider(NULL);
+ io_thread.reset(NULL);
+ db_thread.reset(NULL);
+ }
+
+ // Test harness --------------------------------------------------
+
+ AppCacheStorageImplTest()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
+ }
+
+ template <class Method>
+ void RunTestOnIOThread(Method method) {
+ test_finished_event_ .reset(new base::WaitableEvent(false, false));
+ io_thread->message_loop()->PostTask(
+ FROM_HERE, new WrapperTask<Method>(this, method));
+ test_finished_event_->Wait();
+ }
+
+ void SetUpTest() {
+ DCHECK(MessageLoop::current() == io_thread->message_loop());
+ service_.reset(new AppCacheService);
+ service_->Initialize(FilePath());
+ delegate_.reset(new MockStorageDelegate(this));
+ }
+
+ void TearDownTest() {
+ DCHECK(MessageLoop::current() == io_thread->message_loop());
+ storage()->CancelDelegateCallbacks(delegate());
+ group_ = NULL;
+ cache_ = NULL;
+ cache2_ = NULL;
+ delegate_.reset();
+ service_.reset();
+ FlushDbThreadTasks();
+ }
+
+ void TestFinished() {
+ // We unwind the stack prior to finishing up to let stack
+ // based objects get deleted.
+ DCHECK(MessageLoop::current() == io_thread->message_loop());
+ MessageLoop::current()->PostTask(FROM_HERE,
+ method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::TestFinishedUnwound));
+ }
+
+ void TestFinishedUnwound() {
+ TearDownTest();
+ test_finished_event_->Signal();
+ }
+
+ void PushNextTask(Task* task) {
+ task_stack_.push(task);
+ }
+
+ void ScheduleNextTask() {
+ DCHECK(MessageLoop::current() == io_thread->message_loop());
+ if (task_stack_.empty()) {
+ return;
+ }
+ MessageLoop::current()->PostTask(FROM_HERE, task_stack_.top());
+ task_stack_.pop();
+ }
+
+ static void SignalEvent(base::WaitableEvent* event) {
+ event->Signal();
+ }
+
+ void FlushDbThreadTasks() {
+ // We pump a task thru the db thread to ensure any tasks previously
+ // scheduled on that thread have been performed prior to return.
+ base::WaitableEvent event(false, false);
+ db_thread->message_loop()->PostTask(FROM_HERE,
+ NewRunnableFunction(&AppCacheStorageImplTest::SignalEvent,
+ &event));
+ event.Wait();
+ }
+
+ // LoadCache_Miss ----------------------------------------------------
+
+ void LoadCache_Miss() {
+ // Attempt to load a cache that doesn't exist. Should
+ // complete asyncly.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_LoadCache_Miss));
+
+ storage()->LoadCache(111, delegate());
+ EXPECT_NE(111, delegate()->loaded_cache_id_);
+ }
+
+ void Verify_LoadCache_Miss() {
+ EXPECT_EQ(111, delegate()->loaded_cache_id_);
+ EXPECT_FALSE(delegate()->loaded_cache_);
+ TestFinished();
+ }
+
+ // LoadCache_NearHit -------------------------------------------------
+
+ void LoadCache_NearHit() {
+ // Attempt to load a cache that is currently in use
+ // and does not require loading from storage. This
+ // load should complete syncly.
+
+ // Setup some preconditions. Make an 'unstored' cache for
+ // us to load. The ctor should put it in the working set.
+ int64 cache_id = storage()->NewCacheId();
+ scoped_refptr<AppCache> cache = new AppCache(service(), cache_id);
+
+ // Conduct the test.
+ storage()->LoadCache(cache_id, delegate());
+ EXPECT_EQ(cache_id, delegate()->loaded_cache_id_);
+ EXPECT_EQ(cache.get(), delegate()->loaded_cache_.get());
+ TestFinished();
+ }
+
+ // CreateGroup --------------------------------------------
+
+ void CreateGroupInEmptyOrigin() {
+ // Attempt to load a group that doesn't exist, one should
+ // be created for us, but not stored.
+
+ // Since the origin has no groups, the storage class will respond
+ // syncly.
+ storage()->LoadOrCreateGroup(kManifestUrl, delegate());
+ Verify_CreateGroup();
+ }
+
+ void CreateGroupInPopulatedOrigin() {
+ // Attempt to load a group that doesn't exist, one should
+ // be created for us, but not stored.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_CreateGroup));
+
+ // Since the origin has groups, storage class will have to
+ // consult the database and completion will be async.
+ storage()->origins_with_groups_.insert(kManifestUrl.GetOrigin());
+
+ storage()->LoadOrCreateGroup(kManifestUrl, delegate());
+ EXPECT_FALSE(delegate()->loaded_group_.get());
+ }
+
+ void Verify_CreateGroup() {
+ EXPECT_EQ(kManifestUrl, delegate()->loaded_manifest_url_);
+ EXPECT_TRUE(delegate()->loaded_group_.get());
+ EXPECT_TRUE(delegate()->loaded_group_->HasOneRef());
+ EXPECT_FALSE(delegate()->loaded_group_->newest_complete_cache());
+
+ // Should not have been stored in the database.
+ AppCacheDatabase::GroupRecord record;
+ EXPECT_FALSE(database()->FindGroup(
+ delegate()->loaded_group_->group_id(), &record));
+
+ TestFinished();
+ }
+
+ // LoadGroupAndCache_FarHit --------------------------------------
+
+ void LoadGroupAndCache_FarHit() {
+ // Attempt to load a cache that is not currently in use
+ // and does require loading from disk. This
+ // load should complete asyncly.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_LoadCache_Far_Hit));
+
+ // Setup some preconditions. Create a group and newest cache that
+ // appear to be "stored" and "not currently in use".
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ group_ = NULL;
+ cache_ = NULL;
+
+ // Conduct the cache load test, completes async
+ storage()->LoadCache(1, delegate());
+ }
+
+ void Verify_LoadCache_Far_Hit() {
+ EXPECT_TRUE(delegate()->loaded_cache_);
+ EXPECT_TRUE(delegate()->loaded_cache_->HasOneRef());
+ EXPECT_EQ(1, delegate()->loaded_cache_id_);
+
+ // The group should also have been loaded.
+ EXPECT_TRUE(delegate()->loaded_cache_->owning_group());
+ EXPECT_TRUE(delegate()->loaded_cache_->owning_group()->HasOneRef());
+ EXPECT_EQ(1, delegate()->loaded_cache_->owning_group()->group_id());
+
+ // Drop things from the working set.
+ delegate()->loaded_cache_ = NULL;
+ EXPECT_FALSE(delegate()->loaded_group_);
+
+ // Conduct the group load test, also complete asyncly.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_LoadGroup_Far_Hit));
+
+ storage()->LoadOrCreateGroup(kManifestUrl, delegate());
+ }
+
+ void Verify_LoadGroup_Far_Hit() {
+ EXPECT_TRUE(delegate()->loaded_group_);
+ EXPECT_EQ(kManifestUrl, delegate()->loaded_manifest_url_);
+ EXPECT_TRUE(delegate()->loaded_group_->newest_complete_cache());
+ delegate()->loaded_groups_newest_cache_ = NULL;
+ EXPECT_TRUE(delegate()->loaded_group_->HasOneRef());
+ TestFinished();
+ }
+
+ // StoreNewGroup --------------------------------------
+
+ void StoreNewGroup() {
+ // Store a group and its newest cache. Should complete asyncly.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_StoreNewGroup));
+
+ // Setup some preconditions. Create a group and newest cache that
+ // appear to be "unstored".
+ group_ = new AppCacheGroup(
+ service(), kManifestUrl, storage()->NewGroupId());
+ cache_ = new AppCache(service(), storage()->NewCacheId());
+ cache_->set_complete(true);
+ // Hold a ref to the cache simulate the UpdateJob holding that ref,
+ // and hold a ref to the group to simulate the CacheHost holding that ref.
+
+ // Conduct the store test.
+ storage()->StoreGroupAndNewestCache(group_, cache_, delegate());
+ EXPECT_FALSE(delegate()->stored_group_success_);
+ }
+
+ void Verify_StoreNewGroup() {
+ EXPECT_TRUE(delegate()->stored_group_success_);
+ EXPECT_EQ(group_.get(), delegate()->stored_group_.get());
+ EXPECT_EQ(cache_.get(), group_->newest_complete_cache());
+
+ // Should have been stored in the database.
+ AppCacheDatabase::GroupRecord group_record;
+ AppCacheDatabase::CacheRecord cache_record;
+ EXPECT_TRUE(database()->FindGroup(group_->group_id(), &group_record));
+ EXPECT_TRUE(database()->FindCache(cache_->cache_id(), &cache_record));
+ TestFinished();
+ }
+
+ // StoreExistingGroup --------------------------------------
+
+ void StoreExistingGroup() {
+ // Store a group and its newest cache. Should complete asyncly.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_StoreExistingGroup));
+
+ // Setup some preconditions. Create a group and old complete cache
+ // that appear to be "stored"
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+
+ // And a newest unstored complete cache.
+ cache2_ = new AppCache(service(), 2);
+ cache2_->set_complete(true);
+
+ // Conduct the test.
+ storage()->StoreGroupAndNewestCache(group_, cache2_, delegate());
+ EXPECT_FALSE(delegate()->stored_group_success_);
+ }
+
+ void Verify_StoreExistingGroup() {
+ EXPECT_TRUE(delegate()->stored_group_success_);
+ EXPECT_EQ(group_.get(), delegate()->stored_group_.get());
+ EXPECT_EQ(cache2_.get(), group_->newest_complete_cache());
+
+ // The new cache should have been stored in the database.
+ AppCacheDatabase::GroupRecord group_record;
+ AppCacheDatabase::CacheRecord cache_record;
+ EXPECT_TRUE(database()->FindGroup(1, &group_record));
+ EXPECT_TRUE(database()->FindCache(2, &cache_record));
+
+ // The old cache should have been deleted
+ EXPECT_FALSE(database()->FindCache(1, &cache_record));
+ TestFinished();
+ }
+
+ // StoreExistingGroupExistingCache -------------------------------
+
+ void StoreExistingGroupExistingCache() {
+ // Store a group with updates to its existing newest complete cache.
+ // Setup some preconditions. Create a group and a complete cache that
+ // appear to be "stored".
+
+ // Setup some preconditions. Create a group and old complete cache
+ // that appear to be "stored"
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+
+ // Change the cache.
+ base::TimeTicks now = base::TimeTicks::Now();
+ cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::MASTER));
+ cache_->set_update_time(now);
+
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_StoreExistingGroupExistingCache,
+ now));
+
+ // Conduct the test.
+ EXPECT_EQ(cache_, group_->newest_complete_cache());
+ storage()->StoreGroupAndNewestCache(group_, cache_, delegate());
+ EXPECT_FALSE(delegate()->stored_group_success_);
+ }
+
+ void Verify_StoreExistingGroupExistingCache(
+ base::TimeTicks expected_update_time) {
+ EXPECT_TRUE(delegate()->stored_group_success_);
+ EXPECT_EQ(cache_, group_->newest_complete_cache());
+
+ AppCacheDatabase::CacheRecord cache_record;
+ EXPECT_TRUE(database()->FindCache(1, &cache_record));
+ EXPECT_EQ(1, cache_record.cache_id);
+ EXPECT_EQ(1, cache_record.group_id);
+ EXPECT_FALSE(cache_record.online_wildcard);
+ EXPECT_TRUE(expected_update_time == cache_record.update_time);
+
+ std::vector<AppCacheDatabase::EntryRecord> entry_records;
+ EXPECT_TRUE(database()->FindEntriesForCache(1, &entry_records));
+ EXPECT_EQ(1U, entry_records.size());
+ EXPECT_EQ(1 , entry_records[0].cache_id);
+ EXPECT_EQ(kEntryUrl, entry_records[0].url);
+ EXPECT_EQ(AppCacheEntry::MASTER, entry_records[0].flags);
+ EXPECT_EQ(0, entry_records[0].response_id);
+
+ TestFinished();
+ }
+
+ // MakeGroupObsolete -------------------------------
+
+ void MakeGroupObsolete() {
+ // Make a group obsolete, should complete asyncly.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_MakeGroupObsolete));
+
+ // Setup some preconditions. Create a group and newest cache that
+ // appears to be "stored" and "currently in use".
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ EXPECT_FALSE(storage()->origins_with_groups_.empty());
+
+ // Also insert some related records.
+ AppCacheDatabase::EntryRecord entry_record;
+ entry_record.cache_id = 1;
+ entry_record.flags = AppCacheEntry::FALLBACK;
+ entry_record.response_id = 1;
+ entry_record.url = kEntryUrl;
+ EXPECT_TRUE(database()->InsertEntry(&entry_record));
+
+ AppCacheDatabase::FallbackNameSpaceRecord fallback_namespace_record;
+ fallback_namespace_record.cache_id = 1;
+ fallback_namespace_record.fallback_entry_url = kEntryUrl;
+ fallback_namespace_record.namespace_url = kFallbackNamespace;
+ fallback_namespace_record.origin = kManifestUrl.GetOrigin();
+ EXPECT_TRUE(
+ database()->InsertFallbackNameSpace(&fallback_namespace_record));
+
+ AppCacheDatabase::OnlineWhiteListRecord online_whitelist_record;
+ online_whitelist_record.cache_id = 1;
+ online_whitelist_record.namespace_url = kOnlineNamespace;
+ EXPECT_TRUE(database()->InsertOnlineWhiteList(&online_whitelist_record));
+
+ // Conduct the test.
+ storage()->MakeGroupObsolete(group_, delegate());
+ EXPECT_FALSE(group_->is_obsolete());
+ }
+
+ void Verify_MakeGroupObsolete() {
+ EXPECT_TRUE(delegate()->obsoleted_success_);
+ EXPECT_EQ(group_.get(), delegate()->obsoleted_group_.get());
+ EXPECT_TRUE(group_->is_obsolete());
+ EXPECT_TRUE(storage()->origins_with_groups_.empty());
+
+ // The cache and group have been deleted from the database.
+ AppCacheDatabase::GroupRecord group_record;
+ AppCacheDatabase::CacheRecord cache_record;
+ EXPECT_FALSE(database()->FindGroup(1, &group_record));
+ EXPECT_FALSE(database()->FindCache(1, &cache_record));
+
+ // The related records should have been deleted too.
+ std::vector<AppCacheDatabase::EntryRecord> entry_records;
+ database()->FindEntriesForCache(1, &entry_records);
+ EXPECT_TRUE(entry_records.empty());
+ std::vector<AppCacheDatabase::FallbackNameSpaceRecord> fallback_records;
+ database()->FindFallbackNameSpacesForCache(1, &fallback_records);
+ EXPECT_TRUE(fallback_records.empty());
+ std::vector<AppCacheDatabase::OnlineWhiteListRecord> whitelist_records;
+ database()->FindOnlineWhiteListForCache(1, &whitelist_records);
+ EXPECT_TRUE(whitelist_records.empty());
+
+ TestFinished();
+ }
+
+ // MarkEntryAsForeign -------------------------------
+
+ void MarkEntryAsForeign() {
+ // Setup some preconditions. Create a cache with an entry
+ // in storage and in the working set.
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT));
+ AppCacheDatabase::EntryRecord entry_record;
+ entry_record.cache_id = 1;
+ entry_record.url = kEntryUrl;
+ entry_record.flags = AppCacheEntry::EXPLICIT;
+ entry_record.response_id = 0;
+ EXPECT_TRUE(database()->InsertEntry(&entry_record));
+ EXPECT_FALSE(cache_->GetEntry(kEntryUrl)->IsForeign());
+
+ // Conduct the test.
+ storage()->MarkEntryAsForeign(kEntryUrl, 1);
+
+ // The entry in the working set should have been updated syncly.
+ EXPECT_TRUE(cache_->GetEntry(kEntryUrl)->IsForeign());
+ EXPECT_TRUE(cache_->GetEntry(kEntryUrl)->IsExplicit());
+
+ // And the entry in storage should also be updated, but that
+ // happens asyncly on the db thread.
+ FlushDbThreadTasks();
+ AppCacheDatabase::EntryRecord entry_record2;
+ EXPECT_TRUE(database()->FindEntry(1, kEntryUrl, &entry_record2));
+ EXPECT_EQ(AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN,
+ entry_record2.flags);
+ TestFinished();
+ }
+
+ // MarkEntryAsForeignWithLoadInProgress -------------------------------
+
+ void MarkEntryAsForeignWithLoadInProgress() {
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_MarkEntryAsForeignWithLoadInProgress));
+
+ // Setup some preconditions. Create a cache with an entry
+ // in storage, but not in the working set.
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT));
+ AppCacheDatabase::EntryRecord entry_record;
+ entry_record.cache_id = 1;
+ entry_record.url = kEntryUrl;
+ entry_record.flags = AppCacheEntry::EXPLICIT;
+ entry_record.response_id = 0;
+ EXPECT_TRUE(database()->InsertEntry(&entry_record));
+ EXPECT_FALSE(cache_->GetEntry(kEntryUrl)->IsForeign());
+ EXPECT_TRUE(cache_->HasOneRef());
+ cache_ = NULL;
+ group_ = NULL;
+
+ // Conduct the test, start a cache load, and prior to completion
+ // of that load, mark the entry as foreign.
+ storage()->LoadCache(1, delegate());
+ storage()->MarkEntryAsForeign(kEntryUrl, 1);
+ }
+
+ void Verify_MarkEntryAsForeignWithLoadInProgress() {
+ EXPECT_EQ(1, delegate()->loaded_cache_id_);
+ EXPECT_TRUE(delegate()->loaded_cache_.get());
+
+ // The entry in the working set should have been updated upon load.
+ EXPECT_TRUE(delegate()->loaded_cache_->GetEntry(kEntryUrl)->IsForeign());
+ EXPECT_TRUE(delegate()->loaded_cache_->GetEntry(kEntryUrl)->IsExplicit());
+
+ // And the entry in storage should also be updated.
+ FlushDbThreadTasks();
+ AppCacheDatabase::EntryRecord entry_record;
+ EXPECT_TRUE(database()->FindEntry(1, kEntryUrl, &entry_record));
+ EXPECT_EQ(AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN,
+ entry_record.flags);
+ TestFinished();
+ }
+
+ // FindNoMainResponse -------------------------------
+
+ void FindNoMainResponse() {
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_FindNoMainResponse));
+
+ // Conduct the test.
+ storage()->FindResponseForMainRequest(kEntryUrl, delegate());
+ EXPECT_NE(kEntryUrl, delegate()->found_url_);
+ }
+
+ void Verify_FindNoMainResponse() {
+ EXPECT_EQ(kEntryUrl, delegate()->found_url_);
+ EXPECT_TRUE(delegate()->found_manifest_url_.is_empty());
+ EXPECT_EQ(kNoCacheId, delegate()->found_cache_id_);
+ EXPECT_EQ(kNoResponseId, delegate()->found_entry_.response_id());
+ EXPECT_EQ(kNoResponseId, delegate()->found_fallback_entry_.response_id());
+ EXPECT_EQ(0, delegate()->found_entry_.types());
+ EXPECT_EQ(0, delegate()->found_fallback_entry_.types());
+ TestFinished();
+ }
+
+ // BasicFindMainResponse -------------------------------
+
+ void BasicFindMainResponseInDatabase() {
+ BasicFindMainResponse(true);
+ }
+
+ void BasicFindMainResponseInWorkingSet() {
+ BasicFindMainResponse(false);
+ }
+
+ void BasicFindMainResponse(bool drop_from_working_set) {
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_BasicFindMainResponse));
+
+ // Setup some preconditions. Create a complete cache with an entry
+ // in storage.
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT, 1));
+ AppCacheDatabase::EntryRecord entry_record;
+ entry_record.cache_id = 1;
+ entry_record.url = kEntryUrl;
+ entry_record.flags = AppCacheEntry::EXPLICIT;
+ entry_record.response_id = 1;
+ EXPECT_TRUE(database()->InsertEntry(&entry_record));
+
+ // Optionally drop the cache/group pair from the working set.
+ if (drop_from_working_set) {
+ EXPECT_TRUE(cache_->HasOneRef());
+ cache_ = NULL;
+ EXPECT_TRUE(group_->HasOneRef());
+ group_ = NULL;
+ }
+
+ // Conduct the test.
+ storage()->FindResponseForMainRequest(kEntryUrl, delegate());
+ EXPECT_NE(kEntryUrl, delegate()->found_url_);
+ }
+
+ void Verify_BasicFindMainResponse() {
+ EXPECT_EQ(kEntryUrl, delegate()->found_url_);
+ EXPECT_EQ(kManifestUrl, delegate()->found_manifest_url_);
+ EXPECT_EQ(1, delegate()->found_cache_id_);
+ EXPECT_EQ(1, delegate()->found_entry_.response_id());
+ EXPECT_TRUE(delegate()->found_entry_.IsExplicit());
+ EXPECT_FALSE(delegate()->found_fallback_entry_.has_response_id());
+ TestFinished();
+ }
+
+ // BasicFindMainFallbackResponse -------------------------------
+
+ void BasicFindMainFallbackResponseInDatabase() {
+ BasicFindMainFallbackResponse(true);
+ }
+
+ void BasicFindMainFallbackResponseInWorkingSet() {
+ BasicFindMainFallbackResponse(false);
+ }
+
+ void BasicFindMainFallbackResponse(bool drop_from_working_set) {
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_BasicFindMainFallbackResponse));
+
+ // Setup some preconditions. Create a complete cache with a
+ // fallback namespace and entry.
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::FALLBACK, 1));
+ cache_->AddEntry(kEntryUrl2, AppCacheEntry(AppCacheEntry::FALLBACK, 2));
+ cache_->fallback_namespaces_.push_back(
+ FallbackNamespace(kFallbackNamespace2, kEntryUrl2));
+ cache_->fallback_namespaces_.push_back(
+ FallbackNamespace(kFallbackNamespace, kEntryUrl));
+ AppCacheDatabase::CacheRecord cache_record;
+ std::vector<AppCacheDatabase::EntryRecord> entries;
+ std::vector<AppCacheDatabase::FallbackNameSpaceRecord> fallbacks;
+ std::vector<AppCacheDatabase::OnlineWhiteListRecord> whitelists;
+ cache_->ToDatabaseRecords(group_,
+ &cache_record, &entries, &fallbacks, &whitelists);
+ EXPECT_TRUE(database()->InsertEntryRecords(entries));
+ EXPECT_TRUE(database()->InsertFallbackNameSpaceRecords(fallbacks));
+ EXPECT_TRUE(database()->InsertOnlineWhiteListRecords(whitelists));
+ if (drop_from_working_set) {
+ EXPECT_TRUE(cache_->HasOneRef());
+ cache_ = NULL;
+ EXPECT_TRUE(group_->HasOneRef());
+ group_ = NULL;
+ }
+
+ // Conduct the test. The test url is in both fallback namespace urls,
+ // but should match the longer of the two.
+ storage()->FindResponseForMainRequest(kFallbackTestUrl, delegate());
+ EXPECT_NE(kFallbackTestUrl, delegate()->found_url_);
+ }
+
+ void Verify_BasicFindMainFallbackResponse() {
+ EXPECT_EQ(kFallbackTestUrl, delegate()->found_url_);
+ EXPECT_EQ(kManifestUrl, delegate()->found_manifest_url_);
+ EXPECT_EQ(1, delegate()->found_cache_id_);
+ EXPECT_FALSE(delegate()->found_entry_.has_response_id());
+ EXPECT_EQ(2, delegate()->found_fallback_entry_.response_id());
+ EXPECT_TRUE(delegate()->found_fallback_entry_.IsFallback());
+ TestFinished();
+ }
+
+ // FindMainResponseWithMultipleHits -------------------------------
+
+ void FindMainResponseWithMultipleHits() {
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_FindMainResponseWithMultipleHits));
+
+ // Setup some preconditions. Create 2 complete caches with an entry
+ // for the same url.
+
+ // The first cache, in the database but not in the working set.
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT, 1));
+ AppCacheDatabase::EntryRecord entry_record;
+ entry_record.cache_id = 1;
+ entry_record.url = kEntryUrl;
+ entry_record.flags = AppCacheEntry::EXPLICIT;
+ entry_record.response_id = 1;
+ EXPECT_TRUE(database()->InsertEntry(&entry_record));
+ cache_ = NULL;
+ group_ = NULL;
+
+ // The second cache, in the database and working set.
+ MakeCacheAndGroup(kManifestUrl2, 2, 2, true);
+ cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT, 2));
+ entry_record.cache_id = 2;
+ entry_record.url = kEntryUrl;
+ entry_record.flags = AppCacheEntry::EXPLICIT;
+ entry_record.response_id = 2;
+ EXPECT_TRUE(database()->InsertEntry(&entry_record));
+
+ // Conduct the test, we should find the response from the second cache
+ // since it's "in use".
+ storage()->FindResponseForMainRequest(kEntryUrl, delegate());
+ EXPECT_NE(kEntryUrl, delegate()->found_url_);
+ }
+
+ void Verify_FindMainResponseWithMultipleHits() {
+ EXPECT_EQ(kEntryUrl, delegate()->found_url_);
+ EXPECT_EQ(kManifestUrl2, delegate()->found_manifest_url_);
+ EXPECT_EQ(2, delegate()->found_cache_id_);
+ EXPECT_EQ(2, delegate()->found_entry_.response_id());
+ EXPECT_TRUE(delegate()->found_entry_.IsExplicit());
+ EXPECT_FALSE(delegate()->found_fallback_entry_.has_response_id());
+ TestFinished();
+ }
+
+ // FindMainResponseExclusions -------------------------------
+
+ void FindMainResponseExclusionsInDatabase() {
+ FindMainResponseExclusions(true);
+ }
+
+ void FindMainResponseExclusionsInWorkingSet() {
+ FindMainResponseExclusions(false);
+ }
+
+ void FindMainResponseExclusions(bool drop_from_working_set) {
+ // Setup some preconditions. Create a complete cache with a
+ // foreign entry and an online namespace.
+ MakeCacheAndGroup(kManifestUrl, 1, 1, true);
+ cache_->AddEntry(kEntryUrl,
+ AppCacheEntry(AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN, 1));
+ cache_->online_whitelist_namespaces_.push_back(kOnlineNamespace);
+ AppCacheDatabase::EntryRecord entry_record;
+ entry_record.cache_id = 1;
+ entry_record.url = kEntryUrl;
+ entry_record.flags = AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN;
+ entry_record.response_id = 1;
+ EXPECT_TRUE(database()->InsertEntry(&entry_record));
+ AppCacheDatabase::OnlineWhiteListRecord whitelist_record;
+ whitelist_record.cache_id = 1;
+ whitelist_record.namespace_url = kOnlineNamespace;
+ EXPECT_TRUE(database()->InsertOnlineWhiteList(&whitelist_record));
+ if (drop_from_working_set) {
+ cache_ = NULL;
+ group_ = NULL;
+ }
+
+ // We should not find anything for the foreign entry.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_NotFound, kEntryUrl, false));
+ storage()->FindResponseForMainRequest(kEntryUrl, delegate());
+ }
+
+ void Verify_NotFound(GURL expected_url, bool test_finished) {
+ EXPECT_EQ(expected_url, delegate()->found_url_);
+ EXPECT_TRUE(delegate()->found_manifest_url_.is_empty());
+ EXPECT_EQ(kNoCacheId, delegate()->found_cache_id_);
+ EXPECT_EQ(kNoResponseId, delegate()->found_entry_.response_id());
+ EXPECT_EQ(kNoResponseId, delegate()->found_fallback_entry_.response_id());
+ EXPECT_EQ(0, delegate()->found_entry_.types());
+ EXPECT_EQ(0, delegate()->found_fallback_entry_.types());
+
+ if (!test_finished) {
+ // We should not find anything for the online namespace.
+ PushNextTask(method_factory_.NewRunnableMethod(
+ &AppCacheStorageImplTest::Verify_NotFound, kOnlineNamespace, true));
+ storage()->FindResponseForMainRequest(kOnlineNamespace, delegate());
+ return;
+ }
+
+ TestFinished();
+ }
+
+ // Test case helpers --------------------------------------------------
+
+ AppCacheService* service() {
+ return service_.get();
+ }
+
+ AppCacheStorageImpl* storage() {
+ return static_cast<AppCacheStorageImpl*>(service()->storage());
+ }
+
+ AppCacheDatabase* database() {
+ return storage()->database_;
+ }
+
+ MockStorageDelegate* delegate() {
+ return delegate_.get();
+ }
+
+ void MakeCacheAndGroup(
+ const GURL& manifest_url, int64 group_id, int64 cache_id,
+ bool add_to_database) {
+ group_ = new AppCacheGroup(service(), manifest_url, group_id);
+ cache_ = new AppCache(service(), cache_id);
+ cache_->set_complete(true);
+ group_->AddCache(cache_);
+ if (add_to_database) {
+ AppCacheDatabase::GroupRecord group_record;
+ group_record.group_id = group_id;
+ group_record.manifest_url = manifest_url;
+ group_record.origin = manifest_url.GetOrigin();
+ EXPECT_TRUE(database()->InsertGroup(&group_record));
+ AppCacheDatabase::CacheRecord cache_record;
+ cache_record.cache_id = cache_id;
+ cache_record.group_id = group_id;
+ cache_record.online_wildcard = false;
+ cache_record.update_time = kZeroTimeTicks;
+ EXPECT_TRUE(database()->InsertCache(&cache_record));
+ storage()->origins_with_groups_.insert(manifest_url.GetOrigin());
+ }
+ }
+
+ // Data members --------------------------------------------------
+
+ ScopedRunnableMethodFactory<AppCacheStorageImplTest> method_factory_;
+ scoped_ptr<base::WaitableEvent> test_finished_event_;
+ std::stack<Task*> task_stack_;
+ scoped_ptr<AppCacheService> service_;
+ scoped_ptr<MockStorageDelegate> delegate_;
+ scoped_refptr<AppCacheGroup> group_;
+ scoped_refptr<AppCache> cache_;
+ scoped_refptr<AppCache> cache2_;
+};
+
+
+TEST_F(AppCacheStorageImplTest, LoadCache_Miss) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::LoadCache_Miss);
+}
+
+TEST_F(AppCacheStorageImplTest, LoadCache_NearHit) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::LoadCache_NearHit);
+}
+
+TEST_F(AppCacheStorageImplTest, CreateGroupInEmptyOrigin) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::CreateGroupInEmptyOrigin);
+}
+
+TEST_F(AppCacheStorageImplTest, CreateGroupInPopulatedOrigin) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::CreateGroupInPopulatedOrigin);
+}
+
+TEST_F(AppCacheStorageImplTest, LoadGroupAndCache_FarHit) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::LoadGroupAndCache_FarHit);
+}
+
+TEST_F(AppCacheStorageImplTest, StoreNewGroup) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::StoreNewGroup);
+}
+
+TEST_F(AppCacheStorageImplTest, StoreExistingGroup) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::StoreExistingGroup);
+}
+
+TEST_F(AppCacheStorageImplTest, StoreExistingGroupExistingCache) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::StoreExistingGroupExistingCache);
+}
+
+TEST_F(AppCacheStorageImplTest, MakeGroupObsolete) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::MakeGroupObsolete);
+}
+
+TEST_F(AppCacheStorageImplTest, MarkEntryAsForeign) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::MarkEntryAsForeign);
+}
+
+TEST_F(AppCacheStorageImplTest, MarkEntryAsForeignWithLoadInProgress) {
+ RunTestOnIOThread(
+ &AppCacheStorageImplTest::MarkEntryAsForeignWithLoadInProgress);
+}
+
+TEST_F(AppCacheStorageImplTest, FindNoMainResponse) {
+ RunTestOnIOThread(&AppCacheStorageImplTest::FindNoMainResponse);
+}
+
+TEST_F(AppCacheStorageImplTest, BasicFindMainResponseInDatabase) {
+ RunTestOnIOThread(
+ &AppCacheStorageImplTest::BasicFindMainResponseInDatabase);
+}
+
+TEST_F(AppCacheStorageImplTest, BasicFindMainResponseInWorkingSet) {
+ RunTestOnIOThread(
+ &AppCacheStorageImplTest::BasicFindMainResponseInWorkingSet);
+}
+
+TEST_F(AppCacheStorageImplTest, BasicFindMainFallbackResponseInDatabase) {
+ RunTestOnIOThread(
+ &AppCacheStorageImplTest::BasicFindMainFallbackResponseInDatabase);
+}
+
+TEST_F(AppCacheStorageImplTest, BasicFindMainFallbackResponseInWorkingSet) {
+ RunTestOnIOThread(
+ &AppCacheStorageImplTest::BasicFindMainFallbackResponseInWorkingSet);
+}
+
+TEST_F(AppCacheStorageImplTest, FindMainResponseWithMultipleHits) {
+ RunTestOnIOThread(
+ &AppCacheStorageImplTest::FindMainResponseWithMultipleHits);
+}
+
+TEST_F(AppCacheStorageImplTest, FindMainResponseExclusionsInDatabase) {
+ RunTestOnIOThread(
+ &AppCacheStorageImplTest::FindMainResponseExclusionsInDatabase);
+}
+
+TEST_F(AppCacheStorageImplTest, FindMainResponseExclusionsInWorkingSet) {
+ RunTestOnIOThread(
+ &AppCacheStorageImplTest::FindMainResponseExclusionsInWorkingSet);
+}
+
+// That's all folks!
+
+} // namespace appcache
+
diff --git a/webkit/appcache/appcache_thread.h b/webkit/appcache/appcache_thread.h
index 9f14b1c..7224d23 100644
--- a/webkit/appcache/appcache_thread.h
+++ b/webkit/appcache/appcache_thread.h
@@ -5,10 +5,11 @@
#ifndef WEBKIT_APPCACHE_APPCACHE_THREAD_H_
#define WEBKIT_APPCACHE_APPCACHE_THREAD_H_
+#include "base/task.h"
+
namespace tracked_objects {
class Location;
}
-class Task;
namespace appcache {
@@ -31,6 +32,13 @@ class AppCacheThread {
Task* task);
static bool CurrentlyOn(int id);
+ template <class T>
+ static bool DeleteSoon(int id,
+ const tracked_objects::Location& from_here,
+ T* object) {
+ return PostTask(id, from_here, new DeleteTask<T>(object));
+ }
+
private:
AppCacheThread();
~AppCacheThread();
diff --git a/webkit/appcache/appcache_working_set.cc b/webkit/appcache/appcache_working_set.cc
index 2b61ea3..652116c 100644
--- a/webkit/appcache/appcache_working_set.cc
+++ b/webkit/appcache/appcache_working_set.cc
@@ -14,6 +14,7 @@ namespace appcache {
AppCacheWorkingSet::~AppCacheWorkingSet() {
DCHECK(caches_.empty());
DCHECK(groups_.empty());
+ DCHECK(groups_by_origin_.empty());
}
void AppCacheWorkingSet::AddCache(AppCache* cache) {
@@ -31,10 +32,20 @@ void AppCacheWorkingSet::AddGroup(AppCacheGroup* group) {
const GURL& url = group->manifest_url();
DCHECK(groups_.find(url) == groups_.end());
groups_.insert(GroupMap::value_type(url, group));
+ groups_by_origin_[url.GetOrigin()].insert(GroupMap::value_type(url, group));
}
void AppCacheWorkingSet::RemoveGroup(AppCacheGroup* group) {
- groups_.erase(group->manifest_url());
+ const GURL& url = group->manifest_url();
+ groups_.erase(url);
+
+ GURL origin_url = url.GetOrigin();
+ GroupMap* groups_in_origin = GetMutableGroupsInOrigin(origin_url);
+ if (groups_in_origin) {
+ groups_in_origin->erase(url);
+ if (groups_in_origin->empty())
+ groups_by_origin_.erase(origin_url);
+ }
}
void AppCacheWorkingSet::AddResponseInfo(AppCacheResponseInfo* info) {
diff --git a/webkit/appcache/appcache_working_set.h b/webkit/appcache/appcache_working_set.h
index 6785d50..479e74d 100644
--- a/webkit/appcache/appcache_working_set.h
+++ b/webkit/appcache/appcache_working_set.h
@@ -20,6 +20,8 @@ class AppCacheResponseInfo;
// currently in memory.
class AppCacheWorkingSet {
public:
+ typedef std::map<GURL, AppCacheGroup*> GroupMap;
+
~AppCacheWorkingSet();
void AddCache(AppCache* cache);
@@ -36,6 +38,10 @@ class AppCacheWorkingSet {
return (it != groups_.end()) ? it->second : NULL;
}
+ const GroupMap* GetGroupsInOrigin(const GURL& origin_url) {
+ return GetMutableGroupsInOrigin(origin_url);
+ }
+
void AddResponseInfo(AppCacheResponseInfo* response_info);
void RemoveResponseInfo(AppCacheResponseInfo* response_info);
AppCacheResponseInfo* GetResponseInfo(int64 id) {
@@ -45,10 +51,17 @@ class AppCacheWorkingSet {
private:
typedef base::hash_map<int64, AppCache*> CacheMap;
- typedef std::map<GURL, AppCacheGroup*> GroupMap;
+ typedef std::map<GURL, GroupMap> GroupsByOriginMap;
typedef base::hash_map<int64, AppCacheResponseInfo*> ResponseInfoMap;
+
+ GroupMap* GetMutableGroupsInOrigin(const GURL& origin_url) {
+ GroupsByOriginMap::iterator it = groups_by_origin_.find(origin_url);
+ return (it != groups_by_origin_.end()) ? &it->second : NULL;
+ }
+
CacheMap caches_;
GroupMap groups_;
+ GroupsByOriginMap groups_by_origin_; // origin -> (manifest -> group)
ResponseInfoMap response_infos_;
};
diff --git a/webkit/appcache/mock_appcache_storage.cc b/webkit/appcache/mock_appcache_storage.cc
index 214be249..44bcd12 100644
--- a/webkit/appcache/mock_appcache_storage.cc
+++ b/webkit/appcache/mock_appcache_storage.cc
@@ -124,8 +124,6 @@ void MockAppCacheStorage::MarkEntryAsForeign(
if (entry)
entry->add_types(AppCacheEntry::FOREIGN);
}
- // TODO(michaeln): in real storage update in storage, and if this cache is
- // being loaded be sure to update the memory cache upon load completion.
}
void MockAppCacheStorage::MakeGroupObsolete(
@@ -140,7 +138,7 @@ void MockAppCacheStorage::MakeGroupObsolete(
}
AppCacheResponseReader* MockAppCacheStorage::CreateResponseReader(
- const GURL& origin, int64 response_id) {
+ const GURL& manifest_url, int64 response_id) {
return new AppCacheResponseReader(response_id, disk_cache());
}
@@ -203,14 +201,6 @@ void MockAppCacheStorage::ProcessStoreGroupAndNewestCache(
if (delegate_ref->delegate)
delegate_ref->delegate->OnGroupAndNewestCacheStored(group, true);
-
- // We don't bother with removing responses from 'mock' storage
- // TODO(michaeln): for 'real' storage...
- // std::set<int64> doomed_responses_ = responses from old caches
- // std::set<int64> needed_responses_ = responses from newest cache
- // foreach(needed_responses_)
- // doomed_responses_.remove(needed_response_);
- // DoomResponses(group->manifest_url(), doomed_responses_);
}
namespace {
diff --git a/webkit/tools/test_shell/simple_appcache_system.cc b/webkit/tools/test_shell/simple_appcache_system.cc
index 0f9d452..e714267 100644
--- a/webkit/tools/test_shell/simple_appcache_system.cc
+++ b/webkit/tools/test_shell/simple_appcache_system.cc
@@ -26,6 +26,10 @@ bool AppCacheThread::PostTask(
int id,
const tracked_objects::Location& from_here,
Task* task) {
+ if (SimpleAppCacheSystem::thread_provider()) {
+ return SimpleAppCacheSystem::thread_provider()->PostTask(
+ id, from_here, task);
+ }
scoped_ptr<Task> task_ptr(task);
MessageLoop* loop = SimpleAppCacheSystem::GetMessageLoop(id);
if (loop)
@@ -34,6 +38,8 @@ bool AppCacheThread::PostTask(
}
bool AppCacheThread::CurrentlyOn(int id) {
+ if (SimpleAppCacheSystem::thread_provider())
+ return SimpleAppCacheSystem::thread_provider()->CurrentlyOn(id);
return MessageLoop::current() == SimpleAppCacheSystem::GetMessageLoop(id);
}
@@ -268,7 +274,8 @@ SimpleAppCacheSystem::SimpleAppCacheSystem()
backend_proxy_(new SimpleBackendProxy(this))),
ALLOW_THIS_IN_INITIALIZER_LIST(
frontend_proxy_(new SimpleFrontendProxy(this))),
- backend_impl_(NULL), service_(NULL), db_thread_("AppCacheDBThread") {
+ backend_impl_(NULL), service_(NULL), db_thread_("AppCacheDBThread"),
+ thread_provider_(NULL) {
DCHECK(!instance_);
instance_ = this;
}
@@ -346,11 +353,11 @@ void SimpleAppCacheSystem::WillDestroyCurrentMessageLoop() {
DCHECK(is_io_thread());
DCHECK(backend_impl_->hosts().empty());
- io_message_loop_ = NULL;
delete backend_impl_;
delete service_;
backend_impl_ = NULL;
service_ = NULL;
+ io_message_loop_ = NULL;
// Just in case the main thread is waiting on it.
backend_proxy_->SignalEvent();
diff --git a/webkit/tools/test_shell/simple_appcache_system.h b/webkit/tools/test_shell/simple_appcache_system.h
index adeedd6..0ea465a 100644
--- a/webkit/tools/test_shell/simple_appcache_system.h
+++ b/webkit/tools/test_shell/simple_appcache_system.h
@@ -73,16 +73,38 @@ class SimpleAppCacheSystem : public MessageLoop::DestructionObserver {
instance_->GetExtraResponseBits(request, cache_id, manifest_url);
}
- private:
- friend class SimpleBackendProxy;
- friend class SimpleFrontendProxy;
- friend class appcache::AppCacheThread;
+ // Some unittests create their own IO and DB threads.
enum AppCacheThreadID {
DB_THREAD_ID,
IO_THREAD_ID,
};
+ class ThreadProvider {
+ public:
+ virtual ~ThreadProvider() {}
+ virtual bool PostTask(
+ int id,
+ const tracked_objects::Location& from_here,
+ Task* task) = 0;
+ virtual bool CurrentlyOn(int id) = 0;
+ };
+
+ static void set_thread_provider(ThreadProvider* provider) {
+ DCHECK(instance_);
+ DCHECK(!provider || !instance_->thread_provider_);
+ instance_->thread_provider_ = provider;
+ }
+
+ static ThreadProvider* thread_provider() {
+ return instance_ ? instance_->thread_provider_ : NULL;
+ }
+
+ private:
+ friend class SimpleBackendProxy;
+ friend class SimpleFrontendProxy;
+ friend class appcache::AppCacheThread;
+
// Instance methods called by our static public methods
void InitOnUIThread(const FilePath& cache_directory);
void InitOnIOThread(URLRequestContext* request_context);
@@ -136,6 +158,9 @@ class SimpleAppCacheSystem : public MessageLoop::DestructionObserver {
// We start a thread for use as the DB thread.
base::Thread db_thread_;
+ // Some unittests create there own IO and DB threads.
+ ThreadProvider* thread_provider_;
+
// A low-tech singleton.
static SimpleAppCacheSystem* instance_;
};
diff --git a/webkit/tools/test_shell/test_shell.gyp b/webkit/tools/test_shell/test_shell.gyp
index 1b5ff01..3b78abf 100644
--- a/webkit/tools/test_shell/test_shell.gyp
+++ b/webkit/tools/test_shell/test_shell.gyp
@@ -380,11 +380,13 @@
'../../../skia/ext/vector_canvas_unittest.cc',
'../../appcache/manifest_parser_unittest.cc',
'../../appcache/appcache_unittest.cc',
+ '../../appcache/appcache_database_unittest.cc',
'../../appcache/appcache_group_unittest.cc',
'../../appcache/appcache_host_unittest.cc',
'../../appcache/appcache_request_handler_unittest.cc',
'../../appcache/appcache_response_unittest.cc',
'../../appcache/appcache_storage_unittest.cc',
+ '../../appcache/appcache_storage_impl_unittest.cc',
'../../appcache/appcache_update_job_unittest.cc',
'../../appcache/appcache_url_request_job_unittest.cc',
'../../appcache/mock_appcache_service.h',
diff --git a/webkit/webkit.gyp b/webkit/webkit.gyp
index 37699cb..cf0ff34 100644
--- a/webkit/webkit.gyp
+++ b/webkit/webkit.gyp
@@ -137,6 +137,7 @@
'type': '<(library)',
'msvs_guid': '0B945915-31A7-4A07-A5B5-568D737A39B1',
'dependencies': [
+ '../app/app.gyp:app_base',
'../net/net.gyp:net',
'../third_party/WebKit/WebKit/chromium/WebKit.gyp:webkit',
],
@@ -146,6 +147,8 @@
'appcache/appcache.h',
'appcache/appcache_backend_impl.cc',
'appcache/appcache_backend_impl.h',
+ 'appcache/appcache_database.cc',
+ 'appcache/appcache_database.h',
'appcache/appcache_entry.h',
'appcache/appcache_frontend_impl.cc',
'appcache/appcache_frontend_impl.h',
@@ -188,6 +191,7 @@
'type': '<(library)',
'msvs_guid': '1DA00DDD-44E5-4C56-B2CC-414FB0164492',
'dependencies': [
+ '../app/app.gyp:app_base',
'../base/base.gyp:base',
'../third_party/sqlite/sqlite.gyp:sqlite',
],