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

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

Powered by Google App Engine
This is Rietveld 408576698