Chromium Code Reviews| 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 <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 | |
| OLD | NEW |