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..c7a8f782ce8bb2bced8747aa103cb2a4f1525027 |
--- /dev/null |
+++ b/chrome/browser/extensions/extension_settings_leveldb_storage.cc |
@@ -0,0 +1,413 @@ |
+// 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(); |
dgrogan
2011/06/18 01:54:40
Why do you use snapshots when you're reading? Won
not at google - send to devlin
2011/06/20 05:33:11
Hm. Yes. It just seems... instinctively wrong to
dgrogan
2011/06/21 02:36:05
Ok, they are just defensive. Using them is fine,
not at google - send to devlin
2011/06/22 09:40:38
Cool, I added a comment.
|
+ 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"); |
dgrogan
2011/06/18 01:54:40
Why don't you pass it->status().ToString() back th
not at google - send to devlin
2011/06/20 05:33:11
This is the message sent back to the extension. I
dgrogan
2011/06/21 02:36:05
Ok, I see where you're coming from.
not at google - send to devlin
2011/06/22 09:40:38
Btw I had a look through the leveldb code but wasn
Mihai Parparita -not on Chrome
2011/06/23 03:16:46
Sorry, didn't see this question earlier. Unless th
not at google - send to devlin
2011/06/23 13:44:26
Done.
|
+ } 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()); |
dgrogan
2011/06/18 01:54:40
Does batching up a bunch of deletes on an iterator
not at google - send to devlin
2011/06/20 05:33:11
Maybe I'm misunderstanding something. The snapsho
dgrogan
2011/06/21 02:36:05
Oh, I didn't realize it gets a consistent set of k
not at google - send to devlin
2011/06/22 09:40:38
I think it gets a consistent view of both keys and
|
+ } |
+ 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) { |
+} |
+ |
+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(); |
+} |