summaryrefslogtreecommitdiffstats
path: root/chrome/browser/file_path_watcher_win.cc
diff options
context:
space:
mode:
authormnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-19 10:35:46 +0000
committermnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-19 10:35:46 +0000
commit5199d74e9d09b9a0b3ab9f600fcc96a54a5902ba (patch)
tree6f9cd0f89fca60d9c3ecb8f53a740f051428e862 /chrome/browser/file_path_watcher_win.cc
parentbdb541d643bce8fa366b8102c579ff84f42e40be (diff)
downloadchromium_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.cc243
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();
+}