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/file_path.h"
9 #include "base/file_util.h"
10 #include "base/logging.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/message_loop_proxy.h"
13 #include "base/time.h"
14 #include "base/win/object_watcher.h"
21 class FilePathWatcherImpl
: public FilePathWatcher::PlatformDelegate
,
22 public base::win::ObjectWatcher::Delegate
,
23 public MessageLoop::DestructionObserver
{
27 handle_(INVALID_HANDLE_VALUE
),
28 recursive_watch_(false) {}
30 // FilePathWatcher::PlatformDelegate overrides.
31 virtual bool Watch(const FilePath
& path
,
33 FilePathWatcher::Delegate
* delegate
) OVERRIDE
;
34 virtual void Cancel() OVERRIDE
;
36 // Deletion of the FilePathWatcher will call Cancel() to dispose of this
37 // object in the right thread. This also observes destruction of the required
38 // cleanup thread, in case it quits before Cancel() is called.
39 virtual void WillDestroyCurrentMessageLoop() OVERRIDE
;
41 // Callback from MessageLoopForIO.
42 virtual void OnObjectSignaled(HANDLE object
);
45 virtual ~FilePathWatcherImpl() {}
47 // Setup a watch handle for directory |dir|. Set |recursive| to true to watch
48 // the directory sub trees. Returns true if no fatal error occurs. |handle|
49 // will receive the handle value if |dir| is watchable, otherwise
50 // INVALID_HANDLE_VALUE.
51 static bool SetupWatchHandle(const FilePath
& dir
,
53 HANDLE
* handle
) WARN_UNUSED_RESULT
;
55 // (Re-)Initialize the watch handle.
56 bool UpdateWatch() WARN_UNUSED_RESULT
;
58 // Destroy the watch handle.
61 // Cleans up and stops observing the |message_loop_| thread.
62 void CancelOnMessageLoopThread() OVERRIDE
;
64 // Delegate to notify upon changes.
65 scoped_refptr
<FilePathWatcher::Delegate
> delegate_
;
67 // Path we're supposed to watch (passed to delegate).
70 // Handle for FindFirstChangeNotification.
73 // ObjectWatcher to watch handle_ for events.
74 base::win::ObjectWatcher watcher_
;
76 // Set to true to watch the sub trees of the specified directory file path.
77 bool recursive_watch_
;
79 // Keep track of the last modified time of the file. We use nulltime
80 // to represent the file not existing.
81 base::Time last_modified_
;
83 // The time at which we processed the first notification with the
84 // |last_modified_| time stamp.
85 base::Time first_notification_
;
87 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl
);
90 bool FilePathWatcherImpl::Watch(const FilePath
& path
,
92 FilePathWatcher::Delegate
* delegate
) {
93 DCHECK(target_
.value().empty()); // Can only watch one path.
95 set_message_loop(base::MessageLoopProxy::current());
98 recursive_watch_
= recursive
;
99 MessageLoop::current()->AddDestructionObserver(this);
104 watcher_
.StartWatching(handle_
, this);
109 void FilePathWatcherImpl::Cancel() {
111 // Watch was never called, or the |message_loop_| has already quit.
116 // Switch to the file thread if necessary so we can stop |watcher_|.
117 if (!message_loop()->BelongsToCurrentThread()) {
118 message_loop()->PostTask(FROM_HERE
,
119 base::Bind(&FilePathWatcher::CancelWatch
,
120 make_scoped_refptr(this)));
122 CancelOnMessageLoopThread();
126 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
129 if (handle_
!= INVALID_HANDLE_VALUE
)
133 MessageLoop::current()->RemoveDestructionObserver(this);
138 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
139 CancelOnMessageLoopThread();
142 void FilePathWatcherImpl::OnObjectSignaled(HANDLE object
) {
143 DCHECK(object
== handle_
);
144 // Make sure we stay alive through the body of this function.
145 scoped_refptr
<FilePathWatcherImpl
> keep_alive(this);
147 if (!UpdateWatch()) {
148 delegate_
->OnFilePathError(target_
);
152 // Check whether the event applies to |target_| and notify the delegate.
153 base::PlatformFileInfo file_info
;
154 bool file_exists
= file_util::GetFileInfo(target_
, &file_info
);
155 if (file_exists
&& (last_modified_
.is_null() ||
156 last_modified_
!= file_info
.last_modified
)) {
157 last_modified_
= file_info
.last_modified
;
158 first_notification_
= base::Time::Now();
159 delegate_
->OnFilePathChanged(target_
);
160 } else if (file_exists
&& !first_notification_
.is_null()) {
161 // The target's last modification time is equal to what's on record. This
162 // means that either an unrelated event occurred, or the target changed
163 // again (file modification times only have a resolution of 1s). Comparing
164 // file modification times against the wall clock is not reliable to find
165 // out whether the change is recent, since this code might just run too
166 // late. Moreover, there's no guarantee that file modification time and wall
167 // clock times come from the same source.
169 // Instead, the time at which the first notification carrying the current
170 // |last_notified_| time stamp is recorded. Later notifications that find
171 // the same file modification time only need to be forwarded until wall
172 // clock has advanced one second from the initial notification. After that
173 // interval, client code is guaranteed to having seen the current revision
175 if (base::Time::Now() - first_notification_
>
176 base::TimeDelta::FromSeconds(1)) {
177 // Stop further notifications for this |last_modification_| time stamp.
178 first_notification_
= base::Time();
180 delegate_
->OnFilePathChanged(target_
);
181 } else if (!file_exists
&& !last_modified_
.is_null()) {
182 last_modified_
= base::Time();
183 delegate_
->OnFilePathChanged(target_
);
186 // The watch may have been cancelled by the callback.
187 if (handle_
!= INVALID_HANDLE_VALUE
)
188 watcher_
.StartWatching(handle_
, this);
192 bool FilePathWatcherImpl::SetupWatchHandle(const FilePath
& dir
,
195 *handle
= FindFirstChangeNotification(
198 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_SIZE
|
199 FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_DIR_NAME
|
200 FILE_NOTIFY_CHANGE_ATTRIBUTES
| FILE_NOTIFY_CHANGE_SECURITY
);
201 if (*handle
!= INVALID_HANDLE_VALUE
) {
202 // Make sure the handle we got points to an existing directory. It seems
203 // that windows sometimes hands out watches to directories that are
204 // about to go away, but doesn't sent notifications if that happens.
205 if (!file_util::DirectoryExists(dir
)) {
206 FindCloseChangeNotification(*handle
);
207 *handle
= INVALID_HANDLE_VALUE
;
212 // If FindFirstChangeNotification failed because the target directory
213 // doesn't exist, access is denied (happens if the file is already gone but
214 // there are still handles open), or the target is not a directory, try the
215 // immediate parent directory instead.
216 DWORD error_code
= GetLastError();
217 if (error_code
!= ERROR_FILE_NOT_FOUND
&&
218 error_code
!= ERROR_PATH_NOT_FOUND
&&
219 error_code
!= ERROR_ACCESS_DENIED
&&
220 error_code
!= ERROR_SHARING_VIOLATION
&&
221 error_code
!= ERROR_DIRECTORY
) {
222 using ::operator<<; // Pick the right operator<< below.
223 DPLOG(ERROR
) << "FindFirstChangeNotification failed for "
231 bool FilePathWatcherImpl::UpdateWatch() {
232 if (handle_
!= INVALID_HANDLE_VALUE
)
235 base::PlatformFileInfo file_info
;
236 if (file_util::GetFileInfo(target_
, &file_info
)) {
237 last_modified_
= file_info
.last_modified
;
238 first_notification_
= base::Time::Now();
241 // Start at the target and walk up the directory chain until we succesfully
242 // create a watch handle in |handle_|. |child_dirs| keeps a stack of child
243 // directories stripped from target, in reverse order.
244 std::vector
<FilePath
> child_dirs
;
245 FilePath
watched_path(target_
);
247 if (!SetupWatchHandle(watched_path
, recursive_watch_
, &handle_
))
250 // Break if a valid handle is returned. Try the parent directory otherwise.
251 if (handle_
!= INVALID_HANDLE_VALUE
)
254 // Abort if we hit the root directory.
255 child_dirs
.push_back(watched_path
.BaseName());
256 FilePath
parent(watched_path
.DirName());
257 if (parent
== watched_path
) {
258 DLOG(ERROR
) << "Reached the root directory";
261 watched_path
= parent
;
264 // At this point, handle_ is valid. However, the bottom-up search that the
265 // above code performs races against directory creation. So try to walk back
266 // down and see whether any children appeared in the mean time.
267 while (!child_dirs
.empty()) {
268 watched_path
= watched_path
.Append(child_dirs
.back());
269 child_dirs
.pop_back();
270 HANDLE temp_handle
= INVALID_HANDLE_VALUE
;
271 if (!SetupWatchHandle(watched_path
, recursive_watch_
, &temp_handle
))
273 if (temp_handle
== INVALID_HANDLE_VALUE
)
275 FindCloseChangeNotification(handle_
);
276 handle_
= temp_handle
;
282 void FilePathWatcherImpl::DestroyWatch() {
283 watcher_
.StopWatching();
284 FindCloseChangeNotification(handle_
);
285 handle_
= INVALID_HANDLE_VALUE
;
290 FilePathWatcher::FilePathWatcher() {
291 impl_
= new FilePathWatcherImpl();