LCOV - code coverage report
Current view: top level - stream_channel-2.1.0/lib/src - guarantee_channel.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 27 70 38.6 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright (c) 2016, 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             : import 'package:async/async.dart';
       8             : 
       9             : import '../stream_channel.dart';
      10             : 
      11             : /// A [StreamChannel] that enforces the stream channel guarantees.
      12             : ///
      13             : /// This is exposed via [new StreamChannel.withGuarantees].
      14             : class GuaranteeChannel<T> extends StreamChannelMixin<T> {
      15          11 :   @override
      16          22 :   Stream<T> get stream => _streamController.stream;
      17             : 
      18          11 :   @override
      19          11 :   StreamSink<T> get sink => _sink;
      20             :   late final _GuaranteeSink<T> _sink;
      21             : 
      22             :   /// The controller for [stream].
      23             :   ///
      24             :   /// This intermediate controller allows us to continue listening for a done
      25             :   /// event even after the user has canceled their subscription, and to send our
      26             :   /// own done event when the sink is closed.
      27             :   late final StreamController<T> _streamController;
      28             : 
      29             :   /// The subscription to the inner stream.
      30             :   StreamSubscription<T>? _subscription;
      31             : 
      32             :   /// Whether the sink has closed, causing the underlying channel to disconnect.
      33             :   bool _disconnected = false;
      34             : 
      35          11 :   GuaranteeChannel(Stream<T> innerStream, StreamSink<T> innerSink,
      36             :       {bool allowSinkErrors = true}) {
      37          22 :     _sink = _GuaranteeSink<T>(innerSink, this, allowErrors: allowSinkErrors);
      38             : 
      39             :     // Enforce the single-subscription guarantee by changing a broadcast stream
      40             :     // to single-subscription.
      41          11 :     if (innerStream.isBroadcast) {
      42             :       innerStream =
      43           0 :           innerStream.transform(SingleSubscriptionTransformer<T, T>());
      44             :     }
      45             : 
      46          22 :     _streamController = StreamController<T>(
      47          11 :         onListen: () {
      48             :           // If the sink has disconnected, we've already called
      49             :           // [_streamController.close].
      50          11 :           if (_disconnected) return;
      51             : 
      52          44 :           _subscription = innerStream.listen(_streamController.add,
      53          22 :               onError: _streamController.addError, onDone: () {
      54           0 :             _sink._onStreamDisconnected();
      55           0 :             _streamController.close();
      56             :           });
      57             :         },
      58             :         sync: true);
      59             :   }
      60             : 
      61             :   /// Called by [_GuaranteeSink] when the user closes it.
      62             :   ///
      63             :   /// The sink closing indicates that the connection is closed, so the stream
      64             :   /// should stop emitting events.
      65           0 :   void _onSinkDisconnected() {
      66           0 :     _disconnected = true;
      67           0 :     var subscription = _subscription;
      68           0 :     if (subscription != null) subscription.cancel();
      69           0 :     _streamController.close();
      70             :   }
      71             : }
      72             : 
      73             : /// The sink for [GuaranteeChannel].
      74             : ///
      75             : /// This wraps the inner sink to ignore events and cancel any in-progress
      76             : /// [addStream] calls when the underlying channel closes.
      77             : class _GuaranteeSink<T> implements StreamSink<T> {
      78             :   /// The inner sink being wrapped.
      79             :   final StreamSink<T> _inner;
      80             : 
      81             :   /// The [GuaranteeChannel] this belongs to.
      82             :   final GuaranteeChannel<T> _channel;
      83             : 
      84           0 :   @override
      85           0 :   Future<void> get done => _doneCompleter.future;
      86             :   final _doneCompleter = Completer();
      87             : 
      88             :   /// Whether connection is disconnected.
      89             :   ///
      90             :   /// This can happen because the stream has emitted a done event, or because
      91             :   /// the user added an error when [_allowErrors] is `false`.
      92             :   bool _disconnected = false;
      93             : 
      94             :   /// Whether the user has called [close].
      95             :   bool _closed = false;
      96             : 
      97             :   /// The subscription to the stream passed to [addStream], if a stream is
      98             :   /// currently being added.
      99             :   StreamSubscription<T>? _addStreamSubscription;
     100             : 
     101             :   /// The completer for the future returned by [addStream], if a stream is
     102             :   /// currently being added.
     103             :   Completer? _addStreamCompleter;
     104             : 
     105             :   /// Whether we're currently adding a stream with [addStream].
     106          22 :   bool get _inAddStream => _addStreamSubscription != null;
     107             : 
     108             :   /// Whether errors are passed on to the underlying sink.
     109             :   ///
     110             :   /// If this is `false`, any error passed to the sink is piped to [done] and
     111             :   /// the underlying sink is closed.
     112             :   final bool _allowErrors;
     113             : 
     114          11 :   _GuaranteeSink(this._inner, this._channel, {bool allowErrors = true})
     115             :       : _allowErrors = allowErrors;
     116             : 
     117          11 :   @override
     118             :   void add(T data) {
     119          11 :     if (_closed) throw StateError('Cannot add event after closing.');
     120          11 :     if (_inAddStream) {
     121           0 :       throw StateError('Cannot add event while adding stream.');
     122             :     }
     123          11 :     if (_disconnected) return;
     124             : 
     125          22 :     _inner.add(data);
     126             :   }
     127             : 
     128           0 :   @override
     129             :   void addError(error, [StackTrace? stackTrace]) {
     130           0 :     if (_closed) throw StateError('Cannot add event after closing.');
     131           0 :     if (_inAddStream) {
     132           0 :       throw StateError('Cannot add event while adding stream.');
     133             :     }
     134           0 :     if (_disconnected) return;
     135             : 
     136           0 :     _addError(error, stackTrace);
     137             :   }
     138             : 
     139             :   /// Like [addError], but doesn't check to ensure that an error can be added.
     140             :   ///
     141             :   /// This is called from [addStream], so it shouldn't fail if a stream is being
     142             :   /// added.
     143           0 :   void _addError(Object error, [StackTrace? stackTrace]) {
     144           0 :     if (_allowErrors) {
     145           0 :       _inner.addError(error, stackTrace);
     146             :       return;
     147             :     }
     148             : 
     149           0 :     _doneCompleter.completeError(error, stackTrace);
     150             : 
     151             :     // Treat an error like both the stream and sink disconnecting.
     152           0 :     _onStreamDisconnected();
     153           0 :     _channel._onSinkDisconnected();
     154             : 
     155             :     // Ignore errors from the inner sink. We're already surfacing one error, and
     156             :     // if the user handles it we don't want them to have another top-level.
     157           0 :     _inner.close().catchError((_) {});
     158             :   }
     159             : 
     160          11 :   @override
     161             :   Future<void> addStream(Stream<T> stream) {
     162          11 :     if (_closed) throw StateError('Cannot add stream after closing.');
     163          11 :     if (_inAddStream) {
     164           0 :       throw StateError('Cannot add stream while adding stream.');
     165             :     }
     166          11 :     if (_disconnected) return Future.value();
     167             : 
     168          22 :     _addStreamCompleter = Completer.sync();
     169          44 :     _addStreamSubscription = stream.listen(_inner.add,
     170          33 :         onError: _addError, onDone: _addStreamCompleter!.complete);
     171          33 :     return _addStreamCompleter!.future.then((_) {
     172           0 :       _addStreamCompleter = null;
     173           0 :       _addStreamSubscription = null;
     174             :     });
     175             :   }
     176             : 
     177           0 :   @override
     178             :   Future<void> close() {
     179           0 :     if (_inAddStream) {
     180           0 :       throw StateError('Cannot close sink while adding stream.');
     181             :     }
     182             : 
     183           0 :     if (_closed) return done;
     184           0 :     _closed = true;
     185             : 
     186           0 :     if (!_disconnected) {
     187           0 :       _channel._onSinkDisconnected();
     188           0 :       _doneCompleter.complete(_inner.close());
     189             :     }
     190             : 
     191           0 :     return done;
     192             :   }
     193             : 
     194             :   /// Called by [GuaranteeChannel] when the stream emits a done event.
     195             :   ///
     196             :   /// The stream being done indicates that the connection is closed, so the
     197             :   /// sink should stop forwarding events.
     198           0 :   void _onStreamDisconnected() {
     199           0 :     _disconnected = true;
     200           0 :     if (!_doneCompleter.isCompleted) _doneCompleter.complete();
     201             : 
     202           0 :     if (!_inAddStream) return;
     203           0 :     _addStreamCompleter!.complete(_addStreamSubscription!.cancel());
     204           0 :     _addStreamCompleter = null;
     205           0 :     _addStreamSubscription = null;
     206             :   }
     207             : }

Generated by: LCOV version 1.14