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
63namespace content {
64
65namespace {
66
67const char kDatabaseVersionKey[] = "INITDATA_DB_VERSION";
68const char kNextRegIdKey[] = "INITDATA_NEXT_REGISTRATION_ID";
69const char kNextResIdKey[] = "INITDATA_NEXT_RESOURCE_ID";
70const char kNextVerIdKey[] = "INITDATA_NEXT_VERSION_ID";
71const char kUniqueOriginKey[] = "INITDATA_UNIQUE_ORIGIN:";
72
73const char kRegKeyPrefix[] = "REG:";
74const char kResKeyPrefix[] = "RES:";
75const char kKeySeparator = '\x00';
76
77const char kUncommittedResIdKeyPrefix[] = "URES:";
78const char kPurgeableResIdKeyPrefix[] = "PRES:";
79
80const int64 kCurrentSchemaVersion = 1;
81
82bool 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
92std::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
101std::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
108std::string CreateResourceRecordKey(int64 version_id,
109                                    int64 resource_id) {
110  return CreateResourceRecordKeyPrefix(version_id).append(
111      base::Int64ToString(resource_id));
112}
113
114std::string CreateUniqueOriginKey(const GURL& origin) {
115  return base::StringPrintf("%s%s", kUniqueOriginKey, origin.spec().c_str());
116}
117
118std::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
123void 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
138  std::string value;
139  bool success = data.SerializeToString(&value);
140  DCHECK(success);
141  GURL origin = input.scope.GetOrigin();
142  batch->Put(CreateRegistrationKey(data.registration_id(), origin), value);
143}
144
145void PutResourceRecordToBatch(
146    const ServiceWorkerDatabase::ResourceRecord& input,
147    int64 version_id,
148    leveldb::WriteBatch* batch) {
149  DCHECK(batch);
150
151  // Convert ResourceRecord to ServiceWorkerResourceRecord.
152  ServiceWorkerResourceRecord record;
153  record.set_resource_id(input.resource_id);
154  record.set_url(input.url.spec());
155
156  std::string value;
157  bool success = record.SerializeToString(&value);
158  DCHECK(success);
159  batch->Put(CreateResourceRecordKey(version_id, input.resource_id), value);
160}
161
162void PutUniqueOriginToBatch(const GURL& origin,
163                            leveldb::WriteBatch* batch) {
164  // Value should be empty.
165  batch->Put(CreateUniqueOriginKey(origin), "");
166}
167
168void PutPurgeableResourceIdToBatch(int64 resource_id,
169                                   leveldb::WriteBatch* batch) {
170  // Value should be empty.
171  batch->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix, resource_id), "");
172}
173
174ServiceWorkerDatabase::Status ParseId(
175    const std::string& serialized,
176    int64* out) {
177  DCHECK(out);
178  int64 id;
179  if (!base::StringToInt64(serialized, &id) || id < 0)
180    return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
181  *out = id;
182  return ServiceWorkerDatabase::STATUS_OK;
183}
184
185ServiceWorkerDatabase::Status ParseDatabaseVersion(
186    const std::string& serialized,
187    int64* out) {
188  DCHECK(out);
189  const int kFirstValidVersion = 1;
190  int64 version;
191  if (!base::StringToInt64(serialized, &version) ||
192      version < kFirstValidVersion) {
193    return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
194  }
195  if (kCurrentSchemaVersion < version) {
196    DLOG(ERROR) << "ServiceWorkerDatabase has newer schema version"
197                << " than the current latest version: "
198                << version << " vs " << kCurrentSchemaVersion;
199    return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
200  }
201  *out = version;
202  return ServiceWorkerDatabase::STATUS_OK;
203}
204
205ServiceWorkerDatabase::Status ParseRegistrationData(
206    const std::string& serialized,
207    ServiceWorkerDatabase::RegistrationData* out) {
208  DCHECK(out);
209  ServiceWorkerRegistrationData data;
210  if (!data.ParseFromString(serialized))
211    return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
212
213  GURL scope_url(data.scope_url());
214  GURL script_url(data.script_url());
215  if (!scope_url.is_valid() ||
216      !script_url.is_valid() ||
217      scope_url.GetOrigin() != script_url.GetOrigin()) {
218    return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
219  }
220
221  // Convert ServiceWorkerRegistrationData to RegistrationData.
222  out->registration_id = data.registration_id();
223  out->scope = scope_url;
224  out->script = script_url;
225  out->version_id = data.version_id();
226  out->is_active = data.is_active();
227  out->has_fetch_handler = data.has_fetch_handler();
228  out->last_update_check =
229      base::Time::FromInternalValue(data.last_update_check_time());
230  return ServiceWorkerDatabase::STATUS_OK;
231}
232
233ServiceWorkerDatabase::Status ParseResourceRecord(
234    const std::string& serialized,
235    ServiceWorkerDatabase::ResourceRecord* out) {
236  DCHECK(out);
237  ServiceWorkerResourceRecord record;
238  if (!record.ParseFromString(serialized))
239    return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
240
241  GURL url(record.url());
242  if (!url.is_valid())
243    return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
244
245  // Convert ServiceWorkerResourceRecord to ResourceRecord.
246  out->resource_id = record.resource_id();
247  out->url = url;
248  return ServiceWorkerDatabase::STATUS_OK;
249}
250
251ServiceWorkerDatabase::Status LevelDBStatusToStatus(
252    const leveldb::Status& status) {
253  if (status.ok())
254    return ServiceWorkerDatabase::STATUS_OK;
255  else if (status.IsNotFound())
256    return ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND;
257  else if (status.IsIOError())
258    return ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR;
259  else if (status.IsCorruption())
260    return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED;
261  else
262    return ServiceWorkerDatabase::STATUS_ERROR_FAILED;
263}
264
265const char* StatusToString(ServiceWorkerDatabase::Status status) {
266  switch (status) {
267    case ServiceWorkerDatabase::STATUS_OK:
268      return "Database OK";
269    case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND:
270      return "Database not found";
271    case ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR:
272      return "Database IO error";
273    case ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED:
274      return "Database corrupted";
275    case ServiceWorkerDatabase::STATUS_ERROR_FAILED:
276      return "Database operation failed";
277    case ServiceWorkerDatabase::STATUS_ERROR_MAX:
278      NOTREACHED();
279      return "Database unknown error";
280  }
281  NOTREACHED();
282  return "Database unknown error";
283}
284
285}  // namespace
286
287ServiceWorkerDatabase::RegistrationData::RegistrationData()
288    : registration_id(kInvalidServiceWorkerRegistrationId),
289      version_id(kInvalidServiceWorkerVersionId),
290      is_active(false),
291      has_fetch_handler(false) {
292}
293
294ServiceWorkerDatabase::RegistrationData::~RegistrationData() {
295}
296
297ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path)
298    : path_(path),
299      next_avail_registration_id_(0),
300      next_avail_resource_id_(0),
301      next_avail_version_id_(0),
302      state_(UNINITIALIZED) {
303  sequence_checker_.DetachFromSequence();
304}
305
306ServiceWorkerDatabase::~ServiceWorkerDatabase() {
307  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
308  db_.reset();
309}
310
311ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetNextAvailableIds(
312    int64* next_avail_registration_id,
313    int64* next_avail_version_id,
314    int64* next_avail_resource_id) {
315  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
316  DCHECK(next_avail_registration_id);
317  DCHECK(next_avail_version_id);
318  DCHECK(next_avail_resource_id);
319
320  Status status = LazyOpen(false);
321  if (IsNewOrNonexistentDatabase(status)) {
322    *next_avail_registration_id = 0;
323    *next_avail_version_id = 0;
324    *next_avail_resource_id = 0;
325    return STATUS_OK;
326  }
327  if (status != STATUS_OK)
328    return status;
329
330  status = ReadNextAvailableId(kNextRegIdKey, &next_avail_registration_id_);
331  if (status != STATUS_OK)
332    return status;
333  status = ReadNextAvailableId(kNextVerIdKey, &next_avail_version_id_);
334  if (status != STATUS_OK)
335    return status;
336  status = ReadNextAvailableId(kNextResIdKey, &next_avail_resource_id_);
337  if (status != STATUS_OK)
338    return status;
339
340  *next_avail_registration_id = next_avail_registration_id_;
341  *next_avail_version_id = next_avail_version_id_;
342  *next_avail_resource_id = next_avail_resource_id_;
343  return STATUS_OK;
344}
345
346ServiceWorkerDatabase::Status
347ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set<GURL>* origins) {
348  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
349  DCHECK(origins->empty());
350
351  Status status = LazyOpen(false);
352  if (IsNewOrNonexistentDatabase(status))
353    return STATUS_OK;
354  if (status != STATUS_OK)
355    return status;
356
357  scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
358  for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) {
359    status = LevelDBStatusToStatus(itr->status());
360    if (status != STATUS_OK) {
361      HandleReadResult(FROM_HERE, status);
362      origins->clear();
363      return status;
364    }
365
366    std::string origin;
367    if (!RemovePrefix(itr->key().ToString(), kUniqueOriginKey, &origin))
368      break;
369    origins->insert(GURL(origin));
370  }
371
372  HandleReadResult(FROM_HERE, status);
373  return status;
374}
375
376ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin(
377    const GURL& origin,
378    std::vector<RegistrationData>* registrations) {
379  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
380  DCHECK(registrations->empty());
381
382  Status status = LazyOpen(false);
383  if (IsNewOrNonexistentDatabase(status))
384    return STATUS_OK;
385  if (status != STATUS_OK)
386    return status;
387
388  // Create a key prefix for registrations.
389  std::string prefix = base::StringPrintf(
390      "%s%s%c", kRegKeyPrefix, origin.spec().c_str(), kKeySeparator);
391
392  scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
393  for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
394    status = LevelDBStatusToStatus(itr->status());
395    if (status != STATUS_OK) {
396      HandleReadResult(FROM_HERE, status);
397      registrations->clear();
398      return status;
399    }
400
401    if (!RemovePrefix(itr->key().ToString(), prefix, NULL))
402      break;
403
404    RegistrationData registration;
405    status = ParseRegistrationData(itr->value().ToString(), &registration);
406    if (status != STATUS_OK) {
407      HandleReadResult(FROM_HERE, status);
408      registrations->clear();
409      return status;
410    }
411    registrations->push_back(registration);
412  }
413
414  HandleReadResult(FROM_HERE, status);
415  return status;
416}
417
418ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations(
419    std::vector<RegistrationData>* registrations) {
420  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
421  DCHECK(registrations->empty());
422
423  Status status = LazyOpen(false);
424  if (IsNewOrNonexistentDatabase(status))
425    return STATUS_OK;
426  if (status != STATUS_OK)
427    return status;
428
429  scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
430  for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) {
431    status = LevelDBStatusToStatus(itr->status());
432    if (status != STATUS_OK) {
433      HandleReadResult(FROM_HERE, status);
434      registrations->clear();
435      return status;
436    }
437
438    if (!RemovePrefix(itr->key().ToString(), kRegKeyPrefix, NULL))
439      break;
440
441    RegistrationData registration;
442    status = ParseRegistrationData(itr->value().ToString(), &registration);
443    if (status != STATUS_OK) {
444      HandleReadResult(FROM_HERE, status);
445      registrations->clear();
446      return status;
447    }
448    registrations->push_back(registration);
449  }
450
451  HandleReadResult(FROM_HERE, status);
452  return status;
453}
454
455ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistration(
456    int64 registration_id,
457    const GURL& origin,
458    RegistrationData* registration,
459    std::vector<ResourceRecord>* resources) {
460  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
461  DCHECK(registration);
462  DCHECK(resources);
463
464  Status status = LazyOpen(false);
465  if (IsNewOrNonexistentDatabase(status) || status != STATUS_OK)
466    return status;
467
468  RegistrationData value;
469  status = ReadRegistrationData(registration_id, origin, &value);
470  if (status != STATUS_OK)
471    return status;
472
473  status = ReadResourceRecords(value.version_id, resources);
474  if (status != STATUS_OK)
475    return status;
476
477  *registration = value;
478  return STATUS_OK;
479}
480
481ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteRegistration(
482    const RegistrationData& registration,
483    const std::vector<ResourceRecord>& resources,
484    int64* deleted_version_id,
485    std::vector<int64>* newly_purgeable_resources) {
486  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
487  *deleted_version_id = kInvalidServiceWorkerVersionId;
488  Status status = LazyOpen(true);
489  if (status != STATUS_OK)
490    return status;
491
492  leveldb::WriteBatch batch;
493  BumpNextRegistrationIdIfNeeded(registration.registration_id, &batch);
494  BumpNextVersionIdIfNeeded(registration.version_id, &batch);
495
496  PutUniqueOriginToBatch(registration.scope.GetOrigin(), &batch);
497  PutRegistrationDataToBatch(registration, &batch);
498
499  // Used for avoiding multiple writes for the same resource id or url.
500  std::set<int64> pushed_resources;
501  std::set<GURL> pushed_urls;
502  for (std::vector<ResourceRecord>::const_iterator itr = resources.begin();
503       itr != resources.end(); ++itr) {
504    if (!itr->url.is_valid())
505      return STATUS_ERROR_FAILED;
506
507    // Duplicated resource id or url should not exist.
508    DCHECK(pushed_resources.insert(itr->resource_id).second);
509    DCHECK(pushed_urls.insert(itr->url).second);
510
511    PutResourceRecordToBatch(*itr, registration.version_id, &batch);
512
513    // Delete a resource from the uncommitted list.
514    batch.Delete(CreateResourceIdKey(
515        kUncommittedResIdKeyPrefix, itr->resource_id));
516    // Delete from the purgeable list in case this version was once deleted.
517    batch.Delete(
518        CreateResourceIdKey(kPurgeableResIdKeyPrefix, itr->resource_id));
519  }
520
521  // Retrieve a previous version to sweep purgeable resources.
522  RegistrationData old_registration;
523  status = ReadRegistrationData(registration.registration_id,
524                                registration.scope.GetOrigin(),
525                                &old_registration);
526  if (status != STATUS_OK && status != STATUS_ERROR_NOT_FOUND)
527    return status;
528  if (status == STATUS_OK) {
529    DCHECK_LT(old_registration.version_id, registration.version_id);
530    *deleted_version_id = old_registration.version_id;
531    status = DeleteResourceRecords(
532        old_registration.version_id, newly_purgeable_resources, &batch);
533    if (status != STATUS_OK)
534      return status;
535
536    // Currently resource sharing across versions and registrations is not
537    // supported, so resource ids should not be overlapped between
538    // |registration| and |old_registration|.
539    std::set<int64> deleted_resources(newly_purgeable_resources->begin(),
540                                      newly_purgeable_resources->end());
541    DCHECK(base::STLSetIntersection<std::set<int64> >(
542        pushed_resources, deleted_resources).empty());
543  }
544
545  return WriteBatch(&batch);
546}
547
548ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateVersionToActive(
549    int64 registration_id,
550    const GURL& origin) {
551  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
552  Status status = LazyOpen(false);
553  if (IsNewOrNonexistentDatabase(status))
554    return STATUS_ERROR_NOT_FOUND;
555  if (status != STATUS_OK)
556    return status;
557  if (!origin.is_valid())
558    return STATUS_ERROR_FAILED;
559
560  RegistrationData registration;
561  status = ReadRegistrationData(registration_id, origin, &registration);
562  if (status != STATUS_OK)
563    return status;
564
565  registration.is_active = true;
566
567  leveldb::WriteBatch batch;
568  PutRegistrationDataToBatch(registration, &batch);
569  return WriteBatch(&batch);
570}
571
572ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateLastCheckTime(
573    int64 registration_id,
574    const GURL& origin,
575    const base::Time& time) {
576  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
577  Status status = LazyOpen(false);
578  if (IsNewOrNonexistentDatabase(status))
579    return STATUS_ERROR_NOT_FOUND;
580  if (status != STATUS_OK)
581    return status;
582  if (!origin.is_valid())
583    return STATUS_ERROR_FAILED;
584
585  RegistrationData registration;
586  status = ReadRegistrationData(registration_id, origin, &registration);
587  if (status != STATUS_OK)
588    return status;
589
590  registration.last_update_check = time;
591
592  leveldb::WriteBatch batch;
593  PutRegistrationDataToBatch(registration, &batch);
594  return WriteBatch(&batch);
595}
596
597ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteRegistration(
598    int64 registration_id,
599    const GURL& origin,
600    int64* version_id,
601    std::vector<int64>* newly_purgeable_resources) {
602  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
603  *version_id = kInvalidServiceWorkerVersionId;
604  Status status = LazyOpen(false);
605  if (IsNewOrNonexistentDatabase(status))
606    return STATUS_OK;
607  if (status != STATUS_OK)
608    return status;
609  if (!origin.is_valid())
610    return STATUS_ERROR_FAILED;
611
612  leveldb::WriteBatch batch;
613
614  // Remove |origin| from unique origins if a registration specified by
615  // |registration_id| is the only one for |origin|.
616  // TODO(nhiroki): Check the uniqueness by more efficient way.
617  std::vector<RegistrationData> registrations;
618  status = GetRegistrationsForOrigin(origin, &registrations);
619  if (status != STATUS_OK)
620    return status;
621
622  if (registrations.size() == 1 &&
623      registrations[0].registration_id == registration_id) {
624    batch.Delete(CreateUniqueOriginKey(origin));
625  }
626
627  // Delete a registration specified by |registration_id|.
628  batch.Delete(CreateRegistrationKey(registration_id, origin));
629
630  // Delete resource records associated with the registration.
631  for (std::vector<RegistrationData>::const_iterator itr =
632           registrations.begin(); itr != registrations.end(); ++itr) {
633    if (itr->registration_id == registration_id) {
634      *version_id = itr->version_id;
635      status = DeleteResourceRecords(
636          itr->version_id, newly_purgeable_resources, &batch);
637      if (status != STATUS_OK)
638        return status;
639      break;
640    }
641  }
642
643  return WriteBatch(&batch);
644}
645
646ServiceWorkerDatabase::Status
647ServiceWorkerDatabase::GetUncommittedResourceIds(std::set<int64>* ids) {
648  return ReadResourceIds(kUncommittedResIdKeyPrefix, ids);
649}
650
651ServiceWorkerDatabase::Status
652ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set<int64>& ids) {
653  return WriteResourceIds(kUncommittedResIdKeyPrefix, ids);
654}
655
656ServiceWorkerDatabase::Status
657ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set<int64>& ids) {
658  return DeleteResourceIds(kUncommittedResIdKeyPrefix, ids);
659}
660
661ServiceWorkerDatabase::Status
662ServiceWorkerDatabase::GetPurgeableResourceIds(std::set<int64>* ids) {
663  return ReadResourceIds(kPurgeableResIdKeyPrefix, ids);
664}
665
666ServiceWorkerDatabase::Status
667ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set<int64>& ids) {
668  return WriteResourceIds(kPurgeableResIdKeyPrefix, ids);
669}
670
671ServiceWorkerDatabase::Status
672ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set<int64>& ids) {
673  return DeleteResourceIds(kPurgeableResIdKeyPrefix, ids);
674}
675
676ServiceWorkerDatabase::Status
677ServiceWorkerDatabase::PurgeUncommittedResourceIds(
678    const std::set<int64>& ids) {
679  leveldb::WriteBatch batch;
680  Status status = DeleteResourceIdsInBatch(
681      kUncommittedResIdKeyPrefix, ids, &batch);
682  if (status != STATUS_OK)
683    return status;
684  status = WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix, ids, &batch);
685  if (status != STATUS_OK)
686    return status;
687  return WriteBatch(&batch);
688}
689
690ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteAllDataForOrigin(
691    const GURL& origin,
692    std::vector<int64>* newly_purgeable_resources) {
693  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
694  Status status = LazyOpen(false);
695  if (IsNewOrNonexistentDatabase(status))
696    return STATUS_OK;
697  if (status != STATUS_OK)
698    return status;
699  if (!origin.is_valid())
700    return STATUS_ERROR_FAILED;
701
702  leveldb::WriteBatch batch;
703
704  // Delete from the unique origin list.
705  batch.Delete(CreateUniqueOriginKey(origin));
706
707  std::vector<RegistrationData> registrations;
708  status = GetRegistrationsForOrigin(origin, &registrations);
709  if (status != STATUS_OK)
710    return status;
711
712  // Delete registrations and resource records.
713  for (std::vector<RegistrationData>::const_iterator itr =
714           registrations.begin(); itr != registrations.end(); ++itr) {
715    batch.Delete(CreateRegistrationKey(itr->registration_id, origin));
716    status = DeleteResourceRecords(
717        itr->version_id, newly_purgeable_resources, &batch);
718    if (status != STATUS_OK)
719      return status;
720  }
721
722  return WriteBatch(&batch);
723}
724
725ServiceWorkerDatabase::Status ServiceWorkerDatabase::DestroyDatabase() {
726  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
727  Disable(FROM_HERE, STATUS_OK);
728  return LevelDBStatusToStatus(
729      leveldb::DestroyDB(path_.AsUTF8Unsafe(), leveldb::Options()));
730}
731
732ServiceWorkerDatabase::Status ServiceWorkerDatabase::LazyOpen(
733    bool create_if_missing) {
734  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
735
736  // Do not try to open a database if we tried and failed once.
737  if (state_ == DISABLED)
738    return STATUS_ERROR_FAILED;
739  if (IsOpen())
740    return STATUS_OK;
741
742  // When |path_| is empty, open a database in-memory.
743  bool use_in_memory_db = path_.empty();
744
745  if (!create_if_missing) {
746    // Avoid opening a database if it does not exist at the |path_|.
747    if (use_in_memory_db ||
748        !base::PathExists(path_) ||
749        base::IsDirectoryEmpty(path_)) {
750      return STATUS_ERROR_NOT_FOUND;
751    }
752  }
753
754  leveldb::Options options;
755  options.create_if_missing = create_if_missing;
756  if (use_in_memory_db) {
757    env_.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
758    options.env = env_.get();
759  }
760
761  leveldb::DB* db = NULL;
762  Status status = LevelDBStatusToStatus(
763      leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db));
764  HandleOpenResult(FROM_HERE, status);
765  if (status != STATUS_OK) {
766    DCHECK(!db);
767    // TODO(nhiroki): Should we retry to open the database?
768    return status;
769  }
770  db_.reset(db);
771
772  int64 db_version;
773  status = ReadDatabaseVersion(&db_version);
774  if (status != STATUS_OK)
775    return status;
776  DCHECK_LE(0, db_version);
777  if (db_version > 0)
778    state_ = INITIALIZED;
779  return STATUS_OK;
780}
781
782bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase(
783    ServiceWorkerDatabase::Status status) {
784  if (status == STATUS_ERROR_NOT_FOUND)
785    return true;
786  if (status == STATUS_OK && state_ == UNINITIALIZED)
787    return true;
788  return false;
789}
790
791ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadNextAvailableId(
792    const char* id_key,
793    int64* next_avail_id) {
794  DCHECK(id_key);
795  DCHECK(next_avail_id);
796
797  std::string value;
798  Status status = LevelDBStatusToStatus(
799      db_->Get(leveldb::ReadOptions(), id_key, &value));
800  if (status == STATUS_ERROR_NOT_FOUND) {
801    // Nobody has gotten the next resource id for |id_key|.
802    *next_avail_id = 0;
803    HandleReadResult(FROM_HERE, STATUS_OK);
804    return STATUS_OK;
805  } else if (status != STATUS_OK) {
806    HandleReadResult(FROM_HERE, status);
807    return status;
808  }
809
810  status = ParseId(value, next_avail_id);
811  HandleReadResult(FROM_HERE, status);
812  return status;
813}
814
815ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData(
816    int64 registration_id,
817    const GURL& origin,
818    RegistrationData* registration) {
819  DCHECK(registration);
820
821  const std::string key = CreateRegistrationKey(registration_id, origin);
822  std::string value;
823  Status status = LevelDBStatusToStatus(
824      db_->Get(leveldb::ReadOptions(), key, &value));
825  if (status != STATUS_OK) {
826    HandleReadResult(
827        FROM_HERE,
828        status == STATUS_ERROR_NOT_FOUND ? STATUS_OK : status);
829    return status;
830  }
831
832  status = ParseRegistrationData(value, registration);
833  HandleReadResult(FROM_HERE, status);
834  return status;
835}
836
837ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceRecords(
838    int64 version_id,
839    std::vector<ResourceRecord>* resources) {
840  DCHECK(resources->empty());
841
842  Status status = STATUS_OK;
843  const std::string prefix = CreateResourceRecordKeyPrefix(version_id);
844
845  scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
846  for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
847    Status status = LevelDBStatusToStatus(itr->status());
848    if (status != STATUS_OK) {
849      HandleReadResult(FROM_HERE, status);
850      resources->clear();
851      return status;
852    }
853
854    if (!RemovePrefix(itr->key().ToString(), prefix, NULL))
855      break;
856
857    ResourceRecord resource;
858    status = ParseResourceRecord(itr->value().ToString(), &resource);
859    if (status != STATUS_OK) {
860      HandleReadResult(FROM_HERE, status);
861      resources->clear();
862      return status;
863    }
864    resources->push_back(resource);
865  }
866
867  HandleReadResult(FROM_HERE, status);
868  return status;
869}
870
871ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords(
872    int64 version_id,
873    std::vector<int64>* newly_purgeable_resources,
874    leveldb::WriteBatch* batch) {
875  DCHECK(batch);
876
877  Status status = STATUS_OK;
878  const std::string prefix = CreateResourceRecordKeyPrefix(version_id);
879
880  scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
881  for (itr->Seek(prefix); itr->Valid(); itr->Next()) {
882    status = LevelDBStatusToStatus(itr->status());
883    if (status != STATUS_OK) {
884      HandleReadResult(FROM_HERE, status);
885      return status;
886    }
887
888    const std::string key = itr->key().ToString();
889    std::string unprefixed;
890    if (!RemovePrefix(key, prefix, &unprefixed))
891      break;
892
893    int64 resource_id;
894    status = ParseId(unprefixed, &resource_id);
895    if (status != STATUS_OK) {
896      HandleReadResult(FROM_HERE, status);
897      return status;
898    }
899
900    // Remove a resource record.
901    batch->Delete(key);
902
903    // Currently resource sharing across versions and registrations is not
904    // supported, so we can purge this without caring about it.
905    PutPurgeableResourceIdToBatch(resource_id, batch);
906    newly_purgeable_resources->push_back(resource_id);
907  }
908
909  HandleReadResult(FROM_HERE, status);
910  return status;
911}
912
913ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceIds(
914    const char* id_key_prefix,
915    std::set<int64>* ids) {
916  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
917  DCHECK(id_key_prefix);
918  DCHECK(ids->empty());
919
920  Status status = LazyOpen(false);
921  if (IsNewOrNonexistentDatabase(status))
922    return STATUS_OK;
923  if (status != STATUS_OK)
924    return status;
925
926  scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
927  for (itr->Seek(id_key_prefix); itr->Valid(); itr->Next()) {
928    status = LevelDBStatusToStatus(itr->status());
929    if (status != STATUS_OK) {
930      HandleReadResult(FROM_HERE, status);
931      ids->clear();
932      return status;
933    }
934
935    std::string unprefixed;
936    if (!RemovePrefix(itr->key().ToString(), id_key_prefix, &unprefixed))
937      break;
938
939    int64 resource_id;
940    status = ParseId(unprefixed, &resource_id);
941    if (status != STATUS_OK) {
942      HandleReadResult(FROM_HERE, status);
943      ids->clear();
944      return status;
945    }
946    ids->insert(resource_id);
947  }
948
949  HandleReadResult(FROM_HERE, status);
950  return status;
951}
952
953ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIds(
954    const char* id_key_prefix,
955    const std::set<int64>& ids) {
956  leveldb::WriteBatch batch;
957  Status status = WriteResourceIdsInBatch(id_key_prefix, ids, &batch);
958  if (status != STATUS_OK)
959    return status;
960  return WriteBatch(&batch);
961}
962
963ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIdsInBatch(
964    const char* id_key_prefix,
965    const std::set<int64>& ids,
966    leveldb::WriteBatch* batch) {
967  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
968  DCHECK(id_key_prefix);
969
970  Status status = LazyOpen(true);
971  if (status != STATUS_OK)
972    return status;
973
974  if (ids.empty())
975    return STATUS_OK;
976  for (std::set<int64>::const_iterator itr = ids.begin();
977       itr != ids.end(); ++itr) {
978    // Value should be empty.
979    batch->Put(CreateResourceIdKey(id_key_prefix, *itr), "");
980  }
981  // std::set is sorted, so the last element is the largest.
982  BumpNextResourceIdIfNeeded(*ids.rbegin(), batch);
983  return STATUS_OK;
984}
985
986ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIds(
987    const char* id_key_prefix,
988    const std::set<int64>& ids) {
989  leveldb::WriteBatch batch;
990  Status status = DeleteResourceIdsInBatch(id_key_prefix, ids, &batch);
991  if (status != STATUS_OK)
992    return status;
993  return WriteBatch(&batch);
994}
995
996ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIdsInBatch(
997    const char* id_key_prefix,
998    const std::set<int64>& ids,
999    leveldb::WriteBatch* batch) {
1000  DCHECK(sequence_checker_.CalledOnValidSequencedThread());
1001  DCHECK(id_key_prefix);
1002
1003  Status status = LazyOpen(false);
1004  if (IsNewOrNonexistentDatabase(status))
1005    return STATUS_OK;
1006  if (status != STATUS_OK)
1007    return status;
1008
1009  for (std::set<int64>::const_iterator itr = ids.begin();
1010       itr != ids.end(); ++itr) {
1011    batch->Delete(CreateResourceIdKey(id_key_prefix, *itr));
1012  }
1013  return STATUS_OK;
1014}
1015
1016ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadDatabaseVersion(
1017    int64* db_version) {
1018  std::string value;
1019  Status status = LevelDBStatusToStatus(
1020      db_->Get(leveldb::ReadOptions(), kDatabaseVersionKey, &value));
1021  if (status == STATUS_ERROR_NOT_FOUND) {
1022    // The database hasn't been initialized yet.
1023    *db_version = 0;
1024    HandleReadResult(FROM_HERE, STATUS_OK);
1025    return STATUS_OK;
1026  }
1027
1028  if (status != STATUS_OK) {
1029    HandleReadResult(FROM_HERE, status);
1030    return status;
1031  }
1032
1033  status = ParseDatabaseVersion(value, db_version);
1034  HandleReadResult(FROM_HERE, status);
1035  return status;
1036}
1037
1038ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteBatch(
1039    leveldb::WriteBatch* batch) {
1040  DCHECK(batch);
1041  DCHECK_NE(DISABLED, state_);
1042
1043  if (state_ == UNINITIALIZED) {
1044    // Write the database schema version.
1045    batch->Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion));
1046    state_ = INITIALIZED;
1047  }
1048
1049  Status status = LevelDBStatusToStatus(
1050      db_->Write(leveldb::WriteOptions(), batch));
1051  HandleWriteResult(FROM_HERE, status);
1052  return status;
1053}
1054
1055void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded(
1056    int64 used_id, leveldb::WriteBatch* batch) {
1057  DCHECK(batch);
1058  if (next_avail_registration_id_ <= used_id) {
1059    next_avail_registration_id_ = used_id + 1;
1060    batch->Put(kNextRegIdKey, base::Int64ToString(next_avail_registration_id_));
1061  }
1062}
1063
1064void ServiceWorkerDatabase::BumpNextResourceIdIfNeeded(
1065    int64 used_id, leveldb::WriteBatch* batch) {
1066  DCHECK(batch);
1067  if (next_avail_resource_id_ <= used_id) {
1068    next_avail_resource_id_ = used_id + 1;
1069    batch->Put(kNextResIdKey, base::Int64ToString(next_avail_resource_id_));
1070  }
1071}
1072
1073void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded(
1074    int64 used_id, leveldb::WriteBatch* batch) {
1075  DCHECK(batch);
1076  if (next_avail_version_id_ <= used_id) {
1077    next_avail_version_id_ = used_id + 1;
1078    batch->Put(kNextVerIdKey, base::Int64ToString(next_avail_version_id_));
1079  }
1080}
1081
1082bool ServiceWorkerDatabase::IsOpen() {
1083  return db_ != NULL;
1084}
1085
1086void ServiceWorkerDatabase::Disable(
1087    const tracked_objects::Location& from_here,
1088    Status status) {
1089  if (status != STATUS_OK) {
1090    DLOG(ERROR) << "Failed at: " << from_here.ToString()
1091                << " with error: " << StatusToString(status);
1092    DLOG(ERROR) << "ServiceWorkerDatabase is disabled.";
1093  }
1094  state_ = DISABLED;
1095  db_.reset();
1096}
1097
1098void ServiceWorkerDatabase::HandleOpenResult(
1099    const tracked_objects::Location& from_here,
1100    Status status) {
1101  if (status != STATUS_OK)
1102    Disable(from_here, status);
1103  ServiceWorkerMetrics::CountOpenDatabaseResult(status);
1104}
1105
1106void ServiceWorkerDatabase::HandleReadResult(
1107    const tracked_objects::Location& from_here,
1108    Status status) {
1109  if (status != STATUS_OK)
1110    Disable(from_here, status);
1111  ServiceWorkerMetrics::CountReadDatabaseResult(status);
1112}
1113
1114void ServiceWorkerDatabase::HandleWriteResult(
1115    const tracked_objects::Location& from_here,
1116    Status status) {
1117  if (status != STATUS_OK)
1118    Disable(from_here, status);
1119  ServiceWorkerMetrics::CountWriteDatabaseResult(status);
1120}
1121
1122}  // namespace content
1123