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

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: Change to RefCountedThreadSafe 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 is taken.
31 ResultClosure(
32 leveldb::DB* db,
33 ExtensionSettingsStorage::Callback* callback)
34 : db_(db),
35 settings_(new DictionaryValue()),
36 callback_(callback) {}
37
38 ~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, 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, 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, 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, callback), key_(key), value_(value) {}
244
245 protected:
246 virtual void RunOnFileThread() OVERRIDE {
247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
248 std::string value_as_json;
249 base::JSONWriter::Write(value_.get(), false, &value_as_json);
250 leveldb::Status status =
251 db_->Put(leveldb::WriteOptions(), key_, value_as_json);
252 if (status.ok()) {
253 settings_->Set(key_, value_.release());
254 SucceedOnFileThread();
255 } else {
256 LOG(WARNING) << "DB write failed: " << status.ToString();
257 FailOnFileThread(kGenericOnFailureMessage);
258 }
259 }
260
261 private:
262 std::string key_;
263 scoped_ptr<Value> value_;
264 };
265
266 // Result callback for Set(values).
267 class Set2ResultClosure : public ResultClosure {
268 public:
269 // Ownership of values is taken.
270 Set2ResultClosure(
271 leveldb::DB* db,
272 ExtensionSettingsStorage::Callback* callback,
273 DictionaryValue* values)
274 : ResultClosure(db, callback), values_(values) {
275 }
276
277 protected:
278 virtual void RunOnFileThread() OVERRIDE {
279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
280 // Gather keys since a dictionary can't be modified during iteration.
281 std::vector<std::string> keys;
282 for (DictionaryValue::key_iterator it = values_->begin_keys();
283 it != values_->end_keys(); ++it) {
284 keys.push_back(*it);
285 }
286 // Write values while transferring ownership from values_ to settings_.
287 std::string value_as_json;
288 leveldb::WriteBatch batch;
289 for (unsigned i = 0; i < keys.size(); ++i) {
290 Value* value = NULL;
291 values_->RemoveWithoutPathExpansion(keys[i], &value);
292 base::JSONWriter::Write(value, false, &value_as_json);
293 batch.Put(keys[i], value_as_json);
294 settings_->SetWithoutPathExpansion(keys[i], value);
295 }
296 DCHECK(values_->empty());
297
298 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
299 if (status.ok()) {
300 SucceedOnFileThread();
301 } else {
302 LOG(WARNING) << "DB batch write failed: " << status.ToString();
303 FailOnFileThread(kGenericOnFailureMessage);
304 }
305 }
306
307 private:
308 scoped_ptr<DictionaryValue> values_; // will be empty on destruction
309 };
310
311 // Result closure for Remove(key).
312 class Remove1ResultClosure : public ResultClosure {
313 public:
314 Remove1ResultClosure(
315 leveldb::DB* db,
316 ExtensionSettingsStorage::Callback* callback,
317 const std::string& key)
318 : ResultClosure(db, callback), key_(key) {
319 }
320
321 protected:
322 virtual void RunOnFileThread() OVERRIDE {
323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
324 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_);
325 if (status.ok() || status.IsNotFound()) {
326 SucceedOnFileThread();
327 } else {
328 LOG(WARNING) << "DB delete failed: " << status.ToString();
329 FailOnFileThread(kGenericOnFailureMessage);
330 }
331 }
332
333 private:
334 std::string key_;
335 };
336
337 // Result closure for Remove(keys).
338 class Remove2ResultClosure : public ResultClosure {
339 public:
340 // Ownership of keys is taken.
341 Remove2ResultClosure(
342 leveldb::DB* db,
343 ExtensionSettingsStorage::Callback* callback,
344 ListValue* keys)
345 : ResultClosure(db, callback), keys_(keys) {
346 }
347
348 protected:
349 virtual void RunOnFileThread() OVERRIDE {
350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
351 std::string key;
352 leveldb::WriteBatch batch;
353 for (ListValue::const_iterator it = keys_->begin();
354 it != keys_->end(); ++it) {
355 if ((*it)->GetAsString(&key)) {
356 batch.Delete(key);
357 }
358 }
359
360 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
361 if (status.ok() || status.IsNotFound()) {
362 SucceedOnFileThread();
363 } else {
364 LOG(WARNING) << "DB batch delete failed: " << status.ToString();
365 FailOnFileThread(kGenericOnFailureMessage);
366 }
367 }
368
369 private:
370 scoped_ptr<ListValue> keys_;
371 };
372
373 // Result closure for Clear().
374 class ClearResultClosure : public ResultClosure {
375 public:
376 ClearResultClosure(leveldb::DB* db,
377 ExtensionSettingsStorage::Callback* callback)
378 : ResultClosure(db, callback) {
379 }
380
381 protected:
382 virtual void RunOnFileThread() OVERRIDE {
383 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
384 leveldb::ReadOptions options;
385 // All interaction with the db is done on the same thread, so snapshotting
386 // isn't strictly necessary. This is just defensive.
387 options.snapshot = db_->GetSnapshot();
388 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
389 leveldb::WriteBatch batch;
390
391 for (it->SeekToFirst(); it->Valid(); it->Next()) {
392 batch.Delete(it->key());
393 }
394 db_->ReleaseSnapshot(options.snapshot);
395
396 if (it->status().ok()) {
397 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
398 if (status.ok() || status.IsNotFound()) {
399 SucceedOnFileThread();
400 } else {
401 LOG(WARNING) << "Clear failed: " << status.ToString();
402 FailOnFileThread(kGenericOnFailureMessage);
403 }
404 } else {
405 LOG(WARNING) << "Clear iteration failed: " << it->status().ToString();
406 FailOnFileThread(kGenericOnFailureMessage);
407 }
408 }
409 };
410
411 } // namespace
412
413 /* static */
414 ExtensionSettingsLeveldbStorage* ExtensionSettingsLeveldbStorage::Create(
415 const FilePath& base_path,
416 const std::string& extension_id) {
417 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
418 FilePath path = base_path.AppendASCII(extension_id);
419
420 #if defined(OS_POSIX)
421 std::string os_path(path.value());
422 #elif defined(OS_WIN)
423 std::string os_path = base::SysWideToUTF8(path.value());
424 #endif
425
426 leveldb::Options options;
427 options.create_if_missing = true;
428 leveldb::DB* db;
429 leveldb::Status status = leveldb::DB::Open(options, os_path, &db);
430 if (!status.ok()) {
431 LOG(WARNING) << "Failed to create leveldb at " << path.value() <<
432 ": " << status.ToString();
433 return NULL;
434 }
435 return new ExtensionSettingsLeveldbStorage(db);
436 }
437
438 ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage(
439 leveldb::DB* db)
440 : db_(db), marked_for_deletion_(false) {
441 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
442 }
443
444 ExtensionSettingsLeveldbStorage::~ExtensionSettingsLeveldbStorage() {
445 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
446 }
447
448 void ExtensionSettingsLeveldbStorage::DeleteSoon() {
449 CHECK(!marked_for_deletion_);
450 marked_for_deletion_ = true;
451 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, this);
452 }
453
454 void ExtensionSettingsLeveldbStorage::Get(
455 const std::string& key, ExtensionSettingsStorage::Callback* callback) {
456 CHECK(!marked_for_deletion_);
457 (new Get1ResultClosure(db_.get(), callback, key))->Run();
458 }
459
460 void ExtensionSettingsLeveldbStorage::Get(
461 const ListValue& keys, ExtensionSettingsStorage::Callback* callback) {
462 CHECK(!marked_for_deletion_);
463 (new Get2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run();
464 }
465
466 void ExtensionSettingsLeveldbStorage::Get(
467 ExtensionSettingsStorage::Callback* callback) {
468 CHECK(!marked_for_deletion_);
469 (new Get3ResultClosure(db_.get(), callback))->Run();
470 }
471
472 void ExtensionSettingsLeveldbStorage::Set(
473 const std::string& key,
474 const Value& value,
475 ExtensionSettingsStorage::Callback* callback) {
476 CHECK(!marked_for_deletion_);
477 (new Set1ResultClosure(db_.get(), callback, key, value.DeepCopy()))->Run();
478 }
479
480 void ExtensionSettingsLeveldbStorage::Set(
481 const DictionaryValue& values,
482 ExtensionSettingsStorage::Callback* callback) {
483 CHECK(!marked_for_deletion_);
484 (new Set2ResultClosure(db_.get(), callback, values.DeepCopy()))->Run();
485 }
486
487 void ExtensionSettingsLeveldbStorage::Remove(
488 const std::string& key, ExtensionSettingsStorage::Callback *callback) {
489 CHECK(!marked_for_deletion_);
490 (new Remove1ResultClosure(db_.get(), callback, key))->Run();
491 }
492
493 void ExtensionSettingsLeveldbStorage::Remove(
494 const ListValue& keys, ExtensionSettingsStorage::Callback *callback) {
495 CHECK(!marked_for_deletion_);
496 (new Remove2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run();
497 }
498
499 void ExtensionSettingsLeveldbStorage::Clear(
500 ExtensionSettingsStorage::Callback* callback) {
501 CHECK(!marked_for_deletion_);
502 (new ClearResultClosure(db_.get(), callback))->Run();
503 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698