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.
5 #include "base/files/file_path_watcher.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"
21 class FilePathWatcherImpl
: public FilePathWatcher::PlatformDelegate
,
22 public base::win::ObjectWatcher::Delegate
,
23 public MessageLoop::DestructionObserver
{
26 : handle_(INVALID_HANDLE_VALUE
),
27 recursive_watch_(false) {}
29 // FilePathWatcher::PlatformDelegate overrides.
30 virtual bool Watch(const FilePath
& path
,
32 const FilePathWatcher::Callback
& callback
) override
;
33 virtual void Cancel() override
;
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
;
40 // Callback from MessageLoopForIO.
41 virtual void OnObjectSignaled(HANDLE object
) override
;
44 virtual ~FilePathWatcherImpl() {}
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
,
52 HANDLE
* handle
) WARN_UNUSED_RESULT
;
54 // (Re-)Initialize the watch handle.
55 bool UpdateWatch() WARN_UNUSED_RESULT
;
57 // Destroy the watch handle.
60 // Cleans up and stops observing the |message_loop_| thread.
61 void CancelOnMessageLoopThread() override
;
63 // Callback to notify upon changes.
64 FilePathWatcher::Callback callback_
;
66 // Path we're supposed to watch (passed to callback).
69 // Handle for FindFirstChangeNotification.
72 // ObjectWatcher to watch handle_ for events.
73 base::win::ObjectWatcher watcher_
;
75 // Set to true to watch the sub trees of the specified directory file path.
76 bool recursive_watch_
;
78 // Keep track of the last modified time of the file. We use nulltime
79 // to represent the file not existing.
82 // The time at which we processed the first notification with the
83 // |last_modified_| time stamp.
84 Time first_notification_
;
86 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl
);
89 bool FilePathWatcherImpl::Watch(const FilePath
& path
,
91 const FilePathWatcher::Callback
& callback
) {
92 DCHECK(target_
.value().empty()); // Can only watch one path.
94 set_message_loop(MessageLoopProxy::current());
97 recursive_watch_
= recursive
;
98 MessageLoop::current()->AddDestructionObserver(this);
100 File::Info file_info
;
101 if (GetFileInfo(target_
, &file_info
)) {
102 last_modified_
= file_info
.last_modified
;
103 first_notification_
= Time::Now();
109 watcher_
.StartWatching(handle_
, this);
114 void FilePathWatcherImpl::Cancel() {
115 if (callback_
.is_null()) {
116 // Watch was never called, or the |message_loop_| has already quit.
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)));
127 CancelOnMessageLoopThread();
131 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
132 DCHECK(message_loop()->BelongsToCurrentThread());
135 if (handle_
!= INVALID_HANDLE_VALUE
)
138 if (!callback_
.is_null()) {
139 MessageLoop::current()->RemoveDestructionObserver(this);
144 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
145 CancelOnMessageLoopThread();
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);
153 if (!UpdateWatch()) {
154 callback_
.Run(target_
, true /* error */);
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.
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
190 if (Time::Now() - first_notification_
> TimeDelta::FromSeconds(1)) {
191 // Stop further notifications for this |last_modification_| time stamp.
192 first_notification_
= Time();
194 callback_
.Run(target_
, false);
195 } else if (!file_exists
&& !last_modified_
.is_null()) {
196 last_modified_
= Time();
197 callback_
.Run(target_
, false);
200 // The watch may have been cancelled by the callback.
201 if (handle_
!= INVALID_HANDLE_VALUE
)
202 watcher_
.StartWatching(handle_
, this);
206 bool FilePathWatcherImpl::SetupWatchHandle(const FilePath
& dir
,
209 *handle
= FindFirstChangeNotification(
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
;
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 "
244 bool FilePathWatcherImpl::UpdateWatch() {
245 if (handle_
!= INVALID_HANDLE_VALUE
)
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_
);
254 if (!SetupWatchHandle(watched_path
, recursive_watch_
, &handle_
))
257 // Break if a valid handle is returned. Try the parent directory otherwise.
258 if (handle_
!= INVALID_HANDLE_VALUE
)
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";
268 watched_path
= parent
;
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
))
280 if (temp_handle
== INVALID_HANDLE_VALUE
)
282 FindCloseChangeNotification(handle_
);
283 handle_
= temp_handle
;
289 void FilePathWatcherImpl::DestroyWatch() {
290 watcher_
.StopWatching();
291 FindCloseChangeNotification(handle_
);
292 handle_
= INVALID_HANDLE_VALUE
;
297 FilePathWatcher::FilePathWatcher() {
298 impl_
= new FilePathWatcherImpl();