1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/service_worker/service_worker_database.h"
7 #include "base/files/file_util.h"
8 #include "base/location.h"
9 #include "base/logging.h"
10 #include "base/metrics/histogram.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "content/browser/service_worker/service_worker_database.pb.h"
17 #include "content/browser/service_worker/service_worker_metrics.h"
18 #include "content/common/service_worker/service_worker_types.h"
19 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
20 #include "third_party/leveldatabase/src/include/leveldb/db.h"
21 #include "third_party/leveldatabase/src/include/leveldb/env.h"
22 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
24 // LevelDB database schema
25 // =======================
28 // - int64 value is serialized as a string by base::Int64ToString().
29 // - GURL value is serialized as a string by GURL::spec().
31 // Version 1 (in sorted order)
32 // key: "INITDATA_DB_VERSION"
35 // key: "INITDATA_NEXT_REGISTRATION_ID"
36 // value: <int64 'next_available_registration_id'>
38 // key: "INITDATA_NEXT_RESOURCE_ID"
39 // value: <int64 'next_available_resource_id'>
41 // key: "INITDATA_NEXT_VERSION_ID"
42 // value: <int64 'next_available_version_id'>
44 // key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin'>
47 // key: "PRES:" + <int64 'purgeable_resource_id'>
50 // key: "REG:" + <GURL 'origin'> + '\x00' + <int64 'registration_id'>
51 // (ex. "REG:http://example.com\x00123456")
52 // value: <ServiceWorkerRegistrationData serialized as a string>
54 // key: "REG_HAS_USER_DATA:" + <std::string 'user_data_name'> + '\x00'
55 // + <int64 'registration_id'>
58 // key: "REG_USER_DATA:" + <int64 'registration_id'> + '\x00'
59 // + <std::string user_data_name>
60 // (ex. "REG_USER_DATA:123456\x00foo_bar")
61 // value: <std::string user_data>
63 // key: "RES:" + <int64 'version_id'> + '\x00' + <int64 'resource_id'>
64 // (ex. "RES:123456\x00654321")
65 // value: <ServiceWorkerResourceRecord serialized as a string>
67 // key: "URES:" + <int64 'uncommitted_resource_id'>
74 const char kDatabaseVersionKey
[] = "INITDATA_DB_VERSION";
75 const char kNextRegIdKey
[] = "INITDATA_NEXT_REGISTRATION_ID";
76 const char kNextResIdKey
[] = "INITDATA_NEXT_RESOURCE_ID";
77 const char kNextVerIdKey
[] = "INITDATA_NEXT_VERSION_ID";
78 const char kUniqueOriginKey
[] = "INITDATA_UNIQUE_ORIGIN:";
80 const char kRegKeyPrefix
[] = "REG:";
81 const char kRegUserDataKeyPrefix
[] = "REG_USER_DATA:";
82 const char kRegHasUserDataKeyPrefix
[] = "REG_HAS_USER_DATA:";
83 const char kResKeyPrefix
[] = "RES:";
84 const char kKeySeparator
= '\x00';
86 const char kUncommittedResIdKeyPrefix
[] = "URES:";
87 const char kPurgeableResIdKeyPrefix
[] = "PRES:";
89 const int64 kCurrentSchemaVersion
= 1;
91 bool RemovePrefix(const std::string
& str
,
92 const std::string
& prefix
,
94 if (!StartsWithASCII(str
, prefix
, true))
97 *out
= str
.substr(prefix
.size());
101 std::string
CreateRegistrationKey(int64 registration_id
,
102 const GURL
& origin
) {
103 return base::StringPrintf("%s%s%c%s",
105 origin
.spec().c_str(),
107 base::Int64ToString(registration_id
).c_str());
110 std::string
CreateResourceRecordKeyPrefix(int64 version_id
) {
111 return base::StringPrintf("%s%s%c",
113 base::Int64ToString(version_id
).c_str(),
117 std::string
CreateResourceRecordKey(int64 version_id
,
119 return CreateResourceRecordKeyPrefix(version_id
).append(
120 base::Int64ToString(resource_id
));
123 std::string
CreateUniqueOriginKey(const GURL
& origin
) {
124 return base::StringPrintf("%s%s", kUniqueOriginKey
, origin
.spec().c_str());
127 std::string
CreateResourceIdKey(const char* key_prefix
, int64 resource_id
) {
128 return base::StringPrintf(
129 "%s%s", key_prefix
, base::Int64ToString(resource_id
).c_str());
132 std::string
CreateUserDataKeyPrefix(int64 registration_id
) {
133 return base::StringPrintf("%s%s%c",
134 kRegUserDataKeyPrefix
,
135 base::Int64ToString(registration_id
).c_str(),
139 std::string
CreateUserDataKey(int64 registration_id
,
140 const std::string
& user_data_name
) {
141 return CreateUserDataKeyPrefix(registration_id
).append(user_data_name
);
144 std::string
CreateHasUserDataKeyPrefix(const std::string
& user_data_name
) {
145 return base::StringPrintf("%s%s%c", kRegHasUserDataKeyPrefix
,
146 user_data_name
.c_str(), kKeySeparator
);
149 std::string
CreateHasUserDataKey(int64 registration_id
,
150 const std::string
& user_data_name
) {
151 return CreateHasUserDataKeyPrefix(user_data_name
)
152 .append(base::Int64ToString(registration_id
));
155 void PutRegistrationDataToBatch(
156 const ServiceWorkerDatabase::RegistrationData
& input
,
157 leveldb::WriteBatch
* batch
) {
160 // Convert RegistrationData to ServiceWorkerRegistrationData.
161 ServiceWorkerRegistrationData data
;
162 data
.set_registration_id(input
.registration_id
);
163 data
.set_scope_url(input
.scope
.spec());
164 data
.set_script_url(input
.script
.spec());
165 data
.set_version_id(input
.version_id
);
166 data
.set_is_active(input
.is_active
);
167 data
.set_has_fetch_handler(input
.has_fetch_handler
);
168 data
.set_last_update_check_time(input
.last_update_check
.ToInternalValue());
169 data
.set_resources_total_size_bytes(input
.resources_total_size_bytes
);
172 bool success
= data
.SerializeToString(&value
);
174 GURL origin
= input
.scope
.GetOrigin();
175 batch
->Put(CreateRegistrationKey(data
.registration_id(), origin
), value
);
178 void PutResourceRecordToBatch(
179 const ServiceWorkerDatabase::ResourceRecord
& input
,
181 leveldb::WriteBatch
* batch
) {
183 DCHECK_GE(input
.size_bytes
, 0);
185 // Convert ResourceRecord to ServiceWorkerResourceRecord.
186 ServiceWorkerResourceRecord record
;
187 record
.set_resource_id(input
.resource_id
);
188 record
.set_url(input
.url
.spec());
189 record
.set_size_bytes(input
.size_bytes
);
192 bool success
= record
.SerializeToString(&value
);
194 batch
->Put(CreateResourceRecordKey(version_id
, input
.resource_id
), value
);
197 void PutUniqueOriginToBatch(const GURL
& origin
,
198 leveldb::WriteBatch
* batch
) {
199 // Value should be empty.
200 batch
->Put(CreateUniqueOriginKey(origin
), "");
203 void PutPurgeableResourceIdToBatch(int64 resource_id
,
204 leveldb::WriteBatch
* batch
) {
205 // Value should be empty.
206 batch
->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix
, resource_id
), "");
209 ServiceWorkerDatabase::Status
ParseId(
210 const std::string
& serialized
,
214 if (!base::StringToInt64(serialized
, &id
) || id
< 0)
215 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
217 return ServiceWorkerDatabase::STATUS_OK
;
220 ServiceWorkerDatabase::Status
ParseDatabaseVersion(
221 const std::string
& serialized
,
224 const int kFirstValidVersion
= 1;
226 if (!base::StringToInt64(serialized
, &version
) ||
227 version
< kFirstValidVersion
) {
228 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
230 if (kCurrentSchemaVersion
< version
) {
231 DLOG(ERROR
) << "ServiceWorkerDatabase has newer schema version"
232 << " than the current latest version: "
233 << version
<< " vs " << kCurrentSchemaVersion
;
234 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
237 return ServiceWorkerDatabase::STATUS_OK
;
240 ServiceWorkerDatabase::Status
ParseRegistrationData(
241 const std::string
& serialized
,
242 ServiceWorkerDatabase::RegistrationData
* out
) {
244 ServiceWorkerRegistrationData data
;
245 if (!data
.ParseFromString(serialized
))
246 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
248 GURL
scope_url(data
.scope_url());
249 GURL
script_url(data
.script_url());
250 if (!scope_url
.is_valid() ||
251 !script_url
.is_valid() ||
252 scope_url
.GetOrigin() != script_url
.GetOrigin()) {
253 DLOG(ERROR
) << "Scope URL '" << data
.scope_url() << "' and/or script url '"
255 << "' are invalid or have mismatching origins.";
256 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
259 // Convert ServiceWorkerRegistrationData to RegistrationData.
260 out
->registration_id
= data
.registration_id();
261 out
->scope
= scope_url
;
262 out
->script
= script_url
;
263 out
->version_id
= data
.version_id();
264 out
->is_active
= data
.is_active();
265 out
->has_fetch_handler
= data
.has_fetch_handler();
266 out
->last_update_check
=
267 base::Time::FromInternalValue(data
.last_update_check_time());
268 out
->resources_total_size_bytes
= data
.resources_total_size_bytes();
270 return ServiceWorkerDatabase::STATUS_OK
;
273 ServiceWorkerDatabase::Status
ParseResourceRecord(
274 const std::string
& serialized
,
275 ServiceWorkerDatabase::ResourceRecord
* out
) {
277 ServiceWorkerResourceRecord record
;
278 if (!record
.ParseFromString(serialized
))
279 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
281 GURL
url(record
.url());
283 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
285 // Convert ServiceWorkerResourceRecord to ResourceRecord.
286 out
->resource_id
= record
.resource_id();
288 out
->size_bytes
= record
.size_bytes();
289 return ServiceWorkerDatabase::STATUS_OK
;
292 ServiceWorkerDatabase::Status
LevelDBStatusToStatus(
293 const leveldb::Status
& status
) {
295 return ServiceWorkerDatabase::STATUS_OK
;
296 else if (status
.IsNotFound())
297 return ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND
;
298 else if (status
.IsIOError())
299 return ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR
;
300 else if (status
.IsCorruption())
301 return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
;
303 return ServiceWorkerDatabase::STATUS_ERROR_FAILED
;
308 const char* ServiceWorkerDatabase::StatusToString(
309 ServiceWorkerDatabase::Status status
) {
311 case ServiceWorkerDatabase::STATUS_OK
:
312 return "Database OK";
313 case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND
:
314 return "Database not found";
315 case ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR
:
316 return "Database IO error";
317 case ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED
:
318 return "Database corrupted";
319 case ServiceWorkerDatabase::STATUS_ERROR_FAILED
:
320 return "Database operation failed";
321 case ServiceWorkerDatabase::STATUS_ERROR_MAX
:
323 return "Database unknown error";
326 return "Database unknown error";
329 ServiceWorkerDatabase::RegistrationData::RegistrationData()
330 : registration_id(kInvalidServiceWorkerRegistrationId
),
331 version_id(kInvalidServiceWorkerVersionId
),
333 has_fetch_handler(false),
334 resources_total_size_bytes(0) {
337 ServiceWorkerDatabase::RegistrationData::~RegistrationData() {
340 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath
& path
)
342 next_avail_registration_id_(0),
343 next_avail_resource_id_(0),
344 next_avail_version_id_(0),
345 state_(UNINITIALIZED
) {
346 sequence_checker_
.DetachFromSequence();
349 ServiceWorkerDatabase::~ServiceWorkerDatabase() {
350 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
354 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetNextAvailableIds(
355 int64
* next_avail_registration_id
,
356 int64
* next_avail_version_id
,
357 int64
* next_avail_resource_id
) {
358 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
359 DCHECK(next_avail_registration_id
);
360 DCHECK(next_avail_version_id
);
361 DCHECK(next_avail_resource_id
);
363 Status status
= LazyOpen(false);
364 if (IsNewOrNonexistentDatabase(status
)) {
365 *next_avail_registration_id
= 0;
366 *next_avail_version_id
= 0;
367 *next_avail_resource_id
= 0;
370 if (status
!= STATUS_OK
)
373 status
= ReadNextAvailableId(kNextRegIdKey
, &next_avail_registration_id_
);
374 if (status
!= STATUS_OK
)
376 status
= ReadNextAvailableId(kNextVerIdKey
, &next_avail_version_id_
);
377 if (status
!= STATUS_OK
)
379 status
= ReadNextAvailableId(kNextResIdKey
, &next_avail_resource_id_
);
380 if (status
!= STATUS_OK
)
383 *next_avail_registration_id
= next_avail_registration_id_
;
384 *next_avail_version_id
= next_avail_version_id_
;
385 *next_avail_resource_id
= next_avail_resource_id_
;
389 ServiceWorkerDatabase::Status
390 ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set
<GURL
>* origins
) {
391 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
392 DCHECK(origins
->empty());
394 Status status
= LazyOpen(false);
395 if (IsNewOrNonexistentDatabase(status
))
397 if (status
!= STATUS_OK
)
400 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
401 for (itr
->Seek(kUniqueOriginKey
); itr
->Valid(); itr
->Next()) {
402 status
= LevelDBStatusToStatus(itr
->status());
403 if (status
!= STATUS_OK
) {
404 HandleReadResult(FROM_HERE
, status
);
410 if (!RemovePrefix(itr
->key().ToString(), kUniqueOriginKey
, &origin
))
412 origins
->insert(GURL(origin
));
415 HandleReadResult(FROM_HERE
, status
);
419 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetRegistrationsForOrigin(
421 std::vector
<RegistrationData
>* registrations
) {
422 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
423 DCHECK(registrations
->empty());
425 Status status
= LazyOpen(false);
426 if (IsNewOrNonexistentDatabase(status
))
428 if (status
!= STATUS_OK
)
431 // Create a key prefix for registrations.
432 std::string prefix
= base::StringPrintf(
433 "%s%s%c", kRegKeyPrefix
, origin
.spec().c_str(), kKeySeparator
);
435 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
436 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
437 status
= LevelDBStatusToStatus(itr
->status());
438 if (status
!= STATUS_OK
) {
439 HandleReadResult(FROM_HERE
, status
);
440 registrations
->clear();
444 if (!RemovePrefix(itr
->key().ToString(), prefix
, NULL
))
447 RegistrationData registration
;
448 status
= ParseRegistrationData(itr
->value().ToString(), ®istration
);
449 if (status
!= STATUS_OK
) {
450 HandleReadResult(FROM_HERE
, status
);
451 registrations
->clear();
454 registrations
->push_back(registration
);
457 HandleReadResult(FROM_HERE
, status
);
461 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::GetAllRegistrations(
462 std::vector
<RegistrationData
>* registrations
) {
463 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
464 DCHECK(registrations
->empty());
466 Status status
= LazyOpen(false);
467 if (IsNewOrNonexistentDatabase(status
))
469 if (status
!= STATUS_OK
)
472 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
473 for (itr
->Seek(kRegKeyPrefix
); itr
->Valid(); itr
->Next()) {
474 status
= LevelDBStatusToStatus(itr
->status());
475 if (status
!= STATUS_OK
) {
476 HandleReadResult(FROM_HERE
, status
);
477 registrations
->clear();
481 if (!RemovePrefix(itr
->key().ToString(), kRegKeyPrefix
, NULL
))
484 RegistrationData registration
;
485 status
= ParseRegistrationData(itr
->value().ToString(), ®istration
);
486 if (status
!= STATUS_OK
) {
487 HandleReadResult(FROM_HERE
, status
);
488 registrations
->clear();
491 registrations
->push_back(registration
);
494 HandleReadResult(FROM_HERE
, status
);
498 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadRegistration(
499 int64 registration_id
,
501 RegistrationData
* registration
,
502 std::vector
<ResourceRecord
>* resources
) {
503 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
504 DCHECK(registration
);
507 Status status
= LazyOpen(false);
508 if (IsNewOrNonexistentDatabase(status
) || status
!= STATUS_OK
)
511 RegistrationData value
;
512 status
= ReadRegistrationData(registration_id
, origin
, &value
);
513 if (status
!= STATUS_OK
)
516 status
= ReadResourceRecords(value
.version_id
, resources
);
517 if (status
!= STATUS_OK
)
520 *registration
= value
;
524 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteRegistration(
525 const RegistrationData
& registration
,
526 const std::vector
<ResourceRecord
>& resources
,
527 RegistrationData
* old_registration
,
528 std::vector
<int64
>* newly_purgeable_resources
) {
529 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
530 DCHECK(old_registration
);
531 Status status
= LazyOpen(true);
532 old_registration
->version_id
= kInvalidServiceWorkerVersionId
;
533 if (status
!= STATUS_OK
)
536 leveldb::WriteBatch batch
;
537 BumpNextRegistrationIdIfNeeded(registration
.registration_id
, &batch
);
538 BumpNextVersionIdIfNeeded(registration
.version_id
, &batch
);
540 PutUniqueOriginToBatch(registration
.scope
.GetOrigin(), &batch
);
542 int64_t total_size_bytes
= 0;
543 for (const auto& resource
: resources
) {
544 total_size_bytes
+= resource
.size_bytes
;
546 DCHECK_EQ(total_size_bytes
, registration
.resources_total_size_bytes
)
547 << "The total size in the registration must match the cumulative "
548 << "sizes of the resources.";
550 PutRegistrationDataToBatch(registration
, &batch
);
552 // Used for avoiding multiple writes for the same resource id or url.
553 std::set
<int64
> pushed_resources
;
554 std::set
<GURL
> pushed_urls
;
555 for (std::vector
<ResourceRecord
>::const_iterator itr
= resources
.begin();
556 itr
!= resources
.end(); ++itr
) {
557 if (!itr
->url
.is_valid())
558 return STATUS_ERROR_FAILED
;
560 // Duplicated resource id or url should not exist.
561 DCHECK(pushed_resources
.insert(itr
->resource_id
).second
);
562 DCHECK(pushed_urls
.insert(itr
->url
).second
);
564 PutResourceRecordToBatch(*itr
, registration
.version_id
, &batch
);
566 // Delete a resource from the uncommitted list.
567 batch
.Delete(CreateResourceIdKey(
568 kUncommittedResIdKeyPrefix
, itr
->resource_id
));
569 // Delete from the purgeable list in case this version was once deleted.
571 CreateResourceIdKey(kPurgeableResIdKeyPrefix
, itr
->resource_id
));
574 // Retrieve a previous version to sweep purgeable resources.
575 status
= ReadRegistrationData(registration
.registration_id
,
576 registration
.scope
.GetOrigin(),
578 if (status
!= STATUS_OK
&& status
!= STATUS_ERROR_NOT_FOUND
)
580 if (status
== STATUS_OK
) {
581 DCHECK_LT(old_registration
->version_id
, registration
.version_id
);
582 status
= DeleteResourceRecords(
583 old_registration
->version_id
, newly_purgeable_resources
, &batch
);
584 if (status
!= STATUS_OK
)
587 // Currently resource sharing across versions and registrations is not
588 // supported, so resource ids should not be overlapped between
589 // |registration| and |old_registration|.
590 std::set
<int64
> deleted_resources(newly_purgeable_resources
->begin(),
591 newly_purgeable_resources
->end());
592 DCHECK(base::STLSetIntersection
<std::set
<int64
> >(
593 pushed_resources
, deleted_resources
).empty());
596 return WriteBatch(&batch
);
599 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::UpdateVersionToActive(
600 int64 registration_id
,
601 const GURL
& origin
) {
602 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
603 Status status
= LazyOpen(false);
604 if (IsNewOrNonexistentDatabase(status
))
605 return STATUS_ERROR_NOT_FOUND
;
606 if (status
!= STATUS_OK
)
608 if (!origin
.is_valid())
609 return STATUS_ERROR_FAILED
;
611 RegistrationData registration
;
612 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
613 if (status
!= STATUS_OK
)
616 registration
.is_active
= true;
618 leveldb::WriteBatch batch
;
619 PutRegistrationDataToBatch(registration
, &batch
);
620 return WriteBatch(&batch
);
623 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::UpdateLastCheckTime(
624 int64 registration_id
,
626 const base::Time
& time
) {
627 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
628 Status status
= LazyOpen(false);
629 if (IsNewOrNonexistentDatabase(status
))
630 return STATUS_ERROR_NOT_FOUND
;
631 if (status
!= STATUS_OK
)
633 if (!origin
.is_valid())
634 return STATUS_ERROR_FAILED
;
636 RegistrationData registration
;
637 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
638 if (status
!= STATUS_OK
)
641 registration
.last_update_check
= time
;
643 leveldb::WriteBatch batch
;
644 PutRegistrationDataToBatch(registration
, &batch
);
645 return WriteBatch(&batch
);
648 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteRegistration(
649 int64 registration_id
,
651 RegistrationData
* deleted_version
,
652 std::vector
<int64
>* newly_purgeable_resources
) {
653 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
654 DCHECK(deleted_version
);
655 deleted_version
->version_id
= kInvalidServiceWorkerVersionId
;
656 Status status
= LazyOpen(false);
657 if (IsNewOrNonexistentDatabase(status
))
659 if (status
!= STATUS_OK
)
661 if (!origin
.is_valid())
662 return STATUS_ERROR_FAILED
;
664 leveldb::WriteBatch batch
;
666 // Remove |origin| from unique origins if a registration specified by
667 // |registration_id| is the only one for |origin|.
668 // TODO(nhiroki): Check the uniqueness by more efficient way.
669 std::vector
<RegistrationData
> registrations
;
670 status
= GetRegistrationsForOrigin(origin
, ®istrations
);
671 if (status
!= STATUS_OK
)
674 if (registrations
.size() == 1 &&
675 registrations
[0].registration_id
== registration_id
) {
676 batch
.Delete(CreateUniqueOriginKey(origin
));
679 // Delete a registration specified by |registration_id|.
680 batch
.Delete(CreateRegistrationKey(registration_id
, origin
));
682 // Delete resource records and user data associated with the registration.
683 for (const auto& registration
: registrations
) {
684 if (registration
.registration_id
== registration_id
) {
685 *deleted_version
= registration
;
686 status
= DeleteResourceRecords(
687 registration
.version_id
, newly_purgeable_resources
, &batch
);
688 if (status
!= STATUS_OK
)
691 status
= DeleteUserDataForRegistration(registration_id
, &batch
);
692 if (status
!= STATUS_OK
)
698 return WriteBatch(&batch
);
701 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadUserData(
702 int64 registration_id
,
703 const std::string
& user_data_name
,
704 std::string
* user_data
) {
705 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
706 DCHECK_NE(kInvalidServiceWorkerRegistrationId
, registration_id
);
707 DCHECK(!user_data_name
.empty());
710 Status status
= LazyOpen(false);
711 if (IsNewOrNonexistentDatabase(status
) || status
!= STATUS_OK
)
714 const std::string key
= CreateUserDataKey(registration_id
, user_data_name
);
715 status
= LevelDBStatusToStatus(
716 db_
->Get(leveldb::ReadOptions(), key
, user_data
));
717 HandleReadResult(FROM_HERE
,
718 status
== STATUS_ERROR_NOT_FOUND
? STATUS_OK
: status
);
722 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteUserData(
723 int64 registration_id
,
725 const std::string
& user_data_name
,
726 const std::string
& user_data
) {
727 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
728 DCHECK_NE(kInvalidServiceWorkerRegistrationId
, registration_id
);
729 DCHECK(!user_data_name
.empty());
731 Status status
= LazyOpen(false);
732 if (IsNewOrNonexistentDatabase(status
) || status
!= STATUS_OK
)
735 // There should be the registration specified by |registration_id|.
736 RegistrationData registration
;
737 status
= ReadRegistrationData(registration_id
, origin
, ®istration
);
738 if (status
!= STATUS_OK
)
741 leveldb::WriteBatch batch
;
742 batch
.Put(CreateUserDataKey(registration_id
, user_data_name
), user_data
);
743 batch
.Put(CreateHasUserDataKey(registration_id
, user_data_name
), "");
744 return WriteBatch(&batch
);
747 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteUserData(
748 int64 registration_id
,
749 const std::string
& user_data_name
) {
750 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
751 DCHECK_NE(kInvalidServiceWorkerRegistrationId
, registration_id
);
752 DCHECK(!user_data_name
.empty());
754 Status status
= LazyOpen(false);
755 if (IsNewOrNonexistentDatabase(status
))
757 if (status
!= STATUS_OK
)
760 leveldb::WriteBatch batch
;
761 batch
.Delete(CreateUserDataKey(registration_id
, user_data_name
));
762 batch
.Delete(CreateHasUserDataKey(registration_id
, user_data_name
));
763 return WriteBatch(&batch
);
766 ServiceWorkerDatabase::Status
767 ServiceWorkerDatabase::ReadUserDataForAllRegistrations(
768 const std::string
& user_data_name
,
769 std::vector
<std::pair
<int64
, std::string
>>* user_data
) {
770 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
771 DCHECK(user_data
->empty());
773 Status status
= LazyOpen(false);
774 if (IsNewOrNonexistentDatabase(status
))
776 if (status
!= STATUS_OK
)
779 std::string key_prefix
= CreateHasUserDataKeyPrefix(user_data_name
);
780 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
781 for (itr
->Seek(key_prefix
); itr
->Valid(); itr
->Next()) {
782 status
= LevelDBStatusToStatus(itr
->status());
783 if (status
!= STATUS_OK
) {
784 HandleReadResult(FROM_HERE
, status
);
789 std::string registration_id_string
;
790 if (!RemovePrefix(itr
->key().ToString(), key_prefix
,
791 ®istration_id_string
)) {
795 int64 registration_id
;
796 status
= ParseId(registration_id_string
, ®istration_id
);
797 if (status
!= STATUS_OK
) {
798 HandleReadResult(FROM_HERE
, status
);
804 status
= LevelDBStatusToStatus(
805 db_
->Get(leveldb::ReadOptions(),
806 CreateUserDataKey(registration_id
, user_data_name
), &value
));
807 if (status
!= STATUS_OK
) {
808 HandleReadResult(FROM_HERE
, status
);
812 user_data
->push_back(std::make_pair(registration_id
, value
));
815 HandleReadResult(FROM_HERE
, status
);
819 ServiceWorkerDatabase::Status
820 ServiceWorkerDatabase::GetUncommittedResourceIds(std::set
<int64
>* ids
) {
821 return ReadResourceIds(kUncommittedResIdKeyPrefix
, ids
);
824 ServiceWorkerDatabase::Status
825 ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set
<int64
>& ids
) {
826 return WriteResourceIds(kUncommittedResIdKeyPrefix
, ids
);
829 ServiceWorkerDatabase::Status
830 ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set
<int64
>& ids
) {
831 return DeleteResourceIds(kUncommittedResIdKeyPrefix
, ids
);
834 ServiceWorkerDatabase::Status
835 ServiceWorkerDatabase::GetPurgeableResourceIds(std::set
<int64
>* ids
) {
836 return ReadResourceIds(kPurgeableResIdKeyPrefix
, ids
);
839 ServiceWorkerDatabase::Status
840 ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set
<int64
>& ids
) {
841 return WriteResourceIds(kPurgeableResIdKeyPrefix
, ids
);
844 ServiceWorkerDatabase::Status
845 ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set
<int64
>& ids
) {
846 return DeleteResourceIds(kPurgeableResIdKeyPrefix
, ids
);
849 ServiceWorkerDatabase::Status
850 ServiceWorkerDatabase::PurgeUncommittedResourceIds(
851 const std::set
<int64
>& ids
) {
852 leveldb::WriteBatch batch
;
853 Status status
= DeleteResourceIdsInBatch(
854 kUncommittedResIdKeyPrefix
, ids
, &batch
);
855 if (status
!= STATUS_OK
)
857 status
= WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix
, ids
, &batch
);
858 if (status
!= STATUS_OK
)
860 return WriteBatch(&batch
);
863 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteAllDataForOrigins(
864 const std::set
<GURL
>& origins
,
865 std::vector
<int64
>* newly_purgeable_resources
) {
866 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
867 Status status
= LazyOpen(false);
868 if (IsNewOrNonexistentDatabase(status
))
870 if (status
!= STATUS_OK
)
872 leveldb::WriteBatch batch
;
874 for (const GURL
& origin
: origins
) {
875 if (!origin
.is_valid())
876 return STATUS_ERROR_FAILED
;
878 // Delete from the unique origin list.
879 batch
.Delete(CreateUniqueOriginKey(origin
));
881 std::vector
<RegistrationData
> registrations
;
882 status
= GetRegistrationsForOrigin(origin
, ®istrations
);
883 if (status
!= STATUS_OK
)
886 // Delete registrations, resource records and user data.
887 for (const RegistrationData
& data
: registrations
) {
888 batch
.Delete(CreateRegistrationKey(data
.registration_id
, origin
));
890 status
= DeleteResourceRecords(
891 data
.version_id
, newly_purgeable_resources
, &batch
);
892 if (status
!= STATUS_OK
)
895 status
= DeleteUserDataForRegistration(data
.registration_id
, &batch
);
896 if (status
!= STATUS_OK
)
901 return WriteBatch(&batch
);
904 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DestroyDatabase() {
905 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
906 Disable(FROM_HERE
, STATUS_OK
);
908 leveldb::Options options
;
911 options
.env
= env_
.get();
913 // In-memory database not initialized.
918 return LevelDBStatusToStatus(
919 leveldb::DestroyDB(path_
.AsUTF8Unsafe(), options
));
922 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::LazyOpen(
923 bool create_if_missing
) {
924 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
926 // Do not try to open a database if we tried and failed once.
927 if (state_
== DISABLED
)
928 return STATUS_ERROR_FAILED
;
932 // When |path_| is empty, open a database in-memory.
933 bool use_in_memory_db
= path_
.empty();
935 if (!create_if_missing
) {
936 // Avoid opening a database if it does not exist at the |path_|.
937 if (use_in_memory_db
||
938 !base::PathExists(path_
) ||
939 base::IsDirectoryEmpty(path_
)) {
940 return STATUS_ERROR_NOT_FOUND
;
944 leveldb::Options options
;
945 options
.create_if_missing
= create_if_missing
;
946 if (use_in_memory_db
) {
947 env_
.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
948 options
.env
= env_
.get();
951 leveldb::DB
* db
= NULL
;
952 Status status
= LevelDBStatusToStatus(
953 leveldb::DB::Open(options
, path_
.AsUTF8Unsafe(), &db
));
954 HandleOpenResult(FROM_HERE
, status
);
955 if (status
!= STATUS_OK
) {
957 // TODO(nhiroki): Should we retry to open the database?
963 status
= ReadDatabaseVersion(&db_version
);
964 if (status
!= STATUS_OK
)
966 DCHECK_LE(0, db_version
);
968 state_
= INITIALIZED
;
972 bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase(
973 ServiceWorkerDatabase::Status status
) {
974 if (status
== STATUS_ERROR_NOT_FOUND
)
976 if (status
== STATUS_OK
&& state_
== UNINITIALIZED
)
981 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadNextAvailableId(
983 int64
* next_avail_id
) {
985 DCHECK(next_avail_id
);
988 Status status
= LevelDBStatusToStatus(
989 db_
->Get(leveldb::ReadOptions(), id_key
, &value
));
990 if (status
== STATUS_ERROR_NOT_FOUND
) {
991 // Nobody has gotten the next resource id for |id_key|.
993 HandleReadResult(FROM_HERE
, STATUS_OK
);
995 } else if (status
!= STATUS_OK
) {
996 HandleReadResult(FROM_HERE
, status
);
1000 status
= ParseId(value
, next_avail_id
);
1001 HandleReadResult(FROM_HERE
, status
);
1005 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadRegistrationData(
1006 int64 registration_id
,
1008 RegistrationData
* registration
) {
1009 DCHECK(registration
);
1011 const std::string key
= CreateRegistrationKey(registration_id
, origin
);
1013 Status status
= LevelDBStatusToStatus(
1014 db_
->Get(leveldb::ReadOptions(), key
, &value
));
1015 if (status
!= STATUS_OK
) {
1018 status
== STATUS_ERROR_NOT_FOUND
? STATUS_OK
: status
);
1022 status
= ParseRegistrationData(value
, registration
);
1023 HandleReadResult(FROM_HERE
, status
);
1027 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadResourceRecords(
1029 std::vector
<ResourceRecord
>* resources
) {
1030 DCHECK(resources
->empty());
1032 Status status
= STATUS_OK
;
1033 const std::string prefix
= CreateResourceRecordKeyPrefix(version_id
);
1035 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
1036 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
1037 Status status
= LevelDBStatusToStatus(itr
->status());
1038 if (status
!= STATUS_OK
) {
1039 HandleReadResult(FROM_HERE
, status
);
1044 if (!RemovePrefix(itr
->key().ToString(), prefix
, NULL
))
1047 ResourceRecord resource
;
1048 status
= ParseResourceRecord(itr
->value().ToString(), &resource
);
1049 if (status
!= STATUS_OK
) {
1050 HandleReadResult(FROM_HERE
, status
);
1054 resources
->push_back(resource
);
1057 HandleReadResult(FROM_HERE
, status
);
1061 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceRecords(
1063 std::vector
<int64
>* newly_purgeable_resources
,
1064 leveldb::WriteBatch
* batch
) {
1067 Status status
= STATUS_OK
;
1068 const std::string prefix
= CreateResourceRecordKeyPrefix(version_id
);
1070 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
1071 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
1072 status
= LevelDBStatusToStatus(itr
->status());
1073 if (status
!= STATUS_OK
) {
1074 HandleReadResult(FROM_HERE
, status
);
1078 const std::string key
= itr
->key().ToString();
1079 std::string unprefixed
;
1080 if (!RemovePrefix(key
, prefix
, &unprefixed
))
1084 status
= ParseId(unprefixed
, &resource_id
);
1085 if (status
!= STATUS_OK
) {
1086 HandleReadResult(FROM_HERE
, status
);
1090 // Remove a resource record.
1093 // Currently resource sharing across versions and registrations is not
1094 // supported, so we can purge this without caring about it.
1095 PutPurgeableResourceIdToBatch(resource_id
, batch
);
1096 newly_purgeable_resources
->push_back(resource_id
);
1099 HandleReadResult(FROM_HERE
, status
);
1103 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadResourceIds(
1104 const char* id_key_prefix
,
1105 std::set
<int64
>* ids
) {
1106 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
1107 DCHECK(id_key_prefix
);
1108 DCHECK(ids
->empty());
1110 Status status
= LazyOpen(false);
1111 if (IsNewOrNonexistentDatabase(status
))
1113 if (status
!= STATUS_OK
)
1116 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
1117 for (itr
->Seek(id_key_prefix
); itr
->Valid(); itr
->Next()) {
1118 status
= LevelDBStatusToStatus(itr
->status());
1119 if (status
!= STATUS_OK
) {
1120 HandleReadResult(FROM_HERE
, status
);
1125 std::string unprefixed
;
1126 if (!RemovePrefix(itr
->key().ToString(), id_key_prefix
, &unprefixed
))
1130 status
= ParseId(unprefixed
, &resource_id
);
1131 if (status
!= STATUS_OK
) {
1132 HandleReadResult(FROM_HERE
, status
);
1136 ids
->insert(resource_id
);
1139 HandleReadResult(FROM_HERE
, status
);
1143 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteResourceIds(
1144 const char* id_key_prefix
,
1145 const std::set
<int64
>& ids
) {
1146 leveldb::WriteBatch batch
;
1147 Status status
= WriteResourceIdsInBatch(id_key_prefix
, ids
, &batch
);
1148 if (status
!= STATUS_OK
)
1150 return WriteBatch(&batch
);
1153 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteResourceIdsInBatch(
1154 const char* id_key_prefix
,
1155 const std::set
<int64
>& ids
,
1156 leveldb::WriteBatch
* batch
) {
1157 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
1158 DCHECK(id_key_prefix
);
1160 Status status
= LazyOpen(true);
1161 if (status
!= STATUS_OK
)
1166 for (std::set
<int64
>::const_iterator itr
= ids
.begin();
1167 itr
!= ids
.end(); ++itr
) {
1168 // Value should be empty.
1169 batch
->Put(CreateResourceIdKey(id_key_prefix
, *itr
), "");
1171 // std::set is sorted, so the last element is the largest.
1172 BumpNextResourceIdIfNeeded(*ids
.rbegin(), batch
);
1176 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceIds(
1177 const char* id_key_prefix
,
1178 const std::set
<int64
>& ids
) {
1179 leveldb::WriteBatch batch
;
1180 Status status
= DeleteResourceIdsInBatch(id_key_prefix
, ids
, &batch
);
1181 if (status
!= STATUS_OK
)
1183 return WriteBatch(&batch
);
1186 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::DeleteResourceIdsInBatch(
1187 const char* id_key_prefix
,
1188 const std::set
<int64
>& ids
,
1189 leveldb::WriteBatch
* batch
) {
1190 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
1191 DCHECK(id_key_prefix
);
1193 Status status
= LazyOpen(false);
1194 if (IsNewOrNonexistentDatabase(status
))
1196 if (status
!= STATUS_OK
)
1199 for (std::set
<int64
>::const_iterator itr
= ids
.begin();
1200 itr
!= ids
.end(); ++itr
) {
1201 batch
->Delete(CreateResourceIdKey(id_key_prefix
, *itr
));
1206 ServiceWorkerDatabase::Status
1207 ServiceWorkerDatabase::DeleteUserDataForRegistration(
1208 int64 registration_id
,
1209 leveldb::WriteBatch
* batch
) {
1211 Status status
= STATUS_OK
;
1212 const std::string prefix
= CreateUserDataKeyPrefix(registration_id
);
1214 scoped_ptr
<leveldb::Iterator
> itr(db_
->NewIterator(leveldb::ReadOptions()));
1215 for (itr
->Seek(prefix
); itr
->Valid(); itr
->Next()) {
1216 status
= LevelDBStatusToStatus(itr
->status());
1217 if (status
!= STATUS_OK
) {
1218 HandleReadResult(FROM_HERE
, status
);
1222 const std::string key
= itr
->key().ToString();
1223 std::string user_data_name
;
1224 if (!RemovePrefix(key
, prefix
, &user_data_name
))
1227 batch
->Delete(CreateHasUserDataKey(registration_id
, user_data_name
));
1232 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::ReadDatabaseVersion(
1233 int64
* db_version
) {
1235 Status status
= LevelDBStatusToStatus(
1236 db_
->Get(leveldb::ReadOptions(), kDatabaseVersionKey
, &value
));
1237 if (status
== STATUS_ERROR_NOT_FOUND
) {
1238 // The database hasn't been initialized yet.
1240 HandleReadResult(FROM_HERE
, STATUS_OK
);
1244 if (status
!= STATUS_OK
) {
1245 HandleReadResult(FROM_HERE
, status
);
1249 status
= ParseDatabaseVersion(value
, db_version
);
1250 HandleReadResult(FROM_HERE
, status
);
1254 ServiceWorkerDatabase::Status
ServiceWorkerDatabase::WriteBatch(
1255 leveldb::WriteBatch
* batch
) {
1257 DCHECK_NE(DISABLED
, state_
);
1259 if (state_
== UNINITIALIZED
) {
1260 // Write the database schema version.
1261 batch
->Put(kDatabaseVersionKey
, base::Int64ToString(kCurrentSchemaVersion
));
1262 state_
= INITIALIZED
;
1265 Status status
= LevelDBStatusToStatus(
1266 db_
->Write(leveldb::WriteOptions(), batch
));
1267 HandleWriteResult(FROM_HERE
, status
);
1271 void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded(
1272 int64 used_id
, leveldb::WriteBatch
* batch
) {
1274 if (next_avail_registration_id_
<= used_id
) {
1275 next_avail_registration_id_
= used_id
+ 1;
1276 batch
->Put(kNextRegIdKey
, base::Int64ToString(next_avail_registration_id_
));
1280 void ServiceWorkerDatabase::BumpNextResourceIdIfNeeded(
1281 int64 used_id
, leveldb::WriteBatch
* batch
) {
1283 if (next_avail_resource_id_
<= used_id
) {
1284 next_avail_resource_id_
= used_id
+ 1;
1285 batch
->Put(kNextResIdKey
, base::Int64ToString(next_avail_resource_id_
));
1289 void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded(
1290 int64 used_id
, leveldb::WriteBatch
* batch
) {
1292 if (next_avail_version_id_
<= used_id
) {
1293 next_avail_version_id_
= used_id
+ 1;
1294 batch
->Put(kNextVerIdKey
, base::Int64ToString(next_avail_version_id_
));
1298 bool ServiceWorkerDatabase::IsOpen() {
1302 void ServiceWorkerDatabase::Disable(
1303 const tracked_objects::Location
& from_here
,
1305 if (status
!= STATUS_OK
) {
1306 DLOG(ERROR
) << "Failed at: " << from_here
.ToString()
1307 << " with error: " << StatusToString(status
);
1308 DLOG(ERROR
) << "ServiceWorkerDatabase is disabled.";
1314 void ServiceWorkerDatabase::HandleOpenResult(
1315 const tracked_objects::Location
& from_here
,
1317 if (status
!= STATUS_OK
)
1318 Disable(from_here
, status
);
1319 ServiceWorkerMetrics::CountOpenDatabaseResult(status
);
1322 void ServiceWorkerDatabase::HandleReadResult(
1323 const tracked_objects::Location
& from_here
,
1325 if (status
!= STATUS_OK
)
1326 Disable(from_here
, status
);
1327 ServiceWorkerMetrics::CountReadDatabaseResult(status
);
1330 void ServiceWorkerDatabase::HandleWriteResult(
1331 const tracked_objects::Location
& from_here
,
1333 if (status
!= STATUS_OK
)
1334 Disable(from_here
, status
);
1335 ServiceWorkerMetrics::CountWriteDatabaseResult(status
);
1338 } // namespace content