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 |