| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:io'; | |
| /// Extension type replacing [FileSystemEvent] for `package:watcher` internal | |
| /// use. | |
| /// | |
| /// The [FileSystemDeleteEvent] subclass of [FileSystemEvent] does something | |
| /// surprising for `isDirectory`: it always returns `false`. The constructor | |
| /// accepts a boolean called `isDirectory` but discards it. | |
| /// | |
| /// So, this extension type hides `isDirectory` and instead provides an | |
| /// [EventType] enum with the seven types of event actually used. | |
| extension type Event._(FileSystemEvent _event) { | |
| /// Converts [event] to an [Event]. | |
| /// | |
| /// Returns `null` and asserts `false` if [event] is unexpected on this | |
| /// platform. So, it will cause tests to fail but real code can continue | |
| /// ignoring the event. | |
| /// | |
| /// Returns `null` if [event] should be ignored on this platform. | |
| static Event? checkAndConvert(FileSystemEvent event) { | |
| var result = Event._(event); | |
| if (result.type.isIgnored) return null; | |
| if (Platform.isMacOS && result.type.isNeverReceivedOnMacOS) { | |
| assert(false); | |
| return null; | |
| } | |
| return result; | |
| } | |
| /// Returns an iterable containing this event, split to a "create" and a | |
| /// "delete" event if it's a move event. | |
| Iterable<Event> splitIfMove() sync* { | |
| if (type != EventType.moveFile && type != EventType.moveDirectory) { | |
| yield this; | |
| return; | |
| } | |
| final destination = this.destination; | |
| yield Event._(FileSystemDeleteEvent(path, type == EventType.moveDirectory)); | |
| if (destination != null) { | |
| yield Event._( | |
| FileSystemCreateEvent(destination, type == EventType.moveDirectory), | |
| ); | |
| } | |
| } | |
| /// A create event for a file at [path]. | |
| static Event createFile(String path) => | |
| Event._(FileSystemCreateEvent(path, false)); | |
| /// A create event for a directory at [path]. | |
| static Event createDirectory(String path) => | |
| Event._(FileSystemCreateEvent(path, true)); | |
| /// A delete event for [path]. | |
| /// | |
| /// Delete events do not specify whether they are for files or directories. | |
| static Event delete(String path) => Event._( | |
| FileSystemDeleteEvent( | |
| path, | |
| // `FileSystemDeleteEvent` just discards `isDirectory`. | |
| false /* isDirectory */, | |
| ), | |
| ); | |
| /// A modify event for the file at [path]. | |
| static Event modifyFile(String path) => Event._( | |
| FileSystemModifyEvent( | |
| path, | |
| false /* isDirectory */, | |
| // Don't set `contentChanged`, even pass through from the OS, as | |
| // `package:watcher` never reads it. | |
| false /* contentChanged */, | |
| ), | |
| ); | |
| /// See [FileSystemEvent.path]. | |
| String get path => _event.path; | |
| EventType get type { | |
| switch (_event.type) { | |
| case FileSystemEvent.create: | |
| return _event.isDirectory | |
| ? EventType.createDirectory | |
| : EventType.createFile; | |
| case FileSystemEvent.delete: | |
| return EventType.delete; | |
| case FileSystemEvent.modify: | |
| return _event.isDirectory | |
| ? EventType.modifyDirectory | |
| : EventType.modifyFile; | |
| case FileSystemEvent.move: | |
| return _event.isDirectory | |
| ? EventType.moveDirectory | |
| : EventType.moveFile; | |
| default: | |
| throw StateError('Invalid event type ${_event.type}.'); | |
| } | |
| } | |
| /// See [FileSystemMoveEvent.destination]. | |
| /// | |
| /// For other types of event, always `null`. | |
| String? get destination => _event.type == FileSystemEvent.move | |
| ? (_event as FileSystemMoveEvent).destination | |
| : null; | |
| } | |
| /// See [FileSystemEvent.type]. | |
| /// | |
| /// This additionally encodes [FileSystemEvent.isDirectory], which is specified | |
| /// for all event types except deletes. | |
| enum EventType { | |
| delete, | |
| createFile, | |
| createDirectory, | |
| modifyFile, | |
| modifyDirectory, | |
| moveFile, | |
| moveDirectory; | |
| bool get isNeverReceivedOnMacOS { | |
| // See https://github.com/dart-lang/sdk/issues/14806. | |
| return this == moveFile || this == moveDirectory; | |
| } | |
| bool get isIgnored { | |
| // On Windows, `modifyDirectory` is always accompanied by either | |
| // `createDirectory` or `deleteDirectory`, so it's not needed. | |
| // | |
| // On Linux, `modifyDirectory` means the directory attributes such as | |
| // permissions changed, so it's not a useful event. | |
| // | |
| // `modifyDirectory` on Mac also relates to directory attributes but is | |
| // harder to repro, using `unzip` works. It's not a useful event. See | |
| // https://github.com/dart-lang/tools/issues/2283. | |
| return this == modifyDirectory; | |
| } | |
| } |