Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(217)

Side by Side Diff: content/browser/service_worker/service_worker_database.cc

Issue 248803003: ServiceWorker: Store registration data in ServiceWorkerDatabase (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase on https://codereview.chromium.org/257593003/ Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/service_worker/service_worker_database.h" 5 #include "content/browser/service_worker/service_worker_database.h"
6 6
7 #include <string> 7 #include <string>
8 8
9 #include "base/file_util.h" 9 #include "base/file_util.h"
10 #include "base/location.h" 10 #include "base/location.h"
11 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "content/browser/service_worker/service_worker_database.pb.h"
13 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" 17 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
14 #include "third_party/leveldatabase/src/include/leveldb/db.h" 18 #include "third_party/leveldatabase/src/include/leveldb/db.h"
15 #include "third_party/leveldatabase/src/include/leveldb/env.h" 19 #include "third_party/leveldatabase/src/include/leveldb/env.h"
16 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" 20 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
17 21
18 // LevelDB database schema 22 // LevelDB database schema
19 // ======================= 23 // =======================
20 // 24 //
21 // NOTE 25 // NOTE
22 // - int64 value is serialized as a string by base::Int64ToString(). 26 // - int64 value is serialized as a string by base::Int64ToString().
23 // 27 //
24 // Version 1 (in sorted order) 28 // Version 1 (in sorted order)
25 // key: "DB_VERSION" 29 // key: "INITDATA_DB_VERSION"
26 // value: "1" 30 // value: "1"
27 // 31 //
28 // key: "NEXT_REGISTRATION_ID" 32 // key: "INITDATA_NEXT_REGISTRATION_ID"
29 // value: <int64 'next_available_registration_id'> 33 // value: <int64 'next_available_registration_id'>
30 // 34 //
31 // key: "NEXT_RESOURCE_ID" 35 // key: "INITDATA_NEXT_RESOURCE_ID"
32 // value: <int64 'next_available_resource_id'> 36 // value: <int64 'next_available_resource_id'>
33 // 37 //
34 // key: "NEXT_VERSION_ID" 38 // key: "INITDATA_NEXT_VERSION_ID"
35 // value: <int64 'next_available_version_id'> 39 // value: <int64 'next_available_version_id'>
40 //
41 // key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin' serialized by GURL::spec()>
42 // value: <empty>
43 //
44 // key: "REG:" + (1) + '\x00' + (2)
45 // (1) <GURL 'origin' serialized by GURL::spec()>
46 // (2) <int64 'registration_id'>
47 // (ex. "REG:http://example.com\x00123456")
48 // value: <ServiceWorkerRegistrationData serialized as a string>
36 49
37 namespace content { 50 namespace content {
38 51
39 namespace { 52 namespace {
40 53
41 const char kDatabaseVersionKey[] = "DB_VERSION"; 54 const char kDatabaseVersionKey[] = "INITDATA_DB_VERSION";
42 const char kNextRegIdKey[] = "NEXT_REGISTRATION_ID"; 55 const char kNextRegIdKey[] = "INITDATA_NEXT_REGISTRATION_ID";
43 const char kNextResIdKey[] = "NEXT_RESOURCE_ID"; 56 const char kNextResIdKey[] = "INITDATA_NEXT_RESOURCE_ID";
44 const char kNextVerIdKey[] = "NEXT_VERSION_ID"; 57 const char kNextVerIdKey[] = "INITDATA_NEXT_VERSION_ID";
58 const char kUniqueOriginKey[] = "INITDATA_UNIQUE_ORIGIN:";
59
60 const char kRegKeyPrefix[] = "REG:";
61 const char kKeySeparator = '\x00';
45 62
46 const int64 kCurrentSchemaVersion = 1; 63 const int64 kCurrentSchemaVersion = 1;
47 64
48 } // namespace 65 bool RemovePrefix(const std::string& str,
49 66 const std::string& prefix,
50 ServiceWorkerDatabase::RegistrationData::RegistrationData() 67 std::string* out) {
51 : registration_id(-1), 68 DCHECK(out);
52 version_id(-1), 69 if (!StartsWithASCII(str, prefix, true))
53 is_active(false), 70 return false;
54 has_fetch_handler(false) { 71 *out = str.substr(prefix.size());
72 return true;
55 } 73 }
56 74
57 ServiceWorkerDatabase::RegistrationData::~RegistrationData() { 75 std::string CreateRegistrationKey(int64 registration_id,
76 const GURL& origin) {
77 return base::StringPrintf("%s%s%c%s",
78 kRegKeyPrefix,
79 origin.spec().c_str(),
80 kKeySeparator,
81 base::Int64ToString(registration_id).c_str());
58 } 82 }
59 83
84 std::string CreateUniqueOriginKey(const GURL& origin) {
85 return base::StringPrintf("%s%s", kUniqueOriginKey, origin.spec().c_str());
86 }
87
88 void PutRegistrationDataToBatch(const ServiceWorkerRegistrationData& data,
89 leveldb::WriteBatch* batch) {
90 DCHECK(batch);
91 std::string value;
92 bool success = data.SerializeToString(&value);
93 DCHECK(success);
94 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
95 batch->Put(CreateRegistrationKey(data.registration_id(), origin), value);
96 }
97
98 void PutUniqueOriginToBatch(const GURL& origin,
99 leveldb::WriteBatch* batch) {
100 // Value should be empty.
101 batch->Put(CreateUniqueOriginKey(origin), "");
102 }
103
104 bool ParseRegistrationData(const std::string& serialized,
105 ServiceWorkerRegistrationData* out) {
106 DCHECK(out);
107 ServiceWorkerRegistrationData data;
108 if (!data.ParseFromString(serialized))
109 return false;
110 if (GURL(data.scope_url()).GetOrigin() != GURL(data.script_url()).GetOrigin())
111 return false;
112 *out = data;
113 return true;
114 }
115
116 } // namespace
117
60 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) 118 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path)
61 : path_(path), 119 : path_(path),
62 is_disabled_(false), 120 is_disabled_(false),
63 was_corruption_detected_(false) { 121 was_corruption_detected_(false) {
64 } 122 }
65 123
66 ServiceWorkerDatabase::~ServiceWorkerDatabase() { 124 ServiceWorkerDatabase::~ServiceWorkerDatabase() {
67 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 125 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
68 db_.reset(); 126 db_.reset();
69 } 127 }
(...skipping 18 matching lines...) Expand all
88 !ReadNextAvailableId(kNextVerIdKey, &ver_id) || 146 !ReadNextAvailableId(kNextVerIdKey, &ver_id) ||
89 !ReadNextAvailableId(kNextResIdKey, &res_id)) 147 !ReadNextAvailableId(kNextResIdKey, &res_id))
90 return false; 148 return false;
91 149
92 *next_avail_registration_id = reg_id; 150 *next_avail_registration_id = reg_id;
93 *next_avail_version_id = ver_id; 151 *next_avail_version_id = ver_id;
94 *next_avail_resource_id = res_id; 152 *next_avail_resource_id = res_id;
95 return true; 153 return true;
96 } 154 }
97 155
156 bool ServiceWorkerDatabase::GetOriginsWithRegistrations(
157 std::set<GURL>* origins) {
158 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
159 DCHECK(origins);
160
161 if (!LazyOpen(false) || is_disabled_)
162 return false;
163
164 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
165 for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) {
166 if (!itr->status().ok()) {
167 HandleError(FROM_HERE, itr->status());
168 origins->clear();
169 return false;
170 }
171
172 std::string key = itr->key().ToString();
173 if (!StartsWithASCII(key, kUniqueOriginKey, true))
174 break;
175
176 std::string origin;
177 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.
178 DCHECK(success);
179 origins->insert(GURL(origin));
180 }
181 return true;
182 }
183
184 bool ServiceWorkerDatabase::GetRegistrationsForOrigin(
185 const GURL& origin,
186 std::vector<ServiceWorkerRegistrationData>* registrations) {
187 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
188 DCHECK(registrations);
189
190 if (!LazyOpen(false) || is_disabled_)
191 return false;
192
193 // Create a key prefix for registrations.
194 std::string prefix = base::StringPrintf(
195 "%s%s%c", kRegKeyPrefix, origin.spec().c_str(), kKeySeparator);
196
197 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
198 for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
199 if (!itr->status().ok()) {
200 HandleError(FROM_HERE, itr->status());
201 registrations->clear();
202 return false;
203 }
204
205 std::string key = itr->key().ToString();
206 if (!StartsWithASCII(key, prefix, true))
207 break;
208
209 ServiceWorkerRegistrationData registration;
210 if (!ParseRegistrationData(itr->value().ToString(), &registration)) {
211 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse"));
212 registrations->clear();
213 return false;
214 }
215 registrations->push_back(registration);
216 }
217 return true;
218 }
219
220 bool ServiceWorkerDatabase::ReadRegistration(
221 int64 registration_id,
222 const GURL& origin,
223 ServiceWorkerRegistrationData* registration,
224 std::vector<ServiceWorkerResourceRecord>* resources) {
225 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
226 DCHECK(registration);
227 DCHECK(resources);
228
229 if (!LazyOpen(false) || is_disabled_)
230 return false;
231
232 ServiceWorkerRegistrationData value;
233 if (!ReadRegistrationData(registration_id, origin, &value))
234 return false;
235
236 // TODO(nhiroki): Read ResourceRecords tied with this registration.
237
238 *registration = value;
239 return true;
240 }
241
242 bool ServiceWorkerDatabase::WriteRegistration(
243 const ServiceWorkerRegistrationData& registration,
244 const std::vector<ServiceWorkerResourceRecord>& resources) {
245 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
246 if (!LazyOpen(true) || is_disabled_)
247 return false;
248
249 leveldb::WriteBatch batch;
250 if (!BumpNextAvailableIdIfNeeded(
251 kNextRegIdKey, registration.registration_id(), &batch) ||
252 !BumpNextAvailableIdIfNeeded(
253 kNextVerIdKey, registration.version_id(), &batch)) {
254 return false;
255 }
256
257 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
258 PutRegistrationDataToBatch(registration, &batch);
259
260 // TODO(nhiroki): Write |resources| into the database.
261 return WriteBatch(&batch);
262 }
263
264 bool ServiceWorkerDatabase::UpdateVersionToActive(int64 registration_id,
265 const GURL& origin) {
266 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
267 if (!LazyOpen(false) || is_disabled_)
268 return false;
269
270 ServiceWorkerRegistrationData registration;
271 if (!ReadRegistrationData(registration_id, origin, &registration))
272 return false;
273
274 registration.set_is_active(true);
275
276 leveldb::WriteBatch batch;
277 PutRegistrationDataToBatch(registration, &batch);
278 return WriteBatch(&batch);
279 }
280
281 bool ServiceWorkerDatabase::UpdateLastCheckTime(int64 registration_id,
282 const GURL& origin,
283 const base::Time& time) {
284 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
285 if (!LazyOpen(false) || is_disabled_)
286 return false;
287
288 ServiceWorkerRegistrationData registration;
289 if (!ReadRegistrationData(registration_id, origin, &registration))
290 return false;
291
292 DCHECK_LT(registration.last_update_check_time(), time.ToInternalValue());
293 registration.set_last_update_check_time(time.ToInternalValue());
294
295 leveldb::WriteBatch batch;
296 PutRegistrationDataToBatch(registration, &batch);
297 return WriteBatch(&batch);
298 }
299
300 bool ServiceWorkerDatabase::DeleteRegistration(int64 registration_id,
301 const GURL& origin) {
302 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
303 if (!LazyOpen(false) || is_disabled_)
304 return false;
305
306 leveldb::WriteBatch batch;
307
308 // Remove |origin| from unique origins if a registration specified by
309 // |registration_id| is the only one for |origin|.
310 // TODO(nhiroki): Check the uniqueness by more efficient way.
311 std::vector<ServiceWorkerRegistrationData> registrations;
312 if (!GetRegistrationsForOrigin(origin, &registrations))
313 return false;
314 if (registrations.size() == 1 &&
315 registrations[0].registration_id() == registration_id) {
316 batch.Delete(CreateUniqueOriginKey(origin));
317 }
318
319 batch.Delete(CreateRegistrationKey(registration_id, origin));
320
321 // TODO(nhiroki): Delete ResourceRecords tied with this registration.
322 return WriteBatch(&batch);
323 }
324
98 bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) { 325 bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) {
99 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); 326 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
100 if (IsOpen()) 327 if (IsOpen())
101 return true; 328 return true;
102 329
103 // Do not try to open a database if we tried and failed once. 330 // Do not try to open a database if we tried and failed once.
104 if (is_disabled_) 331 if (is_disabled_)
105 return false; 332 return false;
106 333
107 // When |path_| is empty, open a database in-memory. 334 // When |path_| is empty, open a database in-memory.
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
166 int64 parsed; 393 int64 parsed;
167 if (!base::StringToInt64(value, &parsed)) { 394 if (!base::StringToInt64(value, &parsed)) {
168 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); 395 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse"));
169 return false; 396 return false;
170 } 397 }
171 398
172 *next_avail_id = parsed; 399 *next_avail_id = parsed;
173 return true; 400 return true;
174 } 401 }
175 402
403 bool ServiceWorkerDatabase::ReadRegistrationData(
404 int64 registration_id,
405 const GURL& origin,
406 ServiceWorkerRegistrationData* registration) {
407 DCHECK(registration);
408
409 std::string key = CreateRegistrationKey(registration_id, origin);
410
411 std::string value;
412 leveldb::Status status = db_->Get(leveldb::ReadOptions(), key, &value);
413 if (!status.ok()) {
414 if (!status.IsNotFound())
415 HandleError(FROM_HERE, status);
416 return false;
417 }
418
419 ServiceWorkerRegistrationData parsed;
420 if (!ParseRegistrationData(value, &parsed)) {
421 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse"));
422 return false;
423 }
424
425 *registration = parsed;
426 return true;
427 }
428
176 bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) { 429 bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) {
177 DCHECK(batch); 430 DCHECK(batch);
178 DCHECK(!is_disabled_); 431 DCHECK(!is_disabled_);
179 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch); 432 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch);
180 if (!status.ok()) { 433 if (!status.ok()) {
181 HandleError(FROM_HERE, status); 434 HandleError(FROM_HERE, status);
182 return false; 435 return false;
183 } 436 }
184 return true; 437 return true;
185 } 438 }
186 439
440 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.
441 const char* id_key, int64 used_id, leveldb::WriteBatch* batch) {
442 DCHECK(batch);
443 int64 next_available_id;
444 if (!ReadNextAvailableId(id_key, &next_available_id))
445 return false;
446 if (next_available_id <= used_id)
447 batch->Put(id_key, base::Int64ToString(used_id + 1));
448 return true;
449 }
450
187 bool ServiceWorkerDatabase::IsOpen() { 451 bool ServiceWorkerDatabase::IsOpen() {
188 return db_.get() != NULL; 452 return db_.get() != NULL;
189 } 453 }
190 454
191 bool ServiceWorkerDatabase::IsEmpty() { 455 bool ServiceWorkerDatabase::IsEmpty() {
192 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); 456 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
193 itr->SeekToFirst(); 457 itr->SeekToFirst();
194 // TODO(nhiroki): Handle an error. 458 // TODO(nhiroki): Handle an error.
195 DCHECK(itr->status().ok()); 459 DCHECK(itr->status().ok());
196 return !itr->Valid(); 460 return !itr->Valid();
197 } 461 }
198 462
199 void ServiceWorkerDatabase::HandleError( 463 void ServiceWorkerDatabase::HandleError(
200 const tracked_objects::Location& from_here, 464 const tracked_objects::Location& from_here,
201 const leveldb::Status& status) { 465 const leveldb::Status& status) {
202 // TODO(nhiroki): Add an UMA histogram. 466 // TODO(nhiroki): Add an UMA histogram.
203 DLOG(ERROR) << "Failed at: " << from_here.ToString() 467 DLOG(ERROR) << "Failed at: " << from_here.ToString()
204 << " with error: " << status.ToString(); 468 << " with error: " << status.ToString();
205 is_disabled_ = true; 469 is_disabled_ = true;
206 if (status.IsCorruption()) 470 if (status.IsCorruption())
207 was_corruption_detected_ = true; 471 was_corruption_detected_ = true;
208 db_.reset(); 472 db_.reset();
209 } 473 }
210 474
211 } // namespace content 475 } // namespace content
OLDNEW
« no previous file with comments | « content/browser/service_worker/service_worker_database.h ('k') | content/browser/service_worker/service_worker_database.proto » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698