| 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..f7e6608f77306967bb5b4e07c8424ba34969d02a
|
| --- /dev/null
|
| +++ b/chrome/browser/extensions/extension_settings_leveldb_storage.cc
|
| @@ -0,0 +1,417 @@
|
| +// 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/json/json_reader.h"
|
| +#include "base/json/json_writer.h"
|
| +#include "base/logging.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 {
|
| +
|
| +// 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 RunOnFileThreadImpl(), manipulating settings_
|
| +// or error_, then call either SucceedOnFileThread() or FailOnFileThread().
|
| +class ResultClosure {
|
| + public:
|
| + 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 RunOnFileThreadImpl() = 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_;
|
| + DictionaryValue* settings_;
|
| + std::string error_;
|
| +
|
| + private:
|
| + void RunOnFileThread() {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| + RunOnFileThreadImpl();
|
| + }
|
| +
|
| + void Succeed() {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + callback_->OnSuccess(settings_);
|
| + 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:
|
| + void RunOnFileThreadImpl() {
|
| + if (ReadFromDb(leveldb::ReadOptions(), key_, settings_, &error_)) {
|
| + SucceedOnFileThread();
|
| + } else {
|
| + FailOnFileThread("Failed");
|
| + }
|
| + }
|
| +
|
| + 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:
|
| + void RunOnFileThreadImpl() {
|
| + leveldb::ReadOptions options;
|
| + 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("Failed");
|
| + }
|
| + }
|
| +
|
| + 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:
|
| + void RunOnFileThreadImpl() {
|
| + base::JSONReader json_reader;
|
| + leveldb::ReadOptions options = leveldb::ReadOptions();
|
| + 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("Failed");
|
| + } 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:
|
| + void RunOnFileThreadImpl() {
|
| + 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("Failed");
|
| + }
|
| + }
|
| +
|
| + private:
|
| + std::string key_;
|
| + Value* value_;
|
| +};
|
| +
|
| +// 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:
|
| + void RunOnFileThreadImpl() {
|
| + // 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("Failed");
|
| + }
|
| + }
|
| +
|
| + 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:
|
| + void RunOnFileThreadImpl() {
|
| + leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_);
|
| + if (status.ok() || status.IsNotFound()) {
|
| + SucceedOnFileThread();
|
| + } else {
|
| + LOG(WARNING) << "DB delete failed: " << status.ToString();
|
| + FailOnFileThread("Failed");
|
| + }
|
| + }
|
| +
|
| + 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:
|
| + void RunOnFileThreadImpl() {
|
| + 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("Failed");
|
| + }
|
| + }
|
| +
|
| + 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:
|
| + void RunOnFileThreadImpl() {
|
| + leveldb::ReadOptions options;
|
| + 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("Failed");
|
| + }
|
| + } else {
|
| + LOG(WARNING) << "Clear iteration failed: " << it->status().ToString();
|
| + FailOnFileThread("Failed");
|
| + }
|
| + }
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage(
|
| + leveldb::DB* db) : db_(db) {
|
| +}
|
| +
|
| +ExtensionSettingsLeveldbStorage::~ExtensionSettingsLeveldbStorage() {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| +}
|
| +
|
| +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();
|
| +}
|
|
|