| 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; | |
| 6 | |
| 7 import 'dart:async'; | 5 import 'dart:async'; |
| 8 import 'dart:io'; | 6 import 'dart:io'; |
| 9 | 7 |
| 10 import '../directory_watcher.dart'; | 8 import '../directory_watcher.dart'; |
| 11 import '../constructable_file_system_event.dart'; | 9 import '../constructable_file_system_event.dart'; |
| 12 import '../path_set.dart'; | 10 import '../path_set.dart'; |
| 13 import '../resubscribable.dart'; | 11 import '../resubscribable.dart'; |
| 14 import '../utils.dart'; | 12 import '../utils.dart'; |
| 15 import '../watch_event.dart'; | 13 import '../watch_event.dart'; |
| 16 | 14 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 51 /// The state of files on the filesystem is compared against this to determine | 49 /// The state of files on the filesystem is compared against this to determine |
| 52 /// the real change that occurred when working around issue 14373. This is | 50 /// the real change that occurred when working around issue 14373. This is |
| 53 /// also used to emit REMOVE events when subdirectories are moved out of the | 51 /// also used to emit REMOVE events when subdirectories are moved out of the |
| 54 /// watched directory. | 52 /// watched directory. |
| 55 final PathSet _files; | 53 final PathSet _files; |
| 56 | 54 |
| 57 /// The subscription to the stream returned by [Directory.watch]. | 55 /// The subscription to the stream returned by [Directory.watch]. |
| 58 /// | 56 /// |
| 59 /// This is separate from [_subscriptions] because this stream occasionally | 57 /// This is separate from [_subscriptions] because this stream occasionally |
| 60 /// needs to be resubscribed in order to work around issue 14849. | 58 /// needs to be resubscribed in order to work around issue 14849. |
| 61 StreamSubscription<FileSystemEvent> _watchSubscription; | 59 StreamSubscription<List<FileSystemEvent>> _watchSubscription; |
| 62 | 60 |
| 63 /// The subscription to the [Directory.list] call for the initial listing of | 61 /// The subscription to the [Directory.list] call for the initial listing of |
| 64 /// the directory to determine its initial state. | 62 /// the directory to determine its initial state. |
| 65 StreamSubscription<FileSystemEntity> _initialListSubscription; | 63 StreamSubscription<FileSystemEntity> _initialListSubscription; |
| 66 | 64 |
| 67 /// The subscriptions to [Directory.list] calls for listing the contents of a | 65 /// The subscriptions to [Directory.list] calls for listing the contents of a |
| 68 /// subdirectory that was moved into the watched directory. | 66 /// subdirectory that was moved into the watched directory. |
| 69 final _listSubscriptions = new Set<StreamSubscription<FileSystemEntity>>(); | 67 final _listSubscriptions = new Set<StreamSubscription<FileSystemEntity>>(); |
| 70 | 68 |
| 71 /// The timer for tracking how long we wait for an initial batch of bogus | 69 /// The timer for tracking how long we wait for an initial batch of bogus |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 109 // it's probable that it's a batch of pre-watcher events (see issue 14373). | 107 // it's probable that it's a batch of pre-watcher events (see issue 14373). |
| 110 // Ignore those events and re-list the directory. | 108 // Ignore those events and re-list the directory. |
| 111 if (!isReady) { | 109 if (!isReady) { |
| 112 // Cancel the timer because bogus events only occur in the first batch, so | 110 // Cancel the timer because bogus events only occur in the first batch, so |
| 113 // we can fire [ready] as soon as we're done listing the directory. | 111 // we can fire [ready] as soon as we're done listing the directory. |
| 114 _bogusEventTimer.cancel(); | 112 _bogusEventTimer.cancel(); |
| 115 _listDir().then((_) => _readyCompleter.complete()); | 113 _listDir().then((_) => _readyCompleter.complete()); |
| 116 return; | 114 return; |
| 117 } | 115 } |
| 118 | 116 |
| 119 _sortEvents(batch).forEach((path, events) { | 117 _sortEvents(batch).forEach((path, eventSet) { |
| 120 var canonicalEvent = _canonicalEvent(events); | 118 var canonicalEvent = _canonicalEvent(eventSet); |
| 121 events = canonicalEvent == null ? | 119 var events = canonicalEvent == null ? |
| 122 _eventsBasedOnFileSystem(path) : [canonicalEvent]; | 120 _eventsBasedOnFileSystem(path) : [canonicalEvent]; |
| 123 | 121 |
| 124 for (var event in events) { | 122 for (var event in events) { |
| 125 if (event is FileSystemCreateEvent) { | 123 if (event is FileSystemCreateEvent) { |
| 126 if (!event.isDirectory) { | 124 if (!event.isDirectory) { |
| 127 // If we already know about the file, treat it like a modification. | 125 // If we already know about the file, treat it like a modification. |
| 128 // This can happen if a file is copied on top of an existing one. | 126 // This can happen if a file is copied on top of an existing one. |
| 129 // We'll see an ADD event for the latter file when from the user's | 127 // We'll see an ADD event for the latter file when from the user's |
| 130 // perspective, the file's contents just changed. | 128 // perspective, the file's contents just changed. |
| 131 var type = _files.contains(path) | 129 var type = _files.contains(path) |
| 132 ? ChangeType.MODIFY | 130 ? ChangeType.MODIFY |
| 133 : ChangeType.ADD; | 131 : ChangeType.ADD; |
| 134 | 132 |
| 135 _emitEvent(type, path); | 133 _emitEvent(type, path); |
| 136 _files.add(path); | 134 _files.add(path); |
| 137 continue; | 135 continue; |
| 138 } | 136 } |
| 139 | 137 |
| 140 if (_files.containsDir(path)) continue; | 138 if (_files.containsDir(path)) continue; |
| 141 | 139 |
| 142 var subscription; | 140 StreamSubscription<FileSystemEntity> subscription; |
| 143 subscription = new Directory(path).list(recursive: true) | 141 subscription = new Directory(path).list(recursive: true) |
| 144 .listen((entity) { | 142 .listen((entity) { |
| 145 if (entity is Directory) return; | 143 if (entity is Directory) return; |
| 146 if (_files.contains(path)) return; | 144 if (_files.contains(path)) return; |
| 147 | 145 |
| 148 _emitEvent(ChangeType.ADD, entity.path); | 146 _emitEvent(ChangeType.ADD, entity.path); |
| 149 _files.add(entity.path); | 147 _files.add(entity.path); |
| 150 }, onError: (e, stackTrace) { | 148 }, onError: (e, stackTrace) { |
| 151 _emitError(e, stackTrace); | 149 _emitError(e, stackTrace); |
| 152 }, onDone: () { | 150 }, onDone: () { |
| (...skipping 15 matching lines...) Expand all Loading... |
| 168 | 166 |
| 169 /// Sort all the events in a batch into sets based on their path. | 167 /// Sort all the events in a batch into sets based on their path. |
| 170 /// | 168 /// |
| 171 /// A single input event may result in multiple events in the returned map; | 169 /// A single input event may result in multiple events in the returned map; |
| 172 /// for example, a MOVE event becomes a DELETE event for the source and a | 170 /// for example, a MOVE event becomes a DELETE event for the source and a |
| 173 /// CREATE event for the destination. | 171 /// CREATE event for the destination. |
| 174 /// | 172 /// |
| 175 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it | 173 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it |
| 176 /// contain any events relating to [path]. | 174 /// contain any events relating to [path]. |
| 177 Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) { | 175 Map<String, Set<FileSystemEvent>> _sortEvents(List<FileSystemEvent> batch) { |
| 178 var eventsForPaths = {}; | 176 var eventsForPaths = <String, Set>{}; |
| 179 | 177 |
| 180 // FSEvents can report past events, including events on the root directory | 178 // FSEvents can report past events, including events on the root directory |
| 181 // such as it being created. We want to ignore these. If the directory is | 179 // such as it being created. We want to ignore these. If the directory is |
| 182 // really deleted, that's handled by [_onDone]. | 180 // really deleted, that's handled by [_onDone]. |
| 183 batch = batch.where((event) => event.path != path).toList(); | 181 batch = batch.where((event) => event.path != path).toList(); |
| 184 | 182 |
| 185 // Events within directories that already have events are superfluous; the | 183 // Events within directories that already have events are superfluous; the |
| 186 // directory's full contents will be examined anyway, so we ignore such | 184 // directory's full contents will be examined anyway, so we ignore such |
| 187 // events. Emitting them could cause useless or out-of-order events. | 185 // events. Emitting them could cause useless or out-of-order events. |
| 188 var directories = unionAll(batch.map((event) { | 186 var directories = unionAll(batch.map((event) { |
| 189 if (!event.isDirectory) return new Set(); | 187 if (!event.isDirectory) return new Set(); |
| 190 if (event is! FileSystemMoveEvent) return new Set.from([event.path]); | 188 if (event is FileSystemMoveEvent) { |
| 191 return new Set.from([event.path, event.destination]); | 189 return new Set.from([event.path, event.destination]); |
| 190 } |
| 191 return new Set.from([event.path]); |
| 192 })); | 192 })); |
| 193 | 193 |
| 194 isInModifiedDirectory(path) => | 194 isInModifiedDirectory(path) => |
| 195 directories.any((dir) => path != dir && path.startsWith(dir)); | 195 directories.any((dir) => path != dir && path.startsWith(dir)); |
| 196 | 196 |
| 197 addEvent(path, event) { | 197 addEvent(path, event) { |
| 198 if (isInModifiedDirectory(path)) return; | 198 if (isInModifiedDirectory(path)) return; |
| 199 var set = eventsForPaths.putIfAbsent(path, () => new Set()); | 199 var set = eventsForPaths.putIfAbsent(path, () => new Set()); |
| 200 set.add(event); | 200 set.add(event); |
| 201 } | 201 } |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 287 /// This returns a list whose order should be reflected in the events emitted | 287 /// This returns a list whose order should be reflected in the events emitted |
| 288 /// to the user, unlike the batched events from [Directory.watch]. The | 288 /// to the user, unlike the batched events from [Directory.watch]. The |
| 289 /// returned list may be empty, indicating that no changes occurred to [path] | 289 /// returned list may be empty, indicating that no changes occurred to [path] |
| 290 /// (probably indicating that it was created and then immediately deleted). | 290 /// (probably indicating that it was created and then immediately deleted). |
| 291 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { | 291 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { |
| 292 var fileExisted = _files.contains(path); | 292 var fileExisted = _files.contains(path); |
| 293 var dirExisted = _files.containsDir(path); | 293 var dirExisted = _files.containsDir(path); |
| 294 var fileExists = new File(path).existsSync(); | 294 var fileExists = new File(path).existsSync(); |
| 295 var dirExists = new Directory(path).existsSync(); | 295 var dirExists = new Directory(path).existsSync(); |
| 296 | 296 |
| 297 var events = []; | 297 var events = <FileSystemEvent>[]; |
| 298 if (fileExisted) { | 298 if (fileExisted) { |
| 299 if (fileExists) { | 299 if (fileExists) { |
| 300 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); | 300 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); |
| 301 } else { | 301 } else { |
| 302 events.add(new ConstructableFileSystemDeleteEvent(path, false)); | 302 events.add(new ConstructableFileSystemDeleteEvent(path, false)); |
| 303 } | 303 } |
| 304 } else if (dirExisted) { | 304 } else if (dirExisted) { |
| 305 if (dirExists) { | 305 if (dirExists) { |
| 306 // If we got contradictory events for a directory that used to exist and | 306 // If we got contradictory events for a directory that used to exist and |
| 307 // still exists, we need to rescan the whole thing in case it was | 307 // still exists, we need to rescan the whole thing in case it was |
| (...skipping 22 matching lines...) Expand all Loading... |
| 330 // this is probably issue 14849 rather than a real close event. We should | 330 // this is probably issue 14849 rather than a real close event. We should |
| 331 // just restart the watcher. | 331 // just restart the watcher. |
| 332 if (!isReady && new Directory(path).existsSync()) { | 332 if (!isReady && new Directory(path).existsSync()) { |
| 333 _startWatch(); | 333 _startWatch(); |
| 334 return; | 334 return; |
| 335 } | 335 } |
| 336 | 336 |
| 337 // FSEvents can fail to report the contents of the directory being removed | 337 // FSEvents can fail to report the contents of the directory being removed |
| 338 // when the directory itself is removed, so we need to manually mark the | 338 // when the directory itself is removed, so we need to manually mark the |
| 339 // files as removed. | 339 // files as removed. |
| 340 for (var file in _files.toSet()) { | 340 for (var file in _files.paths) { |
| 341 _emitEvent(ChangeType.REMOVE, file); | 341 _emitEvent(ChangeType.REMOVE, file); |
| 342 } | 342 } |
| 343 _files.clear(); | 343 _files.clear(); |
| 344 close(); | 344 close(); |
| 345 } | 345 } |
| 346 | 346 |
| 347 /// Start or restart the underlying [Directory.watch] stream. | 347 /// Start or restart the underlying [Directory.watch] stream. |
| 348 void _startWatch() { | 348 void _startWatch() { |
| 349 // Batch the FSEvent changes together so that we can dedup events. | 349 // Batch the FSEvent changes together so that we can dedup events. |
| 350 var innerStream = new Directory(path).watch(recursive: true) | 350 var innerStream = new Directory(path).watch(recursive: true) |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 390 if (!isReady) return; | 390 if (!isReady) return; |
| 391 _eventsController.add(new WatchEvent(type, path)); | 391 _eventsController.add(new WatchEvent(type, path)); |
| 392 } | 392 } |
| 393 | 393 |
| 394 /// Emit an error, then close the watcher. | 394 /// Emit an error, then close the watcher. |
| 395 void _emitError(error, StackTrace stackTrace) { | 395 void _emitError(error, StackTrace stackTrace) { |
| 396 _eventsController.addError(error, stackTrace); | 396 _eventsController.addError(error, stackTrace); |
| 397 close(); | 397 close(); |
| 398 } | 398 } |
| 399 } | 399 } |
| OLD | NEW |