| Index: base/prefs/leveldb_pref_store.cc
|
| diff --git a/base/prefs/leveldb_pref_store.cc b/base/prefs/leveldb_pref_store.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..194e17d73e503b50143dae0940971894a2b74fd1
|
| --- /dev/null
|
| +++ b/base/prefs/leveldb_pref_store.cc
|
| @@ -0,0 +1,279 @@
|
| +// Copyright (c) 2012 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 "base/prefs/leveldb_pref_store.h"
|
| +
|
| +#include <algorithm>
|
| +#include <set>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/callback.h"
|
| +#include "base/file_util.h"
|
| +#include "base/json/json_file_value_serializer.h"
|
| +#include "base/json/json_string_value_serializer.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "base/message_loop/message_loop_proxy.h"
|
| +#include "base/prefs/pref_filter.h"
|
| +#include "base/sequenced_task_runner.h"
|
| +#include "base/threading/sequenced_worker_pool.h"
|
| +#include "base/values.h"
|
| +#include "third_party/leveldatabase/src/include/leveldb/db.h"
|
| +
|
| +namespace {
|
| +
|
| +// Some extensions we'll tack on to copies of the Preferences files.
|
| +//const base::FilePath::CharType* kBadExtension = FILE_PATH_LITERAL("bad");
|
| +
|
| +}
|
| +
|
| +LevelDBPrefStore::LevelDBPrefStore(
|
| + const base::FilePath& filename,
|
| + base::SequencedTaskRunner* sequenced_task_runner,
|
| + scoped_ptr<PrefFilter> pref_filter)
|
| + : path_(filename),
|
| + sequenced_task_runner_(sequenced_task_runner),
|
| + original_task_runner_(base::MessageLoopProxy::current()),
|
| + read_only_(false),
|
| + pref_filter_(pref_filter.Pass()),
|
| + initialized_(false),
|
| + read_error_(PREF_READ_ERROR_OTHER) {}
|
| +
|
| +bool LevelDBPrefStore::GetValue(const std::string& key,
|
| + const base::Value** result) const {
|
| + LOG(ERROR) << "Answering call to GetValue(" << key << ")";
|
| + const base::Value* tmp = NULL;
|
| + if (!prefs_.GetValue(key, &tmp)) {
|
| + return false;
|
| + }
|
| +
|
| + if (result)
|
| + *result = tmp;
|
| + return true;
|
| +}
|
| +
|
| +// Callers of GetMutableValue have to also call ReportValueChanged.
|
| +bool LevelDBPrefStore::GetMutableValue(const std::string& key,
|
| + base::Value** result) {
|
| + return prefs_.GetValue(key, result);
|
| +}
|
| +
|
| +void LevelDBPrefStore::AddObserver(PrefStore::Observer* observer) {
|
| + observers_.AddObserver(observer);
|
| +}
|
| +
|
| +void LevelDBPrefStore::RemoveObserver(PrefStore::Observer* observer) {
|
| + observers_.RemoveObserver(observer);
|
| +}
|
| +
|
| +bool LevelDBPrefStore::HasObservers() const {
|
| + return observers_.might_have_observers();
|
| +}
|
| +
|
| +bool LevelDBPrefStore::IsInitializationComplete() const { return initialized_; }
|
| +
|
| +void LevelDBPrefStore::PersistOnFileThread(const std::string& key,
|
| + const std::string& value) {
|
| + DCHECK(sequenced_task_runner_->RunsTasksOnCurrentThread());
|
| +
|
| + leveldb::Status status = db_->Put(leveldb::WriteOptions(), key, value);
|
| + // DCHECK is fine; the corresponding error is ignored in json_pref_store.
|
| + // But change to DLOG_IF or something, not DCHECK.
|
| + DCHECK(status.ok());
|
| +}
|
| +
|
| +void LevelDBPrefStore::RemoveOnFileThread(const std::string& key) {
|
| + DCHECK(sequenced_task_runner_->RunsTasksOnCurrentThread());
|
| +
|
| + leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key);
|
| + // DCHECK is fine; the corresponding error is ignored in json_pref_store.
|
| + // But change to DLOG_IF or something, not DCHECK.
|
| + DCHECK(status.ok());
|
| +}
|
| +
|
| +void LevelDBPrefStore::RemoveFromUIThread(const std::string& key) {
|
| + if (read_only_)
|
| + return;
|
| + sequenced_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(
|
| + &LevelDBPrefStore::RemoveOnFileThread, this, key));
|
| +}
|
| +
|
| +void LevelDBPrefStore::PersistFromUIThread(const std::string& key,
|
| + base::Value* value) {
|
| + if (read_only_)
|
| + return;
|
| + std::string value_string;
|
| + JSONStringValueSerializer serializer(&value_string);
|
| + DCHECK(serializer.Serialize(*value));
|
| +
|
| + sequenced_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(
|
| + &LevelDBPrefStore::PersistOnFileThread, this, key, value_string));
|
| +}
|
| +
|
| +void LevelDBPrefStore::SetValue(const std::string& key, base::Value* value) {
|
| + DCHECK(value);
|
| + scoped_ptr<base::Value> new_value(value);
|
| + base::Value* old_value = NULL;
|
| + bool found = prefs_.GetValue(key, &old_value);
|
| + if (!found || !value->Equals(old_value)) {
|
| + PersistFromUIThread(key, new_value.get());
|
| + prefs_.SetValue(key, new_value.release());
|
| + NotifyObservers(key);
|
| + }
|
| +}
|
| +
|
| +void LevelDBPrefStore::SetValueSilently(const std::string& key,
|
| + base::Value* value) {
|
| + DCHECK(value);
|
| + scoped_ptr<base::Value> new_value(value);
|
| + base::Value* old_value = NULL;
|
| + prefs_.GetValue(key, &old_value);
|
| + if (!old_value || !value->Equals(old_value)) {
|
| + PersistFromUIThread(key, new_value.get());
|
| + prefs_.SetValue(key, new_value.release());
|
| + }
|
| +}
|
| +
|
| +void LevelDBPrefStore::RemoveValue(const std::string& key) {
|
| + if (prefs_.RemoveValue(key)) {
|
| + RemoveFromUIThread(key);
|
| + NotifyObservers(key);
|
| + }
|
| +}
|
| +
|
| +bool LevelDBPrefStore::ReadOnly() const { return read_only_; }
|
| +
|
| +PersistentPrefStore::PrefReadError LevelDBPrefStore::GetReadError() const {
|
| + return read_error_;
|
| +}
|
| +
|
| +PersistentPrefStore::PrefReadError LevelDBPrefStore::ReadPrefs() {
|
| + DCHECK(!db_);
|
| + if (path_.empty()) {
|
| + OnStorageRead(PREF_READ_ERROR_FILE_NOT_SPECIFIED, false);
|
| + return PREF_READ_ERROR_FILE_NOT_SPECIFIED;
|
| + }
|
| +
|
| + bool no_dir = !base::PathExists(path_.DirName());
|
| +
|
| + leveldb::DB* db;
|
| + leveldb::Options options;
|
| + options.create_if_missing = true;
|
| + leveldb::Status status =
|
| + leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db);
|
| + if (!status.ok()) {
|
| + // TODO(dgrogan): Add error histogram and call RepairDB.
|
| + OnStorageRead(PREF_READ_ERROR_FILE_OTHER, no_dir);
|
| + return PREF_READ_ERROR_FILE_OTHER;
|
| + }
|
| + db_.reset(db);
|
| +
|
| + scoped_ptr<leveldb::Iterator> it(db->NewIterator(leveldb::ReadOptions()));
|
| + for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
| + const std::string value_string = it->value().ToString();
|
| + LOG(ERROR) << "Reading: got " << it->key().ToString() << " -> "
|
| + << it->value().ToString();
|
| + JSONStringValueSerializer deserializer(value_string);
|
| + std::string error_message;
|
| + int error_code;
|
| + base::Value* json_value =
|
| + deserializer.Deserialize(&error_code, &error_message);
|
| + DCHECK(json_value) << error_message;
|
| + // TODO(dgrogan): Histogram number of corrupt values?
|
| + prefs_.SetValue(it->key().ToString(), json_value);
|
| + }
|
| +
|
| + PersistentPrefStore::PrefReadError error = PREF_READ_ERROR_NONE;
|
| + if (!it->status().ok()) {
|
| + // TODO(dgrogan): Add error histogram.
|
| + error = PREF_READ_ERROR_FILE_OTHER;
|
| + }
|
| + OnStorageRead(error, no_dir);
|
| + return error;
|
| +}
|
| +
|
| +void LevelDBPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
|
| + DCHECK_EQ(false, initialized_);
|
| + error_delegate_.reset(error_delegate);
|
| + if (path_.empty()) {
|
| + OnStorageRead(PREF_READ_ERROR_FILE_NOT_SPECIFIED, false);
|
| + return;
|
| + }
|
| +
|
| + sequenced_task_runner_->PostTask(FROM_HERE, base::Bind(
|
| + base::IgnoreResult(&LevelDBPrefStore::ReadPrefs), this));
|
| +}
|
| +
|
| +void LevelDBPrefStore::CommitPendingWrite() {
|
| + // No-op, key-value pairs are committed as they are set.
|
| + // TODO(dgrogan): This won't perform well because the extensions prefs are
|
| + // changed frequently. We'll have to track a set of keys that have changed
|
| + // and write them to disk every 10 seconds.
|
| +}
|
| +
|
| +void LevelDBPrefStore::ReportValueChanged(const std::string& key) {
|
| + base::Value* new_value = NULL;
|
| + DCHECK(prefs_.GetValue(key, &new_value));
|
| + if (pref_filter_)
|
| + pref_filter_->FilterUpdate(key);
|
| + PersistFromUIThread(key, new_value);
|
| + NotifyObservers(key);
|
| +}
|
| +
|
| +void LevelDBPrefStore::NotifyObservers(const std::string& key) {
|
| + FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key));
|
| +}
|
| +
|
| +void LevelDBPrefStore::OnStorageRead(PersistentPrefStore::PrefReadError error,
|
| + bool no_dir) {
|
| + if (!original_task_runner_->RunsTasksOnCurrentThread()) {
|
| + // When db is opened async, this function needs to be called on original
|
| + // thread.
|
| + original_task_runner_->PostTask(FROM_HERE,
|
| + base::Bind(&LevelDBPrefStore::OnStorageRead, this, error, no_dir));
|
| + }
|
| + read_error_ = error;
|
| +
|
| + if (no_dir) {
|
| + FOR_EACH_OBSERVER(
|
| + PrefStore::Observer, observers_, OnInitializationCompleted(false));
|
| + return;
|
| + }
|
| +
|
| + initialized_ = true;
|
| +
|
| + switch (error) {
|
| + case PREF_READ_ERROR_ACCESS_DENIED:
|
| + case PREF_READ_ERROR_FILE_OTHER:
|
| + case PREF_READ_ERROR_FILE_LOCKED:
|
| + case PREF_READ_ERROR_JSON_TYPE:
|
| + case PREF_READ_ERROR_FILE_NOT_SPECIFIED:
|
| + read_only_ = true;
|
| + break;
|
| + case PREF_READ_ERROR_NONE:
|
| + break;
|
| + case PREF_READ_ERROR_NO_FILE:
|
| + // If the file just doesn't exist, maybe this is first run. In any case
|
| + // there's no harm in writing out default prefs in this case.
|
| + break;
|
| + case PREF_READ_ERROR_JSON_PARSE:
|
| + case PREF_READ_ERROR_JSON_REPEAT:
|
| + break;
|
| + default:
|
| + NOTREACHED() << "Unknown error: " << error;
|
| + }
|
| +
|
| + // TODO(dgrogan): Call pref_filter_->FilterOnLoad
|
| +
|
| + if (error_delegate_.get() && error != PREF_READ_ERROR_NONE)
|
| + error_delegate_->OnError(error);
|
| +
|
| + FOR_EACH_OBSERVER(
|
| + PrefStore::Observer, observers_, OnInitializationCompleted(true));
|
| +}
|
| +
|
| +LevelDBPrefStore::~LevelDBPrefStore() {}
|
|
|