Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(284)

Side by Side Diff: content/common/file_path_watcher/file_path_watcher_mac.cc

Issue 6670081: Move FilePathWatcher class from browser/... to common/... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressed comments Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698