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