Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(225)

Side by Side Diff: base/files/file_path_watcher_win.cc

Issue 1446363003: Deleted OS_WIN and all Windows specific files from base. (Closed) Base URL: https://github.com/domokit/mojo.git@base_tests
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « base/files/file_path_watcher_unittest.cc ('k') | base/files/file_proxy_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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/thread_task_runner_handle.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 bool Watch(const FilePath& path,
31 bool recursive,
32 const FilePathWatcher::Callback& callback) override;
33 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 void WillDestroyCurrentMessageLoop() override;
39
40 // Callback from MessageLoopForIO.
41 void OnObjectSignaled(HANDLE object) override;
42
43 private:
44 ~FilePathWatcherImpl() override {}
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 |task_runner_| 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_task_runner(ThreadTaskRunnerHandle::Get());
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 |task_runner_| 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 (!task_runner()->BelongsToCurrentThread()) {
123 task_runner()->PostTask(FROM_HERE, Bind(&FilePathWatcher::CancelWatch,
124 make_scoped_refptr(this)));
125 } else {
126 CancelOnMessageLoopThread();
127 }
128 }
129
130 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
131 DCHECK(task_runner()->BelongsToCurrentThread());
132 set_cancelled();
133
134 if (handle_ != INVALID_HANDLE_VALUE)
135 DestroyWatch();
136
137 if (!callback_.is_null()) {
138 MessageLoop::current()->RemoveDestructionObserver(this);
139 callback_.Reset();
140 }
141 }
142
143 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
144 CancelOnMessageLoopThread();
145 }
146
147 void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
148 DCHECK(object == handle_);
149 // Make sure we stay alive through the body of this function.
150 scoped_refptr<FilePathWatcherImpl> keep_alive(this);
151
152 if (!UpdateWatch()) {
153 callback_.Run(target_, true /* error */);
154 return;
155 }
156
157 // Check whether the event applies to |target_| and notify the callback.
158 File::Info file_info;
159 bool file_exists = GetFileInfo(target_, &file_info);
160 if (recursive_watch_) {
161 // Only the mtime of |target_| is tracked but in a recursive watch,
162 // some other file or directory may have changed so all notifications
163 // are passed through. It is possible to figure out which file changed
164 // using ReadDirectoryChangesW() instead of FindFirstChangeNotification(),
165 // but that function is quite complicated:
166 // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw. html
167 callback_.Run(target_, false);
168 } else if (file_exists && (last_modified_.is_null() ||
169 last_modified_ != file_info.last_modified)) {
170 last_modified_ = file_info.last_modified;
171 first_notification_ = Time::Now();
172 callback_.Run(target_, false);
173 } else if (file_exists && last_modified_ == file_info.last_modified &&
174 !first_notification_.is_null()) {
175 // The target's last modification time is equal to what's on record. This
176 // means that either an unrelated event occurred, or the target changed
177 // again (file modification times only have a resolution of 1s). Comparing
178 // file modification times against the wall clock is not reliable to find
179 // out whether the change is recent, since this code might just run too
180 // late. Moreover, there's no guarantee that file modification time and wall
181 // clock times come from the same source.
182 //
183 // Instead, the time at which the first notification carrying the current
184 // |last_notified_| time stamp is recorded. Later notifications that find
185 // the same file modification time only need to be forwarded until wall
186 // clock has advanced one second from the initial notification. After that
187 // interval, client code is guaranteed to having seen the current revision
188 // of the file.
189 if (Time::Now() - first_notification_ > TimeDelta::FromSeconds(1)) {
190 // Stop further notifications for this |last_modification_| time stamp.
191 first_notification_ = Time();
192 }
193 callback_.Run(target_, false);
194 } else if (!file_exists && !last_modified_.is_null()) {
195 last_modified_ = Time();
196 callback_.Run(target_, false);
197 }
198
199 // The watch may have been cancelled by the callback.
200 if (handle_ != INVALID_HANDLE_VALUE)
201 watcher_.StartWatching(handle_, this);
202 }
203
204 // static
205 bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
206 bool recursive,
207 HANDLE* handle) {
208 *handle = FindFirstChangeNotification(
209 dir.value().c_str(),
210 recursive,
211 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
212 FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
213 FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY);
214 if (*handle != INVALID_HANDLE_VALUE) {
215 // Make sure the handle we got points to an existing directory. It seems
216 // that windows sometimes hands out watches to directories that are
217 // about to go away, but doesn't sent notifications if that happens.
218 if (!DirectoryExists(dir)) {
219 FindCloseChangeNotification(*handle);
220 *handle = INVALID_HANDLE_VALUE;
221 }
222 return true;
223 }
224
225 // If FindFirstChangeNotification failed because the target directory
226 // doesn't exist, access is denied (happens if the file is already gone but
227 // there are still handles open), or the target is not a directory, try the
228 // immediate parent directory instead.
229 DWORD error_code = GetLastError();
230 if (error_code != ERROR_FILE_NOT_FOUND &&
231 error_code != ERROR_PATH_NOT_FOUND &&
232 error_code != ERROR_ACCESS_DENIED &&
233 error_code != ERROR_SHARING_VIOLATION &&
234 error_code != ERROR_DIRECTORY) {
235 DPLOG(ERROR) << "FindFirstChangeNotification failed for "
236 << dir.value();
237 return false;
238 }
239
240 return true;
241 }
242
243 bool FilePathWatcherImpl::UpdateWatch() {
244 if (handle_ != INVALID_HANDLE_VALUE)
245 DestroyWatch();
246
247 // Start at the target and walk up the directory chain until we succesfully
248 // create a watch handle in |handle_|. |child_dirs| keeps a stack of child
249 // directories stripped from target, in reverse order.
250 std::vector<FilePath> child_dirs;
251 FilePath watched_path(target_);
252 while (true) {
253 if (!SetupWatchHandle(watched_path, recursive_watch_, &handle_))
254 return false;
255
256 // Break if a valid handle is returned. Try the parent directory otherwise.
257 if (handle_ != INVALID_HANDLE_VALUE)
258 break;
259
260 // Abort if we hit the root directory.
261 child_dirs.push_back(watched_path.BaseName());
262 FilePath parent(watched_path.DirName());
263 if (parent == watched_path) {
264 DLOG(ERROR) << "Reached the root directory";
265 return false;
266 }
267 watched_path = parent;
268 }
269
270 // At this point, handle_ is valid. However, the bottom-up search that the
271 // above code performs races against directory creation. So try to walk back
272 // down and see whether any children appeared in the mean time.
273 while (!child_dirs.empty()) {
274 watched_path = watched_path.Append(child_dirs.back());
275 child_dirs.pop_back();
276 HANDLE temp_handle = INVALID_HANDLE_VALUE;
277 if (!SetupWatchHandle(watched_path, recursive_watch_, &temp_handle))
278 return false;
279 if (temp_handle == INVALID_HANDLE_VALUE)
280 break;
281 FindCloseChangeNotification(handle_);
282 handle_ = temp_handle;
283 }
284
285 return true;
286 }
287
288 void FilePathWatcherImpl::DestroyWatch() {
289 watcher_.StopWatching();
290 FindCloseChangeNotification(handle_);
291 handle_ = INVALID_HANDLE_VALUE;
292 }
293
294 } // namespace
295
296 FilePathWatcher::FilePathWatcher() {
297 impl_ = new FilePathWatcherImpl();
298 }
299
300 } // namespace base
OLDNEW
« no previous file with comments | « base/files/file_path_watcher_unittest.cc ('k') | base/files/file_proxy_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698