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

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

Powered by Google App Engine
This is Rietveld 408576698