Home | History | Annotate | Download | only in files
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/files/file_path_watcher.h"
      6 
      7 #include "base/file_path.h"
      8 #include "base/file_util.h"
      9 #include "base/logging.h"
     10 #include "base/memory/ref_counted.h"
     11 #include "base/message_loop_proxy.h"
     12 #include "base/time.h"
     13 #include "base/win/object_watcher.h"
     14 
     15 namespace base {
     16 namespace files {
     17 
     18 namespace {
     19 
     20 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
     21                             public base::win::ObjectWatcher::Delegate,
     22                             public MessageLoop::DestructionObserver {
     23  public:
     24   FilePathWatcherImpl() : delegate_(NULL), handle_(INVALID_HANDLE_VALUE) {}
     25 
     26   // FilePathWatcher::PlatformDelegate overrides.
     27   virtual bool Watch(const FilePath& path,
     28                      FilePathWatcher::Delegate* delegate) OVERRIDE;
     29   virtual void Cancel() OVERRIDE;
     30 
     31   // Deletion of the FilePathWatcher will call Cancel() to dispose of this
     32   // object in the right thread. This also observes destruction of the required
     33   // cleanup thread, in case it quits before Cancel() is called.
     34   virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
     35 
     36   // Callback from MessageLoopForIO.
     37   virtual void OnObjectSignaled(HANDLE object);
     38 
     39  private:
     40   virtual ~FilePathWatcherImpl() {}
     41 
     42   // Setup a watch handle for directory |dir|. Returns true if no fatal error
     43   // occurs. |handle| will receive the handle value if |dir| is watchable,
     44   // otherwise INVALID_HANDLE_VALUE.
     45   static bool SetupWatchHandle(const FilePath& dir, HANDLE* handle)
     46       WARN_UNUSED_RESULT;
     47 
     48   // (Re-)Initialize the watch handle.
     49   bool UpdateWatch() WARN_UNUSED_RESULT;
     50 
     51   // Destroy the watch handle.
     52   void DestroyWatch();
     53 
     54   // Cleans up and stops observing the |message_loop_| thread.
     55   void CancelOnMessageLoopThread() OVERRIDE;
     56 
     57   // Delegate to notify upon changes.
     58   scoped_refptr<FilePathWatcher::Delegate> delegate_;
     59 
     60   // Path we're supposed to watch (passed to delegate).
     61   FilePath target_;
     62 
     63   // Handle for FindFirstChangeNotification.
     64   HANDLE handle_;
     65 
     66   // ObjectWatcher to watch handle_ for events.
     67   base::win::ObjectWatcher watcher_;
     68 
     69   // Keep track of the last modified time of the file.  We use nulltime
     70   // to represent the file not existing.
     71   base::Time last_modified_;
     72 
     73   // The time at which we processed the first notification with the
     74   // |last_modified_| time stamp.
     75   base::Time first_notification_;
     76 
     77   DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
     78 };
     79 
     80 bool FilePathWatcherImpl::Watch(const FilePath& path,
     81                                 FilePathWatcher::Delegate* delegate) {
     82   DCHECK(target_.value().empty());  // Can only watch one path.
     83 
     84   set_message_loop(base::MessageLoopProxy::CreateForCurrentThread());
     85   delegate_ = delegate;
     86   target_ = path;
     87   MessageLoop::current()->AddDestructionObserver(this);
     88 
     89   if (!UpdateWatch())
     90     return false;
     91 
     92   watcher_.StartWatching(handle_, this);
     93 
     94   return true;
     95 }
     96 
     97 void FilePathWatcherImpl::Cancel() {
     98   if (!delegate_) {
     99     // Watch was never called, or the |message_loop_| has already quit.
    100     set_cancelled();
    101     return;
    102   }
    103 
    104   // Switch to the file thread if necessary so we can stop |watcher_|.
    105   if (!message_loop()->BelongsToCurrentThread()) {
    106     message_loop()->PostTask(FROM_HERE,
    107                              new FilePathWatcher::CancelTask(this));
    108   } else {
    109     CancelOnMessageLoopThread();
    110   }
    111 }
    112 
    113 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
    114   set_cancelled();
    115 
    116   if (handle_ != INVALID_HANDLE_VALUE)
    117     DestroyWatch();
    118 
    119   if (delegate_) {
    120     MessageLoop::current()->RemoveDestructionObserver(this);
    121     delegate_ = NULL;
    122   }
    123 }
    124 
    125 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
    126   CancelOnMessageLoopThread();
    127 }
    128 
    129 void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
    130   DCHECK(object == handle_);
    131   // Make sure we stay alive through the body of this function.
    132   scoped_refptr<FilePathWatcherImpl> keep_alive(this);
    133 
    134   if (!UpdateWatch()) {
    135     delegate_->OnFilePathError(target_);
    136     return;
    137   }
    138 
    139   // Check whether the event applies to |target_| and notify the delegate.
    140   base::PlatformFileInfo file_info;
    141   bool file_exists = file_util::GetFileInfo(target_, &file_info);
    142   if (file_exists && (last_modified_.is_null() ||
    143       last_modified_ != file_info.last_modified)) {
    144     last_modified_ = file_info.last_modified;
    145     first_notification_ = base::Time::Now();
    146     delegate_->OnFilePathChanged(target_);
    147   } else if (file_exists && !first_notification_.is_null()) {
    148     // The target's last modification time is equal to what's on record. This
    149     // means that either an unrelated event occurred, or the target changed
    150     // again (file modification times only have a resolution of 1s). Comparing
    151     // file modification times against the wall clock is not reliable to find
    152     // out whether the change is recent, since this code might just run too
    153     // late. Moreover, there's no guarantee that file modification time and wall
    154     // clock times come from the same source.
    155     //
    156     // Instead, the time at which the first notification carrying the current
    157     // |last_notified_| time stamp is recorded. Later notifications that find
    158     // the same file modification time only need to be forwarded until wall
    159     // clock has advanced one second from the initial notification. After that
    160     // interval, client code is guaranteed to having seen the current revision
    161     // of the file.
    162     if (base::Time::Now() - first_notification_ >
    163         base::TimeDelta::FromSeconds(1)) {
    164       // Stop further notifications for this |last_modification_| time stamp.
    165       first_notification_ = base::Time();
    166     }
    167     delegate_->OnFilePathChanged(target_);
    168   } else if (!file_exists && !last_modified_.is_null()) {
    169     last_modified_ = base::Time();
    170     delegate_->OnFilePathChanged(target_);
    171   }
    172 
    173   // The watch may have been cancelled by the callback.
    174   if (handle_ != INVALID_HANDLE_VALUE)
    175     watcher_.StartWatching(handle_, this);
    176 }
    177 
    178 // static
    179 bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
    180                                            HANDLE* handle) {
    181   *handle = FindFirstChangeNotification(
    182       dir.value().c_str(),
    183       false,  // Don't watch subtrees
    184       FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
    185       FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
    186       FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY);
    187   if (*handle != INVALID_HANDLE_VALUE) {
    188     // Make sure the handle we got points to an existing directory. It seems
    189     // that windows sometimes hands out watches to direectories that are
    190     // about to go away, but doesn't sent notifications if that happens.
    191     if (!file_util::DirectoryExists(dir)) {
    192       FindCloseChangeNotification(*handle);
    193       *handle = INVALID_HANDLE_VALUE;
    194     }
    195     return true;
    196   }
    197 
    198   // If FindFirstChangeNotification failed because the target directory
    199   // doesn't exist, access is denied (happens if the file is already gone but
    200   // there are still handles open), or the target is not a directory, try the
    201   // immediate parent directory instead.
    202   DWORD error_code = GetLastError();
    203   if (error_code != ERROR_FILE_NOT_FOUND &&
    204       error_code != ERROR_PATH_NOT_FOUND &&
    205       error_code != ERROR_ACCESS_DENIED &&
    206       error_code != ERROR_SHARING_VIOLATION &&
    207       error_code != ERROR_DIRECTORY) {
    208     using ::operator<<; // Pick the right operator<< below.
    209     PLOG(ERROR) << "FindFirstChangeNotification failed for "
    210                 << dir.value();
    211     return false;
    212   }
    213 
    214   return true;
    215 }
    216 
    217 bool FilePathWatcherImpl::UpdateWatch() {
    218   if (handle_ != INVALID_HANDLE_VALUE)
    219     DestroyWatch();
    220 
    221   base::PlatformFileInfo file_info;
    222   if (file_util::GetFileInfo(target_, &file_info)) {
    223     last_modified_ = file_info.last_modified;
    224     first_notification_ = base::Time::Now();
    225   }
    226 
    227   // Start at the target and walk up the directory chain until we succesfully
    228   // create a watch handle in |handle_|. |child_dirs| keeps a stack of child
    229   // directories stripped from target, in reverse order.
    230   std::vector<FilePath> child_dirs;
    231   FilePath watched_path(target_);
    232   while (true) {
    233     if (!SetupWatchHandle(watched_path, &handle_))
    234       return false;
    235 
    236     // Break if a valid handle is returned. Try the parent directory otherwise.
    237     if (handle_ != INVALID_HANDLE_VALUE)
    238       break;
    239 
    240     // Abort if we hit the root directory.
    241     child_dirs.push_back(watched_path.BaseName());
    242     FilePath parent(watched_path.DirName());
    243     if (parent == watched_path) {
    244       LOG(ERROR) << "Reached the root directory";
    245       return false;
    246     }
    247     watched_path = parent;
    248   }
    249 
    250   // At this point, handle_ is valid. However, the bottom-up search that the
    251   // above code performs races against directory creation. So try to walk back
    252   // down and see whether any children appeared in the mean time.
    253   while (!child_dirs.empty()) {
    254     watched_path = watched_path.Append(child_dirs.back());
    255     child_dirs.pop_back();
    256     HANDLE temp_handle = INVALID_HANDLE_VALUE;
    257     if (!SetupWatchHandle(watched_path, &temp_handle))
    258       return false;
    259     if (temp_handle == INVALID_HANDLE_VALUE)
    260       break;
    261     FindCloseChangeNotification(handle_);
    262     handle_ = temp_handle;
    263   }
    264 
    265   return true;
    266 }
    267 
    268 void FilePathWatcherImpl::DestroyWatch() {
    269   watcher_.StopWatching();
    270   FindCloseChangeNotification(handle_);
    271   handle_ = INVALID_HANDLE_VALUE;
    272 }
    273 
    274 }  // namespace
    275 
    276 FilePathWatcher::FilePathWatcher() {
    277   impl_ = new FilePathWatcherImpl();
    278 }
    279 
    280 }  // namespace files
    281 }  // namespace base
    282