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 |