LCOV - code coverage report
Current view: top level - async-2.5.0/lib/src - stream_group.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 1 69 1.4 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright (c) 2015, 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:async';
       6             : 
       7             : /// A collection of streams whose events are unified and sent through a central
       8             : /// stream.
       9             : ///
      10             : /// Both errors and data events are forwarded through [stream]. The streams in
      11             : /// the group won't be listened to until [stream] has a listener. **Note that
      12             : /// this means that events emitted by broadcast streams will be dropped until
      13             : /// [stream] has a listener.**
      14             : ///
      15             : /// If the `StreamGroup` is constructed using [new StreamGroup], [stream] will
      16             : /// be single-subscription. In this case, if [stream] is paused or canceled, all
      17             : /// streams in the group will likewise be paused or canceled, respectively.
      18             : ///
      19             : /// If the `StreamGroup` is constructed using [StreamGroup.broadcast],
      20             : /// [stream] will be a broadcast stream. In this case, the streams in the group
      21             : /// will never be paused and single-subscription streams in the group will never
      22             : /// be canceled. **Note that single-subscription streams in a broadcast group
      23             : /// may drop events if a listener is added and later removed.** Broadcast
      24             : /// streams in the group will be canceled once [stream] has no listeners, and
      25             : /// will be listened to again once [stream] has listeners.
      26             : ///
      27             : /// [stream] won't close until [close] is called on the group *and* every stream
      28             : /// in the group closes.
      29             : class StreamGroup<T> implements Sink<Stream<T>> {
      30             :   /// The stream through which all events from streams in the group are emitted.
      31           0 :   Stream<T> get stream => _controller.stream;
      32             :   late StreamController<T> _controller;
      33             : 
      34             :   /// Whether the group is closed, meaning that no more streams may be added.
      35             :   var _closed = false;
      36             : 
      37             :   /// The current state of the group.
      38             :   ///
      39             :   /// See [_StreamGroupState] for detailed descriptions of each state.
      40             :   var _state = _StreamGroupState.dormant;
      41             : 
      42             :   /// Streams that have been added to the group, and their subscriptions if they
      43             :   /// have been subscribed to.
      44             :   ///
      45             :   /// The subscriptions will be null until the group has a listener registered.
      46             :   /// If it's a broadcast group and it goes dormant again, broadcast stream
      47             :   /// subscriptions will be canceled and set to null again. Single-subscriber
      48             :   /// stream subscriptions will be left intact, since they can't be
      49             :   /// re-subscribed.
      50             :   final _subscriptions = <Stream<T>, StreamSubscription<T>?>{};
      51             : 
      52             :   /// Merges the events from [streams] into a single single-subscription stream.
      53             :   ///
      54             :   /// This is equivalent to adding [streams] to a group, closing that group, and
      55             :   /// returning its stream.
      56           0 :   static Stream<T> merge<T>(Iterable<Stream<T>> streams) {
      57           0 :     var group = StreamGroup<T>();
      58           0 :     streams.forEach(group.add);
      59           0 :     group.close();
      60           0 :     return group.stream;
      61             :   }
      62             : 
      63             :   /// Merges the events from [streams] into a single broadcast stream.
      64             :   ///
      65             :   /// This is equivalent to adding [streams] to a broadcast group, closing that
      66             :   /// group, and returning its stream.
      67           0 :   static Stream<T> mergeBroadcast<T>(Iterable<Stream<T>> streams) {
      68           0 :     var group = StreamGroup<T>.broadcast();
      69           0 :     streams.forEach(group.add);
      70           0 :     group.close();
      71           0 :     return group.stream;
      72             :   }
      73             : 
      74             :   /// Creates a new stream group where [stream] is single-subscriber.
      75           0 :   StreamGroup() {
      76           0 :     _controller = StreamController<T>(
      77           0 :         onListen: _onListen,
      78           0 :         onPause: _onPause,
      79           0 :         onResume: _onResume,
      80           0 :         onCancel: _onCancel,
      81             :         sync: true);
      82             :   }
      83             : 
      84             :   /// Creates a new stream group where [stream] is a broadcast stream.
      85           0 :   StreamGroup.broadcast() {
      86           0 :     _controller = StreamController<T>.broadcast(
      87           0 :         onListen: _onListen, onCancel: _onCancelBroadcast, sync: true);
      88             :   }
      89             : 
      90             :   /// Adds [stream] as a member of this group.
      91             :   ///
      92             :   /// Any events from [stream] will be emitted through [this.stream]. If this
      93             :   /// group has a listener, [stream] will be listened to immediately; otherwise
      94             :   /// it will only be listened to once this group gets a listener.
      95             :   ///
      96             :   /// If this is a single-subscription group and its subscription has been
      97             :   /// canceled, [stream] will be canceled as soon as its added. If this returns
      98             :   /// a [Future], it will be returned from [add]. Otherwise, [add] returns
      99             :   /// `null`.
     100             :   ///
     101             :   /// Throws a [StateError] if this group is closed.
     102           0 :   @override
     103             :   Future? add(Stream<T> stream) {
     104           0 :     if (_closed) {
     105           0 :       throw StateError("Can't add a Stream to a closed StreamGroup.");
     106             :     }
     107             : 
     108           0 :     if (_state == _StreamGroupState.dormant) {
     109           0 :       _subscriptions.putIfAbsent(stream, () => null);
     110           0 :     } else if (_state == _StreamGroupState.canceled) {
     111             :       // Listen to the stream and cancel it immediately so that no one else can
     112             :       // listen, for consistency. If the stream has an onCancel listener this
     113             :       // will also fire that, which may help it clean up resources.
     114           0 :       return stream.listen(null).cancel();
     115             :     } else {
     116           0 :       _subscriptions.putIfAbsent(stream, () => _listenToStream(stream));
     117             :     }
     118             : 
     119             :     return null;
     120             :   }
     121             : 
     122             :   /// Removes [stream] as a member of this group.
     123             :   ///
     124             :   /// No further events from [stream] will be emitted through this group. If
     125             :   /// [stream] has been listened to, its subscription will be canceled.
     126             :   ///
     127             :   /// If [stream] has been listened to, this *synchronously* cancels its
     128             :   /// subscription. This means that any events from [stream] that haven't yet
     129             :   /// been emitted through this group will not be.
     130             :   ///
     131             :   /// If [stream]'s subscription is canceled, this returns
     132             :   /// [StreamSubscription.cancel]'s return value. Otherwise, it returns `null`.
     133           0 :   Future? remove(Stream<T> stream) {
     134           0 :     var subscription = _subscriptions.remove(stream);
     135           0 :     var future = subscription == null ? null : subscription.cancel();
     136           0 :     if (_closed && _subscriptions.isEmpty) _controller.close();
     137             :     return future;
     138             :   }
     139             : 
     140             :   /// A callback called when [stream] is listened to.
     141             :   ///
     142             :   /// This is called for both single-subscription and broadcast groups.
     143           0 :   void _onListen() {
     144           0 :     _state = _StreamGroupState.listening;
     145           0 :     _subscriptions.forEach((stream, subscription) {
     146             :       // If this is a broadcast group and this isn't the first time it's been
     147             :       // listened to, there may still be some subscriptions to
     148             :       // single-subscription streams.
     149             :       if (subscription != null) return;
     150           0 :       _subscriptions[stream] = _listenToStream(stream);
     151             :     });
     152             :   }
     153             : 
     154             :   /// A callback called when [stream] is paused.
     155           0 :   void _onPause() {
     156           0 :     _state = _StreamGroupState.paused;
     157           0 :     for (var subscription in _subscriptions.values) {
     158           0 :       subscription!.pause();
     159             :     }
     160             :   }
     161             : 
     162             :   /// A callback called when [stream] is resumed.
     163           0 :   void _onResume() {
     164           0 :     _state = _StreamGroupState.listening;
     165           0 :     for (var subscription in _subscriptions.values) {
     166           0 :       subscription!.resume();
     167             :     }
     168             :   }
     169             : 
     170             :   /// A callback called when [stream] is canceled.
     171             :   ///
     172             :   /// This is only called for single-subscription groups.
     173           0 :   Future? _onCancel() {
     174           0 :     _state = _StreamGroupState.canceled;
     175             : 
     176           0 :     var futures = _subscriptions.values
     177           0 :         .map((subscription) => subscription!.cancel())
     178           0 :         .toList();
     179             : 
     180           0 :     _subscriptions.clear();
     181           0 :     return futures.isEmpty ? null : Future.wait(futures);
     182             :   }
     183             : 
     184             :   /// A callback called when [stream]'s last listener is canceled.
     185             :   ///
     186             :   /// This is only called for broadcast groups.
     187           0 :   void _onCancelBroadcast() {
     188           0 :     _state = _StreamGroupState.dormant;
     189             : 
     190           0 :     _subscriptions.forEach((stream, subscription) {
     191             :       // Cancel the broadcast streams, since we can re-listen to those later,
     192             :       // but allow the single-subscription streams to keep firing. Their events
     193             :       // will still be added to [_controller], but then they'll be dropped since
     194             :       // it has no listeners.
     195           0 :       if (!stream.isBroadcast) return;
     196           0 :       subscription!.cancel();
     197           0 :       _subscriptions[stream] = null;
     198             :     });
     199             :   }
     200             : 
     201             :   /// Starts actively forwarding events from [stream] to [_controller].
     202             :   ///
     203             :   /// This will pause the resulting subscription if [this] is paused.
     204           0 :   StreamSubscription<T> _listenToStream(Stream<T> stream) {
     205           0 :     var subscription = stream.listen(_controller.add,
     206           0 :         onError: _controller.addError, onDone: () => remove(stream));
     207           0 :     if (_state == _StreamGroupState.paused) subscription.pause();
     208             :     return subscription;
     209             :   }
     210             : 
     211             :   /// Closes the group, indicating that no more streams will be added.
     212             :   ///
     213             :   /// If there are no streams in the group, [stream] is closed immediately.
     214             :   /// Otherwise, [stream] will close once all streams in the group close.
     215             :   ///
     216             :   /// Returns a [Future] that completes once [stream] has actually been closed.
     217           0 :   @override
     218             :   Future close() {
     219           0 :     if (_closed) return _controller.done;
     220             : 
     221           0 :     _closed = true;
     222           0 :     if (_subscriptions.isEmpty) _controller.close();
     223             : 
     224           0 :     return _controller.done;
     225             :   }
     226             : }
     227             : 
     228             : /// An enum of possible states of a [StreamGroup].
     229             : class _StreamGroupState {
     230             :   /// The group has no listeners.
     231             :   ///
     232             :   /// New streams added to the group will be listened once the group has a
     233             :   /// listener.
     234             :   static const dormant = _StreamGroupState('dormant');
     235             : 
     236             :   /// The group has one or more listeners and is actively firing events.
     237             :   ///
     238             :   /// New streams added to the group will be immediately listeners.
     239             :   static const listening = _StreamGroupState('listening');
     240             : 
     241             :   /// The group is paused and no more events will be fired until it resumes.
     242             :   ///
     243             :   /// New streams added to the group will be listened to, but then paused. They
     244             :   /// will be resumed once the group itself is resumed.
     245             :   ///
     246             :   /// This state is only used by single-subscriber groups.
     247             :   static const paused = _StreamGroupState('paused');
     248             : 
     249             :   /// The group is canceled and no more events will be fired ever.
     250             :   ///
     251             :   /// New streams added to the group will be listened to, canceled, and
     252             :   /// discarded.
     253             :   ///
     254             :   /// This state is only used by single-subscriber groups.
     255             :   static const canceled = _StreamGroupState('canceled');
     256             : 
     257             :   /// The name of the state.
     258             :   ///
     259             :   /// Used for debugging.
     260             :   final String name;
     261             : 
     262          11 :   const _StreamGroupState(this.name);
     263             : 
     264           0 :   @override
     265           0 :   String toString() => name;
     266             : }

Generated by: LCOV version 1.14