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"
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/browser/service_worker/service_worker_metrics.h"
20 #include "content/common/service_worker/service_worker_types.h"
21 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
22 #include "third_party/leveldatabase/src/include/leveldb/db.h"
23 #include "third_party/leveldatabase/src/include/leveldb/env.h"
24 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
26 // LevelDB database schema
27 // =======================
30 // - int64 value is serialized as a string by base::Int64ToString().
31 // - GURL value is serialized as a string by GURL::spec().
33 // Version 1 (in sorted order)
34 // key: "INITDATA_DB_VERSION"
37 // key: "INITDATA_NEXT_REGISTRATION_ID"
38 // value: <int64 'next_available_registration_id'>
40 // key: "INITDATA_NEXT_RESOURCE_ID"
41 // value: <int64 'next_available_resource_id'>
43 // key: "INITDATA_NEXT_VERSION_ID"
44 // value: <int64 'next_available_version_id'>
46 // key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin'>
49 // key: "PRES:" + <int64 'purgeable_resource_id'>
52 // key: "REG:" + <GURL 'origin'> + '\x00' + <int64 'registration_id'>
53 // (ex. "REG:http://example.com\x00123456")
54 // value: <ServiceWorkerRegistrationData serialized as a string>
56 // key: "RES:" + <int64 'version_id'> + '\x00' + <int64 'resource_id'>
57 // (ex. "RES:123456\x00654321")
58 // value: <ServiceWorkerResourceRecord serialized as a string>
60 // key: "URES:" + <int64 'uncommitted_resource_id'>
67 const char kDatabaseVersionKey
[] = "INITDATA_DB_VERSION";
68 const char kNextRegIdKey
[] = "INITDATA_NEXT_REGISTRATION_ID";
69 const char kNextResIdKey
[] = "INITDATA_NEXT_RESOURCE_ID";
70 const char kNextVerIdKey
[] = "INITDATA_NEXT_VERSION_ID";
71 const char kUniqueOriginKey
[] = "INITDATA_UNIQUE_ORIGIN:";
73 const char kRegKeyPrefix
[] = "REG:";
74 const char kResKeyPrefix
[] = "RES:";
75 const char kKeySeparator
= '\x00';
77 const char kUncommittedResIdKeyPrefix
[] = "URES:";
78 const char kPurgeableResIdKeyPrefix
[] = "PRES:";
80 const int64 kCurrentSchemaVersion
= 1;
82 bool RemovePrefix(const std::string
& str
,
83 const std::string
& prefix
,
85 if (!StartsWithASCII(str
, prefix
, true))
88 *out
= str
.substr(prefix
.size());
92 std::string
CreateRegistrationKey(int64 registration_id
,
94 return base::StringPrintf("%s%s%c%s",
96 origin
.spec().c_str(),
98 base::Int64ToString(registration_id
).c_str());
101 std::string
CreateResourceRecordKeyPrefix(int64 version_id
) {
102 return base::StringPrintf("%s%s%c",
104 base::Int64ToString(version_id
).c_str(),
108 std::string
CreateResourceRecordKey(int64 version_id
,
110 return CreateResourceRecordKeyPrefix(version_id
).append(
111 base::Int64ToString(resource_id
));
114 std::string
CreateUniqueOriginKey(const GURL
& origin
) {
115 return base::StringPrintf("%s%s", kUniqueOriginKey
, origin
.spec().c_str());
118 std::string
CreateResourceIdKey(const char* key_prefix
, int64 resource_id
) {
119 return base::StringPrintf(
120 "%s%s", key_prefix
, base::Int64ToString(resource_id
).c_str());
123 void PutRegistrationDataToBatch(
124 const ServiceWorkerDatabase::RegistrationData
& input
,
125 leveldb::WriteBatch
* batch
) {
128 // Convert RegistrationData to ServiceWorkerRegistrationData.
129 ServiceWorkerRegistrationData data
;
130 data
.set_registration_id(input
.registration_id
);
131 data
.set_scope_url(input
.scope
.spec());
132 data
.set_script_url(input
.script
.spec());
133 data
.set_version_id(input
.version_id
);
134 data
.set_is_active(input
.is_active
);
135 data
.set_has_fetch_handler(input
.has_fetch_handler
);
136 data
.set_last_update_check_time(input
.last_update_check
.ToInternalValue());
139 bool success
= data
.SerializeToString(&value
);
141 GURL origin
= input
.scope
.GetOrigin();
142 batch
->Put(CreateRegistrationKey(data
.registration_id(), origin
), value
);
145 void PutResourceRecordToBatch(
146 const ServiceWorkerDatabase::ResourceRecord
& input
,
148 leveldb::WriteBatch
* batch
) {
151 // Convert ResourceRecord to ServiceWorkerResourceRecord.
152 ServiceWorkerResourceRecord record
;
153 record
.set_resource_id(input
.resource_id
);
154 record
.set_url(input
.url
.spec());
157 bool success
= record
.SerializeToString(&value
);
159 batch
->Put(CreateResourceRecordKey(version_id
, input
.resource_id
), value
);
162 void PutUniqueOriginToBatch(const GURL
& origin
,
163 leveldb::WriteBatch
* batch
) {
164 // Value should be empty.
165 batch
->Put(CreateUniqueOriginKey(origin
), "");
168 void PutPurgeableResourceIdToBatch(int64 resource_id
,
169 leveldb::WriteBatch
* batch
) {
170 // Value should be empty.
171 batch
->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix
, resource_id
), "");
174 ServiceWorkerDatabase::Status
ParseId(
175 const std::string
& serialized
,
179 if (!base::StringToInt64(serialized
, &id
) || id
< 0)
180 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
182 return ServiceWorkerDatabase::STATUS_OK
;
185 ServiceWorkerDatabase::Status
ParseDatabaseVersion(
186 const std::string
& serialized
,
189 const int kFirstValidVersion
= 1;
191 if (!base::StringToInt64(serialized
, &version
) ||
192 version
< kFirstValidVersion
) {
193 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
195 if (kCurrentSchemaVersion
< version
) {
196 DLOG(ERROR
) << "ServiceWorkerDatabase has newer schema version"
197 << " than the current latest version: "
198 << version
<< " vs " << kCurrentSchemaVersion
;
199 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
202 return ServiceWorkerDatabase::STATUS_OK
;
205 ServiceWorkerDatabase::Status
ParseRegistrationData(
206 const std::string
& serialized
,
207 ServiceWorkerDatabase::RegistrationData
* out
) {
209 ServiceWorkerRegistrationData data
;
210 if (!data
.ParseFromString(serialized
))
211 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
213 GURL
scope_url(data
.scope_url());
214 GURL
script_url(data
.script_url());
215 if (!scope_url
.is_valid() ||
216 !script_url
.is_valid() ||
217 scope_url
.GetOrigin() != script_url
.GetOrigin()) {
218 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
221 // Convert ServiceWorkerRegistrationData to RegistrationData.
222 out
->registration_id
= data
.registration_id();
223 out
->scope
= scope_url
;
224 out
->script
= script_url
;
225 out
->version_id
= data
.version_id();
226 out
->is_active
= data
.is_active();
227 out
->has_fetch_handler
= data
.has_fetch_handler();
228 out
->last_update_check
=
229 base::Time::FromInternalValue(data
.last_update_check_time());
230 return ServiceWorkerDatabase::STATUS_OK
;
233 ServiceWorkerDatabase::Status
ParseResourceRecord(
234 const std::string
& serialized
,
235 ServiceWorkerDatabase::ResourceRecord
* out
) {
237 ServiceWorkerResourceRecord record
;
238 if (!record
.ParseFromString(serialized
))
239 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
241 GURL
url(record
.url());
243 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
245 // Convert ServiceWorkerResourceRecord to ResourceRecord.
246 out
->resource_id
= record
.resource_id();
248 return ServiceWorkerDatabase::STATUS_OK
;
251 ServiceWorkerDatabase::Status
LevelDBStatusToStatus(
252 const leveldb::Status
& status
) {
254 return ServiceWorkerDatabase::STATUS_OK
;
255 else if (status
.IsNotFound())
256 return ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND
;
257 else if (status
.IsIOError())
258 return ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR
;
259 else if (status
.IsCorruption())
260 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
262 return ServiceWorkerDatabase::STATUS_ERROR_FAILED
;
265 const char* StatusToString(ServiceWorkerDatabase::Status status
) {
267 case ServiceWorkerDatabase::STATUS_OK
:
268 return "Database OK";
269 case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND
:
270 return "Database not found";
271 case ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR
:
272 return "Database IO error";
273 case ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
:
274 return "Database corrupted";
275 case ServiceWorkerDatabase::STATUS_ERROR_FAILED
:
276 return "Database operation failed";
277 case ServiceWorkerDatabase::STATUS_ERROR_MAX
:
279 return "Database unknown error";
282 return "Database unknown error";
287 ServiceWorkerDatabase::RegistrationData::RegistrationData()
288 : registration_id(kInvalidServiceWorkerRegistrationId
),
289 version_id(kInvalidServiceWorkerVersionId
),
291 has_fetch_handler(false) {
294 ServiceWorkerDatabase::RegistrationData::~RegistrationData() {
297 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath
& path
)
299 next_avail_registration_id_(0),
300 next_avail_resource_id_(0),
301 next_avail_version_id_(0),
302 state_(UNINITIALIZED
) {
303 sequence_checker_
.DetachFromSequence();
306 ServiceWorkerDatabase::~ServiceWorkerDatabase() {
307 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
311 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetNextAvailableIds(
312 int64
* next_avail_registration_id
,
313 int64
* next_avail_version_id
,
314 int64
* next_avail_resource_id
) {
315 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
316 DCHECK(next_avail_registration_id
);
317 DCHECK(next_avail_version_id
);
318 DCHECK(next_avail_resource_id
);
320 Status status
= LazyOpen(false);
321 if (IsNewOrNonexistentDatabase(status
)) {
322 *next_avail_registration_id
= 0;
323 *next_avail_version_id
= 0;
324 *next_avail_resource_id
= 0;
327 if (status
!= STATUS_OK
)
330 status
= ReadNextAvailableId(kNextRegIdKey
, &next_avail_registration_id_
);
331 if (status
!= STATUS_OK
)
333 status
= ReadNextAvailableId(kNextVerIdKey
, &next_avail_version_id_
);
334 if (status
!= STATUS_OK
)
336 status
= ReadNextAvailableId(kNextResIdKey
, &next_avail_resource_id_
);
337 if (status
!= STATUS_OK
)
340 *next_avail_registration_id
= next_avail_registration_id_
;
341 *next_avail_version_id
= next_avail_version_id_
;
342 *next_avail_resource_id
= next_avail_resource_id_
;
346 ServiceWorkerDatabase::Status
347 ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set
<GURL
>* origins
) {
348 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
349 DCHECK(origins
->empty());
351 Status status
= LazyOpen(false);
352 if (IsNewOrNonexistentDatabase(status
))
354 if (status
!= STATUS_OK
)
357 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
358 for (itr
->Seek(kUniqueOriginKey
); itr
->Valid(); itr
->Next()) {
359 status
= LevelDBStatusToStatus(itr
->status());
360 if (status
!= STATUS_OK
) {
361 HandleReadResult(FROM_HERE
, status
);
367 if (!RemovePrefix(itr
->key().ToString(), kUniqueOriginKey
, &origin
))
369 origins
->insert(GURL(origin
));
372 HandleReadResult(FROM_HERE
, status
);
376 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetRegistrationsForOrigin(
378 std::vector
<RegistrationData
>* registrations
) {
379 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
380 DCHECK(registrations
->empty());
382 Status status
= LazyOpen(false);
383 if (IsNewOrNonexistentDatabase(status
))
385 if (status
!= STATUS_OK
)
388 // Create a key prefix for registrations.
389 std::string prefix
= base::StringPrintf(
390 "%s%s%c", kRegKeyPrefix
, origin
.spec().c_str(), kKeySeparator
);
392 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
393 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
394 status
= LevelDBStatusToStatus(itr
->status());
395 if (status
!= STATUS_OK
) {
396 HandleReadResult(FROM_HERE
, status
);
397 registrations
->clear();
401 if (!RemovePrefix(itr
->key().ToString(), prefix
, NULL
))
404 RegistrationData registration
;
405 status
= ParseRegistrationData(itr
->value().ToString(), ®istration
);
406 if (status
!= STATUS_OK
) {
407 HandleReadResult(FROM_HERE
, status
);
408 registrations
->clear();
411 registrations
->push_back(registration
);
414 HandleReadResult(FROM_HERE
, status
);
418 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetAllRegistrations(
419 std::vector
<RegistrationData
>* registrations
) {
420 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
421 DCHECK(registrations
->empty());
423 Status status
= LazyOpen(false);
424 if (IsNewOrNonexistentDatabase(status
))
426 if (status
!= STATUS_OK
)
429 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
430 for (itr
->Seek(kRegKeyPrefix
); itr
->Valid(); itr
->Next()) {
431 status
= LevelDBStatusToStatus(itr
->status());
432 if (status
!= STATUS_OK
) {
433 HandleReadResult(FROM_HERE
, status
);
434 registrations
->clear();
438 if (!RemovePrefix(itr
->key().ToString(), kRegKeyPrefix
, NULL
))
441 RegistrationData registration
;
442 status
= ParseRegistrationData(itr
->value().ToString(), ®istration
);
443 if (status
!= STATUS_OK
) {
444 HandleReadResult(FROM_HERE
, status
);
445 registrations
->clear();
448 registrations
->push_back(registration
);
451 HandleReadResult(FROM_HERE
, status
);
455 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadRegistration(
456 int64 registration_id
,
458 RegistrationData
* registration
,
459 std::vector
<ResourceRecord
>* resources
) {
460 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
461 DCHECK(registration
);
464 Status status
= LazyOpen(false);
465 if (IsNewOrNonexistentDatabase(status
) || status
!= STATUS_OK
)
468 RegistrationData value
;
469 status
= ReadRegistrationData(registration_id
, origin
, &value
);
470 if (status
!= STATUS_OK
)
473 status
= ReadResourceRecords(value
.version_id
, resources
);
474 if (status
!= STATUS_OK
)
477 *registration
= value
;
481 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteRegistration(
482 const RegistrationData
& registration
,
483 const std::vector
<ResourceRecord
>& resources
,
484 int64
* deleted_version_id
,
485 std::vector
<int64
>* newly_purgeable_resources
) {
486 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
487 *deleted_version_id
= kInvalidServiceWorkerVersionId
;
488 Status status
= LazyOpen(true);
489 if (status
!= STATUS_OK
)
492 leveldb::WriteBatch batch
;
493 BumpNextRegistrationIdIfNeeded(registration
.registration_id
, &batch
);
494 BumpNextVersionIdIfNeeded(registration
.version_id
, &batch
);
496 PutUniqueOriginToBatch(registration
.scope
.GetOrigin(), &batch
);
497 PutRegistrationDataToBatch(registration
, &batch
);
499 // Used for avoiding multiple writes for the same resource id or url.
500 std::set
<int64
> pushed_resources
;
501 std::set
<GURL
> pushed_urls
;
502 for (std::vector
<ResourceRecord
>::const_iterator itr
= resources
.begin();
503 itr
!= resources
.end(); ++itr
) {
504 if (!itr
->url
.is_valid())
505 return STATUS_ERROR_FAILED
;
507 // Duplicated resource id or url should not exist.
508 DCHECK(pushed_resources
.insert(itr
->resource_id
).second
);
509 DCHECK(pushed_urls
.insert(itr
->url
).second
);
511 PutResourceRecordToBatch(*itr
, registration
.version_id
, &batch
);
513 // Delete a resource from the uncommitted list.
514 batch
.Delete(CreateResourceIdKey(
515 kUncommittedResIdKeyPrefix
, itr
->resource_id
));
516 // Delete from the purgeable list in case this version was once deleted.
518 CreateResourceIdKey(kPurgeableResIdKeyPrefix
, itr
->resource_id
));
521 // Retrieve a previous version to sweep purgeable resources.
522 RegistrationData old_registration
;
523 status
= ReadRegistrationData(registration
.registration_id
,
524 registration
.scope
.GetOrigin(),
526 if (status
!= STATUS_OK
&& status
!= STATUS_ERROR_NOT_FOUND
)
528 if (status
== STATUS_OK
) {
529 DCHECK_LT(old_registration
.version_id
, registration
.version_id
);
530 *deleted_version_id
= old_registration
.version_id
;
531 status
= DeleteResourceRecords(
532 old_registration
.version_id
, newly_purgeable_resources
, &batch
);
533 if (status
!= STATUS_OK
)
536 // Currently resource sharing across versions and registrations is not
537 // supported, so resource ids should not be overlapped between
538 // |registration| and |old_registration|.
539 std::set
<int64
> deleted_resources(newly_purgeable_resources
->begin(),
540 newly_purgeable_resources
->end());
541 DCHECK(base::STLSetIntersection
<std::set
<int64
> >(
542 pushed_resources
, deleted_resources
).empty());
545 return WriteBatch(&batch
);
548 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::UpdateVersionToActive(
549 int64 registration_id
,
550 const GURL
& origin
) {
551 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
552 Status status
= LazyOpen(false);
553 if (IsNewOrNonexistentDatabase(status
))
554 return STATUS_ERROR_NOT_FOUND
;
555 if (status
!= STATUS_OK
)
557 if (!origin
.is_valid())
558 return STATUS_ERROR_FAILED
;
560 RegistrationData registration
;
561 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
562 if (status
!= STATUS_OK
)
565 registration
.is_active
= true;
567 leveldb::WriteBatch batch
;
568 PutRegistrationDataToBatch(registration
, &batch
);
569 return WriteBatch(&batch
);
572 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::UpdateLastCheckTime(
573 int64 registration_id
,
575 const base::Time
& time
) {
576 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
577 Status status
= LazyOpen(false);
578 if (IsNewOrNonexistentDatabase(status
))
579 return STATUS_ERROR_NOT_FOUND
;
580 if (status
!= STATUS_OK
)
582 if (!origin
.is_valid())
583 return STATUS_ERROR_FAILED
;
585 RegistrationData registration
;
586 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
587 if (status
!= STATUS_OK
)
590 registration
.last_update_check
= time
;
592 leveldb::WriteBatch batch
;
593 PutRegistrationDataToBatch(registration
, &batch
);
594 return WriteBatch(&batch
);
597 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteRegistration(
598 int64 registration_id
,
601 std::vector
<int64
>* newly_purgeable_resources
) {
602 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
603 *version_id
= kInvalidServiceWorkerVersionId
;
604 Status status
= LazyOpen(false);
605 if (IsNewOrNonexistentDatabase(status
))
607 if (status
!= STATUS_OK
)
609 if (!origin
.is_valid())
610 return STATUS_ERROR_FAILED
;
612 leveldb::WriteBatch batch
;
614 // Remove |origin| from unique origins if a registration specified by
615 // |registration_id| is the only one for |origin|.
616 // TODO(nhiroki): Check the uniqueness by more efficient way.
617 std::vector
<RegistrationData
> registrations
;
618 status
= GetRegistrationsForOrigin(origin
, ®istrations
);
619 if (status
!= STATUS_OK
)
622 if (registrations
.size() == 1 &&
623 registrations
[0].registration_id
== registration_id
) {
624 batch
.Delete(CreateUniqueOriginKey(origin
));
627 // Delete a registration specified by |registration_id|.
628 batch
.Delete(CreateRegistrationKey(registration_id
, origin
));
630 // Delete resource records associated with the registration.
631 for (std::vector
<RegistrationData
>::const_iterator itr
=
632 registrations
.begin(); itr
!= registrations
.end(); ++itr
) {
633 if (itr
->registration_id
== registration_id
) {
634 *version_id
= itr
->version_id
;
635 status
= DeleteResourceRecords(
636 itr
->version_id
, newly_purgeable_resources
, &batch
);
637 if (status
!= STATUS_OK
)
643 return WriteBatch(&batch
);
646 ServiceWorkerDatabase::Status
647 ServiceWorkerDatabase::GetUncommittedResourceIds(std::set
<int64
>* ids
) {
648 return ReadResourceIds(kUncommittedResIdKeyPrefix
, ids
);
651 ServiceWorkerDatabase::Status
652 ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set
<int64
>& ids
) {
653 return WriteResourceIds(kUncommittedResIdKeyPrefix
, ids
);
656 ServiceWorkerDatabase::Status
657 ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set
<int64
>& ids
) {
658 return DeleteResourceIds(kUncommittedResIdKeyPrefix
, ids
);
661 ServiceWorkerDatabase::Status
662 ServiceWorkerDatabase::GetPurgeableResourceIds(std::set
<int64
>* ids
) {
663 return ReadResourceIds(kPurgeableResIdKeyPrefix
, ids
);
666 ServiceWorkerDatabase::Status
667 ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set
<int64
>& ids
) {
668 return WriteResourceIds(kPurgeableResIdKeyPrefix
, ids
);
671 ServiceWorkerDatabase::Status
672 ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set
<int64
>& ids
) {
673 return DeleteResourceIds(kPurgeableResIdKeyPrefix
, ids
);
676 ServiceWorkerDatabase::Status
677 ServiceWorkerDatabase::PurgeUncommittedResourceIds(
678 const std::set
<int64
>& ids
) {
679 leveldb::WriteBatch batch
;
680 Status status
= DeleteResourceIdsInBatch(
681 kUncommittedResIdKeyPrefix
, ids
, &batch
);
682 if (status
!= STATUS_OK
)
684 status
= WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix
, ids
, &batch
);
685 if (status
!= STATUS_OK
)
687 return WriteBatch(&batch
);
690 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteAllDataForOrigin(
692 std::vector
<int64
>* newly_purgeable_resources
) {
693 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
694 Status status
= LazyOpen(false);
695 if (IsNewOrNonexistentDatabase(status
))
697 if (status
!= STATUS_OK
)
699 if (!origin
.is_valid())
700 return STATUS_ERROR_FAILED
;
702 leveldb::WriteBatch batch
;
704 // Delete from the unique origin list.
705 batch
.Delete(CreateUniqueOriginKey(origin
));
707 std::vector
<RegistrationData
> registrations
;
708 status
= GetRegistrationsForOrigin(origin
, ®istrations
);
709 if (status
!= STATUS_OK
)
712 // Delete registrations and resource records.
713 for (std::vector
<RegistrationData
>::const_iterator itr
=
714 registrations
.begin(); itr
!= registrations
.end(); ++itr
) {
715 batch
.Delete(CreateRegistrationKey(itr
->registration_id
, origin
));
716 status
= DeleteResourceRecords(
717 itr
->version_id
, newly_purgeable_resources
, &batch
);
718 if (status
!= STATUS_OK
)
722 return WriteBatch(&batch
);
725 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DestroyDatabase() {
726 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
727 Disable(FROM_HERE
, STATUS_OK
);
728 return LevelDBStatusToStatus(
729 leveldb::DestroyDB(path_
.AsUTF8Unsafe(), leveldb::Options()));
732 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::LazyOpen(
733 bool create_if_missing
) {
734 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
736 // Do not try to open a database if we tried and failed once.
737 if (state_
== DISABLED
)
738 return STATUS_ERROR_FAILED
;
742 // When |path_| is empty, open a database in-memory.
743 bool use_in_memory_db
= path_
.empty();
745 if (!create_if_missing
) {
746 // Avoid opening a database if it does not exist at the |path_|.
747 if (use_in_memory_db
||
748 !base::PathExists(path_
) ||
749 base::IsDirectoryEmpty(path_
)) {
750 return STATUS_ERROR_NOT_FOUND
;
754 leveldb::Options options
;
755 options
.create_if_missing
= create_if_missing
;
756 if (use_in_memory_db
) {
757 env_
.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
758 options
.env
= env_
.get();
761 leveldb::DB
* db
= NULL
;
762 Status status
= LevelDBStatusToStatus(
763 leveldb::DB::Open(options
, path_
.AsUTF8Unsafe(), &db
));
764 HandleOpenResult(FROM_HERE
, status
);
765 if (status
!= STATUS_OK
) {
767 // TODO(nhiroki): Should we retry to open the database?
773 status
= ReadDatabaseVersion(&db_version
);
774 if (status
!= STATUS_OK
)
776 DCHECK_LE(0, db_version
);
778 state_
= INITIALIZED
;
782 bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase(
783 ServiceWorkerDatabase::Status status
) {
784 if (status
== STATUS_ERROR_NOT_FOUND
)
786 if (status
== STATUS_OK
&& state_
== UNINITIALIZED
)
791 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadNextAvailableId(
793 int64
* next_avail_id
) {
795 DCHECK(next_avail_id
);
798 Status status
= LevelDBStatusToStatus(
799 db_
->Get(leveldb::ReadOptions(), id_key
, &value
));
800 if (status
== STATUS_ERROR_NOT_FOUND
) {
801 // Nobody has gotten the next resource id for |id_key|.
803 HandleReadResult(FROM_HERE
, STATUS_OK
);
805 } else if (status
!= STATUS_OK
) {
806 HandleReadResult(FROM_HERE
, status
);
810 status
= ParseId(value
, next_avail_id
);
811 HandleReadResult(FROM_HERE
, status
);
815 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadRegistrationData(
816 int64 registration_id
,
818 RegistrationData
* registration
) {
819 DCHECK(registration
);
821 const std::string key
= CreateRegistrationKey(registration_id
, origin
);
823 Status status
= LevelDBStatusToStatus(
824 db_
->Get(leveldb::ReadOptions(), key
, &value
));
825 if (status
!= STATUS_OK
) {
828 status
== STATUS_ERROR_NOT_FOUND
? STATUS_OK
: status
);
832 status
= ParseRegistrationData(value
, registration
);
833 HandleReadResult(FROM_HERE
, status
);
837 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadResourceRecords(
839 std::vector
<ResourceRecord
>* resources
) {
840 DCHECK(resources
->empty());
842 Status status
= STATUS_OK
;
843 const std::string prefix
= CreateResourceRecordKeyPrefix(version_id
);
845 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
846 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
847 Status status
= LevelDBStatusToStatus(itr
->status());
848 if (status
!= STATUS_OK
) {
849 HandleReadResult(FROM_HERE
, status
);
854 if (!RemovePrefix(itr
->key().ToString(), prefix
, NULL
))
857 ResourceRecord resource
;
858 status
= ParseResourceRecord(itr
->value().ToString(), &resource
);
859 if (status
!= STATUS_OK
) {
860 HandleReadResult(FROM_HERE
, status
);
864 resources
->push_back(resource
);
867 HandleReadResult(FROM_HERE
, status
);
871 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceRecords(
873 std::vector
<int64
>* newly_purgeable_resources
,
874 leveldb::WriteBatch
* batch
) {
877 Status status
= STATUS_OK
;
878 const std::string prefix
= CreateResourceRecordKeyPrefix(version_id
);
880 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
881 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
882 status
= LevelDBStatusToStatus(itr
->status());
883 if (status
!= STATUS_OK
) {
884 HandleReadResult(FROM_HERE
, status
);
888 const std::string key
= itr
->key().ToString();
889 std::string unprefixed
;
890 if (!RemovePrefix(key
, prefix
, &unprefixed
))
894 status
= ParseId(unprefixed
, &resource_id
);
895 if (status
!= STATUS_OK
) {
896 HandleReadResult(FROM_HERE
, status
);
900 // Remove a resource record.
903 // Currently resource sharing across versions and registrations is not
904 // supported, so we can purge this without caring about it.
905 PutPurgeableResourceIdToBatch(resource_id
, batch
);
906 newly_purgeable_resources
->push_back(resource_id
);
909 HandleReadResult(FROM_HERE
, status
);
913 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadResourceIds(
914 const char* id_key_prefix
,
915 std::set
<int64
>* ids
) {
916 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
917 DCHECK(id_key_prefix
);
918 DCHECK(ids
->empty());
920 Status status
= LazyOpen(false);
921 if (IsNewOrNonexistentDatabase(status
))
923 if (status
!= STATUS_OK
)
926 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
927 for (itr
->Seek(id_key_prefix
); itr
->Valid(); itr
->Next()) {
928 status
= LevelDBStatusToStatus(itr
->status());
929 if (status
!= STATUS_OK
) {
930 HandleReadResult(FROM_HERE
, status
);
935 std::string unprefixed
;
936 if (!RemovePrefix(itr
->key().ToString(), id_key_prefix
, &unprefixed
))
940 status
= ParseId(unprefixed
, &resource_id
);
941 if (status
!= STATUS_OK
) {
942 HandleReadResult(FROM_HERE
, status
);
946 ids
->insert(resource_id
);
949 HandleReadResult(FROM_HERE
, status
);
953 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteResourceIds(
954 const char* id_key_prefix
,
955 const std::set
<int64
>& ids
) {
956 leveldb::WriteBatch batch
;
957 Status status
= WriteResourceIdsInBatch(id_key_prefix
, ids
, &batch
);
958 if (status
!= STATUS_OK
)
960 return WriteBatch(&batch
);
963 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteResourceIdsInBatch(
964 const char* id_key_prefix
,
965 const std::set
<int64
>& ids
,
966 leveldb::WriteBatch
* batch
) {
967 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
968 DCHECK(id_key_prefix
);
970 Status status
= LazyOpen(true);
971 if (status
!= STATUS_OK
)
976 for (std::set
<int64
>::const_iterator itr
= ids
.begin();
977 itr
!= ids
.end(); ++itr
) {
978 // Value should be empty.
979 batch
->Put(CreateResourceIdKey(id_key_prefix
, *itr
), "");
981 // std::set is sorted, so the last element is the largest.
982 BumpNextResourceIdIfNeeded(*ids
.rbegin(), batch
);
986 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceIds(
987 const char* id_key_prefix
,
988 const std::set
<int64
>& ids
) {
989 leveldb::WriteBatch batch
;
990 Status status
= DeleteResourceIdsInBatch(id_key_prefix
, ids
, &batch
);
991 if (status
!= STATUS_OK
)
993 return WriteBatch(&batch
);
996 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceIdsInBatch(
997 const char* id_key_prefix
,
998 const std::set
<int64
>& ids
,
999 leveldb::WriteBatch
* batch
) {
1000 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
1001 DCHECK(id_key_prefix
);
1003 Status status
= LazyOpen(false);
1004 if (IsNewOrNonexistentDatabase(status
))
1006 if (status
!= STATUS_OK
)
1009 for (std::set
<int64
>::const_iterator itr
= ids
.begin();
1010 itr
!= ids
.end(); ++itr
) {
1011 batch
->Delete(CreateResourceIdKey(id_key_prefix
, *itr
));
1016 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadDatabaseVersion(
1017 int64
* db_version
) {
1019 Status status
= LevelDBStatusToStatus(
1020 db_
->Get(leveldb::ReadOptions(), kDatabaseVersionKey
, &value
));
1021 if (status
== STATUS_ERROR_NOT_FOUND
) {
1022 // The database hasn't been initialized yet.
1024 HandleReadResult(FROM_HERE
, STATUS_OK
);
1028 if (status
!= STATUS_OK
) {
1029 HandleReadResult(FROM_HERE
, status
);
1033 status
= ParseDatabaseVersion(value
, db_version
);
1034 HandleReadResult(FROM_HERE
, status
);
1038 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteBatch(
1039 leveldb::WriteBatch
* batch
) {
1041 DCHECK_NE(DISABLED
, state_
);
1043 if (state_
== UNINITIALIZED
) {
1044 // Write the database schema version.
1045 batch
->Put(kDatabaseVersionKey
, base::Int64ToString(kCurrentSchemaVersion
));
1046 state_
= INITIALIZED
;
1049 Status status
= LevelDBStatusToStatus(
1050 db_
->Write(leveldb::WriteOptions(), batch
));
1051 HandleWriteResult(FROM_HERE
, status
);
1055 void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded(
1056 int64 used_id
, leveldb::WriteBatch
* batch
) {
1058 if (next_avail_registration_id_
<= used_id
) {
1059 next_avail_registration_id_
= used_id
+ 1;
1060 batch
->Put(kNextRegIdKey
, base::Int64ToString(next_avail_registration_id_
));
1064 void ServiceWorkerDatabase::BumpNextResourceIdIfNeeded(
1065 int64 used_id
, leveldb::WriteBatch
* batch
) {
1067 if (next_avail_resource_id_
<= used_id
) {
1068 next_avail_resource_id_
= used_id
+ 1;
1069 batch
->Put(kNextResIdKey
, base::Int64ToString(next_avail_resource_id_
));
1073 void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded(
1074 int64 used_id
, leveldb::WriteBatch
* batch
) {
1076 if (next_avail_version_id_
<= used_id
) {
1077 next_avail_version_id_
= used_id
+ 1;
1078 batch
->Put(kNextVerIdKey
, base::Int64ToString(next_avail_version_id_
));
1082 bool ServiceWorkerDatabase::IsOpen() {
1086 void ServiceWorkerDatabase::Disable(
1087 const tracked_objects::Location
& from_here
,
1089 if (status
!= STATUS_OK
) {
1090 DLOG(ERROR
) << "Failed at: " << from_here
.ToString()
1091 << " with error: " << StatusToString(status
);
1092 DLOG(ERROR
) << "ServiceWorkerDatabase is disabled.";
1098 void ServiceWorkerDatabase::HandleOpenResult(
1099 const tracked_objects::Location
& from_here
,
1101 if (status
!= STATUS_OK
)
1102 Disable(from_here
, status
);
1103 ServiceWorkerMetrics::CountOpenDatabaseResult(status
);
1106 void ServiceWorkerDatabase::HandleReadResult(
1107 const tracked_objects::Location
& from_here
,
1109 if (status
!= STATUS_OK
)
1110 Disable(from_here
, status
);
1111 ServiceWorkerMetrics::CountReadDatabaseResult(status
);
1114 void ServiceWorkerDatabase::HandleWriteResult(
1115 const tracked_objects::Location
& from_here
,
1117 if (status
!= STATUS_OK
)
1118 Disable(from_here
, status
);
1119 ServiceWorkerMetrics::CountWriteDatabaseResult(status
);
1122 } // namespace content