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; |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
66 StreamSubscription<FileSystemEvent> _watchSubscription; | 66 StreamSubscription<FileSystemEvent> _watchSubscription; |
67 | 67 |
68 /// The subscription to the [Directory.list] call for the initial listing of | 68 /// The subscription to the [Directory.list] call for the initial listing of |
69 /// the directory to determine its initial state. | 69 /// the directory to determine its initial state. |
70 StreamSubscription<FileSystemEntity> _initialListSubscription; | 70 StreamSubscription<FileSystemEntity> _initialListSubscription; |
71 | 71 |
72 /// The subscription to the [Directory.list] call for listing the contents of | 72 /// The subscription to the [Directory.list] call for listing the contents of |
73 /// a subdirectory that was moved into the watched directory. | 73 /// a subdirectory that was moved into the watched directory. |
74 StreamSubscription<FileSystemEntity> _listSubscription; | 74 StreamSubscription<FileSystemEntity> _listSubscription; |
75 | 75 |
76 /// The timer for tracking how long we wait for an initial batch of bogus | |
77 /// events (see issue 14373). | |
78 Timer _bogusEventTimer; | |
79 | |
80 _MacOSDirectoryWatcher(String directory, int parentId) | 76 _MacOSDirectoryWatcher(String directory, int parentId) |
81 : directory = directory, | 77 : directory = directory, |
82 _files = new PathSet(directory), | 78 _files = new PathSet(directory), |
83 _id = "$parentId/${_count++}" { | 79 _id = "$parentId/${_count++}" { |
84 _startWatch(); | 80 _listDir().then((_) { |
85 | 81 if (MacOSDirectoryWatcher.logDebugInfo) { |
86 // Before we're ready to emit events, wait for [_listDir] to complete and | 82 print("[$_id] finished initial directory list"); |
87 // for enough time to elapse that if bogus events (issue 14373) would be | 83 } |
88 // emitted, they will be. | 84 _startWatch(); |
89 // | |
90 // If we do receive a batch of events, [_onBatch] will ensure that these | |
91 // futures don't fire and that the directory is re-listed. | |
92 Future.wait([ | |
93 _listDir().then((_) { | |
94 if (MacOSDirectoryWatcher.logDebugInfo) { | |
95 print("[$_id] finished initial directory list"); | |
96 } | |
97 }), | |
98 _waitForBogusEvents() | |
99 ]).then((_) { | |
100 if (MacOSDirectoryWatcher.logDebugInfo) { | 85 if (MacOSDirectoryWatcher.logDebugInfo) { |
101 print("[$_id] watcher is ready, known files:"); | 86 print("[$_id] watcher is ready, known files:"); |
102 for (var file in _files.toSet()) { | 87 for (var file in _files.toSet()) { |
103 print("[$_id] ${p.relative(file, from: directory)}"); | 88 print("[$_id] ${p.relative(file, from: directory)}"); |
104 } | 89 } |
105 } | 90 } |
106 _readyCompleter.complete(); | 91 _readyCompleter.complete(); |
107 }); | 92 }); |
108 } | 93 } |
109 | 94 |
(...skipping 17 matching lines...) Expand all Loading... |
127 for (var event in batch) { | 112 for (var event in batch) { |
128 print("[$_id] ${_formatEvent(event)}"); | 113 print("[$_id] ${_formatEvent(event)}"); |
129 } | 114 } |
130 | 115 |
131 print("[$_id] known files:"); | 116 print("[$_id] known files:"); |
132 for (var file in _files.toSet()) { | 117 for (var file in _files.toSet()) { |
133 print("[$_id] ${p.relative(file, from: directory)}"); | 118 print("[$_id] ${p.relative(file, from: directory)}"); |
134 } | 119 } |
135 } | 120 } |
136 | 121 |
137 // If we get a batch of events before we're ready to begin emitting events, | |
138 // it's probable that it's a batch of pre-watcher events (see issue 14373). | |
139 // Ignore those events and re-list the directory. | |
140 if (!isReady) { | |
141 if (MacOSDirectoryWatcher.logDebugInfo) { | |
142 print("[$_id] not ready to emit events, re-listing directory"); | |
143 } | |
144 | |
145 // Cancel the timer because bogus events only occur in the first batch, so | |
146 // we can fire [ready] as soon as we're done listing the directory. | |
147 _bogusEventTimer.cancel(); | |
148 _listDir().then((_) { | |
149 if (MacOSDirectoryWatcher.logDebugInfo) { | |
150 print("[$_id] watcher is ready, known files:"); | |
151 for (var file in _files.toSet()) { | |
152 print("[$_id] ${p.relative(file, from: directory)}"); | |
153 } | |
154 } | |
155 _readyCompleter.complete(); | |
156 }); | |
157 return; | |
158 } | |
159 | |
160 _sortEvents(batch).forEach((path, events) { | 122 _sortEvents(batch).forEach((path, events) { |
161 var relativePath = p.relative(path, from: directory); | 123 var relativePath = p.relative(path, from: directory); |
162 if (MacOSDirectoryWatcher.logDebugInfo) { | 124 if (MacOSDirectoryWatcher.logDebugInfo) { |
163 print("[$_id] events for $relativePath:"); | 125 print("[$_id] events for $relativePath:"); |
164 for (var event in events) { | 126 for (var event in events) { |
165 print("[$_id] ${_formatEvent(event)}"); | 127 print("[$_id] ${_formatEvent(event)}"); |
166 } | 128 } |
167 } | 129 } |
168 | 130 |
169 var canonicalEvent = _canonicalEvent(events); | 131 var canonicalEvent = _canonicalEvent(events); |
(...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
385 | 347 |
386 return events; | 348 return events; |
387 } | 349 } |
388 | 350 |
389 /// The callback that's run when the [Directory.watch] stream is closed. | 351 /// The callback that's run when the [Directory.watch] stream is closed. |
390 void _onDone() { | 352 void _onDone() { |
391 if (MacOSDirectoryWatcher.logDebugInfo) print("[$_id] stream closed"); | 353 if (MacOSDirectoryWatcher.logDebugInfo) print("[$_id] stream closed"); |
392 | 354 |
393 _watchSubscription = null; | 355 _watchSubscription = null; |
394 | 356 |
395 // If the directory still exists and we're still expecting bogus events, | |
396 // this is probably issue 14849 rather than a real close event. We should | |
397 // just restart the watcher. | |
398 if (!isReady && new Directory(directory).existsSync()) { | |
399 if (MacOSDirectoryWatcher.logDebugInfo) { | |
400 print("[$_id] fake closure (issue 14849), re-opening stream"); | |
401 } | |
402 _startWatch(); | |
403 return; | |
404 } | |
405 | |
406 // FSEvents can fail to report the contents of the directory being removed | 357 // FSEvents can fail to report the contents of the directory being removed |
407 // when the directory itself is removed, so we need to manually mark the | 358 // when the directory itself is removed, so we need to manually mark the |
408 // files as removed. | 359 // files as removed. |
409 for (var file in _files.toSet()) { | 360 for (var file in _files.toSet()) { |
410 _emitEvent(ChangeType.REMOVE, file); | 361 _emitEvent(ChangeType.REMOVE, file); |
411 } | 362 } |
412 _files.clear(); | 363 _files.clear(); |
413 close(); | 364 close(); |
414 } | 365 } |
415 | 366 |
(...skipping 19 matching lines...) Expand all Loading... |
435 var stream = Chain.track(new Directory(directory).list(recursive: true)); | 386 var stream = Chain.track(new Directory(directory).list(recursive: true)); |
436 _initialListSubscription = stream.listen((entity) { | 387 _initialListSubscription = stream.listen((entity) { |
437 if (entity is! Directory) _files.add(entity.path); | 388 if (entity is! Directory) _files.add(entity.path); |
438 }, | 389 }, |
439 onError: _emitError, | 390 onError: _emitError, |
440 onDone: completer.complete, | 391 onDone: completer.complete, |
441 cancelOnError: true); | 392 cancelOnError: true); |
442 return completer.future; | 393 return completer.future; |
443 } | 394 } |
444 | 395 |
445 /// Wait 200ms for a batch of bogus events (issue 14373) to come in. | |
446 /// | |
447 /// 200ms is short in terms of human interaction, but longer than any Mac OS | |
448 /// watcher tests take on the bots, so it should be safe to assume that any | |
449 /// bogus events will be signaled in that time frame. | |
450 Future _waitForBogusEvents() { | |
451 var completer = new Completer(); | |
452 _bogusEventTimer = new Timer( | |
453 new Duration(milliseconds: 200), | |
454 completer.complete); | |
455 return completer.future; | |
456 } | |
457 | |
458 /// Emit an event with the given [type] and [path]. | 396 /// Emit an event with the given [type] and [path]. |
459 void _emitEvent(ChangeType type, String path) { | 397 void _emitEvent(ChangeType type, String path) { |
460 if (!isReady) return; | 398 if (!isReady) return; |
461 | 399 |
462 if (MacOSDirectoryWatcher.logDebugInfo) { | 400 if (MacOSDirectoryWatcher.logDebugInfo) { |
463 print("[$_id] emitting $type ${p.relative(path, from: directory)}"); | 401 print("[$_id] emitting $type ${p.relative(path, from: directory)}"); |
464 } | 402 } |
465 | 403 |
466 _eventsController.add(new WatchEvent(type, path)); | 404 _eventsController.add(new WatchEvent(type, path)); |
467 } | 405 } |
(...skipping 20 matching lines...) Expand all Loading... |
488 } else if (event is FileSystemDeleteEvent) { | 426 } else if (event is FileSystemDeleteEvent) { |
489 return "delete $type $path"; | 427 return "delete $type $path"; |
490 } else if (event is FileSystemModifyEvent) { | 428 } else if (event is FileSystemModifyEvent) { |
491 return "modify $type $path"; | 429 return "modify $type $path"; |
492 } else if (event is FileSystemMoveEvent) { | 430 } else if (event is FileSystemMoveEvent) { |
493 return "move $type $path to " | 431 return "move $type $path to " |
494 "${p.relative(event.destination, from: directory)}"; | 432 "${p.relative(event.destination, from: directory)}"; |
495 } | 433 } |
496 } | 434 } |
497 } | 435 } |
OLD | NEW |