Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(70)

Side by Side Diff: pkg/watcher/lib/src/directory_watcher/windows.dart

Issue 312743002: Use 'Directory.watch' on Windows in pkg/watcher, instead of pooling. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Review updates Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, 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 4
5 library watcher.directory_watcher.mac_os; 5 library watcher.directory_watcher.windows;
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;
11 import 'package:stack_trace/stack_trace.dart'; 11 import 'package:stack_trace/stack_trace.dart';
12 12
13 import '../constructable_file_system_event.dart'; 13 import '../constructable_file_system_event.dart';
14 import '../path_set.dart'; 14 import '../path_set.dart';
15 import '../utils.dart'; 15 import '../utils.dart';
16 import '../watch_event.dart'; 16 import '../watch_event.dart';
17 import 'resubscribable.dart'; 17 import 'resubscribable.dart';
18 18
19 /// Uses the FSEvents subsystem to watch for filesystem events. 19 class WindowsDirectoryWatcher extends ResubscribableDirectoryWatcher {
20 /// 20 WindowsDirectoryWatcher(String directory)
21 /// FSEvents has two main idiosyncrasies that this class works around. First, it 21 : super(directory, () => new _WindowsDirectoryWatcher(directory));
22 /// will occasionally report events that occurred before the filesystem watch
23 /// was initiated. Second, if multiple events happen to the same file in close
24 /// succession, it won't report them in the order they occurred. See issue
25 /// 14373.
26 ///
27 /// This also works around issues 16003 and 14849 in the implementation of
28 /// [Directory.watch].
29 class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher {
30 // TODO(nweiz): remove these when issue 15042 is fixed.
31 static var logDebugInfo = false;
32 static var _count = 0;
33
34 MacOSDirectoryWatcher(String directory)
35 : super(directory, () => new _MacOSDirectoryWatcher(directory, _count++));
36 } 22 }
37 23
38 class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher { 24 class _WindowsDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
39 // TODO(nweiz): remove these when issue 15042 is fixed.
40 static var _count = 0;
41 final String _id;
42
43 final String directory; 25 final String directory;
44 26
45 Stream<WatchEvent> get events => _eventsController.stream; 27 Stream<WatchEvent> get events => _eventsController.stream;
46 final _eventsController = new StreamController<WatchEvent>.broadcast(); 28 final _eventsController = new StreamController<WatchEvent>.broadcast();
47 29
48 bool get isReady => _readyCompleter.isCompleted; 30 bool get isReady => _readyCompleter.isCompleted;
49 31
50 Future get ready => _readyCompleter.future; 32 Future get ready => _readyCompleter.future;
51 final _readyCompleter = new Completer(); 33 final _readyCompleter = new Completer();
52 34
53 /// The set of files that are known to exist recursively within the watched 35 /// The set of files that are known to exist recursively within the watched
54 /// directory. 36 /// directory.
55 /// 37 ///
56 /// The state of files on the filesystem is compared against this to determine 38 /// The state of files on the filesystem is compared against this to determine
57 /// the real change that occurred when working around issue 14373. This is 39 /// the real change that occurred. This is also used to emit REMOVE events
58 /// also used to emit REMOVE events when subdirectories are moved out of the 40 /// when subdirectories are moved out of the watched directory.
59 /// watched directory.
60 final PathSet _files; 41 final PathSet _files;
61 42
62 /// The subscription to the stream returned by [Directory.watch]. 43 /// The subscription to the stream returned by [Directory.watch].
63 ///
64 /// This is separate from [_subscriptions] because this stream occasionally
65 /// needs to be resubscribed in order to work around issue 14849.
66 StreamSubscription<FileSystemEvent> _watchSubscription; 44 StreamSubscription<FileSystemEvent> _watchSubscription;
67 45
46 /// The subscription to the stream returned by [Directory.watch] of the
47 /// parent directory of [directory]. This is needed to detect removal of
48 /// [directory], as Windows will lock the directory when watching.
49 StreamSubscription<FileSystemEvent> _parentWatchSubscription;
50
68 /// The subscription to the [Directory.list] call for the initial listing of 51 /// The subscription to the [Directory.list] call for the initial listing of
69 /// the directory to determine its initial state. 52 /// the directory to determine its initial state.
70 StreamSubscription<FileSystemEntity> _initialListSubscription; 53 StreamSubscription<FileSystemEntity> _initialListSubscription;
71 54
72 /// The subscription to the [Directory.list] call for listing the contents of 55 /// The subscription to the [Directory.list] call for listing the contents of
73 /// a subdirectory that was moved into the watched directory. 56 /// a subdirectory that was moved into the watched directory.
74 StreamSubscription<FileSystemEntity> _listSubscription; 57 StreamSubscription<FileSystemEntity> _listSubscription;
75 58
76 /// The timer for tracking how long we wait for an initial batch of bogus 59 _WindowsDirectoryWatcher(String directory)
77 /// events (see issue 14373). 60 : directory = directory, _files = new PathSet(directory) {
78 Timer _bogusEventTimer;
79
80 _MacOSDirectoryWatcher(String directory, int parentId)
81 : directory = directory,
82 _files = new PathSet(directory),
83 _id = "$parentId/${_count++}" {
84 _startWatch(); 61 _startWatch();
85 62 _startParentWatcher();
86 // Before we're ready to emit events, wait for [_listDir] to complete and 63
87 // for enough time to elapse that if bogus events (issue 14373) would be 64 // Before we're ready to emit events, wait for [_listDir] to complete.
88 // emitted, they will be. 65 _listDir().then(_readyCompleter.complete);
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) {
101 print("[$_id] watcher is ready, known files:");
102 for (var file in _files.toSet()) {
103 print("[$_id] ${p.relative(file, from: directory)}");
104 }
105 }
106 _readyCompleter.complete();
107 });
108 } 66 }
109 67
110 void close() { 68 void close() {
111 if (MacOSDirectoryWatcher.logDebugInfo) {
112 print("[$_id] watcher is closed\n${new Chain.current().terse}");
113 }
114 if (_watchSubscription != null) _watchSubscription.cancel(); 69 if (_watchSubscription != null) _watchSubscription.cancel();
70 if (_parentWatchSubscription != null) _parentWatchSubscription.cancel();
115 if (_initialListSubscription != null) _initialListSubscription.cancel(); 71 if (_initialListSubscription != null) _initialListSubscription.cancel();
116 if (_listSubscription != null) _listSubscription.cancel(); 72 if (_listSubscription != null) _listSubscription.cancel();
117 _watchSubscription = null; 73 _watchSubscription = null;
74 _parentWatchSubscription = null;
118 _initialListSubscription = null; 75 _initialListSubscription = null;
119 _listSubscription = null; 76 _listSubscription = null;
120 _eventsController.close(); 77 _eventsController.close();
121 } 78 }
122 79
80 void _startParentWatcher() {
kasperl 2014/06/03 13:08:10 Add a comment that explains why you have a parent
Anders Johnsen 2014/06/03 13:41:56 Done.
81 var absoluteDir = p.absolute(directory);
82 var parent = p.dirname(absoluteDir);
83 if (FileSystemEntity.identicalSync(parent, directory)) return;
84 var parentStream = Chain.track(
85 new Directory(parent).watch(recursive: false));
86 _parentWatchSubscription = parentStream
87 .listen((event) {
kasperl 2014/06/03 13:08:10 I'd move .listen up to the previous line and use l
Anders Johnsen 2014/06/03 13:41:56 Done.
88 if (!event.path.startsWith(absoluteDir)) {
89 // Only look at events for 'directory'.
90 return;
91 }
92 // Test if the directory is removed.
93 if (FileSystemEntity.typeSync(directory) ==
94 FileSystemEntityType.NOT_FOUND) {
95 for (var path in _files.toSet()) {
kasperl 2014/06/03 13:08:10 Does toSet copy the PathSet? Is it necessary?
Anders Johnsen 2014/06/03 13:41:56 I think it copies, but it does not offer an altern
96 _emitEvent(ChangeType.REMOVE, path);
97 }
98 _files.clear();
99 close();
100 }
101 }, onError: (error) {
102 // Ignore errors, simply close the stream.
103 _parentWatchSubscription.cancel();
104 _parentWatchSubscription = null;
105 });
106 }
107
123 /// The callback that's run when [Directory.watch] emits a batch of events. 108 /// The callback that's run when [Directory.watch] emits a batch of events.
124 void _onBatch(List<FileSystemEvent> batch) { 109 void _onBatch(List<FileSystemEvent> batch) {
125 if (MacOSDirectoryWatcher.logDebugInfo) { 110 // If we get a batch of events before we're ready to begin emitting events.
126 print("[$_id] ======== batch:");
127 for (var event in batch) {
128 print("[$_id] ${_formatEvent(event)}");
129 }
130
131 print("[$_id] known files:");
132 for (var file in _files.toSet()) {
133 print("[$_id] ${p.relative(file, from: directory)}");
134 }
135 }
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. 111 // Ignore those events and re-list the directory.
140 if (!isReady) { 112 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; 113 return;
158 } 114 }
159 115
160 _sortEvents(batch).forEach((path, events) { 116 _sortEvents(batch).forEach((path, events) {
161 var relativePath = p.relative(path, from: directory); 117 var relativePath = p.relative(path, from: directory);
162 if (MacOSDirectoryWatcher.logDebugInfo) {
163 print("[$_id] events for $relativePath:");
164 for (var event in events) {
165 print("[$_id] ${_formatEvent(event)}");
166 }
167 }
168 118
169 var canonicalEvent = _canonicalEvent(events); 119 var canonicalEvent = _canonicalEvent(events);
170 events = canonicalEvent == null ? 120 events = canonicalEvent == null ?
171 _eventsBasedOnFileSystem(path) : [canonicalEvent]; 121 _eventsBasedOnFileSystem(path) : [canonicalEvent];
172 if (MacOSDirectoryWatcher.logDebugInfo) {
173 print("[$_id] canonical event for $relativePath: "
174 "${_formatEvent(canonicalEvent)}");
175 print("[$_id] actionable events for $relativePath: "
176 "${events.map(_formatEvent)}");
177 }
178 122
179 for (var event in events) { 123 for (var event in events) {
180 if (event is FileSystemCreateEvent) { 124 if (event is FileSystemCreateEvent) {
181 if (!event.isDirectory) { 125 if (!event.isDirectory) {
182 // Don't emit ADD events for files or directories that we already
183 // know about. Such an event comes from FSEvents reporting an add
184 // that happened prior to the watch beginning.
185 if (_files.contains(path)) continue;
186
187 _emitEvent(ChangeType.ADD, path); 126 _emitEvent(ChangeType.ADD, path);
188 _files.add(path); 127 _files.add(path);
189 continue; 128 continue;
190 } 129 }
191 130
192 if (_files.containsDir(path)) continue; 131 if (_files.containsDir(path)) continue;
193 132
194 var stream = Chain.track(new Directory(path).list(recursive: true)); 133 var stream = Chain.track(new Directory(path).list(recursive: true));
195 _listSubscription = stream.listen((entity) { 134 _listSubscription = stream.listen((entity) {
kasperl 2014/06/03 13:08:10 The indentation looks nice here.
Anders Johnsen 2014/06/03 13:41:56 Done.
196 if (entity is Directory) return; 135 if (entity is Directory) return;
197 if (_files.contains(path)) return; 136 if (_files.contains(path)) return;
198 137
199 _emitEvent(ChangeType.ADD, entity.path); 138 _emitEvent(ChangeType.ADD, entity.path);
200 _files.add(entity.path); 139 _files.add(entity.path);
201 }, onError: (e, stackTrace) { 140 }, onError: (e, stackTrace) {
202 if (MacOSDirectoryWatcher.logDebugInfo) {
203 print("[$_id] got error listing $relativePath: $e");
204 }
205 _emitError(e, stackTrace); 141 _emitError(e, stackTrace);
206 }, cancelOnError: true); 142 }, cancelOnError: true);
207 } else if (event is FileSystemModifyEvent) { 143 } else if (event is FileSystemModifyEvent) {
208 assert(!event.isDirectory); 144 assert(!event.isDirectory);
209 _emitEvent(ChangeType.MODIFY, path); 145 _emitEvent(ChangeType.MODIFY, path);
210 } else { 146 } else {
211 assert(event is FileSystemDeleteEvent); 147 assert(event is FileSystemDeleteEvent);
212 for (var removedPath in _files.remove(path)) { 148 for (var removedPath in _files.remove(path)) {
213 _emitEvent(ChangeType.REMOVE, removedPath); 149 _emitEvent(ChangeType.REMOVE, removedPath);
214 } 150 }
215 } 151 }
216 } 152 }
217 }); 153 });
218
219 if (MacOSDirectoryWatcher.logDebugInfo) {
220 print("[$_id] ======== batch complete");
221 }
222 } 154 }
223 155
224 /// Sort all the events in a batch into sets based on their path. 156 /// Sort all the events in a batch into sets based on their path.
225 /// 157 ///
226 /// A single input event may result in multiple events in the returned map; 158 /// A single input event may result in multiple events in the returned map;
227 /// for example, a MOVE event becomes a DELETE event for the source and a 159 /// for example, a MOVE event becomes a DELETE event for the source and a
228 /// CREATE event for the destination. 160 /// CREATE event for the destination.
229 /// 161 ///
230 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it 162 /// The returned events won't contain any [FileSystemMoveEvent]s, nor will it
231 /// contain any events relating to [directory]. 163 /// contain any events relating to [directory].
(...skipping 17 matching lines...) Expand all
249 isInModifiedDirectory(path) => 181 isInModifiedDirectory(path) =>
250 directories.any((dir) => path != dir && path.startsWith(dir)); 182 directories.any((dir) => path != dir && path.startsWith(dir));
251 183
252 addEvent(path, event) { 184 addEvent(path, event) {
253 if (isInModifiedDirectory(path)) return; 185 if (isInModifiedDirectory(path)) return;
254 var set = eventsForPaths.putIfAbsent(path, () => new Set()); 186 var set = eventsForPaths.putIfAbsent(path, () => new Set());
255 set.add(event); 187 set.add(event);
256 } 188 }
257 189
258 for (var event in batch) { 190 for (var event in batch) {
259 // The Mac OS watcher doesn't emit move events. See issue 14806. 191 if (event is FileSystemMoveEvent) {
260 assert(event is! FileSystemMoveEvent); 192 FileSystemMoveEvent moveEvent = event;
193 addEvent(moveEvent.destination, event);
194 }
261 addEvent(event.path, event); 195 addEvent(event.path, event);
262 } 196 }
263 197
264 return eventsForPaths; 198 return eventsForPaths;
265 } 199 }
266 200
267 /// Returns the canonical event from a batch of events on the same path, if 201 /// Returns the canonical event from a batch of events on the same path, if
268 /// one exists. 202 /// one exists.
269 /// 203 ///
270 /// If [batch] doesn't contain any contradictory events (e.g. DELETE and 204 /// If [batch] doesn't contain any contradictory events (e.g. DELETE and
(...skipping 18 matching lines...) Expand all
289 if (isDir != event.isDirectory) return null; 223 if (isDir != event.isDirectory) return null;
290 224
291 // Modify events don't contradict either CREATE or REMOVE events. We can 225 // Modify events don't contradict either CREATE or REMOVE events. We can
292 // safely assume the file was modified after a CREATE or before the 226 // safely assume the file was modified after a CREATE or before the
293 // REMOVE; otherwise there will also be a REMOVE or CREATE event 227 // REMOVE; otherwise there will also be a REMOVE or CREATE event
294 // (respectively) that will be contradictory. 228 // (respectively) that will be contradictory.
295 if (event is FileSystemModifyEvent) { 229 if (event is FileSystemModifyEvent) {
296 hadModifyEvent = true; 230 hadModifyEvent = true;
297 continue; 231 continue;
298 } 232 }
299 assert(event is FileSystemCreateEvent || event is FileSystemDeleteEvent); 233 assert(event is FileSystemCreateEvent ||
234 event is FileSystemDeleteEvent ||
235 event is FileSystemMoveEvent);
300 236
301 // If we previously thought this was a MODIFY, we now consider it to be a 237 // If we previously thought this was a MODIFY, we now consider it to be a
302 // CREATE or REMOVE event. This is safe for the same reason as above. 238 // CREATE or REMOVE event. This is safe for the same reason as above.
303 if (type == FileSystemEvent.MODIFY) { 239 if (type == FileSystemEvent.MODIFY) {
304 type = event.type; 240 type = event.type;
305 continue; 241 continue;
306 } 242 }
307 243
308 // A CREATE event contradicts a REMOVE event and vice versa. 244 // A CREATE event contradicts a REMOVE event and vice versa.
309 assert(type == FileSystemEvent.CREATE || type == FileSystemEvent.DELETE); 245 assert(type == FileSystemEvent.CREATE ||
246 type == FileSystemEvent.DELETE ||
247 type == FileSystemEvent.MOVE);
310 if (type != event.type) return null; 248 if (type != event.type) return null;
311 } 249 }
312 250
313 // If we got a CREATE event for a file we already knew about, that comes 251 // If we got a CREATE event for a file we already knew about, that comes
314 // from FSEvents reporting an add that happened prior to the watch 252 // from FSEvents reporting an add that happened prior to the watch
315 // beginning. If we also received a MODIFY event, we want to report that, 253 // beginning. If we also received a MODIFY event, we want to report that,
316 // but not the CREATE. 254 // but not the CREATE.
317 if (type == FileSystemEvent.CREATE && hadModifyEvent && 255 if (type == FileSystemEvent.CREATE && hadModifyEvent &&
318 _files.contains(batch.first.path)) { 256 _files.contains(batch.first.path)) {
319 type = FileSystemEvent.MODIFY; 257 type = FileSystemEvent.MODIFY;
320 } 258 }
321 259
322 switch (type) { 260 switch (type) {
323 case FileSystemEvent.CREATE: 261 case FileSystemEvent.CREATE:
324 // Issue 16003 means that a CREATE event for a directory can indicate 262 return new ConstructableFileSystemCreateEvent(batch.first.path, isDir);
325 // that the directory was moved and then re-created.
326 // [_eventsBasedOnFileSystem] will handle this correctly by producing a
327 // DELETE event followed by a CREATE event if the directory exists.
328 if (isDir) return null;
329 return new ConstructableFileSystemCreateEvent(batch.first.path, false);
330 case FileSystemEvent.DELETE: 263 case FileSystemEvent.DELETE:
331 return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir); 264 return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir);
332 case FileSystemEvent.MODIFY: 265 case FileSystemEvent.MODIFY:
333 return new ConstructableFileSystemModifyEvent( 266 return new ConstructableFileSystemModifyEvent(
334 batch.first.path, isDir, false); 267 batch.first.path, isDir, false);
268 case FileSystemEvent.MOVE:
269 return null;
335 default: assert(false); 270 default: assert(false);
336 } 271 }
337 } 272 }
338 273
339 /// Returns one or more events that describe the change between the last known 274 /// Returns one or more events that describe the change between the last known
340 /// state of [path] and its current state on the filesystem. 275 /// state of [path] and its current state on the filesystem.
341 /// 276 ///
342 /// This returns a list whose order should be reflected in the events emitted 277 /// This returns a list whose order should be reflected in the events emitted
343 /// to the user, unlike the batched events from [Directory.watch]. The 278 /// to the user, unlike the batched events from [Directory.watch]. The
344 /// returned list may be empty, indicating that no changes occurred to [path] 279 /// returned list may be empty, indicating that no changes occurred to [path]
345 /// (probably indicating that it was created and then immediately deleted). 280 /// (probably indicating that it was created and then immediately deleted).
346 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { 281 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) {
347 var fileExisted = _files.contains(path); 282 var fileExisted = _files.contains(path);
348 var dirExisted = _files.containsDir(path); 283 var dirExisted = _files.containsDir(path);
349 var fileExists = new File(path).existsSync(); 284 var fileExists = new File(path).existsSync();
350 var dirExists = new Directory(path).existsSync(); 285 var dirExists = new Directory(path).existsSync();
351 286
352 if (MacOSDirectoryWatcher.logDebugInfo) {
353 print("[$_id] checking file system for "
354 "${p.relative(path, from: directory)}");
355 print("[$_id] file existed: $fileExisted");
356 print("[$_id] dir existed: $dirExisted");
357 print("[$_id] file exists: $fileExists");
358 print("[$_id] dir exists: $dirExists");
359 }
360
361 var events = []; 287 var events = [];
362 if (fileExisted) { 288 if (fileExisted) {
363 if (fileExists) { 289 if (fileExists) {
364 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); 290 events.add(new ConstructableFileSystemModifyEvent(path, false, false));
365 } else { 291 } else {
366 events.add(new ConstructableFileSystemDeleteEvent(path, false)); 292 events.add(new ConstructableFileSystemDeleteEvent(path, false));
367 } 293 }
368 } else if (dirExisted) { 294 } else if (dirExisted) {
369 if (dirExists) { 295 if (dirExists) {
370 // If we got contradictory events for a directory that used to exist and 296 // If we got contradictory events for a directory that used to exist and
371 // still exists, we need to rescan the whole thing in case it was 297 // still exists, we need to rescan the whole thing in case it was
372 // replaced with a different directory. 298 // replaced with a different directory.
373 events.add(new ConstructableFileSystemDeleteEvent(path, true)); 299 events.add(new ConstructableFileSystemDeleteEvent(path, true));
374 events.add(new ConstructableFileSystemCreateEvent(path, true)); 300 events.add(new ConstructableFileSystemCreateEvent(path, true));
375 } else { 301 } else {
376 events.add(new ConstructableFileSystemDeleteEvent(path, true)); 302 events.add(new ConstructableFileSystemDeleteEvent(path, true));
377 } 303 }
378 } 304 }
379 305
380 if (!fileExisted && fileExists) { 306 if (!fileExisted && fileExists) {
381 events.add(new ConstructableFileSystemCreateEvent(path, false)); 307 events.add(new ConstructableFileSystemCreateEvent(path, false));
382 } else if (!dirExisted && dirExists) { 308 } else if (!dirExisted && dirExists) {
383 events.add(new ConstructableFileSystemCreateEvent(path, true)); 309 events.add(new ConstructableFileSystemCreateEvent(path, true));
384 } 310 }
385 311
386 return events; 312 return events;
387 } 313 }
388 314
389 /// The callback that's run when the [Directory.watch] stream is closed. 315 /// The callback that's run when the [Directory.watch] stream is closed.
316 /// Note that this is unlikely to happen on Windows, unless the system itself
317 /// closes the handle.
390 void _onDone() { 318 void _onDone() {
391 if (MacOSDirectoryWatcher.logDebugInfo) print("[$_id] stream closed");
392
393 _watchSubscription = null; 319 _watchSubscription = null;
394 320
395 // If the directory still exists and we're still expecting bogus events, 321 // Emit remove-events for any remaining files.
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
407 // when the directory itself is removed, so we need to manually mark the
408 // files as removed.
409 for (var file in _files.toSet()) { 322 for (var file in _files.toSet()) {
410 _emitEvent(ChangeType.REMOVE, file); 323 _emitEvent(ChangeType.REMOVE, file);
411 } 324 }
412 _files.clear(); 325 _files.clear();
413 close(); 326 close();
414 } 327 }
415 328
416 /// Start or restart the underlying [Directory.watch] stream. 329 /// Start or restart the underlying [Directory.watch] stream.
417 void _startWatch() { 330 void _startWatch() {
418 // Batch the FSEvent changes together so that we can dedup events. 331 // Batch the FSEvent changes together so that we can dedup events.
419 var innerStream = 332 var innerStream =
420 Chain.track(new Directory(directory).watch(recursive: true)) 333 Chain.track(new Directory(directory).watch(recursive: true))
421 .transform(new BatchedStreamTransformer<FileSystemEvent>()); 334 .transform(new BatchedStreamTransformer<FileSystemEvent>());
422 _watchSubscription = innerStream.listen(_onBatch, 335 _watchSubscription = innerStream.listen(_onBatch,
423 onError: _eventsController.addError, 336 onError: _eventsController.addError,
424 onDone: _onDone); 337 onDone: _onDone);
425 } 338 }
426 339
427 /// Starts or restarts listing the watched directory to get an initial picture 340 /// Starts or restarts listing the watched directory to get an initial picture
428 /// of its state. 341 /// of its state.
429 Future _listDir() { 342 Future _listDir() {
430 assert(!isReady); 343 assert(!isReady);
431 if (_initialListSubscription != null) _initialListSubscription.cancel(); 344 if (_initialListSubscription != null) _initialListSubscription.cancel();
432 345
433 _files.clear(); 346 _files.clear();
434 var completer = new Completer(); 347 var completer = new Completer();
435 var stream = Chain.track(new Directory(directory).list(recursive: true)); 348 var stream = Chain.track(new Directory(directory).list(recursive: true));
436 _initialListSubscription = stream.listen((entity) { 349 _initialListSubscription = stream
kasperl 2014/06/03 13:08:10 I preferred the code as it was.
Anders Johnsen 2014/06/03 13:41:56 Rewrote.
437 if (entity is! Directory) _files.add(entity.path); 350 .listen((entity) {
438 }, 351 if (entity is! Directory) _files.add(entity.path);
352 },
439 onError: _emitError, 353 onError: _emitError,
440 onDone: completer.complete, 354 onDone: completer.complete,
441 cancelOnError: true); 355 cancelOnError: true);
442 return completer.future; 356 return completer.future;
443 } 357 }
444 358
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]. 359 /// Emit an event with the given [type] and [path].
459 void _emitEvent(ChangeType type, String path) { 360 void _emitEvent(ChangeType type, String path) {
460 if (!isReady) return; 361 if (!isReady) return;
461 362
462 if (MacOSDirectoryWatcher.logDebugInfo) {
463 print("[$_id] emitting $type ${p.relative(path, from: directory)}");
464 }
465
466 _eventsController.add(new WatchEvent(type, path)); 363 _eventsController.add(new WatchEvent(type, path));
467 } 364 }
468 365
469 /// Emit an error, then close the watcher. 366 /// Emit an error, then close the watcher.
470 void _emitError(error, StackTrace stackTrace) { 367 void _emitError(error, StackTrace stackTrace) {
471 if (MacOSDirectoryWatcher.logDebugInfo) {
472 print("[$_id] emitting error: $error\n" +
473 "${new Chain.forTrace(stackTrace).terse}");
474 }
475 _eventsController.addError(error, stackTrace); 368 _eventsController.addError(error, stackTrace);
476 close(); 369 close();
477 } 370 }
478
479 // TODO(nweiz): remove this when issue 15042 is fixed.
480 /// Return a human-friendly string representation of [event].
481 String _formatEvent(FileSystemEvent event) {
482 if (event == null) return 'null';
483
484 var path = p.relative(event.path, from: directory);
485 var type = event.isDirectory ? 'directory' : 'file';
486 if (event is FileSystemCreateEvent) {
487 return "create $type $path";
488 } else if (event is FileSystemDeleteEvent) {
489 return "delete $type $path";
490 } else if (event is FileSystemModifyEvent) {
491 return "modify $type $path";
492 } else if (event is FileSystemMoveEvent) {
493 return "move $type $path to "
494 "${p.relative(event.destination, from: directory)}";
495 }
496 }
497 } 371 }
OLDNEW
« no previous file with comments | « pkg/watcher/lib/src/directory_watcher.dart ('k') | pkg/watcher/test/directory_watcher/windows_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698