@@ -8,6 +8,7 @@ import 'dart:io';
88import 'package:path/path.dart' as p;
99
1010import '../directory_watcher.dart' ;
11+ import '../event.dart' ;
1112import '../path_set.dart' ;
1213import '../resubscribable.dart' ;
1314import '../utils.dart' ;
@@ -63,7 +64,7 @@ class _MacOSDirectoryWatcher
6364 ///
6465 /// This is separate from [_listSubscriptions] because this stream
6566 /// occasionally needs to be resubscribed in order to work around issue 14849.
66- StreamSubscription <List <FileSystemEvent >>? _watchSubscription;
67+ StreamSubscription <List <Event >>? _watchSubscription;
6768
6869 /// The subscription to the [Directory.list] call for the initial listing of
6970 /// the directory to determine its initial state.
@@ -109,7 +110,7 @@ class _MacOSDirectoryWatcher
109110 }
110111
111112 /// The callback that's run when [Directory.watch] emits a batch of events.
112- void _onBatch (List <FileSystemEvent > batch) {
113+ void _onBatch (List <Event > batch) {
113114 // If we get a batch of events before we're ready to begin emitting events,
114115 // it's probable that it's a batch of pre-watcher events (see issue 14373).
115116 // Ignore those events and re-list the directory.
@@ -132,8 +133,8 @@ class _MacOSDirectoryWatcher
132133 : [canonicalEvent];
133134
134135 for (var event in events) {
135- if (event is FileSystemCreateEvent ) {
136- if ( ! event.isDirectory) {
136+ switch (event.type ) {
137+ case EventType .createFile :
137138 // If we already know about the file, treat it like a modification.
138139 // This can happen if a file is copied on top of an existing one.
139140 // We'll see an ADD event for the latter file when from the user's
@@ -143,157 +144,117 @@ class _MacOSDirectoryWatcher
143144
144145 _emitEvent (type, path);
145146 _files.add (path);
146- continue ;
147- }
148-
149- if (_files.containsDir (path)) continue ;
150-
151- var stream = Directory (path)
152- .list (recursive: true )
153- .ignoring <PathNotFoundException >();
154- var subscription = stream.listen ((entity) {
155- if (entity is Directory ) return ;
156- if (_files.contains (path)) return ;
157-
158- _emitEvent (ChangeType .ADD , entity.path);
159- _files.add (entity.path);
160- }, cancelOnError: true );
161- subscription.onDone (() {
162- _listSubscriptions.remove (subscription);
163- });
164- subscription.onError (_emitError);
165- _listSubscriptions.add (subscription);
166- } else if (event is FileSystemModifyEvent ) {
167- assert (! event.isDirectory);
168- _emitEvent (ChangeType .MODIFY , path);
169- } else {
170- assert (event is FileSystemDeleteEvent );
171- for (var removedPath in _files.remove (path)) {
172- _emitEvent (ChangeType .REMOVE , removedPath);
173- }
147+
148+ case EventType .createDirectory:
149+ if (_files.containsDir (path)) continue ;
150+
151+ var stream = Directory (path)
152+ .list (recursive: true )
153+ .ignoring <PathNotFoundException >();
154+ var subscription = stream.listen ((entity) {
155+ if (entity is Directory ) return ;
156+ if (_files.contains (path)) return ;
157+
158+ _emitEvent (ChangeType .ADD , entity.path);
159+ _files.add (entity.path);
160+ }, cancelOnError: true );
161+ subscription.onDone (() {
162+ _listSubscriptions.remove (subscription);
163+ });
164+ subscription.onError (_emitError);
165+ _listSubscriptions.add (subscription);
166+
167+ case EventType .modifyFile:
168+ _emitEvent (ChangeType .MODIFY , path);
169+
170+ case EventType .delete:
171+ for (var removedPath in _files.remove (path)) {
172+ _emitEvent (ChangeType .REMOVE , removedPath);
173+ }
174+
175+ case EventType .moveFile:
176+ case EventType .moveDirectory:
177+ case EventType .modifyDirectory:
178+ assert (event.type.isNeverReceivedOnMacOS);
174179 }
175180 }
176181 });
177182 }
178183
179184 /// Sort all the events in a batch into sets based on their path.
180185 ///
181- /// A single input event may result in multiple events in the returned map;
182- /// for example, a MOVE event becomes a DELETE event for the source and a
183- /// CREATE event for the destination.
186+ /// Events for `path` are discarded.
184187 ///
185- /// The returned events won't contain any [FileSystemMoveEvent] s, nor will it
186- /// contain any events relating to [path] .
187- Map <String , Set <FileSystemEvent >> _sortEvents (List <FileSystemEvent > batch) {
188- var eventsForPaths = < String , Set <FileSystemEvent >> {};
188+ /// Events under directories that are created are discarded.
189+ Map <String , Set <Event >> _sortEvents (List <Event > batch) {
190+ var eventsForPaths = < String , Set <Event >> {};
189191
190192 // FSEvents can report past events, including events on the root directory
191193 // such as it being created. We want to ignore these. If the directory is
192194 // really deleted, that's handled by [_onDone].
193195 batch = batch.where ((event) => event.path != path).toList ();
194196
195- // Events within directories that already have events are superfluous; the
196- // directory's full contents will be examined anyway, so we ignore such
197- // events. Emitting them could cause useless or out-of-order events.
198- var directories = unionAll (batch.map ((event) {
199- if (! event.isDirectory) return < String > {};
200- if (event is FileSystemMoveEvent ) {
201- var destination = event.destination;
202- if (destination != null ) {
203- return {event.path, destination};
204- }
205- }
206- return {event.path};
197+ // Events within directories that already have create events are not needed
198+ // as the directory's full content will be listed.
199+ var createdDirectories = unionAll (batch.map ((event) {
200+ return event.type == EventType .createDirectory
201+ ? {event.path}
202+ : const < String > {};
207203 }));
208204
209- bool isInModifiedDirectory (String path) =>
210- directories .any ((dir) => path != dir && p.isWithin (dir, path));
205+ bool isInCreatedDirectory (String path) =>
206+ createdDirectories .any ((dir) => path != dir && p.isWithin (dir, path));
211207
212- void addEvent (String path, FileSystemEvent event) {
213- if (isInModifiedDirectory (path)) return ;
214- eventsForPaths.putIfAbsent (path, () => < FileSystemEvent > {}).add (event);
208+ void addEvent (String path, Event event) {
209+ if (isInCreatedDirectory (path)) return ;
210+ eventsForPaths.putIfAbsent (path, () => < Event > {}).add (event);
215211 }
216212
217213 for (var event in batch) {
218- // The Mac OS watcher doesn't emit move events. See issue 14806.
219- assert (event is ! FileSystemMoveEvent );
220214 addEvent (event.path, event);
221215 }
222216
223217 return eventsForPaths;
224218 }
225219
226- /// Returns the canonical event from a batch of events on the same path, if
227- /// one exists.
228- ///
229- /// If [batch] doesn't contain any contradictory events (e.g. DELETE and
230- /// CREATE, or events with different values for `isDirectory` ), this returns a
231- /// single event that describes what happened to the path in question.
232- ///
233- /// If [batch] does contain contradictory events, this returns `null` to
234- /// indicate that the state of the path on the filesystem should be checked to
235- /// determine what occurred.
236- FileSystemEvent ? _canonicalEvent (Set <FileSystemEvent > batch) {
237- // An empty batch indicates that we've learned earlier that the batch is
238- // contradictory (e.g. because of a move).
220+ /// Returns the canonical event from a batch of events on the same path, or
221+ /// `null` to indicate that the filesystem should be checked.
222+ Event ? _canonicalEvent (Set <Event > batch) {
223+ // If the batch is empty, return `null`.
239224 if (batch.isEmpty) return null ;
240225
241- var type = batch.first.type;
242- var isDir = batch.first.isDirectory;
243- var hadModifyEvent = false ;
244-
245- for (var event in batch.skip (1 )) {
246- // If one event reports that the file is a directory and another event
247- // doesn't, that's a contradiction.
248- if (isDir != event.isDirectory) return null ;
249-
250- // Modify events don't contradict either CREATE or REMOVE events. We can
251- // safely assume the file was modified after a CREATE or before the
252- // REMOVE; otherwise there will also be a REMOVE or CREATE event
253- // (respectively) that will be contradictory.
254- if (event is FileSystemModifyEvent ) {
255- hadModifyEvent = true ;
256- continue ;
257- }
258- assert (event is FileSystemCreateEvent || event is FileSystemDeleteEvent );
259-
260- // If we previously thought this was a MODIFY, we now consider it to be a
261- // CREATE or REMOVE event. This is safe for the same reason as above.
262- if (type == FileSystemEvent .modify) {
263- type = event.type;
264- continue ;
226+ // Resolve the event type for the batch.
227+ var types = batch.map ((e) => e.type).toSet ();
228+ EventType type;
229+ if (types.length == 1 ) {
230+ // There's only one event.
231+ type = types.single;
232+ } else if (types.length == 2 &&
233+ types.contains (EventType .modifyFile) &&
234+ types.contains (EventType .createFile)) {
235+ // Combine events of type [EventType.modifyFile] and
236+ // [EventType.createFile] to one event.
237+ if (_files.contains (batch.first.path)) {
238+ // The file already existed: this can happen due to a create from
239+ // before the watcher started being reported.
240+ type = EventType .modifyFile;
241+ } else {
242+ type = EventType .createFile;
265243 }
266-
267- // A CREATE event contradicts a REMOVE event and vice versa.
268- assert (type == FileSystemEvent .create || type == FileSystemEvent .delete);
269- if (type != event.type) return null ;
244+ } else {
245+ // There are incompatible event types, check the filesystem.
246+ return null ;
270247 }
271248
272- // If we got a CREATE event for a file we already knew about, that comes
273- // from FSEvents reporting an add that happened prior to the watch
274- // beginning. If we also received a MODIFY event, we want to report that,
275- // but not the CREATE.
276- if (type == FileSystemEvent .create &&
277- hadModifyEvent &&
278- _files.contains (batch.first.path)) {
279- type = FileSystemEvent .modify;
249+ // Issue 16003 means that a CREATE event for a directory can indicate
250+ // that the directory was moved and then re-created.
251+ // [_eventsBasedOnFileSystem] will handle this correctly by producing a
252+ // DELETE event followed by a CREATE event if the directory exists.
253+ if (type == EventType .createDirectory) {
254+ return null ;
280255 }
281256
282- switch (type) {
283- case FileSystemEvent .create:
284- // Issue 16003 means that a CREATE event for a directory can indicate
285- // that the directory was moved and then re-created.
286- // [_eventsBasedOnFileSystem] will handle this correctly by producing a
287- // DELETE event followed by a CREATE event if the directory exists.
288- if (isDir) return null ;
289- return FileSystemCreateEvent (batch.first.path, false );
290- case FileSystemEvent .delete:
291- return FileSystemDeleteEvent (batch.first.path, isDir);
292- case FileSystemEvent .modify:
293- return FileSystemModifyEvent (batch.first.path, isDir, false );
294- default :
295- throw StateError ('unreachable' );
296- }
257+ return batch.firstWhere ((e) => e.type == type);
297258 }
298259
299260 /// Returns one or more events that describe the change between the last known
@@ -303,35 +264,35 @@ class _MacOSDirectoryWatcher
303264 /// to the user, unlike the batched events from [Directory.watch] . The
304265 /// returned list may be empty, indicating that no changes occurred to [path]
305266 /// (probably indicating that it was created and then immediately deleted).
306- List <FileSystemEvent > _eventsBasedOnFileSystem (String path) {
267+ List <Event > _eventsBasedOnFileSystem (String path) {
307268 var fileExisted = _files.contains (path);
308269 var dirExisted = _files.containsDir (path);
309270 var fileExists = File (path).existsSync ();
310271 var dirExists = Directory (path).existsSync ();
311272
312- var events = < FileSystemEvent > [];
273+ var events = < Event > [];
313274 if (fileExisted) {
314275 if (fileExists) {
315- events.add (FileSystemModifyEvent (path, false , false ));
276+ events.add (Event . modifyFile (path));
316277 } else {
317- events.add (FileSystemDeleteEvent (path, false ));
278+ events.add (Event . delete (path));
318279 }
319280 } else if (dirExisted) {
320281 if (dirExists) {
321282 // If we got contradictory events for a directory that used to exist and
322283 // still exists, we need to rescan the whole thing in case it was
323284 // replaced with a different directory.
324- events.add (FileSystemDeleteEvent (path, true ));
325- events.add (FileSystemCreateEvent (path, true ));
285+ events.add (Event . delete (path));
286+ events.add (Event . createDirectory (path));
326287 } else {
327- events.add (FileSystemDeleteEvent (path, true ));
288+ events.add (Event . delete (path));
328289 }
329290 }
330291
331292 if (! fileExisted && fileExists) {
332- events.add (FileSystemCreateEvent (path, false ));
293+ events.add (Event . createFile (path));
333294 } else if (! dirExisted && dirExists) {
334- events.add (FileSystemCreateEvent (path, true ));
295+ events.add (Event . createDirectory (path));
335296 }
336297
337298 return events;
@@ -362,7 +323,8 @@ class _MacOSDirectoryWatcher
362323 /// Start or restart the underlying [Directory.watch] stream.
363324 void _startWatch () {
364325 // Batch the FSEvent changes together so that we can dedup events.
365- var innerStream = Directory (path).watch (recursive: true ).batchEvents ();
326+ var innerStream =
327+ Directory (path).watch (recursive: true ).batchAndConvertEvents ();
366328 _watchSubscription = innerStream.listen (_onBatch,
367329 onError: _eventsController.addError, onDone: _onDone);
368330 }
0 commit comments