Enterprise policy: Ignore the deprecated ForceSafeSearch if ForceGoogleSafeSearch...
[chromium-blink-merge.git] / base / files / file_path_watcher_fsevents.cc
blobef4e6ee32eb412f3c51d70112a9d0d1824045aa0
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"
17 namespace base {
19 namespace {
21 // The latency parameter passed to FSEventsStreamCreate().
22 const CFAbsoluteTime kEventLatencySeconds = 0.3;
24 class FSEventsTaskRunner : public mac::LibDispatchTaskRunner {
25 public:
26 FSEventsTaskRunner()
27 : mac::LibDispatchTaskRunner("org.chromium.FilePathWatcherFSEvents") {
30 protected:
31 ~FSEventsTaskRunner() override {}
34 static LazyInstance<FSEventsTaskRunner>::Leaky g_task_runner =
35 LAZY_INSTANCE_INITIALIZER;
37 // Resolve any symlinks in the path.
38 FilePath ResolvePath(const FilePath& path) {
39 const unsigned kMaxLinksToResolve = 255;
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());
46 FilePath result;
47 unsigned resolve_count = 0;
48 while (resolve_count < kMaxLinksToResolve && !components.empty()) {
49 FilePath component(*components.begin());
50 components.pop_front();
52 FilePath current;
53 if (component.IsAbsolute()) {
54 current = component;
55 } else {
56 current = result.Append(component);
59 FilePath target;
60 if (ReadSymbolicLink(current, &target)) {
61 if (target.IsAbsolute())
62 result.clear();
63 std::vector<FilePath::StringType> target_components;
64 target.GetComponents(&target_components);
65 components.insert(components.begin(), target_components.begin(),
66 target_components.end());
67 resolve_count++;
68 } else {
69 result = current;
73 if (resolve_count >= kMaxLinksToResolve)
74 result.clear();
75 return result;
78 } // namespace
80 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) {
83 bool FilePathWatcherFSEvents::Watch(const FilePath& path,
84 bool recursive,
85 const FilePathWatcher::Callback& callback) {
86 DCHECK(MessageLoopForIO::current());
87 DCHECK(!callback.is_null());
88 DCHECK(callback_.is_null());
90 // This class could support non-recursive watches, but that is currently
91 // left to FilePathWatcherKQueue.
92 if (!recursive)
93 return false;
95 set_message_loop(MessageLoopProxy::current());
96 callback_ = callback;
98 FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
99 g_task_runner.Get().PostTask(
100 FROM_HERE, Bind(&FilePathWatcherFSEvents::StartEventStream, this,
101 start_event, path));
102 return true;
105 void FilePathWatcherFSEvents::Cancel() {
106 set_cancelled();
107 callback_.Reset();
109 // Switch to the dispatch queue thread to tear down the event stream.
110 g_task_runner.Get().PostTask(
111 FROM_HERE,
112 Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this));
115 // static
116 void FilePathWatcherFSEvents::FSEventsCallback(
117 ConstFSEventStreamRef stream,
118 void* event_watcher,
119 size_t num_events,
120 void* event_paths,
121 const FSEventStreamEventFlags flags[],
122 const FSEventStreamEventId event_ids[]) {
123 FilePathWatcherFSEvents* watcher =
124 reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
125 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
127 bool root_changed = watcher->ResolveTargetPath();
128 std::vector<FilePath> paths;
129 FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
130 for (size_t i = 0; i < num_events; i++) {
131 if (flags[i] & kFSEventStreamEventFlagRootChanged)
132 root_changed = true;
133 if (event_ids[i])
134 root_change_at = std::min(root_change_at, event_ids[i]);
135 paths.push_back(FilePath(
136 reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
139 // Reinitialize the event stream if we find changes to the root. This is
140 // necessary since FSEvents doesn't report any events for the subtree after
141 // the directory to be watched gets created.
142 if (root_changed) {
143 // Resetting the event stream from within the callback fails (FSEvents spews
144 // bad file descriptor errors), so post a task to do the reset.
145 g_task_runner.Get().PostTask(
146 FROM_HERE,
147 Bind(&FilePathWatcherFSEvents::UpdateEventStream, watcher,
148 root_change_at));
151 watcher->OnFilePathsChanged(paths);
154 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {
155 // This method may be called on either the libdispatch or message_loop()
156 // thread. Checking callback_ on the libdispatch thread here is safe because
157 // it is executing in a task posted by Cancel() which first reset callback_.
158 // PostTask forms a sufficient memory barrier to ensure that the value is
159 // consistent on the target thread.
160 DCHECK(callback_.is_null())
161 << "Cancel() must be called before FilePathWatcher is destroyed.";
164 void FilePathWatcherFSEvents::OnFilePathsChanged(
165 const std::vector<FilePath>& paths) {
166 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
167 DCHECK(!resolved_target_.empty());
168 message_loop()->PostTask(
169 FROM_HERE, Bind(&FilePathWatcherFSEvents::DispatchEvents, this, paths,
170 target_, resolved_target_));
173 void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths,
174 const FilePath& target,
175 const FilePath& resolved_target) {
176 DCHECK(message_loop()->RunsTasksOnCurrentThread());
178 // Don't issue callbacks after Cancel() has been called.
179 if (is_cancelled() || callback_.is_null()) {
180 return;
183 for (const FilePath& path : paths) {
184 if (resolved_target.IsParent(path) || resolved_target == path) {
185 callback_.Run(target, false);
186 return;
191 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
192 // For all other implementations, the "message loop thread" is the IO thread,
193 // as returned by message_loop(). This implementation, however, needs to
194 // cancel pending work on the Dispatch Queue thread.
195 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
197 if (fsevent_stream_) {
198 DestroyEventStream();
199 target_.clear();
200 resolved_target_.clear();
204 void FilePathWatcherFSEvents::UpdateEventStream(
205 FSEventStreamEventId start_event) {
206 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
208 // It can happen that the watcher gets canceled while tasks that call this
209 // function are still in flight, so abort if this situation is detected.
210 if (resolved_target_.empty())
211 return;
213 if (fsevent_stream_)
214 DestroyEventStream();
216 ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
217 NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
218 ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
219 NULL, resolved_target_.DirName().value().c_str(),
220 kCFStringEncodingMacHFS));
221 CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
222 ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate(
223 NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array),
224 &kCFTypeArrayCallBacks));
226 FSEventStreamContext context;
227 context.version = 0;
228 context.info = this;
229 context.retain = NULL;
230 context.release = NULL;
231 context.copyDescription = NULL;
233 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
234 watched_paths,
235 start_event,
236 kEventLatencySeconds,
237 kFSEventStreamCreateFlagWatchRoot);
238 FSEventStreamSetDispatchQueue(fsevent_stream_,
239 g_task_runner.Get().GetDispatchQueue());
241 if (!FSEventStreamStart(fsevent_stream_)) {
242 message_loop()->PostTask(
243 FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_));
247 bool FilePathWatcherFSEvents::ResolveTargetPath() {
248 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
249 FilePath resolved = ResolvePath(target_).StripTrailingSeparators();
250 bool changed = resolved != resolved_target_;
251 resolved_target_ = resolved;
252 if (resolved_target_.empty()) {
253 message_loop()->PostTask(
254 FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_));
256 return changed;
259 void FilePathWatcherFSEvents::ReportError(const FilePath& target) {
260 DCHECK(message_loop()->RunsTasksOnCurrentThread());
261 if (!callback_.is_null()) {
262 callback_.Run(target, true);
266 void FilePathWatcherFSEvents::DestroyEventStream() {
267 FSEventStreamStop(fsevent_stream_);
268 FSEventStreamInvalidate(fsevent_stream_);
269 FSEventStreamRelease(fsevent_stream_);
270 fsevent_stream_ = NULL;
273 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event,
274 const FilePath& path) {
275 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
276 DCHECK(resolved_target_.empty());
278 target_ = path;
279 ResolveTargetPath();
280 UpdateEventStream(start_event);
283 } // namespace base