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

Side by Side Diff: pkg/watcher/lib/src/directory_watcher/mac_os.dart

Issue 100823005: Update the Mac OS watcher in pkg/watcher. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years 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
« no previous file with comments | « pkg/pkg.status ('k') | pkg/watcher/test/directory_watcher/mac_os_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library watcher.directory_watcher.mac_os; 5 library watcher.directory_watcher.mac_os;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:io'; 8 import 'dart:io';
9 9
10 import '../constructable_file_system_event.dart'; 10 import '../constructable_file_system_event.dart';
11 import '../path_set.dart'; 11 import '../path_set.dart';
12 import '../utils.dart'; 12 import '../utils.dart';
13 import '../watch_event.dart'; 13 import '../watch_event.dart';
14 import 'resubscribable.dart'; 14 import 'resubscribable.dart';
15 15
16 /// Uses the FSEvents subsystem to watch for filesystem events. 16 /// Uses the FSEvents subsystem to watch for filesystem events.
17 /// 17 ///
18 /// FSEvents has two main idiosyncrasies that this class works around. First, it 18 /// FSEvents has two main idiosyncrasies that this class works around. First, it
19 /// will occasionally report events that occurred before the filesystem watch 19 /// will occasionally report events that occurred before the filesystem watch
20 /// was initiated. Second, if multiple events happen to the same file in close 20 /// was initiated. Second, if multiple events happen to the same file in close
21 /// succession, it won't report them in the order they occurred. See issue 21 /// succession, it won't report them in the order they occurred. See issue
22 /// 14373. 22 /// 14373.
23 /// 23 ///
24 /// This also works around issues 14793, 14806, and 14849 in the implementation 24 /// This also works around issues 15458 and 14849 in the implementation of
25 /// of [Directory.watch]. 25 /// [Directory.watch].
26 class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { 26 class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher {
27 MacOSDirectoryWatcher(String directory) 27 MacOSDirectoryWatcher(String directory)
28 : super(directory, () => new _MacOSDirectoryWatcher(directory)); 28 : super(directory, () => new _MacOSDirectoryWatcher(directory));
29 } 29 }
30 30
31 class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { 31 class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
32 final String directory; 32 final String directory;
33 33
34 Stream<WatchEvent> get events => _eventsController.stream; 34 Stream<WatchEvent> get events => _eventsController.stream;
35 final _eventsController = new StreamController<WatchEvent>.broadcast(); 35 final _eventsController = new StreamController<WatchEvent>.broadcast();
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
97 batches++; 97 batches++;
98 98
99 _sortEvents(batch).forEach((path, events) { 99 _sortEvents(batch).forEach((path, events) {
100 var canonicalEvent = _canonicalEvent(events); 100 var canonicalEvent = _canonicalEvent(events);
101 events = canonicalEvent == null ? 101 events = canonicalEvent == null ?
102 _eventsBasedOnFileSystem(path) : [canonicalEvent]; 102 _eventsBasedOnFileSystem(path) : [canonicalEvent];
103 103
104 for (var event in events) { 104 for (var event in events) {
105 if (event is FileSystemCreateEvent) { 105 if (event is FileSystemCreateEvent) {
106 if (!event.isDirectory) { 106 if (!event.isDirectory) {
107 // Don't emit ADD events for files or directories that we already
108 // know about. Such an event comes from FSEvents reporting an add
109 // that happened prior to the watch beginning.
110 if (_files.contains(path)) continue;
111
107 _emitEvent(ChangeType.ADD, path); 112 _emitEvent(ChangeType.ADD, path);
108 _files.add(path); 113 _files.add(path);
109 continue; 114 continue;
110 } 115 }
111 116
117 if (_files.containsDir(path)) continue;
118
112 _listen(new Directory(path).list(recursive: true), (entity) { 119 _listen(new Directory(path).list(recursive: true), (entity) {
113 if (entity is Directory) return; 120 if (entity is Directory) return;
114 _emitEvent(ChangeType.ADD, entity.path); 121 _emitEvent(ChangeType.ADD, entity.path);
115 _files.add(entity.path); 122 _files.add(entity.path);
116 }, onError: _emitError, cancelOnError: true); 123 }, onError: _emitError, cancelOnError: true);
117 } else if (event is FileSystemModifyEvent) { 124 } else if (event is FileSystemModifyEvent) {
118 assert(!event.isDirectory); 125 assert(!event.isDirectory);
119 _emitEvent(ChangeType.MODIFY, path); 126 _emitEvent(ChangeType.MODIFY, path);
120 } else { 127 } else {
121 assert(event is FileSystemDeleteEvent); 128 assert(event is FileSystemDeleteEvent);
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
154 161
155 isInModifiedDirectory(path) => 162 isInModifiedDirectory(path) =>
156 directories.any((dir) => path != dir && path.startsWith(dir)); 163 directories.any((dir) => path != dir && path.startsWith(dir));
157 164
158 addEvent(path, event) { 165 addEvent(path, event) {
159 if (isInModifiedDirectory(path)) return; 166 if (isInModifiedDirectory(path)) return;
160 var set = eventsForPaths.putIfAbsent(path, () => new Set()); 167 var set = eventsForPaths.putIfAbsent(path, () => new Set());
161 set.add(event); 168 set.add(event);
162 } 169 }
163 170
164 for (var event in batch.where((event) => event is! FileSystemMoveEvent)) { 171 for (var event in batch) {
172 // The Mac OS watcher doesn't emit move events. See issue 14806.
173 assert(event is! FileSystemMoveEvent);
165 addEvent(event.path, event); 174 addEvent(event.path, event);
166 } 175 }
167 176
168 // Issue 14806 means that move events can be misleading if they're in the
169 // same batch as another modification of a related file. If they are, we
170 // make the event set empty to ensure we check the state of the filesystem.
171 // Otherwise, treat them as a DELETE followed by an ADD.
172 for (var event in batch.where((event) => event is FileSystemMoveEvent)) {
173 if (eventsForPaths.containsKey(event.path) ||
174 eventsForPaths.containsKey(event.destination)) {
175
176 if (!isInModifiedDirectory(event.path)) {
177 eventsForPaths[event.path] = new Set();
178 }
179 if (!isInModifiedDirectory(event.destination)) {
180 eventsForPaths[event.destination] = new Set();
181 }
182
183 continue;
184 }
185
186 addEvent(event.path, new ConstructableFileSystemDeleteEvent(
187 event.path, event.isDirectory));
188 addEvent(event.destination, new ConstructableFileSystemCreateEvent(
189 event.path, event.isDirectory));
190 }
191
192 return eventsForPaths; 177 return eventsForPaths;
193 } 178 }
194 179
195 /// Returns the canonical event from a batch of events on the same path, if 180 /// Returns the canonical event from a batch of events on the same path, if
196 /// one exists. 181 /// one exists.
197 /// 182 ///
198 /// If [batch] doesn't contain any contradictory events (e.g. DELETE and 183 /// If [batch] doesn't contain any contradictory events (e.g. DELETE and
199 /// CREATE, or events with different values for [isDirectory]), this returns a 184 /// CREATE, or events with different values for [isDirectory]), this returns a
200 /// single event that describes what happened to the path in question. 185 /// single event that describes what happened to the path in question.
201 /// 186 ///
202 /// If [batch] does contain contradictory events, this returns `null` to 187 /// If [batch] does contain contradictory events, this returns `null` to
203 /// indicate that the state of the path on the filesystem should be checked to 188 /// indicate that the state of the path on the filesystem should be checked to
204 /// determine what occurred. 189 /// determine what occurred.
205 FileSystemEvent _canonicalEvent(Set<FileSystemEvent> batch) { 190 FileSystemEvent _canonicalEvent(Set<FileSystemEvent> batch) {
206 // An empty batch indicates that we've learned earlier that the batch is 191 // An empty batch indicates that we've learned earlier that the batch is
207 // contradictory (e.g. because of a move). 192 // contradictory (e.g. because of a move).
208 if (batch.isEmpty) return null; 193 if (batch.isEmpty) return null;
209 194
210 var type = batch.first.type; 195 var type = batch.first.type;
211 var isDir = batch.first.isDirectory; 196 var isDir = batch.first.isDirectory;
197 var hadModifyEvent = false;
212 198
213 for (var event in batch.skip(1)) { 199 for (var event in batch.skip(1)) {
214 // If one event reports that the file is a directory and another event 200 // If one event reports that the file is a directory and another event
215 // doesn't, that's a contradiction. 201 // doesn't, that's a contradiction.
216 if (isDir != event.isDirectory) return null; 202 if (isDir != event.isDirectory) return null;
217 203
218 // Modify events don't contradict either CREATE or REMOVE events. We can 204 // Modify events don't contradict either CREATE or REMOVE events. We can
219 // safely assume the file was modified after a CREATE or before the 205 // safely assume the file was modified after a CREATE or before the
220 // REMOVE; otherwise there will also be a REMOVE or CREATE event 206 // REMOVE; otherwise there will also be a REMOVE or CREATE event
221 // (respectively) that will be contradictory. 207 // (respectively) that will be contradictory.
222 if (event is FileSystemModifyEvent) continue; 208 if (event is FileSystemModifyEvent) {
209 hadModifyEvent = true;
210 continue;
211 }
223 assert(event is FileSystemCreateEvent || event is FileSystemDeleteEvent); 212 assert(event is FileSystemCreateEvent || event is FileSystemDeleteEvent);
224 213
225 // If we previously thought this was a MODIFY, we now consider it to be a 214 // If we previously thought this was a MODIFY, we now consider it to be a
226 // CREATE or REMOVE event. This is safe for the same reason as above. 215 // CREATE or REMOVE event. This is safe for the same reason as above.
227 if (type == FileSystemEvent.MODIFY) { 216 if (type == FileSystemEvent.MODIFY) {
228 type = event.type; 217 type = event.type;
229 continue; 218 continue;
230 } 219 }
231 220
232 // A CREATE event contradicts a REMOVE event and vice versa. 221 // A CREATE event contradicts a REMOVE event and vice versa.
233 assert(type == FileSystemEvent.CREATE || type == FileSystemEvent.DELETE); 222 assert(type == FileSystemEvent.CREATE || type == FileSystemEvent.DELETE);
234 if (type != event.type) return null; 223 if (type != event.type) return null;
235 } 224 }
236 225
226 // If we got a CREATE event for a file we already knew about, that comes
227 // from FSEvents reporting an add that happened prior to the watch
228 // beginning. If we also received a MODIFY event, we want to report that,
229 // but not the CREATE.
230 if (type == FileSystemEvent.CREATE && hadModifyEvent &&
231 _files.contains(batch.first.path)) {
232 type = FileSystemEvent.MODIFY;
233 }
234
237 switch (type) { 235 switch (type) {
238 case FileSystemEvent.CREATE: 236 case FileSystemEvent.CREATE:
239 // Issue 14793 means that CREATE events can actually mean DELETE, so we 237 return new ConstructableFileSystemCreateEvent(batch.first.path, isDir);
240 // should always check the filesystem for them.
241 return null;
242 case FileSystemEvent.DELETE: 238 case FileSystemEvent.DELETE:
243 return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); 239 // Issue 15458 means that DELETE events for directories can actually
240 // mean CREATE, so we always check the filesystem for them.
241 if (isDir) return null;
242 return new ConstructableFileSystemCreateEvent(batch.first.path, false);
244 case FileSystemEvent.MODIFY: 243 case FileSystemEvent.MODIFY:
245 return new ConstructableFileSystemModifyEvent( 244 return new ConstructableFileSystemModifyEvent(
246 batch.first.path, isDir, false); 245 batch.first.path, isDir, false);
247 default: assert(false); 246 default: assert(false);
248 } 247 }
249 } 248 }
250 249
251 /// Returns one or more events that describe the change between the last known 250 /// Returns one or more events that describe the change between the last known
252 /// state of [path] and its current state on the filesystem. 251 /// state of [path] and its current state on the filesystem.
253 /// 252 ///
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
318 new BatchedStreamTransformer<FileSystemEvent>()); 317 new BatchedStreamTransformer<FileSystemEvent>());
319 _watchSubscription = innerStream.listen(_onBatch, 318 _watchSubscription = innerStream.listen(_onBatch,
320 onError: _eventsController.addError, 319 onError: _eventsController.addError,
321 onDone: _onDone); 320 onDone: _onDone);
322 } 321 }
323 322
324 /// Emit an event with the given [type] and [path]. 323 /// Emit an event with the given [type] and [path].
325 void _emitEvent(ChangeType type, String path) { 324 void _emitEvent(ChangeType type, String path) {
326 if (!isReady) return; 325 if (!isReady) return;
327 326
328 // Don't emit ADD events for files that we already know about. Such an event
329 // probably comes from FSEvents reporting an add that happened prior to the
330 // watch beginning.
331 if (type == ChangeType.ADD && _files.contains(path)) return;
332
333 _eventsController.add(new WatchEvent(type, path)); 327 _eventsController.add(new WatchEvent(type, path));
334 } 328 }
335 329
336 /// Emit an error, then close the watcher. 330 /// Emit an error, then close the watcher.
337 void _emitError(error, StackTrace stackTrace) { 331 void _emitError(error, StackTrace stackTrace) {
338 _eventsController.addError(error, stackTrace); 332 _eventsController.addError(error, stackTrace);
339 close(); 333 close();
340 } 334 }
341 335
342 /// Like [Stream.listen], but automatically adds the subscription to 336 /// Like [Stream.listen], but automatically adds the subscription to
343 /// [_subscriptions] so that it can be canceled when [close] is called. 337 /// [_subscriptions] so that it can be canceled when [close] is called.
344 void _listen(Stream stream, void onData(event), {Function onError, 338 void _listen(Stream stream, void onData(event), {Function onError,
345 void onDone(), bool cancelOnError}) { 339 void onDone(), bool cancelOnError}) {
346 var subscription; 340 var subscription;
347 subscription = stream.listen(onData, onError: onError, onDone: () { 341 subscription = stream.listen(onData, onError: onError, onDone: () {
348 _subscriptions.remove(subscription); 342 _subscriptions.remove(subscription);
349 if (onDone != null) onDone(); 343 if (onDone != null) onDone();
350 }, cancelOnError: cancelOnError); 344 }, cancelOnError: cancelOnError);
351 _subscriptions.add(subscription); 345 _subscriptions.add(subscription);
352 } 346 }
353 } 347 }
OLDNEW
« no previous file with comments | « pkg/pkg.status ('k') | pkg/watcher/test/directory_watcher/mac_os_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698