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