summaryrefslogtreecommitdiffstats
path: root/webkit/appcache
diff options
context:
space:
mode:
Diffstat (limited to 'webkit/appcache')
-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
15 files changed, 3565 insertions, 25 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 {