| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/file_path_watcher/file_path_watcher.h" | 5 #include "content/common/file_path_watcher/file_path_watcher.h" |
| 6 | 6 |
| 7 #include <CoreServices/CoreServices.h> | 7 #include <CoreServices/CoreServices.h> |
| 8 #include <set> | 8 #include <set> |
| 9 | 9 |
| 10 #include "base/file_path.h" | 10 #include "base/file_path.h" |
| 11 #include "base/file_util.h" | 11 #include "base/file_util.h" |
| 12 #include "base/logging.h" | 12 #include "base/logging.h" |
| 13 #include "base/mac/scoped_cftyperef.h" | 13 #include "base/mac/scoped_cftyperef.h" |
| 14 #include "base/message_loop.h" |
| 14 #include "base/singleton.h" | 15 #include "base/singleton.h" |
| 15 #include "base/time.h" | 16 #include "base/time.h" |
| 16 | 17 |
| 18 // Note to future well meaning engineers. Unless kqueue semantics have changed |
| 19 // considerably, do NOT try to reimplement this class using kqueue. The main |
| 20 // problem is that this class requires the ability to watch a directory |
| 21 // and notice changes to any files within it. A kqueue on a directory can watch |
| 22 // for creation and deletion of files, but not for modifications to files within |
| 23 // the directory. To do this with the current kqueue semantics would require |
| 24 // kqueueing every file in the directory, and file descriptors are a limited |
| 25 // resource. If you have a good idea on how to get around this, the source for a |
| 26 // reasonable implementation of this class using kqueues is attached here: |
| 27 // http://code.google.com/p/chromium/issues/detail?id=54822#c13 |
| 28 |
| 17 namespace { | 29 namespace { |
| 18 | 30 |
| 19 // The latency parameter passed to FSEventsStreamCreate(). | 31 // The latency parameter passed to FSEventsStreamCreate(). |
| 20 const CFAbsoluteTime kEventLatencySeconds = 0.3; | 32 const CFAbsoluteTime kEventLatencySeconds = 0.3; |
| 21 | 33 |
| 22 // Mac-specific file watcher implementation based on the FSEvents API. | 34 // Mac-specific file watcher implementation based on the FSEvents API. |
| 23 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { | 35 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { |
| 24 public: | 36 public: |
| 25 FilePathWatcherImpl(); | 37 FilePathWatcherImpl(); |
| 26 | 38 |
| 27 // Called from the FSEvents callback whenever there is a change to the paths | 39 // Called from the FSEvents callback whenever there is a change to the paths |
| 28 void OnFilePathChanged(); | 40 void OnFilePathChanged(); |
| 29 | 41 |
| 30 // (Re-)Initialize the event stream to start reporting events from | 42 // (Re-)Initialize the event stream to start reporting events from |
| 31 // |start_event|. | 43 // |start_event|. |
| 32 void UpdateEventStream(FSEventStreamEventId start_event); | 44 void UpdateEventStream(FSEventStreamEventId start_event); |
| 33 | 45 |
| 34 // FilePathWatcher::PlatformDelegate overrides. | 46 // FilePathWatcher::PlatformDelegate overrides. |
| 35 virtual bool Watch(const FilePath& path, FilePathWatcher::Delegate* delegate); | 47 virtual bool Watch(const FilePath& path, |
| 36 virtual void Cancel(); | 48 FilePathWatcher::Delegate* delegate, |
| 49 base::MessageLoopProxy* loop) OVERRIDE; |
| 50 virtual void Cancel() OVERRIDE; |
| 51 |
| 52 scoped_refptr<base::MessageLoopProxy> run_loop_message_loop() { |
| 53 return run_loop_message_loop_; |
| 54 } |
| 37 | 55 |
| 38 private: | 56 private: |
| 39 virtual ~FilePathWatcherImpl() {} | 57 virtual ~FilePathWatcherImpl() {} |
| 40 | 58 |
| 41 // Destroy the event stream. | 59 // Destroy the event stream. |
| 42 void DestroyEventStream(); | 60 void DestroyEventStream(); |
| 43 | 61 |
| 44 // Delegate to notify upon changes. | 62 // Delegate to notify upon changes. |
| 45 scoped_refptr<FilePathWatcher::Delegate> delegate_; | 63 scoped_refptr<FilePathWatcher::Delegate> delegate_; |
| 46 | 64 |
| 47 // Target path to watch (passed to delegate). | 65 // Target path to watch (passed to delegate). |
| 48 FilePath target_; | 66 FilePath target_; |
| 49 | 67 |
| 50 // Keep track of the last modified time of the file. We use nulltime | 68 // Keep track of the last modified time of the file. We use nulltime |
| 51 // to represent the file not existing. | 69 // to represent the file not existing. |
| 52 base::Time last_modified_; | 70 base::Time last_modified_; |
| 53 | 71 |
| 54 // The time at which we processed the first notification with the | 72 // The time at which we processed the first notification with the |
| 55 // |last_modified_| time stamp. | 73 // |last_modified_| time stamp. |
| 56 base::Time first_notification_; | 74 base::Time first_notification_; |
| 57 | 75 |
| 58 // Backend stream we receive event callbacks from (strong reference). | 76 // Backend stream we receive event callbacks from (strong reference). |
| 59 FSEventStreamRef fsevent_stream_; | 77 FSEventStreamRef fsevent_stream_; |
| 60 | 78 |
| 79 // Run loop for FSEventStream to run on. |
| 80 scoped_refptr<base::MessageLoopProxy> run_loop_message_loop_; |
| 81 |
| 61 // Used to detect early cancellation. | 82 // Used to detect early cancellation. |
| 62 bool canceled_; | 83 bool canceled_; |
| 63 | 84 |
| 64 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); | 85 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); |
| 65 }; | 86 }; |
| 66 | 87 |
| 67 // The callback passed to FSEventStreamCreate(). | 88 // The callback passed to FSEventStreamCreate(). |
| 68 void FSEventsCallback(ConstFSEventStreamRef stream, | 89 void FSEventsCallback(ConstFSEventStreamRef stream, |
| 69 void* event_watcher, size_t num_events, | 90 void* event_watcher, size_t num_events, |
| 70 void* event_paths, const FSEventStreamEventFlags flags[], | 91 void* event_paths, const FSEventStreamEventFlags flags[], |
| 71 const FSEventStreamEventId event_ids[]) { | 92 const FSEventStreamEventId event_ids[]) { |
| 72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 73 | |
| 74 FilePathWatcherImpl* watcher = | 93 FilePathWatcherImpl* watcher = |
| 75 reinterpret_cast<FilePathWatcherImpl*>(event_watcher); | 94 reinterpret_cast<FilePathWatcherImpl*>(event_watcher); |
| 95 DCHECK(watcher->run_loop_message_loop()->BelongsToCurrentThread()); |
| 96 |
| 76 bool root_changed = false; | 97 bool root_changed = false; |
| 77 FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream); | 98 FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream); |
| 78 for (size_t i = 0; i < num_events; i++) { | 99 for (size_t i = 0; i < num_events; i++) { |
| 79 if (flags[i] & kFSEventStreamEventFlagRootChanged) | 100 if (flags[i] & kFSEventStreamEventFlagRootChanged) |
| 80 root_changed = true; | 101 root_changed = true; |
| 81 if (event_ids[i]) | 102 if (event_ids[i]) |
| 82 root_change_at = std::min(root_change_at, event_ids[i]); | 103 root_change_at = std::min(root_change_at, event_ids[i]); |
| 83 } | 104 } |
| 84 | 105 |
| 85 // Reinitialize the event stream if we find changes to the root. This is | 106 // Reinitialize the event stream if we find changes to the root. This is |
| 86 // necessary since FSEvents doesn't report any events for the subtree after | 107 // necessary since FSEvents doesn't report any events for the subtree after |
| 87 // the directory to be watched gets created. | 108 // the directory to be watched gets created. |
| 88 if (root_changed) { | 109 if (root_changed) { |
| 89 // Resetting the event stream from within the callback fails (FSEvents spews | 110 // Resetting the event stream from within the callback fails (FSEvents spews |
| 90 // bad file descriptor errors), so post a task to do the reset. | 111 // bad file descriptor errors), so post a task to do the reset. |
| 91 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 112 watcher->run_loop_message_loop()->PostTask(FROM_HERE, |
| 92 NewRunnableMethod(watcher, &FilePathWatcherImpl::UpdateEventStream, | 113 NewRunnableMethod(watcher, &FilePathWatcherImpl::UpdateEventStream, |
| 93 root_change_at)); | 114 root_change_at)); |
| 94 } | 115 } |
| 95 | 116 |
| 96 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 117 watcher->OnFilePathChanged(); |
| 97 NewRunnableMethod(watcher, &FilePathWatcherImpl::OnFilePathChanged)); | |
| 98 } | 118 } |
| 99 | 119 |
| 100 // FilePathWatcherImpl implementation: | 120 // FilePathWatcherImpl implementation: |
| 101 | 121 |
| 102 FilePathWatcherImpl::FilePathWatcherImpl() | 122 FilePathWatcherImpl::FilePathWatcherImpl() |
| 103 : fsevent_stream_(NULL), | 123 : fsevent_stream_(NULL), |
| 104 canceled_(false) { | 124 canceled_(false) { |
| 105 } | 125 } |
| 106 | 126 |
| 107 void FilePathWatcherImpl::OnFilePathChanged() { | 127 void FilePathWatcherImpl::OnFilePathChanged() { |
| 108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 128 // Switch to the CFRunLoop based thread if necessary, so we can tear down |
| 129 // the event stream. |
| 130 if (!message_loop()->BelongsToCurrentThread()) { |
| 131 message_loop()->PostTask( |
| 132 FROM_HERE, |
| 133 NewRunnableMethod(this, &FilePathWatcherImpl::OnFilePathChanged)); |
| 134 return; |
| 135 } |
| 136 |
| 137 DCHECK(message_loop()->BelongsToCurrentThread()); |
| 109 DCHECK(!target_.empty()); | 138 DCHECK(!target_.empty()); |
| 110 | 139 |
| 111 base::PlatformFileInfo file_info; | 140 base::PlatformFileInfo file_info; |
| 112 bool file_exists = file_util::GetFileInfo(target_, &file_info); | 141 bool file_exists = file_util::GetFileInfo(target_, &file_info); |
| 113 if (file_exists && (last_modified_.is_null() || | 142 if (file_exists && (last_modified_.is_null() || |
| 114 last_modified_ != file_info.last_modified)) { | 143 last_modified_ != file_info.last_modified)) { |
| 115 last_modified_ = file_info.last_modified; | 144 last_modified_ = file_info.last_modified; |
| 116 first_notification_ = base::Time::Now(); | 145 first_notification_ = base::Time::Now(); |
| 117 delegate_->OnFilePathChanged(target_); | 146 delegate_->OnFilePathChanged(target_); |
| 118 } else if (file_exists && !first_notification_.is_null()) { | 147 } else if (file_exists && !first_notification_.is_null()) { |
| (...skipping 17 matching lines...) Expand all Loading... |
| 136 first_notification_ = base::Time(); | 165 first_notification_ = base::Time(); |
| 137 } | 166 } |
| 138 delegate_->OnFilePathChanged(target_); | 167 delegate_->OnFilePathChanged(target_); |
| 139 } else if (!file_exists && !last_modified_.is_null()) { | 168 } else if (!file_exists && !last_modified_.is_null()) { |
| 140 last_modified_ = base::Time(); | 169 last_modified_ = base::Time(); |
| 141 delegate_->OnFilePathChanged(target_); | 170 delegate_->OnFilePathChanged(target_); |
| 142 } | 171 } |
| 143 } | 172 } |
| 144 | 173 |
| 145 bool FilePathWatcherImpl::Watch(const FilePath& path, | 174 bool FilePathWatcherImpl::Watch(const FilePath& path, |
| 146 FilePathWatcher::Delegate* delegate) { | 175 FilePathWatcher::Delegate* delegate, |
| 176 base::MessageLoopProxy* loop) { |
| 147 DCHECK(target_.value().empty()); | 177 DCHECK(target_.value().empty()); |
| 178 DCHECK(MessageLoopForIO::current()); |
| 148 | 179 |
| 180 set_message_loop(base::MessageLoopProxy::CreateForCurrentThread()); |
| 181 run_loop_message_loop_ = loop; |
| 149 target_ = path; | 182 target_ = path; |
| 150 delegate_ = delegate; | 183 delegate_ = delegate; |
| 151 | 184 |
| 152 FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); | 185 FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); |
| 153 | 186 |
| 154 base::PlatformFileInfo file_info; | 187 base::PlatformFileInfo file_info; |
| 155 if (file_util::GetFileInfo(target_, &file_info)) { | 188 if (file_util::GetFileInfo(target_, &file_info)) { |
| 156 last_modified_ = file_info.last_modified; | 189 last_modified_ = file_info.last_modified; |
| 157 first_notification_ = base::Time::Now(); | 190 first_notification_ = base::Time::Now(); |
| 158 } | 191 } |
| 159 | 192 |
| 160 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 193 run_loop_message_loop()->PostTask(FROM_HERE, |
| 161 NewRunnableMethod(this, &FilePathWatcherImpl::UpdateEventStream, | 194 NewRunnableMethod(this, &FilePathWatcherImpl::UpdateEventStream, |
| 162 start_event)); | 195 start_event)); |
| 163 | 196 |
| 164 return true; | 197 return true; |
| 165 } | 198 } |
| 166 | 199 |
| 167 void FilePathWatcherImpl::Cancel() { | 200 void FilePathWatcherImpl::Cancel() { |
| 168 // Switch to the UI thread if necessary, so we can tear down the event stream. | 201 if (!run_loop_message_loop().get()) { |
| 169 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 202 // Watch was never called, so exit. |
| 170 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 203 return; |
| 204 } |
| 205 |
| 206 // Switch to the CFRunLoop based thread if necessary, so we can tear down |
| 207 // the event stream. |
| 208 if (!run_loop_message_loop()->BelongsToCurrentThread()) { |
| 209 run_loop_message_loop()->PostTask(FROM_HERE, |
| 171 NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); | 210 NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); |
| 172 return; | 211 return; |
| 173 } | 212 } |
| 174 | 213 |
| 175 canceled_ = true; | 214 canceled_ = true; |
| 176 if (fsevent_stream_) | 215 if (fsevent_stream_) |
| 177 DestroyEventStream(); | 216 DestroyEventStream(); |
| 178 } | 217 } |
| 179 | 218 |
| 180 void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { | 219 void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { |
| 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 220 DCHECK(run_loop_message_loop()->BelongsToCurrentThread()); |
| 221 DCHECK(MessageLoopForUI::current()); |
| 182 | 222 |
| 183 // It can happen that the watcher gets canceled while tasks that call this | 223 // It can happen that the watcher gets canceled while tasks that call this |
| 184 // function are still in flight, so abort if this situation is detected. | 224 // function are still in flight, so abort if this situation is detected. |
| 185 if (canceled_) | 225 if (canceled_) |
| 186 return; | 226 return; |
| 187 | 227 |
| 188 if (fsevent_stream_) | 228 if (fsevent_stream_) |
| 189 DestroyEventStream(); | 229 DestroyEventStream(); |
| 190 | 230 |
| 191 base::mac::ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString( | 231 base::mac::ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString( |
| (...skipping 13 matching lines...) Expand all Loading... |
| 205 context.copyDescription = NULL; | 245 context.copyDescription = NULL; |
| 206 | 246 |
| 207 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, | 247 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, |
| 208 watched_paths, | 248 watched_paths, |
| 209 start_event, | 249 start_event, |
| 210 kEventLatencySeconds, | 250 kEventLatencySeconds, |
| 211 kFSEventStreamCreateFlagWatchRoot); | 251 kFSEventStreamCreateFlagWatchRoot); |
| 212 FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), | 252 FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), |
| 213 kCFRunLoopDefaultMode); | 253 kCFRunLoopDefaultMode); |
| 214 if (!FSEventStreamStart(fsevent_stream_)) { | 254 if (!FSEventStreamStart(fsevent_stream_)) { |
| 215 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 255 message_loop()->PostTask(FROM_HERE, |
| 216 NewRunnableMethod(delegate_.get(), | 256 NewRunnableMethod(delegate_.get(), |
| 217 &FilePathWatcher::Delegate::OnError)); | 257 &FilePathWatcher::Delegate::OnError)); |
| 218 } | 258 } |
| 219 } | 259 } |
| 220 | 260 |
| 221 void FilePathWatcherImpl::DestroyEventStream() { | 261 void FilePathWatcherImpl::DestroyEventStream() { |
| 222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 262 DCHECK(run_loop_message_loop()->BelongsToCurrentThread()); |
| 223 FSEventStreamStop(fsevent_stream_); | 263 FSEventStreamStop(fsevent_stream_); |
| 224 FSEventStreamUnscheduleFromRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), | 264 FSEventStreamUnscheduleFromRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), |
| 225 kCFRunLoopDefaultMode); | 265 kCFRunLoopDefaultMode); |
| 226 FSEventStreamRelease(fsevent_stream_); | 266 FSEventStreamRelease(fsevent_stream_); |
| 227 fsevent_stream_ = NULL; | 267 fsevent_stream_ = NULL; |
| 228 } | 268 } |
| 229 | 269 |
| 230 } // namespace | 270 } // namespace |
| 231 | 271 |
| 232 FilePathWatcher::FilePathWatcher() { | 272 FilePathWatcher::FilePathWatcher() { |
| 233 impl_ = new FilePathWatcherImpl(); | 273 impl_ = new FilePathWatcherImpl(); |
| 234 } | 274 } |
| OLD | NEW |