Skip to content

Commit 0093e22

Browse files
committed
Add package-private extension type Event on FileSystemEvent.
1 parent d67ab97 commit 0093e22

File tree

5 files changed

+143
-30
lines changed

5 files changed

+143
-30
lines changed

.github/workflows/watcher.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
fail-fast: false
5555
matrix:
5656
os: [ubuntu-latest, macos-latest, windows-latest]
57-
sdk: [3.1, dev]
57+
sdk: [3.3, dev]
5858
steps:
5959
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
6060
- uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c

pkgs/watcher/lib/src/directory_watcher/linux.dart

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:io';
88
import 'package:async/async.dart';
99

1010
import '../directory_watcher.dart';
11+
import '../event.dart';
1112
import '../path_set.dart';
1213
import '../resubscribable.dart';
1314
import '../utils.dart';
@@ -81,7 +82,10 @@ class _LinuxDirectoryWatcher
8182
})));
8283

8384
// Batch the inotify changes together so that we can dedup events.
84-
var innerStream = _nativeEvents.stream.batchEvents();
85+
var innerStream = _nativeEvents.stream
86+
.map(Event.new)
87+
.where((e) => e.isValid)
88+
.batchEvents();
8589
_listen(innerStream, _onBatch,
8690
onError: (Object error, StackTrace stackTrace) {
8791
// Guarantee that ready always completes.
@@ -145,7 +149,7 @@ class _LinuxDirectoryWatcher
145149
}
146150

147151
/// The callback that's run when a batch of changes comes in.
148-
void _onBatch(List<FileSystemEvent> batch) {
152+
void _onBatch(List<Event> batch) {
149153
var files = <String>{};
150154
var dirs = <String>{};
151155
var changed = <String>{};
@@ -162,30 +166,33 @@ class _LinuxDirectoryWatcher
162166

163167
changed.add(event.path);
164168

165-
if (event is FileSystemMoveEvent) {
166-
files.remove(event.path);
167-
dirs.remove(event.path);
168-
169-
var destination = event.destination;
170-
if (destination == null) continue;
169+
switch (event.type) {
170+
case EventType.moveFile:
171+
files.remove(event.path);
172+
var destination = event.destination!;
173+
changed.add(destination);
174+
files.add(destination);
175+
dirs.remove(destination);
171176

172-
changed.add(destination);
173-
if (event.isDirectory) {
177+
case EventType.moveDirectory:
178+
dirs.remove(event.path);
179+
var destination = event.destination!;
174180
files.remove(destination);
175181
dirs.add(destination);
176-
} else {
177-
files.add(destination);
178-
dirs.remove(destination);
179-
}
180-
} else if (event is FileSystemDeleteEvent) {
181-
files.remove(event.path);
182-
dirs.remove(event.path);
183-
} else if (event.isDirectory) {
184-
files.remove(event.path);
185-
dirs.add(event.path);
186-
} else {
187-
files.add(event.path);
188-
dirs.remove(event.path);
182+
183+
case EventType.delete:
184+
files.remove(event.path);
185+
dirs.remove(event.path);
186+
187+
case EventType.createDirectory:
188+
case EventType.modifyDirectory:
189+
files.remove(event.path);
190+
dirs.add(event.path);
191+
192+
case EventType.createFile:
193+
case EventType.modifyFile:
194+
files.add(event.path);
195+
dirs.remove(event.path);
189196
}
190197
}
191198

pkgs/watcher/lib/src/event.dart

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
/// Extension type replacing [FileSystemEvent] for `package:watcher` internal
8+
/// use.
9+
///
10+
/// The [FileSystemDeleteEvent] subclass of [FileSystemEvent] does something
11+
/// surprising for `isDirectory`: it always returns `false`. The constructor
12+
/// accepts a boolean called `isDirectory` but discards it.
13+
///
14+
/// So, this extension type hides `isDirectory` and instead provides an
15+
/// [EventType] enum with the seven types of event actually used.
16+
extension type Event(FileSystemEvent event) {
17+
/// A create event for a file at [path].
18+
static Event createFile(String path) =>
19+
Event(FileSystemCreateEvent(path, false));
20+
21+
/// A create event for a directory at [path].
22+
static Event createDirectory(String path) =>
23+
Event(FileSystemCreateEvent(path, true));
24+
25+
/// A delete event for [path].
26+
///
27+
/// Delete events do not specify whether they are for files or directories.
28+
static Event delete(String path) => Event(FileSystemDeleteEvent(
29+
path,
30+
// `FileSystemDeleteEvent` just discards `isDirectory`.
31+
false /* isDirectory */));
32+
33+
/// A modify event for the file at [path].
34+
static Event modifyFile(String path) => Event(FileSystemModifyEvent(
35+
path,
36+
false /* isDirectory */,
37+
// Don't set contentChanged, even pass through from the OS, as it's not
38+
// used in `package:watcher`.
39+
false /* contentChanged */));
40+
41+
/// A modify event for the directory at [path].
42+
static Event modifyDirectory(String path) => Event(FileSystemModifyEvent(
43+
path,
44+
true /* isDirectory */,
45+
// `contentChanged` is not used by `package:watcher`, don't set it.
46+
false));
47+
48+
/// See [FileSystemEvent.path].
49+
String get path => event.path;
50+
51+
EventType get type {
52+
switch (event.type) {
53+
case FileSystemEvent.create:
54+
return event.isDirectory
55+
? EventType.createDirectory
56+
: EventType.createFile;
57+
case FileSystemEvent.delete:
58+
return EventType.delete;
59+
case FileSystemEvent.modify:
60+
return event.isDirectory
61+
? EventType.modifyDirectory
62+
: EventType.modifyFile;
63+
case FileSystemEvent.move:
64+
return event.isDirectory ? EventType.moveDirectory : EventType.moveFile;
65+
default:
66+
throw StateError('Invalid event type ${event.type}.');
67+
}
68+
}
69+
70+
/// See [FileSystemMoveEvent.destination].
71+
///
72+
/// Move events with `null` destination are invalid according to [isValid] and
73+
/// should be dropped.
74+
///
75+
/// For other types of event, always `null`.
76+
String? get destination => event.type == FileSystemEvent.move
77+
? (event as FileSystemMoveEvent).destination
78+
: null;
79+
80+
/// Whether the event is valid and can be processed.
81+
///
82+
/// If not, it should be dropped.
83+
bool get isValid {
84+
if (type == EventType.moveDirectory || type == EventType.moveFile) {
85+
if (destination == null) {
86+
return false;
87+
}
88+
}
89+
return true;
90+
}
91+
}
92+
93+
/// See [FileSystemEvent.type].
94+
///
95+
/// This additionally encodes [FileSystemEvent.isDirectory], which is specified
96+
/// for all event types except deletes.
97+
enum EventType {
98+
delete,
99+
createFile,
100+
createDirectory,
101+
modifyFile,
102+
modifyDirectory,
103+
moveFile,
104+
moveDirectory;
105+
}

pkgs/watcher/lib/src/file_watcher/native.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:async';
66
import 'dart:io';
77

8+
import '../event.dart';
89
import '../file_watcher.dart';
910
import '../resubscribable.dart';
1011
import '../utils.dart';
@@ -33,7 +34,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher {
3334
Future<void> get ready => _readyCompleter.future;
3435
final _readyCompleter = Completer<void>();
3536

36-
StreamSubscription<List<FileSystemEvent>>? _subscription;
37+
StreamSubscription<List<Event>>? _subscription;
3738

3839
/// On MacOS only, whether the file existed on startup.
3940
bool? _existedAtStartup;
@@ -50,7 +51,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher {
5051
var file = File(path);
5152

5253
// Batch the events together so that we can dedupe them.
53-
var stream = file.watch().batchEvents();
54+
var stream = file.watch().map(Event.new).batchEvents();
5455

5556
if (Platform.isMacOS) {
5657
var existedAtStartupFuture = file.exists();
@@ -65,8 +66,8 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher {
6566
onError: _eventsController.addError, onDone: _onDone);
6667
}
6768

68-
void _onBatch(List<FileSystemEvent> batch) {
69-
if (batch.any((event) => event.type == FileSystemEvent.delete)) {
69+
void _onBatch(List<Event> batch) {
70+
if (batch.any((event) => event.type == EventType.delete)) {
7071
// If the file is deleted, the underlying stream will close. We handle
7172
// emitting our own REMOVE event in [_onDone].
7273
return;
@@ -77,7 +78,7 @@ class _NativeFileWatcher implements FileWatcher, ManuallyClosedWatcher {
7778
// created just before the `watch`. If the file existed at startup then it
7879
// should be ignored.
7980
if (_existedAtStartup! &&
80-
batch.every((event) => event.type == FileSystemEvent.create)) {
81+
batch.every((event) => event.type == EventType.createFile)) {
8182
return;
8283
}
8384
}

pkgs/watcher/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ repository: https://github.com/dart-lang/tools/tree/main/pkgs/watcher
77
issue_tracker: https://github.com/dart-lang/tools/labels/package%3Awatcher
88

99
environment:
10-
sdk: ^3.1.0
10+
sdk: ^3.3.0
1111

1212
dependencies:
1313
async: ^2.5.0

0 commit comments

Comments
 (0)