@@ -10,6 +10,7 @@ import 'dart:io';
10
10
import 'package:path/path.dart' as p;
11
11
12
12
import '../directory_watcher.dart' ;
13
+ import '../event.dart' ;
13
14
import '../path_set.dart' ;
14
15
import '../resubscribable.dart' ;
15
16
import '../utils.dart' ;
@@ -26,11 +27,13 @@ class WindowsDirectoryWatcher extends ResubscribableWatcher
26
27
27
28
class _EventBatcher {
28
29
static const Duration _batchDelay = Duration (milliseconds: 100 );
29
- final List <FileSystemEvent > events = [];
30
+ final List <Event > events = [];
30
31
Timer ? timer;
31
32
32
33
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);
34
37
timer? .cancel ();
35
38
timer = Timer (_batchDelay, callback);
36
39
}
@@ -173,172 +176,136 @@ class _WindowsDirectoryWatcher
173
176
}
174
177
175
178
/// 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) {
177
180
_sortEvents (batch).forEach ((path, eventSet) {
178
181
var canonicalEvent = _canonicalEvent (eventSet);
179
182
var events = canonicalEvent == null
180
183
? _eventsBasedOnFileSystem (path)
181
184
: [canonicalEvent];
182
185
183
186
for (var event in events) {
184
- if (event is FileSystemCreateEvent ) {
185
- if ( ! event.isDirectory) {
187
+ switch (event.type ) {
188
+ case EventType .createFile :
186
189
if (_files.contains (path)) continue ;
187
-
188
190
_emitEvent (ChangeType .ADD , path);
189
191
_files.add (path);
190
- continue ;
191
- }
192
192
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:
220
221
_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);
227
237
}
228
238
}
229
239
});
230
240
}
231
241
232
242
/// Sort all the events in a batch into sets based on their path.
233
243
///
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.
246
250
var directories = unionAll (
247
251
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};
254
256
}
255
- return {event.path };
257
+ return const < String > { };
256
258
}),
257
259
);
258
260
259
261
bool isInModifiedDirectory (String path) =>
260
262
directories.any ((dir) => path != dir && p.isWithin (dir, path));
261
263
262
- void addEvent (String path, FileSystemEvent event) {
264
+ void addEvent (String path, Event event) {
263
265
if (isInModifiedDirectory (path)) return ;
264
- eventsForPaths.putIfAbsent (path, () => < FileSystemEvent > {}).add (event);
266
+ eventsForPaths.putIfAbsent (path, () => < Event > {}).add (event);
265
267
}
266
268
267
269
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
- }
274
270
addEvent (event.path, event);
271
+ final destination = event.destination;
272
+ if (destination != null ) {
273
+ addEvent (destination, event);
274
+ }
275
275
}
276
276
277
277
return eventsForPaths;
278
278
}
279
279
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`.
293
284
if (batch.isEmpty) return null ;
294
285
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 ;
328
301
}
329
302
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 ;
341
306
}
307
+
308
+ return batch.firstWhere ((e) => e.type == type);
342
309
}
343
310
344
311
/// Returns zero or more events that describe the change between the last
@@ -348,7 +315,7 @@ class _WindowsDirectoryWatcher
348
315
/// to the user, unlike the batched events from [Directory.watch] . The
349
316
/// returned list may be empty, indicating that no changes occurred to [path]
350
317
/// (probably indicating that it was created and then immediately deleted).
351
- List <FileSystemEvent > _eventsBasedOnFileSystem (String path) {
318
+ List <Event > _eventsBasedOnFileSystem (String path) {
352
319
var fileExisted = _files.contains (path);
353
320
var dirExisted = _files.containsDir (path);
354
321
@@ -358,32 +325,32 @@ class _WindowsDirectoryWatcher
358
325
fileExists = File (path).existsSync ();
359
326
dirExists = Directory (path).existsSync ();
360
327
} on FileSystemException {
361
- return const < FileSystemEvent > [];
328
+ return const < Event > [];
362
329
}
363
330
364
- var events = < FileSystemEvent > [];
331
+ var events = < Event > [];
365
332
if (fileExisted) {
366
333
if (fileExists) {
367
- events.add (FileSystemModifyEvent (path, false , false ));
334
+ events.add (Event . modifyFile (path));
368
335
} else {
369
- events.add (FileSystemDeleteEvent (path, false ));
336
+ events.add (Event . delete (path));
370
337
}
371
338
} else if (dirExisted) {
372
339
if (dirExists) {
373
340
// If we got contradictory events for a directory that used to exist and
374
341
// still exists, we need to rescan the whole thing in case it was
375
342
// 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));
378
345
} else {
379
- events.add (FileSystemDeleteEvent (path, true ));
346
+ events.add (Event . delete (path));
380
347
}
381
348
}
382
349
383
350
if (! fileExisted && fileExists) {
384
- events.add (FileSystemCreateEvent (path, false ));
351
+ events.add (Event . createFile (path));
385
352
} else if (! dirExisted && dirExists) {
386
- events.add (FileSystemCreateEvent (path, true ));
353
+ events.add (Event . createDirectory (path));
387
354
}
388
355
389
356
return events;
0 commit comments