diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index fa92f0e32..05d16b483 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -39,6 +39,8 @@ various situations involving subdirectory moves. - Bug fix: with `DirectoryWatcher` on MacOS, fix events for changes in new directories: don't emit duplicate ADD, don't emit MODIFY without ADD. +- Bug fix: with `DirectoryWatcher` on MacOS, fix handling of repeated deletes + of a directory with the same name in a short space of time. - Bug fix: with `FileWatcher` on MacOS, a modify event was sometimes reported if the file was created immediately before the watcher was created. Now, if the file exists when the watcher is created then this modify event is not sent. diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index 93289972c..8b5d0d920 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -195,18 +195,23 @@ class _MacOSDirectoryWatcher batch = batch.where((event) => event.path != path).toList(); // Events within directories that already have create events are not needed - // as the directory's full content will be listed. - var createdDirectories = unionAll(batch.map((event) { - return event.type == EventType.createDirectory + // as the directory's full content will be listed. And, events that are + // under direcory deletes are not needed as all files are removed. + var ignoredPaths = unionAll(batch.map((event) { + return event.type == EventType.createDirectory || + // Events don't distinguish file deletes from directory deletes, + // but that doesn't matter here as deleted files will not match + // as the parent directory of any file. + event.type == EventType.delete ? {event.path} : const {}; })); - bool isInCreatedDirectory(String path) => - createdDirectories.any((dir) => path != dir && p.isWithin(dir, path)); + bool isUnderDeleteOrDirectoryCreate(String path) => + ignoredPaths.any((dir) => path != dir && p.isWithin(dir, path)); void addEvent(String path, Event event) { - if (isInCreatedDirectory(path)) return; + if (isUnderDeleteOrDirectoryCreate(path)) return; eventsForPaths.putIfAbsent(path, () => {}).add(event); } diff --git a/pkgs/watcher/test/directory_watcher/file_tests.dart b/pkgs/watcher/test/directory_watcher/file_tests.dart index 827ac147b..2441cad2a 100644 --- a/pkgs/watcher/test/directory_watcher/file_tests.dart +++ b/pkgs/watcher/test/directory_watcher/file_tests.dart @@ -471,6 +471,25 @@ void _fileTests({required bool isNative}) { await expectNoEvents(); }); + test('multiple deletes order is respected', () async { + createDir('watched'); + writeFile('a/1'); + writeFile('b/1'); + + await startWatcher(path: 'watched'); + + renameDir('a', 'watched/x'); + renameDir('watched/x', 'a'); + renameDir('b', 'watched/x'); + writeFile('watched/x/1', contents: 'updated'); + // This is a "duplicate" delete of x, but it's not the same delete and the + // watcher needs to notice that it happens after the update to x/1 so + // there is no file left behind. + renameDir('watched/x', 'b'); + + await expectNoEvents(); + }); + test('subdirectory watching is robust against races', () async { // Make sandboxPath accessible to child isolates created by Isolate.run. final sandboxPath = d.sandbox;