1// Copyright 2013 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_storage.h"
6
7#include <string>
8
9#include "base/bind_helpers.h"
10#include "base/message_loop/message_loop.h"
11#include "base/sequenced_task_runner.h"
12#include "base/task_runner_util.h"
13#include "content/browser/service_worker/service_worker_context_core.h"
14#include "content/browser/service_worker/service_worker_disk_cache.h"
15#include "content/browser/service_worker/service_worker_histograms.h"
16#include "content/browser/service_worker/service_worker_info.h"
17#include "content/browser/service_worker/service_worker_registration.h"
18#include "content/browser/service_worker/service_worker_utils.h"
19#include "content/browser/service_worker/service_worker_version.h"
20#include "content/common/service_worker/service_worker_types.h"
21#include "content/public/browser/browser_thread.h"
22#include "net/base/net_errors.h"
23#include "webkit/browser/quota/quota_manager_proxy.h"
24
25namespace content {
26
27namespace {
28
29void RunSoon(const tracked_objects::Location& from_here,
30             const base::Closure& closure) {
31  base::MessageLoop::current()->PostTask(from_here, closure);
32}
33
34void CompleteFindNow(
35    const scoped_refptr<ServiceWorkerRegistration>& registration,
36    ServiceWorkerStatusCode status,
37    const ServiceWorkerStorage::FindRegistrationCallback& callback) {
38  callback.Run(status, registration);
39}
40
41void CompleteFindSoon(
42    const tracked_objects::Location& from_here,
43    const scoped_refptr<ServiceWorkerRegistration>& registration,
44    ServiceWorkerStatusCode status,
45    const ServiceWorkerStorage::FindRegistrationCallback& callback) {
46  RunSoon(from_here, base::Bind(callback, status, registration));
47}
48
49const base::FilePath::CharType kServiceWorkerDirectory[] =
50    FILE_PATH_LITERAL("Service Worker");
51const base::FilePath::CharType kDatabaseName[] =
52    FILE_PATH_LITERAL("Database");
53const base::FilePath::CharType kDiskCacheName[] =
54    FILE_PATH_LITERAL("Cache");
55
56const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
57const int kMaxDiskCacheSize = 250 * 1024 * 1024;
58
59void EmptyCompletionCallback(int) {}
60
61ServiceWorkerStatusCode DatabaseStatusToStatusCode(
62    ServiceWorkerDatabase::Status status) {
63  switch (status) {
64    case ServiceWorkerDatabase::STATUS_OK:
65      return SERVICE_WORKER_OK;
66    case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND:
67      return SERVICE_WORKER_ERROR_NOT_FOUND;
68    case ServiceWorkerDatabase::STATUS_ERROR_MAX:
69      NOTREACHED();
70    default:
71      return SERVICE_WORKER_ERROR_FAILED;
72  }
73}
74
75}  // namespace
76
77ServiceWorkerStorage::InitialData::InitialData()
78    : next_registration_id(kInvalidServiceWorkerRegistrationId),
79      next_version_id(kInvalidServiceWorkerVersionId),
80      next_resource_id(kInvalidServiceWorkerResourceId) {
81}
82
83ServiceWorkerStorage::InitialData::~InitialData() {
84}
85
86ServiceWorkerStorage::ServiceWorkerStorage(
87    const base::FilePath& path,
88    base::WeakPtr<ServiceWorkerContextCore> context,
89    base::SequencedTaskRunner* database_task_runner,
90    base::MessageLoopProxy* disk_cache_thread,
91    quota::QuotaManagerProxy* quota_manager_proxy)
92    : next_registration_id_(kInvalidServiceWorkerRegistrationId),
93      next_version_id_(kInvalidServiceWorkerVersionId),
94      next_resource_id_(kInvalidServiceWorkerResourceId),
95      state_(UNINITIALIZED),
96      context_(context),
97      database_task_runner_(database_task_runner),
98      disk_cache_thread_(disk_cache_thread),
99      quota_manager_proxy_(quota_manager_proxy),
100      is_purge_pending_(false),
101      weak_factory_(this) {
102  if (!path.empty())
103    path_ = path.Append(kServiceWorkerDirectory);
104  database_.reset(new ServiceWorkerDatabase(GetDatabasePath()));
105}
106
107ServiceWorkerStorage::~ServiceWorkerStorage() {
108  weak_factory_.InvalidateWeakPtrs();
109  database_task_runner_->DeleteSoon(FROM_HERE, database_.release());
110}
111
112void ServiceWorkerStorage::FindRegistrationForDocument(
113    const GURL& document_url,
114    const FindRegistrationCallback& callback) {
115  DCHECK(!document_url.has_ref());
116  if (!LazyInitialize(base::Bind(
117          &ServiceWorkerStorage::FindRegistrationForDocument,
118          weak_factory_.GetWeakPtr(), document_url, callback))) {
119    if (state_ != INITIALIZING || !context_) {
120      CompleteFindNow(scoped_refptr<ServiceWorkerRegistration>(),
121                      SERVICE_WORKER_ERROR_FAILED, callback);
122    }
123    return;
124  }
125  DCHECK_EQ(INITIALIZED, state_);
126
127  // See if there are any stored registrations for the origin.
128  if (!ContainsKey(registered_origins_, document_url.GetOrigin())) {
129    // Look for something currently being installed.
130    scoped_refptr<ServiceWorkerRegistration> installing_registration =
131        FindInstallingRegistrationForDocument(document_url);
132    CompleteFindNow(
133        installing_registration,
134        installing_registration ?
135            SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
136        callback);
137    return;
138  }
139
140  database_task_runner_->PostTask(
141      FROM_HERE,
142      base::Bind(
143          &FindForDocumentInDB,
144          database_.get(),
145          base::MessageLoopProxy::current(),
146          document_url,
147          base::Bind(&ServiceWorkerStorage::DidFindRegistrationForDocument,
148                     weak_factory_.GetWeakPtr(), document_url, callback)));
149}
150
151void ServiceWorkerStorage::FindRegistrationForPattern(
152    const GURL& scope,
153    const FindRegistrationCallback& callback) {
154  if (!LazyInitialize(base::Bind(
155          &ServiceWorkerStorage::FindRegistrationForPattern,
156          weak_factory_.GetWeakPtr(), scope, callback))) {
157    if (state_ != INITIALIZING || !context_) {
158      CompleteFindSoon(FROM_HERE, scoped_refptr<ServiceWorkerRegistration>(),
159                       SERVICE_WORKER_ERROR_FAILED, callback);
160    }
161    return;
162  }
163  DCHECK_EQ(INITIALIZED, state_);
164
165  // See if there are any stored registrations for the origin.
166  if (!ContainsKey(registered_origins_, scope.GetOrigin())) {
167    // Look for something currently being installed.
168    scoped_refptr<ServiceWorkerRegistration> installing_registration =
169        FindInstallingRegistrationForPattern(scope);
170    CompleteFindSoon(
171        FROM_HERE, installing_registration,
172        installing_registration ?
173            SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
174        callback);
175    return;
176  }
177
178  database_task_runner_->PostTask(
179      FROM_HERE,
180      base::Bind(
181          &FindForPatternInDB,
182          database_.get(),
183          base::MessageLoopProxy::current(),
184          scope,
185          base::Bind(&ServiceWorkerStorage::DidFindRegistrationForPattern,
186                     weak_factory_.GetWeakPtr(), scope, callback)));
187}
188
189void ServiceWorkerStorage::FindRegistrationForId(
190    int64 registration_id,
191    const GURL& origin,
192    const FindRegistrationCallback& callback) {
193  if (!LazyInitialize(base::Bind(
194          &ServiceWorkerStorage::FindRegistrationForId,
195          weak_factory_.GetWeakPtr(), registration_id, origin, callback))) {
196    if (state_ != INITIALIZING || !context_) {
197      CompleteFindNow(scoped_refptr<ServiceWorkerRegistration>(),
198                      SERVICE_WORKER_ERROR_FAILED, callback);
199    }
200    return;
201  }
202  DCHECK_EQ(INITIALIZED, state_);
203
204  // See if there are any stored registrations for the origin.
205  if (!ContainsKey(registered_origins_, origin)) {
206    // Look for something currently being installed.
207    scoped_refptr<ServiceWorkerRegistration> installing_registration =
208        FindInstallingRegistrationForId(registration_id);
209    CompleteFindNow(
210        installing_registration,
211        installing_registration ?
212            SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
213        callback);
214    return;
215  }
216
217  scoped_refptr<ServiceWorkerRegistration> registration =
218      context_->GetLiveRegistration(registration_id);
219  if (registration) {
220    CompleteFindNow(registration, SERVICE_WORKER_OK, callback);
221    return;
222  }
223
224  database_task_runner_->PostTask(
225      FROM_HERE,
226      base::Bind(&FindForIdInDB,
227                 database_.get(),
228                 base::MessageLoopProxy::current(),
229                 registration_id, origin,
230                 base::Bind(&ServiceWorkerStorage::DidFindRegistrationForId,
231                            weak_factory_.GetWeakPtr(), callback)));
232}
233
234void ServiceWorkerStorage::GetAllRegistrations(
235    const GetAllRegistrationInfosCallback& callback) {
236  if (!LazyInitialize(base::Bind(
237          &ServiceWorkerStorage::GetAllRegistrations,
238          weak_factory_.GetWeakPtr(), callback))) {
239    if (state_ != INITIALIZING || !context_) {
240      RunSoon(FROM_HERE, base::Bind(
241          callback, std::vector<ServiceWorkerRegistrationInfo>()));
242    }
243    return;
244  }
245  DCHECK_EQ(INITIALIZED, state_);
246
247  RegistrationList* registrations = new RegistrationList;
248  PostTaskAndReplyWithResult(
249      database_task_runner_,
250      FROM_HERE,
251      base::Bind(&ServiceWorkerDatabase::GetAllRegistrations,
252                 base::Unretained(database_.get()),
253                 base::Unretained(registrations)),
254      base::Bind(&ServiceWorkerStorage::DidGetAllRegistrations,
255                 weak_factory_.GetWeakPtr(),
256                 callback,
257                 base::Owned(registrations)));
258}
259
260void ServiceWorkerStorage::StoreRegistration(
261    ServiceWorkerRegistration* registration,
262    ServiceWorkerVersion* version,
263    const StatusCallback& callback) {
264  DCHECK(registration);
265  DCHECK(version);
266
267  DCHECK(state_ == INITIALIZED || state_ == DISABLED);
268  if (state_ != INITIALIZED || !context_) {
269    RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED));
270    return;
271  }
272
273  ServiceWorkerDatabase::RegistrationData data;
274  data.registration_id = registration->id();
275  data.scope = registration->pattern();
276  data.script = registration->script_url();
277  data.has_fetch_handler = true;
278  data.version_id = version->version_id();
279  data.last_update_check = base::Time::Now();
280  data.is_active = false;  // initially stored in the waiting state
281
282  ResourceList resources;
283  version->script_cache_map()->GetResources(&resources);
284
285  database_task_runner_->PostTask(
286      FROM_HERE,
287      base::Bind(&WriteRegistrationInDB,
288                 database_.get(),
289                 base::MessageLoopProxy::current(),
290                 data, resources,
291                 base::Bind(&ServiceWorkerStorage::DidStoreRegistration,
292                            weak_factory_.GetWeakPtr(),
293                            callback)));
294}
295
296void ServiceWorkerStorage::UpdateToActiveState(
297    ServiceWorkerRegistration* registration,
298    const StatusCallback& callback) {
299  DCHECK(registration);
300
301  DCHECK(state_ == INITIALIZED || state_ == DISABLED);
302  if (state_ != INITIALIZED || !context_) {
303    RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED));
304    return;
305  }
306
307  PostTaskAndReplyWithResult(
308      database_task_runner_,
309      FROM_HERE,
310      base::Bind(&ServiceWorkerDatabase::UpdateVersionToActive,
311                 base::Unretained(database_.get()),
312                 registration->id(),
313                 registration->script_url().GetOrigin()),
314      base::Bind(&ServiceWorkerStorage::DidUpdateToActiveState,
315                 weak_factory_.GetWeakPtr(),
316                 callback));
317}
318
319void ServiceWorkerStorage::DeleteRegistration(
320    int64 registration_id,
321    const GURL& origin,
322    const StatusCallback& callback) {
323  DCHECK(state_ == INITIALIZED || state_ == DISABLED);
324  if (state_ != INITIALIZED || !context_) {
325    RunSoon(FROM_HERE, base::Bind(callback, SERVICE_WORKER_ERROR_FAILED));
326    return;
327  }
328
329  database_task_runner_->PostTask(
330      FROM_HERE,
331      base::Bind(&DeleteRegistrationFromDB,
332                 database_.get(),
333                 base::MessageLoopProxy::current(),
334                 registration_id, origin,
335                 base::Bind(&ServiceWorkerStorage::DidDeleteRegistration,
336                            weak_factory_.GetWeakPtr(), origin, callback)));
337
338  // TODO(michaeln): Either its instance should also be
339  // removed from liveregistrations map or the live object
340  // should marked as deleted in some way and not 'findable'
341  // thereafter.
342}
343
344scoped_ptr<ServiceWorkerResponseReader>
345ServiceWorkerStorage::CreateResponseReader(int64 response_id) {
346  return make_scoped_ptr(
347      new ServiceWorkerResponseReader(response_id, disk_cache()));
348}
349
350scoped_ptr<ServiceWorkerResponseWriter>
351ServiceWorkerStorage::CreateResponseWriter(int64 response_id) {
352  return make_scoped_ptr(
353      new ServiceWorkerResponseWriter(response_id, disk_cache()));
354}
355
356void ServiceWorkerStorage::StoreUncommittedReponseId(int64 id) {
357  DCHECK_NE(kInvalidServiceWorkerResponseId, id);
358  database_task_runner_->PostTask(
359      FROM_HERE,
360      base::Bind(base::IgnoreResult(
361          &ServiceWorkerDatabase::WriteUncommittedResourceIds),
362          base::Unretained(database_.get()),
363          std::set<int64>(&id, &id + 1)));
364}
365
366void ServiceWorkerStorage::DoomUncommittedResponse(int64 id) {
367  DCHECK_NE(kInvalidServiceWorkerResponseId, id);
368  database_task_runner_->PostTask(
369      FROM_HERE,
370      base::Bind(base::IgnoreResult(
371          &ServiceWorkerDatabase::PurgeUncommittedResourceIds),
372          base::Unretained(database_.get()),
373          std::set<int64>(&id, &id + 1)));
374  StartPurgingResources(std::vector<int64>(1, id));
375}
376
377int64 ServiceWorkerStorage::NewRegistrationId() {
378  if (state_ == DISABLED)
379    return kInvalidServiceWorkerRegistrationId;
380  DCHECK_EQ(INITIALIZED, state_);
381  return next_registration_id_++;
382}
383
384int64 ServiceWorkerStorage::NewVersionId() {
385  if (state_ == DISABLED)
386    return kInvalidServiceWorkerVersionId;
387  DCHECK_EQ(INITIALIZED, state_);
388  return next_version_id_++;
389}
390
391int64 ServiceWorkerStorage::NewResourceId() {
392  if (state_ == DISABLED)
393    return kInvalidServiceWorkerResourceId;
394  DCHECK_EQ(INITIALIZED, state_);
395  return next_resource_id_++;
396}
397
398void ServiceWorkerStorage::NotifyInstallingRegistration(
399      ServiceWorkerRegistration* registration) {
400  installing_registrations_[registration->id()] = registration;
401}
402
403void ServiceWorkerStorage::NotifyDoneInstallingRegistration(
404      ServiceWorkerRegistration* registration,
405      ServiceWorkerVersion* version,
406      ServiceWorkerStatusCode status) {
407  installing_registrations_.erase(registration->id());
408  if (status != SERVICE_WORKER_OK && version) {
409    ResourceList resources;
410    version->script_cache_map()->GetResources(&resources);
411
412    std::set<int64> ids;
413    for (size_t i = 0; i < resources.size(); ++i)
414      ids.insert(resources[i].resource_id);
415
416    database_task_runner_->PostTask(
417        FROM_HERE,
418        base::Bind(base::IgnoreResult(
419            &ServiceWorkerDatabase::PurgeUncommittedResourceIds),
420            base::Unretained(database_.get()),
421            ids));
422
423    StartPurgingResources(resources);
424  }
425}
426
427base::FilePath ServiceWorkerStorage::GetDatabasePath() {
428  if (path_.empty())
429    return base::FilePath();
430  return path_.Append(kDatabaseName);
431}
432
433base::FilePath ServiceWorkerStorage::GetDiskCachePath() {
434  if (path_.empty())
435    return base::FilePath();
436  return path_.Append(kDiskCacheName);
437}
438
439bool ServiceWorkerStorage::LazyInitialize(const base::Closure& callback) {
440  if (!context_)
441    return false;
442
443  switch (state_) {
444    case INITIALIZED:
445      return true;
446    case DISABLED:
447      return false;
448    case INITIALIZING:
449      pending_tasks_.push_back(callback);
450      return false;
451    case UNINITIALIZED:
452      pending_tasks_.push_back(callback);
453      // Fall-through.
454  }
455
456  state_ = INITIALIZING;
457  database_task_runner_->PostTask(
458      FROM_HERE,
459      base::Bind(&ReadInitialDataFromDB,
460                 database_.get(),
461                 base::MessageLoopProxy::current(),
462                 base::Bind(&ServiceWorkerStorage::DidReadInitialData,
463                            weak_factory_.GetWeakPtr())));
464  return false;
465}
466
467void ServiceWorkerStorage::DidReadInitialData(
468    InitialData* data,
469    ServiceWorkerDatabase::Status status) {
470  DCHECK(data);
471  DCHECK_EQ(INITIALIZING, state_);
472
473  if (status == ServiceWorkerDatabase::STATUS_OK) {
474    next_registration_id_ = data->next_registration_id;
475    next_version_id_ = data->next_version_id;
476    next_resource_id_ = data->next_resource_id;
477    registered_origins_.swap(data->origins);
478    state_ = INITIALIZED;
479  } else {
480    // TODO(nhiroki): If status==STATUS_ERROR_CORRUPTED, do corruption recovery
481    // (http://crbug.com/371675).
482    DLOG(WARNING) << "Failed to initialize: " << status;
483    state_ = DISABLED;
484  }
485
486  for (std::vector<base::Closure>::const_iterator it = pending_tasks_.begin();
487       it != pending_tasks_.end(); ++it) {
488    RunSoon(FROM_HERE, *it);
489  }
490  pending_tasks_.clear();
491}
492
493void ServiceWorkerStorage::DidFindRegistrationForDocument(
494    const GURL& document_url,
495    const FindRegistrationCallback& callback,
496    const ServiceWorkerDatabase::RegistrationData& data,
497    const ResourceList& resources,
498    ServiceWorkerDatabase::Status status) {
499  if (status == ServiceWorkerDatabase::STATUS_OK) {
500    callback.Run(SERVICE_WORKER_OK, GetOrCreateRegistration(data, resources));
501    return;
502  }
503
504  if (status == ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) {
505    // Look for something currently being installed.
506    scoped_refptr<ServiceWorkerRegistration> installing_registration =
507        FindInstallingRegistrationForDocument(document_url);
508    callback.Run(installing_registration ?
509        SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
510        installing_registration);
511    return;
512  }
513
514  // TODO(nhiroki): Handle database error (http://crbug.com/371675).
515  callback.Run(DatabaseStatusToStatusCode(status),
516               scoped_refptr<ServiceWorkerRegistration>());
517}
518
519void ServiceWorkerStorage::DidFindRegistrationForPattern(
520    const GURL& scope,
521    const FindRegistrationCallback& callback,
522    const ServiceWorkerDatabase::RegistrationData& data,
523    const ResourceList& resources,
524    ServiceWorkerDatabase::Status status) {
525  if (status == ServiceWorkerDatabase::STATUS_OK) {
526    callback.Run(SERVICE_WORKER_OK, GetOrCreateRegistration(data, resources));
527    return;
528  }
529
530  if (status == ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND) {
531    scoped_refptr<ServiceWorkerRegistration> installing_registration =
532        FindInstallingRegistrationForPattern(scope);
533    callback.Run(installing_registration ?
534        SERVICE_WORKER_OK : SERVICE_WORKER_ERROR_NOT_FOUND,
535        installing_registration);
536    return;
537  }
538
539  // TODO(nhiroki): Handle database error (http://crbug.com/371675).
540  callback.Run(DatabaseStatusToStatusCode(status),
541               scoped_refptr<ServiceWorkerRegistration>());
542}
543
544void ServiceWorkerStorage::DidFindRegistrationForId(
545    const FindRegistrationCallback& callback,
546    const ServiceWorkerDatabase::RegistrationData& data,
547    const ResourceList& resources,
548    ServiceWorkerDatabase::Status status) {
549  if (status == ServiceWorkerDatabase::STATUS_OK) {
550    callback.Run(SERVICE_WORKER_OK,
551                 GetOrCreateRegistration(data, resources));
552    return;
553  }
554  // TODO(nhiroki): Handle database error (http://crbug.com/371675).
555  callback.Run(DatabaseStatusToStatusCode(status),
556               scoped_refptr<ServiceWorkerRegistration>());
557}
558
559void ServiceWorkerStorage::DidGetAllRegistrations(
560    const GetAllRegistrationInfosCallback& callback,
561    RegistrationList* registrations,
562    ServiceWorkerDatabase::Status status) {
563  DCHECK(registrations);
564  if (status != ServiceWorkerDatabase::STATUS_OK) {
565    // TODO(nhiroki): Handle database error (http://crbug.com/371675).
566    callback.Run(std::vector<ServiceWorkerRegistrationInfo>());
567    return;
568  }
569
570  // Add all stored registrations.
571  std::set<int64> pushed_registrations;
572  std::vector<ServiceWorkerRegistrationInfo> infos;
573  for (RegistrationList::const_iterator it = registrations->begin();
574       it != registrations->end(); ++it) {
575    const bool inserted =
576        pushed_registrations.insert(it->registration_id).second;
577    DCHECK(inserted);
578
579    ServiceWorkerRegistration* registration =
580        context_->GetLiveRegistration(it->registration_id);
581    if (registration) {
582      infos.push_back(registration->GetInfo());
583      continue;
584    }
585
586    ServiceWorkerRegistrationInfo info;
587    info.pattern = it->scope;
588    info.script_url = it->script;
589    info.registration_id = it->registration_id;
590    if (ServiceWorkerVersion* version =
591            context_->GetLiveVersion(it->version_id)) {
592      if (it->is_active)
593        info.active_version = version->GetInfo();
594      else
595        info.waiting_version = version->GetInfo();
596      infos.push_back(info);
597      continue;
598    }
599
600    if (it->is_active) {
601      info.active_version.is_null = false;
602      info.active_version.status = ServiceWorkerVersion::ACTIVE;
603      info.active_version.version_id = it->version_id;
604    } else {
605      info.waiting_version.is_null = false;
606      info.waiting_version.status = ServiceWorkerVersion::INSTALLED;
607      info.waiting_version.version_id = it->version_id;
608    }
609    infos.push_back(info);
610  }
611
612  // Add unstored registrations that are being installed.
613  for (RegistrationRefsById::const_iterator it =
614           installing_registrations_.begin();
615       it != installing_registrations_.end(); ++it) {
616    if (pushed_registrations.insert(it->first).second)
617      infos.push_back(it->second->GetInfo());
618  }
619
620  callback.Run(infos);
621}
622
623void ServiceWorkerStorage::DidStoreRegistration(
624    const StatusCallback& callback,
625    const GURL& origin,
626    const std::vector<int64>& newly_purgeable_resources,
627    ServiceWorkerDatabase::Status status) {
628  if (status != ServiceWorkerDatabase::STATUS_OK) {
629    // TODO(nhiroki): Handle database error (http://crbug.com/371675).
630    callback.Run(DatabaseStatusToStatusCode(status));
631    return;
632  }
633  registered_origins_.insert(origin);
634  callback.Run(SERVICE_WORKER_OK);
635  StartPurgingResources(newly_purgeable_resources);
636}
637
638void ServiceWorkerStorage::DidUpdateToActiveState(
639    const StatusCallback& callback,
640    ServiceWorkerDatabase::Status status) {
641  // TODO(nhiroki): Handle database error (http://crbug.com/371675).
642  callback.Run(DatabaseStatusToStatusCode(status));
643}
644
645void ServiceWorkerStorage::DidDeleteRegistration(
646    const GURL& origin,
647    const StatusCallback& callback,
648    bool origin_is_deletable,
649    const std::vector<int64>& newly_purgeable_resources,
650    ServiceWorkerDatabase::Status status) {
651  if (status != ServiceWorkerDatabase::STATUS_OK) {
652    // TODO(nhiroki): Handle database error (http://crbug.com/371675).
653    callback.Run(DatabaseStatusToStatusCode(status));
654    return;
655  }
656  if (origin_is_deletable)
657    registered_origins_.erase(origin);
658  callback.Run(SERVICE_WORKER_OK);
659  StartPurgingResources(newly_purgeable_resources);
660}
661
662scoped_refptr<ServiceWorkerRegistration>
663ServiceWorkerStorage::GetOrCreateRegistration(
664    const ServiceWorkerDatabase::RegistrationData& data,
665    const ResourceList& resources) {
666  scoped_refptr<ServiceWorkerRegistration> registration =
667      context_->GetLiveRegistration(data.registration_id);
668  if (registration)
669    return registration;
670
671  registration = new ServiceWorkerRegistration(
672      data.scope, data.script, data.registration_id, context_);
673  scoped_refptr<ServiceWorkerVersion> version =
674      context_->GetLiveVersion(data.version_id);
675  if (!version) {
676    version = new ServiceWorkerVersion(registration, data.version_id, context_);
677    version->SetStatus(data.is_active ?
678        ServiceWorkerVersion::ACTIVE : ServiceWorkerVersion::INSTALLED);
679    version->script_cache_map()->SetResources(resources);
680  }
681
682  if (version->status() == ServiceWorkerVersion::ACTIVE)
683    registration->set_active_version(version);
684  else if (version->status() == ServiceWorkerVersion::INSTALLED)
685    registration->set_waiting_version(version);
686  else
687    NOTREACHED();
688  // TODO(michaeln): Hmmm, what if DeleteReg was invoked after
689  // the Find result we're returning here? NOTREACHED condition?
690  return registration;
691}
692
693ServiceWorkerRegistration*
694ServiceWorkerStorage::FindInstallingRegistrationForDocument(
695    const GURL& document_url) {
696  DCHECK(!document_url.has_ref());
697
698  LongestScopeMatcher matcher(document_url);
699  ServiceWorkerRegistration* match = NULL;
700
701  // TODO(nhiroki): This searches over installing registrations linearly and it
702  // couldn't be scalable. Maybe the regs should be partitioned by origin.
703  for (RegistrationRefsById::const_iterator it =
704           installing_registrations_.begin();
705       it != installing_registrations_.end(); ++it) {
706    if (matcher.MatchLongest(it->second->pattern()))
707      match = it->second;
708  }
709  return match;
710}
711
712ServiceWorkerRegistration*
713ServiceWorkerStorage::FindInstallingRegistrationForPattern(
714    const GURL& scope) {
715  for (RegistrationRefsById::const_iterator it =
716           installing_registrations_.begin();
717       it != installing_registrations_.end(); ++it) {
718    if (it->second->pattern() == scope)
719      return it->second;
720  }
721  return NULL;
722}
723
724ServiceWorkerRegistration*
725ServiceWorkerStorage::FindInstallingRegistrationForId(
726    int64 registration_id) {
727  RegistrationRefsById::const_iterator found =
728      installing_registrations_.find(registration_id);
729  if (found == installing_registrations_.end())
730    return NULL;
731  return found->second;
732}
733
734ServiceWorkerDiskCache* ServiceWorkerStorage::disk_cache() {
735  if (disk_cache_)
736    return disk_cache_.get();
737
738  disk_cache_.reset(new ServiceWorkerDiskCache);
739
740  base::FilePath path = GetDiskCachePath();
741  if (path.empty()) {
742    int rv = disk_cache_->InitWithMemBackend(
743        kMaxMemDiskCacheSize,
744        base::Bind(&EmptyCompletionCallback));
745    DCHECK_EQ(net::OK, rv);
746    return disk_cache_.get();
747  }
748
749  int rv = disk_cache_->InitWithDiskBackend(
750      path, kMaxDiskCacheSize, false,
751      disk_cache_thread_.get(),
752      base::Bind(&ServiceWorkerStorage::OnDiskCacheInitialized,
753                 weak_factory_.GetWeakPtr()));
754  if (rv != net::ERR_IO_PENDING)
755    OnDiskCacheInitialized(rv);
756
757  return disk_cache_.get();
758}
759
760void ServiceWorkerStorage::OnDiskCacheInitialized(int rv) {
761  if (rv != net::OK) {
762    LOG(ERROR) << "Failed to open the serviceworker diskcache: "
763               << net::ErrorToString(rv);
764    // TODO(michaeln): DeleteAndStartOver()
765    disk_cache_->Disable();
766    state_ = DISABLED;
767  }
768  ServiceWorkerHistograms::CountInitDiskCacheResult(rv == net::OK);
769}
770
771void ServiceWorkerStorage::StartPurgingResources(
772    const std::vector<int64>& ids) {
773  for (size_t i = 0; i < ids.size(); ++i)
774    purgeable_reource_ids_.push_back(ids[i]);
775  ContinuePurgingResources();
776}
777
778void ServiceWorkerStorage::StartPurgingResources(
779    const ResourceList& resources) {
780  for (size_t i = 0; i < resources.size(); ++i)
781    purgeable_reource_ids_.push_back(resources[i].resource_id);
782  ContinuePurgingResources();
783}
784
785void ServiceWorkerStorage::ContinuePurgingResources() {
786  if (purgeable_reource_ids_.empty() || is_purge_pending_)
787    return;
788
789  // Do one at a time until we're done, use RunSoon to avoid recursion when
790  // DoomEntry returns immediately.
791  is_purge_pending_ = true;
792  int64 id = purgeable_reource_ids_.front();
793  purgeable_reource_ids_.pop_front();
794  RunSoon(FROM_HERE,
795          base::Bind(&ServiceWorkerStorage::PurgeResource,
796                     weak_factory_.GetWeakPtr(), id));
797}
798
799void ServiceWorkerStorage::PurgeResource(int64 id) {
800  DCHECK(is_purge_pending_);
801  int rv = disk_cache()->DoomEntry(
802      id, base::Bind(&ServiceWorkerStorage::OnResourcePurged,
803                     weak_factory_.GetWeakPtr(), id));
804  if (rv != net::ERR_IO_PENDING)
805    OnResourcePurged(id, rv);
806}
807
808void ServiceWorkerStorage::OnResourcePurged(int64 id, int rv) {
809  DCHECK(is_purge_pending_);
810  is_purge_pending_ = false;
811
812  database_task_runner_->PostTask(
813      FROM_HERE,
814      base::Bind(base::IgnoreResult(
815          &ServiceWorkerDatabase::ClearPurgeableResourceIds),
816          base::Unretained(database_.get()),
817          std::set<int64>(&id, &id + 1)));
818
819  ContinuePurgingResources();
820}
821
822void ServiceWorkerStorage::ReadInitialDataFromDB(
823    ServiceWorkerDatabase* database,
824    scoped_refptr<base::SequencedTaskRunner> original_task_runner,
825    const InitializeCallback& callback) {
826  DCHECK(database);
827  scoped_ptr<ServiceWorkerStorage::InitialData> data(
828      new ServiceWorkerStorage::InitialData());
829
830  ServiceWorkerDatabase::Status status =
831      database->GetNextAvailableIds(&data->next_registration_id,
832                                    &data->next_version_id,
833                                    &data->next_resource_id);
834  if (status != ServiceWorkerDatabase::STATUS_OK) {
835    original_task_runner->PostTask(
836        FROM_HERE, base::Bind(callback, base::Owned(data.release()), status));
837    return;
838  }
839
840  status = database->GetOriginsWithRegistrations(&data->origins);
841  original_task_runner->PostTask(
842      FROM_HERE, base::Bind(callback, base::Owned(data.release()), status));
843}
844
845void ServiceWorkerStorage::DeleteRegistrationFromDB(
846    ServiceWorkerDatabase* database,
847    scoped_refptr<base::SequencedTaskRunner> original_task_runner,
848    int64 registration_id,
849    const GURL& origin,
850    const DeleteRegistrationCallback& callback) {
851  DCHECK(database);
852
853  std::vector<int64> newly_purgeable_resources;
854  ServiceWorkerDatabase::Status status =
855      database->DeleteRegistration(registration_id, origin,
856                                   &newly_purgeable_resources);
857  if (status != ServiceWorkerDatabase::STATUS_OK) {
858    original_task_runner->PostTask(
859        FROM_HERE, base::Bind(callback, false, std::vector<int64>(), status));
860    return;
861  }
862
863  // TODO(nhiroki): Add convenient method to ServiceWorkerDatabase to check the
864  // unique origin list.
865  std::vector<ServiceWorkerDatabase::RegistrationData> registrations;
866  status = database->GetRegistrationsForOrigin(origin, &registrations);
867  if (status != ServiceWorkerDatabase::STATUS_OK) {
868    original_task_runner->PostTask(
869        FROM_HERE, base::Bind(callback, false, std::vector<int64>(), status));
870    return;
871  }
872
873  bool deletable = registrations.empty();
874  original_task_runner->PostTask(
875      FROM_HERE, base::Bind(callback, deletable,
876                            newly_purgeable_resources, status));
877}
878
879void ServiceWorkerStorage::WriteRegistrationInDB(
880    ServiceWorkerDatabase* database,
881    scoped_refptr<base::SequencedTaskRunner> original_task_runner,
882    const ServiceWorkerDatabase::RegistrationData& data,
883    const ResourceList& resources,
884    const WriteRegistrationCallback& callback) {
885  DCHECK(database);
886  std::vector<int64> newly_purgeable_resources;
887  ServiceWorkerDatabase::Status status =
888      database->WriteRegistration(data, resources, &newly_purgeable_resources);
889  original_task_runner->PostTask(
890      FROM_HERE,
891      base::Bind(callback, data.script.GetOrigin(),
892                 newly_purgeable_resources, status));
893}
894
895void ServiceWorkerStorage::FindForDocumentInDB(
896    ServiceWorkerDatabase* database,
897    scoped_refptr<base::SequencedTaskRunner> original_task_runner,
898    const GURL& document_url,
899    const FindInDBCallback& callback) {
900  GURL origin = document_url.GetOrigin();
901  RegistrationList registrations;
902  ServiceWorkerDatabase::Status status =
903      database->GetRegistrationsForOrigin(origin, &registrations);
904  if (status != ServiceWorkerDatabase::STATUS_OK) {
905    original_task_runner->PostTask(
906        FROM_HERE,
907        base::Bind(callback,
908                   ServiceWorkerDatabase::RegistrationData(),
909                   ResourceList(),
910                   status));
911    return;
912  }
913
914  ServiceWorkerDatabase::RegistrationData data;
915  ResourceList resources;
916  status = ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND;
917
918  // Find one with a pattern match.
919  LongestScopeMatcher matcher(document_url);
920  int64 match = kInvalidServiceWorkerRegistrationId;
921  for (size_t i = 0; i < registrations.size(); ++i) {
922    if (matcher.MatchLongest(registrations[i].scope))
923      match = registrations[i].registration_id;
924  }
925
926  if (match != kInvalidServiceWorkerRegistrationId)
927    status = database->ReadRegistration(match, origin, &data, &resources);
928
929  original_task_runner->PostTask(
930      FROM_HERE,
931      base::Bind(callback, data, resources, status));
932}
933
934void ServiceWorkerStorage::FindForPatternInDB(
935    ServiceWorkerDatabase* database,
936    scoped_refptr<base::SequencedTaskRunner> original_task_runner,
937    const GURL& scope,
938    const FindInDBCallback& callback) {
939  GURL origin = scope.GetOrigin();
940  std::vector<ServiceWorkerDatabase::RegistrationData> registrations;
941  ServiceWorkerDatabase::Status status =
942      database->GetRegistrationsForOrigin(origin, &registrations);
943  if (status != ServiceWorkerDatabase::STATUS_OK) {
944    original_task_runner->PostTask(
945        FROM_HERE,
946        base::Bind(callback,
947                   ServiceWorkerDatabase::RegistrationData(),
948                   ResourceList(),
949                   status));
950    return;
951  }
952
953  // Find one with an exact matching scope.
954  ServiceWorkerDatabase::RegistrationData data;
955  ResourceList resources;
956  status = ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND;
957  for (RegistrationList::const_iterator it = registrations.begin();
958       it != registrations.end(); ++it) {
959    if (scope != it->scope)
960      continue;
961    status = database->ReadRegistration(it->registration_id, origin,
962                                        &data, &resources);
963    break;  // We're done looping.
964  }
965
966  original_task_runner->PostTask(
967      FROM_HERE,
968      base::Bind(callback, data, resources, status));
969}
970
971void ServiceWorkerStorage::FindForIdInDB(
972    ServiceWorkerDatabase* database,
973    scoped_refptr<base::SequencedTaskRunner> original_task_runner,
974    int64 registration_id,
975    const GURL& origin,
976    const FindInDBCallback& callback) {
977  ServiceWorkerDatabase::RegistrationData data;
978  ResourceList resources;
979  ServiceWorkerDatabase::Status status =
980      database->ReadRegistration(registration_id, origin, &data, &resources);
981  original_task_runner->PostTask(
982      FROM_HERE, base::Bind(callback, data, resources, status));
983}
984
985}  // namespace content
986