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/files/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());
137 data
.set_resources_total_size_bytes(input
.resources_total_size_bytes
);
140 bool success
= data
.SerializeToString(&value
);
142 GURL origin
= input
.scope
.GetOrigin();
143 batch
->Put(CreateRegistrationKey(data
.registration_id(), origin
), value
);
146 void PutResourceRecordToBatch(
147 const ServiceWorkerDatabase::ResourceRecord
& input
,
149 leveldb::WriteBatch
* batch
) {
151 DCHECK_GE(input
.size_bytes
, 0);
153 // Convert ResourceRecord to ServiceWorkerResourceRecord.
154 ServiceWorkerResourceRecord record
;
155 record
.set_resource_id(input
.resource_id
);
156 record
.set_url(input
.url
.spec());
157 record
.set_size_bytes(input
.size_bytes
);
160 bool success
= record
.SerializeToString(&value
);
162 batch
->Put(CreateResourceRecordKey(version_id
, input
.resource_id
), value
);
165 void PutUniqueOriginToBatch(const GURL
& origin
,
166 leveldb::WriteBatch
* batch
) {
167 // Value should be empty.
168 batch
->Put(CreateUniqueOriginKey(origin
), "");
171 void PutPurgeableResourceIdToBatch(int64 resource_id
,
172 leveldb::WriteBatch
* batch
) {
173 // Value should be empty.
174 batch
->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix
, resource_id
), "");
177 ServiceWorkerDatabase::Status
ParseId(
178 const std::string
& serialized
,
182 if (!base::StringToInt64(serialized
, &id
) || id
< 0)
183 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
185 return ServiceWorkerDatabase::STATUS_OK
;
188 ServiceWorkerDatabase::Status
ParseDatabaseVersion(
189 const std::string
& serialized
,
192 const int kFirstValidVersion
= 1;
194 if (!base::StringToInt64(serialized
, &version
) ||
195 version
< kFirstValidVersion
) {
196 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
198 if (kCurrentSchemaVersion
< version
) {
199 DLOG(ERROR
) << "ServiceWorkerDatabase has newer schema version"
200 << " than the current latest version: "
201 << version
<< " vs " << kCurrentSchemaVersion
;
202 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
205 return ServiceWorkerDatabase::STATUS_OK
;
208 ServiceWorkerDatabase::Status
ParseRegistrationData(
209 const std::string
& serialized
,
210 ServiceWorkerDatabase::RegistrationData
* out
) {
212 ServiceWorkerRegistrationData data
;
213 if (!data
.ParseFromString(serialized
))
214 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
216 GURL
scope_url(data
.scope_url());
217 GURL
script_url(data
.script_url());
218 if (!scope_url
.is_valid() ||
219 !script_url
.is_valid() ||
220 scope_url
.GetOrigin() != script_url
.GetOrigin()) {
221 DLOG(ERROR
) << "Scope URL '" << data
.scope_url() << "' and/or script url '"
223 << "' are invalid or have mismatching origins.";
224 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
227 // Convert ServiceWorkerRegistrationData to RegistrationData.
228 out
->registration_id
= data
.registration_id();
229 out
->scope
= scope_url
;
230 out
->script
= script_url
;
231 out
->version_id
= data
.version_id();
232 out
->is_active
= data
.is_active();
233 out
->has_fetch_handler
= data
.has_fetch_handler();
234 out
->last_update_check
=
235 base::Time::FromInternalValue(data
.last_update_check_time());
236 out
->resources_total_size_bytes
= data
.resources_total_size_bytes();
238 return ServiceWorkerDatabase::STATUS_OK
;
241 ServiceWorkerDatabase::Status
ParseResourceRecord(
242 const std::string
& serialized
,
243 ServiceWorkerDatabase::ResourceRecord
* out
) {
245 ServiceWorkerResourceRecord record
;
246 if (!record
.ParseFromString(serialized
))
247 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
249 GURL
url(record
.url());
251 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
253 // Convert ServiceWorkerResourceRecord to ResourceRecord.
254 out
->resource_id
= record
.resource_id();
256 out
->size_bytes
= record
.size_bytes();
257 return ServiceWorkerDatabase::STATUS_OK
;
260 ServiceWorkerDatabase::Status
LevelDBStatusToStatus(
261 const leveldb::Status
& status
) {
263 return ServiceWorkerDatabase::STATUS_OK
;
264 else if (status
.IsNotFound())
265 return ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND
;
266 else if (status
.IsIOError())
267 return ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR
;
268 else if (status
.IsCorruption())
269 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
271 return ServiceWorkerDatabase::STATUS_ERROR_FAILED
;
276 const char* ServiceWorkerDatabase::StatusToString(
277 ServiceWorkerDatabase::Status status
) {
279 case ServiceWorkerDatabase::STATUS_OK
:
280 return "Database OK";
281 case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND
:
282 return "Database not found";
283 case ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR
:
284 return "Database IO error";
285 case ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
:
286 return "Database corrupted";
287 case ServiceWorkerDatabase::STATUS_ERROR_FAILED
:
288 return "Database operation failed";
289 case ServiceWorkerDatabase::STATUS_ERROR_MAX
:
291 return "Database unknown error";
294 return "Database unknown error";
297 ServiceWorkerDatabase::RegistrationData::RegistrationData()
298 : registration_id(kInvalidServiceWorkerRegistrationId
),
299 version_id(kInvalidServiceWorkerVersionId
),
301 has_fetch_handler(false),
302 resources_total_size_bytes(0) {
305 ServiceWorkerDatabase::RegistrationData::~RegistrationData() {
308 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath
& path
)
310 next_avail_registration_id_(0),
311 next_avail_resource_id_(0),
312 next_avail_version_id_(0),
313 state_(UNINITIALIZED
) {
314 sequence_checker_
.DetachFromSequence();
317 ServiceWorkerDatabase::~ServiceWorkerDatabase() {
318 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
322 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetNextAvailableIds(
323 int64
* next_avail_registration_id
,
324 int64
* next_avail_version_id
,
325 int64
* next_avail_resource_id
) {
326 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
327 DCHECK(next_avail_registration_id
);
328 DCHECK(next_avail_version_id
);
329 DCHECK(next_avail_resource_id
);
331 Status status
= LazyOpen(false);
332 if (IsNewOrNonexistentDatabase(status
)) {
333 *next_avail_registration_id
= 0;
334 *next_avail_version_id
= 0;
335 *next_avail_resource_id
= 0;
338 if (status
!= STATUS_OK
)
341 status
= ReadNextAvailableId(kNextRegIdKey
, &next_avail_registration_id_
);
342 if (status
!= STATUS_OK
)
344 status
= ReadNextAvailableId(kNextVerIdKey
, &next_avail_version_id_
);
345 if (status
!= STATUS_OK
)
347 status
= ReadNextAvailableId(kNextResIdKey
, &next_avail_resource_id_
);
348 if (status
!= STATUS_OK
)
351 *next_avail_registration_id
= next_avail_registration_id_
;
352 *next_avail_version_id
= next_avail_version_id_
;
353 *next_avail_resource_id
= next_avail_resource_id_
;
357 ServiceWorkerDatabase::Status
358 ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set
<GURL
>* origins
) {
359 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
360 DCHECK(origins
->empty());
362 Status status
= LazyOpen(false);
363 if (IsNewOrNonexistentDatabase(status
))
365 if (status
!= STATUS_OK
)
368 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
369 for (itr
->Seek(kUniqueOriginKey
); itr
->Valid(); itr
->Next()) {
370 status
= LevelDBStatusToStatus(itr
->status());
371 if (status
!= STATUS_OK
) {
372 HandleReadResult(FROM_HERE
, status
);
378 if (!RemovePrefix(itr
->key().ToString(), kUniqueOriginKey
, &origin
))
380 origins
->insert(GURL(origin
));
383 HandleReadResult(FROM_HERE
, status
);
387 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetRegistrationsForOrigin(
389 std::vector
<RegistrationData
>* registrations
) {
390 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
391 DCHECK(registrations
->empty());
393 Status status
= LazyOpen(false);
394 if (IsNewOrNonexistentDatabase(status
))
396 if (status
!= STATUS_OK
)
399 // Create a key prefix for registrations.
400 std::string prefix
= base::StringPrintf(
401 "%s%s%c", kRegKeyPrefix
, origin
.spec().c_str(), kKeySeparator
);
403 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
404 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
405 status
= LevelDBStatusToStatus(itr
->status());
406 if (status
!= STATUS_OK
) {
407 HandleReadResult(FROM_HERE
, status
);
408 registrations
->clear();
412 if (!RemovePrefix(itr
->key().ToString(), prefix
, NULL
))
415 RegistrationData registration
;
416 status
= ParseRegistrationData(itr
->value().ToString(), ®istration
);
417 if (status
!= STATUS_OK
) {
418 HandleReadResult(FROM_HERE
, status
);
419 registrations
->clear();
422 registrations
->push_back(registration
);
425 HandleReadResult(FROM_HERE
, status
);
429 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetAllRegistrations(
430 std::vector
<RegistrationData
>* registrations
) {
431 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
432 DCHECK(registrations
->empty());
434 Status status
= LazyOpen(false);
435 if (IsNewOrNonexistentDatabase(status
))
437 if (status
!= STATUS_OK
)
440 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
441 for (itr
->Seek(kRegKeyPrefix
); itr
->Valid(); itr
->Next()) {
442 status
= LevelDBStatusToStatus(itr
->status());
443 if (status
!= STATUS_OK
) {
444 HandleReadResult(FROM_HERE
, status
);
445 registrations
->clear();
449 if (!RemovePrefix(itr
->key().ToString(), kRegKeyPrefix
, NULL
))
452 RegistrationData registration
;
453 status
= ParseRegistrationData(itr
->value().ToString(), ®istration
);
454 if (status
!= STATUS_OK
) {
455 HandleReadResult(FROM_HERE
, status
);
456 registrations
->clear();
459 registrations
->push_back(registration
);
462 HandleReadResult(FROM_HERE
, status
);
466 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadRegistration(
467 int64 registration_id
,
469 RegistrationData
* registration
,
470 std::vector
<ResourceRecord
>* resources
) {
471 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
472 DCHECK(registration
);
475 Status status
= LazyOpen(false);
476 if (IsNewOrNonexistentDatabase(status
) || status
!= STATUS_OK
)
479 RegistrationData value
;
480 status
= ReadRegistrationData(registration_id
, origin
, &value
);
481 if (status
!= STATUS_OK
)
484 status
= ReadResourceRecords(value
.version_id
, resources
);
485 if (status
!= STATUS_OK
)
488 *registration
= value
;
492 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteRegistration(
493 const RegistrationData
& registration
,
494 const std::vector
<ResourceRecord
>& resources
,
495 RegistrationData
* old_registration
,
496 std::vector
<int64
>* newly_purgeable_resources
) {
497 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
498 DCHECK(old_registration
);
499 Status status
= LazyOpen(true);
500 old_registration
->version_id
= kInvalidServiceWorkerVersionId
;
501 if (status
!= STATUS_OK
)
504 leveldb::WriteBatch batch
;
505 BumpNextRegistrationIdIfNeeded(registration
.registration_id
, &batch
);
506 BumpNextVersionIdIfNeeded(registration
.version_id
, &batch
);
508 PutUniqueOriginToBatch(registration
.scope
.GetOrigin(), &batch
);
510 int64_t total_size_bytes
= 0;
511 for (const auto& resource
: resources
) {
512 total_size_bytes
+= resource
.size_bytes
;
514 DCHECK_EQ(total_size_bytes
, registration
.resources_total_size_bytes
)
515 << "The total size in the registration must match the cumulative "
516 << "sizes of the resources.";
518 PutRegistrationDataToBatch(registration
, &batch
);
520 // Used for avoiding multiple writes for the same resource id or url.
521 std::set
<int64
> pushed_resources
;
522 std::set
<GURL
> pushed_urls
;
523 for (std::vector
<ResourceRecord
>::const_iterator itr
= resources
.begin();
524 itr
!= resources
.end(); ++itr
) {
525 if (!itr
->url
.is_valid())
526 return STATUS_ERROR_FAILED
;
528 // Duplicated resource id or url should not exist.
529 DCHECK(pushed_resources
.insert(itr
->resource_id
).second
);
530 DCHECK(pushed_urls
.insert(itr
->url
).second
);
532 PutResourceRecordToBatch(*itr
, registration
.version_id
, &batch
);
534 // Delete a resource from the uncommitted list.
535 batch
.Delete(CreateResourceIdKey(
536 kUncommittedResIdKeyPrefix
, itr
->resource_id
));
537 // Delete from the purgeable list in case this version was once deleted.
539 CreateResourceIdKey(kPurgeableResIdKeyPrefix
, itr
->resource_id
));
542 // Retrieve a previous version to sweep purgeable resources.
543 status
= ReadRegistrationData(registration
.registration_id
,
544 registration
.scope
.GetOrigin(),
546 if (status
!= STATUS_OK
&& status
!= STATUS_ERROR_NOT_FOUND
)
548 if (status
== STATUS_OK
) {
549 DCHECK_LT(old_registration
->version_id
, registration
.version_id
);
550 status
= DeleteResourceRecords(
551 old_registration
->version_id
, newly_purgeable_resources
, &batch
);
552 if (status
!= STATUS_OK
)
555 // Currently resource sharing across versions and registrations is not
556 // supported, so resource ids should not be overlapped between
557 // |registration| and |old_registration|.
558 std::set
<int64
> deleted_resources(newly_purgeable_resources
->begin(),
559 newly_purgeable_resources
->end());
560 DCHECK(base::STLSetIntersection
<std::set
<int64
> >(
561 pushed_resources
, deleted_resources
).empty());
564 return WriteBatch(&batch
);
567 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::UpdateVersionToActive(
568 int64 registration_id
,
569 const GURL
& origin
) {
570 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
571 Status status
= LazyOpen(false);
572 if (IsNewOrNonexistentDatabase(status
))
573 return STATUS_ERROR_NOT_FOUND
;
574 if (status
!= STATUS_OK
)
576 if (!origin
.is_valid())
577 return STATUS_ERROR_FAILED
;
579 RegistrationData registration
;
580 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
581 if (status
!= STATUS_OK
)
584 registration
.is_active
= true;
586 leveldb::WriteBatch batch
;
587 PutRegistrationDataToBatch(registration
, &batch
);
588 return WriteBatch(&batch
);
591 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::UpdateLastCheckTime(
592 int64 registration_id
,
594 const base::Time
& time
) {
595 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
596 Status status
= LazyOpen(false);
597 if (IsNewOrNonexistentDatabase(status
))
598 return STATUS_ERROR_NOT_FOUND
;
599 if (status
!= STATUS_OK
)
601 if (!origin
.is_valid())
602 return STATUS_ERROR_FAILED
;
604 RegistrationData registration
;
605 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
606 if (status
!= STATUS_OK
)
609 registration
.last_update_check
= time
;
611 leveldb::WriteBatch batch
;
612 PutRegistrationDataToBatch(registration
, &batch
);
613 return WriteBatch(&batch
);
616 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteRegistration(
617 int64 registration_id
,
619 RegistrationData
* deleted_version
,
620 std::vector
<int64
>* newly_purgeable_resources
) {
621 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
622 DCHECK(deleted_version
);
623 deleted_version
->version_id
= kInvalidServiceWorkerVersionId
;
624 Status status
= LazyOpen(false);
625 if (IsNewOrNonexistentDatabase(status
))
627 if (status
!= STATUS_OK
)
629 if (!origin
.is_valid())
630 return STATUS_ERROR_FAILED
;
632 leveldb::WriteBatch batch
;
634 // Remove |origin| from unique origins if a registration specified by
635 // |registration_id| is the only one for |origin|.
636 // TODO(nhiroki): Check the uniqueness by more efficient way.
637 std::vector
<RegistrationData
> registrations
;
638 status
= GetRegistrationsForOrigin(origin
, ®istrations
);
639 if (status
!= STATUS_OK
)
642 if (registrations
.size() == 1 &&
643 registrations
[0].registration_id
== registration_id
) {
644 batch
.Delete(CreateUniqueOriginKey(origin
));
647 // Delete a registration specified by |registration_id|.
648 batch
.Delete(CreateRegistrationKey(registration_id
, origin
));
650 // Delete resource records associated with the registration.
651 for (const auto& registration
: registrations
) {
652 if (registration
.registration_id
== registration_id
) {
653 *deleted_version
= registration
;
654 status
= DeleteResourceRecords(
655 registration
.version_id
, newly_purgeable_resources
, &batch
);
656 if (status
!= STATUS_OK
)
662 return WriteBatch(&batch
);
665 ServiceWorkerDatabase::Status
666 ServiceWorkerDatabase::GetUncommittedResourceIds(std::set
<int64
>* ids
) {
667 return ReadResourceIds(kUncommittedResIdKeyPrefix
, ids
);
670 ServiceWorkerDatabase::Status
671 ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set
<int64
>& ids
) {
672 return WriteResourceIds(kUncommittedResIdKeyPrefix
, ids
);
675 ServiceWorkerDatabase::Status
676 ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set
<int64
>& ids
) {
677 return DeleteResourceIds(kUncommittedResIdKeyPrefix
, ids
);
680 ServiceWorkerDatabase::Status
681 ServiceWorkerDatabase::GetPurgeableResourceIds(std::set
<int64
>* ids
) {
682 return ReadResourceIds(kPurgeableResIdKeyPrefix
, ids
);
685 ServiceWorkerDatabase::Status
686 ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set
<int64
>& ids
) {
687 return WriteResourceIds(kPurgeableResIdKeyPrefix
, ids
);
690 ServiceWorkerDatabase::Status
691 ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set
<int64
>& ids
) {
692 return DeleteResourceIds(kPurgeableResIdKeyPrefix
, ids
);
695 ServiceWorkerDatabase::Status
696 ServiceWorkerDatabase::PurgeUncommittedResourceIds(
697 const std::set
<int64
>& ids
) {
698 leveldb::WriteBatch batch
;
699 Status status
= DeleteResourceIdsInBatch(
700 kUncommittedResIdKeyPrefix
, ids
, &batch
);
701 if (status
!= STATUS_OK
)
703 status
= WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix
, ids
, &batch
);
704 if (status
!= STATUS_OK
)
706 return WriteBatch(&batch
);
709 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteAllDataForOrigins(
710 const std::set
<GURL
>& origins
,
711 std::vector
<int64
>* newly_purgeable_resources
) {
712 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
713 Status status
= LazyOpen(false);
714 if (IsNewOrNonexistentDatabase(status
))
716 if (status
!= STATUS_OK
)
718 leveldb::WriteBatch batch
;
720 for (const GURL
& origin
: origins
) {
721 if (!origin
.is_valid())
722 return STATUS_ERROR_FAILED
;
724 // Delete from the unique origin list.
725 batch
.Delete(CreateUniqueOriginKey(origin
));
727 std::vector
<RegistrationData
> registrations
;
728 status
= GetRegistrationsForOrigin(origin
, ®istrations
);
729 if (status
!= STATUS_OK
)
732 // Delete registrations and resource records.
733 for (const RegistrationData
& data
: registrations
) {
734 batch
.Delete(CreateRegistrationKey(data
.registration_id
, origin
));
735 status
= DeleteResourceRecords(
736 data
.version_id
, newly_purgeable_resources
, &batch
);
737 if (status
!= STATUS_OK
)
742 return WriteBatch(&batch
);
745 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DestroyDatabase() {
746 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
747 Disable(FROM_HERE
, STATUS_OK
);
749 leveldb::Options options
;
752 options
.env
= env_
.get();
754 // In-memory database not initialized.
759 return LevelDBStatusToStatus(
760 leveldb::DestroyDB(path_
.AsUTF8Unsafe(), options
));
763 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::LazyOpen(
764 bool create_if_missing
) {
765 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
767 // Do not try to open a database if we tried and failed once.
768 if (state_
== DISABLED
)
769 return STATUS_ERROR_FAILED
;
773 // When |path_| is empty, open a database in-memory.
774 bool use_in_memory_db
= path_
.empty();
776 if (!create_if_missing
) {
777 // Avoid opening a database if it does not exist at the |path_|.
778 if (use_in_memory_db
||
779 !base::PathExists(path_
) ||
780 base::IsDirectoryEmpty(path_
)) {
781 return STATUS_ERROR_NOT_FOUND
;
785 leveldb::Options options
;
786 options
.create_if_missing
= create_if_missing
;
787 if (use_in_memory_db
) {
788 env_
.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
789 options
.env
= env_
.get();
792 leveldb::DB
* db
= NULL
;
793 Status status
= LevelDBStatusToStatus(
794 leveldb::DB::Open(options
, path_
.AsUTF8Unsafe(), &db
));
795 HandleOpenResult(FROM_HERE
, status
);
796 if (status
!= STATUS_OK
) {
798 // TODO(nhiroki): Should we retry to open the database?
804 status
= ReadDatabaseVersion(&db_version
);
805 if (status
!= STATUS_OK
)
807 DCHECK_LE(0, db_version
);
809 state_
= INITIALIZED
;
813 bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase(
814 ServiceWorkerDatabase::Status status
) {
815 if (status
== STATUS_ERROR_NOT_FOUND
)
817 if (status
== STATUS_OK
&& state_
== UNINITIALIZED
)
822 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadNextAvailableId(
824 int64
* next_avail_id
) {
826 DCHECK(next_avail_id
);
829 Status status
= LevelDBStatusToStatus(
830 db_
->Get(leveldb::ReadOptions(), id_key
, &value
));
831 if (status
== STATUS_ERROR_NOT_FOUND
) {
832 // Nobody has gotten the next resource id for |id_key|.
834 HandleReadResult(FROM_HERE
, STATUS_OK
);
836 } else if (status
!= STATUS_OK
) {
837 HandleReadResult(FROM_HERE
, status
);
841 status
= ParseId(value
, next_avail_id
);
842 HandleReadResult(FROM_HERE
, status
);
846 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadRegistrationData(
847 int64 registration_id
,
849 RegistrationData
* registration
) {
850 DCHECK(registration
);
852 const std::string key
= CreateRegistrationKey(registration_id
, origin
);
854 Status status
= LevelDBStatusToStatus(
855 db_
->Get(leveldb::ReadOptions(), key
, &value
));
856 if (status
!= STATUS_OK
) {
859 status
== STATUS_ERROR_NOT_FOUND
? STATUS_OK
: status
);
863 status
= ParseRegistrationData(value
, registration
);
864 HandleReadResult(FROM_HERE
, status
);
868 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadResourceRecords(
870 std::vector
<ResourceRecord
>* resources
) {
871 DCHECK(resources
->empty());
873 Status status
= STATUS_OK
;
874 const std::string prefix
= CreateResourceRecordKeyPrefix(version_id
);
876 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
877 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
878 Status status
= LevelDBStatusToStatus(itr
->status());
879 if (status
!= STATUS_OK
) {
880 HandleReadResult(FROM_HERE
, status
);
885 if (!RemovePrefix(itr
->key().ToString(), prefix
, NULL
))
888 ResourceRecord resource
;
889 status
= ParseResourceRecord(itr
->value().ToString(), &resource
);
890 if (status
!= STATUS_OK
) {
891 HandleReadResult(FROM_HERE
, status
);
895 resources
->push_back(resource
);
898 HandleReadResult(FROM_HERE
, status
);
902 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceRecords(
904 std::vector
<int64
>* newly_purgeable_resources
,
905 leveldb::WriteBatch
* batch
) {
908 Status status
= STATUS_OK
;
909 const std::string prefix
= CreateResourceRecordKeyPrefix(version_id
);
911 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
912 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
913 status
= LevelDBStatusToStatus(itr
->status());
914 if (status
!= STATUS_OK
) {
915 HandleReadResult(FROM_HERE
, status
);
919 const std::string key
= itr
->key().ToString();
920 std::string unprefixed
;
921 if (!RemovePrefix(key
, prefix
, &unprefixed
))
925 status
= ParseId(unprefixed
, &resource_id
);
926 if (status
!= STATUS_OK
) {
927 HandleReadResult(FROM_HERE
, status
);
931 // Remove a resource record.
934 // Currently resource sharing across versions and registrations is not
935 // supported, so we can purge this without caring about it.
936 PutPurgeableResourceIdToBatch(resource_id
, batch
);
937 newly_purgeable_resources
->push_back(resource_id
);
940 HandleReadResult(FROM_HERE
, status
);
944 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadResourceIds(
945 const char* id_key_prefix
,
946 std::set
<int64
>* ids
) {
947 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
948 DCHECK(id_key_prefix
);
949 DCHECK(ids
->empty());
951 Status status
= LazyOpen(false);
952 if (IsNewOrNonexistentDatabase(status
))
954 if (status
!= STATUS_OK
)
957 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
958 for (itr
->Seek(id_key_prefix
); itr
->Valid(); itr
->Next()) {
959 status
= LevelDBStatusToStatus(itr
->status());
960 if (status
!= STATUS_OK
) {
961 HandleReadResult(FROM_HERE
, status
);
966 std::string unprefixed
;
967 if (!RemovePrefix(itr
->key().ToString(), id_key_prefix
, &unprefixed
))
971 status
= ParseId(unprefixed
, &resource_id
);
972 if (status
!= STATUS_OK
) {
973 HandleReadResult(FROM_HERE
, status
);
977 ids
->insert(resource_id
);
980 HandleReadResult(FROM_HERE
, status
);
984 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteResourceIds(
985 const char* id_key_prefix
,
986 const std::set
<int64
>& ids
) {
987 leveldb::WriteBatch batch
;
988 Status status
= WriteResourceIdsInBatch(id_key_prefix
, ids
, &batch
);
989 if (status
!= STATUS_OK
)
991 return WriteBatch(&batch
);
994 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteResourceIdsInBatch(
995 const char* id_key_prefix
,
996 const std::set
<int64
>& ids
,
997 leveldb::WriteBatch
* batch
) {
998 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
999 DCHECK(id_key_prefix
);
1001 Status status
= LazyOpen(true);
1002 if (status
!= STATUS_OK
)
1007 for (std::set
<int64
>::const_iterator itr
= ids
.begin();
1008 itr
!= ids
.end(); ++itr
) {
1009 // Value should be empty.
1010 batch
->Put(CreateResourceIdKey(id_key_prefix
, *itr
), "");
1012 // std::set is sorted, so the last element is the largest.
1013 BumpNextResourceIdIfNeeded(*ids
.rbegin(), batch
);
1017 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceIds(
1018 const char* id_key_prefix
,
1019 const std::set
<int64
>& ids
) {
1020 leveldb::WriteBatch batch
;
1021 Status status
= DeleteResourceIdsInBatch(id_key_prefix
, ids
, &batch
);
1022 if (status
!= STATUS_OK
)
1024 return WriteBatch(&batch
);
1027 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceIdsInBatch(
1028 const char* id_key_prefix
,
1029 const std::set
<int64
>& ids
,
1030 leveldb::WriteBatch
* batch
) {
1031 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
1032 DCHECK(id_key_prefix
);
1034 Status status
= LazyOpen(false);
1035 if (IsNewOrNonexistentDatabase(status
))
1037 if (status
!= STATUS_OK
)
1040 for (std::set
<int64
>::const_iterator itr
= ids
.begin();
1041 itr
!= ids
.end(); ++itr
) {
1042 batch
->Delete(CreateResourceIdKey(id_key_prefix
, *itr
));
1047 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadDatabaseVersion(
1048 int64
* db_version
) {
1050 Status status
= LevelDBStatusToStatus(
1051 db_
->Get(leveldb::ReadOptions(), kDatabaseVersionKey
, &value
));
1052 if (status
== STATUS_ERROR_NOT_FOUND
) {
1053 // The database hasn't been initialized yet.
1055 HandleReadResult(FROM_HERE
, STATUS_OK
);
1059 if (status
!= STATUS_OK
) {
1060 HandleReadResult(FROM_HERE
, status
);
1064 status
= ParseDatabaseVersion(value
, db_version
);
1065 HandleReadResult(FROM_HERE
, status
);
1069 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteBatch(
1070 leveldb::WriteBatch
* batch
) {
1072 DCHECK_NE(DISABLED
, state_
);
1074 if (state_
== UNINITIALIZED
) {
1075 // Write the database schema version.
1076 batch
->Put(kDatabaseVersionKey
, base::Int64ToString(kCurrentSchemaVersion
));
1077 state_
= INITIALIZED
;
1080 Status status
= LevelDBStatusToStatus(
1081 db_
->Write(leveldb::WriteOptions(), batch
));
1082 HandleWriteResult(FROM_HERE
, status
);
1086 void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded(
1087 int64 used_id
, leveldb::WriteBatch
* batch
) {
1089 if (next_avail_registration_id_
<= used_id
) {
1090 next_avail_registration_id_
= used_id
+ 1;
1091 batch
->Put(kNextRegIdKey
, base::Int64ToString(next_avail_registration_id_
));
1095 void ServiceWorkerDatabase::BumpNextResourceIdIfNeeded(
1096 int64 used_id
, leveldb::WriteBatch
* batch
) {
1098 if (next_avail_resource_id_
<= used_id
) {
1099 next_avail_resource_id_
= used_id
+ 1;
1100 batch
->Put(kNextResIdKey
, base::Int64ToString(next_avail_resource_id_
));
1104 void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded(
1105 int64 used_id
, leveldb::WriteBatch
* batch
) {
1107 if (next_avail_version_id_
<= used_id
) {
1108 next_avail_version_id_
= used_id
+ 1;
1109 batch
->Put(kNextVerIdKey
, base::Int64ToString(next_avail_version_id_
));
1113 bool ServiceWorkerDatabase::IsOpen() {
1117 void ServiceWorkerDatabase::Disable(
1118 const tracked_objects::Location
& from_here
,
1120 if (status
!= STATUS_OK
) {
1121 DLOG(ERROR
) << "Failed at: " << from_here
.ToString()
1122 << " with error: " << StatusToString(status
);
1123 DLOG(ERROR
) << "ServiceWorkerDatabase is disabled.";
1129 void ServiceWorkerDatabase::HandleOpenResult(
1130 const tracked_objects::Location
& from_here
,
1132 if (status
!= STATUS_OK
)
1133 Disable(from_here
, status
);
1134 ServiceWorkerMetrics::CountOpenDatabaseResult(status
);
1137 void ServiceWorkerDatabase::HandleReadResult(
1138 const tracked_objects::Location
& from_here
,
1140 if (status
!= STATUS_OK
)
1141 Disable(from_here
, status
);
1142 ServiceWorkerMetrics::CountReadDatabaseResult(status
);
1145 void ServiceWorkerDatabase::HandleWriteResult(
1146 const tracked_objects::Location
& from_here
,
1148 if (status
!= STATUS_OK
)
1149 Disable(from_here
, status
);
1150 ServiceWorkerMetrics::CountWriteDatabaseResult(status
);
1153 } // namespace content