text: // Copyright (c) 2011 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/files/file_path_watcher.h"
  
  #include "base/bind.h"
  #include "base/files/file.h"
  #include "base/files/file_path.h"
  #include "base/files/file_util.h"
  #include "base/logging.h"
  #include "base/macros.h"
  #include "base/memory/ptr_util.h"
  #include "base/threading/scoped_blocking_call.h"
  #include "base/threading/sequenced_task_runner_handle.h"
  #include "base/time/time.h"
  #include "base/win/object_watcher.h"
  
  #include <windows.h>
  
  namespace base {
  
  namespace {
  
  class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
                              public base::win::ObjectWatcher::Delegate {
   public:
    FilePathWatcherImpl()
        : handle_(INVALID_HANDLE_VALUE),
          recursive_watch_(false) {}
    ~FilePathWatcherImpl() override;
  
    // FilePathWatcher::PlatformDelegate:
    bool Watch(const FilePath& path,
               bool recursive,
               const FilePathWatcher::Callback& callback) override;
    void Cancel() override;
  
    // base::win::ObjectWatcher::Delegate:
    void OnObjectSignaled(HANDLE object) override;
  
   private:
    // Setup a watch handle for directory |dir|. Set |recursive| to true to watch
    // the directory sub trees. 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,
                                 bool recursive,
                                 HANDLE* handle) WARN_UNUSED_RESULT;
  
    // (Re-)Initialize the watch handle.
    bool UpdateWatch() WARN_UNUSED_RESULT;
  
    // Destroy the watch handle.
    void DestroyWatch();
  
    // Callback to notify upon changes.
    FilePathWatcher::Callback callback_;
  
    // Path we're supposed to watch (passed to callback).
    FilePath target_;
  
    // Set to true in the destructor.
    bool* was_deleted_ptr_ = nullptr;
  
    // Handle for FindFirstChangeNotification.
    HANDLE handle_;
  
    // ObjectWatcher to watch handle_ for events.
    base::win::ObjectWatcher watcher_;
  
    // Set to true to watch the sub trees of the specified directory file path.
    bool recursive_watch_;
  
    // Keep track of the last modified time of the file.  We use nulltime
    // to represent the file not existing.
    Time last_modified_;
  
    // The time at which we processed the first notification with the
    // |last_modified_| time stamp.
    Time first_notification_;
  
    DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
  };
  
  FilePathWatcherImpl::~FilePathWatcherImpl() {
    DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
    if (was_deleted_ptr_)
      *was_deleted_ptr_ = true;
  }
  
  bool FilePathWatcherImpl::Watch(const FilePath& path,
                                  bool recursive,
                                  const FilePathWatcher::Callback& callback) {
    DCHECK(target_.value().empty());  // Can only watch one path.
  
    set_task_runner(SequencedTaskRunnerHandle::Get());
    callback_ = callback;
    target_ = path;
    recursive_watch_ = recursive;
  
    File::Info file_info;
    ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
    if (GetFileInfo(target_, &file_info)) {
      last_modified_ = file_info.last_modified;
      first_notification_ = Time::Now();
    }
  
    if (!UpdateWatch())
      return false;
  
    watcher_.StartWatchingOnce(handle_, this);
  
    return true;
  }
  
  void FilePathWatcherImpl::Cancel() {
    if (callback_.is_null()) {
      // Watch was never called, or the |task_runner_| has already quit.
      set_cancelled();
      return;
    }
  
    DCHECK(task_runner()->RunsTasksInCurrentSequence());
    set_cancelled();
  
    if (handle_ != INVALID_HANDLE_VALUE)
      DestroyWatch();
  
    callback_.Reset();
  }
  
  void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
    DCHECK(task_runner()->RunsTasksInCurrentSequence());
    DCHECK_EQ(object, handle_);
    DCHECK(!was_deleted_ptr_);
  
    bool was_deleted = false;
    was_deleted_ptr_ = &was_deleted;
  
    if (!UpdateWatch()) {
      callback_.Run(target_, true /* error */);
      return;
    }
  
    // Check whether the event applies to |target_| and notify the callback.
    File::Info file_info;
    bool file_exists = false;
    {
      ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
      file_exists = GetFileInfo(target_, &file_info);
    }
    if (recursive_watch_) {
      // Only the mtime of |target_| is tracked but in a recursive watch,
      // some other file or directory may have changed so all notifications
      // are passed through. It is possible to figure out which file changed
      // using ReadDirectoryChangesW() instead of FindFirstChangeNotification(),
      // but that function is quite complicated:
      // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html
      callback_.Run(target_, false);
    } else if (file_exists && (last_modified_.is_null() ||
               last_modified_ != file_info.last_modified)) {
      last_modified_ = file_info.last_modified;
      first_notification_ = Time::Now();
      callback_.Run(target_, false);
    } else if (file_exists && last_modified_ == file_info.last_modified &&
               !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 (Time::Now() - first_notification_ > TimeDelta::FromSeconds(1)) {
        // Stop further notifications for this |last_modification_| time stamp.
        first_notification_ = Time();
      }
      callback_.Run(target_, false);
    } else if (!file_exists && !last_modified_.is_null()) {
      last_modified_ = Time();
      callback_.Run(target_, false);
    }
  
    // The watch may have been cancelled by the callback.
    if (!was_deleted) {
      watcher_.StartWatchingOnce(handle_, this);
      was_deleted_ptr_ = nullptr;
    }
  }
  
  // static
  bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
                                             bool recursive,
                                             HANDLE* handle) {
    ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
    *handle = FindFirstChangeNotification(
        dir.value().c_str(),
        recursive,
        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 directories that are
      // about to go away, but doesn't sent notifications if that happens.
      if (!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_SHARING_VIOLATION &&
        error_code != ERROR_DIRECTORY) {
      DPLOG(ERROR) << "FindFirstChangeNotification failed for "
                   << dir.value();
      return false;
    }
  
    return true;
  }
  
  bool FilePathWatcherImpl::UpdateWatch() {
    if (handle_ != INVALID_HANDLE_VALUE)
      DestroyWatch();
  
    ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
  
    // 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, recursive_watch_, &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) {
        DLOG(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, recursive_watch_, &temp_handle))
        return false;
      if (temp_handle == INVALID_HANDLE_VALUE)
        break;
      FindCloseChangeNotification(handle_);
      handle_ = temp_handle;
    }
  
    return true;
  }
  
  void FilePathWatcherImpl::DestroyWatch() {
    watcher_.StopWatching();
  
    ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
    FindCloseChangeNotification(handle_);
    handle_ = INVALID_HANDLE_VALUE;
  }
  
  }  // namespace
  
  FilePathWatcher::FilePathWatcher() {
    sequence_checker_.DetachFromSequence();
    impl_ = std::make_unique<FilePathWatcherImpl>();
  }
  
  }  // namespace base
  