@@ -10,6 +10,7 @@ import 'dart:io';
1010import 'package:path/path.dart' as p;
1111
1212import '../directory_watcher.dart' ;
13+ import '../event.dart' ;
1314import '../path_set.dart' ;
1415import '../resubscribable.dart' ;
1516import '../utils.dart' ;
@@ -26,11 +27,13 @@ class WindowsDirectoryWatcher extends ResubscribableWatcher
2627
2728class _EventBatcher {
2829 static const Duration _batchDelay = Duration (milliseconds: 100 );
29- final List <FileSystemEvent > events = [];
30+ final List <Event > events = [];
3031 Timer ? timer;
3132
3233 void addEvent (FileSystemEvent event, void Function () callback) {
33- events.add (event);
34+ final convertedEvent = Event .checkAndConvert (event);
35+ if (convertedEvent == null ) return ;
36+ events.add (convertedEvent);
3437 timer? .cancel ();
3538 timer = Timer (_batchDelay, callback);
3639 }
@@ -173,172 +176,136 @@ class _WindowsDirectoryWatcher
173176 }
174177
175178 /// The callback that's run when [Directory.watch] emits a batch of events.
176- void _onBatch (List <FileSystemEvent > batch) {
179+ void _onBatch (List <Event > batch) {
177180 _sortEvents (batch).forEach ((path, eventSet) {
178181 var canonicalEvent = _canonicalEvent (eventSet);
179182 var events = canonicalEvent == null
180183 ? _eventsBasedOnFileSystem (path)
181184 : [canonicalEvent];
182185
183186 for (var event in events) {
184- if (event is FileSystemCreateEvent ) {
185- if ( ! event.isDirectory) {
187+ switch (event.type ) {
188+ case EventType .createFile :
186189 if (_files.contains (path)) continue ;
187-
188190 _emitEvent (ChangeType .ADD , path);
189191 _files.add (path);
190- continue ;
191- }
192192
193- if (_files.containsDir (path)) continue ;
194-
195- // "Path not found" can be caused by creating then quickly removing
196- // a directory: continue without reporting an error. Nested files
197- // that get removed during the `list` are already ignored by `list`
198- // itself, so there are no other types of "path not found" that
199- // might need different handling here.
200- var stream = Directory (path)
201- .list (recursive: true )
202- .ignoring <PathNotFoundException >();
203- var subscription = stream.listen ((entity) {
204- if (entity is Directory ) return ;
205- if (_files.contains (entity.path)) return ;
206-
207- _emitEvent (ChangeType .ADD , entity.path);
208- _files.add (entity.path);
209- }, cancelOnError: true );
210- subscription.onDone (() {
211- _listSubscriptions.remove (subscription);
212- });
213- subscription.onError ((Object e, StackTrace stackTrace) {
214- _listSubscriptions.remove (subscription);
215- _emitError (e, stackTrace);
216- });
217- _listSubscriptions.add (subscription);
218- } else if (event is FileSystemModifyEvent ) {
219- if (! event.isDirectory) {
193+ case EventType .createDirectory:
194+ if (_files.containsDir (path)) continue ;
195+
196+ // "Path not found" can be caused by creating then quickly removing
197+ // a directory: continue without reporting an error. Nested files
198+ // that get removed during the `list` are already ignored by `list`
199+ // itself, so there are no other types of "path not found" that
200+ // might need different handling here.
201+ var stream = Directory (path)
202+ .list (recursive: true )
203+ .ignoring <PathNotFoundException >();
204+ var subscription = stream.listen ((entity) {
205+ if (entity is Directory ) return ;
206+ if (_files.contains (entity.path)) return ;
207+
208+ _emitEvent (ChangeType .ADD , entity.path);
209+ _files.add (entity.path);
210+ }, cancelOnError: true );
211+ subscription.onDone (() {
212+ _listSubscriptions.remove (subscription);
213+ });
214+ subscription.onError ((Object e, StackTrace stackTrace) {
215+ _listSubscriptions.remove (subscription);
216+ _emitError (e, stackTrace);
217+ });
218+ _listSubscriptions.add (subscription);
219+
220+ case EventType .modifyFile:
220221 _emitEvent (ChangeType .MODIFY , path);
221- }
222- } else {
223- assert (event is FileSystemDeleteEvent );
224- for (var removedPath in _files.remove (path)) {
225- _emitEvent (ChangeType .REMOVE , removedPath);
226- }
222+
223+ case EventType .delete:
224+ for (var removedPath in _files.remove (path)) {
225+ _emitEvent (ChangeType .REMOVE , removedPath);
226+ }
227+
228+ // Move events are removed by `_canonicalEvent` and never returned by
229+ // `_eventsBasedOnFileSystem`.
230+ case EventType .moveFile:
231+ case EventType .moveDirectory:
232+ throw StateError (event.type.name);
233+
234+ // Dropped by [Event.checkAndConvert].
235+ case EventType .modifyDirectory:
236+ assert (event.type.isIgnoredOnWindows);
227237 }
228238 }
229239 });
230240 }
231241
232242 /// Sort all the events in a batch into sets based on their path.
233243 ///
234- /// A single input event may result in multiple events in the returned map;
235- /// for example, a MOVE event becomes a DELETE event for the source and a
236- /// CREATE event for the destination.
237- ///
238- /// The returned events won't contain any [FileSystemMoveEvent] s, nor will it
239- /// contain any events relating to [path] .
240- Map <String , Set <FileSystemEvent >> _sortEvents (List <FileSystemEvent > batch) {
241- var eventsForPaths = < String , Set <FileSystemEvent >> {};
242-
243- // Events within directories that already have events are superfluous; the
244- // directory's full contents will be examined anyway, so we ignore such
245- // events. Emitting them could cause useless or out-of-order events.
244+ /// Events for [path] are discarded.
245+ Map <String , Set <Event >> _sortEvents (List <Event > batch) {
246+ var eventsForPaths = < String , Set <Event >> {};
247+
248+ // Events within created or moved directories are not needed as the
249+ // directory's full contents will be listed.
246250 var directories = unionAll (
247251 batch.map ((event) {
248- if (! event.isDirectory) return < String > {};
249- if (event is FileSystemMoveEvent ) {
250- var destination = event.destination;
251- if (destination != null ) {
252- return {event.path, destination};
253- }
252+ if (event.type == EventType .createDirectory ||
253+ event.type == EventType .moveDirectory) {
254+ final destination = event.destination;
255+ return {event.path, if (destination != null ) destination};
254256 }
255- return {event.path };
257+ return const < String > { };
256258 }),
257259 );
258260
259261 bool isInModifiedDirectory (String path) =>
260262 directories.any ((dir) => path != dir && p.isWithin (dir, path));
261263
262- void addEvent (String path, FileSystemEvent event) {
264+ void addEvent (String path, Event event) {
263265 if (isInModifiedDirectory (path)) return ;
264- eventsForPaths.putIfAbsent (path, () => < FileSystemEvent > {}).add (event);
266+ eventsForPaths.putIfAbsent (path, () => < Event > {}).add (event);
265267 }
266268
267269 for (var event in batch) {
268- if (event is FileSystemMoveEvent ) {
269- var destination = event.destination;
270- if (destination != null ) {
271- addEvent (destination, event);
272- }
273- }
274270 addEvent (event.path, event);
271+ final destination = event.destination;
272+ if (destination != null ) {
273+ addEvent (destination, event);
274+ }
275275 }
276276
277277 return eventsForPaths;
278278 }
279279
280- /// Returns the canonical event from a batch of events on the same path, if
281- /// one exists.
282- ///
283- /// If [batch] doesn't contain any contradictory events (e.g. DELETE and
284- /// CREATE, or events with different values for `isDirectory` ), this returns a
285- /// single event that describes what happened to the path in question.
286- ///
287- /// If [batch] does contain contradictory events, this returns `null` to
288- /// indicate that the state of the path on the filesystem should be checked to
289- /// determine what occurred.
290- FileSystemEvent ? _canonicalEvent (Set <FileSystemEvent > batch) {
291- // An empty batch indicates that we've learned earlier that the batch is
292- // contradictory (e.g. because of a move).
280+ /// Returns the canonical event from a batch of events on the same path, or
281+ /// `null` to indicate that the filesystem should be checked.
282+ Event ? _canonicalEvent (Set <Event > batch) {
283+ // If the batch is empty, return `null`.
293284 if (batch.isEmpty) return null ;
294285
295- var type = batch.first.type;
296- var isDir = batch.first.isDirectory;
297-
298- for (var event in batch.skip (1 )) {
299- // If one event reports that the file is a directory and another event
300- // doesn't, that's a contradiction.
301- if (isDir != event.isDirectory) return null ;
302-
303- // Modify events don't contradict either CREATE or REMOVE events. We can
304- // safely assume the file was modified after a CREATE or before the
305- // REMOVE; otherwise there will also be a REMOVE or CREATE event
306- // (respectively) that will be contradictory.
307- if (event is FileSystemModifyEvent ) continue ;
308- assert (
309- event is FileSystemCreateEvent ||
310- event is FileSystemDeleteEvent ||
311- event is FileSystemMoveEvent ,
312- );
313-
314- // If we previously thought this was a MODIFY, we now consider it to be a
315- // CREATE or REMOVE event. This is safe for the same reason as above.
316- if (type == FileSystemEvent .modify) {
317- type = event.type;
318- continue ;
319- }
320-
321- // A CREATE event contradicts a REMOVE event and vice versa.
322- assert (
323- type == FileSystemEvent .create ||
324- type == FileSystemEvent .delete ||
325- type == FileSystemEvent .move,
326- );
327- if (type != event.type) return null ;
286+ // Resolve the event type for the batch.
287+ var types = batch.map ((e) => e.type).toSet ();
288+ EventType type;
289+ if (types.length == 1 ) {
290+ // There's only one event.
291+ type = types.single;
292+ } else if (types.length == 2 &&
293+ types.contains (EventType .modifyFile) &&
294+ types.contains (EventType .createFile)) {
295+ // Combine events of type [EventType.modifyFile] and
296+ // [EventType.createFile] to one event.
297+ type = EventType .createFile;
298+ } else {
299+ // There are incompatible event types, check the filesystem.
300+ return null ;
328301 }
329302
330- switch (type) {
331- case FileSystemEvent .create:
332- return FileSystemCreateEvent (batch.first.path, isDir);
333- case FileSystemEvent .delete:
334- return FileSystemDeleteEvent (batch.first.path, isDir);
335- case FileSystemEvent .modify:
336- return FileSystemModifyEvent (batch.first.path, isDir, false );
337- case FileSystemEvent .move:
338- return null ;
339- default :
340- throw StateError ('unreachable' );
303+ // Move events are always resolved by checking the filesystem.
304+ if (type == EventType .moveFile || type == EventType .moveDirectory) {
305+ return null ;
341306 }
307+
308+ return batch.firstWhere ((e) => e.type == type);
342309 }
343310
344311 /// Returns zero or more events that describe the change between the last
@@ -348,7 +315,7 @@ class _WindowsDirectoryWatcher
348315 /// to the user, unlike the batched events from [Directory.watch] . The
349316 /// returned list may be empty, indicating that no changes occurred to [path]
350317 /// (probably indicating that it was created and then immediately deleted).
351- List <FileSystemEvent > _eventsBasedOnFileSystem (String path) {
318+ List <Event > _eventsBasedOnFileSystem (String path) {
352319 var fileExisted = _files.contains (path);
353320 var dirExisted = _files.containsDir (path);
354321
@@ -358,32 +325,32 @@ class _WindowsDirectoryWatcher
358325 fileExists = File (path).existsSync ();
359326 dirExists = Directory (path).existsSync ();
360327 } on FileSystemException {
361- return const < FileSystemEvent > [];
328+ return const < Event > [];
362329 }
363330
364- var events = < FileSystemEvent > [];
331+ var events = < Event > [];
365332 if (fileExisted) {
366333 if (fileExists) {
367- events.add (FileSystemModifyEvent (path, false , false ));
334+ events.add (Event . modifyFile (path));
368335 } else {
369- events.add (FileSystemDeleteEvent (path, false ));
336+ events.add (Event . delete (path));
370337 }
371338 } else if (dirExisted) {
372339 if (dirExists) {
373340 // If we got contradictory events for a directory that used to exist and
374341 // still exists, we need to rescan the whole thing in case it was
375342 // replaced with a different directory.
376- events.add (FileSystemDeleteEvent (path, true ));
377- events.add (FileSystemCreateEvent (path, true ));
343+ events.add (Event . delete (path));
344+ events.add (Event . createDirectory (path));
378345 } else {
379- events.add (FileSystemDeleteEvent (path, true ));
346+ events.add (Event . delete (path));
380347 }
381348 }
382349
383350 if (! fileExisted && fileExists) {
384- events.add (FileSystemCreateEvent (path, false ));
351+ events.add (Event . createFile (path));
385352 } else if (! dirExisted && dirExists) {
386- events.add (FileSystemCreateEvent (path, true ));
353+ events.add (Event . createDirectory (path));
387354 }
388355
389356 return events;
0 commit comments