1// Copyright (c) 2012 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 "chrome/browser/extensions/state_store.h"
6
7#include "base/bind.h"
8#include "base/message_loop/message_loop.h"
9#include "chrome/browser/chrome_notification_types.h"
10#include "content/public/browser/notification_service.h"
11#include "content/public/browser/notification_types.h"
12#include "extensions/common/extension.h"
13
14namespace {
15
16// Delay, in seconds, before we should open the State Store database. We
17// defer it to avoid slowing down startup. See http://crbug.com/161848
18const int kInitDelaySeconds = 1;
19
20std::string GetFullKey(const std::string& extension_id,
21                       const std::string& key) {
22  return extension_id + "." + key;
23}
24
25}  // namespace
26
27namespace extensions {
28
29// Helper class to delay tasks until we're ready to start executing them.
30class StateStore::DelayedTaskQueue {
31 public:
32  DelayedTaskQueue() : ready_(false) {}
33  ~DelayedTaskQueue() {}
34
35  // Queues up a task for invoking once we're ready. Invokes immediately if
36  // we're already ready.
37  void InvokeWhenReady(base::Closure task);
38
39  // Marks us ready, and invokes all pending tasks.
40  void SetReady();
41
42 private:
43  bool ready_;
44  std::vector<base::Closure> pending_tasks_;
45};
46
47void StateStore::DelayedTaskQueue::InvokeWhenReady(base::Closure task) {
48  if (ready_) {
49    task.Run();
50  } else {
51    pending_tasks_.push_back(task);
52  }
53}
54
55void StateStore::DelayedTaskQueue::SetReady() {
56  ready_ = true;
57
58  for (size_t i = 0; i < pending_tasks_.size(); ++i)
59    pending_tasks_[i].Run();
60  pending_tasks_.clear();
61}
62
63StateStore::StateStore(Profile* profile,
64                       const base::FilePath& db_path,
65                       bool deferred_load)
66    : db_path_(db_path), task_queue_(new DelayedTaskQueue()) {
67  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
68                 content::Source<Profile>(profile));
69  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
70                 content::Source<Profile>(profile));
71
72  if (deferred_load) {
73    // Don't Init until the first page is loaded or the session restored.
74    registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
75                   content::NotificationService::
76                       AllBrowserContextsAndSources());
77    registrar_.Add(this, chrome::NOTIFICATION_SESSION_RESTORE_DONE,
78                   content::NotificationService::
79                       AllBrowserContextsAndSources());
80  } else {
81    Init();
82  }
83}
84
85StateStore::StateStore(Profile* profile, scoped_ptr<ValueStore> value_store)
86    : store_(value_store.Pass()), task_queue_(new DelayedTaskQueue()) {
87  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
88                 content::Source<Profile>(profile));
89  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
90                 content::Source<Profile>(profile));
91
92  // This constructor is for testing. No need to delay Init.
93  Init();
94}
95
96StateStore::~StateStore() {
97}
98
99void StateStore::RegisterKey(const std::string& key) {
100  registered_keys_.insert(key);
101}
102
103void StateStore::GetExtensionValue(const std::string& extension_id,
104                                   const std::string& key,
105                                   ReadCallback callback) {
106  task_queue_->InvokeWhenReady(
107      base::Bind(&ValueStoreFrontend::Get, base::Unretained(&store_),
108                 GetFullKey(extension_id, key), callback));
109}
110
111void StateStore::SetExtensionValue(
112    const std::string& extension_id,
113    const std::string& key,
114    scoped_ptr<base::Value> value) {
115  task_queue_->InvokeWhenReady(
116      base::Bind(&ValueStoreFrontend::Set, base::Unretained(&store_),
117                 GetFullKey(extension_id, key), base::Passed(&value)));
118}
119
120void StateStore::RemoveExtensionValue(const std::string& extension_id,
121                                      const std::string& key) {
122  task_queue_->InvokeWhenReady(
123      base::Bind(&ValueStoreFrontend::Remove, base::Unretained(&store_),
124                 GetFullKey(extension_id, key)));
125}
126
127void StateStore::Observe(int type,
128                         const content::NotificationSource& source,
129                         const content::NotificationDetails& details) {
130  switch (type) {
131    case chrome::NOTIFICATION_EXTENSION_INSTALLED:
132      RemoveKeysForExtension(
133          content::Details<const InstalledExtensionInfo>(details)->extension->
134              id());
135      break;
136    case chrome::NOTIFICATION_EXTENSION_UNINSTALLED:
137      RemoveKeysForExtension(
138          content::Details<const Extension>(details)->id());
139      break;
140    case chrome::NOTIFICATION_SESSION_RESTORE_DONE:
141    case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME:
142      registrar_.Remove(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
143                        content::NotificationService::AllSources());
144      registrar_.Remove(this, chrome::NOTIFICATION_SESSION_RESTORE_DONE,
145                        content::NotificationService::AllSources());
146      base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
147          base::Bind(&StateStore::Init, AsWeakPtr()),
148          base::TimeDelta::FromSeconds(kInitDelaySeconds));
149      break;
150    default:
151      NOTREACHED();
152      return;
153  }
154}
155
156void StateStore::Init() {
157  if (!db_path_.empty())
158    store_.Init(db_path_);
159  task_queue_->SetReady();
160}
161
162void StateStore::RemoveKeysForExtension(const std::string& extension_id) {
163  for (std::set<std::string>::iterator key = registered_keys_.begin();
164       key != registered_keys_.end(); ++key) {
165    task_queue_->InvokeWhenReady(
166        base::Bind(&ValueStoreFrontend::Remove, base::Unretained(&store_),
167                   GetFullKey(extension_id, *key)));
168  }
169}
170
171}  // namespace extensions
172