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/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 // =======================
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"
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'>
48 // key: "PRES:" + <int64 'purgeable_resource_id'>
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'>
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;
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
,
92 if (!StartsWithASCII(str
, prefix
, true))
95 *out
= str
.substr(prefix
.size());
99 std::string
CreateRegistrationKey(int64 registration_id
,
100 const GURL
& origin
) {
101 return base::StringPrintf("%s%s%c%s",
103 origin
.spec().c_str(),
105 base::Int64ToString(registration_id
).c_str());
108 std::string
CreateResourceRecordKeyPrefix(int64 version_id
) {
109 return base::StringPrintf("%s%s%c",
111 base::Int64ToString(version_id
).c_str(),
115 std::string
CreateResourceRecordKey(int64 version_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
) {
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());
146 bool success
= data
.SerializeToString(&value
);
148 GURL origin
= input
.scope
.GetOrigin();
149 batch
->Put(CreateRegistrationKey(data
.registration_id(), origin
), value
);
152 void PutResourceRecordToBatch(
153 const ServiceWorkerDatabase::ResourceRecord
& input
,
155 leveldb::WriteBatch
* batch
) {
158 // Convert ResourceRecord to ServiceWorkerResourceRecord.
159 ServiceWorkerResourceRecord record
;
160 record
.set_resource_id(input
.resource_id
);
161 record
.set_url(input
.url
.spec());
164 bool success
= record
.SerializeToString(&value
);
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
,
186 if (!base::StringToInt64(serialized
, &id
) || id
< 0)
187 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
189 return ServiceWorkerDatabase::STATUS_OK
;
192 ServiceWorkerDatabase::Status
ParseDatabaseVersion(
193 const std::string
& serialized
,
196 const int kFirstValidVersion
= 1;
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
;
209 return ServiceWorkerDatabase::STATUS_OK
;
212 ServiceWorkerDatabase::Status
ParseRegistrationData(
213 const std::string
& serialized
,
214 ServiceWorkerDatabase::RegistrationData
* 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
) {
244 ServiceWorkerResourceRecord record
;
245 if (!record
.ParseFromString(serialized
))
246 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
248 GURL
url(record
.url());
250 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
252 // Convert ServiceWorkerResourceRecord to ResourceRecord.
253 out
->resource_id
= record
.resource_id();
255 return ServiceWorkerDatabase::STATUS_OK
;
258 ServiceWorkerDatabase::Status
LevelDBStatusToStatus(
259 const leveldb::Status
& status
) {
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
;
269 return ServiceWorkerDatabase::STATUS_ERROR_FAILED
;
272 const char* StatusToString(ServiceWorkerDatabase::Status 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
:
286 return "Database unknown error";
289 return "Database unknown error";
294 ServiceWorkerDatabase::RegistrationData::RegistrationData()
295 : registration_id(kInvalidServiceWorkerRegistrationId
),
296 version_id(kInvalidServiceWorkerVersionId
),
298 has_fetch_handler(false) {
301 ServiceWorkerDatabase::RegistrationData::~RegistrationData() {
304 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath
& 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());
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;
334 if (status
!= STATUS_OK
)
337 status
= ReadNextAvailableId(kNextRegIdKey
, &next_avail_registration_id_
);
338 if (status
!= STATUS_OK
)
340 status
= ReadNextAvailableId(kNextVerIdKey
, &next_avail_version_id_
);
341 if (status
!= STATUS_OK
)
343 status
= ReadNextAvailableId(kNextResIdKey
, &next_avail_resource_id_
);
344 if (status
!= STATUS_OK
)
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_
;
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
))
361 if (status
!= STATUS_OK
)
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
);
374 if (!RemovePrefix(itr
->key().ToString(), kUniqueOriginKey
, &origin
))
376 origins
->insert(GURL(origin
));
379 HandleReadResult(FROM_HERE
, status
);
383 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetRegistrationsForOrigin(
385 std::vector
<RegistrationData
>* registrations
) {
386 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
387 DCHECK(registrations
->empty());
389 Status status
= LazyOpen(false);
390 if (IsNewOrNonexistentDatabase(status
))
392 if (status
!= STATUS_OK
)
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();
408 if (!RemovePrefix(itr
->key().ToString(), prefix
, NULL
))
411 RegistrationData registration
;
412 status
= ParseRegistrationData(itr
->value().ToString(), ®istration
);
413 if (status
!= STATUS_OK
) {
414 HandleReadResult(FROM_HERE
, status
);
415 registrations
->clear();
418 registrations
->push_back(registration
);
421 HandleReadResult(FROM_HERE
, 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
))
433 if (status
!= STATUS_OK
)
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();
445 if (!RemovePrefix(itr
->key().ToString(), kRegKeyPrefix
, NULL
))
448 RegistrationData registration
;
449 status
= ParseRegistrationData(itr
->value().ToString(), ®istration
);
450 if (status
!= STATUS_OK
) {
451 HandleReadResult(FROM_HERE
, status
);
452 registrations
->clear();
455 registrations
->push_back(registration
);
458 HandleReadResult(FROM_HERE
, status
);
462 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadRegistration(
463 int64 registration_id
,
465 RegistrationData
* registration
,
466 std::vector
<ResourceRecord
>* resources
) {
467 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
468 DCHECK(registration
);
471 Status status
= LazyOpen(false);
472 if (IsNewOrNonexistentDatabase(status
) || status
!= STATUS_OK
)
475 RegistrationData value
;
476 status
= ReadRegistrationData(registration_id
, origin
, &value
);
477 if (status
!= STATUS_OK
)
480 status
= ReadResourceRecords(value
.version_id
, resources
);
481 if (status
!= STATUS_OK
)
484 *registration
= value
;
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
)
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(),
528 if (status
!= STATUS_OK
&& status
!= STATUS_ERROR_NOT_FOUND
)
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
)
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
)
558 if (!origin
.is_valid())
559 return STATUS_ERROR_FAILED
;
561 RegistrationData registration
;
562 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
563 if (status
!= STATUS_OK
)
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
,
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
)
583 if (!origin
.is_valid())
584 return STATUS_ERROR_FAILED
;
586 RegistrationData registration
;
587 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
588 if (status
!= STATUS_OK
)
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
,
601 std::vector
<int64
>* newly_purgeable_resources
) {
602 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
603 Status status
= LazyOpen(false);
604 if (IsNewOrNonexistentDatabase(status
))
606 if (status
!= STATUS_OK
)
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
, ®istrations
);
618 if (status
!= STATUS_OK
)
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
)
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
)
682 status
= WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix
, ids
, &batch
);
683 if (status
!= STATUS_OK
)
685 return WriteBatch(&batch
);
688 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteAllDataForOrigin(
690 std::vector
<int64
>* newly_purgeable_resources
) {
691 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
692 Status status
= LazyOpen(false);
693 if (IsNewOrNonexistentDatabase(status
))
695 if (status
!= STATUS_OK
)
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
, ®istrations
);
707 if (status
!= STATUS_OK
)
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
)
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
;
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
) {
758 // TODO(nhiroki): Should we retry to open the database?
764 status
= ReadDatabaseVersion(&db_version
);
765 if (status
!= STATUS_OK
)
767 DCHECK_LE(0, db_version
);
769 state_
= INITIALIZED
;
773 bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase(
774 ServiceWorkerDatabase::Status status
) {
775 if (status
== STATUS_ERROR_NOT_FOUND
)
777 if (status
== STATUS_OK
&& state_
== UNINITIALIZED
)
782 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadNextAvailableId(
784 int64
* next_avail_id
) {
786 DCHECK(next_avail_id
);
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|.
794 HandleReadResult(FROM_HERE
, STATUS_OK
);
796 } else if (status
!= STATUS_OK
) {
797 HandleReadResult(FROM_HERE
, status
);
801 status
= ParseId(value
, next_avail_id
);
802 HandleReadResult(FROM_HERE
, status
);
806 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadRegistrationData(
807 int64 registration_id
,
809 RegistrationData
* registration
) {
810 DCHECK(registration
);
812 const std::string key
= CreateRegistrationKey(registration_id
, origin
);
814 Status status
= LevelDBStatusToStatus(
815 db_
->Get(leveldb::ReadOptions(), key
, &value
));
816 if (status
!= STATUS_OK
) {
819 status
== STATUS_ERROR_NOT_FOUND
? STATUS_OK
: status
);
823 status
= ParseRegistrationData(value
, registration
);
824 HandleReadResult(FROM_HERE
, status
);
828 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadResourceRecords(
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
);
845 if (!RemovePrefix(itr
->key().ToString(), prefix
, NULL
))
848 ResourceRecord resource
;
849 status
= ParseResourceRecord(itr
->value().ToString(), &resource
);
850 if (status
!= STATUS_OK
) {
851 HandleReadResult(FROM_HERE
, status
);
855 resources
->push_back(resource
);
858 HandleReadResult(FROM_HERE
, status
);
862 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceRecords(
864 std::vector
<int64
>* newly_purgeable_resources
,
865 leveldb::WriteBatch
* 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
);
879 const std::string key
= itr
->key().ToString();
880 std::string unprefixed
;
881 if (!RemovePrefix(key
, prefix
, &unprefixed
))
885 status
= ParseId(unprefixed
, &resource_id
);
886 if (status
!= STATUS_OK
) {
887 HandleReadResult(FROM_HERE
, status
);
891 // Remove a resource record.
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
);
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
))
914 if (status
!= STATUS_OK
)
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
);
926 std::string unprefixed
;
927 if (!RemovePrefix(itr
->key().ToString(), id_key_prefix
, &unprefixed
))
931 status
= ParseId(unprefixed
, &resource_id
);
932 if (status
!= STATUS_OK
) {
933 HandleReadResult(FROM_HERE
, status
);
937 ids
->insert(resource_id
);
940 HandleReadResult(FROM_HERE
, 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
)
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
)
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
), "");
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
)
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
))
993 if (status
!= STATUS_OK
)
996 for (std::set
<int64
>::const_iterator itr
= ids
.begin();
997 itr
!= ids
.end(); ++itr
) {
998 batch
->Delete(CreateResourceIdKey(id_key_prefix
, *itr
));
1003 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadDatabaseVersion(
1004 int64
* db_version
) {
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.
1011 HandleReadResult(FROM_HERE
, STATUS_OK
);
1015 if (status
!= STATUS_OK
) {
1016 HandleReadResult(FROM_HERE
, status
);
1020 status
= ParseDatabaseVersion(value
, db_version
);
1021 HandleReadResult(FROM_HERE
, status
);
1025 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteBatch(
1026 leveldb::WriteBatch
* 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
);
1042 void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded(
1043 int64 used_id
, leveldb::WriteBatch
* 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
) {
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() {
1064 void ServiceWorkerDatabase::Disable(
1065 const tracked_objects::Location
& from_here
,
1067 DLOG(ERROR
) << "Failed at: " << from_here
.ToString()
1068 << " with error: " << StatusToString(status
);
1069 DLOG(ERROR
) << "ServiceWorkerDatabase is disabled.";
1074 void ServiceWorkerDatabase::HandleOpenResult(
1075 const tracked_objects::Location
& from_here
,
1077 if (status
!= ServiceWorkerDatabase::STATUS_OK
)
1078 Disable(from_here
, status
);
1079 UMA_HISTOGRAM_ENUMERATION(kOpenResultHistogramLabel
,
1081 ServiceWorkerDatabase::STATUS_ERROR_MAX
);
1084 void ServiceWorkerDatabase::HandleReadResult(
1085 const tracked_objects::Location
& from_here
,
1087 if (status
!= ServiceWorkerDatabase::STATUS_OK
)
1088 Disable(from_here
, status
);
1089 UMA_HISTOGRAM_ENUMERATION(kReadResultHistogramLabel
,
1091 ServiceWorkerDatabase::STATUS_ERROR_MAX
);
1094 void ServiceWorkerDatabase::HandleWriteResult(
1095 const tracked_objects::Location
& from_here
,
1097 if (status
!= ServiceWorkerDatabase::STATUS_OK
)
1098 Disable(from_here
, status
);
1099 UMA_HISTOGRAM_ENUMERATION(kWriteResultHistogramLabel
,
1101 ServiceWorkerDatabase::STATUS_ERROR_MAX
);
1104 } // namespace content