@@ -8,6 +8,7 @@ import 'dart:io';
8
8
import 'package:path/path.dart' as p;
9
9
10
10
import '../directory_watcher.dart' ;
11
+ import '../event.dart' ;
11
12
import '../path_set.dart' ;
12
13
import '../resubscribable.dart' ;
13
14
import '../utils.dart' ;
@@ -63,7 +64,7 @@ class _MacOSDirectoryWatcher
63
64
///
64
65
/// This is separate from [_listSubscriptions] because this stream
65
66
/// occasionally needs to be resubscribed in order to work around issue 14849.
66
- StreamSubscription <List <FileSystemEvent >>? _watchSubscription;
67
+ StreamSubscription <List <Event >>? _watchSubscription;
67
68
68
69
/// The subscription to the [Directory.list] call for the initial listing of
69
70
/// the directory to determine its initial state.
@@ -109,7 +110,7 @@ class _MacOSDirectoryWatcher
109
110
}
110
111
111
112
/// 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) {
113
114
// If we get a batch of events before we're ready to begin emitting events,
114
115
// it's probable that it's a batch of pre-watcher events (see issue 14373).
115
116
// Ignore those events and re-list the directory.
@@ -132,8 +133,8 @@ class _MacOSDirectoryWatcher
132
133
: [canonicalEvent];
133
134
134
135
for (var event in events) {
135
- if (event is FileSystemCreateEvent ) {
136
- if ( ! event.isDirectory) {
136
+ switch (event.type ) {
137
+ case EventType .createFile :
137
138
// If we already know about the file, treat it like a modification.
138
139
// This can happen if a file is copied on top of an existing one.
139
140
// We'll see an ADD event for the latter file when from the user's
@@ -143,157 +144,117 @@ class _MacOSDirectoryWatcher
143
144
144
145
_emitEvent (type, path);
145
146
_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);
174
179
}
175
180
}
176
181
});
177
182
}
178
183
179
184
/// Sort all the events in a batch into sets based on their path.
180
185
///
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.
184
187
///
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 >> {};
189
191
190
192
// FSEvents can report past events, including events on the root directory
191
193
// such as it being created. We want to ignore these. If the directory is
192
194
// really deleted, that's handled by [_onDone].
193
195
batch = batch.where ((event) => event.path != path).toList ();
194
196
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 > {};
207
203
}));
208
204
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));
211
207
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);
215
211
}
216
212
217
213
for (var event in batch) {
218
- // The Mac OS watcher doesn't emit move events. See issue 14806.
219
- assert (event is ! FileSystemMoveEvent );
220
214
addEvent (event.path, event);
221
215
}
222
216
223
217
return eventsForPaths;
224
218
}
225
219
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`.
239
224
if (batch.isEmpty) return null ;
240
225
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;
265
243
}
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 ;
270
247
}
271
248
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 ;
280
255
}
281
256
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);
297
258
}
298
259
299
260
/// Returns one or more events that describe the change between the last known
@@ -303,35 +264,35 @@ class _MacOSDirectoryWatcher
303
264
/// to the user, unlike the batched events from [Directory.watch] . The
304
265
/// returned list may be empty, indicating that no changes occurred to [path]
305
266
/// (probably indicating that it was created and then immediately deleted).
306
- List <FileSystemEvent > _eventsBasedOnFileSystem (String path) {
267
+ List <Event > _eventsBasedOnFileSystem (String path) {
307
268
var fileExisted = _files.contains (path);
308
269
var dirExisted = _files.containsDir (path);
309
270
var fileExists = File (path).existsSync ();
310
271
var dirExists = Directory (path).existsSync ();
311
272
312
- var events = < FileSystemEvent > [];
273
+ var events = < Event > [];
313
274
if (fileExisted) {
314
275
if (fileExists) {
315
- events.add (FileSystemModifyEvent (path, false , false ));
276
+ events.add (Event . modifyFile (path));
316
277
} else {
317
- events.add (FileSystemDeleteEvent (path, false ));
278
+ events.add (Event . delete (path));
318
279
}
319
280
} else if (dirExisted) {
320
281
if (dirExists) {
321
282
// If we got contradictory events for a directory that used to exist and
322
283
// still exists, we need to rescan the whole thing in case it was
323
284
// 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));
326
287
} else {
327
- events.add (FileSystemDeleteEvent (path, true ));
288
+ events.add (Event . delete (path));
328
289
}
329
290
}
330
291
331
292
if (! fileExisted && fileExists) {
332
- events.add (FileSystemCreateEvent (path, false ));
293
+ events.add (Event . createFile (path));
333
294
} else if (! dirExisted && dirExists) {
334
- events.add (FileSystemCreateEvent (path, true ));
295
+ events.add (Event . createDirectory (path));
335
296
}
336
297
337
298
return events;
@@ -362,7 +323,8 @@ class _MacOSDirectoryWatcher
362
323
/// Start or restart the underlying [Directory.watch] stream.
363
324
void _startWatch () {
364
325
// 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 ();
366
328
_watchSubscription = innerStream.listen (_onBatch,
367
329
onError: _eventsController.addError, onDone: _onDone);
368
330
}
0 commit comments