diff options
author | mnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-19 10:35:46 +0000 |
---|---|---|
committer | mnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-19 10:35:46 +0000 |
commit | 5199d74e9d09b9a0b3ab9f600fcc96a54a5902ba (patch) | |
tree | 6f9cd0f89fca60d9c3ecb8f53a740f051428e862 /chrome/browser/file_path_watcher_win.cc | |
parent | bdb541d643bce8fa366b8102c579ff84f42e40be (diff) | |
download | chromium_src-5199d74e9d09b9a0b3ab9f600fcc96a54a5902ba.zip chromium_src-5199d74e9d09b9a0b3ab9f600fcc96a54a5902ba.tar.gz chromium_src-5199d74e9d09b9a0b3ab9f600fcc96a54a5902ba.tar.bz2 |
Reland 56341 - Add support for watching directories to FileWatcher.
BUG=none
TEST=Unit tests in file_watcher_unittest.cc.
Review URL: http://codereview.chromium.org/3184010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56670 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/file_path_watcher_win.cc')
-rw-r--r-- | chrome/browser/file_path_watcher_win.cc | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/chrome/browser/file_path_watcher_win.cc b/chrome/browser/file_path_watcher_win.cc new file mode 100644 index 0000000..194e91e --- /dev/null +++ b/chrome/browser/file_path_watcher_win.cc @@ -0,0 +1,243 @@ +// 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 "chrome/browser/file_path_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 FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, + public base::ObjectWatcher::Delegate { + public: + FilePathWatcherImpl() : delegate_(NULL), handle_(INVALID_HANDLE_VALUE) {} + + virtual bool Watch(const FilePath& path, FilePathWatcher::Delegate* delegate); + virtual void Cancel(); + + // Callback from MessageLoopForIO. + virtual void OnObjectSignaled(HANDLE object); + + private: + virtual ~FilePathWatcherImpl(); + + // Setup a watch handle for directory |dir|. Returns true if no fatal error + // occurs. |handle| will receive the handle value if |dir| is watchable, + // otherwise INVALID_HANDLE_VALUE. + static bool SetupWatchHandle(const FilePath& dir, HANDLE* handle) + WARN_UNUSED_RESULT; + + // (Re-)Initialize the watch handle. + bool UpdateWatch() WARN_UNUSED_RESULT; + + // Destroy the watch handle. + void DestroyWatch(); + + // Delegate to notify upon changes. + scoped_refptr<FilePathWatcher::Delegate> delegate_; + + // Path we're supposed to watch (passed to delegate). + FilePath target_; + + // 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_; + + // The time at which we processed the first notification with the + // |last_modified_| time stamp. + base::Time first_notification_; + + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); +}; + +bool FilePathWatcherImpl::Watch(const FilePath& path, + FilePathWatcher::Delegate* delegate) { + DCHECK(target_.value().empty()); // Can only watch one path. + delegate_ = delegate; + target_ = path; + + if (!UpdateWatch()) + return false; + + watcher_.StartWatching(handle_, this); + + return true; +} + +void FilePathWatcherImpl::Cancel() { + // Switch to the file thread if necessary so we can stop |watcher_|. + if (!ChromeThread::CurrentlyOn(ChromeThread::FILE)) { + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); + return; + } + + if (handle_ != INVALID_HANDLE_VALUE) + DestroyWatch(); +} + +void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) { + DCHECK(object == handle_); + // Make sure we stay alive through the body of this function. + scoped_refptr<FilePathWatcherImpl> keep_alive(this); + + if (!UpdateWatch()) { + delegate_->OnError(); + return; + } + + // Check whether the event applies to |target_| and notify the delegate. + file_util::FileInfo file_info; + bool file_exists = file_util::GetFileInfo(target_, &file_info); + if (file_exists && (last_modified_.is_null() || + last_modified_ != file_info.last_modified)) { + last_modified_ = file_info.last_modified; + first_notification_ = base::Time::Now(); + delegate_->OnFilePathChanged(target_); + } else if (file_exists && !first_notification_.is_null()) { + // The target's last modification time is equal to what's on record. This + // means that either an unrelated event occurred, or the target changed + // again (file modification times only have a resolution of 1s). Comparing + // file modification times against the wall clock is not reliable to find + // out whether the change is recent, since this code might just run too + // late. Moreover, there's no guarantee that file modification time and wall + // clock times come from the same source. + // + // Instead, the time at which the first notification carrying the current + // |last_notified_| time stamp is recorded. Later notifications that find + // the same file modification time only need to be forwarded until wall + // clock has advanced one second from the initial notification. After that + // interval, client code is guaranteed to having seen the current revision + // of the file. + if (base::Time::Now() - first_notification_ > + base::TimeDelta::FromSeconds(1)) { + // Stop further notifications for this |last_modification_| time stamp. + first_notification_ = base::Time(); + } + delegate_->OnFilePathChanged(target_); + } else if (!file_exists && !last_modified_.is_null()) { + last_modified_ = base::Time(); + delegate_->OnFilePathChanged(target_); + } + + // The watch may have been cancelled by the callback. + if (handle_ != INVALID_HANDLE_VALUE) + watcher_.StartWatching(handle_, this); +} + +FilePathWatcherImpl::~FilePathWatcherImpl() { + if (handle_ != INVALID_HANDLE_VALUE) + DestroyWatch(); +} + +// static +bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir, + HANDLE* handle) { + *handle = FindFirstChangeNotification( + dir.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 | + FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY); + if (*handle != INVALID_HANDLE_VALUE) { + // Make sure the handle we got points to an existing directory. It seems + // that windows sometimes hands out watches to direectories that are + // about to go away, but doesn't sent notifications if that happens. + if (!file_util::DirectoryExists(dir)) { + FindCloseChangeNotification(*handle); + *handle = INVALID_HANDLE_VALUE; + } + return true; + } + + // If FindFirstChangeNotification failed because the target directory + // doesn't exist, access is denied (happens if the file is already gone but + // there are still handles open), or the target is not a directory, try the + // immediate parent directory instead. + DWORD error_code = GetLastError(); + if (error_code != ERROR_FILE_NOT_FOUND && + error_code != ERROR_PATH_NOT_FOUND && + error_code != ERROR_ACCESS_DENIED && + error_code != ERROR_DIRECTORY) { + PLOG(ERROR) << "FindFirstChangeNotification failed for " + << dir.value(); + return false; + } + + return true; +} + +bool FilePathWatcherImpl::UpdateWatch() { + if (handle_ != INVALID_HANDLE_VALUE) + DestroyWatch(); + + file_util::FileInfo file_info; + if (file_util::GetFileInfo(target_, &file_info)) { + last_modified_ = file_info.last_modified; + first_notification_ = base::Time::Now(); + } + + // Start at the target and walk up the directory chain until we succesfully + // create a watch handle in |handle_|. |child_dirs| keeps a stack of child + // directories stripped from target, in reverse order. + std::vector<FilePath> child_dirs; + FilePath watched_path(target_); + while (true) { + if (!SetupWatchHandle(watched_path, &handle_)) + return false; + + // Break if a valid handle is returned. Try the parent directory otherwise. + if (handle_ != INVALID_HANDLE_VALUE) + break; + + // Abort if we hit the root directory. + child_dirs.push_back(watched_path.BaseName()); + FilePath parent(watched_path.DirName()); + if (parent == watched_path) { + LOG(ERROR) << "Reached the root directory"; + return false; + } + watched_path = parent; + } + + // At this point, handle_ is valid. However, the bottom-up search that the + // above code performs races against directory creation. So try to walk back + // down and see whether any children appeared in the mean time. + while (!child_dirs.empty()) { + watched_path = watched_path.Append(child_dirs.back()); + child_dirs.pop_back(); + HANDLE temp_handle = INVALID_HANDLE_VALUE; + if (!SetupWatchHandle(watched_path, &temp_handle)) + return false; + if (temp_handle == INVALID_HANDLE_VALUE) + break; + FindCloseChangeNotification(handle_); + handle_ = temp_handle; + } + + return true; +} + +void FilePathWatcherImpl::DestroyWatch() { + watcher_.StopWatching(); + FindCloseChangeNotification(handle_); + handle_ = INVALID_HANDLE_VALUE; +} + +} // namespace + +FilePathWatcher::FilePathWatcher() { + impl_ = new FilePathWatcherImpl(); +} |