| 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..9859a1cbefd932912536bdf1dbd7e96d0b815a06 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"
|
| @@ -20,31 +24,119 @@
|
| //
|
| // NOTE
|
| // - int64 value is serialized as a string by base::Int64ToString().
|
| +// - GURL value is serialized as a string by GURL::spec().
|
| //
|
| // 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'>
|
| +// value: <empty>
|
| +//
|
| +// key: "REG:" + <GURL 'origin'> + '\x00' + <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 = input.scope.GetOrigin();
|
| + 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 +151,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 +175,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;
|
| + }
|
| +
|
| + *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 (!ReadNextAvailableId(kNextRegIdKey, ®_id) ||
|
| - !ReadNextAvailableId(kNextVerIdKey, &ver_id) ||
|
| - !ReadNextAvailableId(kNextResIdKey, &res_id))
|
| + 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 +388,14 @@ bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) {
|
| }
|
| db_.reset(db);
|
|
|
| - if (IsEmpty() && !PopulateInitialData()) {
|
| - 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 +403,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 +424,72 @@ 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;
|
| + }
|
| +
|
| + const int kFirstValidVersion = 1;
|
| + if (parsed < kFirstValidVersion || kCurrentSchemaVersion < parsed) {
|
| + HandleError(FROM_HERE, leveldb::Status::Corruption("invalid DB version"));
|
| + return false;
|
| + }
|
| +
|
| + *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 +498,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(
|
|
|