OLD | NEW |
---|---|
(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 | |
OLD | NEW |