| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 // TODO(rnystrom): Merge with mac_os version. | 4 // TODO(rnystrom): Merge with mac_os version. |
| 5 | 5 |
| 6 library watcher.directory_watcher.windows; | 6 library watcher.directory_watcher.windows; |
| 7 | 7 |
| 8 import 'dart:async'; | 8 import 'dart:async'; |
| 9 import 'dart:collection'; | 9 import 'dart:collection'; |
| 10 import 'dart:io'; | 10 import 'dart:io'; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 21 class WindowsDirectoryWatcher extends ResubscribableDirectoryWatcher { | 21 class WindowsDirectoryWatcher extends ResubscribableDirectoryWatcher { |
| 22 WindowsDirectoryWatcher(String directory) | 22 WindowsDirectoryWatcher(String directory) |
| 23 : super(directory, () => new _WindowsDirectoryWatcher(directory)); | 23 : super(directory, () => new _WindowsDirectoryWatcher(directory)); |
| 24 } | 24 } |
| 25 | 25 |
| 26 class _EventBatcher { | 26 class _EventBatcher { |
| 27 static const Duration _BATCH_DELAY = const Duration(milliseconds: 100); | 27 static const Duration _BATCH_DELAY = const Duration(milliseconds: 100); |
| 28 final List<FileSystemEvent> events = []; | 28 final List<FileSystemEvent> events = []; |
| 29 Timer timer; | 29 Timer timer; |
| 30 | 30 |
| 31 void addEvent(FileSystemEvent event) { | 31 void addEvent(FileSystemEvent event, void callback()) { |
| 32 events.add(event); | 32 events.add(event); |
| 33 } | |
| 34 | |
| 35 void startTimer(void callback()) { | |
| 36 if (timer != null) { | 33 if (timer != null) { |
| 37 timer.cancel(); | 34 timer.cancel(); |
| 38 } | 35 } |
| 39 timer = new Timer(_BATCH_DELAY, callback); | 36 timer = new Timer(_BATCH_DELAY, callback); |
| 40 } | 37 } |
| 41 | 38 |
| 42 void cancelTimer() { | 39 void cancelTimer() { |
| 43 timer.cancel(); | 40 timer.cancel(); |
| 44 } | 41 } |
| 45 } | 42 } |
| (...skipping 25 matching lines...) Expand all Loading... |
| 71 | 68 |
| 72 /// The subscription to the stream returned by [Directory.watch] of the | 69 /// The subscription to the stream returned by [Directory.watch] of the |
| 73 /// parent directory to [directory]. This is needed to detect changes to | 70 /// parent directory to [directory]. This is needed to detect changes to |
| 74 /// [directory], as they are not included on Windows. | 71 /// [directory], as they are not included on Windows. |
| 75 StreamSubscription<FileSystemEvent> _parentWatchSubscription; | 72 StreamSubscription<FileSystemEvent> _parentWatchSubscription; |
| 76 | 73 |
| 77 /// The subscription to the [Directory.list] call for the initial listing of | 74 /// The subscription to the [Directory.list] call for the initial listing of |
| 78 /// the directory to determine its initial state. | 75 /// the directory to determine its initial state. |
| 79 StreamSubscription<FileSystemEntity> _initialListSubscription; | 76 StreamSubscription<FileSystemEntity> _initialListSubscription; |
| 80 | 77 |
| 81 /// The subscriptions to the [Directory.list] call for listing the contents of | 78 /// The subscriptions to the [Directory.list] calls for listing the contents |
| 82 /// subdirectories that was moved into the watched directory. | 79 /// of subdirectories that were moved into the watched directory. |
| 83 final Set<StreamSubscription<FileSystemEntity>> _listSubscriptions | 80 final Set<StreamSubscription<FileSystemEntity>> _listSubscriptions |
| 84 = new HashSet<StreamSubscription<FileSystemEntity>>(); | 81 = new HashSet<StreamSubscription<FileSystemEntity>>(); |
| 85 | 82 |
| 86 _WindowsDirectoryWatcher(String directory) | 83 _WindowsDirectoryWatcher(String directory) |
| 87 : directory = directory, _files = new PathSet(directory) { | 84 : directory = directory, _files = new PathSet(directory) { |
| 88 _startWatch(); | |
| 89 _startParentWatcher(); | |
| 90 | |
| 91 // Before we're ready to emit events, wait for [_listDir] to complete. | 85 // Before we're ready to emit events, wait for [_listDir] to complete. |
| 92 _listDir().then(_readyCompleter.complete); | 86 _listDir().then((_) { |
| 87 _startWatch(); |
| 88 _startParentWatcher(); |
| 89 _readyCompleter.complete(); |
| 90 }); |
| 93 } | 91 } |
| 94 | 92 |
| 95 void close() { | 93 void close() { |
| 96 if (_watchSubscription != null) _watchSubscription.cancel(); | 94 if (_watchSubscription != null) _watchSubscription.cancel(); |
| 97 if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); | 95 if (_parentWatchSubscription != null) _parentWatchSubscription.cancel(); |
| 98 if (_initialListSubscription != null) _initialListSubscription.cancel(); | 96 if (_initialListSubscription != null) _initialListSubscription.cancel(); |
| 99 for (var sub in _listSubscriptions) { | 97 for (var sub in _listSubscriptions) { |
| 100 sub.cancel(); | 98 sub.cancel(); |
| 101 } | 99 } |
| 102 _listSubscriptions.clear(); | 100 _listSubscriptions.clear(); |
| 103 for (var batcher in _eventBatchers.values) { | 101 for (var batcher in _eventBatchers.values) { |
| 104 batcher.cancelTimer(); | 102 batcher.cancelTimer(); |
| 105 } | 103 } |
| 106 _eventBatchers.clear(); | 104 _eventBatchers.clear(); |
| 107 _watchSubscription = null; | 105 _watchSubscription = null; |
| 108 _parentWatchSubscription = null; | 106 _parentWatchSubscription = null; |
| 109 _initialListSubscription = null; | 107 _initialListSubscription = null; |
| 110 _eventsController.close(); | 108 _eventsController.close(); |
| 111 } | 109 } |
| 112 | 110 |
| 113 /// On Windows, if [directory] is deleted, we will not receive any event. | 111 /// On Windows, if [directory] is deleted, we will not receive any event. |
| 112 /// |
| 114 /// Instead, we add a watcher on the parent folder (if any), that can notify | 113 /// Instead, we add a watcher on the parent folder (if any), that can notify |
| 115 /// us about [directory]. | 114 /// us about [directory]. This also includes events such as moves. |
| 116 /// This also includes events such as moves. | |
| 117 void _startParentWatcher() { | 115 void _startParentWatcher() { |
| 118 var absoluteDir = p.absolute(directory); | 116 var absoluteDir = p.absolute(directory); |
| 119 var parent = p.dirname(absoluteDir); | 117 var parent = p.dirname(absoluteDir); |
| 120 // Check if we [directory] is already the root directory. | 118 // Check if [directory] is already the root directory. |
| 121 if (FileSystemEntity.identicalSync(parent, directory)) return; | 119 if (FileSystemEntity.identicalSync(parent, directory)) return; |
| 122 var parentStream = Chain.track( | 120 var parentStream = Chain.track( |
| 123 new Directory(parent).watch(recursive: false)); | 121 new Directory(parent).watch(recursive: false)); |
| 124 _parentWatchSubscription = parentStream.listen((event) { | 122 _parentWatchSubscription = parentStream.listen((event) { |
| 125 // Only look at events for 'directory'. | 123 // Only look at events for 'directory'. |
| 126 if (p.basename(event.path) != p.basename(absoluteDir)) return; | 124 if (p.basename(event.path) != p.basename(absoluteDir)) return; |
| 127 // Test if the directory is removed. FileSystemEntity.typeSync will | 125 // Test if the directory is removed. FileSystemEntity.typeSync will |
| 128 // return NOT_FOUND if it's unable to decide upon the type, including | 126 // return NOT_FOUND if it's unable to decide upon the type, including |
| 129 // access denied issues, which may happen when the directory is deleted. | 127 // access denied issues, which may happen when the directory is deleted. |
| 130 // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean | 128 // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean |
| 131 // the directory is now gone. | 129 // the directory is now gone. |
| 132 if (event is FileSystemMoveEvent || | 130 if (event is FileSystemMoveEvent || |
| 133 event is FileSystemDeleteEvent || | 131 event is FileSystemDeleteEvent || |
| 134 (FileSystemEntity.typeSync(directory) == | 132 (FileSystemEntity.typeSync(directory) == |
| 135 FileSystemEntityType.NOT_FOUND)) { | 133 FileSystemEntityType.NOT_FOUND)) { |
| 136 for (var path in _files.toSet()) { | 134 for (var path in _files.toSet()) { |
| 137 _emitEvent(ChangeType.REMOVE, path); | 135 _emitEvent(ChangeType.REMOVE, path); |
| 138 } | 136 } |
| 139 _files.clear(); | 137 _files.clear(); |
| 140 close(); | 138 close(); |
| 141 } | 139 } |
| 142 }, onError: (error) { | 140 }, onError: (error) { |
| 143 // Ignore errors, simply close the stream. | 141 // Ignore errors, simply close the stream. The user listens on |
| 142 // [directory], and while it can fail to listen on the parent, we may |
| 143 // still be able to listen on the path requested. |
| 144 _parentWatchSubscription.cancel(); | 144 _parentWatchSubscription.cancel(); |
| 145 _parentWatchSubscription = null; | 145 _parentWatchSubscription = null; |
| 146 }); | 146 }); |
| 147 } | 147 } |
| 148 | 148 |
| 149 void _onEvent(FileSystemEvent event) { | 149 void _onEvent(FileSystemEvent event) { |
| 150 // If we get a event before we're ready to begin emitting events, | 150 assert(isReady); |
| 151 // ignore those events and re-list the directory. | 151 final batcher = _eventBatchers.putIfAbsent( |
| 152 if (!isReady) { | |
| 153 _listDir().then((_) { | |
| 154 _readyCompleter.complete(); | |
| 155 }); | |
| 156 return; | |
| 157 } | |
| 158 | |
| 159 _EventBatcher batcher = _eventBatchers.putIfAbsent( | |
| 160 event.path, () => new _EventBatcher()); | 152 event.path, () => new _EventBatcher()); |
| 161 batcher.addEvent(event); | 153 batcher.addEvent(event, () { |
| 162 batcher.startTimer(() { | |
| 163 _eventBatchers.remove(event.path); | 154 _eventBatchers.remove(event.path); |
| 164 _onBatch(batcher.events); | 155 _onBatch(batcher.events); |
| 165 }); | 156 }); |
| 166 } | 157 } |
| 167 | 158 |
| 168 /// The callback that's run when [Directory.watch] emits a batch of events. | 159 /// The callback that's run when [Directory.watch] emits a batch of events. |
| 169 void _onBatch(List<FileSystemEvent> batch) { | 160 void _onBatch(List<FileSystemEvent> batch) { |
| 170 _sortEvents(batch).forEach((path, events) { | 161 _sortEvents(batch).forEach((path, events) { |
| 171 var relativePath = p.relative(path, from: directory); | 162 var relativePath = p.relative(path, from: directory); |
| 172 | 163 |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 239 directories.any((dir) => path != dir && path.startsWith(dir)); | 230 directories.any((dir) => path != dir && path.startsWith(dir)); |
| 240 | 231 |
| 241 addEvent(path, event) { | 232 addEvent(path, event) { |
| 242 if (isInModifiedDirectory(path)) return; | 233 if (isInModifiedDirectory(path)) return; |
| 243 var set = eventsForPaths.putIfAbsent(path, () => new Set()); | 234 var set = eventsForPaths.putIfAbsent(path, () => new Set()); |
| 244 set.add(event); | 235 set.add(event); |
| 245 } | 236 } |
| 246 | 237 |
| 247 for (var event in batch) { | 238 for (var event in batch) { |
| 248 if (event is FileSystemMoveEvent) { | 239 if (event is FileSystemMoveEvent) { |
| 249 FileSystemMoveEvent moveEvent = event; | 240 addEvent(event.destination, event); |
| 250 addEvent(moveEvent.destination, event); | |
| 251 } | 241 } |
| 252 addEvent(event.path, event); | 242 addEvent(event.path, event); |
| 253 } | 243 } |
| 254 | 244 |
| 255 return eventsForPaths; | 245 return eventsForPaths; |
| 256 } | 246 } |
| 257 | 247 |
| 258 /// Returns the canonical event from a batch of events on the same path, if | 248 /// Returns the canonical event from a batch of events on the same path, if |
| 259 /// one exists. | 249 /// one exists. |
| 260 /// | 250 /// |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 359 | 349 |
| 360 return events; | 350 return events; |
| 361 } | 351 } |
| 362 | 352 |
| 363 /// The callback that's run when the [Directory.watch] stream is closed. | 353 /// The callback that's run when the [Directory.watch] stream is closed. |
| 364 /// Note that this is unlikely to happen on Windows, unless the system itself | 354 /// Note that this is unlikely to happen on Windows, unless the system itself |
| 365 /// closes the handle. | 355 /// closes the handle. |
| 366 void _onDone() { | 356 void _onDone() { |
| 367 _watchSubscription = null; | 357 _watchSubscription = null; |
| 368 | 358 |
| 369 // Emit remove-events for any remaining files. | 359 // Emit remove events for any remaining files. |
| 370 for (var file in _files.toSet()) { | 360 for (var file in _files.toSet()) { |
| 371 _emitEvent(ChangeType.REMOVE, file); | 361 _emitEvent(ChangeType.REMOVE, file); |
| 372 } | 362 } |
| 373 _files.clear(); | 363 _files.clear(); |
| 374 close(); | 364 close(); |
| 375 } | 365 } |
| 376 | 366 |
| 377 /// Start or restart the underlying [Directory.watch] stream. | 367 /// Start or restart the underlying [Directory.watch] stream. |
| 378 void _startWatch() { | 368 void _startWatch() { |
| 379 // Batch the events changes together so that we can dedup events. | 369 // Batch the events together so that we can dedup events. |
| 380 var innerStream = | 370 var innerStream = |
| 381 Chain.track(new Directory(directory).watch(recursive: true)); | 371 Chain.track(new Directory(directory).watch(recursive: true)); |
| 382 _watchSubscription = innerStream.listen(_onEvent, | 372 _watchSubscription = innerStream.listen(_onEvent, |
| 383 onError: _eventsController.addError, | 373 onError: _eventsController.addError, |
| 384 onDone: _onDone); | 374 onDone: _onDone); |
| 385 } | 375 } |
| 386 | 376 |
| 387 /// Starts or restarts listing the watched directory to get an initial picture | 377 /// Starts or restarts listing the watched directory to get an initial picture |
| 388 /// of its state. | 378 /// of its state. |
| 389 Future _listDir() { | 379 Future _listDir() { |
| (...skipping 20 matching lines...) Expand all Loading... |
| 410 | 400 |
| 411 _eventsController.add(new WatchEvent(type, path)); | 401 _eventsController.add(new WatchEvent(type, path)); |
| 412 } | 402 } |
| 413 | 403 |
| 414 /// Emit an error, then close the watcher. | 404 /// Emit an error, then close the watcher. |
| 415 void _emitError(error, StackTrace stackTrace) { | 405 void _emitError(error, StackTrace stackTrace) { |
| 416 _eventsController.addError(error, stackTrace); | 406 _eventsController.addError(error, stackTrace); |
| 417 close(); | 407 close(); |
| 418 } | 408 } |
| 419 } | 409 } |
| OLD | NEW |