| // Copyright (c) 2013, 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:async'; |
| |
| import 'package:path/path.dart' as p; |
| |
| import '../watcher.dart'; |
| |
| /// A wrapper for [ManuallyClosedWatcher] that encapsulates support for closing |
| /// the watcher when it has no subscribers and re-opening it when it's |
| /// re-subscribed. |
| /// |
| /// It's simpler to implement watchers without worrying about this behavior. |
| /// This class wraps a watcher class which can be written with the simplifying |
| /// assumption that it can continue emitting events until an explicit `close` |
| /// method is called, at which point it will cease emitting events entirely. The |
| /// [ManuallyClosedWatcher] interface is used for these watchers. |
| /// |
| /// This would be more cleanly implemented as a function that takes a class and |
| /// emits a new class, but Dart doesn't support that sort of thing. Instead it |
| /// takes a factory function that produces instances of the inner class. |
| abstract class ResubscribableWatcher implements Watcher { |
| /// The factory function that produces instances of the inner class. |
| final ManuallyClosedWatcher Function(String path) _factory; |
| |
| @override |
| final String path; |
| |
| @override |
| Stream<WatchEvent> get events => _eventsController.stream; |
| late StreamController<WatchEvent> _eventsController; |
| |
| @override |
| bool get isReady => _readyCompleter.isCompleted; |
| |
| @override |
| Future<void> get ready => _readyCompleter.future; |
| var _readyCompleter = Completer<void>(); |
| |
| /// Creates a new [ResubscribableWatcher] wrapping the watchers |
| /// emitted by [_factory]. |
| ResubscribableWatcher(String path, this._factory) |
| : path = _normalizeAndStripTrailingSeparator(path) { |
| late ManuallyClosedWatcher watcher; |
| late StreamSubscription<WatchEvent> subscription; |
| |
| _eventsController = StreamController<WatchEvent>.broadcast( |
| onListen: () async { |
| final completer = _readyCompleter; |
| watcher = _factory(this.path); |
| subscription = watcher.events.listen( |
| _eventsController.add, |
| onError: _eventsController.addError, |
| onDone: _eventsController.close, |
| ); |
| |
| // It's important that we complete the value of [_readyCompleter] at |
| // the time [onListen] is called, as opposed to the value when |
| // [watcher.ready] fires. A new completer may be created by that time. |
| await watcher.ready; |
| completer.complete(); |
| }, |
| onCancel: () { |
| // Cancel the subscription before closing the watcher so that the |
| // watcher's `onDone` event doesn't close [events]. |
| subscription.cancel(); |
| watcher.close(); |
| _readyCompleter = Completer(); |
| }, |
| sync: true, |
| ); |
| } |
| } |
| |
| /// An interface for watchers with an explicit, manual [close] method. |
| /// |
| /// See [ResubscribableWatcher]. |
| abstract class ManuallyClosedWatcher implements Watcher { |
| /// Closes the watcher. |
| /// |
| /// Subclasses should close their [events] stream and release any internal |
| /// resources. |
| void close(); |
| } |
| |
| /// Normalizes [path] then strips any extra trailing separator. |
| /// |
| /// Removes adjacent separators, `.` and `..` but does not convert to absolute. |
| String _normalizeAndStripTrailingSeparator(String path) { |
| path = p.normalize(path); |
| return path.length > 1 && path.endsWith(p.separator) |
| ? path.substring(0, path.length - 1) |
| : path; |
| } |