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..2ff9008fe887da758fa47d6c1a5f3fbfce0e8630 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,41 +26,95 @@ |
| // - 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; |
| -} // namespace |
| +bool RemovePrefix(const std::string& str, |
| + const std::string& prefix, |
| + std::string* out) { |
| + DCHECK(out); |
| + if (!StartsWithASCII(str, prefix, true)) |
| + return false; |
| + *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 ServiceWorkerRegistrationData& data, |
| + leveldb::WriteBatch* batch) { |
| + DCHECK(batch); |
| + std::string value; |
| + bool success = data.SerializeToString(&value); |
| + DCHECK(success); |
| + GURL origin = GURL(data.scope_url()).GetOrigin(); |
|
michaeln
2014/04/25 00:48:21
Maybe this class should entirely encapsulate the u
nhiroki
2014/04/25 02:06:11
I see. Will make them as required.
kinuko
2014/04/25 03:43:27
Fyi, it's possible to populate a field with a defa
nhiroki
2014/04/25 11:01:05
And there is base::Time issue.
michaeln
2014/04/26 00:29:02
I think the serialization format is an impl detail
|
| + batch->Put(CreateRegistrationKey(data.registration_id(), origin), value); |
| +} |
| -ServiceWorkerDatabase::RegistrationData::RegistrationData() |
| - : registration_id(-1), |
| - version_id(-1), |
| - is_active(false), |
| - has_fetch_handler(false) { |
| +void PutUniqueOriginToBatch(const GURL& origin, |
| + leveldb::WriteBatch* batch) { |
| + // Value should be empty. |
| + batch->Put(CreateUniqueOriginKey(origin), ""); |
| } |
| -ServiceWorkerDatabase::RegistrationData::~RegistrationData() { |
| +bool ParseRegistrationData(const std::string& serialized, |
| + ServiceWorkerRegistrationData* out) { |
| + DCHECK(out); |
| + ServiceWorkerRegistrationData data; |
| + if (!data.ParseFromString(serialized)) |
| + return false; |
| + if (GURL(data.scope_url()).GetOrigin() != GURL(data.script_url()).GetOrigin()) |
| + return false; |
| + *out = data; |
| + return true; |
| } |
| +} // namespace |
| + |
| ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) |
| : path_(path), |
| is_disabled_(false), |
| @@ -95,6 +153,175 @@ bool ServiceWorkerDatabase::GetNextAvailableIds( |
| return true; |
| } |
| +bool ServiceWorkerDatabase::GetOriginsWithRegistrations( |
| + std::set<GURL>* origins) { |
| + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
| + DCHECK(origins); |
| + |
| + if (!LazyOpen(false) || is_disabled_) |
| + return false; |
| + |
| + 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 key = itr->key().ToString(); |
| + if (!StartsWithASCII(key, kUniqueOriginKey, true)) |
| + break; |
| + |
| + std::string origin; |
| + bool success = RemovePrefix(key, kUniqueOriginKey, &origin); |
|
michaeln
2014/04/25 00:48:21
This function is funky? Either it doesn't need to
nhiroki
2014/04/25 11:01:05
Done.
|
| + DCHECK(success); |
| + origins->insert(GURL(origin)); |
| + } |
| + return true; |
| +} |
| + |
| +bool ServiceWorkerDatabase::GetRegistrationsForOrigin( |
| + const GURL& origin, |
| + std::vector<ServiceWorkerRegistrationData>* 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; |
| + } |
| + |
| + std::string key = itr->key().ToString(); |
| + if (!StartsWithASCII(key, prefix, true)) |
| + break; |
| + |
| + ServiceWorkerRegistrationData 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, |
| + ServiceWorkerRegistrationData* registration, |
| + std::vector<ServiceWorkerResourceRecord>* resources) { |
| + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
| + DCHECK(registration); |
| + DCHECK(resources); |
| + |
| + if (!LazyOpen(false) || is_disabled_) |
| + return false; |
| + |
| + ServiceWorkerRegistrationData 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 ServiceWorkerRegistrationData& registration, |
| + const std::vector<ServiceWorkerResourceRecord>& resources) { |
| + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
| + if (!LazyOpen(true) || is_disabled_) |
| + return false; |
| + |
| + leveldb::WriteBatch batch; |
| + if (!BumpNextAvailableIdIfNeeded( |
| + kNextRegIdKey, registration.registration_id(), &batch) || |
| + !BumpNextAvailableIdIfNeeded( |
| + kNextVerIdKey, registration.version_id(), &batch)) { |
| + return false; |
| + } |
| + |
| + PutUniqueOriginToBatch(GURL(registration.scope_url()).GetOrigin(), &batch); |
|
michaeln
2014/04/25 00:48:21
is leveldb smart about noticing when a write doesn
nhiroki
2014/04/25 02:06:11
AFAICS, leveldb always writes log record even if t
nhiroki
2014/04/25 11:01:05
Let me address this in a following patch (added TO
|
| + 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; |
| + |
| + ServiceWorkerRegistrationData registration; |
| + if (!ReadRegistrationData(registration_id, origin, ®istration)) |
| + return false; |
| + |
| + registration.set_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; |
| + |
| + ServiceWorkerRegistrationData registration; |
| + if (!ReadRegistrationData(registration_id, origin, ®istration)) |
| + return false; |
| + |
| + DCHECK_LT(registration.last_update_check_time(), time.ToInternalValue()); |
| + registration.set_last_update_check_time(time.ToInternalValue()); |
| + |
| + 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<ServiceWorkerRegistrationData> 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()) |
| @@ -173,6 +400,32 @@ bool ServiceWorkerDatabase::ReadNextAvailableId( |
| return true; |
| } |
| +bool ServiceWorkerDatabase::ReadRegistrationData( |
| + int64 registration_id, |
| + const GURL& origin, |
| + ServiceWorkerRegistrationData* 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; |
| + } |
| + |
| + ServiceWorkerRegistrationData parsed; |
| + if (!ParseRegistrationData(value, &parsed)) { |
| + HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); |
| + return false; |
| + } |
| + |
| + *registration = parsed; |
| + return true; |
| +} |
| + |
| bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) { |
| DCHECK(batch); |
| DCHECK(!is_disabled_); |
| @@ -184,6 +437,17 @@ bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) { |
| return true; |
| } |
| +bool ServiceWorkerDatabase::BumpNextAvailableIdIfNeeded( |
|
michaeln
2014/04/25 00:48:21
As coded this function will have problems if an id
nhiroki
2014/04/25 11:01:05
Good idea! Addressed it.
|
| + const char* id_key, int64 used_id, leveldb::WriteBatch* batch) { |
| + DCHECK(batch); |
| + int64 next_available_id; |
| + if (!ReadNextAvailableId(id_key, &next_available_id)) |
| + return false; |
| + if (next_available_id <= used_id) |
| + batch->Put(id_key, base::Int64ToString(used_id + 1)); |
| + return true; |
| +} |
| + |
| bool ServiceWorkerDatabase::IsOpen() { |
| return db_.get() != NULL; |
| } |