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 "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 |