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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/extensions/extension_settings_leveldb_storage.cc
diff --git a/chrome/browser/extensions/extension_settings_leveldb_storage.cc b/chrome/browser/extensions/extension_settings_leveldb_storage.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c19c759f218796f8474735ce6d29fcd6c08bfe24
--- /dev/null
+++ b/chrome/browser/extensions/extension_settings_leveldb_storage.cc
@@ -0,0 +1,517 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_settings_leveldb_storage.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/sys_string_conversions.h"
+#include "content/browser/browser_thread.h"
+#include "third_party/leveldb/include/leveldb/iterator.h"
+#include "third_party/leveldb/include/leveldb/write_batch.h"
+
+namespace {
+
+// Generic error message sent to extensions on failure.
+const char* kGenericOnFailureMessage = "Extension settings failed";
+
+// General closure type for computing a value on the FILE thread and calling
+// back on the UI thread. The *OnFileThread methods should run on the file
+// thread, all others should run on the UI thread.
+//
+// Subclasses should implement RunOnFileThread(), manipulating settings_
+// or error_, then call either SucceedOnFileThread() or FailOnFileThread().
+class ResultClosure {
+ public:
+ // Ownership of callback is taken.
+ ResultClosure(leveldb::DB* db, ExtensionSettingsStorage::Callback* callback)
+ : db_(db),
+ settings_(new DictionaryValue()),
+ callback_(callback) {}
+
+ ~ResultClosure() {}
+
+ void Run() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&ResultClosure::RunOnFileThread, base::Unretained(this)));
+ }
+
+ protected:
+ virtual void RunOnFileThread() = 0;
+
+ void SucceedOnFileThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ResultClosure::Succeed, base::Unretained(this)));
+ }
+
+ void FailOnFileThread(std::string error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ error_ = error;
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ResultClosure::Fail, base::Unretained(this)));
+ }
+
+ // Helper for the Get() methods; reads a single key from the database using
+ // an existing leveldb options object.
+ // Returns whether the read was successful, in which case the given settings
+ // will have the value set. Otherwise, error will be set.
+ bool ReadFromDb(
+ leveldb::ReadOptions options,
+ const std::string& key,
+ DictionaryValue* settings,
+ std::string* error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ std::string value_as_json;
+ leveldb::Status s = db_->Get(options, key, &value_as_json);
+ if (s.ok()) {
+ base::JSONReader json_reader;
+ Value* value = json_reader.JsonToValue(value_as_json, false, false);
+ if (value != NULL) {
+ settings->Set(key, value);
+ return true;
+ } else {
+ // TODO(kalman): clear the offending non-JSON value from the database.
+ LOG(ERROR) << "Invalid JSON: " << value_as_json;
+ *error = "Invalid JSON";
+ return false;
+ }
+ } else if (s.IsNotFound()) {
+ // Despite there being no value, it was still a success.
+ return true;
+ } else {
+ LOG(ERROR) << "Error reading from database: " << s.ToString();
+ *error = s.ToString();
+ return false;
+ }
+ }
+
+ 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.
+ DictionaryValue* settings_;
+ std::string error_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResultClosure);
+
+ void Succeed() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ 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.
+ delete this;
+ }
+
+ void Fail() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ delete settings_;
+ callback_->OnFailure(error_);
+ delete this;
+ }
+
+ scoped_ptr<ExtensionSettingsStorage::Callback> callback_;
+};
+
+// Result closure for Get(key).
+class Get1ResultClosure : public ResultClosure {
+ public:
+ Get1ResultClosure(
+ leveldb::DB* db,
+ ExtensionSettingsStorage::Callback* callback,
+ const std::string& key)
+ : ResultClosure(db, callback), key_(key) {}
+
+ protected:
+ virtual void RunOnFileThread() OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (ReadFromDb(leveldb::ReadOptions(), key_, settings_, &error_)) {
+ SucceedOnFileThread();
+ } else {
+ FailOnFileThread(kGenericOnFailureMessage);
+ }
+ }
+
+ private:
+ std::string key_;
+};
+
+// Result closure for Get(keys).
+class Get2ResultClosure : public ResultClosure {
+ public:
+ // Ownership of keys is taken.
+ Get2ResultClosure(
+ leveldb::DB* db,
+ ExtensionSettingsStorage::Callback* callback,
+ ListValue* keys)
+ : ResultClosure(db, callback), keys_(keys) {}
+
+ protected:
+ virtual void RunOnFileThread() OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ leveldb::ReadOptions options;
+ // All interaction with the db is done on the same thread, so snapshotting
+ // isn't strictly necessary. This is just defensive.
+ options.snapshot = db_->GetSnapshot();
+ bool success = true;
+ std::string key;
+
+ for (ListValue::const_iterator it = keys_->begin();
+ success && it != keys_->end(); ++it) {
+ if ((*it)->GetAsString(&key)) {
+ if (!ReadFromDb(options, key, settings_, &error_)) {
+ success = false;
+ }
+ }
+ }
+ db_->ReleaseSnapshot(options.snapshot);
+
+ if (success) {
+ SucceedOnFileThread();
+ } else {
+ FailOnFileThread(kGenericOnFailureMessage);
+ }
+ }
+
+ private:
+ scoped_ptr<ListValue> keys_;
+};
+
+// Result closure for Get() .
+class Get3ResultClosure : public ResultClosure {
+ public:
+ Get3ResultClosure(
+ leveldb::DB* db,
+ ExtensionSettingsStorage::Callback* callback)
+ : ResultClosure(db, callback) {
+ }
+
+ protected:
+ virtual void RunOnFileThread() OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ base::JSONReader json_reader;
+ leveldb::ReadOptions options = leveldb::ReadOptions();
+ // All interaction with the db is done on the same thread, so snapshotting
+ // isn't strictly necessary. This is just defensive.
+ options.snapshot = db_->GetSnapshot();
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
+
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ Value* value =
+ json_reader.JsonToValue(it->value().ToString(), false, false);
+ if (value != NULL) {
+ settings_->Set(it->key().ToString(), value);
+ } else {
+ // TODO(kalman): clear the offending non-JSON value from the database.
+ LOG(ERROR) << "Invalid JSON: " << it->value().ToString();
+ }
+ }
+ db_->ReleaseSnapshot(options.snapshot);
+
+ if (!it->status().ok()) {
+ LOG(ERROR) << "DB iteration failed: " << it->status().ToString();
+ FailOnFileThread(kGenericOnFailureMessage);
+ } else {
+ SucceedOnFileThread();
+ }
+ }
+};
+
+// Result closure for Set(key, value).
+class Set1ResultClosure : public ResultClosure {
+ public:
+ // Ownership of the value is taken.
+ Set1ResultClosure(
+ leveldb::DB* db,
+ ExtensionSettingsStorage::Callback* callback,
+ const std::string& key,
+ Value* value)
+ : ResultClosure(db, callback), key_(key), value_(value) {}
+
+ protected:
+ virtual void RunOnFileThread() OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ std::string value_as_json;
+ base::JSONWriter::Write(value_, false, &value_as_json);
+ leveldb::Status status =
+ db_->Put(leveldb::WriteOptions(), key_, value_as_json);
+ if (status.ok()) {
+ settings_->Set(key_, value_);
+ SucceedOnFileThread();
+ } else {
+ LOG(WARNING) << "DB write failed: " << status.ToString();
+ delete value_;
+ FailOnFileThread(kGenericOnFailureMessage);
+ }
+ }
+
+ private:
+ std::string key_;
+ 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.
+};
+
+// Result callback for Set(values).
+class Set2ResultClosure : public ResultClosure {
+ public:
+ // Ownership of values is taken.
+ Set2ResultClosure(
+ leveldb::DB* db,
+ ExtensionSettingsStorage::Callback* callback,
+ DictionaryValue* values)
+ : ResultClosure(db, callback), values_(values) {
+ }
+
+ protected:
+ virtual void RunOnFileThread() OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ // Gather keys since a dictionary can't be modified during iteration.
+ std::vector<std::string> keys;
+ for (DictionaryValue::key_iterator it = values_->begin_keys();
+ it != values_->end_keys(); ++it) {
+ keys.push_back(*it);
+ }
+ // Write values while transferring ownership from values_ to settings_.
+ std::string value_as_json;
+ leveldb::WriteBatch batch;
+ for (unsigned i = 0; i < keys.size(); ++i) {
+ Value* value = NULL;
+ values_->RemoveWithoutPathExpansion(keys[i], &value);
+ base::JSONWriter::Write(value, false, &value_as_json);
+ batch.Put(keys[i], value_as_json);
+ settings_->SetWithoutPathExpansion(keys[i], value);
+ }
+ DCHECK(values_->empty());
+
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (status.ok()) {
+ SucceedOnFileThread();
+ } else {
+ LOG(WARNING) << "DB batch write failed: " << status.ToString();
+ FailOnFileThread(kGenericOnFailureMessage);
+ }
+ }
+
+ private:
+ scoped_ptr<DictionaryValue> values_; // will be empty on destruction
+};
+
+// Result closure for Remove(key).
+class Remove1ResultClosure : public ResultClosure {
+ public:
+ Remove1ResultClosure(
+ leveldb::DB* db,
+ ExtensionSettingsStorage::Callback* callback,
+ const std::string& key)
+ : ResultClosure(db, callback), key_(key) {
+ }
+
+ protected:
+ virtual void RunOnFileThread() OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_);
+ if (status.ok() || status.IsNotFound()) {
+ SucceedOnFileThread();
+ } else {
+ LOG(WARNING) << "DB delete failed: " << status.ToString();
+ FailOnFileThread(kGenericOnFailureMessage);
+ }
+ }
+
+ private:
+ std::string key_;
+};
+
+// Result closure for Remove(keys).
+class Remove2ResultClosure : public ResultClosure {
+ public:
+ // Ownership of keys is taken.
+ Remove2ResultClosure(
+ leveldb::DB* db,
+ ExtensionSettingsStorage::Callback* callback,
+ ListValue* keys)
+ : ResultClosure(db, callback), keys_(keys) {
+ }
+
+ protected:
+ virtual void RunOnFileThread() OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ std::string key;
+ leveldb::WriteBatch batch;
+ for (ListValue::const_iterator it = keys_->begin();
+ it != keys_->end(); ++it) {
+ if ((*it)->GetAsString(&key)) {
+ batch.Delete(key);
+ }
+ }
+
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (status.ok() || status.IsNotFound()) {
+ SucceedOnFileThread();
+ } else {
+ LOG(WARNING) << "DB batch delete failed: " << status.ToString();
+ FailOnFileThread(kGenericOnFailureMessage);
+ }
+ }
+
+ private:
+ scoped_ptr<ListValue> keys_;
+};
+
+// Result closure for Clear().
+class ClearResultClosure : public ResultClosure {
+ public:
+ ClearResultClosure(
+ leveldb::DB* db,
+ ExtensionSettingsStorage::Callback* callback)
+ : ResultClosure(db, callback) {
+ }
+
+ protected:
+ virtual void RunOnFileThread() OVERRIDE {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ leveldb::ReadOptions options;
+ // All interaction with the db is done on the same thread, so snapshotting
+ // isn't strictly necessary. This is just defensive.
+ options.snapshot = db_->GetSnapshot();
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
+ leveldb::WriteBatch batch;
+
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ batch.Delete(it->key());
+ }
+ db_->ReleaseSnapshot(options.snapshot);
+
+ if (it->status().ok()) {
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (status.ok() || status.IsNotFound()) {
+ SucceedOnFileThread();
+ } else {
+ LOG(WARNING) << "Clear failed: " << status.ToString();
+ FailOnFileThread(kGenericOnFailureMessage);
+ }
+ } else {
+ LOG(WARNING) << "Clear iteration failed: " << it->status().ToString();
+ FailOnFileThread(kGenericOnFailureMessage);
+ }
+ }
+};
+
+// Deletes a storage area on the FILE thread.
+class DeleteStorageClosure {
+ public:
+ // Ownership of storage is taken.
+ explicit DeleteStorageClosure(ExtensionSettingsLeveldbStorage* storage)
+ : storage_(storage) {}
+
+ ~DeleteStorageClosure() {}
+
+ void Run() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(
+ &DeleteStorageClosure::RunOnFileThread, base::Unretained(this)));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DeleteStorageClosure);
+
+ void RunOnFileThread() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ delete this;
+ }
+
+ scoped_ptr<ExtensionSettingsStorage> storage_;
+};
+
+} // namespace
+
+/* static */
+ExtensionSettingsLeveldbStorage* ExtensionSettingsLeveldbStorage::Create(
+ const FilePath& base_path,
+ const std::string& extension_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ FilePath path = base_path.AppendASCII(extension_id);
+
+#if defined(OS_POSIX)
+ std::string os_path(path.value());
+#elif defined(OS_WIN)
+ std::string os_path = base::SysWideToUTF8(path.value());
+#endif
+
+ leveldb::Options options;
+ options.create_if_missing = true;
+ leveldb::DB* db;
+ leveldb::Status status = leveldb::DB::Open(options, os_path, &db);
+ if (!status.ok()) {
+ LOG(WARNING) << "Failed to create leveldb at " << path.value() <<
+ ": " << status.ToString();
+ return NULL;
+ }
+ return new ExtensionSettingsLeveldbStorage(db);
+}
+
+void ExtensionSettingsLeveldbStorage::DestroyEventually() {
+ (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.
+}
+
+void ExtensionSettingsLeveldbStorage::Get(
+ const std::string& key, ExtensionSettingsStorage::Callback* callback) {
+ (new Get1ResultClosure(db_.get(), callback, key))->Run();
+}
+
+void ExtensionSettingsLeveldbStorage::Get(
+ const ListValue& keys, ExtensionSettingsStorage::Callback* callback) {
+ (new Get2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run();
+}
+
+void ExtensionSettingsLeveldbStorage::Get(
+ ExtensionSettingsStorage::Callback* callback) {
+ (new Get3ResultClosure(db_.get(), callback))->Run();
+}
+
+void ExtensionSettingsLeveldbStorage::Set(
+ const std::string& key,
+ const Value& value,
+ ExtensionSettingsStorage::Callback* callback) {
+ (new Set1ResultClosure(db_.get(), callback, key, value.DeepCopy()))->Run();
+}
+
+void ExtensionSettingsLeveldbStorage::Set(
+ const DictionaryValue& values,
+ ExtensionSettingsStorage::Callback* callback) {
+ (new Set2ResultClosure(db_.get(), callback, values.DeepCopy()))->Run();
+}
+
+void ExtensionSettingsLeveldbStorage::Remove(
+ const std::string& key, ExtensionSettingsStorage::Callback *callback) {
+ (new Remove1ResultClosure(db_.get(), callback, key))->Run();
+}
+
+void ExtensionSettingsLeveldbStorage::Remove(
+ const ListValue& keys, ExtensionSettingsStorage::Callback *callback) {
+ (new Remove2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run();
+}
+
+void ExtensionSettingsLeveldbStorage::Clear(
+ ExtensionSettingsStorage::Callback* callback) {
+ (new ClearResultClosure(db_.get(), callback))->Run();
+}
+
+ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage(
+ leveldb::DB* db)
+ : db_(db) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+}
+
+ExtensionSettingsLeveldbStorage::~ExtensionSettingsLeveldbStorage() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+}

Powered by Google App Engine
This is Rietveld 408576698