Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / base / files / file_path_watcher_fsevents.cc
blobda01c431bfe75e5323d2ce82531dc904ba3d0c95
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.
5 #include "base/files/file_path_watcher_fsevents.h"
7 #include <list>
9 #include "base/bind.h"
10 #include "base/files/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 #include "base/thread_task_runner_handle.h"
18 namespace base {
20 namespace {
22 // The latency parameter passed to FSEventsStreamCreate().
23 const CFAbsoluteTime kEventLatencySeconds = 0.3;
25 class FSEventsTaskRunner : public mac::LibDispatchTaskRunner {
26 public:
27 FSEventsTaskRunner()
28 : mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") {
31 protected:
32 ~FSEventsTaskRunner() override {}
35 static LazyInstance<FSEventsTaskRunner>::Leaky g_task_runner =
36 LAZY_INSTANCE_INITIALIZER;
38 // Resolve any symlinks in the path.
39 FilePath ResolvePath(const FilePath& path) {
40 const unsigned kMaxLinksToResolve = 255;
42 std::vector<FilePath::StringType> component_vector;
43 path.GetComponents(&component_vector);
44 std::list<FilePath::StringType>
45 components(component_vector.begin(), component_vector.end());
47 FilePath result;
48 unsigned resolve_count = 0;
49 while (resolve_count < kMaxLinksToResolve && !components.empty()) {
50 FilePath component(*components.begin());
51 components.pop_front();
53 FilePath current;
54 if (component.IsAbsolute()) {
55 current = component;
56 } else {
57 current = result.Append(component);
60 FilePath target;
61 if (ReadSymbolicLink(current, &target)) {
62 if (target.IsAbsolute())
63 result.clear();
64 std::vector<FilePath::StringType> target_components;
65 target.GetComponents(&target_components);
66 components.insert(components.begin(), target_components.begin(),
67 target_components.end());
68 resolve_count++;
69 } else {
70 result = current;
74 if (resolve_count >= kMaxLinksToResolve)
75 result.clear();
76 return result;
79 } // namespace
81 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) {
84 bool FilePathWatcherFSEvents::Watch(const FilePath& path,
85 bool recursive,
86 const FilePathWatcher::Callback& callback) {
87 DCHECK(MessageLoopForIO::current());
88 DCHECK(!callback.is_null());
89 DCHECK(callback_.is_null());
91 // This class could support non-recursive watches, but that is currently
92 // left to FilePathWatcherKQueue.
93 if (!recursive)
94 return false;
96 set_task_runner(ThreadTaskRunnerHandle::Get());
97 callback_ = callback;
99 FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
100 g_task_runner.Get().PostTask(
101 FROM_HERE, Bind(&FilePathWatcherFSEvents::StartEventStream, this,
102 start_event, path));
103 return true;
106 void FilePathWatcherFSEvents::Cancel() {
107 set_cancelled();
108 callback_.Reset();
110 // Switch to the dispatch queue thread to tear down the event stream.
111 g_task_runner.Get().PostTask(
112 FROM_HERE,
113 Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this));
116 // static
117 void FilePathWatcherFSEvents::FSEventsCallback(
118 ConstFSEventStreamRef stream,
119 void* event_watcher,
120 size_t num_events,
121 void* event_paths,
122 const FSEventStreamEventFlags flags[],
123 const FSEventStreamEventId event_ids[]) {
124 FilePathWatcherFSEvents* watcher =
125 reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
126 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
128 bool root_changed = watcher->ResolveTargetPath();
129 std::vector<FilePath> paths;
130 FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
131 for (size_t i = 0; i < num_events; i++) {
132 if (flags[i] & kFSEventStreamEventFlagRootChanged)
133 root_changed = true;
134 if (event_ids[i])
135 root_change_at = std::min(root_change_at, event_ids[i]);
136 paths.push_back(FilePath(
137 reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
140 // Reinitialize the event stream if we find changes to the root. This is
141 // necessary since FSEvents doesn't report any events for the subtree after
142 // the directory to be watched gets created.
143 if (root_changed) {
144 // Resetting the event stream from within the callback fails (FSEvents spews
145 // bad file descriptor errors), so post a task to do the reset.
146 g_task_runner.Get().PostTask(
147 FROM_HERE,
148 Bind(&FilePathWatcherFSEvents::UpdateEventStream, watcher,
149 root_change_at));
152 watcher->OnFilePathsChanged(paths);
155 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {
156 // This method may be called on either the libdispatch or task_runner()
157 // thread. Checking callback_ on the libdispatch thread here is safe because
158 // it is executing in a task posted by Cancel() which first reset callback_.
159 // PostTask forms a sufficient memory barrier to ensure that the value is
160 // consistent on the target thread.
161 DCHECK(callback_.is_null())
162 << "Cancel() must be called before FilePathWatcher is destroyed.";
165 void FilePathWatcherFSEvents::OnFilePathsChanged(
166 const std::vector<FilePath>& paths) {
167 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
168 DCHECK(!resolved_target_.empty());
169 task_runner()->PostTask(
170 FROM_HERE, Bind(&FilePathWatcherFSEvents::DispatchEvents, this, paths,
171 target_, resolved_target_));
174 void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths,
175 const FilePath& target,
176 const FilePath& resolved_target) {
177 DCHECK(task_runner()->RunsTasksOnCurrentThread());
179 // Don't issue callbacks after Cancel() has been called.
180 if (is_cancelled() || callback_.is_null()) {
181 return;
184 for (const FilePath& path : paths) {
185 if (resolved_target.IsParent(path) || resolved_target == path) {
186 callback_.Run(target, false);
187 return;
192 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
193 // For all other implementations, the "message loop thread" is the IO thread,
194 // as returned by task_runner(). This implementation, however, needs to
195 // cancel pending work on the Dispatch Queue thread.
196 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
198 if (fsevent_stream_) {
199 DestroyEventStream();
200 target_.clear();
201 resolved_target_.clear();
205 void FilePathWatcherFSEvents::UpdateEventStream(
206 FSEventStreamEventId start_event) {
207 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
209 // It can happen that the watcher gets canceled while tasks that call this
210 // function are still in flight, so abort if this situation is detected.
211 if (resolved_target_.empty())
212 return;
214 if (fsevent_stream_)
215 DestroyEventStream();
217 ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
218 NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
219 ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
220 NULL, resolved_target_.DirName().value().c_str(),
221 kCFStringEncodingMacHFS));
222 CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
223 ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate(
224 NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array),
225 &kCFTypeArrayCallBacks));
227 FSEventStreamContext context;
228 context.version = 0;
229 context.info = this;
230 context.retain = NULL;
231 context.release = NULL;
232 context.copyDescription = NULL;
234 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
235 watched_paths,
236 start_event,
237 kEventLatencySeconds,
238 kFSEventStreamCreateFlagWatchRoot);
239 FSEventStreamSetDispatchQueue(fsevent_stream_,
240 g_task_runner.Get().GetDispatchQueue());
242 if (!FSEventStreamStart(fsevent_stream_)) {
243 task_runner()->PostTask(
244 FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_));
248 bool FilePathWatcherFSEvents::ResolveTargetPath() {
249 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
250 FilePath resolved = ResolvePath(target_).StripTrailingSeparators();
251 bool changed = resolved != resolved_target_;
252 resolved_target_ = resolved;
253 if (resolved_target_.empty()) {
254 task_runner()->PostTask(
255 FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_));
257 return changed;
260 void FilePathWatcherFSEvents::ReportError(const FilePath& target) {
261 DCHECK(task_runner()->RunsTasksOnCurrentThread());
262 if (!callback_.is_null()) {
263 callback_.Run(target, true);
267 void FilePathWatcherFSEvents::DestroyEventStream() {
268 FSEventStreamStop(fsevent_stream_);
269 FSEventStreamInvalidate(fsevent_stream_);
270 FSEventStreamRelease(fsevent_stream_);
271 fsevent_stream_ = NULL;
274 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event,
275 const FilePath& path) {
276 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
277 DCHECK(resolved_target_.empty());
279 target_ = path;
280 ResolveTargetPath();
281 UpdateEventStream(start_event);
284 } // namespace base