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

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

Issue 283423003: Use FSEvents for recursive file watch on Mac (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix win compile Created 6 years, 6 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
OLDNEW
(Empty)
1 // Copyright 2014 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_fsevents.h"
6
7 #include <list>
8
9 #include "base/bind.h"
10 #include "base/file_util.h"
11 #include "base/lazy_instance.h"
12 #include "base/logging.h"
13 #include "base/mac/libdispatch_task_runner.h"
14 #include "base/mac/scoped_cftyperef.h"
15 #include "base/message_loop/message_loop.h"
16
17 namespace base {
18
19 namespace {
20
21 // The latency parameter passed to FSEventsStreamCreate().
22 const CFAbsoluteTime kEventLatencySeconds = 0.3;
23
24 class FSEventsTaskRunner : public mac::LibDispatchTaskRunner {
25 public:
26 FSEventsTaskRunner()
27 : mac::LibDispatchTaskRunner("chromium.org.FilePathWatcherFSEvents") {
28 }
29
30 protected:
31 virtual ~FSEventsTaskRunner() {}
32 };
33
34 static LazyInstance<FSEventsTaskRunner>::Leaky g_task_runner =
35 LAZY_INSTANCE_INITIALIZER;
36
37 // Resolve any symlinks in the path.
38 FilePath ResolvePath(const FilePath& path) {
39 const unsigned kMaxLinksToResolve = 255;
40
41 std::vector<FilePath::StringType> component_vector;
42 path.GetComponents(&component_vector);
43 std::list<FilePath::StringType>
44 components(component_vector.begin(), component_vector.end());
45
46 FilePath result;
47 unsigned resolve_count = 0;
48 while (resolve_count < kMaxLinksToResolve && !components.empty()) {
49 FilePath current = result.Append(*components.begin());
50 components.pop_front();
51
52 FilePath target;
53 if (ReadSymbolicLink(current, &target)) {
54 if (target.IsAbsolute())
55 result.clear();
56 std::vector<FilePath::StringType> target_components;
57 target.GetComponents(&target_components);
58 components.insert(components.begin(), target_components.begin(),
59 target_components.end());
60 resolve_count++;
61 } else {
62 result = current;
63 }
64 }
65
66 if (resolve_count >= kMaxLinksToResolve)
67 result.clear();
68 return result;
69 }
70
71 // The callback passed to FSEventStreamCreate().
72 void FSEventsCallback(ConstFSEventStreamRef stream,
73 void* event_watcher, size_t num_events,
74 void* event_paths, const FSEventStreamEventFlags flags[],
75 const FSEventStreamEventId event_ids[]) {
76 FilePathWatcherFSEvents* watcher =
77 reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
78 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
79
80 bool root_changed = watcher->ResolveTargetPath();
81 std::vector<FilePath> paths;
82 FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
83 for (size_t i = 0; i < num_events; i++) {
84 if (flags[i] & kFSEventStreamEventFlagRootChanged)
85 root_changed = true;
86 if (event_ids[i])
87 root_change_at = std::min(root_change_at, event_ids[i]);
88 paths.push_back(FilePath(
89 reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
90 }
91
92 // Reinitialize the event stream if we find changes to the root. This is
93 // necessary since FSEvents doesn't report any events for the subtree after
94 // the directory to be watched gets created.
95 if (root_changed) {
96 // Resetting the event stream from within the callback fails (FSEvents spews
97 // bad file descriptor errors), so post a task to do the reset.
98 g_task_runner.Get().PostTask(
99 FROM_HERE,
100 Bind(&FilePathWatcherFSEvents::UpdateEventStream, watcher,
101 root_change_at));
102 }
103
104 watcher->OnFilePathsChanged(paths);
105 }
106
107 } // namespace
108
109 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) {
110 }
111
112 void FilePathWatcherFSEvents::OnFilePathsChanged(
113 const std::vector<FilePath>& paths) {
114 if (!message_loop()->BelongsToCurrentThread()) {
115 message_loop()->PostTask(
116 FROM_HERE,
117 Bind(&FilePathWatcherFSEvents::OnFilePathsChanged, this, paths));
118 return;
119 }
120
121 DCHECK(message_loop()->BelongsToCurrentThread());
122 if (resolved_target_.empty())
123 return;
124
125 for (size_t i = 0; i < paths.size(); i++) {
126 if (resolved_target_.IsParent(paths[i]) || resolved_target_ == paths[i]) {
127 callback_.Run(target_, false);
128 return;
129 }
130 }
131 }
132
133 bool FilePathWatcherFSEvents::Watch(const FilePath& path,
134 bool recursive,
135 const FilePathWatcher::Callback& callback) {
136 DCHECK(resolved_target_.empty());
137 DCHECK(MessageLoopForIO::current());
138 DCHECK(!callback.is_null());
139
140 // This class could support non-recursive watches, but that is currently
141 // left to FilePathWatcherKQueue.
142 if (!recursive)
143 return false;
144
145 set_message_loop(MessageLoopProxy::current());
146 callback_ = callback;
147 target_ = path;
148
149 FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
150 g_task_runner.Get().PostTask(
151 FROM_HERE,
152 Bind(&FilePathWatcherFSEvents::StartEventStream, this, start_event));
153 return true;
154 }
155
156 void FilePathWatcherFSEvents::Cancel() {
157 if (callback_.is_null()) {
158 // Watch was never called, so exit.
159 set_cancelled();
160 return;
161 }
162
163 // Switch to the dispatch queue thread if necessary, so we can tear down
164 // the event stream.
165 if (!g_task_runner.Get().RunsTasksOnCurrentThread()) {
166 g_task_runner.Get().PostTask(
167 FROM_HERE,
168 Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this));
169 } else {
170 CancelOnMessageLoopThread();
171 }
172 }
173
174 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
175 // For all other implementations, the "message loop thread" is the IO thread,
176 // as returned by message_loop(). This implementation, however, needs to
177 // cancel pending work on the Dipatch Queue thread.
178 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
179
180 set_cancelled();
181 if (fsevent_stream_) {
182 DestroyEventStream();
183 callback_.Reset();
184 target_.clear();
185 resolved_target_.clear();
186 }
187 }
188
189 void FilePathWatcherFSEvents::UpdateEventStream(
190 FSEventStreamEventId start_event) {
191 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
192
193 // It can happen that the watcher gets canceled while tasks that call this
194 // function are still in flight, so abort if this situation is detected.
195 if (is_cancelled() || resolved_target_.empty())
196 return;
197
198 if (fsevent_stream_)
199 DestroyEventStream();
200
201 ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
202 NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
203 ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
204 NULL, resolved_target_.DirName().value().c_str(),
205 kCFStringEncodingMacHFS));
206 CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
207 ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate(
208 NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array),
209 &kCFTypeArrayCallBacks));
210
211 FSEventStreamContext context;
212 context.version = 0;
213 context.info = this;
214 context.retain = NULL;
215 context.release = NULL;
216 context.copyDescription = NULL;
217
218 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
219 watched_paths,
220 start_event,
221 kEventLatencySeconds,
222 kFSEventStreamCreateFlagWatchRoot);
223 FSEventStreamSetDispatchQueue(fsevent_stream_,
224 g_task_runner.Get().GetDispatchQueue());
225
226 if (!FSEventStreamStart(fsevent_stream_))
227 message_loop()->PostTask(FROM_HERE, Bind(callback_, target_, true));
228 }
229
230 bool FilePathWatcherFSEvents::ResolveTargetPath() {
231 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
232 FilePath resolved = ResolvePath(target_).StripTrailingSeparators();
233 bool changed = resolved != resolved_target_;
234 resolved_target_ = resolved;
235 if (resolved_target_.empty())
236 message_loop()->PostTask(FROM_HERE, Bind(callback_, target_, true));
237 return changed;
238 }
239
240 void FilePathWatcherFSEvents::DestroyEventStream() {
241 FSEventStreamStop(fsevent_stream_);
242 FSEventStreamInvalidate(fsevent_stream_);
243 FSEventStreamRelease(fsevent_stream_);
244 fsevent_stream_ = NULL;
245 }
246
247 void FilePathWatcherFSEvents::StartEventStream(
248 FSEventStreamEventId start_event) {
249 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
250 ResolveTargetPath();
251 UpdateEventStream(start_event);
252 }
253
254 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {}
255
256 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698