Index: content/browser/service_worker/service_worker_database.cc |
diff --git a/content/browser/service_worker/service_worker_database.cc b/content/browser/service_worker/service_worker_database.cc |
index 469223ba3e980c6f6c42b4324d261894bd1bb7c4..980e0bc7b4a7dfea14ea6ba8ec62ff053401f01d 100644 |
--- a/content/browser/service_worker/service_worker_database.cc |
+++ b/content/browser/service_worker/service_worker_database.cc |
@@ -10,6 +10,10 @@ |
#include "base/location.h" |
#include "base/logging.h" |
#include "base/strings/string_number_conversions.h" |
+#include "base/strings/string_split.h" |
+#include "base/strings/string_util.h" |
+#include "base/strings/stringprintf.h" |
+#include "content/browser/service_worker/service_worker_database.pb.h" |
#include "third_party/leveldatabase/src/helpers/memenv/memenv.h" |
#include "third_party/leveldatabase/src/include/leveldb/db.h" |
#include "third_party/leveldatabase/src/include/leveldb/env.h" |
@@ -22,29 +26,118 @@ |
// - int64 value is serialized as a string by base::Int64ToString(). |
// |
// Version 1 (in sorted order) |
-// key: "DB_VERSION" |
+// key: "INITDATA_DB_VERSION" |
// value: "1" |
// |
-// key: "NEXT_REGISTRATION_ID" |
+// key: "INITDATA_NEXT_REGISTRATION_ID" |
// value: <int64 'next_available_registration_id'> |
// |
-// key: "NEXT_RESOURCE_ID" |
+// key: "INITDATA_NEXT_RESOURCE_ID" |
// value: <int64 'next_available_resource_id'> |
// |
-// key: "NEXT_VERSION_ID" |
+// key: "INITDATA_NEXT_VERSION_ID" |
// value: <int64 'next_available_version_id'> |
+// |
+// key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin' serialized by GURL::spec()> |
+// value: <empty> |
+// |
+// key: "REG:" + (1) + '\x00' + (2) |
+// (1) <GURL 'origin' serialized by GURL::spec()> |
+// (2) <int64 'registration_id'> |
+// (ex. "REG:http://example.com\x00123456") |
+// value: <ServiceWorkerRegistrationData serialized as a string> |
namespace content { |
namespace { |
-const char kDatabaseVersionKey[] = "DB_VERSION"; |
-const char kNextRegIdKey[] = "NEXT_REGISTRATION_ID"; |
-const char kNextResIdKey[] = "NEXT_RESOURCE_ID"; |
-const char kNextVerIdKey[] = "NEXT_VERSION_ID"; |
+const char kDatabaseVersionKey[] = "INITDATA_DB_VERSION"; |
+const char kNextRegIdKey[] = "INITDATA_NEXT_REGISTRATION_ID"; |
+const char kNextResIdKey[] = "INITDATA_NEXT_RESOURCE_ID"; |
+const char kNextVerIdKey[] = "INITDATA_NEXT_VERSION_ID"; |
+const char kUniqueOriginKey[] = "INITDATA_UNIQUE_ORIGIN:"; |
+ |
+const char kRegKeyPrefix[] = "REG:"; |
+const char kKeySeparator = '\x00'; |
const int64 kCurrentSchemaVersion = 1; |
+bool RemovePrefix(const std::string& str, |
+ const std::string& prefix, |
+ std::string* out) { |
+ if (!StartsWithASCII(str, prefix, true)) |
+ return false; |
+ if (out) |
+ *out = str.substr(prefix.size()); |
+ return true; |
+} |
+ |
+std::string CreateRegistrationKey(int64 registration_id, |
+ const GURL& origin) { |
+ return base::StringPrintf("%s%s%c%s", |
+ kRegKeyPrefix, |
+ origin.spec().c_str(), |
+ kKeySeparator, |
+ base::Int64ToString(registration_id).c_str()); |
+} |
+ |
+std::string CreateUniqueOriginKey(const GURL& origin) { |
+ return base::StringPrintf("%s%s", kUniqueOriginKey, origin.spec().c_str()); |
+} |
+ |
+void PutRegistrationDataToBatch( |
+ const ServiceWorkerDatabase::RegistrationData& input, |
+ leveldb::WriteBatch* batch) { |
+ DCHECK(batch); |
+ |
+ // Convert RegistrationData to ServiceWorkerRegistrationData. |
+ ServiceWorkerRegistrationData data; |
+ data.set_registration_id(input.registration_id); |
+ data.set_scope_url(input.scope.spec()); |
+ data.set_script_url(input.script.spec()); |
+ data.set_version_id(input.version_id); |
+ data.set_is_active(input.is_active); |
+ data.set_has_fetch_handler(input.has_fetch_handler); |
+ data.set_last_update_check_time(input.last_update_check.ToInternalValue()); |
+ |
+ std::string value; |
+ bool success = data.SerializeToString(&value); |
+ DCHECK(success); |
+ GURL origin = GURL(data.scope_url()).GetOrigin(); |
michaeln
2014/04/26 00:29:02
could use input.scope.GetOrigin()
nhiroki
2014/04/28 04:15:59
Done.
|
+ batch->Put(CreateRegistrationKey(data.registration_id(), origin), value); |
+} |
+ |
+void PutUniqueOriginToBatch(const GURL& origin, |
+ leveldb::WriteBatch* batch) { |
+ // Value should be empty. |
+ batch->Put(CreateUniqueOriginKey(origin), ""); |
+} |
+ |
+bool ParseRegistrationData( |
+ const std::string& serialized, |
+ ServiceWorkerDatabase::RegistrationData* out) { |
+ DCHECK(out); |
+ ServiceWorkerRegistrationData data; |
+ if (!data.ParseFromString(serialized)) |
+ return false; |
+ |
+ GURL scope_url(data.scope_url()); |
+ GURL script_url(data.script_url()); |
+ if (scope_url.GetOrigin() != script_url.GetOrigin()) |
+ return false; |
+ |
+ // Convert ServiceWorkerRegistrationData to RegistrationData. |
+ out->registration_id = data.registration_id(); |
+ out->scope = scope_url; |
+ out->script = script_url; |
+ out->version_id = data.version_id(); |
+ out->is_active = data.is_active(); |
+ out->has_fetch_handler = data.has_fetch_handler(); |
+ out->last_update_check = |
+ base::Time::FromInternalValue(data.last_update_check_time()); |
+ return true; |
+} |
+ |
} // namespace |
ServiceWorkerDatabase::RegistrationData::RegistrationData() |
@@ -59,6 +152,9 @@ ServiceWorkerDatabase::RegistrationData::~RegistrationData() { |
ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) |
: path_(path), |
+ next_avail_registration_id_(0), |
+ next_avail_resource_id_(0), |
+ next_avail_version_id_(0), |
is_disabled_(false), |
was_corruption_detected_(false) { |
} |
@@ -80,21 +176,180 @@ bool ServiceWorkerDatabase::GetNextAvailableIds( |
if (!LazyOpen(false) || is_disabled_) |
return false; |
- int64 reg_id = -1; |
- int64 ver_id = -1; |
- int64 res_id = -1; |
+ if (!ReadNextAvailableId(kNextRegIdKey, &next_avail_registration_id_) || |
+ !ReadNextAvailableId(kNextVerIdKey, &next_avail_version_id_) || |
+ !ReadNextAvailableId(kNextResIdKey, &next_avail_resource_id_)) { |
+ return false; |
+ } |
- if (!ReadNextAvailableId(kNextRegIdKey, ®_id) || |
- !ReadNextAvailableId(kNextVerIdKey, &ver_id) || |
- !ReadNextAvailableId(kNextResIdKey, &res_id)) |
+ *next_avail_registration_id = next_avail_registration_id_; |
+ *next_avail_version_id = next_avail_version_id_; |
+ *next_avail_resource_id = next_avail_resource_id_; |
+ return true; |
+} |
+ |
+bool ServiceWorkerDatabase::GetOriginsWithRegistrations( |
+ std::set<GURL>* origins) { |
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
+ DCHECK(origins); |
+ |
+ if (!LazyOpen(false) || is_disabled_) |
return false; |
- *next_avail_registration_id = reg_id; |
- *next_avail_version_id = ver_id; |
- *next_avail_resource_id = res_id; |
+ scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); |
+ for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) { |
+ if (!itr->status().ok()) { |
+ HandleError(FROM_HERE, itr->status()); |
+ origins->clear(); |
+ return false; |
+ } |
+ |
+ std::string origin; |
+ if (!RemovePrefix(itr->key().ToString(), kUniqueOriginKey, &origin)) |
+ break; |
+ origins->insert(GURL(origin)); |
+ } |
return true; |
} |
+bool ServiceWorkerDatabase::GetRegistrationsForOrigin( |
+ const GURL& origin, |
+ std::vector<RegistrationData>* registrations) { |
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
+ DCHECK(registrations); |
+ |
+ if (!LazyOpen(false) || is_disabled_) |
+ return false; |
+ |
+ // Create a key prefix for registrations. |
+ std::string prefix = base::StringPrintf( |
+ "%s%s%c", kRegKeyPrefix, origin.spec().c_str(), kKeySeparator); |
+ |
+ scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); |
+ for (itr->Seek(prefix); itr->Valid(); itr->Next()) { |
+ if (!itr->status().ok()) { |
+ HandleError(FROM_HERE, itr->status()); |
+ registrations->clear(); |
+ return false; |
+ } |
+ |
+ if (!RemovePrefix(itr->key().ToString(), prefix, NULL)) |
+ break; |
+ |
+ RegistrationData registration; |
+ if (!ParseRegistrationData(itr->value().ToString(), ®istration)) { |
+ HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); |
+ registrations->clear(); |
+ return false; |
+ } |
+ registrations->push_back(registration); |
+ } |
+ return true; |
+} |
+ |
+bool ServiceWorkerDatabase::ReadRegistration( |
+ int64 registration_id, |
+ const GURL& origin, |
+ RegistrationData* registration, |
+ std::vector<ResourceRecord>* resources) { |
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
+ DCHECK(registration); |
+ DCHECK(resources); |
+ |
+ if (!LazyOpen(false) || is_disabled_) |
+ return false; |
+ |
+ RegistrationData value; |
+ if (!ReadRegistrationData(registration_id, origin, &value)) |
+ return false; |
+ |
+ // TODO(nhiroki): Read ResourceRecords tied with this registration. |
+ |
+ *registration = value; |
+ return true; |
+} |
+ |
+bool ServiceWorkerDatabase::WriteRegistration( |
+ const RegistrationData& registration, |
+ const std::vector<ResourceRecord>& resources) { |
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
+ if (!LazyOpen(true) || is_disabled_) |
+ return false; |
+ |
+ leveldb::WriteBatch batch; |
+ BumpNextRegistrationIdIfNeeded(registration.registration_id, &batch); |
+ BumpNextVersionIdIfNeeded(registration.version_id, &batch); |
+ |
+ // TODO(nhiroki): Skip to add the origin into the unique origin list if it |
+ // has already been added. |
+ PutUniqueOriginToBatch(registration.scope.GetOrigin(), &batch); |
+ |
+ PutRegistrationDataToBatch(registration, &batch); |
+ |
+ // TODO(nhiroki): Write |resources| into the database. |
+ return WriteBatch(&batch); |
+} |
+ |
+bool ServiceWorkerDatabase::UpdateVersionToActive(int64 registration_id, |
+ const GURL& origin) { |
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
+ if (!LazyOpen(false) || is_disabled_) |
+ return false; |
+ |
+ RegistrationData registration; |
+ if (!ReadRegistrationData(registration_id, origin, ®istration)) |
+ return false; |
+ |
+ registration.is_active = true; |
+ |
+ leveldb::WriteBatch batch; |
+ PutRegistrationDataToBatch(registration, &batch); |
+ return WriteBatch(&batch); |
+} |
+ |
+bool ServiceWorkerDatabase::UpdateLastCheckTime(int64 registration_id, |
+ const GURL& origin, |
+ const base::Time& time) { |
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
+ if (!LazyOpen(false) || is_disabled_) |
+ return false; |
+ |
+ RegistrationData registration; |
+ if (!ReadRegistrationData(registration_id, origin, ®istration)) |
+ return false; |
+ |
+ registration.last_update_check = time; |
+ |
+ leveldb::WriteBatch batch; |
+ PutRegistrationDataToBatch(registration, &batch); |
+ return WriteBatch(&batch); |
+} |
+ |
+bool ServiceWorkerDatabase::DeleteRegistration(int64 registration_id, |
+ const GURL& origin) { |
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
+ if (!LazyOpen(false) || is_disabled_) |
+ return false; |
+ |
+ leveldb::WriteBatch batch; |
+ |
+ // Remove |origin| from unique origins if a registration specified by |
+ // |registration_id| is the only one for |origin|. |
+ // TODO(nhiroki): Check the uniqueness by more efficient way. |
+ std::vector<RegistrationData> registrations; |
+ if (!GetRegistrationsForOrigin(origin, ®istrations)) |
+ return false; |
+ if (registrations.size() == 1 && |
+ registrations[0].registration_id == registration_id) { |
+ batch.Delete(CreateUniqueOriginKey(origin)); |
+ } |
+ |
+ batch.Delete(CreateRegistrationKey(registration_id, origin)); |
+ |
+ // TODO(nhiroki): Delete ResourceRecords tied with this registration. |
+ return WriteBatch(&batch); |
+} |
+ |
bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) { |
DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
if (IsOpen()) |
@@ -134,23 +389,14 @@ bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) { |
} |
db_.reset(db); |
- if (IsEmpty() && !PopulateInitialData()) { |
nhiroki
2014/04/25 11:05:01
fyi: Removed all write operations from LazyOpen().
michaeln
2014/04/26 00:29:02
fantastique ;)
|
- DLOG(ERROR) << "Failed to populate the database."; |
- db_.reset(); |
+ int64 db_version; |
+ if (!ReadDatabaseVersion(&db_version)) |
return false; |
- } |
+ if (db_version > 0) |
+ is_initialized_ = true; |
return true; |
} |
-bool ServiceWorkerDatabase::PopulateInitialData() { |
- leveldb::WriteBatch batch; |
- batch.Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion)); |
- batch.Put(kNextRegIdKey, base::Int64ToString(0)); |
- batch.Put(kNextResIdKey, base::Int64ToString(0)); |
- batch.Put(kNextVerIdKey, base::Int64ToString(0)); |
- return WriteBatch(&batch); |
-} |
- |
bool ServiceWorkerDatabase::ReadNextAvailableId( |
const char* id_key, int64* next_avail_id) { |
DCHECK(id_key); |
@@ -158,6 +404,12 @@ bool ServiceWorkerDatabase::ReadNextAvailableId( |
std::string value; |
leveldb::Status status = db_->Get(leveldb::ReadOptions(), id_key, &value); |
+ if (status.IsNotFound()) { |
+ // Nobody has gotten the next resource id for |id_key|. |
+ *next_avail_id = 0; |
+ return true; |
+ } |
+ |
if (!status.ok()) { |
HandleError(FROM_HERE, status); |
return false; |
@@ -173,9 +425,66 @@ bool ServiceWorkerDatabase::ReadNextAvailableId( |
return true; |
} |
+bool ServiceWorkerDatabase::ReadRegistrationData( |
+ int64 registration_id, |
+ const GURL& origin, |
+ RegistrationData* registration) { |
+ DCHECK(registration); |
+ |
+ std::string key = CreateRegistrationKey(registration_id, origin); |
+ |
+ std::string value; |
+ leveldb::Status status = db_->Get(leveldb::ReadOptions(), key, &value); |
+ if (!status.ok()) { |
+ if (!status.IsNotFound()) |
+ HandleError(FROM_HERE, status); |
+ return false; |
+ } |
+ |
+ RegistrationData parsed; |
+ if (!ParseRegistrationData(value, &parsed)) { |
+ HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); |
+ return false; |
+ } |
+ |
+ *registration = parsed; |
+ return true; |
+} |
+ |
+bool ServiceWorkerDatabase::ReadDatabaseVersion(int64* db_version) { |
+ std::string value; |
+ leveldb::Status status = |
+ db_->Get(leveldb::ReadOptions(), kDatabaseVersionKey, &value); |
+ if (status.IsNotFound()) { |
+ // The database hasn't been initialized yet. |
+ *db_version = 0; |
+ return true; |
+ } |
+ if (!status.ok()) { |
+ HandleError(FROM_HERE, status); |
+ return false; |
+ } |
+ |
+ int64 parsed; |
+ if (!base::StringToInt64(value, &parsed)) { |
+ HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); |
+ return false; |
+ } |
+ |
michaeln
2014/04/26 00:29:02
??
const int kFirstValidVersion = 1;
if (parsed <
nhiroki
2014/04/28 04:15:59
Done.
|
+ *db_version = parsed; |
+ return true; |
+} |
+ |
bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) { |
DCHECK(batch); |
DCHECK(!is_disabled_); |
+ |
+ if (!is_initialized_) { |
+ // Write the database schema version. |
+ batch->Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion)); |
+ is_initialized_ = true; |
+ } |
+ |
leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch); |
if (!status.ok()) { |
HandleError(FROM_HERE, status); |
@@ -184,16 +493,26 @@ bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) { |
return true; |
} |
-bool ServiceWorkerDatabase::IsOpen() { |
- return db_.get() != NULL; |
+void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded( |
+ int64 used_id, leveldb::WriteBatch* batch) { |
+ DCHECK(batch); |
+ if (next_avail_registration_id_ <= used_id) { |
+ next_avail_registration_id_ = used_id + 1; |
+ batch->Put(kNextRegIdKey, base::Int64ToString(next_avail_registration_id_)); |
+ } |
} |
-bool ServiceWorkerDatabase::IsEmpty() { |
- scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); |
- itr->SeekToFirst(); |
- // TODO(nhiroki): Handle an error. |
- DCHECK(itr->status().ok()); |
- return !itr->Valid(); |
+void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded( |
+ int64 used_id, leveldb::WriteBatch* batch) { |
+ DCHECK(batch); |
+ if (next_avail_version_id_ <= used_id) { |
+ next_avail_version_id_ = used_id + 1; |
+ batch->Put(kNextVerIdKey, base::Int64ToString(next_avail_version_id_)); |
+ } |
+} |
+ |
+bool ServiceWorkerDatabase::IsOpen() { |
+ return db_.get() != NULL; |
} |
void ServiceWorkerDatabase::HandleError( |