summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
Diffstat (limited to 'base')
-rw-r--r--base/base.gyp2
-rw-r--r--base/base.scons1
-rw-r--r--base/base_unittests.scons1
-rw-r--r--base/directory_watcher.h5
-rw-r--r--base/directory_watcher_inotify.cc329
-rw-r--r--base/directory_watcher_unittest.cc195
-rw-r--r--base/directory_watcher_win.cc8
7 files changed, 492 insertions, 49 deletions
diff --git a/base/base.gyp b/base/base.gyp
index 4017756..70efc3d 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -79,6 +79,7 @@
'debug_util_posix.cc',
'debug_util_win.cc',
'directory_watcher.h',
+ 'directory_watcher_inotify.cc',
'directory_watcher_win.cc',
'event_recorder.cc',
'event_recorder.h',
@@ -321,6 +322,7 @@
{ # else: OS != "linux"
'sources!': [
'atomicops_internals_x86_gcc.cc',
+ 'directory_watcher_inotify.cc',
'hmac_nss.cc',
'idle_timer_none.cc',
'message_pump_glib.cc',
diff --git a/base/base.scons b/base/base.scons
index 485dd13..f3a7f9b 100644
--- a/base/base.scons
+++ b/base/base.scons
@@ -390,6 +390,7 @@ if env.Bit('linux'):
'base_paths_linux.cc',
'clipboard_linux.cc',
'data_pack.cc',
+ 'directory_watcher_inotify.cc',
'event_recorder_stubs.cc',
'file_util_linux.cc',
'file_version_info_linux.cc',
diff --git a/base/base_unittests.scons b/base/base_unittests.scons
index b99e671..08709f1 100644
--- a/base/base_unittests.scons
+++ b/base/base_unittests.scons
@@ -155,7 +155,6 @@ if env.Bit('mac'):
if not env.Bit('windows'):
# Remove Windows-specific tests.
input_files.Remove(
- 'directory_watcher_unittest.cc',
'file_version_info_unittest.cc',
'object_watcher_unittest.cc',
'pe_image_unittest.cc',
diff --git a/base/directory_watcher.h b/base/directory_watcher.h
index 3048d66..d6fc534 100644
--- a/base/directory_watcher.h
+++ b/base/directory_watcher.h
@@ -28,8 +28,9 @@ class 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);
+ // If |recursive| is true, the delegate will be notified for each change
+ // within the directory tree starting at |path|. Returns false on error.
+ bool Watch(const FilePath& path, Delegate* delegate, bool recursive);
private:
class Impl;
diff --git a/base/directory_watcher_inotify.cc b/base/directory_watcher_inotify.cc
new file mode 100644
index 0000000..7e906fa
--- /dev/null
+++ b/base/directory_watcher_inotify.cc
@@ -0,0 +1,329 @@
+// 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 "base/directory_watcher.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/inotify.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/hash_tables.h"
+#include "base/lock.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+#include "base/task.h"
+#include "base/thread.h"
+
+namespace {
+
+// Singleton to manage all inotify watches.
+class InotifyReader {
+ public:
+ typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch.
+ static const Watch kInvalidWatch = -1;
+
+ // Watch |path| for changes. |delegate| will be notified on each change. Does
+ // not check for duplicates. If you call it n times with same |path|
+ // and |delegate|, it will receive n notifications for each change
+ // in |path|. It makes implementation of DirectoryWatcher simple.
+ // Returns kInvalidWatch on failure.
+ Watch AddWatch(const FilePath& path, DirectoryWatcher::Delegate* delegate);
+
+ // Remove |watch| for |delegate|. If you had n watches for same |delegate|
+ // and path, after calling this function you will have n - 1.
+ // Returns true on success.
+ bool RemoveWatch(Watch watch, DirectoryWatcher::Delegate* delegate);
+
+ // Callback for InotifyReaderTask.
+ void OnInotifyEvent(inotify_event* event);
+
+ private:
+ friend struct DefaultSingletonTraits<InotifyReader>;
+
+ typedef std::pair<DirectoryWatcher::Delegate*, MessageLoop*> DelegateInfo;
+ typedef std::multiset<DelegateInfo> DelegateSet;
+
+ InotifyReader();
+ ~InotifyReader();
+
+ // We keep track of which delegates want to be notified on which watches.
+ // Multiset is used because there may be many DirectoryWatchers for same path
+ // and delegate.
+ base::hash_map<Watch, DelegateSet> delegates_;
+
+ // For each watch we also want to know the path it's watching.
+ base::hash_map<Watch, FilePath> paths_;
+
+ // Lock to protect delegates_ and paths_.
+ Lock lock_;
+
+ // Separate thread on which we run blocking read for inotify events.
+ base::Thread thread_;
+
+ // File descriptor returned by inotify_init.
+ const int inotify_fd_;
+
+ // Use self-pipe trick to unblock select during shutdown.
+ int shutdown_pipe_[2];
+
+ // Flag set to true when startup was successful.
+ bool valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(InotifyReader);
+};
+
+class InotifyReaderTask : public Task {
+ public:
+ InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
+ : reader_(reader),
+ inotify_fd_(inotify_fd),
+ shutdown_fd_(shutdown_fd) {
+ }
+
+ virtual void Run() {
+ while (true) {
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(inotify_fd_, &rfds);
+ FD_SET(shutdown_fd_, &rfds);
+
+ // Wait until some inotify events are available.
+ int select_result = select(std::max(inotify_fd_, shutdown_fd_) + 1,
+ &rfds, NULL, NULL, NULL);
+ if (select_result < 0) {
+ if (errno == EINTR)
+ continue;
+ DLOG(WARNING) << "select failed: " << strerror(errno);
+ return;
+ }
+
+ if (FD_ISSET(shutdown_fd_, &rfds))
+ return;
+
+ // Adjust buffer size to current event queue size.
+ int buffer_size;
+ int ioctl_result = ioctl(inotify_fd_, FIONREAD, &buffer_size);
+
+ if (ioctl_result != 0) {
+ DLOG(WARNING) << "ioctl failed: " << strerror(errno);
+ return;
+ }
+
+ std::vector<char> buffer(buffer_size);
+
+ ssize_t bytes_read;
+ do {
+ bytes_read = read(inotify_fd_, &buffer[0], buffer_size);
+ } while (bytes_read < 0 && errno == EINTR);
+
+ if (bytes_read < 0) {
+ DLOG(WARNING) << "read from inotify fd failed: " << strerror(errno);
+ return;
+ }
+
+ ssize_t i = 0;
+ while (i < bytes_read) {
+ inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]);
+ size_t event_size = sizeof(inotify_event) + event->len;
+ DCHECK(i + event_size <= static_cast<size_t>(bytes_read));
+ reader_->OnInotifyEvent(event);
+ i += event_size;
+ }
+ }
+ }
+
+ private:
+ InotifyReader* reader_;
+ int inotify_fd_;
+ int shutdown_fd_;
+
+ DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
+};
+
+class InotifyReaderNotifyTask : public Task {
+ public:
+ InotifyReaderNotifyTask(DirectoryWatcher::Delegate* delegate,
+ const FilePath& path)
+ : delegate_(delegate),
+ path_(path) {
+ }
+
+ virtual void Run() {
+ delegate_->OnDirectoryChanged(path_);
+ }
+
+ private:
+ DirectoryWatcher::Delegate* delegate_;
+ FilePath path_;
+};
+
+InotifyReader::InotifyReader()
+ : thread_("inotify_reader"),
+ inotify_fd_(inotify_init()),
+ valid_(false) {
+ shutdown_pipe_[0] = -1;
+ shutdown_pipe_[1] = -1;
+ if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
+ thread_.message_loop()->PostTask(
+ FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0]));
+ valid_ = true;
+ }
+}
+
+InotifyReader::~InotifyReader() {
+ if (valid_) {
+ // Write to the self-pipe so that the select call in InotifyReaderTask returns.
+ write(shutdown_pipe_[1], "", 1);
+ thread_.Stop();
+ }
+ if (inotify_fd_ >= 0)
+ close(inotify_fd_);
+ if (shutdown_pipe_[0] >= 0)
+ close(shutdown_pipe_[0]);
+ if (shutdown_pipe_[1] >= 0)
+ close(shutdown_pipe_[1]);
+}
+
+InotifyReader::Watch InotifyReader::AddWatch(
+ const FilePath& path, DirectoryWatcher::Delegate* delegate) {
+ if (!valid_)
+ return kInvalidWatch;
+
+ AutoLock auto_lock(lock_);
+
+ Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
+ IN_CREATE | IN_DELETE |
+ IN_CLOSE_WRITE | IN_MOVE);
+ if (watch == kInvalidWatch)
+ return kInvalidWatch;
+
+ if (paths_[watch].empty())
+ paths_[watch] = path; // We don't yet watch this path.
+
+ delegates_[watch].insert(std::make_pair(delegate, MessageLoop::current()));
+
+ return watch;
+}
+
+bool InotifyReader::RemoveWatch(Watch watch,
+ DirectoryWatcher::Delegate* delegate) {
+ if (!valid_)
+ return false;
+
+ AutoLock auto_lock(lock_);
+
+ if (paths_[watch].empty())
+ return false; // We don't recognize this watch.
+
+ // Only erase one occurrence of delegate (there may be more).
+ delegates_[watch].erase(
+ delegates_[watch].find(std::make_pair(delegate, MessageLoop::current())));
+
+ if (delegates_[watch].empty()) {
+ paths_.erase(watch);
+ delegates_.erase(watch);
+
+ return (inotify_rm_watch(inotify_fd_, watch) == 0);
+ }
+
+ return true;
+}
+
+void InotifyReader::OnInotifyEvent(inotify_event* event) {
+ if (event->mask & IN_IGNORED)
+ return;
+
+ DelegateSet delegates_to_notify;
+ FilePath changed_path;
+
+ {
+ AutoLock auto_lock(lock_);
+ changed_path = paths_[event->wd];
+ delegates_to_notify.insert(delegates_[event->wd].begin(),
+ delegates_[event->wd].end());
+ }
+
+ DelegateSet::iterator i;
+ for (i = delegates_to_notify.begin(); i != delegates_to_notify.end(); ++i) {
+ DirectoryWatcher::Delegate* delegate = i->first;
+ MessageLoop* loop = i->second;
+ loop->PostTask(FROM_HERE,
+ new InotifyReaderNotifyTask(delegate, changed_path));
+ }
+}
+
+} // namespace
+
+// Private implementation class implementing the behavior of DirectoryWatcher.
+class DirectoryWatcher::Impl : public base::RefCounted<DirectoryWatcher::Impl> {
+ public:
+ Impl(DirectoryWatcher::Delegate* delegate)
+ : delegate_(delegate),
+ watch_(InotifyReader::kInvalidWatch) {
+ }
+
+ ~Impl();
+
+ // Register interest in any changes in |path|.
+ // Returns false on error.
+ bool Watch(const FilePath& path);
+
+ private:
+ // Delegate to notify upon changes.
+ DirectoryWatcher::Delegate* delegate_;
+
+ // Path we're watching (passed to delegate).
+ FilePath path_;
+
+ // Watch returned by InotifyReader.
+ InotifyReader::Watch watch_;
+};
+
+DirectoryWatcher::Impl::~Impl() {
+ if (watch_ != InotifyReader::kInvalidWatch)
+ Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_);
+}
+
+bool DirectoryWatcher::Impl::Watch(const FilePath& path) {
+ DCHECK(watch_ == InotifyReader::kInvalidWatch); // Can only watch one path.
+
+ path_ = path;
+ watch_ = Singleton<InotifyReader>::get()->AddWatch(path, delegate_);
+
+ return watch_ != InotifyReader::kInvalidWatch;
+}
+
+DirectoryWatcher::DirectoryWatcher() {
+}
+
+DirectoryWatcher::~DirectoryWatcher() {
+ // Declared in .cc file for access to ~DirectoryWatcher::Impl.
+}
+
+bool DirectoryWatcher::Watch(const FilePath& path,
+ Delegate* delegate, bool recursive) {
+ if (recursive) {
+ // TODO(phajdan.jr): Support recursive watches.
+ // Unfortunately inotify has no "native" support for them, but it can be
+ // emulated by walking the directory tree and setting up watches for each
+ // directory. Of course this is ineffective for large directory trees.
+ // For the algorithm, see the link below:
+ // http://osdir.com/ml/gnome.dashboard.devel/2004-10/msg00022.html
+ NOTIMPLEMENTED();
+ return false;
+ }
+ impl_ = new DirectoryWatcher::Impl(delegate);
+ return impl_->Watch(path);
+}
diff --git a/base/directory_watcher_unittest.cc b/base/directory_watcher_unittest.cc
index 21a6269..3b6f645 100644
--- a/base/directory_watcher_unittest.cc
+++ b/base/directory_watcher_unittest.cc
@@ -12,61 +12,97 @@
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/path_service.h"
+#include "base/platform_thread.h"
#include "base/string_util.h"
#if defined(OS_WIN)
#include "base/win_util.h"
#endif
#include "testing/gtest/include/gtest/gtest.h"
-// For tests where we wait a bit to verify nothing happened
+// TODO(phajdan.jr): Clean up ifdefs in this file when Linux/Windows differences
+// get sorted out.
+
namespace {
+
+// For tests where we wait a bit to verify nothing happened
const int kWaitForEventTime = 500;
-}
+
+// Unfortunately Windows supports only recursive watches and Linux
+// only non-recursive ones.
+#if defined(OS_WIN)
+const bool kDefaultRecursiveValue = true;
+#elif defined(OS_LINUX)
+const bool kDefaultRecursiveValue = false;
+#endif
+
+} // namespace
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"));
+ FilePath path;
+ ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &path));
+ test_dir_ = path.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());
+ file_util::Delete(test_dir_, true);
+ file_util::CreateDirectory(test_dir_);
directory_mods_ = 0;
quit_mod_count_ = 0;
+
+ original_thread_id_ = PlatformThread::CurrentId();
}
virtual void OnDirectoryChanged(const FilePath& path) {
+ EXPECT_EQ(original_thread_id_, PlatformThread::CurrentId());
++directory_mods_;
- if (directory_mods_ == quit_mod_count_)
+ // The exact number is verified by VerifyExpectedNumberOfModifications.
+ // Sometimes we don't want such check, see WaitForFirstNotification.
+ if (directory_mods_ >= quit_mod_count_)
MessageLoop::current()->Quit();
}
virtual void TearDown() {
+ // Make sure there are no tasks in the loop.
+ loop_.RunAllPending();
+
// Clean up test directory.
- ASSERT_TRUE(file_util::Delete(test_dir_.value(), true));
- ASSERT_FALSE(file_util::PathExists(test_dir_.value()));
+ ASSERT_TRUE(file_util::Delete(test_dir_, true));
+ ASSERT_FALSE(file_util::PathExists(test_dir_));
}
// 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();
+ file_util::WriteFile(path, content.c_str(), content.length());
}
- // Run the message loop until we've seen |n| directory modifications.
- void LoopUntilModsEqual(int n) {
+ void SetExpectedNumberOfModifications(int n) {
quit_mod_count_ = n;
+ }
+
+ void VerifyExpectedNumberOfModifications() {
+ // Check that we get at least the expected number of notifications.
+ if (quit_mod_count_ - directory_mods_ > 0)
+ loop_.Run();
+
+ // Check that we get no more than the expected number of notifications.
+ loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
+ kWaitForEventTime);
+ loop_.Run();
+ EXPECT_EQ(quit_mod_count_, directory_mods_);
+ }
+
+ // Only use this function if you don't care about getting
+ // too many notifications. Useful for tests where you get
+ // different number of notifications on different platforms.
+ void WaitForFirstNotification() {
+ directory_mods_ = 0;
+ SetExpectedNumberOfModifications(1);
loop_.Run();
}
@@ -81,63 +117,89 @@ class DirectoryWatcherTest : public testing::Test,
// The number of directory mods which, when reached, cause us to quit
// our message loop.
int quit_mod_count_;
+
+ // Keep track of original thread id to verify that callbacks are called
+ // on the same thread.
+ PlatformThreadId original_thread_id_;
};
// Basic test: add a file and verify we notice it.
TEST_F(DirectoryWatcherTest, NewFile) {
DirectoryWatcher watcher;
- ASSERT_TRUE(watcher.Watch(test_dir_, this));
+ ASSERT_TRUE(watcher.Watch(test_dir_, this, kDefaultRecursiveValue));
+ SetExpectedNumberOfModifications(2);
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
- LoopUntilModsEqual(2);
+ VerifyExpectedNumberOfModifications();
}
// Verify that modifying a file is caught.
TEST_F(DirectoryWatcherTest, ModifiedFile) {
DirectoryWatcher watcher;
- ASSERT_TRUE(watcher.Watch(test_dir_, this));
+ ASSERT_TRUE(watcher.Watch(test_dir_, this, kDefaultRecursiveValue));
// Write a file to the test dir.
+ SetExpectedNumberOfModifications(2);
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
- LoopUntilModsEqual(2);
+ VerifyExpectedNumberOfModifications();
// Now make sure we get notified if the file is modified.
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some new content");
- LoopUntilModsEqual(3);
+ // Use a more forgiving function to check because on Linux you will get
+ // 1 notification, and on Windows 2 (and nothing seems to convince it to
+ // send less notifications).
+ WaitForFirstNotification();
}
// Verify that letting the watcher go out of scope stops notifications.
TEST_F(DirectoryWatcherTest, Unregister) {
{
DirectoryWatcher watcher;
- ASSERT_TRUE(watcher.Watch(test_dir_, this));
+ ASSERT_TRUE(watcher.Watch(test_dir_, this, kDefaultRecursiveValue));
// And then let it fall out of scope, clearing its watch.
}
// Write a file to the test dir.
+ SetExpectedNumberOfModifications(0);
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);
+ VerifyExpectedNumberOfModifications();
}
-// Verify that modifications to a subdirectory are noticed.
TEST_F(DirectoryWatcherTest, SubDir) {
FilePath subdir(FILE_PATH_LITERAL("SubDir"));
ASSERT_TRUE(file_util::CreateDirectory(test_dir_.Append(subdir)));
- DirectoryWatcher watcher;
- ASSERT_TRUE(watcher.Watch(test_dir_, this));
- // Write a file to the subdir.
- FilePath test_path = subdir.AppendASCII("test_file");
- WriteTestDirFile(test_path.value(), "some content");
- LoopUntilModsEqual(2);
+#if defined(OS_WIN)
+ // TODO(port): Recursive watches are not implemented on Linux.
+
+ // Verify that modifications to a subdirectory are noticed by recursive watch.
+ {
+ DirectoryWatcher watcher;
+ ASSERT_TRUE(watcher.Watch(test_dir_, this, true));
+ // Write a file to the subdir.
+ SetExpectedNumberOfModifications(2);
+ FilePath test_path = subdir.AppendASCII("test_file");
+ WriteTestDirFile(test_path.value(), "some content");
+ VerifyExpectedNumberOfModifications();
+ }
+#endif // defined(OS_WIN)
+
+#if !defined(OS_WIN)
+ // TODO: Enable when the root cause of http://crbug.com/5072 is fixed.
+
+ // Verify that modifications to a subdirectory are not noticed
+ // by a not-recursive watch.
+ {
+ DirectoryWatcher watcher;
+ ASSERT_TRUE(watcher.Watch(test_dir_, this, false));
+ // Write a file to the subdir.
+ SetExpectedNumberOfModifications(0);
+ FilePath test_path = subdir.AppendASCII("test_file");
+ WriteTestDirFile(test_path.value(), "some content");
+ VerifyExpectedNumberOfModifications();
+ }
+#endif // !defined(OS_WIN)
}
namespace {
@@ -145,34 +207,77 @@ namespace {
// Deletes the DirectoryWatcher when it's notified.
class Deleter : public DirectoryWatcher::Delegate {
public:
- Deleter(DirectoryWatcher* watcher) : watcher_(watcher) {}
+ Deleter(DirectoryWatcher* watcher, MessageLoop* loop)
+ : watcher_(watcher),
+ loop_(loop) {
+ }
+
virtual void OnDirectoryChanged(const FilePath& path) {
watcher_.reset(NULL);
- MessageLoop::current()->Quit();
+ loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
}
scoped_ptr<DirectoryWatcher> watcher_;
+ MessageLoop* loop_;
};
} // 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));
+ Deleter deleter(watcher, &loop_); // Takes ownership of watcher.
+ ASSERT_TRUE(watcher->Watch(test_dir_, &deleter, kDefaultRecursiveValue));
WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
- LoopUntilModsEqual(2);
+ loop_.Run();
// We win if we haven't crashed yet.
// Might as well double-check it got deleted, too.
ASSERT_TRUE(deleter.watcher_.get() == NULL);
}
+TEST_F(DirectoryWatcherTest, MultipleWatchersSingleFile) {
+ DirectoryWatcher watcher1, watcher2;
+ ASSERT_TRUE(watcher1.Watch(test_dir_, this, kDefaultRecursiveValue));
+ ASSERT_TRUE(watcher2.Watch(test_dir_, this, kDefaultRecursiveValue));
+
+ SetExpectedNumberOfModifications(4); // Each watcher should fire twice.
+ WriteTestDirFile(FILE_PATH_LITERAL("test_file"), "some content");
+ VerifyExpectedNumberOfModifications();
+}
+
+TEST_F(DirectoryWatcherTest, MultipleWatchersDifferentFiles) {
+ const int kNumberOfWatchers = 5;
+ DirectoryWatcher watchers[kNumberOfWatchers];
+ FilePath subdirs[kNumberOfWatchers];
+ for (int i = 0; i < kNumberOfWatchers; i++) {
+ subdirs[i] = FilePath(FILE_PATH_LITERAL("Dir")).AppendASCII(IntToString(i));
+ ASSERT_TRUE(file_util::CreateDirectory(test_dir_.Append(subdirs[i])));
+
+ ASSERT_TRUE(watchers[i].Watch(test_dir_.Append(subdirs[i]), this,
+ kDefaultRecursiveValue));
+ }
+ for (int i = 0; i < kNumberOfWatchers; i++) {
+ // Verify that we only get modifications from one watcher (each watcher has
+ // different directory).
+
+ ASSERT_EQ(0, directory_mods_);
+
+ // Write a file to the subdir.
+ FilePath test_path = subdirs[i].AppendASCII("test_file");
+ SetExpectedNumberOfModifications(2);
+ WriteTestDirFile(test_path.value(), "some content");
+ VerifyExpectedNumberOfModifications();
+
+ directory_mods_ = 0;
+ }
+}
+
// Verify that watching a directory that doesn't exist fails, but doesn't
// asssert.
// Basic test: add a file and verify we notice it.
TEST_F(DirectoryWatcherTest, NonExistentDirectory) {
DirectoryWatcher watcher;
- ASSERT_FALSE(watcher.Watch(test_dir_.AppendASCII("does-not-exist"), this));
+ ASSERT_FALSE(watcher.Watch(test_dir_.AppendASCII("does-not-exist"), this,
+ kDefaultRecursiveValue));
}
diff --git a/base/directory_watcher_win.cc b/base/directory_watcher_win.cc
index f978277..21fdd38 100644
--- a/base/directory_watcher_win.cc
+++ b/base/directory_watcher_win.cc
@@ -5,6 +5,7 @@
#include "base/directory_watcher.h"
#include "base/file_path.h"
+#include "base/logging.h"
#include "base/object_watcher.h"
// Private implementation class implementing the behavior of DirectoryWatcher.
@@ -80,7 +81,12 @@ DirectoryWatcher::~DirectoryWatcher() {
}
bool DirectoryWatcher::Watch(const FilePath& path,
- Delegate* delegate) {
+ Delegate* delegate, bool recursive) {
+ if (!recursive) {
+ // See http://crbug.com/5072.
+ NOTIMPLEMENTED();
+ return false;
+ }
impl_ = new DirectoryWatcher::Impl(delegate);
return impl_->Watch(path);
}