mash: Add a simple, temporary preferences store.
In the long run, we'll need a real solution to preferences, but for now,
we replace the implementation of PersistentPrefsStore with one that
writes JSON values to file on the mojo:filesystem. The current interface
isn't really a match made in heaven and it doesn't really deal with all
the complexity that we need to worry about (see 580652), but it's
reasonably sandboxed and will work for now.
BUG=557405, 580652
Review URL: https://codereview.chromium.org/1624683002
Cr-Commit-Position: refs/heads/master@{#371086}
diff --git a/components/filesystem/BUILD.gn b/components/filesystem/BUILD.gn
index 299ac10..bbb131a4 100644
--- a/components/filesystem/BUILD.gn
+++ b/components/filesystem/BUILD.gn
@@ -62,6 +62,7 @@
deps = [
"//base",
"//components/filesystem/public/interfaces",
+ "//mojo/common",
"//mojo/platform_handle:for_shared_library",
"//mojo/public/cpp/bindings",
"//mojo/shell/public/cpp:test_support",
diff --git a/components/filesystem/DEPS b/components/filesystem/DEPS
index ba5ed501..89e17ff 100644
--- a/components/filesystem/DEPS
+++ b/components/filesystem/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+mojo/common",
"+mojo/shell",
"+mojo/platform_handle",
"+mojo/public",
diff --git a/components/filesystem/directory_impl.cc b/components/filesystem/directory_impl.cc
index 715c31f..8c86fc7 100644
--- a/components/filesystem/directory_impl.cc
+++ b/components/filesystem/directory_impl.cc
@@ -70,7 +70,7 @@
base::File base_file(path, open_flags);
if (!base_file.IsValid()) {
- callback.Run(FileError::FAILED);
+ callback.Run(GetError(base_file));
return;
}
@@ -203,7 +203,7 @@
void DirectoryImpl::Flush(const FlushCallback& callback) {
base::File file(directory_path_, base::File::FLAG_READ);
if (!file.IsValid()) {
- callback.Run(FileError::FAILED);
+ callback.Run(GetError(file));
return;
}
diff --git a/components/filesystem/directory_impl_unittest.cc b/components/filesystem/directory_impl_unittest.cc
index a5638b7..41deec9 100644
--- a/components/filesystem/directory_impl_unittest.cc
+++ b/components/filesystem/directory_impl_unittest.cc
@@ -103,7 +103,7 @@
directory->OpenFile("my_file", nullptr, kFlagRead | kFlagOpen,
Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingResponse());
- EXPECT_EQ(FileError::FAILED, error);
+ EXPECT_EQ(FileError::NOT_FOUND, error);
// Opening my_new_file should succeed.
error = FileError::FAILED;
@@ -122,7 +122,7 @@
directory->OpenFile("my_new_file", nullptr, kFlagRead | kFlagOpen,
Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingResponse());
- EXPECT_EQ(FileError::FAILED, error);
+ EXPECT_EQ(FileError::NOT_FOUND, error);
}
TEST_F(DirectoryImplTest, CantOpenDirectoriesAsFiles) {
diff --git a/components/filesystem/file_impl.cc b/components/filesystem/file_impl.cc
index 1d6b7e2..da156c86 100644
--- a/components/filesystem/file_impl.cc
+++ b/components/filesystem/file_impl.cc
@@ -14,6 +14,7 @@
#include "base/logging.h"
#include "build/build_config.h"
#include "components/filesystem/util.h"
+#include "mojo/common/common_type_converters.h"
#include "mojo/platform_handle/platform_handle_functions.h"
static_assert(sizeof(off_t) <= sizeof(int64_t), "off_t too big");
@@ -93,6 +94,28 @@
callback.Run(FileError::OK, std::move(bytes_read));
}
+void FileImpl::ReadEntireFile(const ReadEntireFileCallback& callback) {
+ if (!file_.IsValid()) {
+ callback.Run(GetError(file_), mojo::Array<uint8_t>());
+ return;
+ }
+
+ // Seek to the front of the file.
+ if (file_.Seek(base::File::FROM_BEGIN, 0) == -1) {
+ callback.Run(FileError::FAILED, mojo::Array<uint8_t>());
+ return;
+ }
+
+ std::string contents;
+ const int kBufferSize = 1 << 16;
+ scoped_ptr<char[]> buf(new char[kBufferSize]);
+ size_t len;
+ while ((len = file_.ReadAtCurrentPos(buf.get(), kBufferSize)) > 0)
+ contents.append(buf.get(), len);
+
+ callback.Run(FileError::OK, mojo::Array<uint8_t>::From(contents));
+}
+
// TODO(vtl): Move the implementation to a thread pool.
void FileImpl::Write(mojo::Array<uint8_t> bytes_to_write,
int64_t offset,
diff --git a/components/filesystem/file_impl.h b/components/filesystem/file_impl.h
index b7e5185..aeb77c1 100644
--- a/components/filesystem/file_impl.h
+++ b/components/filesystem/file_impl.h
@@ -34,6 +34,7 @@
int64_t offset,
Whence whence,
const ReadCallback& callback) override;
+ void ReadEntireFile(const ReadEntireFileCallback& callback) override;
void Write(mojo::Array<uint8_t> bytes_to_write,
int64_t offset,
Whence whence,
diff --git a/components/filesystem/public/cpp/prefs/BUILD.gn b/components/filesystem/public/cpp/prefs/BUILD.gn
new file mode 100644
index 0000000..22131db
--- /dev/null
+++ b/components/filesystem/public/cpp/prefs/BUILD.gn
@@ -0,0 +1,20 @@
+# Copyright 2016 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.
+
+source_set("prefs") {
+ sources = [
+ "filesystem_json_pref_store.cc",
+ "filesystem_json_pref_store.h",
+ "pref_service_factory.cc",
+ "pref_service_factory.h",
+ ]
+
+ deps = [
+ "//base",
+ "//base:prefs",
+ "//components/filesystem/public/interfaces",
+ "//mojo/common",
+ "//mojo/shell/public/cpp",
+ ]
+}
diff --git a/components/filesystem/public/cpp/prefs/filesystem_json_pref_store.cc b/components/filesystem/public/cpp/prefs/filesystem_json_pref_store.cc
new file mode 100644
index 0000000..ffb93b7
--- /dev/null
+++ b/components/filesystem/public/cpp/prefs/filesystem_json_pref_store.cc
@@ -0,0 +1,452 @@
+// 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 "components/filesystem/public/cpp/prefs/filesystem_json_pref_store.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/prefs/pref_filter.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/task_runner_util.h"
+#include "base/time/default_clock.h"
+#include "base/values.h"
+#include "mojo/common/common_type_converters.h"
+
+namespace filesystem {
+
+// Result returned from internal read tasks.
+struct FilesystemJsonPrefStore::ReadResult {
+ public:
+ ReadResult();
+ ~ReadResult();
+
+ scoped_ptr<base::Value> value;
+ PrefReadError error;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReadResult);
+};
+
+FilesystemJsonPrefStore::ReadResult::ReadResult()
+ : error(PersistentPrefStore::PREF_READ_ERROR_NONE) {}
+
+FilesystemJsonPrefStore::ReadResult::~ReadResult() {}
+
+namespace {
+
+PersistentPrefStore::PrefReadError HandleReadErrors(const base::Value* value) {
+ if (!value->IsType(base::Value::TYPE_DICTIONARY))
+ return PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE;
+ return PersistentPrefStore::PREF_READ_ERROR_NONE;
+}
+
+} // namespace
+
+FilesystemJsonPrefStore::FilesystemJsonPrefStore(
+ const std::string& pref_filename,
+ filesystem::FileSystemPtr filesystem,
+ scoped_ptr<PrefFilter> pref_filter)
+ : path_(pref_filename),
+ binding_(this),
+ filesystem_(std::move(filesystem)),
+ prefs_(new base::DictionaryValue()),
+ read_only_(false),
+ pref_filter_(std::move(pref_filter)),
+ initialized_(false),
+ filtering_in_progress_(false),
+ pending_lossy_write_(false),
+ read_error_(PREF_READ_ERROR_NONE) {
+ DCHECK(!path_.empty());
+}
+
+bool FilesystemJsonPrefStore::GetValue(const std::string& key,
+ const base::Value** result) const {
+ DCHECK(CalledOnValidThread());
+
+ base::Value* tmp = nullptr;
+ if (!prefs_->Get(key, &tmp))
+ return false;
+
+ if (result)
+ *result = tmp;
+ return true;
+}
+
+void FilesystemJsonPrefStore::AddObserver(PrefStore::Observer* observer) {
+ DCHECK(CalledOnValidThread());
+
+ observers_.AddObserver(observer);
+}
+
+void FilesystemJsonPrefStore::RemoveObserver(PrefStore::Observer* observer) {
+ DCHECK(CalledOnValidThread());
+
+ observers_.RemoveObserver(observer);
+}
+
+bool FilesystemJsonPrefStore::HasObservers() const {
+ DCHECK(CalledOnValidThread());
+
+ return observers_.might_have_observers();
+}
+
+bool FilesystemJsonPrefStore::IsInitializationComplete() const {
+ DCHECK(CalledOnValidThread());
+
+ return initialized_;
+}
+
+bool FilesystemJsonPrefStore::GetMutableValue(const std::string& key,
+ base::Value** result) {
+ DCHECK(CalledOnValidThread());
+
+ return prefs_->Get(key, result);
+}
+
+void FilesystemJsonPrefStore::SetValue(const std::string& key,
+ scoped_ptr<base::Value> value,
+ uint32_t flags) {
+ DCHECK(CalledOnValidThread());
+
+ DCHECK(value);
+ base::Value* old_value = nullptr;
+ prefs_->Get(key, &old_value);
+ if (!old_value || !value->Equals(old_value)) {
+ prefs_->Set(key, std::move(value));
+ ReportValueChanged(key, flags);
+ }
+}
+
+void FilesystemJsonPrefStore::SetValueSilently(const std::string& key,
+ scoped_ptr<base::Value> value,
+ uint32_t flags) {
+ DCHECK(CalledOnValidThread());
+
+ DCHECK(value);
+ base::Value* old_value = nullptr;
+ prefs_->Get(key, &old_value);
+ if (!old_value || !value->Equals(old_value)) {
+ prefs_->Set(key, std::move(value));
+ ScheduleWrite(flags);
+ }
+}
+
+void FilesystemJsonPrefStore::RemoveValue(const std::string& key,
+ uint32_t flags) {
+ DCHECK(CalledOnValidThread());
+
+ if (prefs_->RemovePath(key, nullptr))
+ ReportValueChanged(key, flags);
+}
+
+void FilesystemJsonPrefStore::RemoveValueSilently(const std::string& key,
+ uint32_t flags) {
+ DCHECK(CalledOnValidThread());
+
+ prefs_->RemovePath(key, nullptr);
+ ScheduleWrite(flags);
+}
+
+bool FilesystemJsonPrefStore::ReadOnly() const {
+ DCHECK(CalledOnValidThread());
+
+ return read_only_;
+}
+
+PersistentPrefStore::PrefReadError FilesystemJsonPrefStore::GetReadError()
+ const {
+ DCHECK(CalledOnValidThread());
+
+ return read_error_;
+}
+
+PersistentPrefStore::PrefReadError FilesystemJsonPrefStore::ReadPrefs() {
+ NOTREACHED();
+ // TODO(erg): Synchronously reading files makes no sense in a mojo world and
+ // should be removed from the API.
+ return PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE;
+}
+
+void FilesystemJsonPrefStore::ReadPrefsAsync(
+ ReadErrorDelegate* error_delegate) {
+ DCHECK(CalledOnValidThread());
+
+ initialized_ = false;
+ error_delegate_.reset(error_delegate);
+
+ if (!directory_) {
+ OpenFilesystem(
+ Bind(&FilesystemJsonPrefStore::OnPreferencesReadStart, AsWeakPtr()));
+ } else {
+ OnPreferencesReadStart();
+ }
+}
+
+void FilesystemJsonPrefStore::CommitPendingWrite() {
+ DCHECK(CalledOnValidThread());
+
+ // TODO(erg): This is another one of those cases where we have problems
+ // because of mismatch between the models used in the pref service versus
+ // here. Most of the time, CommitPendingWrite() is called from
+ // PrefService. However, in JSONPrefStore, we also call this method on
+ // shutdown and thus need to synchronously write. But in mojo:filesystem,
+ // everything is done asynchronously. So we're sort of stuck until we can
+ // change the interface, which we'll do in the longer term.
+
+ SchedulePendingLossyWrites();
+}
+
+void FilesystemJsonPrefStore::SchedulePendingLossyWrites() {
+ // This method is misnamed for the sake of the interface. Given that writing
+ // is already asynchronous in this new world, "sheduleing" a pending write
+ // just starts the asynchronous process.
+ if (pending_lossy_write_)
+ PerformWrite();
+}
+
+void FilesystemJsonPrefStore::ReportValueChanged(const std::string& key,
+ uint32_t flags) {
+ DCHECK(CalledOnValidThread());
+
+ if (pref_filter_)
+ pref_filter_->FilterUpdate(key);
+
+ FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key));
+
+ ScheduleWrite(flags);
+}
+
+void FilesystemJsonPrefStore::OnFileSystemShutdown() {}
+
+void FilesystemJsonPrefStore::OnFileRead(scoped_ptr<ReadResult> read_result) {
+ DCHECK(CalledOnValidThread());
+
+ DCHECK(read_result);
+
+ scoped_ptr<base::DictionaryValue> unfiltered_prefs(new base::DictionaryValue);
+
+ read_error_ = read_result->error;
+
+ switch (read_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:
+ DCHECK(read_result->value.get());
+ unfiltered_prefs.reset(
+ static_cast<base::DictionaryValue*>(read_result->value.release()));
+ 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.
+ case PREF_READ_ERROR_JSON_PARSE:
+ case PREF_READ_ERROR_JSON_REPEAT:
+ break;
+ case PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE:
+ // This is a special error code to be returned by ReadPrefs when it
+ // can't complete synchronously, it should never be returned by the read
+ // operation itself.
+ case PREF_READ_ERROR_MAX_ENUM:
+ NOTREACHED();
+ break;
+ }
+
+ if (pref_filter_) {
+ filtering_in_progress_ = true;
+ const PrefFilter::PostFilterOnLoadCallback post_filter_on_load_callback(
+ base::Bind(&FilesystemJsonPrefStore::FinalizeFileRead, AsWeakPtr()));
+ pref_filter_->FilterOnLoad(post_filter_on_load_callback,
+ std::move(unfiltered_prefs));
+ } else {
+ FinalizeFileRead(std::move(unfiltered_prefs), false);
+ }
+}
+
+FilesystemJsonPrefStore::~FilesystemJsonPrefStore() {
+ // TODO(erg): Now that writing is asynchronous, we can't really async write
+ // prefs at shutdown. See comment in CommitPendingWrite().
+}
+
+void FilesystemJsonPrefStore::FinalizeFileRead(
+ scoped_ptr<base::DictionaryValue> prefs,
+ bool schedule_write) {
+ DCHECK(CalledOnValidThread());
+
+ filtering_in_progress_ = false;
+
+ prefs_ = std::move(prefs);
+
+ initialized_ = true;
+
+ if (schedule_write)
+ ScheduleWrite(DEFAULT_PREF_WRITE_FLAGS);
+
+ if (error_delegate_ && read_error_ != PREF_READ_ERROR_NONE)
+ error_delegate_->OnError(read_error_);
+
+ FOR_EACH_OBSERVER(PrefStore::Observer, observers_,
+ OnInitializationCompleted(true));
+
+ return;
+}
+
+void FilesystemJsonPrefStore::ScheduleWrite(uint32_t flags) {
+ if (read_only_)
+ return;
+
+ if (flags & LOSSY_PREF_WRITE_FLAG)
+ pending_lossy_write_ = true;
+ else
+ PerformWrite();
+}
+
+void FilesystemJsonPrefStore::PerformWrite() {
+ if (!directory_) {
+ OpenFilesystem(
+ Bind(&FilesystemJsonPrefStore::OnTempFileWriteStart, AsWeakPtr()));
+ } else {
+ OnTempFileWriteStart();
+ }
+}
+
+void FilesystemJsonPrefStore::OpenFilesystem(base::Closure callback) {
+ filesystem::FileSystemClientPtr client;
+ binding_.Bind(GetProxy(&client));
+
+ filesystem_->OpenFileSystem(
+ "origin", GetProxy(&directory_), std::move(client),
+ base::Bind(&FilesystemJsonPrefStore::OnOpenFilesystem, AsWeakPtr(),
+ callback));
+}
+
+void FilesystemJsonPrefStore::OnOpenFilesystem(base::Closure callback,
+ FileError err) {
+ if (err != FileError::OK) {
+ // Do real error checking.
+ NOTIMPLEMENTED();
+ return;
+ }
+
+ callback.Run();
+}
+
+void FilesystemJsonPrefStore::OnTempFileWriteStart() {
+ // Open up a temporary file and truncate it.
+ directory_->OpenFile(
+ "tmp", GetProxy(&temporary_file_), kFlagWrite | kFlagCreate,
+ Bind(&FilesystemJsonPrefStore::OnTempFileOpened, AsWeakPtr()));
+}
+
+void FilesystemJsonPrefStore::OnTempFileOpened(FileError err) {
+ // TODO(erg): Error handling. The JsonPrefStore code assumes that writing the
+ // file can never fail.
+ CHECK_EQ(FileError::OK, err);
+
+ // Calculate what we want to write, and then write to the temporary file.
+ pending_lossy_write_ = false;
+
+ if (pref_filter_)
+ pref_filter_->FilterSerializeData(prefs_.get());
+
+ std::string output;
+ JSONStringValueSerializer serializer(&output);
+ serializer.set_pretty_print(false);
+ serializer.Serialize(*prefs_);
+
+ temporary_file_->Write(
+ mojo::Array<uint8_t>::From(output), 0, Whence::FROM_CURRENT,
+ Bind(&FilesystemJsonPrefStore::OnTempFileWrite, AsWeakPtr()));
+}
+
+void FilesystemJsonPrefStore::OnTempFileWrite(FileError err,
+ uint32_t num_bytes_written) {
+ // TODO(erg): Error handling. The JsonPrefStore code assumes that writing the
+ // file can never fail.
+ CHECK_EQ(FileError::OK, err);
+
+ // Now that we've written the file, close it.
+ temporary_file_->Close(
+ Bind(&FilesystemJsonPrefStore::OnTempFileClosed, AsWeakPtr()));
+}
+
+void FilesystemJsonPrefStore::OnTempFileClosed(FileError err) {
+ // TODO(erg): Error handling. The JsonPrefStore code assumes that writing the
+ // file can never fail.
+ CHECK_EQ(FileError::OK, err);
+
+ temporary_file_.reset();
+ directory_->Rename(
+ "tmp", path_,
+ Bind(&FilesystemJsonPrefStore::OnTempFileRenamed, AsWeakPtr()));
+}
+
+void FilesystemJsonPrefStore::OnTempFileRenamed(FileError err) {}
+
+void FilesystemJsonPrefStore::OnPreferencesReadStart() {
+ // TODO(erg): implement me.
+ directory_->OpenFile(
+ path_, GetProxy(&preferences_file_), kFlagRead | kFlagOpen,
+ Bind(&FilesystemJsonPrefStore::OnPreferencesFileOpened, AsWeakPtr()));
+}
+
+void FilesystemJsonPrefStore::OnPreferencesFileOpened(FileError err) {
+ // TODO(erg): Error handling.
+ if (err == FileError::OK) {
+ preferences_file_->ReadEntireFile(
+ Bind(&FilesystemJsonPrefStore::OnPreferencesFileRead, AsWeakPtr()));
+ } else {
+ OnPreferencesFileRead(err, mojo::Array<uint8_t>());
+ }
+}
+
+void FilesystemJsonPrefStore::OnPreferencesFileRead(
+ FileError err,
+ mojo::Array<uint8_t> contents) {
+ scoped_ptr<FilesystemJsonPrefStore::ReadResult> read_result(
+ new FilesystemJsonPrefStore::ReadResult);
+ // TODO(erg): Needs even better error handling.
+ switch (err) {
+ case FileError::IN_USE:
+ case FileError::ACCESS_DENIED: {
+ read_only_ = true;
+ break;
+ }
+ case FileError::NOT_FOUND: {
+ // If the file just doesn't exist, maybe this is the first run. Just
+ // don't pass a value.
+ read_result->error = PREF_READ_ERROR_NO_FILE;
+ break;
+ }
+ default: {
+ int error_code;
+ std::string error_msg;
+ JSONStringValueDeserializer deserializer(base::StringPiece(
+ reinterpret_cast<char*>(&contents.front()), contents.size()));
+ read_result->value = deserializer.Deserialize(&error_code, &error_msg);
+ read_result->error = HandleReadErrors(read_result->value.get());
+ }
+ }
+
+ preferences_file_.reset();
+
+ OnFileRead(std::move(read_result));
+}
+
+} // namespace filesystem
diff --git a/components/filesystem/public/cpp/prefs/filesystem_json_pref_store.h b/components/filesystem/public/cpp/prefs/filesystem_json_pref_store.h
new file mode 100644
index 0000000..685b361
--- /dev/null
+++ b/components/filesystem/public/cpp/prefs/filesystem_json_pref_store.h
@@ -0,0 +1,183 @@
+// Copyright 2016 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.
+
+#ifndef COMPONENTS_FILESYSTEM_PUBLIC_CPP_PREFS_FILESYSTEM_JSON_PREF_STORE_H_
+#define COMPONENTS_FILESYSTEM_PUBLIC_CPP_PREFS_FILESYSTEM_JSON_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <set>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/prefs/base_prefs_export.h"
+#include "base/prefs/persistent_pref_store.h"
+#include "base/prefs/pref_filter.h"
+#include "base/threading/non_thread_safe.h"
+#include "components/filesystem/public/interfaces/directory.mojom.h"
+#include "components/filesystem/public/interfaces/file.mojom.h"
+#include "components/filesystem/public/interfaces/file_system.mojom.h"
+#include "components/filesystem/public/interfaces/types.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+class PrefFilter;
+
+namespace base {
+class Clock;
+class DictionaryValue;
+class FilePath;
+class JsonPrefStoreLossyWriteTest;
+class Value;
+}
+
+namespace filesystem {
+
+// A forked, hack'n'slashed copy of base::JsonPrefStore which writes its
+// preference data to the mojo filesystem instead of the real
+// filesystem. Unlike base::JsonPrefStore, this class can safely be used inside
+// a sandboxed process.
+//
+// In the long run, we'll want to replace the current base::PrefService code
+// with something very different, especially since this component hard punts on
+// all the hard things that the preference service does (enterprise management,
+// parental controls, extension integration, etc.) and its interface is really
+// not optimal for a mojoified world--there are a few places where we assume
+// that writing to disk is synchronous...but it no longer is!
+//
+// Removing this class is a part of crbug.com/580652.
+class FilesystemJsonPrefStore
+ : public PersistentPrefStore,
+ public filesystem::FileSystemClient,
+ public base::SupportsWeakPtr<FilesystemJsonPrefStore>,
+ public base::NonThreadSafe {
+ public:
+ struct ReadResult;
+
+ FilesystemJsonPrefStore(const std::string& pref_filename,
+ filesystem::FileSystemPtr filesystem,
+ scoped_ptr<PrefFilter> pref_filter);
+
+ // PrefStore overrides:
+ bool GetValue(const std::string& key,
+ const base::Value** result) const override;
+ void AddObserver(PrefStore::Observer* observer) override;
+ void RemoveObserver(PrefStore::Observer* observer) override;
+ bool HasObservers() const override;
+ bool IsInitializationComplete() const override;
+
+ // PersistentPrefStore overrides:
+ bool GetMutableValue(const std::string& key, base::Value** result) override;
+ void SetValue(const std::string& key,
+ scoped_ptr<base::Value> value,
+ uint32_t flags) override;
+ void SetValueSilently(const std::string& key,
+ scoped_ptr<base::Value> value,
+ uint32_t flags) override;
+ void RemoveValue(const std::string& key, uint32_t flags) override;
+ bool ReadOnly() const override;
+ PrefReadError GetReadError() const override;
+ // Note this method may be asynchronous if this instance has a |pref_filter_|
+ // in which case it will return PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE.
+ // See details in pref_filter.h.
+ PrefReadError ReadPrefs() override;
+ void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
+ void CommitPendingWrite() override;
+ void SchedulePendingLossyWrites() override;
+ void ReportValueChanged(const std::string& key, uint32_t flags) override;
+
+ // FileSystemClient overrides:
+ void OnFileSystemShutdown() override;
+
+ // Just like RemoveValue(), but doesn't notify observers. Used when doing some
+ // cleanup that shouldn't otherwise alert observers.
+ void RemoveValueSilently(const std::string& key, uint32_t flags);
+
+ private:
+ friend class base::JsonPrefStoreLossyWriteTest;
+
+ ~FilesystemJsonPrefStore() override;
+
+ // This method is called after the JSON file has been read. It then hands
+ // |value| (or an empty dictionary in some read error cases) to the
+ // |pref_filter| if one is set. It also gives a callback pointing at
+ // FinalizeFileRead() to that |pref_filter_| which is then responsible for
+ // invoking it when done. If there is no |pref_filter_|, FinalizeFileRead()
+ // is invoked directly.
+ void OnFileRead(scoped_ptr<ReadResult> read_result);
+
+ // This method is called after the JSON file has been read and the result has
+ // potentially been intercepted and modified by |pref_filter_|.
+ // |schedule_write| indicates whether a write should be immediately scheduled
+ // (typically because the |pref_filter_| has already altered the |prefs|) --
+ // this will be ignored if this store is read-only.
+ void FinalizeFileRead(scoped_ptr<base::DictionaryValue> prefs,
+ bool schedule_write);
+
+ // Schedule a write with the file writer as long as |flags| doesn't contain
+ // WriteablePrefStore::LOSSY_PREF_WRITE_FLAG.
+ void ScheduleWrite(uint32_t flags);
+
+ // Actually performs a write. Unlike the //base version of this class, we
+ // don't use the ImportantFileWriter and instead write using the mojo
+ // filesystem API.
+ void PerformWrite();
+
+ // Opens the filesystem and calls |callback| when completed, whether
+ // successfully or unsuccessfully.
+ void OpenFilesystem(base::Closure callback);
+
+ // Callback method which verifies that there were no errors on opening the
+ // filesystem, and if there aren't, invokes the passed in callback.
+ void OnOpenFilesystem(base::Closure callback, FileError err);
+
+ // Asynchronous implementation details of PerformWrite().
+ void OnTempFileWriteStart();
+ void OnTempFileOpened(FileError err);
+ void OnTempFileWrite(FileError err, uint32_t num_bytes_written);
+ void OnTempFileClosed(FileError err);
+ void OnTempFileRenamed(FileError err);
+
+ // Asynchronous implementation details of ReadPrefsAsync().
+ void OnPreferencesReadStart();
+ void OnPreferencesFileOpened(FileError err);
+ void OnPreferencesFileRead(FileError err, mojo::Array<uint8_t> contents);
+
+ const std::string path_;
+ mojo::Binding<filesystem::FileSystemClient> binding_;
+ filesystem::FileSystemPtr filesystem_;
+
+ // |directory_| is only bound after the first attempt to access the
+ // |filesystem. See OpenFilesystem().
+ DirectoryPtr directory_;
+
+ FilePtr preferences_file_;
+ FilePtr temporary_file_;
+
+ scoped_ptr<base::DictionaryValue> prefs_;
+
+ bool read_only_;
+
+ scoped_ptr<PrefFilter> pref_filter_;
+ base::ObserverList<PrefStore::Observer, true> observers_;
+
+ scoped_ptr<ReadErrorDelegate> error_delegate_;
+
+ bool initialized_;
+ bool filtering_in_progress_;
+ bool pending_lossy_write_;
+ PrefReadError read_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(FilesystemJsonPrefStore);
+};
+
+} // namespace filesystem
+
+#endif // COMPONENTS_FILESYSTEM_PUBLIC_CPP_PREFS_FILESYSTEM_JSON_PREF_STORE_H_
diff --git a/components/filesystem/public/cpp/prefs/pref_service_factory.cc b/components/filesystem/public/cpp/prefs/pref_service_factory.cc
new file mode 100644
index 0000000..b2209e7
--- /dev/null
+++ b/components/filesystem/public/cpp/prefs/pref_service_factory.cc
@@ -0,0 +1,46 @@
+// Copyright 2016 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 "components/filesystem/public/cpp/prefs/pref_service_factory.h"
+
+#include "base/bind.h"
+#include "base/prefs/pref_notifier_impl.h"
+#include "base/prefs/pref_registry.h"
+#include "base/prefs/pref_service.h"
+#include "base/prefs/pref_value_store.h"
+#include "base/prefs/value_map_pref_store.h"
+#include "base/prefs/writeable_pref_store.h"
+#include "components/filesystem/public/cpp/prefs/filesystem_json_pref_store.h"
+#include "mojo/shell/public/cpp/application_impl.h"
+
+namespace filesystem {
+
+namespace {
+
+// Do-nothing default implementation.
+void DoNothingHandleReadError(PersistentPrefStore::PrefReadError error) {}
+
+} // namespace
+
+scoped_ptr<PrefService> CreatePrefService(mojo::ApplicationImpl* application,
+ PrefRegistry* pref_registry) {
+ filesystem::FileSystemPtr filesystem;
+ application->ConnectToService("mojo:filesystem", &filesystem);
+
+ scoped_refptr<FilesystemJsonPrefStore> user_prefs =
+ new FilesystemJsonPrefStore("preferences.json", std::move(filesystem),
+ nullptr /* TODO(erg): pref filter */);
+
+ PrefNotifierImpl* pref_notifier = new PrefNotifierImpl();
+ scoped_ptr<PrefService> pref_service(new PrefService(
+ pref_notifier,
+ new PrefValueStore(nullptr, nullptr, nullptr, nullptr, user_prefs.get(),
+ nullptr, pref_registry->defaults().get(),
+ pref_notifier),
+ user_prefs.get(), pref_registry, base::Bind(&DoNothingHandleReadError),
+ true /* async */));
+ return pref_service;
+}
+
+} // namespace filesystem
diff --git a/components/filesystem/public/cpp/prefs/pref_service_factory.h b/components/filesystem/public/cpp/prefs/pref_service_factory.h
new file mode 100644
index 0000000..ccf981e
--- /dev/null
+++ b/components/filesystem/public/cpp/prefs/pref_service_factory.h
@@ -0,0 +1,27 @@
+// Copyright 2016 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.
+
+#ifndef COMPONENTS_FILESYSTEM_PUBLIC_CPP_PREFS_PREF_SERVICE_FACTORY_H_
+#define COMPONENTS_FILESYSTEM_PUBLIC_CPP_PREFS_PREF_SERVICE_FACTORY_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/pref_service.h"
+
+namespace mojo {
+class ApplicationImpl;
+}
+
+class PrefRegistry;
+
+namespace filesystem {
+
+// This factory method creates a PrefService for the local process based on the
+// preference registry passed in. This PrefService will synchronize with a JSON
+// file in the mojo:filesystem.
+scoped_ptr<PrefService> CreatePrefService(mojo::ApplicationImpl* application,
+ PrefRegistry* registry);
+
+} // namespace filesystem
+
+#endif // COMPONENTS_FILESYSTEM_PUBLIC_CPP_PREFS_PREF_SERVICE_FACTORY_H_
diff --git a/components/filesystem/public/interfaces/file.mojom b/components/filesystem/public/interfaces/file.mojom
index d64c0f1..ec0c6fa 100644
--- a/components/filesystem/public/interfaces/file.mojom
+++ b/components/filesystem/public/interfaces/file.mojom
@@ -27,6 +27,10 @@
Read(uint32 num_bytes_to_read, int64 offset, Whence whence)
=> (FileError error, array<uint8>? bytes_read);
+ // Returns the entire contents of the file as an array of bytes. The mojo
+ // equivalent of base::ReadFileToString().
+ ReadEntireFile() => (FileError error, array<uint8>? bytes_read);
+
// Writes |bytes_to_write| to the location specified by |offset|/|whence|.
// TODO(vtl): Clarify behavior when |num_bytes_written| is less than the size
// of |bytes_to_write|.
diff --git a/mash/wallpaper/BUILD.gn b/mash/wallpaper/BUILD.gn
index f776d9fe..26cad0f0 100644
--- a/mash/wallpaper/BUILD.gn
+++ b/mash/wallpaper/BUILD.gn
@@ -13,6 +13,9 @@
deps = [
"//base",
+ "//base:prefs",
+ "//components/filesystem/public/cpp/prefs",
+ "//components/filesystem/public/interfaces",
"//components/mus/public/cpp",
"//components/mus/public/interfaces",
"//mash/wm/public/interfaces",
diff --git a/mash/wallpaper/DEPS b/mash/wallpaper/DEPS
new file mode 100644
index 0000000..96dcb7bd
--- /dev/null
+++ b/mash/wallpaper/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/filesystem/public/cpp/prefs",
+]
diff --git a/mash/wallpaper/wallpaper.cc b/mash/wallpaper/wallpaper.cc
index fa86f6db..311c4c7 100644
--- a/mash/wallpaper/wallpaper.cc
+++ b/mash/wallpaper/wallpaper.cc
@@ -3,6 +3,8 @@
// found in the LICENSE file.
#include "base/macros.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "components/filesystem/public/cpp/prefs/pref_service_factory.h"
#include "components/mus/public/cpp/property_type_converters.h"
#include "mash/wm/public/interfaces/container.mojom.h"
#include "mojo/public/c/system/main.h"
@@ -42,6 +44,10 @@
WallpaperApplicationDelegate() {}
~WallpaperApplicationDelegate() override {}
+ void OnLoaded(bool whatever) {
+ // TODO(erg): Now do something with this result.
+ }
+
private:
// mojo::ApplicationDelegate:
void Initialize(mojo::ApplicationImpl* app) override {
@@ -50,6 +56,12 @@
aura_init_.reset(new views::AuraInit(app, "views_mus_resources.pak"));
views::WindowManagerConnection::Create(app);
+ scoped_refptr<PrefRegistrySimple> registry = new PrefRegistrySimple;
+ registry->RegisterStringPref("filename", "", 0);
+ pref_service_ = filesystem::CreatePrefService(app, registry.get());
+ pref_service_->AddPrefInitObserver(base::Bind(
+ &WallpaperApplicationDelegate::OnLoaded, base::Unretained(this)));
+
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
@@ -69,6 +81,7 @@
mojo::TracingImpl tracing_;
scoped_ptr<views::AuraInit> aura_init_;
+ scoped_ptr<PrefService> pref_service_;
DISALLOW_COPY_AND_ASSIGN(WallpaperApplicationDelegate);
};