OLD | NEW |
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 'package:path/path.dart' as p; | |
11 | |
12 import '../constructable_file_system_event.dart'; | 10 import '../constructable_file_system_event.dart'; |
13 import '../path_set.dart'; | 11 import '../path_set.dart'; |
14 import '../utils.dart'; | 12 import '../utils.dart'; |
15 import '../watch_event.dart'; | 13 import '../watch_event.dart'; |
16 import 'resubscribable.dart'; | 14 import 'resubscribable.dart'; |
17 | 15 |
18 /// Uses the FSEvents subsystem to watch for filesystem events. | 16 /// Uses the FSEvents subsystem to watch for filesystem events. |
19 /// | 17 /// |
20 /// 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 |
21 /// will occasionally report events that occurred before the filesystem watch | 19 /// will occasionally report events that occurred before the filesystem watch |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
73 _MacOSDirectoryWatcher(String directory) | 71 _MacOSDirectoryWatcher(String directory) |
74 : directory = directory, | 72 : directory = directory, |
75 _files = new PathSet(directory) { | 73 _files = new PathSet(directory) { |
76 _startWatch(); | 74 _startWatch(); |
77 | 75 |
78 _listen(new Directory(directory).list(recursive: true), | 76 _listen(new Directory(directory).list(recursive: true), |
79 (entity) { | 77 (entity) { |
80 if (entity is! Directory) _files.add(entity.path); | 78 if (entity is! Directory) _files.add(entity.path); |
81 }, | 79 }, |
82 onError: _emitError, | 80 onError: _emitError, |
83 onDone: () { | 81 onDone: _readyCompleter.complete, |
84 if (_runningOnBuildbot) { | |
85 print("watcher is ready"); | |
86 } | |
87 _readyCompleter.complete(); | |
88 }, | |
89 cancelOnError: true); | 82 cancelOnError: true); |
90 } | 83 } |
91 | 84 |
92 void close() { | 85 void close() { |
93 for (var subscription in _subscriptions) { | 86 for (var subscription in _subscriptions) { |
94 subscription.cancel(); | 87 subscription.cancel(); |
95 } | 88 } |
96 _subscriptions.clear(); | 89 _subscriptions.clear(); |
97 if (_watchSubscription != null) _watchSubscription.cancel(); | 90 if (_watchSubscription != null) _watchSubscription.cancel(); |
98 _watchSubscription = null; | 91 _watchSubscription = null; |
99 _eventsController.close(); | 92 _eventsController.close(); |
100 } | 93 } |
101 | 94 |
102 /// The callback that's run when [Directory.watch] emits a batch of events. | 95 /// The callback that's run when [Directory.watch] emits a batch of events. |
103 void _onBatch(List<FileSystemEvent> batch) { | 96 void _onBatch(List<FileSystemEvent> batch) { |
104 if (_runningOnBuildbot) { | |
105 print("======== batch:"); | |
106 for (var event in batch) { | |
107 print(" ${_formatEvent(event)}"); | |
108 } | |
109 | |
110 print("known files:"); | |
111 for (var foo in _files.toSet()) { | |
112 print(" ${p.relative(foo, from: directory)}"); | |
113 } | |
114 } | |
115 | |
116 batches++; | 97 batches++; |
117 | 98 |
118 _sortEvents(batch).forEach((path, events) { | 99 _sortEvents(batch).forEach((path, events) { |
119 var relativePath = p.relative(path, from: directory); | |
120 if (_runningOnBuildbot) { | |
121 print("events for $relativePath:\n"); | |
122 for (var event in events) { | |
123 print(" ${_formatEvent(event)}"); | |
124 } | |
125 } | |
126 | |
127 var canonicalEvent = _canonicalEvent(events); | 100 var canonicalEvent = _canonicalEvent(events); |
128 events = canonicalEvent == null ? | 101 events = canonicalEvent == null ? |
129 _eventsBasedOnFileSystem(path) : [canonicalEvent]; | 102 _eventsBasedOnFileSystem(path) : [canonicalEvent]; |
130 if (_runningOnBuildbot) { | |
131 print("canonical event for $relativePath: " | |
132 "${_formatEvent(canonicalEvent)}"); | |
133 print("actionable events for $relativePath: " | |
134 "${events.map(_formatEvent)}"); | |
135 } | |
136 | 103 |
137 for (var event in events) { | 104 for (var event in events) { |
138 if (event is FileSystemCreateEvent) { | 105 if (event is FileSystemCreateEvent) { |
139 if (!event.isDirectory) { | 106 if (!event.isDirectory) { |
140 _emitEvent(ChangeType.ADD, path); | 107 _emitEvent(ChangeType.ADD, path); |
141 _files.add(path); | 108 _files.add(path); |
142 continue; | 109 continue; |
143 } | 110 } |
144 | 111 |
145 _listen(new Directory(path).list(recursive: true), (entity) { | 112 _listen(new Directory(path).list(recursive: true), (entity) { |
146 if (entity is Directory) return; | 113 if (entity is Directory) return; |
147 _emitEvent(ChangeType.ADD, entity.path); | 114 _emitEvent(ChangeType.ADD, entity.path); |
148 _files.add(entity.path); | 115 _files.add(entity.path); |
149 }, onError: (e, stackTrace) { | 116 }, onError: _emitError, cancelOnError: true); |
150 if (_runningOnBuildbot) { | |
151 print("got error listing $relativePath: $e"); | |
152 } | |
153 _emitError(e, stackTrace); | |
154 }, cancelOnError: true); | |
155 } else if (event is FileSystemModifyEvent) { | 117 } else if (event is FileSystemModifyEvent) { |
156 assert(!event.isDirectory); | 118 assert(!event.isDirectory); |
157 _emitEvent(ChangeType.MODIFY, path); | 119 _emitEvent(ChangeType.MODIFY, path); |
158 } else { | 120 } else { |
159 assert(event is FileSystemDeleteEvent); | 121 assert(event is FileSystemDeleteEvent); |
160 for (var removedPath in _files.remove(path)) { | 122 for (var removedPath in _files.remove(path)) { |
161 _emitEvent(ChangeType.REMOVE, removedPath); | 123 _emitEvent(ChangeType.REMOVE, removedPath); |
162 } | 124 } |
163 } | 125 } |
164 } | 126 } |
165 }); | 127 }); |
166 | |
167 if (_runningOnBuildbot) { | |
168 print("========"); | |
169 } | |
170 } | 128 } |
171 | 129 |
172 /// Sort all the events in a batch into sets based on their path. | 130 /// Sort all the events in a batch into sets based on their path. |
173 /// | 131 /// |
174 /// A single input event may result in multiple events in the returned map; | 132 /// A single input event may result in multiple events in the returned map; |
175 /// for example, a MOVE event becomes a DELETE event for the source and a | 133 /// for example, a MOVE event becomes a DELETE event for the source and a |
176 /// CREATE event for the destination. | 134 /// CREATE event for the destination. |
177 /// | 135 /// |
178 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it | 136 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it |
179 /// contain any events relating to [directory]. | 137 /// contain any events relating to [directory]. |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
296 /// This returns a list whose order should be reflected in the events emitted | 254 /// This returns a list whose order should be reflected in the events emitted |
297 /// to the user, unlike the batched events from [Directory.watch]. The | 255 /// to the user, unlike the batched events from [Directory.watch]. The |
298 /// returned list may be empty, indicating that no changes occurred to [path] | 256 /// returned list may be empty, indicating that no changes occurred to [path] |
299 /// (probably indicating that it was created and then immediately deleted). | 257 /// (probably indicating that it was created and then immediately deleted). |
300 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { | 258 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { |
301 var fileExisted = _files.contains(path); | 259 var fileExisted = _files.contains(path); |
302 var dirExisted = _files.containsDir(path); | 260 var dirExisted = _files.containsDir(path); |
303 var fileExists = new File(path).existsSync(); | 261 var fileExists = new File(path).existsSync(); |
304 var dirExists = new Directory(path).existsSync(); | 262 var dirExists = new Directory(path).existsSync(); |
305 | 263 |
306 if (_runningOnBuildbot) { | |
307 print("file existed: $fileExisted"); | |
308 print("dir existed: $dirExisted"); | |
309 print("file exists: $fileExists"); | |
310 print("dir exists: $dirExists"); | |
311 } | |
312 | |
313 var events = []; | 264 var events = []; |
314 if (fileExisted) { | 265 if (fileExisted) { |
315 if (fileExists) { | 266 if (fileExists) { |
316 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); | 267 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); |
317 } else { | 268 } else { |
318 events.add(new ConstructableFileSystemDeleteEvent(path, false)); | 269 events.add(new ConstructableFileSystemDeleteEvent(path, false)); |
319 } | 270 } |
320 } else if (dirExisted) { | 271 } else if (dirExisted) { |
321 if (dirExists) { | 272 if (dirExists) { |
322 // If we got contradictory events for a directory that used to exist and | 273 // If we got contradictory events for a directory that used to exist and |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
372 | 323 |
373 /// Emit an event with the given [type] and [path]. | 324 /// Emit an event with the given [type] and [path]. |
374 void _emitEvent(ChangeType type, String path) { | 325 void _emitEvent(ChangeType type, String path) { |
375 if (!isReady) return; | 326 if (!isReady) return; |
376 | 327 |
377 // Don't emit ADD events for files that we already know about. Such an event | 328 // Don't emit ADD events for files that we already know about. Such an event |
378 // probably comes from FSEvents reporting an add that happened prior to the | 329 // probably comes from FSEvents reporting an add that happened prior to the |
379 // watch beginning. | 330 // watch beginning. |
380 if (type == ChangeType.ADD && _files.contains(path)) return; | 331 if (type == ChangeType.ADD && _files.contains(path)) return; |
381 | 332 |
382 if (_runningOnBuildbot) { | |
383 print("emitting $type ${p.relative(path, from: directory)}"); | |
384 } | |
385 | |
386 _eventsController.add(new WatchEvent(type, path)); | 333 _eventsController.add(new WatchEvent(type, path)); |
387 } | 334 } |
388 | 335 |
389 /// Emit an error, then close the watcher. | 336 /// Emit an error, then close the watcher. |
390 void _emitError(error, StackTrace stackTrace) { | 337 void _emitError(error, StackTrace stackTrace) { |
391 _eventsController.addError(error, stackTrace); | 338 _eventsController.addError(error, stackTrace); |
392 close(); | 339 close(); |
393 } | 340 } |
394 | 341 |
395 /// Like [Stream.listen], but automatically adds the subscription to | 342 /// Like [Stream.listen], but automatically adds the subscription to |
396 /// [_subscriptions] so that it can be canceled when [close] is called. | 343 /// [_subscriptions] so that it can be canceled when [close] is called. |
397 void _listen(Stream stream, void onData(event), {Function onError, | 344 void _listen(Stream stream, void onData(event), {Function onError, |
398 void onDone(), bool cancelOnError}) { | 345 void onDone(), bool cancelOnError}) { |
399 var subscription; | 346 var subscription; |
400 subscription = stream.listen(onData, onError: onError, onDone: () { | 347 subscription = stream.listen(onData, onError: onError, onDone: () { |
401 _subscriptions.remove(subscription); | 348 _subscriptions.remove(subscription); |
402 if (onDone != null) onDone(); | 349 if (onDone != null) onDone(); |
403 }, cancelOnError: cancelOnError); | 350 }, cancelOnError: cancelOnError); |
404 _subscriptions.add(subscription); | 351 _subscriptions.add(subscription); |
405 } | 352 } |
406 | |
407 /// Return a human-friendly string representation of [event]. | |
408 String _formatEvent(FileSystemEvent event) { | |
409 if (event == null) return 'null'; | |
410 | |
411 var path = p.relative(event.path, from: directory); | |
412 var type = event.isDirectory ? 'directory' : 'file'; | |
413 if (event is FileSystemCreateEvent) { | |
414 return "create $type $path"; | |
415 } else if (event is FileSystemDeleteEvent) { | |
416 return "delete $type $path"; | |
417 } else if (event is FileSystemModifyEvent) { | |
418 return "modify $type $path"; | |
419 } else if (event is FileSystemMoveEvent) { | |
420 return "move $type $path to " | |
421 "${p.relative(event.destination, from: directory)}"; | |
422 } | |
423 } | |
424 | |
425 // TODO(nweiz): remove this when the buildbots calm down. | |
426 /// Whether this code is running on a buildbot. | |
427 bool get _runningOnBuildbot => | |
428 Platform.environment['LOGNAME'] == 'chrome-bot'; | |
429 } | 353 } |
OLD | NEW |