diff options
author | tony@chromium.org <tony@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-15 01:57:07 +0000 |
---|---|---|
committer | tony@chromium.org <tony@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-15 01:57:07 +0000 |
commit | d6f9c9e2adb5c36fe4091f70ecda64e69dc031b4 (patch) | |
tree | 9f4e6d8d010a33244e2acfc2f20de26ded8a375e /base | |
parent | e0fc2f1f81f13440af3bb4cf5e56a031f91fc163 (diff) | |
download | chromium_src-d6f9c9e2adb5c36fe4091f70ecda64e69dc031b4.zip chromium_src-d6f9c9e2adb5c36fe4091f70ecda64e69dc031b4.tar.gz chromium_src-d6f9c9e2adb5c36fe4091f70ecda64e69dc031b4.tar.bz2 |
Move FileWatcher from src/base/ to src/chrome/browser/ and switch
it from using MessageLoop to post tasks to using
ChromeThread::PostTask, which is safer.
Review URL: http://codereview.chromium.org/864001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@41560 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/base.gyp | 1 | ||||
-rw-r--r-- | base/base.gypi | 22 | ||||
-rw-r--r-- | base/file_watcher.h | 65 | ||||
-rw-r--r-- | base/file_watcher_inotify.cc | 323 | ||||
-rw-r--r-- | base/file_watcher_mac.cc | 134 | ||||
-rw-r--r-- | base/file_watcher_stub.cc | 20 | ||||
-rw-r--r-- | base/file_watcher_unittest.cc | 261 | ||||
-rw-r--r-- | base/file_watcher_win.cc | 113 |
8 files changed, 0 insertions, 939 deletions
diff --git a/base/base.gyp b/base/base.gyp index 1a40a29..aae38b7 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -70,7 +70,6 @@ 'file_path_unittest.cc', 'file_util_unittest.cc', 'file_version_info_unittest.cc', - 'file_watcher_unittest.cc', 'gmock_unittest.cc', 'histogram_unittest.cc', 'hmac_unittest.cc', diff --git a/base/base.gypi b/base/base.gypi index a251641..72ce24d 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -293,7 +293,6 @@ 'sources!': [ 'atomicops_internals_x86_gcc.cc', 'base_paths_posix.cc', - 'file_watcher_inotify.cc', 'linux_util.cc', 'message_pump_glib.cc', ], @@ -316,17 +315,6 @@ 'sources/': [ ['exclude', '_openbsd\\.cc$'] ], }, ], - [ 'GENERATOR == "quentin"', { - # Quentin builds don't have a recent enough glibc to include the - # inotify headers - 'sources!': [ - 'file_watcher_inotify.cc', - ], - 'sources': [ - 'file_watcher_stub.cc', - ], - }, - ], [ 'OS == "mac"', { 'sources!': [ # TODO(wtc): Remove nss_util.{cc,h} when http://crbug.com/30689 @@ -421,12 +409,6 @@ ], },], [ 'OS == "freebsd" or OS == "openbsd"', { - 'sources!': [ - 'file_watcher_inotify.cc', - ], - 'sources': [ - 'file_watcher_stub.cc', - ], 'link_settings': { 'libraries': [ '-L/usr/local/lib -lexecinfo', @@ -506,10 +488,6 @@ 'base_drop_target.cc', 'base_drop_target.h', 'data_pack.cc', - 'file_watcher.h', - 'file_watcher_inotify.cc', - 'file_watcher_mac.cc', - 'file_watcher_win.cc', 'dynamic_annotations.h', 'dynamic_annotations.cc', 'event_recorder.cc', diff --git a/base/file_watcher.h b/base/file_watcher.h deleted file mode 100644 index 1147d42..0000000 --- a/base/file_watcher.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2010 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 file for changes. - -#ifndef BASE_FILE_WATCHER_H_ -#define BASE_FILE_WATCHER_H_ - -#include "base/basictypes.h" -#include "base/ref_counted.h" - -class FilePath; -class MessageLoop; - -// This class lets you register interest in changes on a file. The delegate -// will get called whenever the file is changed, including created or deleted. -// WARNING: To be able to get create/delete notifications and to work cross -// platform, we actually listen for changes to the directory containing -// the file. -// WARNING: On OSX and Windows, the OS API doesn't tell us which file in the -// directory changed. We work around this by watching the file time, but this -// can result in some extra notifications if we get other notifications within -// 2s of the file having changed. -class FileWatcher { - public: - class Delegate { - public: - virtual ~Delegate() {} - virtual void OnFileChanged(const FilePath& path) = 0; - }; - - FileWatcher(); - ~FileWatcher() {} - - // Register interest in any changes on the file |path|. - // OnFileChanged will be called back for each change to the file. - // Any background operations will be ran on |backend_loop|, or inside Watch - // if |backend_loop| is NULL. Note: The directory containing |path| must - // exist before you try to watch the file. - // Returns false on error. - bool Watch(const FilePath& path, Delegate* delegate, - MessageLoop* backend_loop) { - return impl_->Watch(path, delegate, backend_loop); - } - - // Used internally to encapsulate different members on different platforms. - class PlatformDelegate : public base::RefCounted<PlatformDelegate> { - public: - virtual bool Watch(const FilePath& path, Delegate* delegate, - MessageLoop* backend_loop) = 0; - - protected: - friend class base::RefCounted<PlatformDelegate>; - - virtual ~PlatformDelegate() {} - }; - - private: - scoped_refptr<PlatformDelegate> impl_; - - DISALLOW_COPY_AND_ASSIGN(FileWatcher); -}; - -#endif // BASE_FILE_WATCHER_H_ diff --git a/base/file_watcher_inotify.cc b/base/file_watcher_inotify.cc deleted file mode 100644 index 9bc4626..0000000 --- a/base/file_watcher_inotify.cc +++ /dev/null @@ -1,323 +0,0 @@ -// 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/file_watcher.h" - -#include <errno.h> -#include <string.h> -#include <sys/inotify.h> -#include <sys/ioctl.h> -#include <sys/select.h> -#include <unistd.h> - -#include <algorithm> -#include <set> -#include <utility> -#include <vector> - -#include "base/eintr_wrapper.h" -#include "base/file_path.h" -#include "base/file_util.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" -#include "base/waitable_event.h" - -namespace { - -class FileWatcherImpl; - -// 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. |watcher| will be notified on each change. - // Returns kInvalidWatch on failure. - Watch AddWatch(const FilePath& path, FileWatcherImpl* watcher); - - // Remove |watch|. Returns true on success. - bool RemoveWatch(Watch watch, FileWatcherImpl* watcher); - - // Callback for InotifyReaderTask. - void OnInotifyEvent(const inotify_event* event); - - private: - friend struct DefaultSingletonTraits<InotifyReader>; - - typedef std::set<FileWatcherImpl*> WatcherSet; - - InotifyReader(); - ~InotifyReader(); - - // We keep track of which delegates want to be notified on which watches. - base::hash_map<Watch, WatcherSet> watchers_; - - // Lock to protect watchers_. - 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 FileWatcherImpl : public FileWatcher::PlatformDelegate { - public: - FileWatcherImpl(); - ~FileWatcherImpl(); - - // Called for each event coming from the watch. - void OnInotifyEvent(const inotify_event* event); - - // Start watching |path| for changes and notify |delegate| on each change. - // Returns true if watch for |path| has been added successfully. - virtual bool Watch(const FilePath& path, FileWatcher::Delegate* delegate, - MessageLoop* backend_loop); - - private: - // Delegate to notify upon changes. - FileWatcher::Delegate* delegate_; - - // Watch returned by InotifyReader. - InotifyReader::Watch watch_; - - // The file we're watching. - FilePath path_; - - // Loop where we post file change notifications to. - MessageLoop* loop_; - - DISALLOW_COPY_AND_ASSIGN(FileWatcherImpl); -}; - -class FileWatcherImplNotifyTask : public Task { - public: - FileWatcherImplNotifyTask(FileWatcher::Delegate* delegate, - const FilePath& path) - : delegate_(delegate), path_(path) { - } - - virtual void Run() { - delegate_->OnFileChanged(path_); - } - - private: - FileWatcher::Delegate* delegate_; - FilePath path_; - - DISALLOW_COPY_AND_ASSIGN(FileWatcherImplNotifyTask); -}; - -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 = - HANDLE_EINTR(select(std::max(inotify_fd_, shutdown_fd_) + 1, - &rfds, NULL, NULL, NULL)); - if (select_result < 0) { - DPLOG(WARNING) << "select failed"; - return; - } - - if (FD_ISSET(shutdown_fd_, &rfds)) - return; - - // Adjust buffer size to current event queue size. - int buffer_size; - int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, - &buffer_size)); - - if (ioctl_result != 0) { - DPLOG(WARNING) << "ioctl failed"; - return; - } - - std::vector<char> buffer(buffer_size); - - ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd_, &buffer[0], - buffer_size)); - - if (bytes_read < 0) { - DPLOG(WARNING) << "read from inotify fd failed"; - 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); -}; - -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. - ssize_t ret = HANDLE_EINTR(write(shutdown_pipe_[1], "", 1)); - DPCHECK(ret > 0); - DCHECK_EQ(ret, 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, FileWatcherImpl* watcher) { - 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; - - watchers_[watch].insert(watcher); - - return watch; -} - -bool InotifyReader::RemoveWatch(Watch watch, - FileWatcherImpl* watcher) { - if (!valid_) - return false; - - AutoLock auto_lock(lock_); - - watchers_[watch].erase(watcher); - - if (watchers_[watch].empty()) { - watchers_.erase(watch); - return (inotify_rm_watch(inotify_fd_, watch) == 0); - } - - return true; -} - -void InotifyReader::OnInotifyEvent(const inotify_event* event) { - if (event->mask & IN_IGNORED) - return; - - // In case you want to limit the scope of this lock, it's not sufficient - // to just copy things under the lock, and then run the notifications - // without holding the lock. FileWatcherImpl's dtor removes its watches, - // and to do that obtains the lock. After it finishes removing watches, - // it's destroyed. So, if you copy under the lock and notify without the lock, - // it's possible you'll copy the FileWatcherImpl which is being - // destroyed, then it will destroy itself, and then you'll try to notify it. - AutoLock auto_lock(lock_); - - for (WatcherSet::iterator watcher = watchers_[event->wd].begin(); - watcher != watchers_[event->wd].end(); - ++watcher) { - (*watcher)->OnInotifyEvent(event); - } -} - -FileWatcherImpl::FileWatcherImpl() - : watch_(InotifyReader::kInvalidWatch) { -} - -FileWatcherImpl::~FileWatcherImpl() { - if (watch_ == InotifyReader::kInvalidWatch) - return; - - Singleton<InotifyReader>::get()->RemoveWatch(watch_, this); -} - -void FileWatcherImpl::OnInotifyEvent(const inotify_event* event) { - // Since we're watching the directory, filter out inotify events - // if it's not related to the file we're watching. - if (path_ != path_.DirName().Append(event->name)) - return; - - loop_->PostTask(FROM_HERE, - new FileWatcherImplNotifyTask(delegate_, path_)); -} - -bool FileWatcherImpl::Watch(const FilePath& path, - FileWatcher::Delegate* delegate, - MessageLoop* backend_loop) { - // Each FileWatcherImpl can only watch one file. - DCHECK(watch_ == InotifyReader::kInvalidWatch); - - // It's not possible to watch a file that doesn't exist, so instead, - // watch the parent directory. - if (!file_util::PathExists(path.DirName())) - return false; - - delegate_ = delegate; - path_ = path; - loop_ = MessageLoop::current(); - watch_ = Singleton<InotifyReader>::get()->AddWatch(path.DirName(), this); - return watch_ != InotifyReader::kInvalidWatch; -} - -} // namespace - -FileWatcher::FileWatcher() { - impl_ = new FileWatcherImpl(); -} diff --git a/base/file_watcher_mac.cc b/base/file_watcher_mac.cc deleted file mode 100644 index 2d85133..0000000 --- a/base/file_watcher_mac.cc +++ /dev/null @@ -1,134 +0,0 @@ -// 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/file_watcher.h" - -#include <CoreServices/CoreServices.h> - -#include "base/file_path.h" -#include "base/file_util.h" -#include "base/logging.h" -#include "base/message_loop.h" -#include "base/scoped_cftyperef.h" -#include "base/time.h" - -namespace { - -const CFAbsoluteTime kEventLatencySeconds = 0.3; - -class FileWatcherImpl : public FileWatcher::PlatformDelegate { - public: - FileWatcherImpl() {} - ~FileWatcherImpl() { - if (!path_.value().empty()) { - FSEventStreamStop(fsevent_stream_); - FSEventStreamInvalidate(fsevent_stream_); - FSEventStreamRelease(fsevent_stream_); - } - } - - virtual bool Watch(const FilePath& path, FileWatcher::Delegate* delegate, - MessageLoop* backend_loop); - - void OnFSEventsCallback(const FilePath& event_path) { - DCHECK(!path_.value().empty()); - FilePath absolute_event_path = event_path; - if (!file_util::AbsolutePath(&absolute_event_path)) - return; - - file_util::FileInfo file_info; - bool file_exists = file_util::GetFileInfo(path_, &file_info); - if (file_exists && (last_modified_.is_null() || - last_modified_ != file_info.last_modified)) { - last_modified_ = file_info.last_modified; - delegate_->OnFileChanged(path_); - } else if (file_exists && (base::Time::Now() - last_modified_ < - base::TimeDelta::FromSeconds(2))) { - // Since we only have a resolution of 1s, if we get a callback within - // 2s of the file having changed, go ahead and notify our observer. This - // might be from a different file change, but it's better to notify too - // much rather than miss a notification. - delegate_->OnFileChanged(path_); - } else if (!file_exists && !last_modified_.is_null()) { - last_modified_ = base::Time(); - delegate_->OnFileChanged(path_); - } - } - - private: - // Delegate to notify upon changes. - FileWatcher::Delegate* delegate_; - - // Path we're watching (passed to delegate). - FilePath path_; - - // Backend stream we receive event callbacks from (strong reference). - FSEventStreamRef fsevent_stream_; - - // Keep track of the last modified time of the file. We use nulltime - // to represent the file not existing. - base::Time last_modified_; - - DISALLOW_COPY_AND_ASSIGN(FileWatcherImpl); -}; - -void FSEventsCallback(ConstFSEventStreamRef stream, - void* event_watcher, size_t num_events, - void* event_paths, const FSEventStreamEventFlags flags[], - const FSEventStreamEventId event_ids[]) { - char** paths = reinterpret_cast<char**>(event_paths); - FileWatcherImpl* watcher = - reinterpret_cast<FileWatcherImpl*>(event_watcher); - for (size_t i = 0; i < num_events; i++) - watcher->OnFSEventsCallback(FilePath(paths[i])); -} - -bool FileWatcherImpl::Watch(const FilePath& path, - FileWatcher::Delegate* delegate, - MessageLoop* backend_loop) { - DCHECK(path_.value().empty()); // Can only watch one path. - DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); - - FilePath parent_dir = path.DirName(); - if (!file_util::AbsolutePath(&parent_dir)) - return false; - - file_util::FileInfo file_info; - if (file_util::GetFileInfo(path, &file_info)) - last_modified_ = file_info.last_modified; - - path_ = path; - delegate_ = delegate; - - scoped_cftyperef<CFStringRef> cf_path(CFStringCreateWithCString( - NULL, path.DirName().value().c_str(), kCFStringEncodingMacHFS)); - CFStringRef path_for_array = cf_path.get(); - scoped_cftyperef<CFArrayRef> watched_paths(CFArrayCreate( - NULL, reinterpret_cast<const void**>(&path_for_array), 1, - &kCFTypeArrayCallBacks)); - - FSEventStreamContext context; - context.version = 0; - context.info = this; - context.retain = NULL; - context.release = NULL; - context.copyDescription = NULL; - - fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, - watched_paths, - kFSEventStreamEventIdSinceNow, - kEventLatencySeconds, - kFSEventStreamCreateFlagNone); - FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), - kCFRunLoopDefaultMode); - FSEventStreamStart(fsevent_stream_); - - return true; -} - -} // namespace - -FileWatcher::FileWatcher() { - impl_ = new FileWatcherImpl(); -} diff --git a/base/file_watcher_stub.cc b/base/file_watcher_stub.cc deleted file mode 100644 index 99484fc..0000000 --- a/base/file_watcher_stub.cc +++ /dev/null @@ -1,20 +0,0 @@ -// 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. - -// This file exists for Linux systems which don't have the inotify headers, and -// thus cannot build file_watcher_inotify.cc - -#include "base/file_watcher.h" - -class FileWatcherImpl : public FileWatcher::PlatformDelegate { - public: - virtual bool Watch(const FilePath& path, FileWatcher::Delegate* delegate, - MessageLoop* backend_loop) { - return false; - } -}; - -FileWatcher::FileWatcher() { - impl_ = new FileWatcherImpl(); -} diff --git a/base/file_watcher_unittest.cc b/base/file_watcher_unittest.cc deleted file mode 100644 index 014488e..0000000 --- a/base/file_watcher_unittest.cc +++ /dev/null @@ -1,261 +0,0 @@ -// 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/file_watcher.h" - -#include <limits> - -#include "base/basictypes.h" -#include "base/file_path.h" -#include "base/file_util.h" -#include "base/message_loop.h" -#include "base/path_service.h" -#include "base/platform_thread.h" -#include "base/scoped_temp_dir.h" -#include "base/string_util.h" -#include "base/thread.h" -#if defined(OS_WIN) -#include "base/win_util.h" -#endif // defined(OS_WIN) -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -// For tests where we wait a bit to verify nothing happened -const int kWaitForEventTime = 500; - -class FileWatcherTest : public testing::Test { - public: - // Implementation of FileWatcher on Mac requires UI loop. - FileWatcherTest() - : loop_(MessageLoop::TYPE_UI), - notified_delegates_(0), - expected_notified_delegates_(0) { - } - - void OnTestDelegateFirstNotification() { - notified_delegates_++; - if (notified_delegates_ >= expected_notified_delegates_) - MessageLoop::current()->Quit(); - } - - protected: - virtual void SetUp() { - temp_dir_.reset(new ScopedTempDir); - ASSERT_TRUE(temp_dir_->CreateUniqueTempDir()); - } - - FilePath test_file() { - return temp_dir_->path().AppendASCII("FileWatcherTest"); - } - - virtual void TearDown() { - // Make sure there are no tasks in the loop. - loop_.RunAllPending(); - } - - // Write |content| to the test file. Returns true on success. - bool WriteTestFile(const std::string& content) { - int write_size = file_util::WriteFile(test_file(), content.c_str(), - content.length()); - return write_size == static_cast<int>(content.length()); - } - - void SetExpectedNumberOfNotifiedDelegates(int n) { - notified_delegates_ = 0; - expected_notified_delegates_ = n; - } - - void VerifyExpectedNumberOfNotifiedDelegates() { - // Check that we get at least the expected number of notified delegates. - if (expected_notified_delegates_ - notified_delegates_ > 0) - loop_.Run(); - EXPECT_EQ(expected_notified_delegates_, notified_delegates_); - } - - void VerifyNoExtraNotifications() { - // Check that we get no more than the expected number of notified delegates. - loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask, - kWaitForEventTime); - loop_.Run(); - EXPECT_EQ(expected_notified_delegates_, notified_delegates_); - } - - // We need this function for reliable tests on Mac OS X. FSEvents API - // has a latency interval and can merge multiple events into one, - // and we need a clear distinction between events triggered by test setup code - // and test code. - void SyncIfPOSIX() { -#if defined(OS_POSIX) - sync(); -#endif // defined(OS_POSIX) - } - - MessageLoop loop_; - scoped_ptr<ScopedTempDir> temp_dir_; - - // The number of test delegates which received their notification. - int notified_delegates_; - - // The number of notified test delegates after which we quit the message loop. - int expected_notified_delegates_; -}; - -class TestDelegate : public FileWatcher::Delegate { - public: - explicit TestDelegate(FileWatcherTest* test) - : test_(test), - got_notification_(false), - original_thread_id_(PlatformThread::CurrentId()) { - } - - bool got_notification() const { - return got_notification_; - } - - void reset() { - got_notification_ = false; - } - - virtual void OnFileChanged(const FilePath& path) { - EXPECT_EQ(original_thread_id_, PlatformThread::CurrentId()); - if (!got_notification_) - test_->OnTestDelegateFirstNotification(); - got_notification_ = true; - } - - private: - // Hold a pointer to current test fixture to inform it on first notification. - FileWatcherTest* test_; - - // Set to true after first notification. - bool got_notification_; - - // Keep track of original thread id to verify that callbacks are called - // on the same thread. - PlatformThreadId original_thread_id_; -}; - -// Basic test: Create the file and verify that we notice. -TEST_F(FileWatcherTest, NewFile) { - FileWatcher watcher; - TestDelegate delegate(this); - ASSERT_TRUE(watcher.Watch(test_file(), &delegate, NULL)); - - SetExpectedNumberOfNotifiedDelegates(1); - ASSERT_TRUE(WriteTestFile("content")); - VerifyExpectedNumberOfNotifiedDelegates(); -} - -// Verify that modifying the file is caught. -TEST_F(FileWatcherTest, ModifiedFile) { - ASSERT_TRUE(WriteTestFile("content")); - SyncIfPOSIX(); - - FileWatcher watcher; - TestDelegate delegate(this); - ASSERT_TRUE(watcher.Watch(test_file(), &delegate, NULL)); - - // Now make sure we get notified if the file is modified. - SetExpectedNumberOfNotifiedDelegates(1); - ASSERT_TRUE(WriteTestFile("new content")); - VerifyExpectedNumberOfNotifiedDelegates(); -} - -TEST_F(FileWatcherTest, DeletedFile) { - ASSERT_TRUE(WriteTestFile("content")); - SyncIfPOSIX(); - - FileWatcher watcher; - TestDelegate delegate(this); - ASSERT_TRUE(watcher.Watch(test_file(), &delegate, NULL)); - - // Now make sure we get notified if the file is deleted. - SetExpectedNumberOfNotifiedDelegates(1); - file_util::Delete(test_file(), false); - VerifyExpectedNumberOfNotifiedDelegates(); -} - -// Verify that letting the watcher go out of scope stops notifications. -TEST_F(FileWatcherTest, Unregister) { - TestDelegate delegate(this); - - { - FileWatcher watcher; - ASSERT_TRUE(watcher.Watch(test_file(), &delegate, NULL)); - - // And then let it fall out of scope, clearing its watch. - } - - // Write a file to the test dir. - SetExpectedNumberOfNotifiedDelegates(0); - ASSERT_TRUE(WriteTestFile("content")); - VerifyExpectedNumberOfNotifiedDelegates(); - VerifyNoExtraNotifications(); -} - - -namespace { -// Used by the DeleteDuringNotify test below. -// Deletes the FileWatcher when it's notified. -class Deleter : public FileWatcher::Delegate { - public: - Deleter(FileWatcher* watcher, MessageLoop* loop) - : watcher_(watcher), - loop_(loop) { - } - - virtual void OnFileChanged(const FilePath& path) { - watcher_.reset(NULL); - loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); - } - - scoped_ptr<FileWatcher> watcher_; - MessageLoop* loop_; -}; -} // anonymous namespace - -// Verify that deleting a watcher during the callback doesn't crash. -TEST_F(FileWatcherTest, DeleteDuringNotify) { - FileWatcher* watcher = new FileWatcher; - Deleter deleter(watcher, &loop_); // Takes ownership of watcher. - ASSERT_TRUE(watcher->Watch(test_file(), &deleter, NULL)); - - ASSERT_TRUE(WriteTestFile("content")); - 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(FileWatcherTest, BackendLoop) { - base::Thread thread("test"); - ASSERT_TRUE(thread.Start()); - - FileWatcher watcher; - TestDelegate delegate(this); - ASSERT_TRUE(watcher.Watch(test_file(), &delegate, thread.message_loop())); -} - -TEST_F(FileWatcherTest, MultipleWatchersSingleFile) { - FileWatcher watcher1, watcher2; - TestDelegate delegate1(this), delegate2(this); - ASSERT_TRUE(watcher1.Watch(test_file(), &delegate1, NULL)); - ASSERT_TRUE(watcher2.Watch(test_file(), &delegate2, NULL)); - - SetExpectedNumberOfNotifiedDelegates(2); - ASSERT_TRUE(WriteTestFile("content")); - VerifyExpectedNumberOfNotifiedDelegates(); -} - -// Verify that watching a file who's parent directory doesn't exist -// fails, but doesn't asssert. -TEST_F(FileWatcherTest, NonExistentDirectory) { - FileWatcher watcher; - ASSERT_FALSE(watcher.Watch(test_file().AppendASCII("FileToWatch"), - NULL, NULL)); -} - -} // namespace diff --git a/base/file_watcher_win.cc b/base/file_watcher_win.cc deleted file mode 100644 index ac04757..0000000 --- a/base/file_watcher_win.cc +++ /dev/null @@ -1,113 +0,0 @@ -// 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/file_watcher.h" - -#include "base/file_path.h" -#include "base/file_util.h" -#include "base/logging.h" -#include "base/object_watcher.h" -#include "base/ref_counted.h" -#include "base/time.h" - -namespace { - -class FileWatcherImpl : public FileWatcher::PlatformDelegate, - public base::ObjectWatcher::Delegate { - public: - FileWatcherImpl() : delegate_(NULL), handle_(INVALID_HANDLE_VALUE) {} - - virtual bool Watch(const FilePath& path, FileWatcher::Delegate* delegate, - MessageLoop* backend_loop); - - // Callback from MessageLoopForIO. - virtual void OnObjectSignaled(HANDLE object); - - private: - virtual ~FileWatcherImpl(); - - // Delegate to notify upon changes. - FileWatcher::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_; - - // Keep track of the last modified time of the file. We use nulltime - // to represent the file not existing. - base::Time last_modified_; - - DISALLOW_COPY_AND_ASSIGN(FileWatcherImpl); -}; - -FileWatcherImpl::~FileWatcherImpl() { - if (handle_ != INVALID_HANDLE_VALUE) { - watcher_.StopWatching(); - FindCloseChangeNotification(handle_); - } -} - -bool FileWatcherImpl::Watch(const FilePath& path, - FileWatcher::Delegate* delegate, - MessageLoop* backend_loop) { - DCHECK(path_.value().empty()); // Can only watch one path. - file_util::FileInfo file_info; - if (file_util::GetFileInfo(path, &file_info)) - last_modified_ = file_info.last_modified; - - // FindFirstChangeNotification watches directories, so use the parent path. - handle_ = FindFirstChangeNotification( - path.DirName().value().c_str(), - false, // Don't watch subtrees - FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE | - FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME); - if (handle_ == INVALID_HANDLE_VALUE) - return false; - - delegate_ = delegate; - path_ = path; - watcher_.StartWatching(handle_, this); - - return true; -} - -void FileWatcherImpl::OnObjectSignaled(HANDLE object) { - DCHECK(object == handle_); - // Make sure we stay alive through the body of this function. - scoped_refptr<FileWatcherImpl> keep_alive(this); - - file_util::FileInfo file_info; - bool file_exists = file_util::GetFileInfo(path_, &file_info); - if (file_exists && (last_modified_.is_null() || - last_modified_ != file_info.last_modified)) { - last_modified_ = file_info.last_modified; - delegate_->OnFileChanged(path_); - } else if (file_exists && (base::Time::Now() - last_modified_ < - base::TimeDelta::FromSeconds(2))) { - // Since we only have a resolution of 1s, if we get a callback within - // 2s of the file having changed, go ahead and notify our observer. This - // might be from a different file change, but it's better to notify too - // much rather than miss a notification. - delegate_->OnFileChanged(path_); - } else if (!file_exists && !last_modified_.is_null()) { - last_modified_ = base::Time(); - delegate_->OnFileChanged(path_); - } - - // Register for more notifications on file change. - BOOL ok = FindNextChangeNotification(object); - DCHECK(ok); - watcher_.StartWatching(object, this); -} - -} // namespace - -FileWatcher::FileWatcher() { - impl_ = new FileWatcherImpl(); -} |