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 |