summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorevanm@google.com <evanm@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-10-17 02:45:56 +0000
committerevanm@google.com <evanm@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-10-17 02:45:56 +0000
commit9e8d4f92df67dd8d4c8d4097035aaf6279fe84ba (patch)
treeed08cb639f07c35da5eabc10f4519cb88c10ae64
parent9b6633c2877f08381f84f60a33a5eeb9bfa0f3e0 (diff)
downloadchromium_src-9e8d4f92df67dd8d4c8d4097035aaf6279fe84ba.zip
chromium_src-9e8d4f92df67dd8d4c8d4097035aaf6279fe84ba.tar.gz
chromium_src-9e8d4f92df67dd8d4c8d4097035aaf6279fe84ba.tar.bz2
Add a DirectoryWatcher class, allowing objects to be notified whenever
a directory's contents change. Review URL: http://codereview.chromium.org/6377 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3504 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/build/base.vcproj18
-rw-r--r--base/build/base_unittests.vcproj8
-rw-r--r--base/directory_watcher.h42
-rw-r--r--base/directory_watcher_unittest.cc174
-rw-r--r--base/directory_watcher_win.cc86
5 files changed, 321 insertions, 7 deletions
diff --git a/base/build/base.vcproj b/base/build/base.vcproj
index 5db1e7a..76a08e9 100644
--- a/base/build/base.vcproj
+++ b/base/build/base.vcproj
@@ -270,6 +270,14 @@
>
</File>
<File
+ RelativePath="..\directory_watcher.h"
+ >
+ </File>
+ <File
+ RelativePath="..\directory_watcher_win.cc"
+ >
+ </File>
+ <File
RelativePath="..\event_recorder.cc"
>
</File>
@@ -646,11 +654,11 @@
>
</File>
<File
- RelativePath="..\simple_thread.h"
+ RelativePath="..\simple_thread.cc"
>
</File>
<File
- RelativePath="..\simple_thread.cc"
+ RelativePath="..\simple_thread.h"
>
</File>
<File
@@ -758,15 +766,15 @@
>
</File>
<File
- RelativePath="..\thread_local_storage.h"
+ RelativePath="..\thread_local.h"
>
</File>
<File
- RelativePath="..\thread_local_storage_win.cc"
+ RelativePath="..\thread_local_storage.h"
>
</File>
<File
- RelativePath="..\thread_local.h"
+ RelativePath="..\thread_local_storage_win.cc"
>
</File>
<File
diff --git a/base/build/base_unittests.vcproj b/base/build/base_unittests.vcproj
index 0f37520..7df06c9 100644
--- a/base/build/base_unittests.vcproj
+++ b/base/build/base_unittests.vcproj
@@ -184,6 +184,10 @@
>
</File>
<File
+ RelativePath="..\directory_watcher_unittest.cc"
+ >
+ </File>
+ <File
RelativePath="..\file_path_unittest.cc"
>
</File>
@@ -256,11 +260,11 @@
>
</File>
<File
- RelativePath="..\gfx\rect_unittest.cc"
+ RelativePath="..\rand_util_unittest.cc"
>
</File>
<File
- RelativePath="..\rand_util_unittest.cc"
+ RelativePath="..\gfx\rect_unittest.cc"
>
</File>
<File
diff --git a/base/directory_watcher.h b/base/directory_watcher.h
new file mode 100644
index 0000000..3048d66
--- /dev/null
+++ b/base/directory_watcher.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2006-2008 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.
+
+// This module provides a way to monitor a directory for changes.
+
+#ifndef BASE_DIRECTORY_WATCHER_H_
+#define BASE_DIRECTORY_WATCHER_H_
+
+#include <string>
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+
+class FilePath;
+
+// This class lets you register interest in changes on a directory.
+// The delegate will get called whenever a file is added or changed in the
+// directory.
+class DirectoryWatcher {
+ public:
+ class Delegate {
+ public:
+ virtual void OnDirectoryChanged(const FilePath& path) = 0;
+ };
+
+ DirectoryWatcher();
+ ~DirectoryWatcher();
+
+ // Register interest in any changes in the directory |path|.
+ // OnDirectoryChanged will be called back for each change within the dir.
+ // Returns false on error.
+ bool Watch(const FilePath& path, Delegate* delegate);
+
+ private:
+ class Impl;
+ friend class Impl;
+ scoped_refptr<Impl> impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectoryWatcher);
+};
+
+#endif // BASE_DIRECTORY_WATCHER_H_
diff --git a/base/directory_watcher_unittest.cc b/base/directory_watcher_unittest.cc
new file mode 100644
index 0000000..5121287
--- /dev/null
+++ b/base/directory_watcher_unittest.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2008 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/directory_watcher.h"
+
+#include <fstream>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// For tests where we wait a bit to verify nothing happened
+namespace {
+const int kWaitForEventTime = 500;
+}
+
+class DirectoryWatcherTest : public testing::Test,
+ public DirectoryWatcher::Delegate {
+ protected:
+ virtual void SetUp() {
+ // Name a subdirectory of the temp directory.
+ std::wstring path_str;
+ ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &path_str));
+ test_dir_ = FilePath(path_str).Append(
+ FILE_PATH_LITERAL("DirectoryWatcherTest"));
+
+ // Create a fresh, empty copy of this directory.
+ file_util::Delete(test_dir_.value(), true);
+ file_util::CreateDirectory(test_dir_.value());
+
+ directory_mods_ = 0;
+ quit_mod_count_ = 0;
+ }
+
+ virtual void OnDirectoryChanged(const FilePath& path) {
+ ++directory_mods_;
+ if (directory_mods_ == quit_mod_count_)
+ MessageLoop::current()->Quit();
+ }
+
+ virtual void TearDown() {
+ // Clean up test directory.
+ ASSERT_TRUE(file_util::Delete(test_dir_.value(), true));
+ ASSERT_FALSE(file_util::PathExists(test_dir_.value()));
+ }
+
+ // Write |content| to a file under the test directory.
+ void WriteTestDirFile(const FilePath::StringType& filename,
+ const std::string& content) {
+ FilePath path = test_dir_.Append(filename);
+
+ std::ofstream file;
+ file.open(WideToUTF8(path.value()).c_str());
+ file << content;
+ file.close();
+ }
+
+ // Run the message loop until we've seen |n| directory modifications.
+ void LoopUntilModsEqual(int n) {
+ quit_mod_count_ = n;
+ loop_.Run();
+ }
+
+ MessageLoop loop_;
+
+ // The path to a temporary directory used for testing.
+ FilePath test_dir_;
+
+ // The number of times a directory modification has been observed.
+ int directory_mods_;
+
+ // The number of directory mods which, when reached, cause us to quit
+ // our message loop.
+ int quit_mod_count_;
+};
+
+// Basic test: add a file and verify we notice it.
+TEST_F(DirectoryWatcherTest, NewFile) {
+ DirectoryWatcher watcher;
+ ASSERT_TRUE(watcher.Watch(test_dir_, this));
+
+ WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
+ LoopUntilModsEqual(2);
+}
+
+// Verify that modifying a file is caught.
+TEST_F(DirectoryWatcherTest, ModifiedFile) {
+ DirectoryWatcher watcher;
+ ASSERT_TRUE(watcher.Watch(test_dir_, this));
+
+ // Write a file to the test dir.
+ WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
+ LoopUntilModsEqual(2);
+
+ // Now make sure we get notified if the file is modified.
+ WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some new content");
+ LoopUntilModsEqual(3);
+}
+
+// Verify that letting the watcher go out of scope stops notifications.
+TEST_F(DirectoryWatcherTest, Unregister) {
+ {
+ DirectoryWatcher watcher;
+ ASSERT_TRUE(watcher.Watch(test_dir_, this));
+
+ // And then let it fall out of scope, clearing its watch.
+ }
+
+ // Write a file to the test dir.
+ WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
+
+ // We won't get a notification, so we just wait around a bit to verify
+ // that notification doesn't come.
+ loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
+ kWaitForEventTime);
+ loop_.Run();
+
+ ASSERT_EQ(directory_mods_, 0);
+}
+
+// Verify that modifications to a subdirectory isn't noticed.
+TEST_F(DirectoryWatcherTest, SubDir) {
+ FilePath subdir = test_dir_.Append(FILE_PATH_LITERAL("SubDir"));
+ ASSERT_TRUE(file_util::CreateDirectory(subdir.value()));
+
+ DirectoryWatcher watcher;
+ ASSERT_TRUE(watcher.Watch(test_dir_, this));
+ // Write a file to the subdir.
+ FilePath test_path = subdir.Append(FILE_PATH_LITERAL("test_file"));
+ WriteTestDirFile(test_path.value(), "some content");
+
+ // We won't get a notification, so we just wait around a bit to verify
+ // that notification doesn't come.
+ loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
+ kWaitForEventTime);
+ loop_.Run();
+
+ // We shouldn't have been notified and shouldn't have crashed.
+ ASSERT_EQ(directory_mods_, 0);
+}
+
+namespace {
+// Used by the DeleteDuringNotify test below.
+// Deletes the DirectoryWatcher when it's notified.
+class Deleter : public DirectoryWatcher::Delegate {
+ public:
+ Deleter(DirectoryWatcher* watcher) : watcher_(watcher) {}
+ virtual void OnDirectoryChanged(const FilePath& path) {
+ watcher_.reset(NULL);
+ MessageLoop::current()->Quit();
+ }
+
+ scoped_ptr<DirectoryWatcher> watcher_;
+};
+} // anonymous namespace
+
+// Verify that deleting a watcher during the callback
+TEST_F(DirectoryWatcherTest, DeleteDuringNotify) {
+ DirectoryWatcher* watcher = new DirectoryWatcher;
+ Deleter deleter(watcher); // Takes ownership of watcher.
+ ASSERT_TRUE(watcher->Watch(test_dir_, &deleter));
+
+ WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
+ LoopUntilModsEqual(2);
+
+ // We win if we haven't crashed yet.
+ // Might as well double-check it got deleted, too.
+ ASSERT_TRUE(deleter.watcher_.get() == NULL);
+}
diff --git a/base/directory_watcher_win.cc b/base/directory_watcher_win.cc
new file mode 100644
index 0000000..cdaec39
--- /dev/null
+++ b/base/directory_watcher_win.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2006-2008 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/directory_watcher.h"
+
+#include "base/file_path.h"
+#include "base/object_watcher.h"
+
+// Private implementation class implementing the behavior of DirectoryWatcher.
+class DirectoryWatcher::Impl : public base::RefCounted<DirectoryWatcher::Impl>,
+ public base::ObjectWatcher::Delegate {
+ public:
+ Impl(DirectoryWatcher::Delegate* delegate)
+ : delegate_(delegate), handle_(INVALID_HANDLE_VALUE) {}
+ ~Impl();
+
+ // Register interest in any changes in |path|.
+ // Returns false on error.
+ bool Watch(const FilePath& path);
+
+ // Callback from MessageLoopForIO.
+ virtual void OnObjectSignaled(HANDLE object);
+
+ private:
+ // Delegate to notify upon changes.
+ DirectoryWatcher::Delegate* delegate_;
+ // Path we're watching (passed to delegate).
+ FilePath path_;
+ // Handle for FindFirstChangeNotification.
+ HANDLE handle_;
+ // ObjectWatcher to watch handle_ for events.
+ base::ObjectWatcher watcher_;
+};
+
+DirectoryWatcher::Impl::~Impl() {
+ if (handle_ != INVALID_HANDLE_VALUE) {
+ watcher_.StopWatching();
+ FindCloseChangeNotification(handle_);
+ }
+}
+
+bool DirectoryWatcher::Impl::Watch(const FilePath& path) {
+ DCHECK(path_.value().empty()); // Can only watch one path.
+
+ handle_ = FindFirstChangeNotification(
+ path.value().c_str(),
+ FALSE, // Don't watch subtree.
+ FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
+ FILE_NOTIFY_CHANGE_LAST_WRITE);
+ if (handle_ == INVALID_HANDLE_VALUE) {
+ NOTREACHED();
+ return false;
+ }
+
+ path_ = path;
+ watcher_.StartWatching(handle_, this);
+
+ return true;
+}
+
+void DirectoryWatcher::Impl::OnObjectSignaled(HANDLE object) {
+ DCHECK(object == handle_);
+ // Make sure we stay alive through the body of this function.
+ scoped_refptr<DirectoryWatcher::Impl> keep_alive(this);
+
+ delegate_->OnDirectoryChanged(path_);
+
+ // Register for more notifications on file change.
+ BOOL ok = FindNextChangeNotification(object);
+ DCHECK(ok);
+ watcher_.StartWatching(object, this);
+}
+
+DirectoryWatcher::DirectoryWatcher() {
+}
+
+DirectoryWatcher::~DirectoryWatcher() {
+ // Declared in .cc file for access to ~DirectoryWatcher::Impl.
+}
+
+bool DirectoryWatcher::Watch(const FilePath& path,
+ Delegate* delegate) {
+ impl_ = new DirectoryWatcher::Impl(delegate);
+ return impl_->Watch(path);
+}