Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkgs/watcher/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 11 additions & 6 deletions pkgs/watcher/lib/src/directory_watcher/mac_os.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 <String>{};
}));

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, () => <Event>{}).add(event);
}

Expand Down
19 changes: 19 additions & 0 deletions pkgs/watcher/test/directory_watcher/file_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading