| 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; | 10 import 'package:path/path.dart' as p; |
| 11 | 11 |
| 12 import '../constructable_file_system_event.dart'; | 12 import '../constructable_file_system_event.dart'; |
| 13 import '../path_set.dart'; | 13 import '../path_set.dart'; |
| 14 import '../utils.dart'; | 14 import '../utils.dart'; |
| 15 import '../watch_event.dart'; | 15 import '../watch_event.dart'; |
| 16 import 'resubscribable.dart'; | 16 import 'resubscribable.dart'; |
| 17 | 17 |
| 18 /// Uses the FSEvents subsystem to watch for filesystem events. | 18 /// Uses the FSEvents subsystem to watch for filesystem events. |
| 19 /// | 19 /// |
| 20 /// 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 |
| 21 /// will occasionally report events that occurred before the filesystem watch | 21 /// will occasionally report events that occurred before the filesystem watch |
| 22 /// was initiated. Second, if multiple events happen to the same file in close | 22 /// was initiated. Second, if multiple events happen to the same file in close |
| 23 /// succession, it won't report them in the order they occurred. See issue | 23 /// succession, it won't report them in the order they occurred. See issue |
| 24 /// 14373. | 24 /// 14373. |
| 25 /// | 25 /// |
| 26 /// This also works around issues 14793, 14806, and 14849 in the implementation | 26 /// This also works around issues 14793, 14806, and 14849 in the implementation |
| 27 /// of [Directory.watch]. | 27 /// of [Directory.watch]. |
| 28 class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { | 28 class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { |
| 29 // TODO(nweiz): remove this when issue 15042 is fixed. | 29 // TODO(nweiz): remove these when issue 15042 is fixed. |
| 30 static bool logDebugInfo = false; | 30 static var logDebugInfo = false; |
| 31 static var _count = 0; |
| 32 final int _id; |
| 31 | 33 |
| 32 MacOSDirectoryWatcher(String directory) | 34 MacOSDirectoryWatcher(String directory) |
| 33 : super(directory, () => new _MacOSDirectoryWatcher(directory)); | 35 : _id = _count++, |
| 36 super(directory, () => new _MacOSDirectoryWatcher(directory, _count)); |
| 34 } | 37 } |
| 35 | 38 |
| 36 class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { | 39 class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { |
| 40 // TODO(nweiz): remove these when issue 15042 is fixed. |
| 41 static var _count = 0; |
| 42 final String _id; |
| 43 |
| 37 final String directory; | 44 final String directory; |
| 38 | 45 |
| 39 Stream<WatchEvent> get events => _eventsController.stream; | 46 Stream<WatchEvent> get events => _eventsController.stream; |
| 40 final _eventsController = new StreamController<WatchEvent>.broadcast(); | 47 final _eventsController = new StreamController<WatchEvent>.broadcast(); |
| 41 | 48 |
| 42 bool get isReady => _readyCompleter.isCompleted; | 49 bool get isReady => _readyCompleter.isCompleted; |
| 43 | 50 |
| 44 Future get ready => _readyCompleter.future; | 51 Future get ready => _readyCompleter.future; |
| 45 final _readyCompleter = new Completer(); | 52 final _readyCompleter = new Completer(); |
| 46 | 53 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 66 /// This is separate from [_subscriptions] because this stream occasionally | 73 /// This is separate from [_subscriptions] because this stream occasionally |
| 67 /// needs to be resubscribed in order to work around issue 14849. | 74 /// needs to be resubscribed in order to work around issue 14849. |
| 68 StreamSubscription<FileSystemEvent> _watchSubscription; | 75 StreamSubscription<FileSystemEvent> _watchSubscription; |
| 69 | 76 |
| 70 /// A set of subscriptions that this watcher subscribes to. | 77 /// A set of subscriptions that this watcher subscribes to. |
| 71 /// | 78 /// |
| 72 /// These are gathered together so that they may all be canceled when the | 79 /// These are gathered together so that they may all be canceled when the |
| 73 /// watcher is closed. This does not include [_watchSubscription]. | 80 /// watcher is closed. This does not include [_watchSubscription]. |
| 74 final _subscriptions = new Set<StreamSubscription>(); | 81 final _subscriptions = new Set<StreamSubscription>(); |
| 75 | 82 |
| 76 _MacOSDirectoryWatcher(String directory) | 83 _MacOSDirectoryWatcher(String directory, int parentId) |
| 77 : directory = directory, | 84 : directory = directory, |
| 78 _files = new PathSet(directory) { | 85 _files = new PathSet(directory), |
| 86 _id = "$parentId/${_count++}" { |
| 79 _startWatch(); | 87 _startWatch(); |
| 80 | 88 |
| 81 _listen(new Directory(directory).list(recursive: true), | 89 _listen(new Directory(directory).list(recursive: true), |
| 82 (entity) { | 90 (entity) { |
| 83 if (entity is! Directory) _files.add(entity.path); | 91 if (entity is! Directory) _files.add(entity.path); |
| 84 }, | 92 }, |
| 85 onError: _emitError, | 93 onError: _emitError, |
| 86 onDone: () { | 94 onDone: () { |
| 87 if (MacOSDirectoryWatcher.logDebugInfo) { | 95 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 88 print("watcher is ready"); | 96 print("[$_id] watcher is ready, known files:"); |
| 97 for (var file in _files.toSet()) { |
| 98 print("[$_id] ${p.relative(file, from: directory)}"); |
| 99 } |
| 89 } | 100 } |
| 90 _readyCompleter.complete(); | 101 _readyCompleter.complete(); |
| 91 }, | 102 }, |
| 92 cancelOnError: true); | 103 cancelOnError: true); |
| 93 } | 104 } |
| 94 | 105 |
| 95 void close() { | 106 void close() { |
| 107 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 108 print("[$_id] watcher is closed"); |
| 109 } |
| 96 for (var subscription in _subscriptions) { | 110 for (var subscription in _subscriptions) { |
| 97 subscription.cancel(); | 111 subscription.cancel(); |
| 98 } | 112 } |
| 99 _subscriptions.clear(); | 113 _subscriptions.clear(); |
| 100 if (_watchSubscription != null) _watchSubscription.cancel(); | 114 if (_watchSubscription != null) _watchSubscription.cancel(); |
| 101 _watchSubscription = null; | 115 _watchSubscription = null; |
| 102 _eventsController.close(); | 116 _eventsController.close(); |
| 103 } | 117 } |
| 104 | 118 |
| 105 /// The callback that's run when [Directory.watch] emits a batch of events. | 119 /// The callback that's run when [Directory.watch] emits a batch of events. |
| 106 void _onBatch(List<FileSystemEvent> batch) { | 120 void _onBatch(List<FileSystemEvent> batch) { |
| 107 if (MacOSDirectoryWatcher.logDebugInfo) { | 121 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 108 print("======== batch:"); | 122 print("[$_id] ======== batch:"); |
| 109 for (var event in batch) { | 123 for (var event in batch) { |
| 110 print(" ${_formatEvent(event)}"); | 124 print("[$_id] ${_formatEvent(event)}"); |
| 111 } | 125 } |
| 112 | 126 |
| 113 print("known files:"); | 127 print("[$_id] known files:"); |
| 114 for (var foo in _files.toSet()) { | 128 for (var file in _files.toSet()) { |
| 115 print(" ${p.relative(foo, from: directory)}"); | 129 print("[$_id] ${p.relative(file, from: directory)}"); |
| 116 } | 130 } |
| 117 } | 131 } |
| 118 | 132 |
| 119 batches++; | 133 batches++; |
| 120 | 134 |
| 121 _sortEvents(batch).forEach((path, events) { | 135 _sortEvents(batch).forEach((path, events) { |
| 122 var relativePath = p.relative(path, from: directory); | 136 var relativePath = p.relative(path, from: directory); |
| 123 if (MacOSDirectoryWatcher.logDebugInfo) { | 137 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 124 print("events for $relativePath:\n"); | 138 print("[$_id] events for $relativePath:\n"); |
| 125 for (var event in events) { | 139 for (var event in events) { |
| 126 print(" ${_formatEvent(event)}"); | 140 print("[$_id] ${_formatEvent(event)}"); |
| 127 } | 141 } |
| 128 } | 142 } |
| 129 | 143 |
| 130 var canonicalEvent = _canonicalEvent(events); | 144 var canonicalEvent = _canonicalEvent(events); |
| 131 events = canonicalEvent == null ? | 145 events = canonicalEvent == null ? |
| 132 _eventsBasedOnFileSystem(path) : [canonicalEvent]; | 146 _eventsBasedOnFileSystem(path) : [canonicalEvent]; |
| 133 if (MacOSDirectoryWatcher.logDebugInfo) { | 147 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 134 print("canonical event for $relativePath: " | 148 print("[$_id] canonical event for $relativePath: " |
| 135 "${_formatEvent(canonicalEvent)}"); | 149 "${_formatEvent(canonicalEvent)}"); |
| 136 print("actionable events for $relativePath: " | 150 print("[$_id] actionable events for $relativePath: " |
| 137 "${events.map(_formatEvent)}"); | 151 "${events.map(_formatEvent)}"); |
| 138 } | 152 } |
| 139 | 153 |
| 140 for (var event in events) { | 154 for (var event in events) { |
| 141 if (event is FileSystemCreateEvent) { | 155 if (event is FileSystemCreateEvent) { |
| 142 if (!event.isDirectory) { | 156 if (!event.isDirectory) { |
| 143 _emitEvent(ChangeType.ADD, path); | 157 _emitEvent(ChangeType.ADD, path); |
| 144 _files.add(path); | 158 _files.add(path); |
| 145 continue; | 159 continue; |
| 146 } | 160 } |
| 147 | 161 |
| 148 _listen(new Directory(path).list(recursive: true), (entity) { | 162 _listen(new Directory(path).list(recursive: true), (entity) { |
| 149 if (entity is Directory) return; | 163 if (entity is Directory) return; |
| 150 _emitEvent(ChangeType.ADD, entity.path); | 164 _emitEvent(ChangeType.ADD, entity.path); |
| 151 _files.add(entity.path); | 165 _files.add(entity.path); |
| 152 }, onError: (e, stackTrace) { | 166 }, onError: (e, stackTrace) { |
| 153 if (MacOSDirectoryWatcher.logDebugInfo) { | 167 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 154 print("got error listing $relativePath: $e"); | 168 print("[$_id] got error listing $relativePath: $e"); |
| 155 } | 169 } |
| 156 _emitError(e, stackTrace); | 170 _emitError(e, stackTrace); |
| 157 }, cancelOnError: true); | 171 }, cancelOnError: true); |
| 158 } else if (event is FileSystemModifyEvent) { | 172 } else if (event is FileSystemModifyEvent) { |
| 159 assert(!event.isDirectory); | 173 assert(!event.isDirectory); |
| 160 _emitEvent(ChangeType.MODIFY, path); | 174 _emitEvent(ChangeType.MODIFY, path); |
| 161 } else { | 175 } else { |
| 162 assert(event is FileSystemDeleteEvent); | 176 assert(event is FileSystemDeleteEvent); |
| 163 for (var removedPath in _files.remove(path)) { | 177 for (var removedPath in _files.remove(path)) { |
| 164 _emitEvent(ChangeType.REMOVE, removedPath); | 178 _emitEvent(ChangeType.REMOVE, removedPath); |
| 165 } | 179 } |
| 166 } | 180 } |
| 167 } | 181 } |
| 168 }); | 182 }); |
| 169 | 183 |
| 170 if (MacOSDirectoryWatcher.logDebugInfo) { | 184 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 171 print("========"); | 185 print("[$_id] ======== batch complete"); |
| 172 } | 186 } |
| 173 } | 187 } |
| 174 | 188 |
| 175 /// Sort all the events in a batch into sets based on their path. | 189 /// Sort all the events in a batch into sets based on their path. |
| 176 /// | 190 /// |
| 177 /// A single input event may result in multiple events in the returned map; | 191 /// A single input event may result in multiple events in the returned map; |
| 178 /// for example, a MOVE event becomes a DELETE event for the source and a | 192 /// for example, a MOVE event becomes a DELETE event for the source and a |
| 179 /// CREATE event for the destination. | 193 /// CREATE event for the destination. |
| 180 /// | 194 /// |
| 181 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it | 195 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 300 /// to the user, unlike the batched events from [Directory.watch]. The | 314 /// to the user, unlike the batched events from [Directory.watch]. The |
| 301 /// returned list may be empty, indicating that no changes occurred to [path] | 315 /// returned list may be empty, indicating that no changes occurred to [path] |
| 302 /// (probably indicating that it was created and then immediately deleted). | 316 /// (probably indicating that it was created and then immediately deleted). |
| 303 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { | 317 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { |
| 304 var fileExisted = _files.contains(path); | 318 var fileExisted = _files.contains(path); |
| 305 var dirExisted = _files.containsDir(path); | 319 var dirExisted = _files.containsDir(path); |
| 306 var fileExists = new File(path).existsSync(); | 320 var fileExists = new File(path).existsSync(); |
| 307 var dirExists = new Directory(path).existsSync(); | 321 var dirExists = new Directory(path).existsSync(); |
| 308 | 322 |
| 309 if (MacOSDirectoryWatcher.logDebugInfo) { | 323 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 310 print("file existed: $fileExisted"); | 324 print("[$_id] file existed: $fileExisted"); |
| 311 print("dir existed: $dirExisted"); | 325 print("[$_id] dir existed: $dirExisted"); |
| 312 print("file exists: $fileExists"); | 326 print("[$_id] file exists: $fileExists"); |
| 313 print("dir exists: $dirExists"); | 327 print("[$_id] dir exists: $dirExists"); |
| 314 } | 328 } |
| 315 | 329 |
| 316 var events = []; | 330 var events = []; |
| 317 if (fileExisted) { | 331 if (fileExisted) { |
| 318 if (fileExists) { | 332 if (fileExists) { |
| 319 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); | 333 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); |
| 320 } else { | 334 } else { |
| 321 events.add(new ConstructableFileSystemDeleteEvent(path, false)); | 335 events.add(new ConstructableFileSystemDeleteEvent(path, false)); |
| 322 } | 336 } |
| 323 } else if (dirExisted) { | 337 } else if (dirExisted) { |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 376 /// Emit an event with the given [type] and [path]. | 390 /// Emit an event with the given [type] and [path]. |
| 377 void _emitEvent(ChangeType type, String path) { | 391 void _emitEvent(ChangeType type, String path) { |
| 378 if (!isReady) return; | 392 if (!isReady) return; |
| 379 | 393 |
| 380 // Don't emit ADD events for files that we already know about. Such an event | 394 // Don't emit ADD events for files that we already know about. Such an event |
| 381 // probably comes from FSEvents reporting an add that happened prior to the | 395 // probably comes from FSEvents reporting an add that happened prior to the |
| 382 // watch beginning. | 396 // watch beginning. |
| 383 if (type == ChangeType.ADD && _files.contains(path)) return; | 397 if (type == ChangeType.ADD && _files.contains(path)) return; |
| 384 | 398 |
| 385 if (MacOSDirectoryWatcher.logDebugInfo) { | 399 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 386 print("emitting $type ${p.relative(path, from: directory)}"); | 400 print("[$_id] emitting $type ${p.relative(path, from: directory)}"); |
| 387 } | 401 } |
| 388 | 402 |
| 389 _eventsController.add(new WatchEvent(type, path)); | 403 _eventsController.add(new WatchEvent(type, path)); |
| 390 } | 404 } |
| 391 | 405 |
| 392 /// Emit an error, then close the watcher. | 406 /// Emit an error, then close the watcher. |
| 393 void _emitError(error, StackTrace stackTrace) { | 407 void _emitError(error, StackTrace stackTrace) { |
| 394 _eventsController.addError(error, stackTrace); | 408 _eventsController.addError(error, stackTrace); |
| 395 close(); | 409 close(); |
| 396 } | 410 } |
| (...skipping 22 matching lines...) Expand all Loading... |
| 419 } else if (event is FileSystemDeleteEvent) { | 433 } else if (event is FileSystemDeleteEvent) { |
| 420 return "delete $type $path"; | 434 return "delete $type $path"; |
| 421 } else if (event is FileSystemModifyEvent) { | 435 } else if (event is FileSystemModifyEvent) { |
| 422 return "modify $type $path"; | 436 return "modify $type $path"; |
| 423 } else if (event is FileSystemMoveEvent) { | 437 } else if (event is FileSystemMoveEvent) { |
| 424 return "move $type $path to " | 438 return "move $type $path to " |
| 425 "${p.relative(event.destination, from: directory)}"; | 439 "${p.relative(event.destination, from: directory)}"; |
| 426 } | 440 } |
| 427 } | 441 } |
| 428 } | 442 } |
| OLD | NEW |