LCOV - code coverage report
Current view: top level - async-1.13.3/lib/src - stream_group.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 23 54 42.6 %
Date: 2017-10-10 20:17:03 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 [new 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          10 :   Stream<T> get stream => _controller.stream;
      32             :   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 = new Map<Stream<T>, StreamSubscription<T>>();
      51             : 
      52             :   /// Merges the events from [streams] into a single (single-subscriber) stream.
      53             :   ///
      54             :   /// This is equivalent to adding [streams] to a group, closing that group, and
      55             :   /// returning its stream.
      56             :   static Stream<T> merge<T>(Iterable<Stream<T>> streams) {
      57           0 :     var group = new StreamGroup<T>();
      58           0 :     streams.forEach(group.add);
      59           0 :     group.close();
      60           0 :     return group.stream;
      61             :   }
      62             : 
      63             :   /// Creates a new stream group where [stream] is single-subscriber.
      64           0 :   StreamGroup() {
      65           0 :     _controller = new StreamController<T>(
      66           0 :         onListen: _onListen,
      67           0 :         onPause: _onPause,
      68           0 :         onResume: _onResume,
      69           0 :         onCancel: _onCancel,
      70             :         sync: true);
      71             :   }
      72             : 
      73             :   /// Creates a new stream group where [stream] is a broadcast stream.
      74           5 :   StreamGroup.broadcast() {
      75          10 :     _controller = new StreamController<T>.broadcast(
      76          10 :         onListen: _onListen, onCancel: _onCancelBroadcast, sync: true);
      77             :   }
      78             : 
      79             :   /// Adds [stream] as a member of this group.
      80             :   ///
      81             :   /// Any events from [stream] will be emitted through [this.stream]. If this
      82             :   /// group has a listener, [stream] will be listened to immediately; otherwise
      83             :   /// it will only be listened to once this group gets a listener.
      84             :   ///
      85             :   /// If this is a single-subscription group and its subscription has been
      86             :   /// canceled, [stream] will be canceled as soon as its added. If this returns
      87             :   /// a [Future], it will be returned from [add]. Otherwise, [add] returns
      88             :   /// `null`.
      89             :   ///
      90             :   /// Throws a [StateError] if this group is closed.
      91             :   Future add(Stream<T> stream) {
      92           5 :     if (_closed) {
      93           0 :       throw new StateError("Can't add a Stream to a closed StreamGroup.");
      94             :     }
      95             : 
      96          10 :     if (_state == _StreamGroupState.dormant) {
      97           0 :       _subscriptions.putIfAbsent(stream, () => null);
      98          10 :     } else if (_state == _StreamGroupState.canceled) {
      99             :       // Listen to the stream and cancel it immediately so that no one else can
     100             :       // listen, for consistency. If the stream has an onCancel listener this
     101             :       // will also fire that, which may help it clean up resources.
     102           0 :       return stream.listen(null).cancel();
     103             :     } else {
     104          15 :       _subscriptions.putIfAbsent(stream, () => _listenToStream(stream));
     105             :     }
     106             : 
     107             :     return null;
     108             :   }
     109             : 
     110             :   /// Removes [stream] as a member of this group.
     111             :   ///
     112             :   /// No further events from [stream] will be emitted through this group. If
     113             :   /// [stream] has been listened to, its subscription will be canceled.
     114             :   ///
     115             :   /// If [stream] has been listened to, this *synchronously* cancels its
     116             :   /// subscription. This means that any events from [stream] that haven't yet
     117             :   /// been emitted through this group will not be.
     118             :   ///
     119             :   /// If [stream]'s subscription is canceled, this returns
     120             :   /// [StreamSubscription.cancel]'s return value. Otherwise, it returns `null`.
     121             :   Future remove(Stream<T> stream) {
     122          10 :     var subscription = _subscriptions.remove(stream);
     123           5 :     var future = subscription == null ? null : subscription.cancel();
     124           5 :     if (_closed && _subscriptions.isEmpty) _controller.close();
     125             :     return future;
     126             :   }
     127             : 
     128             :   /// A callback called when [stream] is listened to.
     129             :   ///
     130             :   /// This is called for both single-subscription and broadcast groups.
     131             :   void _onListen() {
     132           5 :     _state = _StreamGroupState.listening;
     133          10 :     _subscriptions.forEach((stream, subscription) {
     134             :       // If this is a broadcast group and this isn't the first time it's been
     135             :       // listened to, there may still be some subscriptions to
     136             :       // single-subscription streams.
     137             :       if (subscription != null) return;
     138           0 :       _subscriptions[stream] = _listenToStream(stream);
     139             :     });
     140             :   }
     141             : 
     142             :   /// A callback called when [stream] is paused.
     143             :   void _onPause() {
     144           0 :     _state = _StreamGroupState.paused;
     145           0 :     for (var subscription in _subscriptions.values) {
     146           0 :       subscription.pause();
     147             :     }
     148             :   }
     149             : 
     150             :   /// A callback called when [stream] is resumed.
     151             :   void _onResume() {
     152           0 :     _state = _StreamGroupState.listening;
     153           0 :     for (var subscription in _subscriptions.values) {
     154           0 :       subscription.resume();
     155             :     }
     156             :   }
     157             : 
     158             :   /// A callback called when [stream] is canceled.
     159             :   ///
     160             :   /// This is only called for single-subscription groups.
     161             :   Future _onCancel() {
     162           0 :     _state = _StreamGroupState.canceled;
     163             : 
     164           0 :     var futures = _subscriptions.values
     165           0 :         .map((subscription) => subscription.cancel())
     166           0 :         .where((future) => future != null)
     167           0 :         .toList();
     168             : 
     169           0 :     _subscriptions.clear();
     170           0 :     return futures.isEmpty ? null : Future.wait(futures);
     171             :   }
     172             : 
     173             :   /// A callback called when [stream]'s last listener is canceled.
     174             :   ///
     175             :   /// This is only called for broadcast groups.
     176             :   void _onCancelBroadcast() {
     177           5 :     _state = _StreamGroupState.dormant;
     178             : 
     179          10 :     _subscriptions.forEach((stream, subscription) {
     180             :       // Cancel the broadcast streams, since we can re-listen to those later,
     181             :       // but allow the single-subscription streams to keep firing. Their events
     182             :       // will still be added to [_controller], but then they'll be dropped since
     183             :       // it has no listeners.
     184           0 :       if (!stream.isBroadcast) return;
     185           0 :       subscription.cancel();
     186           0 :       _subscriptions[stream] = null;
     187             :     });
     188             :   }
     189             : 
     190             :   /// Starts actively forwarding events from [stream] to [_controller].
     191             :   ///
     192             :   /// This will pause the resulting subscription if [this] is paused.
     193             :   StreamSubscription<T> _listenToStream(Stream<T> stream) {
     194          15 :     var subscription = stream.listen(_controller.add,
     195          15 :         onError: _controller.addError, onDone: () => remove(stream));
     196          10 :     if (_state == _StreamGroupState.paused) subscription.pause();
     197             :     return subscription;
     198             :   }
     199             : 
     200             :   /// Closes the group, indicating that no more streams will be added.
     201             :   ///
     202             :   /// If there are no streams in the group, [stream] is closed immediately.
     203             :   /// Otherwise, [stream] will close once all streams in the group close.
     204             :   ///
     205             :   /// Returns a [Future] that completes once [stream] has actually been closed.
     206             :   Future close() {
     207           5 :     if (_closed) return _controller.done;
     208             : 
     209           5 :     _closed = true;
     210          20 :     if (_subscriptions.isEmpty) _controller.close();
     211             : 
     212          10 :     return _controller.done;
     213             :   }
     214             : }
     215             : 
     216             : /// An enum of possible states of a [StreamGroup].
     217             : class _StreamGroupState {
     218             :   /// The group has no listeners.
     219             :   ///
     220             :   /// New streams added to the group will be listened once the group has a
     221             :   /// listener.
     222             :   static const dormant = const _StreamGroupState("dormant");
     223             : 
     224             :   /// The group has one or more listeners and is actively firing events.
     225             :   ///
     226             :   /// New streams added to the group will be immediately listeners.
     227             :   static const listening = const _StreamGroupState("listening");
     228             : 
     229             :   /// The group is paused and no more events will be fired until it resumes.
     230             :   ///
     231             :   /// New streams added to the group will be listened to, but then paused. They
     232             :   /// will be resumed once the group itself is resumed.
     233             :   ///
     234             :   /// This state is only used by single-subscriber groups.
     235             :   static const paused = const _StreamGroupState("paused");
     236             : 
     237             :   /// The group is canceled and no more events will be fired ever.
     238             :   ///
     239             :   /// New streams added to the group will be listened to, canceled, and
     240             :   /// discarded.
     241             :   ///
     242             :   /// This state is only used by single-subscriber groups.
     243             :   static const canceled = const _StreamGroupState("canceled");
     244             : 
     245             :   /// The name of the state.
     246             :   ///
     247             :   /// Used for debugging.
     248             :   final String name;
     249             : 
     250           5 :   const _StreamGroupState(this.name);
     251             : 
     252           0 :   String toString() => name;
     253             : }

Generated by: LCOV version 1.13