Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(854)

Side by Side Diff: chrome/browser/extensions/extension_settings_leveldb_storage.cc

Issue 7189029: Implement an initial extension settings API. (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Add missing files Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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/extension_settings_leveldb_storage.h"
6
7 #include "base/bind.h"
8 #include "base/json/json_reader.h"
9 #include "base/json/json_writer.h"
10 #include "base/logging.h"
11 #include "content/browser/browser_thread.h"
12 #include "third_party/leveldb/include/leveldb/iterator.h"
13 #include "third_party/leveldb/include/leveldb/write_batch.h"
14
15 namespace {
16
17 // General closure type for computing a value on the FILE thread and calling
18 // back on the UI thread. The *OnFileThread methods should run on the file
19 // thread, all others should run on the UI thread.
20 //
21 // Subclasses should implement RunOnFileThreadImpl(), manipulating settings_
22 // or error_, then call either SucceedOnFileThread() or FailOnFileThread().
23 class ResultClosure {
24 public:
25 ResultClosure(leveldb::DB* db, ExtensionSettingsStorage::Callback* callback)
26 : db_(db), settings_(new DictionaryValue()), callback_(callback) {}
27
28 ~ResultClosure() {}
29
30 void Run() {
31 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
32 BrowserThread::PostTask(
33 BrowserThread::FILE,
34 FROM_HERE,
35 base::Bind(&ResultClosure::RunOnFileThread, base::Unretained(this)));
36 }
37
38 protected:
39 virtual void RunOnFileThreadImpl() = 0;
40
41 void SucceedOnFileThread() {
42 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
43 BrowserThread::PostTask(
44 BrowserThread::UI,
45 FROM_HERE,
46 base::Bind(&ResultClosure::Succeed, base::Unretained(this)));
47 }
48
49 void FailOnFileThread(std::string error) {
50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
51 error_ = error;
52 BrowserThread::PostTask(
53 BrowserThread::UI,
54 FROM_HERE,
55 base::Bind(&ResultClosure::Fail, base::Unretained(this)));
56 }
57
58 // Helper for the Get() methods; reads a single key from the database using
59 // an existing leveldb options object.
60 // Returns whether the read was successful, in which case the given settings
61 // will have the value set. Otherwise, error will be set.
62 bool ReadFromDb(leveldb::ReadOptions options, const std::string& key,
63 DictionaryValue* settings, std::string* error) {
64 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
65 std::string value_as_json;
66 leveldb::Status s = db_->Get(options, key, &value_as_json);
67 if (s.ok()) {
68 base::JSONReader json_reader;
69 Value* value = json_reader.JsonToValue(value_as_json, false, false);
70 if (value != NULL) {
71 settings->Set(key, value);
72 return true;
73 } else {
74 // TODO(kalman): clear the offending non-JSON value from the database.
75 LOG(ERROR) << "Invalid JSON: " << value_as_json;
76 *error = "Invalid JSON";
77 return false;
78 }
79 } else if (s.IsNotFound()) {
80 // Despite there being no value, it was still a success.
81 return true;
82 } else {
83 LOG(ERROR) << "Error reading from database: " << s.ToString();
84 *error = s.ToString();
85 return false;
86 }
87 }
88
89 leveldb::DB* db_;
90 DictionaryValue* settings_;
91 std::string error_;
92
93 private:
94 void RunOnFileThread() {
95 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
96 RunOnFileThreadImpl();
97 }
98
99 void Succeed() {
100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
101 callback_->OnSuccess(settings_);
102 delete this;
103 }
104
105 void Fail() {
106 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
107 delete settings_;
108 callback_->OnFailure(error_);
109 delete this;
110 }
111
112 scoped_ptr<ExtensionSettingsStorage::Callback> callback_;
113 };
114
115 // Result closure for Get(key).
116 class Get1ResultClosure : public ResultClosure {
117 public:
118 Get1ResultClosure(leveldb::DB* db,
119 ExtensionSettingsStorage::Callback* callback, const std::string& key)
120 : ResultClosure(db, callback), key_(key) {}
121
122 protected:
123 void RunOnFileThreadImpl() {
124 if (ReadFromDb(leveldb::ReadOptions(), key_, settings_, &error_)) {
125 SucceedOnFileThread();
126 } else {
127 FailOnFileThread("Failed");
128 }
129 }
130
131 private:
132 std::string key_;
133 };
134
135 // Result closure for Get(keys).
136 class Get2ResultClosure : public ResultClosure {
137 public:
138 // Ownership of keys is taken.
139 Get2ResultClosure(leveldb::DB* db,
140 ExtensionSettingsStorage::Callback* callback, ListValue* keys)
141 : ResultClosure(db, callback), keys_(keys) {}
142
143 protected:
144 void RunOnFileThreadImpl() {
145 leveldb::ReadOptions options;
146 options.snapshot = db_->GetSnapshot();
dgrogan 2011/06/18 01:54:40 Why do you use snapshots when you're reading? Won
not at google - send to devlin 2011/06/20 05:33:11 Hm. Yes. It just seems... instinctively wrong to
dgrogan 2011/06/21 02:36:05 Ok, they are just defensive. Using them is fine,
not at google - send to devlin 2011/06/22 09:40:38 Cool, I added a comment.
147 bool success = true;
148 std::string key;
149
150 for (ListValue::const_iterator it = keys_->begin();
151 success && it != keys_->end(); ++it) {
152 if ((*it)->GetAsString(&key)) {
153 if (!ReadFromDb(options, key, settings_, &error_)) {
154 success = false;
155 }
156 }
157 }
158 db_->ReleaseSnapshot(options.snapshot);
159
160 if (success) {
161 SucceedOnFileThread();
162 } else {
163 FailOnFileThread("Failed");
164 }
165 }
166
167 private:
168 scoped_ptr<ListValue> keys_;
169 };
170
171 // Result closure for Get() .
172 class Get3ResultClosure : public ResultClosure {
173 public:
174 Get3ResultClosure(leveldb::DB* db,
175 ExtensionSettingsStorage::Callback* callback)
176 : ResultClosure(db, callback) {
177 }
178
179 protected:
180 void RunOnFileThreadImpl() {
181 base::JSONReader json_reader;
182 leveldb::ReadOptions options = leveldb::ReadOptions();
183 options.snapshot = db_->GetSnapshot();
184 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
185
186 for (it->SeekToFirst(); it->Valid(); it->Next()) {
187 Value* value =
188 json_reader.JsonToValue(it->value().ToString(), false, false);
189 if (value != NULL) {
190 settings_->Set(it->key().ToString(), value);
191 } else {
192 // TODO(kalman): clear the offending non-JSON value from the database.
193 LOG(ERROR) << "Invalid JSON: " << it->value().ToString();
194 }
195 }
196 db_->ReleaseSnapshot(options.snapshot);
197
198 if (!it->status().ok()) {
199 LOG(ERROR) << "DB iteration failed: " << it->status().ToString();
200 FailOnFileThread("Failed");
dgrogan 2011/06/18 01:54:40 Why don't you pass it->status().ToString() back th
not at google - send to devlin 2011/06/20 05:33:11 This is the message sent back to the extension. I
dgrogan 2011/06/21 02:36:05 Ok, I see where you're coming from.
not at google - send to devlin 2011/06/22 09:40:38 Btw I had a look through the leveldb code but wasn
Mihai Parparita -not on Chrome 2011/06/23 03:16:46 Sorry, didn't see this question earlier. Unless th
not at google - send to devlin 2011/06/23 13:44:26 Done.
201 } else {
202 SucceedOnFileThread();
203 }
204 }
205 };
206
207 // Result closure for Set(key, value).
208 class Set1ResultClosure : public ResultClosure {
209 public:
210 // Ownership of the value is taken.
211 Set1ResultClosure(leveldb::DB* db,
212 ExtensionSettingsStorage::Callback* callback, const std::string& key,
213 Value* value) : ResultClosure(db, callback), key_(key), value_(value) {}
214
215 protected:
216 void RunOnFileThreadImpl() {
217 std::string value_as_json;
218 base::JSONWriter::Write(value_, false, &value_as_json);
219 leveldb::Status status =
220 db_->Put(leveldb::WriteOptions(), key_, value_as_json);
221 if (status.ok()) {
222 settings_->Set(key_, value_);
223 SucceedOnFileThread();
224 } else {
225 LOG(WARNING) << "DB write failed: " << status.ToString();
226 delete value_;
227 FailOnFileThread("Failed");
228 }
229 }
230
231 private:
232 std::string key_;
233 Value* value_;
234 };
235
236 // Result callback for Set(values).
237 class Set2ResultClosure : public ResultClosure {
238 public:
239 // Ownership of values is taken.
240 Set2ResultClosure(leveldb::DB* db,
241 ExtensionSettingsStorage::Callback* callback, DictionaryValue* values)
242 : ResultClosure(db, callback), values_(values) {
243 }
244
245 protected:
246 void RunOnFileThreadImpl() {
247 // Gather keys since a dictionary can't be modified during iteration.
248 std::vector<std::string> keys;
249 for (DictionaryValue::key_iterator it = values_->begin_keys();
250 it != values_->end_keys(); ++it) {
251 keys.push_back(*it);
252 }
253 // Write values while transferring ownership from values_ to settings_.
254 std::string value_as_json;
255 leveldb::WriteBatch batch;
256 for (unsigned i = 0; i < keys.size(); ++i) {
257 Value* value = NULL;
258 values_->RemoveWithoutPathExpansion(keys[i], &value);
259 base::JSONWriter::Write(value, false, &value_as_json);
260 batch.Put(keys[i], value_as_json);
261 settings_->SetWithoutPathExpansion(keys[i], value);
262 }
263 DCHECK(values_->empty());
264
265 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
266 if (status.ok()) {
267 SucceedOnFileThread();
268 } else {
269 LOG(WARNING) << "DB batch write failed: " << status.ToString();
270 FailOnFileThread("Failed");
271 }
272 }
273
274 private:
275 scoped_ptr<DictionaryValue> values_; // will be empty on destruction
276 };
277
278 // Result closure for Remove(key).
279 class Remove1ResultClosure : public ResultClosure {
280 public:
281 Remove1ResultClosure(leveldb::DB* db,
282 ExtensionSettingsStorage::Callback* callback, const std::string& key)
283 : ResultClosure(db, callback), key_(key) {
284 }
285
286 protected:
287 void RunOnFileThreadImpl() {
288 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_);
289 if (status.ok() || status.IsNotFound()) {
290 SucceedOnFileThread();
291 } else {
292 LOG(WARNING) << "DB delete failed: " << status.ToString();
293 FailOnFileThread("Failed");
294 }
295 }
296
297 private:
298 std::string key_;
299 };
300
301 // Result closure for Remove(keys).
302 class Remove2ResultClosure : public ResultClosure {
303 public:
304 // Ownership of keys is taken.
305 Remove2ResultClosure(leveldb::DB* db,
306 ExtensionSettingsStorage::Callback* callback, ListValue* keys)
307 : ResultClosure(db, callback), keys_(keys) {
308 }
309
310 protected:
311 void RunOnFileThreadImpl() {
312 std::string key;
313 leveldb::WriteBatch batch;
314 for (ListValue::const_iterator it = keys_->begin();
315 it != keys_->end(); ++it) {
316 if ((*it)->GetAsString(&key)) {
317 batch.Delete(key);
318 }
319 }
320
321 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
322 if (status.ok() || status.IsNotFound()) {
323 SucceedOnFileThread();
324 } else {
325 LOG(WARNING) << "DB batch delete failed: " << status.ToString();
326 FailOnFileThread("Failed");
327 }
328 }
329
330 private:
331 scoped_ptr<ListValue> keys_;
332 };
333
334 // Result closure for Clear().
335 class ClearResultClosure : public ResultClosure {
336 public:
337 ClearResultClosure(leveldb::DB* db,
338 ExtensionSettingsStorage::Callback* callback)
339 : ResultClosure(db, callback) {
340 }
341
342 protected:
343 void RunOnFileThreadImpl() {
344 leveldb::ReadOptions options;
345 options.snapshot = db_->GetSnapshot();
346 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
347 leveldb::WriteBatch batch;
348
349 for (it->SeekToFirst(); it->Valid(); it->Next()) {
350 batch.Delete(it->key());
dgrogan 2011/06/18 01:54:40 Does batching up a bunch of deletes on an iterator
not at google - send to devlin 2011/06/20 05:33:11 Maybe I'm misunderstanding something. The snapsho
dgrogan 2011/06/21 02:36:05 Oh, I didn't realize it gets a consistent set of k
not at google - send to devlin 2011/06/22 09:40:38 I think it gets a consistent view of both keys and
351 }
352 db_->ReleaseSnapshot(options.snapshot);
353
354 if (it->status().ok()) {
355 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
356 if (status.ok() || status.IsNotFound()) {
357 SucceedOnFileThread();
358 } else {
359 LOG(WARNING) << "Clear failed: " << status.ToString();
360 FailOnFileThread("Failed");
361 }
362 } else {
363 LOG(WARNING) << "Clear iteration failed: " << it->status().ToString();
364 FailOnFileThread("Failed");
365 }
366 }
367 };
368
369 } // namespace
370
371 ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage(
372 leveldb::DB* db) : db_(db) {
373 }
374
375 void ExtensionSettingsLeveldbStorage::Get(const std::string& key,
376 ExtensionSettingsStorage::Callback* callback) {
377 (new Get1ResultClosure(db_.get(), callback, key))->Run();
378 }
379
380 void ExtensionSettingsLeveldbStorage::Get(const ListValue& keys,
381 ExtensionSettingsStorage::Callback* callback) {
382 (new Get2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run();
383 }
384
385 void ExtensionSettingsLeveldbStorage::Get(
386 ExtensionSettingsStorage::Callback* callback) {
387 (new Get3ResultClosure(db_.get(), callback))->Run();
388 }
389
390 void ExtensionSettingsLeveldbStorage::Set(const std::string& key,
391 const Value& value, ExtensionSettingsStorage::Callback* callback) {
392 (new Set1ResultClosure(db_.get(), callback, key, value.DeepCopy()))->Run();
393 }
394
395 void ExtensionSettingsLeveldbStorage::Set(const DictionaryValue& values,
396 ExtensionSettingsStorage::Callback* callback) {
397 (new Set2ResultClosure(db_.get(), callback, values.DeepCopy()))->Run();
398 }
399
400 void ExtensionSettingsLeveldbStorage::Remove(const std::string& key,
401 ExtensionSettingsStorage::Callback *callback) {
402 (new Remove1ResultClosure(db_.get(), callback, key))->Run();
403 }
404
405 void ExtensionSettingsLeveldbStorage::Remove(const ListValue& keys,
406 ExtensionSettingsStorage::Callback *callback) {
407 (new Remove2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run();
408 }
409
410 void ExtensionSettingsLeveldbStorage::Clear(
411 ExtensionSettingsStorage::Callback* callback) {
412 (new ClearResultClosure(db_.get(), callback))->Run();
413 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698