| 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 |
| 76 _MacOSDirectoryWatcher(String directory, int parentId) | 80 _MacOSDirectoryWatcher(String directory, int parentId) |
| 77 : directory = directory, | 81 : directory = directory, |
| 78 _files = new PathSet(directory), | 82 _files = new PathSet(directory), |
| 79 _id = "$parentId/${_count++}" { | 83 _id = "$parentId/${_count++}" { |
| 80 _listDir().then((_) { | 84 _startWatch(); |
| 81 if (MacOSDirectoryWatcher.logDebugInfo) { | 85 |
| 82 print("[$_id] finished initial directory list"); | 86 // Before we're ready to emit events, wait for [_listDir] to complete and |
| 83 } | 87 // for enough time to elapse that if bogus events (issue 14373) would be |
| 84 _startWatch(); | 88 // emitted, they will be. |
| 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((_) { |
| 85 if (MacOSDirectoryWatcher.logDebugInfo) { | 100 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 86 print("[$_id] watcher is ready, known files:"); | 101 print("[$_id] watcher is ready, known files:"); |
| 87 for (var file in _files.toSet()) { | 102 for (var file in _files.toSet()) { |
| 88 print("[$_id] ${p.relative(file, from: directory)}"); | 103 print("[$_id] ${p.relative(file, from: directory)}"); |
| 89 } | 104 } |
| 90 } | 105 } |
| 91 _readyCompleter.complete(); | 106 _readyCompleter.complete(); |
| 92 }); | 107 }); |
| 93 } | 108 } |
| 94 | 109 |
| (...skipping 17 matching lines...) Expand all Loading... |
| 112 for (var event in batch) { | 127 for (var event in batch) { |
| 113 print("[$_id] ${_formatEvent(event)}"); | 128 print("[$_id] ${_formatEvent(event)}"); |
| 114 } | 129 } |
| 115 | 130 |
| 116 print("[$_id] known files:"); | 131 print("[$_id] known files:"); |
| 117 for (var file in _files.toSet()) { | 132 for (var file in _files.toSet()) { |
| 118 print("[$_id] ${p.relative(file, from: directory)}"); | 133 print("[$_id] ${p.relative(file, from: directory)}"); |
| 119 } | 134 } |
| 120 } | 135 } |
| 121 | 136 |
| 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 |
| 122 _sortEvents(batch).forEach((path, events) { | 160 _sortEvents(batch).forEach((path, events) { |
| 123 var relativePath = p.relative(path, from: directory); | 161 var relativePath = p.relative(path, from: directory); |
| 124 if (MacOSDirectoryWatcher.logDebugInfo) { | 162 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 125 print("[$_id] events for $relativePath:"); | 163 print("[$_id] events for $relativePath:"); |
| 126 for (var event in events) { | 164 for (var event in events) { |
| 127 print("[$_id] ${_formatEvent(event)}"); | 165 print("[$_id] ${_formatEvent(event)}"); |
| 128 } | 166 } |
| 129 } | 167 } |
| 130 | 168 |
| 131 var canonicalEvent = _canonicalEvent(events); | 169 var canonicalEvent = _canonicalEvent(events); |
| (...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 347 | 385 |
| 348 return events; | 386 return events; |
| 349 } | 387 } |
| 350 | 388 |
| 351 /// The callback that's run when the [Directory.watch] stream is closed. | 389 /// The callback that's run when the [Directory.watch] stream is closed. |
| 352 void _onDone() { | 390 void _onDone() { |
| 353 if (MacOSDirectoryWatcher.logDebugInfo) print("[$_id] stream closed"); | 391 if (MacOSDirectoryWatcher.logDebugInfo) print("[$_id] stream closed"); |
| 354 | 392 |
| 355 _watchSubscription = null; | 393 _watchSubscription = null; |
| 356 | 394 |
| 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 |
| 357 // FSEvents can fail to report the contents of the directory being removed | 406 // FSEvents can fail to report the contents of the directory being removed |
| 358 // when the directory itself is removed, so we need to manually mark the | 407 // when the directory itself is removed, so we need to manually mark the |
| 359 // files as removed. | 408 // files as removed. |
| 360 for (var file in _files.toSet()) { | 409 for (var file in _files.toSet()) { |
| 361 _emitEvent(ChangeType.REMOVE, file); | 410 _emitEvent(ChangeType.REMOVE, file); |
| 362 } | 411 } |
| 363 _files.clear(); | 412 _files.clear(); |
| 364 close(); | 413 close(); |
| 365 } | 414 } |
| 366 | 415 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 386 var stream = Chain.track(new Directory(directory).list(recursive: true)); | 435 var stream = Chain.track(new Directory(directory).list(recursive: true)); |
| 387 _initialListSubscription = stream.listen((entity) { | 436 _initialListSubscription = stream.listen((entity) { |
| 388 if (entity is! Directory) _files.add(entity.path); | 437 if (entity is! Directory) _files.add(entity.path); |
| 389 }, | 438 }, |
| 390 onError: _emitError, | 439 onError: _emitError, |
| 391 onDone: completer.complete, | 440 onDone: completer.complete, |
| 392 cancelOnError: true); | 441 cancelOnError: true); |
| 393 return completer.future; | 442 return completer.future; |
| 394 } | 443 } |
| 395 | 444 |
| 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 |
| 396 /// Emit an event with the given [type] and [path]. | 458 /// Emit an event with the given [type] and [path]. |
| 397 void _emitEvent(ChangeType type, String path) { | 459 void _emitEvent(ChangeType type, String path) { |
| 398 if (!isReady) return; | 460 if (!isReady) return; |
| 399 | 461 |
| 400 if (MacOSDirectoryWatcher.logDebugInfo) { | 462 if (MacOSDirectoryWatcher.logDebugInfo) { |
| 401 print("[$_id] emitting $type ${p.relative(path, from: directory)}"); | 463 print("[$_id] emitting $type ${p.relative(path, from: directory)}"); |
| 402 } | 464 } |
| 403 | 465 |
| 404 _eventsController.add(new WatchEvent(type, path)); | 466 _eventsController.add(new WatchEvent(type, path)); |
| 405 } | 467 } |
| (...skipping 20 matching lines...) Expand all Loading... |
| 426 } else if (event is FileSystemDeleteEvent) { | 488 } else if (event is FileSystemDeleteEvent) { |
| 427 return "delete $type $path"; | 489 return "delete $type $path"; |
| 428 } else if (event is FileSystemModifyEvent) { | 490 } else if (event is FileSystemModifyEvent) { |
| 429 return "modify $type $path"; | 491 return "modify $type $path"; |
| 430 } else if (event is FileSystemMoveEvent) { | 492 } else if (event is FileSystemMoveEvent) { |
| 431 return "move $type $path to " | 493 return "move $type $path to " |
| 432 "${p.relative(event.destination, from: directory)}"; | 494 "${p.relative(event.destination, from: directory)}"; |
| 433 } | 495 } |
| 434 } | 496 } |
| 435 } | 497 } |
| OLD | NEW |