diff options
Diffstat (limited to 'base')
-rw-r--r-- | base/base.gyp | 2 | ||||
-rw-r--r-- | base/base.scons | 1 | ||||
-rw-r--r-- | base/base_unittests.scons | 1 | ||||
-rw-r--r-- | base/directory_watcher.h | 5 | ||||
-rw-r--r-- | base/directory_watcher_inotify.cc | 329 | ||||
-rw-r--r-- | base/directory_watcher_unittest.cc | 195 | ||||
-rw-r--r-- | base/directory_watcher_win.cc | 8 |
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); } |