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

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: Use base::Closure for storage callback, style fixes, mac/windows fixes 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/compiler_specific.h"
9 #include "base/json/json_reader.h"
10 #include "base/json/json_writer.h"
11 #include "base/logging.h"
12 #include "base/sys_string_conversions.h"
13 #include "content/browser/browser_thread.h"
14 #include "third_party/leveldb/include/leveldb/iterator.h"
15 #include "third_party/leveldb/include/leveldb/write_batch.h"
16
17 namespace {
18
19 // Generic error message sent to extensions on failure.
20 const char* kGenericOnFailureMessage = "Extension settings failed";
21
22 // General closure type for computing a value on the FILE thread and calling
23 // back on the UI thread. The *OnFileThread methods should run on the file
24 // thread, all others should run on the UI thread.
25 //
26 // Subclasses should implement RunOnFileThread(), manipulating settings_
27 // or error_, then call either SucceedOnFileThread() or FailOnFileThread().
28 class ResultClosure {
29 public:
30 // Ownership of callback is taken.
31 ResultClosure(leveldb::DB* db, ExtensionSettingsStorage::Callback* callback)
32 : db_(db),
33 settings_(new DictionaryValue()),
34 callback_(callback) {}
35
36 ~ResultClosure() {}
37
38 void Run() {
39 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
40 BrowserThread::PostTask(
41 BrowserThread::FILE,
42 FROM_HERE,
43 base::Bind(&ResultClosure::RunOnFileThread, base::Unretained(this)));
44 }
45
46 protected:
47 virtual void RunOnFileThread() = 0;
48
49 void SucceedOnFileThread() {
50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
51 BrowserThread::PostTask(
52 BrowserThread::UI,
53 FROM_HERE,
54 base::Bind(&ResultClosure::Succeed, base::Unretained(this)));
55 }
56
57 void FailOnFileThread(std::string error) {
58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
59 error_ = error;
60 BrowserThread::PostTask(
61 BrowserThread::UI,
62 FROM_HERE,
63 base::Bind(&ResultClosure::Fail, base::Unretained(this)));
64 }
65
66 // Helper for the Get() methods; reads a single key from the database using
67 // an existing leveldb options object.
68 // Returns whether the read was successful, in which case the given settings
69 // will have the value set. Otherwise, error will be set.
70 bool ReadFromDb(
71 leveldb::ReadOptions options,
72 const std::string& key,
73 DictionaryValue* settings,
74 std::string* error) {
75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
76 std::string value_as_json;
77 leveldb::Status s = db_->Get(options, key, &value_as_json);
78 if (s.ok()) {
79 base::JSONReader json_reader;
80 Value* value = json_reader.JsonToValue(value_as_json, false, false);
81 if (value != NULL) {
82 settings->Set(key, value);
83 return true;
84 } else {
85 // TODO(kalman): clear the offending non-JSON value from the database.
86 LOG(ERROR) << "Invalid JSON: " << value_as_json;
87 *error = "Invalid JSON";
88 return false;
89 }
90 } else if (s.IsNotFound()) {
91 // Despite there being no value, it was still a success.
92 return true;
93 } else {
94 LOG(ERROR) << "Error reading from database: " << s.ToString();
95 *error = s.ToString();
96 return false;
97 }
98 }
99
100 leveldb::DB* db_;
Matt Perry 2011/06/23 19:14:11 beware of lifetime here. there's no guarantee that
not at google - send to devlin 2011/06/27 08:51:02 Hm. Well this actually should never happen. The
Matt Perry 2011/06/29 18:08:11 I see. So you're saying RunOnFileThread is the onl
not at google - send to devlin 2011/08/03 06:36:51 Done.
101 DictionaryValue* settings_;
102 std::string error_;
103
104 private:
105 DISALLOW_COPY_AND_ASSIGN(ResultClosure);
106
107 void Succeed() {
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
109 callback_->OnSuccess(settings_);
Matt Perry 2011/06/23 19:14:11 make settings_ a scoped_ptr, and use settings_.rel
not at google - send to devlin 2011/06/27 08:51:02 Done.
110 delete this;
111 }
112
113 void Fail() {
114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
115 delete settings_;
116 callback_->OnFailure(error_);
117 delete this;
118 }
119
120 scoped_ptr<ExtensionSettingsStorage::Callback> callback_;
121 };
122
123 // Result closure for Get(key).
124 class Get1ResultClosure : public ResultClosure {
125 public:
126 Get1ResultClosure(
127 leveldb::DB* db,
128 ExtensionSettingsStorage::Callback* callback,
129 const std::string& key)
130 : ResultClosure(db, callback), key_(key) {}
131
132 protected:
133 virtual void RunOnFileThread() OVERRIDE {
134 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
135 if (ReadFromDb(leveldb::ReadOptions(), key_, settings_, &error_)) {
136 SucceedOnFileThread();
137 } else {
138 FailOnFileThread(kGenericOnFailureMessage);
139 }
140 }
141
142 private:
143 std::string key_;
144 };
145
146 // Result closure for Get(keys).
147 class Get2ResultClosure : public ResultClosure {
148 public:
149 // Ownership of keys is taken.
150 Get2ResultClosure(
151 leveldb::DB* db,
152 ExtensionSettingsStorage::Callback* callback,
153 ListValue* keys)
154 : ResultClosure(db, callback), keys_(keys) {}
155
156 protected:
157 virtual void RunOnFileThread() OVERRIDE {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
159 leveldb::ReadOptions options;
160 // All interaction with the db is done on the same thread, so snapshotting
161 // isn't strictly necessary. This is just defensive.
162 options.snapshot = db_->GetSnapshot();
163 bool success = true;
164 std::string key;
165
166 for (ListValue::const_iterator it = keys_->begin();
167 success && it != keys_->end(); ++it) {
168 if ((*it)->GetAsString(&key)) {
169 if (!ReadFromDb(options, key, settings_, &error_)) {
170 success = false;
171 }
172 }
173 }
174 db_->ReleaseSnapshot(options.snapshot);
175
176 if (success) {
177 SucceedOnFileThread();
178 } else {
179 FailOnFileThread(kGenericOnFailureMessage);
180 }
181 }
182
183 private:
184 scoped_ptr<ListValue> keys_;
185 };
186
187 // Result closure for Get() .
188 class Get3ResultClosure : public ResultClosure {
189 public:
190 Get3ResultClosure(
191 leveldb::DB* db,
192 ExtensionSettingsStorage::Callback* callback)
193 : ResultClosure(db, callback) {
194 }
195
196 protected:
197 virtual void RunOnFileThread() OVERRIDE {
198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
199 base::JSONReader json_reader;
200 leveldb::ReadOptions options = leveldb::ReadOptions();
201 // All interaction with the db is done on the same thread, so snapshotting
202 // isn't strictly necessary. This is just defensive.
203 options.snapshot = db_->GetSnapshot();
204 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
205
206 for (it->SeekToFirst(); it->Valid(); it->Next()) {
207 Value* value =
208 json_reader.JsonToValue(it->value().ToString(), false, false);
209 if (value != NULL) {
210 settings_->Set(it->key().ToString(), value);
211 } else {
212 // TODO(kalman): clear the offending non-JSON value from the database.
213 LOG(ERROR) << "Invalid JSON: " << it->value().ToString();
214 }
215 }
216 db_->ReleaseSnapshot(options.snapshot);
217
218 if (!it->status().ok()) {
219 LOG(ERROR) << "DB iteration failed: " << it->status().ToString();
220 FailOnFileThread(kGenericOnFailureMessage);
221 } else {
222 SucceedOnFileThread();
223 }
224 }
225 };
226
227 // Result closure for Set(key, value).
228 class Set1ResultClosure : public ResultClosure {
229 public:
230 // Ownership of the value is taken.
231 Set1ResultClosure(
232 leveldb::DB* db,
233 ExtensionSettingsStorage::Callback* callback,
234 const std::string& key,
235 Value* value)
236 : ResultClosure(db, callback), key_(key), value_(value) {}
237
238 protected:
239 virtual void RunOnFileThread() OVERRIDE {
240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
241 std::string value_as_json;
242 base::JSONWriter::Write(value_, false, &value_as_json);
243 leveldb::Status status =
244 db_->Put(leveldb::WriteOptions(), key_, value_as_json);
245 if (status.ok()) {
246 settings_->Set(key_, value_);
247 SucceedOnFileThread();
248 } else {
249 LOG(WARNING) << "DB write failed: " << status.ToString();
250 delete value_;
251 FailOnFileThread(kGenericOnFailureMessage);
252 }
253 }
254
255 private:
256 std::string key_;
257 Value* value_;
Matt Perry 2011/06/23 19:14:11 see comment about settings_
not at google - send to devlin 2011/06/27 08:51:02 Done.
258 };
259
260 // Result callback for Set(values).
261 class Set2ResultClosure : public ResultClosure {
262 public:
263 // Ownership of values is taken.
264 Set2ResultClosure(
265 leveldb::DB* db,
266 ExtensionSettingsStorage::Callback* callback,
267 DictionaryValue* values)
268 : ResultClosure(db, callback), values_(values) {
269 }
270
271 protected:
272 virtual void RunOnFileThread() OVERRIDE {
273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
274 // Gather keys since a dictionary can't be modified during iteration.
275 std::vector<std::string> keys;
276 for (DictionaryValue::key_iterator it = values_->begin_keys();
277 it != values_->end_keys(); ++it) {
278 keys.push_back(*it);
279 }
280 // Write values while transferring ownership from values_ to settings_.
281 std::string value_as_json;
282 leveldb::WriteBatch batch;
283 for (unsigned i = 0; i < keys.size(); ++i) {
284 Value* value = NULL;
285 values_->RemoveWithoutPathExpansion(keys[i], &value);
286 base::JSONWriter::Write(value, false, &value_as_json);
287 batch.Put(keys[i], value_as_json);
288 settings_->SetWithoutPathExpansion(keys[i], value);
289 }
290 DCHECK(values_->empty());
291
292 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
293 if (status.ok()) {
294 SucceedOnFileThread();
295 } else {
296 LOG(WARNING) << "DB batch write failed: " << status.ToString();
297 FailOnFileThread(kGenericOnFailureMessage);
298 }
299 }
300
301 private:
302 scoped_ptr<DictionaryValue> values_; // will be empty on destruction
303 };
304
305 // Result closure for Remove(key).
306 class Remove1ResultClosure : public ResultClosure {
307 public:
308 Remove1ResultClosure(
309 leveldb::DB* db,
310 ExtensionSettingsStorage::Callback* callback,
311 const std::string& key)
312 : ResultClosure(db, callback), key_(key) {
313 }
314
315 protected:
316 virtual void RunOnFileThread() OVERRIDE {
317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
318 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_);
319 if (status.ok() || status.IsNotFound()) {
320 SucceedOnFileThread();
321 } else {
322 LOG(WARNING) << "DB delete failed: " << status.ToString();
323 FailOnFileThread(kGenericOnFailureMessage);
324 }
325 }
326
327 private:
328 std::string key_;
329 };
330
331 // Result closure for Remove(keys).
332 class Remove2ResultClosure : public ResultClosure {
333 public:
334 // Ownership of keys is taken.
335 Remove2ResultClosure(
336 leveldb::DB* db,
337 ExtensionSettingsStorage::Callback* callback,
338 ListValue* keys)
339 : ResultClosure(db, callback), keys_(keys) {
340 }
341
342 protected:
343 virtual void RunOnFileThread() OVERRIDE {
344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
345 std::string key;
346 leveldb::WriteBatch batch;
347 for (ListValue::const_iterator it = keys_->begin();
348 it != keys_->end(); ++it) {
349 if ((*it)->GetAsString(&key)) {
350 batch.Delete(key);
351 }
352 }
353
354 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
355 if (status.ok() || status.IsNotFound()) {
356 SucceedOnFileThread();
357 } else {
358 LOG(WARNING) << "DB batch delete failed: " << status.ToString();
359 FailOnFileThread(kGenericOnFailureMessage);
360 }
361 }
362
363 private:
364 scoped_ptr<ListValue> keys_;
365 };
366
367 // Result closure for Clear().
368 class ClearResultClosure : public ResultClosure {
369 public:
370 ClearResultClosure(
371 leveldb::DB* db,
372 ExtensionSettingsStorage::Callback* callback)
373 : ResultClosure(db, callback) {
374 }
375
376 protected:
377 virtual void RunOnFileThread() OVERRIDE {
378 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
379 leveldb::ReadOptions options;
380 // All interaction with the db is done on the same thread, so snapshotting
381 // isn't strictly necessary. This is just defensive.
382 options.snapshot = db_->GetSnapshot();
383 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
384 leveldb::WriteBatch batch;
385
386 for (it->SeekToFirst(); it->Valid(); it->Next()) {
387 batch.Delete(it->key());
388 }
389 db_->ReleaseSnapshot(options.snapshot);
390
391 if (it->status().ok()) {
392 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
393 if (status.ok() || status.IsNotFound()) {
394 SucceedOnFileThread();
395 } else {
396 LOG(WARNING) << "Clear failed: " << status.ToString();
397 FailOnFileThread(kGenericOnFailureMessage);
398 }
399 } else {
400 LOG(WARNING) << "Clear iteration failed: " << it->status().ToString();
401 FailOnFileThread(kGenericOnFailureMessage);
402 }
403 }
404 };
405
406 // Deletes a storage area on the FILE thread.
407 class DeleteStorageClosure {
408 public:
409 // Ownership of storage is taken.
410 explicit DeleteStorageClosure(ExtensionSettingsLeveldbStorage* storage)
411 : storage_(storage) {}
412
413 ~DeleteStorageClosure() {}
414
415 void Run() {
416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
417 BrowserThread::PostTask(
418 BrowserThread::FILE,
419 FROM_HERE,
420 base::Bind(
421 &DeleteStorageClosure::RunOnFileThread, base::Unretained(this)));
422 }
423
424 private:
425 DISALLOW_COPY_AND_ASSIGN(DeleteStorageClosure);
426
427 void RunOnFileThread() {
428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
429 delete this;
430 }
431
432 scoped_ptr<ExtensionSettingsStorage> storage_;
433 };
434
435 } // namespace
436
437 /* static */
438 ExtensionSettingsLeveldbStorage* ExtensionSettingsLeveldbStorage::Create(
439 const FilePath& base_path,
440 const std::string& extension_id) {
441 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
442 FilePath path = base_path.AppendASCII(extension_id);
443
444 #if defined(OS_POSIX)
445 std::string os_path(path.value());
446 #elif defined(OS_WIN)
447 std::string os_path = base::SysWideToUTF8(path.value());
448 #endif
449
450 leveldb::Options options;
451 options.create_if_missing = true;
452 leveldb::DB* db;
453 leveldb::Status status = leveldb::DB::Open(options, os_path, &db);
454 if (!status.ok()) {
455 LOG(WARNING) << "Failed to create leveldb at " << path.value() <<
456 ": " << status.ToString();
457 return NULL;
458 }
459 return new ExtensionSettingsLeveldbStorage(db);
460 }
461
462 void ExtensionSettingsLeveldbStorage::DestroyEventually() {
463 (new DeleteStorageClosure(this))->Run();
Matt Perry 2011/06/23 19:14:11 we have BrowserThread::DeleteSoon to handle this c
not at google - send to devlin 2011/06/27 08:51:02 Done.
464 }
465
466 void ExtensionSettingsLeveldbStorage::Get(
467 const std::string& key, ExtensionSettingsStorage::Callback* callback) {
468 (new Get1ResultClosure(db_.get(), callback, key))->Run();
469 }
470
471 void ExtensionSettingsLeveldbStorage::Get(
472 const ListValue& keys, ExtensionSettingsStorage::Callback* callback) {
473 (new Get2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run();
474 }
475
476 void ExtensionSettingsLeveldbStorage::Get(
477 ExtensionSettingsStorage::Callback* callback) {
478 (new Get3ResultClosure(db_.get(), callback))->Run();
479 }
480
481 void ExtensionSettingsLeveldbStorage::Set(
482 const std::string& key,
483 const Value& value,
484 ExtensionSettingsStorage::Callback* callback) {
485 (new Set1ResultClosure(db_.get(), callback, key, value.DeepCopy()))->Run();
486 }
487
488 void ExtensionSettingsLeveldbStorage::Set(
489 const DictionaryValue& values,
490 ExtensionSettingsStorage::Callback* callback) {
491 (new Set2ResultClosure(db_.get(), callback, values.DeepCopy()))->Run();
492 }
493
494 void ExtensionSettingsLeveldbStorage::Remove(
495 const std::string& key, ExtensionSettingsStorage::Callback *callback) {
496 (new Remove1ResultClosure(db_.get(), callback, key))->Run();
497 }
498
499 void ExtensionSettingsLeveldbStorage::Remove(
500 const ListValue& keys, ExtensionSettingsStorage::Callback *callback) {
501 (new Remove2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run();
502 }
503
504 void ExtensionSettingsLeveldbStorage::Clear(
505 ExtensionSettingsStorage::Callback* callback) {
506 (new ClearResultClosure(db_.get(), callback))->Run();
507 }
508
509 ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage(
510 leveldb::DB* db)
511 : db_(db) {
512 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
513 }
514
515 ExtensionSettingsLeveldbStorage::~ExtensionSettingsLeveldbStorage() {
516 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
517 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698