Chromium Code Reviews| 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( |