// 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 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; } }