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