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

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: another 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 base::FilePath ResolvePath(const base::FilePath& path) {
39 const unsigned kMaxLinksToResolve = 255;
40
41 std::vector<FilePath::StringType> component_array;
Mattias Nissler (ping if slow) 2014/05/28 08:08:32 FilePath vs. base::FilePath inconsistency here.
Mattias Nissler (ping if slow) 2014/05/28 08:08:32 nit: this should be |component_vector|.
vandebo (ex-Chrome) 2014/05/28 18:54:52 Fixed.
vandebo (ex-Chrome) 2014/05/28 18:54:52 Done.
42 path.GetComponents(&component_array);
43 std::list<base::FilePath> components;
Mattias Nissler (ping if slow) 2014/05/28 08:08:32 Shouldn't this rather be std::list<base::FilePath:
vandebo (ex-Chrome) 2014/05/28 18:54:52 Indeed.
44 for (size_t i = 0; i < component_array.size(); i++)
45 components.push_back(base::FilePath(component_array[i]));
46
47 base::FilePath result;
48 unsigned resolve_count = 0;
49 while (resolve_count < kMaxLinksToResolve && !components.empty()) {
50 base::FilePath current = result.Append(*components.begin());
51 components.pop_front();
52
53 base::FilePath target;
54 if (base::ReadSymbolicLink(current, &target)) {
55 if (target.IsAbsolute())
56 result.clear();
57 std::vector<FilePath::StringType> target_components;
58 target.GetComponents(&target_components);
59 for (std::vector<FilePath::StringType>::const_reverse_iterator it =
60 target_components.rbegin();
61 it != target_components.rend();
62 ++it) {
63 components.push_front(base::FilePath(*it));
64 }
Mattias Nissler (ping if slow) 2014/05/28 08:08:32 instead of the loop, you could also do std::copy(
vandebo (ex-Chrome) 2014/05/28 18:54:52 Yup, I had target_components.insert(...) before bu
65 resolve_count++;
66 } else {
67 result = current;
68 }
69 }
70
71 if (resolve_count >= kMaxLinksToResolve)
72 result.clear();
73 return result;
74 }
75
76 // The callback passed to FSEventStreamCreate().
77 void FSEventsCallback(ConstFSEventStreamRef stream,
78 void* event_watcher, size_t num_events,
79 void* event_paths, const FSEventStreamEventFlags flags[],
80 const FSEventStreamEventId event_ids[]) {
81 base::FilePathWatcherFSEvents* watcher =
82 reinterpret_cast<base::FilePathWatcherFSEvents*>(event_watcher);
83 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
84
85 bool root_changed = false;
86 std::vector<FilePath> paths;
87 FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
88 for (size_t i = 0; i < num_events; i++) {
89 if (flags[i] & kFSEventStreamEventFlagRootChanged)
90 root_changed = true;
91 if (event_ids[i])
92 root_change_at = std::min(root_change_at, event_ids[i]);
93 paths.push_back(FilePath(
94 reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
95 }
96
97 // Reinitialize the event stream if we find changes to the root. This is
98 // necessary since FSEvents doesn't report any events for the subtree after
99 // the directory to be watched gets created.
100 if (root_changed) {
101 // Resetting the event stream from within the callback fails (FSEvents spews
102 // bad file descriptor errors), so post a task to do the reset.
103 g_task_runner.Get().PostTask(
104 FROM_HERE,
105 base::Bind(&base::FilePathWatcherFSEvents::UpdateEventStream, watcher,
106 root_change_at));
107 }
108
109 watcher->OnFilePathsChanged(paths);
110 }
111
112 } // namespace
113
114 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) {
115 }
116
117 void FilePathWatcherFSEvents::OnFilePathsChanged(
118 const std::vector<FilePath>& paths) {
119 if (!message_loop()->BelongsToCurrentThread()) {
120 message_loop()->PostTask(
121 FROM_HERE,
122 base::Bind(&FilePathWatcherFSEvents::OnFilePathsChanged, this, paths));
123 return;
124 }
125
126 DCHECK(message_loop()->BelongsToCurrentThread());
127 DCHECK(!resolved_target_.empty());
128
129 for (size_t i = 0; i < paths.size(); i++) {
130 if (resolved_target_.IsParent(paths[i]) || resolved_target_ == paths[i]) {
131 callback_.Run(target_, false);
132 return;
133 }
134 }
135 }
136
137 bool FilePathWatcherFSEvents::Watch(const FilePath& path,
138 bool recursive,
139 const FilePathWatcher::Callback& callback) {
140 DCHECK(resolved_target_.empty());
141 DCHECK(MessageLoopForIO::current());
142 DCHECK(!callback.is_null());
143
144 // This class could support non-recursive watches, but that is currently
145 // left to FilePathWatcherKQueue.
146 if (!recursive)
147 return false;
148
149 set_message_loop(base::MessageLoopProxy::current());
150 callback_ = callback;
151 target_ = path;
152
153 FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
154 g_task_runner.Get().PostTask(
155 FROM_HERE,
156 base::Bind(&FilePathWatcherFSEvents::StartEventStream, this,
157 start_event));
158 return true;
159 }
160
161 void FilePathWatcherFSEvents::Cancel() {
162 if (callback_.is_null()) {
163 // Watch was never called, so exit.
164 set_cancelled();
165 return;
166 }
167
168 // Switch to the dispatch queue thread if necessary, so we can tear down
169 // the event stream.
170 if (!g_task_runner.Get().RunsTasksOnCurrentThread()) {
171 g_task_runner.Get().PostTask(
172 FROM_HERE,
173 base::Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this));
174 } else {
175 CancelOnMessageLoopThread();
176 }
177 }
178
179 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
180 // For all other implementations, the "message loop thread" is the IO thread,
181 // as returned by message_loop(). This implementation, however, needs to
182 // cancel pending work on the Dipatch Queue thread.
183 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
184
185 set_cancelled();
186 if (fsevent_stream_) {
187 DestroyEventStream();
188 callback_.Reset();
189 target_.clear();
190 resolved_target_.clear();
191 }
192 }
193
194 void FilePathWatcherFSEvents::UpdateEventStream(
195 FSEventStreamEventId start_event) {
196 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
197
198 // It can happen that the watcher gets canceled while tasks that call this
199 // function are still in flight, so abort if this situation is detected.
200 if (is_cancelled())
201 return;
202
203 if (fsevent_stream_)
204 DestroyEventStream();
205
206 base::ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
207 NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
208 base::ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
209 NULL, resolved_target_.DirName().value().c_str(),
210 kCFStringEncodingMacHFS));
211 CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
212 base::ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate(
213 NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array),
214 &kCFTypeArrayCallBacks));
215
216 FSEventStreamContext context;
217 context.version = 0;
218 context.info = this;
219 context.retain = NULL;
220 context.release = NULL;
221 context.copyDescription = NULL;
222
223 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
224 watched_paths,
225 start_event,
226 kEventLatencySeconds,
227 kFSEventStreamCreateFlagWatchRoot);
228 FSEventStreamSetDispatchQueue(fsevent_stream_,
229 g_task_runner.Get().GetDispatchQueue());
230
231 if (!FSEventStreamStart(fsevent_stream_))
232 message_loop()->PostTask(FROM_HERE, base::Bind(callback_, target_, true));
233 }
234
235 void FilePathWatcherFSEvents::DestroyEventStream() {
236 FSEventStreamStop(fsevent_stream_);
237 FSEventStreamInvalidate(fsevent_stream_);
238 FSEventStreamRelease(fsevent_stream_);
239 fsevent_stream_ = NULL;
240 }
241
242 void FilePathWatcherFSEvents::StartEventStream(
243 FSEventStreamEventId start_event) {
244 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
245 resolved_target_ = ResolvePath(target_).StripTrailingSeparators();
Mattias Nissler (ping if slow) 2014/05/28 08:08:32 If we only resolve when starting the watcher, we'l
vandebo (ex-Chrome) 2014/05/28 18:54:52 Yup, Fixed.
246 if (resolved_target_.empty()) {
247 message_loop()->PostTask(FROM_HERE, base::Bind(callback_, target_, true));
248 return;
249 }
250 UpdateEventStream(start_event);
251 }
252
253 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {}
254
255 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698