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:
@ -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
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_
|
174
base/directory_watcher_unittest.cc
Normal file
174
base/directory_watcher_unittest.cc
Normal file
@ -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);
|
||||
}
|
86
base/directory_watcher_win.cc
Normal file
86
base/directory_watcher_win.cc
Normal file
@ -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);
|
||||
}
|
Reference in New Issue
Block a user