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

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

Powered by Google App Engine
This is Rietveld 408576698