OLD | NEW |
---|---|
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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 scoped_refptr<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->message_loop()->PostTask(FROM_HERE, |
97 NewRunnableMethod(watcher, &FilePathWatcherImpl::OnFilePathChanged)); | 118 NewRunnableMethod(watcher, &FilePathWatcherImpl::OnFilePathChanged)); |
98 } | 119 } |
99 | 120 |
100 // FilePathWatcherImpl implementation: | 121 // FilePathWatcherImpl implementation: |
101 | 122 |
102 FilePathWatcherImpl::FilePathWatcherImpl() | 123 FilePathWatcherImpl::FilePathWatcherImpl() |
103 : fsevent_stream_(NULL), | 124 : fsevent_stream_(NULL), |
104 canceled_(false) { | 125 canceled_(false) { |
105 } | 126 } |
106 | 127 |
107 void FilePathWatcherImpl::OnFilePathChanged() { | 128 void FilePathWatcherImpl::OnFilePathChanged() { |
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 129 DCHECK(message_loop()->BelongsToCurrentThread()); |
109 DCHECK(!target_.empty()); | 130 DCHECK(!target_.empty()); |
110 | 131 |
111 base::PlatformFileInfo file_info; | 132 base::PlatformFileInfo file_info; |
112 bool file_exists = file_util::GetFileInfo(target_, &file_info); | 133 bool file_exists = file_util::GetFileInfo(target_, &file_info); |
113 if (file_exists && (last_modified_.is_null() || | 134 if (file_exists && (last_modified_.is_null() || |
114 last_modified_ != file_info.last_modified)) { | 135 last_modified_ != file_info.last_modified)) { |
115 last_modified_ = file_info.last_modified; | 136 last_modified_ = file_info.last_modified; |
116 first_notification_ = base::Time::Now(); | 137 first_notification_ = base::Time::Now(); |
117 delegate_->OnFilePathChanged(target_); | 138 delegate_->OnFilePathChanged(target_); |
118 } else if (file_exists && !first_notification_.is_null()) { | 139 } else if (file_exists && !first_notification_.is_null()) { |
(...skipping 17 matching lines...) Expand all Loading... | |
136 first_notification_ = base::Time(); | 157 first_notification_ = base::Time(); |
137 } | 158 } |
138 delegate_->OnFilePathChanged(target_); | 159 delegate_->OnFilePathChanged(target_); |
139 } else if (!file_exists && !last_modified_.is_null()) { | 160 } else if (!file_exists && !last_modified_.is_null()) { |
140 last_modified_ = base::Time(); | 161 last_modified_ = base::Time(); |
141 delegate_->OnFilePathChanged(target_); | 162 delegate_->OnFilePathChanged(target_); |
142 } | 163 } |
143 } | 164 } |
144 | 165 |
145 bool FilePathWatcherImpl::Watch(const FilePath& path, | 166 bool FilePathWatcherImpl::Watch(const FilePath& path, |
146 FilePathWatcher::Delegate* delegate) { | 167 FilePathWatcher::Delegate* delegate, |
168 scoped_refptr<base::MessageLoopProxy> loop) { | |
147 DCHECK(target_.value().empty()); | 169 DCHECK(target_.value().empty()); |
170 DCHECK(MessageLoopForIO::current()); | |
148 | 171 |
172 run_loop_message_loop_ = loop; | |
149 target_ = path; | 173 target_ = path; |
150 delegate_ = delegate; | 174 delegate_ = delegate; |
151 | 175 |
152 FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); | 176 FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); |
153 | 177 |
154 base::PlatformFileInfo file_info; | 178 base::PlatformFileInfo file_info; |
155 if (file_util::GetFileInfo(target_, &file_info)) { | 179 if (file_util::GetFileInfo(target_, &file_info)) { |
156 last_modified_ = file_info.last_modified; | 180 last_modified_ = file_info.last_modified; |
157 first_notification_ = base::Time::Now(); | 181 first_notification_ = base::Time::Now(); |
158 } | 182 } |
159 | 183 |
160 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 184 run_loop_message_loop()->PostTask(FROM_HERE, |
161 NewRunnableMethod(this, &FilePathWatcherImpl::UpdateEventStream, | 185 NewRunnableMethod(this, &FilePathWatcherImpl::UpdateEventStream, |
162 start_event)); | 186 start_event)); |
163 | 187 |
164 return true; | 188 return true; |
165 } | 189 } |
166 | 190 |
167 void FilePathWatcherImpl::Cancel() { | 191 void FilePathWatcherImpl::Cancel() { |
192 if (!run_loop_message_loop().get()) { | |
193 // Watch was never called, so exit. | |
194 return; | |
195 } | |
196 | |
168 // Switch to the UI thread if necessary, so we can tear down the event stream. | 197 // Switch to the UI thread if necessary, so we can tear down the event stream. |
Mattias Nissler (ping if slow)
2011/03/17 18:14:27
nit: update comment.
| |
169 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 198 if (!run_loop_message_loop()->BelongsToCurrentThread()) { |
170 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 199 run_loop_message_loop()->PostTask(FROM_HERE, |
171 NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); | 200 NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); |
172 return; | 201 return; |
173 } | 202 } |
174 | 203 |
175 canceled_ = true; | 204 canceled_ = true; |
176 if (fsevent_stream_) | 205 if (fsevent_stream_) |
177 DestroyEventStream(); | 206 DestroyEventStream(); |
178 } | 207 } |
179 | 208 |
180 void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { | 209 void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { |
181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 210 DCHECK(run_loop_message_loop()->BelongsToCurrentThread()); |
211 DCHECK(MessageLoopForUI::current()); | |
182 | 212 |
183 // It can happen that the watcher gets canceled while tasks that call this | 213 // 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. | 214 // function are still in flight, so abort if this situation is detected. |
185 if (canceled_) | 215 if (canceled_) |
186 return; | 216 return; |
187 | 217 |
188 if (fsevent_stream_) | 218 if (fsevent_stream_) |
189 DestroyEventStream(); | 219 DestroyEventStream(); |
190 | 220 |
191 base::mac::ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString( | 221 base::mac::ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString( |
(...skipping 13 matching lines...) Expand all Loading... | |
205 context.copyDescription = NULL; | 235 context.copyDescription = NULL; |
206 | 236 |
207 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, | 237 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, |
208 watched_paths, | 238 watched_paths, |
209 start_event, | 239 start_event, |
210 kEventLatencySeconds, | 240 kEventLatencySeconds, |
211 kFSEventStreamCreateFlagWatchRoot); | 241 kFSEventStreamCreateFlagWatchRoot); |
212 FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), | 242 FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), |
213 kCFRunLoopDefaultMode); | 243 kCFRunLoopDefaultMode); |
214 if (!FSEventStreamStart(fsevent_stream_)) { | 244 if (!FSEventStreamStart(fsevent_stream_)) { |
215 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 245 message_loop()->PostTask(FROM_HERE, |
216 NewRunnableMethod(delegate_.get(), | 246 NewRunnableMethod(delegate_.get(), |
217 &FilePathWatcher::Delegate::OnError)); | 247 &FilePathWatcher::Delegate::OnError)); |
218 } | 248 } |
219 } | 249 } |
220 | 250 |
221 void FilePathWatcherImpl::DestroyEventStream() { | 251 void FilePathWatcherImpl::DestroyEventStream() { |
222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 252 DCHECK(run_loop_message_loop()->BelongsToCurrentThread()); |
223 FSEventStreamStop(fsevent_stream_); | 253 FSEventStreamStop(fsevent_stream_); |
224 FSEventStreamUnscheduleFromRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), | 254 FSEventStreamUnscheduleFromRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), |
225 kCFRunLoopDefaultMode); | 255 kCFRunLoopDefaultMode); |
226 FSEventStreamRelease(fsevent_stream_); | 256 FSEventStreamRelease(fsevent_stream_); |
227 fsevent_stream_ = NULL; | 257 fsevent_stream_ = NULL; |
228 } | 258 } |
229 | 259 |
230 } // namespace | 260 } // namespace |
231 | 261 |
232 FilePathWatcher::FilePathWatcher() { | 262 FilePathWatcher::FilePathWatcher() { |
233 impl_ = new FilePathWatcherImpl(); | 263 impl_ = new FilePathWatcherImpl(); |
234 } | 264 } |
OLD | NEW |