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

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

Powered by Google App Engine
This is Rietveld 408576698