Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / browser / service_worker / service_worker_database.cc
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.
4
5 #include "content/browser/service_worker/service_worker_database.h"
6
7 #include <string>
8
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"
25
26 // LevelDB database schema
27 // =======================
28 //
29 // NOTE
30 // - int64 value is serialized as a string by base::Int64ToString().
31 // - GURL value is serialized as a string by GURL::spec().
32 //
33 // Version 1 (in sorted order)
34 //   key: "INITDATA_DB_VERSION"
35 //   value: "1"
36 //
37 //   key: "INITDATA_NEXT_REGISTRATION_ID"
38 //   value: <int64 'next_available_registration_id'>
39 //
40 //   key: "INITDATA_NEXT_RESOURCE_ID"
41 //   value: <int64 'next_available_resource_id'>
42 //
43 //   key: "INITDATA_NEXT_VERSION_ID"
44 //   value: <int64 'next_available_version_id'>
45 //
46 //   key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin'>
47 //   value: <empty>
48 //
49 //   key: "PRES:" + <int64 'purgeable_resource_id'>
50 //   value: <empty>
51 //
52 //   key: "REG:" + <GURL 'origin'> + '\x00' + <int64 'registration_id'>
53 //     (ex. "REG:http://example.com\x00123456")
54 //   value: <ServiceWorkerRegistrationData serialized as a string>
55 //
56 //   key: "RES:" + <int64 'version_id'> + '\x00' + <int64 'resource_id'>
57 //     (ex. "RES:123456\x00654321")
58 //   value: <ServiceWorkerResourceRecord serialized as a string>
59 //
60 //   key: "URES:" + <int64 'uncommitted_resource_id'>
61 //   value: <empty>
62
63 namespace content {
64
65 namespace {
66
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:";
72
73 const char kRegKeyPrefix[] = "REG:";
74 const char kResKeyPrefix[] = "RES:";
75 const char kKeySeparator = '\x00';
76
77 const char kUncommittedResIdKeyPrefix[] = "URES:";
78 const char kPurgeableResIdKeyPrefix[] = "PRES:";
79
80 const int64 kCurrentSchemaVersion = 1;
81
82 bool RemovePrefix(const std::string& str,
83                   const std::string& prefix,
84                   std::string* out) {
85   if (!StartsWithASCII(str, prefix, true))
86     return false;
87   if (out)
88     *out = str.substr(prefix.size());
89   return true;
90 }
91
92 std::string CreateRegistrationKey(int64 registration_id,
93                                   const GURL& origin) {
94   return base::StringPrintf("%s%s%c%s",
95                             kRegKeyPrefix,
96                             origin.spec().c_str(),
97                             kKeySeparator,
98                             base::Int64ToString(registration_id).c_str());
99 }
100
101 std::string CreateResourceRecordKeyPrefix(int64 version_id) {
102   return base::StringPrintf("%s%s%c",
103                             kResKeyPrefix,
104                             base::Int64ToString(version_id).c_str(),
105                             kKeySeparator);
106 }
107
108 std::string CreateResourceRecordKey(int64 version_id,
109                                     int64 resource_id) {
110   return CreateResourceRecordKeyPrefix(version_id).append(
111       base::Int64ToString(resource_id));
112 }
113
114 std::string CreateUniqueOriginKey(const GURL& origin) {
115   return base::StringPrintf("%s%s", kUniqueOriginKey, origin.spec().c_str());
116 }
117
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());
121 }
122
123 void PutRegistrationDataToBatch(
124     const ServiceWorkerDatabase::RegistrationData& input,
125     leveldb::WriteBatch* batch) {
126   DCHECK(batch);
127
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);
138
139   std::string value;
140   bool success = data.SerializeToString(&value);
141   DCHECK(success);
142   GURL origin = input.scope.GetOrigin();
143   batch->Put(CreateRegistrationKey(data.registration_id(), origin), value);
144 }
145
146 void PutResourceRecordToBatch(
147     const ServiceWorkerDatabase::ResourceRecord& input,
148     int64 version_id,
149     leveldb::WriteBatch* batch) {
150   DCHECK(batch);
151   DCHECK_GE(input.size_bytes, 0);
152
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);
158
159   std::string value;
160   bool success = record.SerializeToString(&value);
161   DCHECK(success);
162   batch->Put(CreateResourceRecordKey(version_id, input.resource_id), value);
163 }
164
165 void PutUniqueOriginToBatch(const GURL& origin,
166                             leveldb::WriteBatch* batch) {
167   // Value should be empty.
168   batch->Put(CreateUniqueOriginKey(origin), "");
169 }
170
171 void PutPurgeableResourceIdToBatch(int64 resource_id,
172                                    leveldb::WriteBatch* batch) {
173   // Value should be empty.
174   batch->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix, resource_id), "");
175 }
176
177 ServiceWorkerDatabase::Status ParseId(
178     const std::string& serialized,
179     int64* out) {
180   DCHECK(out);
181   int64 id;
182   if (!base::StringToInt64(serialized, &id) || id < 0)
183     return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
184   *out = id;
185   return ServiceWorkerDatabase::STATUS_OK;
186 }
187
188 ServiceWorkerDatabase::Status ParseDatabaseVersion(
189     const std::string& serialized,
190     int64* out) {
191   DCHECK(out);
192   const int kFirstValidVersion = 1;
193   int64 version;
194   if (!base::StringToInt64(serialized, &version) ||
195       version < kFirstValidVersion) {
196     return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
197   }
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;
203   }
204   *out = version;
205   return ServiceWorkerDatabase::STATUS_OK;
206 }
207
208 ServiceWorkerDatabase::Status ParseRegistrationData(
209     const std::string& serialized,
210     ServiceWorkerDatabase::RegistrationData* out) {
211   DCHECK(out);
212   ServiceWorkerRegistrationData data;
213   if (!data.ParseFromString(serialized))
214     return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
215
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 '"
222                 << data.script_url()
223                 << "' are invalid or have mismatching origins.";
224     return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
225   }
226
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();
237
238   return ServiceWorkerDatabase::STATUS_OK;
239 }
240
241 ServiceWorkerDatabase::Status ParseResourceRecord(
242     const std::string& serialized,
243     ServiceWorkerDatabase::ResourceRecord* out) {
244   DCHECK(out);
245   ServiceWorkerResourceRecord record;
246   if (!record.ParseFromString(serialized))
247     return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
248
249   GURL url(record.url());
250   if (!url.is_valid())
251     return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
252
253   // Convert ServiceWorkerResourceRecord to ResourceRecord.
254   out->resource_id = record.resource_id();
255   out->url = url;
256   out->size_bytes = record.size_bytes();
257   return ServiceWorkerDatabase::STATUS_OK;
258 }
259
260 ServiceWorkerDatabase::Status LevelDBStatusToStatus(
261     const leveldb::Status& status) {
262   if (status.ok())
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;
270   else
271     return ServiceWorkerDatabase::STATUS_ERROR_FAILED;
272 }
273
274 }  // namespace
275
276 const char* ServiceWorkerDatabase::StatusToString(
277   ServiceWorkerDatabase::Status status) {
278   switch (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:
290       NOTREACHED();
291       return "Database unknown error";
292   }
293   NOTREACHED();
294   return "Database unknown error";
295 }
296
297 ServiceWorkerDatabase::RegistrationData::RegistrationData()
298     : registration_id(kInvalidServiceWorkerRegistrationId),
299       version_id(kInvalidServiceWorkerVersionId),
300       is_active(false),
301       has_fetch_handler(false),
302       resources_total_size_bytes(0) {
303 }
304
305 ServiceWorkerDatabase::RegistrationData::~RegistrationData() {
306 }
307
308 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path)
309     : path_(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();
315 }
316
317 ServiceWorkerDatabase::~ServiceWorkerDatabase() {
318   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
319   db_.reset();
320 }
321
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);
330
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;
336     return STATUS_OK;
337   }
338   if (status != STATUS_OK)
339     return status;
340
341   status = ReadNextAvailableId(kNextRegIdKey, &next_avail_registration_id_);
342   if (status != STATUS_OK)
343     return status;
344   status = ReadNextAvailableId(kNextVerIdKey, &next_avail_version_id_);
345   if (status != STATUS_OK)
346     return status;
347   status = ReadNextAvailableId(kNextResIdKey, &next_avail_resource_id_);
348   if (status != STATUS_OK)
349     return status;
350
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_;
354   return STATUS_OK;
355 }
356
357 ServiceWorkerDatabase::Status
358 ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set<GURL>* origins) {
359   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
360   DCHECK(origins->empty());
361
362   Status status = LazyOpen(false);
363   if (IsNewOrNonexistentDatabase(status))
364     return STATUS_OK;
365   if (status != STATUS_OK)
366     return status;
367
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);
373       origins->clear();
374       return status;
375     }
376
377     std::string origin;
378     if (!RemovePrefix(itr->key().ToString(), kUniqueOriginKey, &origin))
379       break;
380     origins->insert(GURL(origin));
381   }
382
383   HandleReadResult(FROM_HERE, status);
384   return status;
385 }
386
387 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin(
388     const GURL& origin,
389     std::vector<RegistrationData>* registrations) {
390   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
391   DCHECK(registrations->empty());
392
393   Status status = LazyOpen(false);
394   if (IsNewOrNonexistentDatabase(status))
395     return STATUS_OK;
396   if (status != STATUS_OK)
397     return status;
398
399   // Create a key prefix for registrations.
400   std::string prefix = base::StringPrintf(
401       "%s%s%c", kRegKeyPrefix, origin.spec().c_str(), kKeySeparator);
402
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();
409       return status;
410     }
411
412     if (!RemovePrefix(itr->key().ToString(), prefix, NULL))
413       break;
414
415     RegistrationData registration;
416     status = ParseRegistrationData(itr->value().ToString(), &registration);
417     if (status != STATUS_OK) {
418       HandleReadResult(FROM_HERE, status);
419       registrations->clear();
420       return status;
421     }
422     registrations->push_back(registration);
423   }
424
425   HandleReadResult(FROM_HERE, status);
426   return status;
427 }
428
429 ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations(
430     std::vector<RegistrationData>* registrations) {
431   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
432   DCHECK(registrations->empty());
433
434   Status status = LazyOpen(false);
435   if (IsNewOrNonexistentDatabase(status))
436     return STATUS_OK;
437   if (status != STATUS_OK)
438     return status;
439
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();
446       return status;
447     }
448
449     if (!RemovePrefix(itr->key().ToString(), kRegKeyPrefix, NULL))
450       break;
451
452     RegistrationData registration;
453     status = ParseRegistrationData(itr->value().ToString(), &registration);
454     if (status != STATUS_OK) {
455       HandleReadResult(FROM_HERE, status);
456       registrations->clear();
457       return status;
458     }
459     registrations->push_back(registration);
460   }
461
462   HandleReadResult(FROM_HERE, status);
463   return status;
464 }
465
466 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistration(
467     int64 registration_id,
468     const GURL& origin,
469     RegistrationData* registration,
470     std::vector<ResourceRecord>* resources) {
471   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
472   DCHECK(registration);
473   DCHECK(resources);
474
475   Status status = LazyOpen(false);
476   if (IsNewOrNonexistentDatabase(status) || status != STATUS_OK)
477     return status;
478
479   RegistrationData value;
480   status = ReadRegistrationData(registration_id, origin, &value);
481   if (status != STATUS_OK)
482     return status;
483
484   status = ReadResourceRecords(value.version_id, resources);
485   if (status != STATUS_OK)
486     return status;
487
488   *registration = value;
489   return STATUS_OK;
490 }
491
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)
502     return status;
503
504   leveldb::WriteBatch batch;
505   BumpNextRegistrationIdIfNeeded(registration.registration_id, &batch);
506   BumpNextVersionIdIfNeeded(registration.version_id, &batch);
507
508   PutUniqueOriginToBatch(registration.scope.GetOrigin(), &batch);
509 #if DCHECK_IS_ON
510   int64_t total_size_bytes = 0;
511   for (const auto& resource : resources) {
512     total_size_bytes += resource.size_bytes;
513   }
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.";
517 #endif
518   PutRegistrationDataToBatch(registration, &batch);
519
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;
527
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);
531
532     PutResourceRecordToBatch(*itr, registration.version_id, &batch);
533
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.
538     batch.Delete(
539         CreateResourceIdKey(kPurgeableResIdKeyPrefix, itr->resource_id));
540   }
541
542   // Retrieve a previous version to sweep purgeable resources.
543   status = ReadRegistrationData(registration.registration_id,
544                                 registration.scope.GetOrigin(),
545                                 old_registration);
546   if (status != STATUS_OK && status != STATUS_ERROR_NOT_FOUND)
547     return status;
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)
553       return status;
554
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());
562   }
563
564   return WriteBatch(&batch);
565 }
566
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)
575     return status;
576   if (!origin.is_valid())
577     return STATUS_ERROR_FAILED;
578
579   RegistrationData registration;
580   status = ReadRegistrationData(registration_id, origin, &registration);
581   if (status != STATUS_OK)
582     return status;
583
584   registration.is_active = true;
585
586   leveldb::WriteBatch batch;
587   PutRegistrationDataToBatch(registration, &batch);
588   return WriteBatch(&batch);
589 }
590
591 ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateLastCheckTime(
592     int64 registration_id,
593     const GURL& origin,
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)
600     return status;
601   if (!origin.is_valid())
602     return STATUS_ERROR_FAILED;
603
604   RegistrationData registration;
605   status = ReadRegistrationData(registration_id, origin, &registration);
606   if (status != STATUS_OK)
607     return status;
608
609   registration.last_update_check = time;
610
611   leveldb::WriteBatch batch;
612   PutRegistrationDataToBatch(registration, &batch);
613   return WriteBatch(&batch);
614 }
615
616 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteRegistration(
617     int64 registration_id,
618     const GURL& origin,
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))
626     return STATUS_OK;
627   if (status != STATUS_OK)
628     return status;
629   if (!origin.is_valid())
630     return STATUS_ERROR_FAILED;
631
632   leveldb::WriteBatch batch;
633
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, &registrations);
639   if (status != STATUS_OK)
640     return status;
641
642   if (registrations.size() == 1 &&
643       registrations[0].registration_id == registration_id) {
644     batch.Delete(CreateUniqueOriginKey(origin));
645   }
646
647   // Delete a registration specified by |registration_id|.
648   batch.Delete(CreateRegistrationKey(registration_id, origin));
649
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)
657         return status;
658       break;
659     }
660   }
661
662   return WriteBatch(&batch);
663 }
664
665 ServiceWorkerDatabase::Status
666 ServiceWorkerDatabase::GetUncommittedResourceIds(std::set<int64>* ids) {
667   return ReadResourceIds(kUncommittedResIdKeyPrefix, ids);
668 }
669
670 ServiceWorkerDatabase::Status
671 ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set<int64>& ids) {
672   return WriteResourceIds(kUncommittedResIdKeyPrefix, ids);
673 }
674
675 ServiceWorkerDatabase::Status
676 ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set<int64>& ids) {
677   return DeleteResourceIds(kUncommittedResIdKeyPrefix, ids);
678 }
679
680 ServiceWorkerDatabase::Status
681 ServiceWorkerDatabase::GetPurgeableResourceIds(std::set<int64>* ids) {
682   return ReadResourceIds(kPurgeableResIdKeyPrefix, ids);
683 }
684
685 ServiceWorkerDatabase::Status
686 ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set<int64>& ids) {
687   return WriteResourceIds(kPurgeableResIdKeyPrefix, ids);
688 }
689
690 ServiceWorkerDatabase::Status
691 ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set<int64>& ids) {
692   return DeleteResourceIds(kPurgeableResIdKeyPrefix, ids);
693 }
694
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)
702     return status;
703   status = WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix, ids, &batch);
704   if (status != STATUS_OK)
705     return status;
706   return WriteBatch(&batch);
707 }
708
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))
715     return STATUS_OK;
716   if (status != STATUS_OK)
717     return status;
718   leveldb::WriteBatch batch;
719
720   for (const GURL& origin : origins) {
721     if (!origin.is_valid())
722       return STATUS_ERROR_FAILED;
723
724     // Delete from the unique origin list.
725     batch.Delete(CreateUniqueOriginKey(origin));
726
727     std::vector<RegistrationData> registrations;
728     status = GetRegistrationsForOrigin(origin, &registrations);
729     if (status != STATUS_OK)
730       return status;
731
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)
738         return status;
739     }
740   }
741
742   return WriteBatch(&batch);
743 }
744
745 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DestroyDatabase() {
746   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
747   Disable(FROM_HERE, STATUS_OK);
748
749   leveldb::Options options;
750   if (path_.empty()) {
751     if (env_) {
752       options.env = env_.get();
753     } else {
754       // In-memory database not initialized.
755       return STATUS_OK;
756     }
757   }
758
759   return LevelDBStatusToStatus(
760       leveldb::DestroyDB(path_.AsUTF8Unsafe(), options));
761 }
762
763 ServiceWorkerDatabase::Status ServiceWorkerDatabase::LazyOpen(
764     bool create_if_missing) {
765   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
766
767   // Do not try to open a database if we tried and failed once.
768   if (state_ == DISABLED)
769     return STATUS_ERROR_FAILED;
770   if (IsOpen())
771     return STATUS_OK;
772
773   // When |path_| is empty, open a database in-memory.
774   bool use_in_memory_db = path_.empty();
775
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;
782     }
783   }
784
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();
790   }
791
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) {
797     DCHECK(!db);
798     // TODO(nhiroki): Should we retry to open the database?
799     return status;
800   }
801   db_.reset(db);
802
803   int64 db_version;
804   status = ReadDatabaseVersion(&db_version);
805   if (status != STATUS_OK)
806     return status;
807   DCHECK_LE(0, db_version);
808   if (db_version > 0)
809     state_ = INITIALIZED;
810   return STATUS_OK;
811 }
812
813 bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase(
814     ServiceWorkerDatabase::Status status) {
815   if (status == STATUS_ERROR_NOT_FOUND)
816     return true;
817   if (status == STATUS_OK && state_ == UNINITIALIZED)
818     return true;
819   return false;
820 }
821
822 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadNextAvailableId(
823     const char* id_key,
824     int64* next_avail_id) {
825   DCHECK(id_key);
826   DCHECK(next_avail_id);
827
828   std::string value;
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|.
833     *next_avail_id = 0;
834     HandleReadResult(FROM_HERE, STATUS_OK);
835     return STATUS_OK;
836   } else if (status != STATUS_OK) {
837     HandleReadResult(FROM_HERE, status);
838     return status;
839   }
840
841   status = ParseId(value, next_avail_id);
842   HandleReadResult(FROM_HERE, status);
843   return status;
844 }
845
846 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData(
847     int64 registration_id,
848     const GURL& origin,
849     RegistrationData* registration) {
850   DCHECK(registration);
851
852   const std::string key = CreateRegistrationKey(registration_id, origin);
853   std::string value;
854   Status status = LevelDBStatusToStatus(
855       db_->Get(leveldb::ReadOptions(), key, &value));
856   if (status != STATUS_OK) {
857     HandleReadResult(
858         FROM_HERE,
859         status == STATUS_ERROR_NOT_FOUND ? STATUS_OK : status);
860     return status;
861   }
862
863   status = ParseRegistrationData(value, registration);
864   HandleReadResult(FROM_HERE, status);
865   return status;
866 }
867
868 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceRecords(
869     int64 version_id,
870     std::vector<ResourceRecord>* resources) {
871   DCHECK(resources->empty());
872
873   Status status = STATUS_OK;
874   const std::string prefix = CreateResourceRecordKeyPrefix(version_id);
875
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);
881       resources->clear();
882       return status;
883     }
884
885     if (!RemovePrefix(itr->key().ToString(), prefix, NULL))
886       break;
887
888     ResourceRecord resource;
889     status = ParseResourceRecord(itr->value().ToString(), &resource);
890     if (status != STATUS_OK) {
891       HandleReadResult(FROM_HERE, status);
892       resources->clear();
893       return status;
894     }
895     resources->push_back(resource);
896   }
897
898   HandleReadResult(FROM_HERE, status);
899   return status;
900 }
901
902 ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords(
903     int64 version_id,
904     std::vector<int64>* newly_purgeable_resources,
905     leveldb::WriteBatch* batch) {
906   DCHECK(batch);
907
908   Status status = STATUS_OK;
909   const std::string prefix = CreateResourceRecordKeyPrefix(version_id);
910
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);
916       return status;
917     }
918
919     const std::string key = itr->key().ToString();
920     std::string unprefixed;
921     if (!RemovePrefix(key, prefix, &unprefixed))
922       break;
923
924     int64 resource_id;
925     status = ParseId(unprefixed, &resource_id);
926     if (status != STATUS_OK) {
927       HandleReadResult(FROM_HERE, status);
928       return status;
929     }
930
931     // Remove a resource record.
932     batch->Delete(key);
933
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);
938   }
939
940   HandleReadResult(FROM_HERE, status);
941   return status;
942 }
943
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());
950
951   Status status = LazyOpen(false);
952   if (IsNewOrNonexistentDatabase(status))
953     return STATUS_OK;
954   if (status != STATUS_OK)
955     return status;
956
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);
962       ids->clear();
963       return status;
964     }
965
966     std::string unprefixed;
967     if (!RemovePrefix(itr->key().ToString(), id_key_prefix, &unprefixed))
968       break;
969
970     int64 resource_id;
971     status = ParseId(unprefixed, &resource_id);
972     if (status != STATUS_OK) {
973       HandleReadResult(FROM_HERE, status);
974       ids->clear();
975       return status;
976     }
977     ids->insert(resource_id);
978   }
979
980   HandleReadResult(FROM_HERE, status);
981   return status;
982 }
983
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)
990     return status;
991   return WriteBatch(&batch);
992 }
993
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);
1000
1001   Status status = LazyOpen(true);
1002   if (status != STATUS_OK)
1003     return status;
1004
1005   if (ids.empty())
1006     return 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), "");
1011   }
1012   // std::set is sorted, so the last element is the largest.
1013   BumpNextResourceIdIfNeeded(*ids.rbegin(), batch);
1014   return STATUS_OK;
1015 }
1016
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)
1023     return status;
1024   return WriteBatch(&batch);
1025 }
1026
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);
1033
1034   Status status = LazyOpen(false);
1035   if (IsNewOrNonexistentDatabase(status))
1036     return STATUS_OK;
1037   if (status != STATUS_OK)
1038     return status;
1039
1040   for (std::set<int64>::const_iterator itr = ids.begin();
1041        itr != ids.end(); ++itr) {
1042     batch->Delete(CreateResourceIdKey(id_key_prefix, *itr));
1043   }
1044   return STATUS_OK;
1045 }
1046
1047 ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadDatabaseVersion(
1048     int64* db_version) {
1049   std::string value;
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.
1054     *db_version = 0;
1055     HandleReadResult(FROM_HERE, STATUS_OK);
1056     return STATUS_OK;
1057   }
1058
1059   if (status != STATUS_OK) {
1060     HandleReadResult(FROM_HERE, status);
1061     return status;
1062   }
1063
1064   status = ParseDatabaseVersion(value, db_version);
1065   HandleReadResult(FROM_HERE, status);
1066   return status;
1067 }
1068
1069 ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteBatch(
1070     leveldb::WriteBatch* batch) {
1071   DCHECK(batch);
1072   DCHECK_NE(DISABLED, state_);
1073
1074   if (state_ == UNINITIALIZED) {
1075     // Write the database schema version.
1076     batch->Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion));
1077     state_ = INITIALIZED;
1078   }
1079
1080   Status status = LevelDBStatusToStatus(
1081       db_->Write(leveldb::WriteOptions(), batch));
1082   HandleWriteResult(FROM_HERE, status);
1083   return status;
1084 }
1085
1086 void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded(
1087     int64 used_id, leveldb::WriteBatch* batch) {
1088   DCHECK(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_));
1092   }
1093 }
1094
1095 void ServiceWorkerDatabase::BumpNextResourceIdIfNeeded(
1096     int64 used_id, leveldb::WriteBatch* batch) {
1097   DCHECK(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_));
1101   }
1102 }
1103
1104 void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded(
1105     int64 used_id, leveldb::WriteBatch* batch) {
1106   DCHECK(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_));
1110   }
1111 }
1112
1113 bool ServiceWorkerDatabase::IsOpen() {
1114   return db_ != NULL;
1115 }
1116
1117 void ServiceWorkerDatabase::Disable(
1118     const tracked_objects::Location& from_here,
1119     Status status) {
1120   if (status != STATUS_OK) {
1121     DLOG(ERROR) << "Failed at: " << from_here.ToString()
1122                 << " with error: " << StatusToString(status);
1123     DLOG(ERROR) << "ServiceWorkerDatabase is disabled.";
1124   }
1125   state_ = DISABLED;
1126   db_.reset();
1127 }
1128
1129 void ServiceWorkerDatabase::HandleOpenResult(
1130     const tracked_objects::Location& from_here,
1131     Status status) {
1132   if (status != STATUS_OK)
1133     Disable(from_here, status);
1134   ServiceWorkerMetrics::CountOpenDatabaseResult(status);
1135 }
1136
1137 void ServiceWorkerDatabase::HandleReadResult(
1138     const tracked_objects::Location& from_here,
1139     Status status) {
1140   if (status != STATUS_OK)
1141     Disable(from_here, status);
1142   ServiceWorkerMetrics::CountReadDatabaseResult(status);
1143 }
1144
1145 void ServiceWorkerDatabase::HandleWriteResult(
1146     const tracked_objects::Location& from_here,
1147     Status status) {
1148   if (status != STATUS_OK)
1149     Disable(from_here, status);
1150   ServiceWorkerMetrics::CountWriteDatabaseResult(status);
1151 }
1152
1153 }  // namespace content