0

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
This commit is contained in:
evanm@google.com
2008-10-17 02:45:56 +00:00
parent 9b6633c287
commit 9e8d4f92df
5 changed files with 322 additions and 8 deletions

@ -269,6 +269,14 @@
RelativePath="..\debug_util_win.cc"
>
</File>
<File
RelativePath="..\directory_watcher.h"
>
</File>
<File
RelativePath="..\directory_watcher_win.cc"
>
</File>
<File
RelativePath="..\event_recorder.cc"
>
@ -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
@ -757,6 +765,10 @@
RelativePath="..\thread.h"
>
</File>
<File
RelativePath="..\thread_local.h"
>
</File>
<File
RelativePath="..\thread_local_storage.h"
>
@ -765,10 +777,6 @@
RelativePath="..\thread_local_storage_win.cc"
>
</File>
<File
RelativePath="..\thread_local.h"
>
</File>
<File
RelativePath="..\thread_local_win.cc"
>

@ -183,6 +183,10 @@
RelativePath="..\condition_variable_unittest.cc"
>
</File>
<File
RelativePath="..\directory_watcher_unittest.cc"
>
</File>
<File
RelativePath="..\file_path_unittest.cc"
>
@ -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

42
base/directory_watcher.h Normal file

@ -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_

@ -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);
}

@ -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);
}