ProfilePolicyConnectorFactory: Refactoring from Profile to BrowserContext.
[chromium-blink-merge.git] / base / files / file_path_watcher_win.cc
blob63e548067139788104cf2ca43976510532937e74
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/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/profiler/scoped_tracker.h"
15 #include "base/time/time.h"
16 #include "base/win/object_watcher.h"
18 namespace base {
20 namespace {
22 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
23 public base::win::ObjectWatcher::Delegate,
24 public MessageLoop::DestructionObserver {
25 public:
26 FilePathWatcherImpl()
27 : handle_(INVALID_HANDLE_VALUE),
28 recursive_watch_(false) {}
30 // FilePathWatcher::PlatformDelegate overrides.
31 virtual bool Watch(const FilePath& path,
32 bool recursive,
33 const FilePathWatcher::Callback& callback) 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) override;
44 private:
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,
52 bool recursive,
53 HANDLE* handle) WARN_UNUSED_RESULT;
55 // (Re-)Initialize the watch handle.
56 bool UpdateWatch() WARN_UNUSED_RESULT;
58 // Destroy the watch handle.
59 void DestroyWatch();
61 // Cleans up and stops observing the |message_loop_| thread.
62 void CancelOnMessageLoopThread() override;
64 // Callback to notify upon changes.
65 FilePathWatcher::Callback callback_;
67 // Path we're supposed to watch (passed to callback).
68 FilePath target_;
70 // Handle for FindFirstChangeNotification.
71 HANDLE handle_;
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 Time last_modified_;
83 // The time at which we processed the first notification with the
84 // |last_modified_| time stamp.
85 Time first_notification_;
87 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
90 bool FilePathWatcherImpl::Watch(const FilePath& path,
91 bool recursive,
92 const FilePathWatcher::Callback& callback) {
93 DCHECK(target_.value().empty()); // Can only watch one path.
95 set_message_loop(MessageLoopProxy::current());
96 callback_ = callback;
97 target_ = path;
98 recursive_watch_ = recursive;
99 MessageLoop::current()->AddDestructionObserver(this);
101 File::Info file_info;
102 if (GetFileInfo(target_, &file_info)) {
103 last_modified_ = file_info.last_modified;
104 first_notification_ = Time::Now();
107 if (!UpdateWatch())
108 return false;
110 watcher_.StartWatching(handle_, this);
112 return true;
115 void FilePathWatcherImpl::Cancel() {
116 if (callback_.is_null()) {
117 // Watch was never called, or the |message_loop_| has already quit.
118 set_cancelled();
119 return;
122 // Switch to the file thread if necessary so we can stop |watcher_|.
123 if (!message_loop()->BelongsToCurrentThread()) {
124 message_loop()->PostTask(FROM_HERE,
125 Bind(&FilePathWatcher::CancelWatch,
126 make_scoped_refptr(this)));
127 } else {
128 CancelOnMessageLoopThread();
132 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
133 DCHECK(message_loop()->BelongsToCurrentThread());
134 set_cancelled();
136 if (handle_ != INVALID_HANDLE_VALUE)
137 DestroyWatch();
139 if (!callback_.is_null()) {
140 MessageLoop::current()->RemoveDestructionObserver(this);
141 callback_.Reset();
145 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
146 CancelOnMessageLoopThread();
149 void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
150 // TODO(vadimt): Remove ScopedTracker below once crbug.com/418183 is fixed.
151 tracked_objects::ScopedTracker tracking_profile(
152 FROM_HERE_WITH_EXPLICIT_FUNCTION(
153 "418183 FilePathWatcherImpl::OnObjectSignaled"));
155 DCHECK(object == handle_);
156 // Make sure we stay alive through the body of this function.
157 scoped_refptr<FilePathWatcherImpl> keep_alive(this);
159 if (!UpdateWatch()) {
160 callback_.Run(target_, true /* error */);
161 return;
164 // Check whether the event applies to |target_| and notify the callback.
165 File::Info file_info;
166 bool file_exists = GetFileInfo(target_, &file_info);
167 if (recursive_watch_) {
168 // Only the mtime of |target_| is tracked but in a recursive watch,
169 // some other file or directory may have changed so all notifications
170 // are passed through. It is possible to figure out which file changed
171 // using ReadDirectoryChangesW() instead of FindFirstChangeNotification(),
172 // but that function is quite complicated:
173 // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html
174 callback_.Run(target_, false);
175 } else if (file_exists && (last_modified_.is_null() ||
176 last_modified_ != file_info.last_modified)) {
177 last_modified_ = file_info.last_modified;
178 first_notification_ = Time::Now();
179 callback_.Run(target_, false);
180 } else if (file_exists && last_modified_ == file_info.last_modified &&
181 !first_notification_.is_null()) {
182 // The target's last modification time is equal to what's on record. This
183 // means that either an unrelated event occurred, or the target changed
184 // again (file modification times only have a resolution of 1s). Comparing
185 // file modification times against the wall clock is not reliable to find
186 // out whether the change is recent, since this code might just run too
187 // late. Moreover, there's no guarantee that file modification time and wall
188 // clock times come from the same source.
190 // Instead, the time at which the first notification carrying the current
191 // |last_notified_| time stamp is recorded. Later notifications that find
192 // the same file modification time only need to be forwarded until wall
193 // clock has advanced one second from the initial notification. After that
194 // interval, client code is guaranteed to having seen the current revision
195 // of the file.
196 if (Time::Now() - first_notification_ > TimeDelta::FromSeconds(1)) {
197 // Stop further notifications for this |last_modification_| time stamp.
198 first_notification_ = Time();
200 callback_.Run(target_, false);
201 } else if (!file_exists && !last_modified_.is_null()) {
202 last_modified_ = Time();
203 callback_.Run(target_, false);
206 // The watch may have been cancelled by the callback.
207 if (handle_ != INVALID_HANDLE_VALUE)
208 watcher_.StartWatching(handle_, this);
211 // static
212 bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
213 bool recursive,
214 HANDLE* handle) {
215 *handle = FindFirstChangeNotification(
216 dir.value().c_str(),
217 recursive,
218 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
219 FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
220 FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY);
221 if (*handle != INVALID_HANDLE_VALUE) {
222 // Make sure the handle we got points to an existing directory. It seems
223 // that windows sometimes hands out watches to directories that are
224 // about to go away, but doesn't sent notifications if that happens.
225 if (!DirectoryExists(dir)) {
226 FindCloseChangeNotification(*handle);
227 *handle = INVALID_HANDLE_VALUE;
229 return true;
232 // If FindFirstChangeNotification failed because the target directory
233 // doesn't exist, access is denied (happens if the file is already gone but
234 // there are still handles open), or the target is not a directory, try the
235 // immediate parent directory instead.
236 DWORD error_code = GetLastError();
237 if (error_code != ERROR_FILE_NOT_FOUND &&
238 error_code != ERROR_PATH_NOT_FOUND &&
239 error_code != ERROR_ACCESS_DENIED &&
240 error_code != ERROR_SHARING_VIOLATION &&
241 error_code != ERROR_DIRECTORY) {
242 DPLOG(ERROR) << "FindFirstChangeNotification failed for "
243 << dir.value();
244 return false;
247 return true;
250 bool FilePathWatcherImpl::UpdateWatch() {
251 if (handle_ != INVALID_HANDLE_VALUE)
252 DestroyWatch();
254 // Start at the target and walk up the directory chain until we succesfully
255 // create a watch handle in |handle_|. |child_dirs| keeps a stack of child
256 // directories stripped from target, in reverse order.
257 std::vector<FilePath> child_dirs;
258 FilePath watched_path(target_);
259 while (true) {
260 if (!SetupWatchHandle(watched_path, recursive_watch_, &handle_))
261 return false;
263 // Break if a valid handle is returned. Try the parent directory otherwise.
264 if (handle_ != INVALID_HANDLE_VALUE)
265 break;
267 // Abort if we hit the root directory.
268 child_dirs.push_back(watched_path.BaseName());
269 FilePath parent(watched_path.DirName());
270 if (parent == watched_path) {
271 DLOG(ERROR) << "Reached the root directory";
272 return false;
274 watched_path = parent;
277 // At this point, handle_ is valid. However, the bottom-up search that the
278 // above code performs races against directory creation. So try to walk back
279 // down and see whether any children appeared in the mean time.
280 while (!child_dirs.empty()) {
281 watched_path = watched_path.Append(child_dirs.back());
282 child_dirs.pop_back();
283 HANDLE temp_handle = INVALID_HANDLE_VALUE;
284 if (!SetupWatchHandle(watched_path, recursive_watch_, &temp_handle))
285 return false;
286 if (temp_handle == INVALID_HANDLE_VALUE)
287 break;
288 FindCloseChangeNotification(handle_);
289 handle_ = temp_handle;
292 return true;
295 void FilePathWatcherImpl::DestroyWatch() {
296 watcher_.StopWatching();
297 FindCloseChangeNotification(handle_);
298 handle_ = INVALID_HANDLE_VALUE;
301 } // namespace
303 FilePathWatcher::FilePathWatcher() {
304 impl_ = new FilePathWatcherImpl();
307 } // namespace base