diff options
author | evanm@google.com <evanm@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-17 02:45:56 +0000 |
---|---|---|
committer | evanm@google.com <evanm@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-17 02:45:56 +0000 |
commit | 9e8d4f92df67dd8d4c8d4097035aaf6279fe84ba (patch) | |
tree | ed08cb639f07c35da5eabc10f4519cb88c10ae64 /base | |
parent | 9b6633c2877f08381f84f60a33a5eeb9bfa0f3e0 (diff) | |
download | chromium_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
Diffstat (limited to 'base')
-rw-r--r-- | base/build/base.vcproj | 18 | ||||
-rw-r--r-- | base/build/base_unittests.vcproj | 8 | ||||
-rw-r--r-- | base/directory_watcher.h | 42 | ||||
-rw-r--r-- | base/directory_watcher_unittest.cc | 174 | ||||
-rw-r--r-- | base/directory_watcher_win.cc | 86 |
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); +} |