ServiceWorker: Add UMA for ServiceWorkerDatabase
[chromium-blink-merge.git] / content / browser / service_worker / service_worker_database.cc
blobf4da827e5a42f6e0165cd3339b4fa035edad34bb
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
3 // found in the LICENSE file.
5 #include "content/browser/service_worker/service_worker_database.h"
7 #include <string>
9 #include "base/file_util.h"
10 #include "base/location.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "content/browser/service_worker/service_worker_database.pb.h"
19 #include "content/common/service_worker/service_worker_types.h"
20 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
21 #include "third_party/leveldatabase/src/include/leveldb/db.h"
22 #include "third_party/leveldatabase/src/include/leveldb/env.h"
23 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
25 // LevelDB database schema
26 // =======================
28 // NOTE
29 // - int64 value is serialized as a string by base::Int64ToString().
30 // - GURL value is serialized as a string by GURL::spec().
32 // Version 1 (in sorted order)
33 // key: "INITDATA_DB_VERSION"
34 // value: "1"
36 // key: "INITDATA_NEXT_REGISTRATION_ID"
37 // value: <int64 'next_available_registration_id'>
39 // key: "INITDATA_NEXT_RESOURCE_ID"
40 // value: <int64 'next_available_resource_id'>
42 // key: "INITDATA_NEXT_VERSION_ID"
43 // value: <int64 'next_available_version_id'>
45 // key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin'>
46 // value: <empty>
48 // key: "PRES:" + <int64 'purgeable_resource_id'>
49 // value: <empty>
51 // key: "REG:" + <GURL 'origin'> + '\x00' + <int64 'registration_id'>
52 // (ex. "REG:http://example.com\x00123456")
53 // value: <ServiceWorkerRegistrationData serialized as a string>
55 // key: "RES:" + <int64 'version_id'> + '\x00' + <int64 'resource_id'>
56 // (ex. "RES:123456\x00654321")
57 // value: <ServiceWorkerResourceRecord serialized as a string>
59 // key: "URES:" + <int64 'uncommitted_resource_id'>
60 // value: <empty>
62 namespace content {
64 namespace {
66 const char kDatabaseVersionKey[] = "INITDATA_DB_VERSION";
67 const char kNextRegIdKey[] = "INITDATA_NEXT_REGISTRATION_ID";
68 const char kNextResIdKey[] = "INITDATA_NEXT_RESOURCE_ID";
69 const char kNextVerIdKey[] = "INITDATA_NEXT_VERSION_ID";
70 const char kUniqueOriginKey[] = "INITDATA_UNIQUE_ORIGIN:";
72 const char kRegKeyPrefix[] = "REG:";
73 const char kResKeyPrefix[] = "RES:";
74 const char kKeySeparator = '\x00';
76 const char kUncommittedResIdKeyPrefix[] = "URES:";
77 const char kPurgeableResIdKeyPrefix[] = "PRES:";
79 const int64 kCurrentSchemaVersion = 1;
81 // For histogram.
82 const char kOpenResultHistogramLabel[] =
83 "ServiceWorker.Database.OpenResult";
84 const char kReadResultHistogramLabel[] =
85 "ServiceWorker.Database.ReadResult";
86 const char kWriteResultHistogramLabel[] =
87 "ServiceWorker.Database.WriteResult";
89 bool RemovePrefix(const std::string& str,
90 const std::string& prefix,
91 std::string* out) {
92 if (!StartsWithASCII(str, prefix, true))
93 return false;
94 if (out)
95 *out = str.substr(prefix.size());
96 return true;
99 std::string CreateRegistrationKey(int64 registration_id,
100 const GURL& origin) {
101 return base::StringPrintf("%s%s%c%s",
102 kRegKeyPrefix,
103 origin.spec().c_str(),
104 kKeySeparator,
105 base::Int64ToString(registration_id).c_str());
108 std::string CreateResourceRecordKeyPrefix(int64 version_id) {
109 return base::StringPrintf("%s%s%c",
110 kResKeyPrefix,
111 base::Int64ToString(version_id).c_str(),
112 kKeySeparator);
115 std::string CreateResourceRecordKey(int64 version_id,
116 int64 resource_id) {
117 return CreateResourceRecordKeyPrefix(version_id).append(
118 base::Int64ToString(resource_id));
121 std::string CreateUniqueOriginKey(const GURL& origin) {
122 return base::StringPrintf("%s%s", kUniqueOriginKey, origin.spec().c_str());
125 std::string CreateResourceIdKey(const char* key_prefix, int64 resource_id) {
126 return base::StringPrintf(
127 "%s%s", key_prefix, base::Int64ToString(resource_id).c_str());
130 void PutRegistrationDataToBatch(
131 const ServiceWorkerDatabase::RegistrationData& input,
132 leveldb::WriteBatch* batch) {
133 DCHECK(batch);
135 // Convert RegistrationData to ServiceWorkerRegistrationData.
136 ServiceWorkerRegistrationData data;
137 data.set_registration_id(input.registration_id);
138 data.set_scope_url(input.scope.spec());
139 data.set_script_url(input.script.spec());
140 data.set_version_id(input.version_id);
141 data.set_is_active(input.is_active);
142 data.set_has_fetch_handler(input.has_fetch_handler);
143 data.set_last_update_check_time(input.last_update_check.ToInternalValue());
145 std::string value;
146 bool success = data.SerializeToString(&value);
147 DCHECK(success);
148 GURL origin = input.scope.GetOrigin();
149 batch->Put(CreateRegistrationKey(data.registration_id(), origin), value);
152 void PutResourceRecordToBatch(
153 const ServiceWorkerDatabase::ResourceRecord& input,
154 int64 version_id,
155 leveldb::WriteBatch* batch) {
156 DCHECK(batch);
158 // Convert ResourceRecord to ServiceWorkerResourceRecord.
159 ServiceWorkerResourceRecord record;
160 record.set_resource_id(input.resource_id);
161 record.set_url(input.url.spec());
163 std::string value;
164 bool success = record.SerializeToString(&value);
165 DCHECK(success);
166 batch->Put(CreateResourceRecordKey(version_id, input.resource_id), value);
169 void PutUniqueOriginToBatch(const GURL& origin,
170 leveldb::WriteBatch* batch) {
171 // Value should be empty.
172 batch->Put(CreateUniqueOriginKey(origin), "");
175 void PutPurgeableResourceIdToBatch(int64 resource_id,
176 leveldb::WriteBatch* batch) {
177 // Value should be empty.
178 batch->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix, resource_id), "");
181 ServiceWorkerDatabase::Status ParseId(
182 const std::string& serialized,
183 int64* out) {
184 DCHECK(out);
185 int64 id;
186 if (!base::StringToInt64(serialized, &id) || id < 0)
187 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
188 *out = id;
189 return ServiceWorkerDatabase::STATUS_OK;
192 ServiceWorkerDatabase::Status ParseDatabaseVersion(
193 const std::string& serialized,
194 int64* out) {
195 DCHECK(out);
196 const int kFirstValidVersion = 1;
197 int64 version;
198 if (!base::StringToInt64(serialized, &version) ||
199 version < kFirstValidVersion) {
200 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
202 if (kCurrentSchemaVersion < version) {
203 DLOG(ERROR) << "ServiceWorkerDatabase has newer schema version"
204 << " than the current latest version: "
205 << version << " vs " << kCurrentSchemaVersion;
206 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
208 *out = version;
209 return ServiceWorkerDatabase::STATUS_OK;
212 ServiceWorkerDatabase::Status ParseRegistrationData(
213 const std::string& serialized,
214 ServiceWorkerDatabase::RegistrationData* out) {
215 DCHECK(out);
216 ServiceWorkerRegistrationData data;
217 if (!data.ParseFromString(serialized))
218 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
220 GURL scope_url(data.scope_url());
221 GURL script_url(data.script_url());
222 if (!scope_url.is_valid() ||
223 !script_url.is_valid() ||
224 scope_url.GetOrigin() != script_url.GetOrigin()) {
225 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
228 // Convert ServiceWorkerRegistrationData to RegistrationData.
229 out->registration_id = data.registration_id();
230 out->scope = scope_url;
231 out->script = script_url;
232 out->version_id = data.version_id();
233 out->is_active = data.is_active();
234 out->has_fetch_handler = data.has_fetch_handler();
235 out->last_update_check =
236 base::Time::FromInternalValue(data.last_update_check_time());
237 return ServiceWorkerDatabase::STATUS_OK;
240 ServiceWorkerDatabase::Status ParseResourceRecord(
241 const std::string& serialized,
242 ServiceWorkerDatabase::ResourceRecord* out) {
243 DCHECK(out);
244 ServiceWorkerResourceRecord record;
245 if (!record.ParseFromString(serialized))
246 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
248 GURL url(record.url());
249 if (!url.is_valid())
250 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
252 // Convert ServiceWorkerResourceRecord to ResourceRecord.
253 out->resource_id = record.resource_id();
254 out->url = url;
255 return ServiceWorkerDatabase::STATUS_OK;
258 ServiceWorkerDatabase::Status LevelDBStatusToStatus(
259 const leveldb::Status& status) {
260 if (status.ok())
261 return ServiceWorkerDatabase::STATUS_OK;
262 else if (status.IsNotFound())
263 return ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND;
264 else if (status.IsIOError())
265 return ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR;
266 else if (status.IsCorruption())
267 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
268 else
269 return ServiceWorkerDatabase::STATUS_ERROR_FAILED;
272 const char* StatusToString(ServiceWorkerDatabase::Status status) {
273 switch (status) {
274 case ServiceWorkerDatabase::STATUS_OK:
275 return "Database OK";
276 case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND:
277 return "Database not found";
278 case ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR:
279 return "Database IO error";
280 case ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED:
281 return "Database corrupted";
282 case ServiceWorkerDatabase::STATUS_ERROR_FAILED:
283 return "Database operation failed";
284 case ServiceWorkerDatabase::STATUS_ERROR_MAX:
285 NOTREACHED();
286 return "Database unknown error";
288 NOTREACHED();
289 return "Database unknown error";
292 } // namespace
294 ServiceWorkerDatabase::RegistrationData::RegistrationData()
295 : registration_id(kInvalidServiceWorkerRegistrationId),
296 version_id(kInvalidServiceWorkerVersionId),
297 is_active(false),
298 has_fetch_handler(false) {
301 ServiceWorkerDatabase::RegistrationData::~RegistrationData() {
304 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path)
305 : path_(path),
306 next_avail_registration_id_(0),
307 next_avail_resource_id_(0),
308 next_avail_version_id_(0),
309 state_(UNINITIALIZED) {
310 sequence_checker_.DetachFromSequence();
313 ServiceWorkerDatabase::~ServiceWorkerDatabase() {
314 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
315 db_.reset();
318 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetNextAvailableIds(
319 int64* next_avail_registration_id,
320 int64* next_avail_version_id,
321 int64* next_avail_resource_id) {
322 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
323 DCHECK(next_avail_registration_id);
324 DCHECK(next_avail_version_id);
325 DCHECK(next_avail_resource_id);
327 Status status = LazyOpen(false);
328 if (IsNewOrNonexistentDatabase(status)) {
329 *next_avail_registration_id = 0;
330 *next_avail_version_id = 0;
331 *next_avail_resource_id = 0;
332 return STATUS_OK;
334 if (status != STATUS_OK)
335 return status;
337 status = ReadNextAvailableId(kNextRegIdKey, &next_avail_registration_id_);
338 if (status != STATUS_OK)
339 return status;
340 status = ReadNextAvailableId(kNextVerIdKey, &next_avail_version_id_);
341 if (status != STATUS_OK)
342 return status;
343 status = ReadNextAvailableId(kNextResIdKey, &next_avail_resource_id_);
344 if (status != STATUS_OK)
345 return status;
347 *next_avail_registration_id = next_avail_registration_id_;
348 *next_avail_version_id = next_avail_version_id_;
349 *next_avail_resource_id = next_avail_resource_id_;
350 return STATUS_OK;
353 ServiceWorkerDatabase::Status
354 ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set<GURL>* origins) {
355 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
356 DCHECK(origins->empty());
358 Status status = LazyOpen(false);
359 if (IsNewOrNonexistentDatabase(status))
360 return STATUS_OK;
361 if (status != STATUS_OK)
362 return status;
364 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
365 for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) {
366 status = LevelDBStatusToStatus(itr->status());
367 if (status != STATUS_OK) {
368 HandleReadResult(FROM_HERE, status);
369 origins->clear();
370 return status;
373 std::string origin;
374 if (!RemovePrefix(itr->key().ToString(), kUniqueOriginKey, &origin))
375 break;
376 origins->insert(GURL(origin));
379 HandleReadResult(FROM_HERE, status);
380 return status;
383 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin(
384 const GURL& origin,
385 std::vector<RegistrationData>* registrations) {
386 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
387 DCHECK(registrations->empty());
389 Status status = LazyOpen(false);
390 if (IsNewOrNonexistentDatabase(status))
391 return STATUS_OK;
392 if (status != STATUS_OK)
393 return status;
395 // Create a key prefix for registrations.
396 std::string prefix = base::StringPrintf(
397 "%s%s%c", kRegKeyPrefix, origin.spec().c_str(), kKeySeparator);
399 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
400 for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
401 status = LevelDBStatusToStatus(itr->status());
402 if (status != STATUS_OK) {
403 HandleReadResult(FROM_HERE, status);
404 registrations->clear();
405 return status;
408 if (!RemovePrefix(itr->key().ToString(), prefix, NULL))
409 break;
411 RegistrationData registration;
412 status = ParseRegistrationData(itr->value().ToString(), &registration);
413 if (status != STATUS_OK) {
414 HandleReadResult(FROM_HERE, status);
415 registrations->clear();
416 return status;
418 registrations->push_back(registration);
421 HandleReadResult(FROM_HERE, status);
422 return status;
425 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations(
426 std::vector<RegistrationData>* registrations) {
427 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
428 DCHECK(registrations->empty());
430 Status status = LazyOpen(false);
431 if (IsNewOrNonexistentDatabase(status))
432 return STATUS_OK;
433 if (status != STATUS_OK)
434 return status;
436 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
437 for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) {
438 status = LevelDBStatusToStatus(itr->status());
439 if (status != STATUS_OK) {
440 HandleReadResult(FROM_HERE, status);
441 registrations->clear();
442 return status;
445 if (!RemovePrefix(itr->key().ToString(), kRegKeyPrefix, NULL))
446 break;
448 RegistrationData registration;
449 status = ParseRegistrationData(itr->value().ToString(), &registration);
450 if (status != STATUS_OK) {
451 HandleReadResult(FROM_HERE, status);
452 registrations->clear();
453 return status;
455 registrations->push_back(registration);
458 HandleReadResult(FROM_HERE, status);
459 return status;
462 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistration(
463 int64 registration_id,
464 const GURL& origin,
465 RegistrationData* registration,
466 std::vector<ResourceRecord>* resources) {
467 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
468 DCHECK(registration);
469 DCHECK(resources);
471 Status status = LazyOpen(false);
472 if (IsNewOrNonexistentDatabase(status) || status != STATUS_OK)
473 return status;
475 RegistrationData value;
476 status = ReadRegistrationData(registration_id, origin, &value);
477 if (status != STATUS_OK)
478 return status;
480 status = ReadResourceRecords(value.version_id, resources);
481 if (status != STATUS_OK)
482 return status;
484 *registration = value;
485 return STATUS_OK;
488 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteRegistration(
489 const RegistrationData& registration,
490 const std::vector<ResourceRecord>& resources,
491 std::vector<int64>* newly_purgeable_resources) {
492 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
493 Status status = LazyOpen(true);
494 if (status != STATUS_OK)
495 return status;
497 leveldb::WriteBatch batch;
498 BumpNextRegistrationIdIfNeeded(registration.registration_id, &batch);
499 BumpNextVersionIdIfNeeded(registration.version_id, &batch);
501 PutUniqueOriginToBatch(registration.scope.GetOrigin(), &batch);
502 PutRegistrationDataToBatch(registration, &batch);
504 // Used for avoiding multiple writes for the same resource id or url.
505 std::set<int64> pushed_resources;
506 std::set<GURL> pushed_urls;
507 for (std::vector<ResourceRecord>::const_iterator itr = resources.begin();
508 itr != resources.end(); ++itr) {
509 if (!itr->url.is_valid())
510 return STATUS_ERROR_FAILED;
512 // Duplicated resource id or url should not exist.
513 DCHECK(pushed_resources.insert(itr->resource_id).second);
514 DCHECK(pushed_urls.insert(itr->url).second);
516 PutResourceRecordToBatch(*itr, registration.version_id, &batch);
518 // Delete a resource from the uncommitted list.
519 batch.Delete(CreateResourceIdKey(
520 kUncommittedResIdKeyPrefix, itr->resource_id));
523 // Retrieve a previous version to sweep purgeable resources.
524 RegistrationData old_registration;
525 status = ReadRegistrationData(registration.registration_id,
526 registration.scope.GetOrigin(),
527 &old_registration);
528 if (status != STATUS_OK && status != STATUS_ERROR_NOT_FOUND)
529 return status;
530 if (status == STATUS_OK) {
531 DCHECK_LT(old_registration.version_id, registration.version_id);
532 status = DeleteResourceRecords(
533 old_registration.version_id, newly_purgeable_resources, &batch);
534 if (status != STATUS_OK)
535 return status;
537 // Currently resource sharing across versions and registrations is not
538 // supported, so resource ids should not be overlapped between
539 // |registration| and |old_registration|.
540 std::set<int64> deleted_resources(newly_purgeable_resources->begin(),
541 newly_purgeable_resources->end());
542 DCHECK(base::STLSetIntersection<std::set<int64> >(
543 pushed_resources, deleted_resources).empty());
546 return WriteBatch(&batch);
549 ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateVersionToActive(
550 int64 registration_id,
551 const GURL& origin) {
552 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
553 ServiceWorkerDatabase::Status status = LazyOpen(false);
554 if (IsNewOrNonexistentDatabase(status))
555 return STATUS_ERROR_NOT_FOUND;
556 if (status != STATUS_OK)
557 return status;
558 if (!origin.is_valid())
559 return STATUS_ERROR_FAILED;
561 RegistrationData registration;
562 status = ReadRegistrationData(registration_id, origin, &registration);
563 if (status != STATUS_OK)
564 return status;
566 registration.is_active = true;
568 leveldb::WriteBatch batch;
569 PutRegistrationDataToBatch(registration, &batch);
570 return WriteBatch(&batch);
573 ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateLastCheckTime(
574 int64 registration_id,
575 const GURL& origin,
576 const base::Time& time) {
577 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
578 ServiceWorkerDatabase::Status status = LazyOpen(false);
579 if (IsNewOrNonexistentDatabase(status))
580 return STATUS_ERROR_NOT_FOUND;
581 if (status != STATUS_OK)
582 return status;
583 if (!origin.is_valid())
584 return STATUS_ERROR_FAILED;
586 RegistrationData registration;
587 status = ReadRegistrationData(registration_id, origin, &registration);
588 if (status != STATUS_OK)
589 return status;
591 registration.last_update_check = time;
593 leveldb::WriteBatch batch;
594 PutRegistrationDataToBatch(registration, &batch);
595 return WriteBatch(&batch);
598 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteRegistration(
599 int64 registration_id,
600 const GURL& origin,
601 std::vector<int64>* newly_purgeable_resources) {
602 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
603 Status status = LazyOpen(false);
604 if (IsNewOrNonexistentDatabase(status))
605 return STATUS_OK;
606 if (status != STATUS_OK)
607 return status;
608 if (!origin.is_valid())
609 return STATUS_ERROR_FAILED;
611 leveldb::WriteBatch batch;
613 // Remove |origin| from unique origins if a registration specified by
614 // |registration_id| is the only one for |origin|.
615 // TODO(nhiroki): Check the uniqueness by more efficient way.
616 std::vector<RegistrationData> registrations;
617 status = GetRegistrationsForOrigin(origin, &registrations);
618 if (status != STATUS_OK)
619 return status;
621 if (registrations.size() == 1 &&
622 registrations[0].registration_id == registration_id) {
623 batch.Delete(CreateUniqueOriginKey(origin));
626 // Delete a registration specified by |registration_id|.
627 batch.Delete(CreateRegistrationKey(registration_id, origin));
629 // Delete resource records associated with the registration.
630 for (std::vector<RegistrationData>::const_iterator itr =
631 registrations.begin(); itr != registrations.end(); ++itr) {
632 if (itr->registration_id == registration_id) {
633 status = DeleteResourceRecords(
634 itr->version_id, newly_purgeable_resources, &batch);
635 if (status != STATUS_OK)
636 return status;
637 break;
641 return WriteBatch(&batch);
644 ServiceWorkerDatabase::Status
645 ServiceWorkerDatabase::GetUncommittedResourceIds(std::set<int64>* ids) {
646 return ReadResourceIds(kUncommittedResIdKeyPrefix, ids);
649 ServiceWorkerDatabase::Status
650 ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set<int64>& ids) {
651 return WriteResourceIds(kUncommittedResIdKeyPrefix, ids);
654 ServiceWorkerDatabase::Status
655 ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set<int64>& ids) {
656 return DeleteResourceIds(kUncommittedResIdKeyPrefix, ids);
659 ServiceWorkerDatabase::Status
660 ServiceWorkerDatabase::GetPurgeableResourceIds(std::set<int64>* ids) {
661 return ReadResourceIds(kPurgeableResIdKeyPrefix, ids);
664 ServiceWorkerDatabase::Status
665 ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set<int64>& ids) {
666 return WriteResourceIds(kPurgeableResIdKeyPrefix, ids);
669 ServiceWorkerDatabase::Status
670 ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set<int64>& ids) {
671 return DeleteResourceIds(kPurgeableResIdKeyPrefix, ids);
674 ServiceWorkerDatabase::Status
675 ServiceWorkerDatabase::PurgeUncommittedResourceIds(
676 const std::set<int64>& ids) {
677 leveldb::WriteBatch batch;
678 Status status = DeleteResourceIdsInBatch(
679 kUncommittedResIdKeyPrefix, ids, &batch);
680 if (status != STATUS_OK)
681 return status;
682 status = WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix, ids, &batch);
683 if (status != STATUS_OK)
684 return status;
685 return WriteBatch(&batch);
688 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteAllDataForOrigin(
689 const GURL& origin,
690 std::vector<int64>* newly_purgeable_resources) {
691 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
692 Status status = LazyOpen(false);
693 if (IsNewOrNonexistentDatabase(status))
694 return STATUS_OK;
695 if (status != STATUS_OK)
696 return status;
697 if (!origin.is_valid())
698 return STATUS_ERROR_FAILED;
700 leveldb::WriteBatch batch;
702 // Delete from the unique origin list.
703 batch.Delete(CreateUniqueOriginKey(origin));
705 std::vector<RegistrationData> registrations;
706 status = GetRegistrationsForOrigin(origin, &registrations);
707 if (status != STATUS_OK)
708 return status;
710 // Delete registrations and resource records.
711 for (std::vector<RegistrationData>::const_iterator itr =
712 registrations.begin(); itr != registrations.end(); ++itr) {
713 batch.Delete(CreateRegistrationKey(itr->registration_id, origin));
714 status = DeleteResourceRecords(
715 itr->version_id, newly_purgeable_resources, &batch);
716 if (status != STATUS_OK)
717 return status;
720 return WriteBatch(&batch);
723 ServiceWorkerDatabase::Status ServiceWorkerDatabase::LazyOpen(
724 bool create_if_missing) {
725 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
727 // Do not try to open a database if we tried and failed once.
728 if (state_ == DISABLED)
729 return STATUS_ERROR_FAILED;
730 if (IsOpen())
731 return STATUS_OK;
733 // When |path_| is empty, open a database in-memory.
734 bool use_in_memory_db = path_.empty();
736 if (!create_if_missing) {
737 // Avoid opening a database if it does not exist at the |path_|.
738 if (use_in_memory_db ||
739 !base::PathExists(path_) ||
740 base::IsDirectoryEmpty(path_)) {
741 return STATUS_ERROR_NOT_FOUND;
745 leveldb::Options options;
746 options.create_if_missing = create_if_missing;
747 if (use_in_memory_db) {
748 env_.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
749 options.env = env_.get();
752 leveldb::DB* db = NULL;
753 Status status = LevelDBStatusToStatus(
754 leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db));
755 HandleOpenResult(FROM_HERE, status);
756 if (status != STATUS_OK) {
757 DCHECK(!db);
758 // TODO(nhiroki): Should we retry to open the database?
759 return status;
761 db_.reset(db);
763 int64 db_version;
764 status = ReadDatabaseVersion(&db_version);
765 if (status != STATUS_OK)
766 return status;
767 DCHECK_LE(0, db_version);
768 if (db_version > 0)
769 state_ = INITIALIZED;
770 return STATUS_OK;
773 bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase(
774 ServiceWorkerDatabase::Status status) {
775 if (status == STATUS_ERROR_NOT_FOUND)
776 return true;
777 if (status == STATUS_OK && state_ == UNINITIALIZED)
778 return true;
779 return false;
782 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadNextAvailableId(
783 const char* id_key,
784 int64* next_avail_id) {
785 DCHECK(id_key);
786 DCHECK(next_avail_id);
788 std::string value;
789 Status status = LevelDBStatusToStatus(
790 db_->Get(leveldb::ReadOptions(), id_key, &value));
791 if (status == STATUS_ERROR_NOT_FOUND) {
792 // Nobody has gotten the next resource id for |id_key|.
793 *next_avail_id = 0;
794 HandleReadResult(FROM_HERE, STATUS_OK);
795 return STATUS_OK;
796 } else if (status != STATUS_OK) {
797 HandleReadResult(FROM_HERE, status);
798 return status;
801 status = ParseId(value, next_avail_id);
802 HandleReadResult(FROM_HERE, status);
803 return status;
806 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData(
807 int64 registration_id,
808 const GURL& origin,
809 RegistrationData* registration) {
810 DCHECK(registration);
812 const std::string key = CreateRegistrationKey(registration_id, origin);
813 std::string value;
814 Status status = LevelDBStatusToStatus(
815 db_->Get(leveldb::ReadOptions(), key, &value));
816 if (status != STATUS_OK) {
817 HandleReadResult(
818 FROM_HERE,
819 status == STATUS_ERROR_NOT_FOUND ? STATUS_OK : status);
820 return status;
823 status = ParseRegistrationData(value, registration);
824 HandleReadResult(FROM_HERE, status);
825 return status;
828 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceRecords(
829 int64 version_id,
830 std::vector<ResourceRecord>* resources) {
831 DCHECK(resources->empty());
833 Status status = STATUS_OK;
834 const std::string prefix = CreateResourceRecordKeyPrefix(version_id);
836 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
837 for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
838 Status status = LevelDBStatusToStatus(itr->status());
839 if (status != STATUS_OK) {
840 HandleReadResult(FROM_HERE, status);
841 resources->clear();
842 return status;
845 if (!RemovePrefix(itr->key().ToString(), prefix, NULL))
846 break;
848 ResourceRecord resource;
849 status = ParseResourceRecord(itr->value().ToString(), &resource);
850 if (status != STATUS_OK) {
851 HandleReadResult(FROM_HERE, status);
852 resources->clear();
853 return status;
855 resources->push_back(resource);
858 HandleReadResult(FROM_HERE, status);
859 return status;
862 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords(
863 int64 version_id,
864 std::vector<int64>* newly_purgeable_resources,
865 leveldb::WriteBatch* batch) {
866 DCHECK(batch);
868 Status status = STATUS_OK;
869 const std::string prefix = CreateResourceRecordKeyPrefix(version_id);
871 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
872 for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
873 status = LevelDBStatusToStatus(itr->status());
874 if (status != STATUS_OK) {
875 HandleReadResult(FROM_HERE, status);
876 return status;
879 const std::string key = itr->key().ToString();
880 std::string unprefixed;
881 if (!RemovePrefix(key, prefix, &unprefixed))
882 break;
884 int64 resource_id;
885 status = ParseId(unprefixed, &resource_id);
886 if (status != STATUS_OK) {
887 HandleReadResult(FROM_HERE, status);
888 return status;
891 // Remove a resource record.
892 batch->Delete(key);
894 // Currently resource sharing across versions and registrations is not
895 // supported, so we can purge this without caring about it.
896 PutPurgeableResourceIdToBatch(resource_id, batch);
897 newly_purgeable_resources->push_back(resource_id);
900 HandleReadResult(FROM_HERE, status);
901 return status;
904 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceIds(
905 const char* id_key_prefix,
906 std::set<int64>* ids) {
907 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
908 DCHECK(id_key_prefix);
909 DCHECK(ids->empty());
911 Status status = LazyOpen(false);
912 if (IsNewOrNonexistentDatabase(status))
913 return STATUS_OK;
914 if (status != STATUS_OK)
915 return status;
917 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
918 for (itr->Seek(id_key_prefix); itr->Valid(); itr->Next()) {
919 status = LevelDBStatusToStatus(itr->status());
920 if (status != STATUS_OK) {
921 HandleReadResult(FROM_HERE, status);
922 ids->clear();
923 return status;
926 std::string unprefixed;
927 if (!RemovePrefix(itr->key().ToString(), id_key_prefix, &unprefixed))
928 break;
930 int64 resource_id;
931 status = ParseId(unprefixed, &resource_id);
932 if (status != STATUS_OK) {
933 HandleReadResult(FROM_HERE, status);
934 ids->clear();
935 return status;
937 ids->insert(resource_id);
940 HandleReadResult(FROM_HERE, status);
941 return status;
944 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIds(
945 const char* id_key_prefix,
946 const std::set<int64>& ids) {
947 leveldb::WriteBatch batch;
948 Status status = WriteResourceIdsInBatch(id_key_prefix, ids, &batch);
949 if (status != STATUS_OK)
950 return status;
951 return WriteBatch(&batch);
954 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIdsInBatch(
955 const char* id_key_prefix,
956 const std::set<int64>& ids,
957 leveldb::WriteBatch* batch) {
958 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
959 DCHECK(id_key_prefix);
961 Status status = LazyOpen(true);
962 if (status != STATUS_OK)
963 return status;
965 for (std::set<int64>::const_iterator itr = ids.begin();
966 itr != ids.end(); ++itr) {
967 // Value should be empty.
968 batch->Put(CreateResourceIdKey(id_key_prefix, *itr), "");
970 return STATUS_OK;
973 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIds(
974 const char* id_key_prefix,
975 const std::set<int64>& ids) {
976 leveldb::WriteBatch batch;
977 Status status = DeleteResourceIdsInBatch(id_key_prefix, ids, &batch);
978 if (status != STATUS_OK)
979 return status;
980 return WriteBatch(&batch);
983 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIdsInBatch(
984 const char* id_key_prefix,
985 const std::set<int64>& ids,
986 leveldb::WriteBatch* batch) {
987 DCHECK(sequence_checker_.CalledOnValidSequencedThread());
988 DCHECK(id_key_prefix);
990 Status status = LazyOpen(false);
991 if (IsNewOrNonexistentDatabase(status))
992 return STATUS_OK;
993 if (status != STATUS_OK)
994 return status;
996 for (std::set<int64>::const_iterator itr = ids.begin();
997 itr != ids.end(); ++itr) {
998 batch->Delete(CreateResourceIdKey(id_key_prefix, *itr));
1000 return STATUS_OK;
1003 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadDatabaseVersion(
1004 int64* db_version) {
1005 std::string value;
1006 Status status = LevelDBStatusToStatus(
1007 db_->Get(leveldb::ReadOptions(), kDatabaseVersionKey, &value));
1008 if (status == STATUS_ERROR_NOT_FOUND) {
1009 // The database hasn't been initialized yet.
1010 *db_version = 0;
1011 HandleReadResult(FROM_HERE, STATUS_OK);
1012 return STATUS_OK;
1015 if (status != STATUS_OK) {
1016 HandleReadResult(FROM_HERE, status);
1017 return status;
1020 status = ParseDatabaseVersion(value, db_version);
1021 HandleReadResult(FROM_HERE, status);
1022 return status;
1025 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteBatch(
1026 leveldb::WriteBatch* batch) {
1027 DCHECK(batch);
1028 DCHECK_NE(DISABLED, state_);
1030 if (state_ == UNINITIALIZED) {
1031 // Write the database schema version.
1032 batch->Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion));
1033 state_ = INITIALIZED;
1036 Status status = LevelDBStatusToStatus(
1037 db_->Write(leveldb::WriteOptions(), batch));
1038 HandleWriteResult(FROM_HERE, status);
1039 return status;
1042 void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded(
1043 int64 used_id, leveldb::WriteBatch* batch) {
1044 DCHECK(batch);
1045 if (next_avail_registration_id_ <= used_id) {
1046 next_avail_registration_id_ = used_id + 1;
1047 batch->Put(kNextRegIdKey, base::Int64ToString(next_avail_registration_id_));
1051 void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded(
1052 int64 used_id, leveldb::WriteBatch* batch) {
1053 DCHECK(batch);
1054 if (next_avail_version_id_ <= used_id) {
1055 next_avail_version_id_ = used_id + 1;
1056 batch->Put(kNextVerIdKey, base::Int64ToString(next_avail_version_id_));
1060 bool ServiceWorkerDatabase::IsOpen() {
1061 return db_ != NULL;
1064 void ServiceWorkerDatabase::Disable(
1065 const tracked_objects::Location& from_here,
1066 Status status) {
1067 DLOG(ERROR) << "Failed at: " << from_here.ToString()
1068 << " with error: " << StatusToString(status);
1069 DLOG(ERROR) << "ServiceWorkerDatabase is disabled.";
1070 state_ = DISABLED;
1071 db_.reset();
1074 void ServiceWorkerDatabase::HandleOpenResult(
1075 const tracked_objects::Location& from_here,
1076 Status status) {
1077 if (status != ServiceWorkerDatabase::STATUS_OK)
1078 Disable(from_here, status);
1079 UMA_HISTOGRAM_ENUMERATION(kOpenResultHistogramLabel,
1080 status,
1081 ServiceWorkerDatabase::STATUS_ERROR_MAX);
1084 void ServiceWorkerDatabase::HandleReadResult(
1085 const tracked_objects::Location& from_here,
1086 Status status) {
1087 if (status != ServiceWorkerDatabase::STATUS_OK)
1088 Disable(from_here, status);
1089 UMA_HISTOGRAM_ENUMERATION(kReadResultHistogramLabel,
1090 status,
1091 ServiceWorkerDatabase::STATUS_ERROR_MAX);
1094 void ServiceWorkerDatabase::HandleWriteResult(
1095 const tracked_objects::Location& from_here,
1096 Status status) {
1097 if (status != ServiceWorkerDatabase::STATUS_OK)
1098 Disable(from_here, status);
1099 UMA_HISTOGRAM_ENUMERATION(kWriteResultHistogramLabel,
1100 status,
1101 ServiceWorkerDatabase::STATUS_ERROR_MAX);
1104 } // namespace content