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

Side by Side Diff: content/common/file_path_watcher/file_path_watcher_win.cc

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

Powered by Google App Engine
This is Rietveld 408576698