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; | |
7 | |
8 import 'dart:async'; | 6 import 'dart:async'; |
9 import 'dart:collection'; | 7 import 'dart:collection'; |
10 import 'dart:io'; | 8 import 'dart:io'; |
11 | 9 |
12 import 'package:path/path.dart' as p; | 10 import 'package:path/path.dart' as p; |
13 | 11 |
14 import '../constructable_file_system_event.dart'; | 12 import '../constructable_file_system_event.dart'; |
15 import '../directory_watcher.dart'; | 13 import '../directory_watcher.dart'; |
16 import '../path_set.dart'; | 14 import '../path_set.dart'; |
17 import '../resubscribable.dart'; | 15 import '../resubscribable.dart'; |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
129 if (p.basename(event.path) != p.basename(absoluteDir)) return; | 127 if (p.basename(event.path) != p.basename(absoluteDir)) return; |
130 // Test if the directory is removed. FileSystemEntity.typeSync will | 128 // Test if the directory is removed. FileSystemEntity.typeSync will |
131 // return NOT_FOUND if it's unable to decide upon the type, including | 129 // return NOT_FOUND if it's unable to decide upon the type, including |
132 // access denied issues, which may happen when the directory is deleted. | 130 // access denied issues, which may happen when the directory is deleted. |
133 // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean | 131 // FileSystemMoveEvent and FileSystemDeleteEvent events will always mean |
134 // the directory is now gone. | 132 // the directory is now gone. |
135 if (event is FileSystemMoveEvent || | 133 if (event is FileSystemMoveEvent || |
136 event is FileSystemDeleteEvent || | 134 event is FileSystemDeleteEvent || |
137 (FileSystemEntity.typeSync(path) == | 135 (FileSystemEntity.typeSync(path) == |
138 FileSystemEntityType.NOT_FOUND)) { | 136 FileSystemEntityType.NOT_FOUND)) { |
139 for (var path in _files.toSet()) { | 137 for (var path in _files.paths) { |
140 _emitEvent(ChangeType.REMOVE, path); | 138 _emitEvent(ChangeType.REMOVE, path); |
141 } | 139 } |
142 _files.clear(); | 140 _files.clear(); |
143 close(); | 141 close(); |
144 } | 142 } |
145 }, onError: (error) { | 143 }, onError: (error) { |
146 // Ignore errors, simply close the stream. The user listens on | 144 // Ignore errors, simply close the stream. The user listens on |
147 // [directory], and while it can fail to listen on the parent, we may | 145 // [directory], and while it can fail to listen on the parent, we may |
148 // still be able to listen on the path requested. | 146 // still be able to listen on the path requested. |
149 _parentWatchSubscription.cancel(); | 147 _parentWatchSubscription.cancel(); |
150 _parentWatchSubscription = null; | 148 _parentWatchSubscription = null; |
151 }); | 149 }); |
152 } | 150 } |
153 | 151 |
154 void _onEvent(FileSystemEvent event) { | 152 void _onEvent(FileSystemEvent event) { |
155 assert(isReady); | 153 assert(isReady); |
156 final batcher = _eventBatchers.putIfAbsent( | 154 final batcher = _eventBatchers.putIfAbsent( |
157 event.path, () => new _EventBatcher()); | 155 event.path, () => new _EventBatcher()); |
158 batcher.addEvent(event, () { | 156 batcher.addEvent(event, () { |
159 _eventBatchers.remove(event.path); | 157 _eventBatchers.remove(event.path); |
160 _onBatch(batcher.events); | 158 _onBatch(batcher.events); |
161 }); | 159 }); |
162 } | 160 } |
163 | 161 |
164 /// The callback that's run when [Directory.watch] emits a batch of events. | 162 /// The callback that's run when [Directory.watch] emits a batch of events. |
165 void _onBatch(List<FileSystemEvent> batch) { | 163 void _onBatch(List<FileSystemEvent> batch) { |
166 _sortEvents(batch).forEach((path, events) { | 164 _sortEvents(batch).forEach((path, eventSet) { |
167 | 165 |
168 var canonicalEvent = _canonicalEvent(events); | 166 var canonicalEvent = _canonicalEvent(eventSet); |
169 events = canonicalEvent == null ? | 167 var events = canonicalEvent == null ? |
170 _eventsBasedOnFileSystem(path) : [canonicalEvent]; | 168 _eventsBasedOnFileSystem(path) : [canonicalEvent]; |
171 | 169 |
172 for (var event in events) { | 170 for (var event in events) { |
173 if (event is FileSystemCreateEvent) { | 171 if (event is FileSystemCreateEvent) { |
174 if (!event.isDirectory) { | 172 if (!event.isDirectory) { |
175 if (_files.contains(path)) continue; | 173 if (_files.contains(path)) continue; |
176 | 174 |
177 _emitEvent(ChangeType.ADD, path); | 175 _emitEvent(ChangeType.ADD, path); |
178 _files.add(path); | 176 _files.add(path); |
179 continue; | 177 continue; |
180 } | 178 } |
181 | 179 |
182 if (_files.containsDir(path)) continue; | 180 if (_files.containsDir(path)) continue; |
183 | 181 |
184 var stream = new Directory(path).list(recursive: true); | 182 var stream = new Directory(path).list(recursive: true); |
185 var sub; | 183 StreamSubscription<FileSystemEntity> subscription; |
186 sub = stream.listen((entity) { | 184 subscription = stream.listen((entity) { |
187 if (entity is Directory) return; | 185 if (entity is Directory) return; |
188 if (_files.contains(path)) return; | 186 if (_files.contains(path)) return; |
189 | 187 |
190 _emitEvent(ChangeType.ADD, entity.path); | 188 _emitEvent(ChangeType.ADD, entity.path); |
191 _files.add(entity.path); | 189 _files.add(entity.path); |
192 }, onDone: () { | 190 }, onDone: () { |
193 _listSubscriptions.remove(sub); | 191 _listSubscriptions.remove(subscription); |
194 }, onError: (e, stackTrace) { | 192 }, onError: (e, stackTrace) { |
195 _listSubscriptions.remove(sub); | 193 _listSubscriptions.remove(subscription); |
196 _emitError(e, stackTrace); | 194 _emitError(e, stackTrace); |
197 }, cancelOnError: true); | 195 }, cancelOnError: true); |
198 _listSubscriptions.add(sub); | 196 _listSubscriptions.add(subscription); |
199 } else if (event is FileSystemModifyEvent) { | 197 } else if (event is FileSystemModifyEvent) { |
200 if (!event.isDirectory) { | 198 if (!event.isDirectory) { |
201 _emitEvent(ChangeType.MODIFY, path); | 199 _emitEvent(ChangeType.MODIFY, path); |
202 } | 200 } |
203 } else { | 201 } else { |
204 assert(event is FileSystemDeleteEvent); | 202 assert(event is FileSystemDeleteEvent); |
205 for (var removedPath in _files.remove(path)) { | 203 for (var removedPath in _files.remove(path)) { |
206 _emitEvent(ChangeType.REMOVE, removedPath); | 204 _emitEvent(ChangeType.REMOVE, removedPath); |
207 } | 205 } |
208 } | 206 } |
209 } | 207 } |
210 }); | 208 }); |
211 } | 209 } |
212 | 210 |
213 /// Sort all the events in a batch into sets based on their path. | 211 /// Sort all the events in a batch into sets based on their path. |
214 /// | 212 /// |
215 /// A single input event may result in multiple events in the returned map; | 213 /// A single input event may result in multiple events in the returned map; |
216 /// for example, a MOVE event becomes a DELETE event for the source and a | 214 /// for example, a MOVE event becomes a DELETE event for the source and a |
217 /// CREATE event for the destination. | 215 /// CREATE event for the destination. |
218 /// | 216 /// |
219 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it | 217 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it |
220 /// contain any events relating to [path]. | 218 /// contain any events relating to [path]. |
221 Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) { | 219 Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) { |
222 var eventsForPaths = {}; | 220 var eventsForPaths = <String, Set>{}; |
223 | 221 |
224 // Events within directories that already have events are superfluous; the | 222 // Events within directories that already have events are superfluous; the |
225 // directory's full contents will be examined anyway, so we ignore such | 223 // directory's full contents will be examined anyway, so we ignore such |
226 // events. Emitting them could cause useless or out-of-order events. | 224 // events. Emitting them could cause useless or out-of-order events. |
227 var directories = unionAll(batch.map((event) { | 225 var directories = unionAll(batch.map((event) { |
228 if (!event.isDirectory) return new Set(); | 226 if (!event.isDirectory) return new Set(); |
229 if (event is! FileSystemMoveEvent) return new Set.from([event.path]); | 227 if (event is FileSystemMoveEvent) { |
230 return new Set.from([event.path, event.destination]); | 228 return new Set.from([event.path, event.destination]); |
| 229 } |
| 230 return new Set.from([event.path]); |
231 })); | 231 })); |
232 | 232 |
233 isInModifiedDirectory(path) => | 233 isInModifiedDirectory(path) => |
234 directories.any((dir) => path != dir && path.startsWith(dir)); | 234 directories.any((dir) => path != dir && path.startsWith(dir)); |
235 | 235 |
236 addEvent(path, event) { | 236 addEvent(path, event) { |
237 if (isInModifiedDirectory(path)) return; | 237 if (isInModifiedDirectory(path)) return; |
238 var set = eventsForPaths.putIfAbsent(path, () => new Set()); | 238 var set = eventsForPaths.putIfAbsent(path, () => new Set()); |
239 set.add(event); | 239 set.add(event); |
240 } | 240 } |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
315 /// This returns a list whose order should be reflected in the events emitted | 315 /// This returns a list whose order should be reflected in the events emitted |
316 /// to the user, unlike the batched events from [Directory.watch]. The | 316 /// to the user, unlike the batched events from [Directory.watch]. The |
317 /// returned list may be empty, indicating that no changes occurred to [path] | 317 /// returned list may be empty, indicating that no changes occurred to [path] |
318 /// (probably indicating that it was created and then immediately deleted). | 318 /// (probably indicating that it was created and then immediately deleted). |
319 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { | 319 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { |
320 var fileExisted = _files.contains(path); | 320 var fileExisted = _files.contains(path); |
321 var dirExisted = _files.containsDir(path); | 321 var dirExisted = _files.containsDir(path); |
322 var fileExists = new File(path).existsSync(); | 322 var fileExists = new File(path).existsSync(); |
323 var dirExists = new Directory(path).existsSync(); | 323 var dirExists = new Directory(path).existsSync(); |
324 | 324 |
325 var events = []; | 325 var events = <FileSystemEvent>[]; |
326 if (fileExisted) { | 326 if (fileExisted) { |
327 if (fileExists) { | 327 if (fileExists) { |
328 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); | 328 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); |
329 } else { | 329 } else { |
330 events.add(new ConstructableFileSystemDeleteEvent(path, false)); | 330 events.add(new ConstructableFileSystemDeleteEvent(path, false)); |
331 } | 331 } |
332 } else if (dirExisted) { | 332 } else if (dirExisted) { |
333 if (dirExists) { | 333 if (dirExists) { |
334 // If we got contradictory events for a directory that used to exist and | 334 // If we got contradictory events for a directory that used to exist and |
335 // still exists, we need to rescan the whole thing in case it was | 335 // still exists, we need to rescan the whole thing in case it was |
(...skipping 14 matching lines...) Expand all Loading... |
350 return events; | 350 return events; |
351 } | 351 } |
352 | 352 |
353 /// 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. |
354 /// 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 |
355 /// closes the handle. | 355 /// closes the handle. |
356 void _onDone() { | 356 void _onDone() { |
357 _watchSubscription = null; | 357 _watchSubscription = null; |
358 | 358 |
359 // Emit remove events for any remaining files. | 359 // Emit remove events for any remaining files. |
360 for (var file in _files.toSet()) { | 360 for (var file in _files.paths) { |
361 _emitEvent(ChangeType.REMOVE, file); | 361 _emitEvent(ChangeType.REMOVE, file); |
362 } | 362 } |
363 _files.clear(); | 363 _files.clear(); |
364 close(); | 364 close(); |
365 } | 365 } |
366 | 366 |
367 /// Start or restart the underlying [Directory.watch] stream. | 367 /// Start or restart the underlying [Directory.watch] stream. |
368 void _startWatch() { | 368 void _startWatch() { |
369 // Batch the events together so that we can dedup events. | 369 // Batch the events together so that we can dedup events. |
370 var innerStream = new Directory(path).watch(recursive: true); | 370 var innerStream = new Directory(path).watch(recursive: true); |
(...skipping 28 matching lines...) Expand all Loading... |
399 | 399 |
400 _eventsController.add(new WatchEvent(type, path)); | 400 _eventsController.add(new WatchEvent(type, path)); |
401 } | 401 } |
402 | 402 |
403 /// Emit an error, then close the watcher. | 403 /// Emit an error, then close the watcher. |
404 void _emitError(error, StackTrace stackTrace) { | 404 void _emitError(error, StackTrace stackTrace) { |
405 _eventsController.addError(error, stackTrace); | 405 _eventsController.addError(error, stackTrace); |
406 close(); | 406 close(); |
407 } | 407 } |
408 } | 408 } |
OLD | NEW |