summaryrefslogtreecommitdiffstats
path: root/chrome/common
diff options
context:
space:
mode:
authorsanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-20 18:20:29 +0000
committersanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-20 18:20:29 +0000
commit6658ca866d0e28950179a65179c1a4d6e8e3f8df (patch)
tree5e7c4767096a10bc71aa0bd4bf2228f1476a564e /chrome/common
parente45b40f5bfe4f20aece5a8fa25d9a4c6c89f22e1 (diff)
downloadchromium_src-6658ca866d0e28950179a65179c1a4d6e8e3f8df.zip
chromium_src-6658ca866d0e28950179a65179c1a4d6e8e3f8df.tar.gz
chromium_src-6658ca866d0e28950179a65179c1a4d6e8e3f8df.tar.bz2
Moved ImportantFileWriter to chrome/common in preparation of moving JsonPrefStore to chrome/common.
BUG=None. TEST=Unit-tests moved and modified. Review URL: http://codereview.chromium.org/2128014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47818 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r--chrome/common/important_file_writer.cc150
-rw-r--r--chrome/common/important_file_writer.h112
-rw-r--r--chrome/common/important_file_writer_unittest.cc115
3 files changed, 377 insertions, 0 deletions
diff --git a/chrome/common/important_file_writer.cc b/chrome/common/important_file_writer.cc
new file mode 100644
index 0000000..3466958
--- /dev/null
+++ b/chrome/common/important_file_writer.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2010 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/common/important_file_writer.h"
+
+#include <stdio.h>
+
+#include <ostream>
+#include <string>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/message_loop_proxy.h"
+#include "base/string_util.h"
+#include "base/task.h"
+#include "base/thread.h"
+#include "base/time.h"
+
+using base::TimeDelta;
+
+namespace {
+
+const int kDefaultCommitIntervalMs = 10000;
+
+class WriteToDiskTask : public Task {
+ public:
+ WriteToDiskTask(const FilePath& path, const std::string& data)
+ : path_(path),
+ data_(data) {
+ }
+
+ virtual void Run() {
+ // Write the data to a temp file then rename to avoid data loss if we crash
+ // while writing the file. Ensure that the temp file is on the same volume
+ // as target file, so it can be moved in one step, and that the temp file
+ // is securely created.
+ FilePath tmp_file_path;
+ FILE* tmp_file = file_util::CreateAndOpenTemporaryFileInDir(
+ path_.DirName(), &tmp_file_path);
+ if (!tmp_file) {
+ LogFailure("could not create temporary file");
+ return;
+ }
+
+ size_t bytes_written = fwrite(data_.data(), 1, data_.length(), tmp_file);
+ if (!file_util::CloseFile(tmp_file)) {
+ file_util::Delete(tmp_file_path, false);
+ LogFailure("failed to close temporary file");
+ return;
+ }
+ if (bytes_written < data_.length()) {
+ file_util::Delete(tmp_file_path, false);
+ LogFailure("error writing, bytes_written=" + UintToString(
+ static_cast<unsigned int>(bytes_written)));
+ return;
+ }
+
+ if (file_util::ReplaceFile(tmp_file_path, path_)) {
+ LogSuccess();
+ return;
+ }
+
+ file_util::Delete(tmp_file_path, false);
+ LogFailure("could not rename temporary file");
+ }
+
+ private:
+ void LogSuccess() {
+ LOG(INFO) << "successfully saved " << path_.value();
+ }
+
+ void LogFailure(const std::string& message) {
+ LOG(WARNING) << "failed to write " << path_.value()
+ << ": " << message;
+ }
+
+ const FilePath path_;
+ const std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(WriteToDiskTask);
+};
+
+} // namespace
+
+ImportantFileWriter::ImportantFileWriter(
+ const FilePath& path, base::MessageLoopProxy* file_message_loop_proxy)
+ : path_(path),
+ file_message_loop_proxy_(file_message_loop_proxy),
+ serializer_(NULL),
+ commit_interval_(TimeDelta::FromMilliseconds(
+ kDefaultCommitIntervalMs)) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(file_message_loop_proxy_.get());
+}
+
+ImportantFileWriter::~ImportantFileWriter() {
+ // We're usually a member variable of some other object, which also tends
+ // to be our serializer. It may not be safe to call back to the parent object
+ // being destructed.
+ DCHECK(!HasPendingWrite());
+}
+
+bool ImportantFileWriter::HasPendingWrite() const {
+ DCHECK(CalledOnValidThread());
+ return timer_.IsRunning();
+}
+
+void ImportantFileWriter::WriteNow(const std::string& data) {
+ DCHECK(CalledOnValidThread());
+
+ if (HasPendingWrite())
+ timer_.Stop();
+
+ // TODO(sanjeevr): Add a DCHECK for the return value of PostTask.
+ // (Some tests fail if we add the DCHECK and they need to be fixed first).
+ file_message_loop_proxy_->PostTask(FROM_HERE,
+ new WriteToDiskTask(path_, data));
+}
+
+void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) {
+ DCHECK(CalledOnValidThread());
+
+ DCHECK(serializer);
+ serializer_ = serializer;
+
+ if (!MessageLoop::current()) {
+ // Happens in unit tests.
+ DoScheduledWrite();
+ return;
+ }
+
+ if (!timer_.IsRunning()) {
+ timer_.Start(commit_interval_, this,
+ &ImportantFileWriter::DoScheduledWrite);
+ }
+}
+
+void ImportantFileWriter::DoScheduledWrite() {
+ DCHECK(serializer_);
+ std::string data;
+ if (serializer_->SerializeData(&data)) {
+ WriteNow(data);
+ } else {
+ LOG(WARNING) << "failed to serialize data to be saved in "
+ << path_.value();
+ }
+ serializer_ = NULL;
+}
diff --git a/chrome/common/important_file_writer.h b/chrome/common/important_file_writer.h
new file mode 100644
index 0000000..d4f57a0
--- /dev/null
+++ b/chrome/common/important_file_writer.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2009 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 CHROME_COMMON_IMPORTANT_FILE_WRITER_H_
+#define CHROME_COMMON_IMPORTANT_FILE_WRITER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/non_thread_safe.h"
+#include "base/ref_counted.h"
+#include "base/time.h"
+#include "base/timer.h"
+
+namespace base {
+class MessageLoopProxy;
+class Thread;
+}
+
+// Helper to ensure that a file won't be corrupted by the write (for example on
+// application crash). Consider a naive way to save an important file F:
+//
+// 1. Open F for writing, truncating it.
+// 2. Write new data to F.
+//
+// It's good when it works, but it gets very bad if step 2. doesn't complete.
+// It can be caused by a crash, a computer hang, or a weird I/O error. And you
+// end up with a broken file.
+//
+// To be safe, we don't start with writing directly to F. Instead, we write to
+// to a temporary file. Only after that write is successful, we rename the
+// temporary file to target filename.
+//
+// If you want to know more about this approach and ext3/ext4 fsync issues, see
+// http://valhenson.livejournal.com/37921.html
+class ImportantFileWriter : public NonThreadSafe {
+ public:
+ // Used by ScheduleSave to lazily provide the data to be saved. Allows us
+ // to also batch data serializations.
+ class DataSerializer {
+ public:
+ virtual ~DataSerializer() {}
+
+ // Should put serialized string in |data| and return true on successful
+ // serialization. Will be called on the same thread on which
+ // ImportantFileWriter has been created.
+ virtual bool SerializeData(std::string* data) = 0;
+ };
+
+ // Initialize the writer.
+ // |path| is the name of file to write.
+ // |file_message_loop_proxy| is the MessageLoopProxy for a thread on which
+ // file I/O can be done.
+ // All non-const methods, ctor and dtor must be called on the same thread.
+ ImportantFileWriter(const FilePath& path,
+ base::MessageLoopProxy* file_message_loop_proxy);
+
+ // You have to ensure that there are no pending writes at the moment
+ // of destruction.
+ ~ImportantFileWriter();
+
+ FilePath path() const { return path_; }
+
+ // Returns true if there is a scheduled write pending which has not yet
+ // been started.
+ bool HasPendingWrite() const;
+
+ // Save |data| to target filename. Does not block. If there is a pending write
+ // scheduled by ScheduleWrite, it is cancelled.
+ void WriteNow(const std::string& data);
+
+ // Schedule a save to target filename. Data will be serialized and saved
+ // to disk after the commit interval. If another ScheduleWrite is issued
+ // before that, only one serialization and write to disk will happen, and
+ // the most recent |serializer| will be used. This operation does not block.
+ // |serializer| should remain valid through the lifetime of
+ // ImportantFileWriter.
+ void ScheduleWrite(DataSerializer* serializer);
+
+ // Serialize data pending to be saved and execute write on backend thread.
+ void DoScheduledWrite();
+
+ base::TimeDelta commit_interval() const {
+ return commit_interval_;
+ }
+
+ void set_commit_interval(const base::TimeDelta& interval) {
+ commit_interval_ = interval;
+ }
+
+ private:
+ // Path being written to.
+ const FilePath path_;
+
+ // MessageLoopProxy for the thread on which file I/O can be done.
+ scoped_refptr<base::MessageLoopProxy> file_message_loop_proxy_;
+
+ // Timer used to schedule commit after ScheduleWrite.
+ base::OneShotTimer<ImportantFileWriter> timer_;
+
+ // Serializer which will provide the data to be saved.
+ DataSerializer* serializer_;
+
+ // Time delta after which scheduled data will be written to disk.
+ base::TimeDelta commit_interval_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImportantFileWriter);
+};
+
+#endif // CHROME_COMMON_IMPORTANT_FILE_WRITER_H_
diff --git a/chrome/common/important_file_writer_unittest.cc b/chrome/common/important_file_writer_unittest.cc
new file mode 100644
index 0000000..3d44ae0
--- /dev/null
+++ b/chrome/common/important_file_writer_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2009 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/common/important_file_writer.h"
+
+#include "base/compiler_specific.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/scoped_temp_dir.h"
+#include "base/thread.h"
+#include "base/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+std::string GetFileContent(const FilePath& path) {
+ std::string content;
+ if (!file_util::ReadFileToString(path, &content)) {
+ NOTREACHED();
+ }
+ return content;
+}
+
+class DataSerializer : public ImportantFileWriter::DataSerializer {
+ public:
+ explicit DataSerializer(const std::string& data) : data_(data) {
+ }
+
+ virtual bool SerializeData(std::string* output) {
+ output->assign(data_);
+ return true;
+ }
+
+ private:
+ const std::string data_;
+};
+
+} // namespace
+
+class ImportantFileWriterTest : public testing::Test {
+ public:
+ ImportantFileWriterTest() { }
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ file_ = temp_dir_.path().AppendASCII("test-file");
+ }
+
+ protected:
+ FilePath file_;
+ MessageLoop loop_;
+
+ private:
+ ScopedTempDir temp_dir_;
+};
+
+TEST_F(ImportantFileWriterTest, Basic) {
+ ImportantFileWriter writer(file_,
+ base::MessageLoopProxy::CreateForCurrentThread());
+ EXPECT_FALSE(file_util::PathExists(writer.path()));
+ writer.WriteNow("foo");
+ loop_.RunAllPending();
+
+ ASSERT_TRUE(file_util::PathExists(writer.path()));
+ EXPECT_EQ("foo", GetFileContent(writer.path()));
+}
+
+TEST_F(ImportantFileWriterTest, ScheduleWrite) {
+ ImportantFileWriter writer(file_,
+ base::MessageLoopProxy::CreateForCurrentThread());
+ writer.set_commit_interval(base::TimeDelta::FromMilliseconds(25));
+ EXPECT_FALSE(writer.HasPendingWrite());
+ DataSerializer serializer("foo");
+ writer.ScheduleWrite(&serializer);
+ EXPECT_TRUE(writer.HasPendingWrite());
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ new MessageLoop::QuitTask(), 100);
+ MessageLoop::current()->Run();
+ EXPECT_FALSE(writer.HasPendingWrite());
+ ASSERT_TRUE(file_util::PathExists(writer.path()));
+ EXPECT_EQ("foo", GetFileContent(writer.path()));
+}
+
+TEST_F(ImportantFileWriterTest, DoScheduledWrite) {
+ ImportantFileWriter writer(file_,
+ base::MessageLoopProxy::CreateForCurrentThread());
+ EXPECT_FALSE(writer.HasPendingWrite());
+ DataSerializer serializer("foo");
+ writer.ScheduleWrite(&serializer);
+ EXPECT_TRUE(writer.HasPendingWrite());
+ writer.DoScheduledWrite();
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ new MessageLoop::QuitTask(), 100);
+ MessageLoop::current()->Run();
+ EXPECT_FALSE(writer.HasPendingWrite());
+ ASSERT_TRUE(file_util::PathExists(writer.path()));
+ EXPECT_EQ("foo", GetFileContent(writer.path()));
+}
+
+TEST_F(ImportantFileWriterTest, BatchingWrites) {
+ ImportantFileWriter writer(file_,
+ base::MessageLoopProxy::CreateForCurrentThread());
+ writer.set_commit_interval(base::TimeDelta::FromMilliseconds(25));
+ DataSerializer foo("foo"), bar("bar"), baz("baz");
+ writer.ScheduleWrite(&foo);
+ writer.ScheduleWrite(&bar);
+ writer.ScheduleWrite(&baz);
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ new MessageLoop::QuitTask(), 100);
+ MessageLoop::current()->Run();
+ ASSERT_TRUE(file_util::PathExists(writer.path()));
+ EXPECT_EQ("baz", GetFileContent(writer.path()));
+}