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 : /// Allows the caller to force a channel to disconnect. 12 : /// 13 : /// When [disconnect] is called, the channel (or channels) transformed by this 14 : /// transformer will act as though the remote end had disconnected—the stream 15 : /// will emit a done event, and the sink will ignore future inputs. The inner 16 : /// sink will also be closed to notify the remote end of the disconnection. 17 : /// 18 : /// If a channel is transformed after the [disconnect] has been called, it will 19 : /// be disconnected immediately. 20 : class Disconnector<T> implements StreamChannelTransformer<T, T> { 21 : /// Whether [disconnect] has been called. 22 0 : bool get isDisconnected => _disconnectMemo.hasRun; 23 : 24 : /// The sinks for transformed channels. 25 : /// 26 : /// Note that we assume that transformed channels provide the stream channel 27 : /// guarantees. This allows us to only track sinks, because we know closing 28 : /// the underlying sink will cause the stream to emit a done event. 29 : final _sinks = <_DisconnectorSink<T>>[]; 30 : 31 : /// Disconnects all channels that have been transformed. 32 : /// 33 : /// Returns a future that completes when all inner sinks' [StreamSink.close] 34 : /// futures have completed. Note that a [StreamController]'s sink won't close 35 : /// until the corresponding stream has a listener. 36 0 : Future<void> disconnect() => _disconnectMemo.runOnce(() { 37 0 : var futures = _sinks.map((sink) => sink._disconnect()).toList(); 38 0 : _sinks.clear(); 39 0 : return Future.wait(futures, eagerError: true); 40 : }); 41 : final _disconnectMemo = AsyncMemoizer(); 42 : 43 0 : @override 44 : StreamChannel<T> bind(StreamChannel<T> channel) { 45 0 : return channel.changeSink((innerSink) { 46 0 : var sink = _DisconnectorSink<T>(innerSink); 47 : 48 0 : if (isDisconnected) { 49 : // Ignore errors here, because otherwise there would be no way for the 50 : // user to handle them gracefully. 51 0 : sink._disconnect().catchError((_) {}); 52 : } else { 53 0 : _sinks.add(sink); 54 : } 55 : 56 : return sink; 57 : }); 58 : } 59 : } 60 : 61 : /// A sink wrapper that can force a disconnection. 62 : class _DisconnectorSink<T> implements StreamSink<T> { 63 : /// The inner sink. 64 : final StreamSink<T> _inner; 65 : 66 0 : @override 67 0 : Future<void> get done => _inner.done; 68 : 69 : /// Whether [Disconnector.disconnect] has been called. 70 : var _isDisconnected = false; 71 : 72 : /// Whether the user has called [close]. 73 : var _closed = false; 74 : 75 : /// The subscription to the stream passed to [addStream], if a stream is 76 : /// currently being added. 77 : StreamSubscription<T>? _addStreamSubscription; 78 : 79 : /// The completer for the future returned by [addStream], if a stream is 80 : /// currently being added. 81 : Completer? _addStreamCompleter; 82 : 83 : /// Whether we're currently adding a stream with [addStream]. 84 0 : bool get _inAddStream => _addStreamSubscription != null; 85 : 86 0 : _DisconnectorSink(this._inner); 87 : 88 0 : @override 89 : void add(T data) { 90 0 : if (_closed) throw StateError('Cannot add event after closing.'); 91 0 : if (_inAddStream) { 92 0 : throw StateError('Cannot add event while adding stream.'); 93 : } 94 0 : if (_isDisconnected) return; 95 : 96 0 : _inner.add(data); 97 : } 98 : 99 0 : @override 100 : void addError(error, [StackTrace? stackTrace]) { 101 0 : if (_closed) throw StateError('Cannot add event after closing.'); 102 0 : if (_inAddStream) { 103 0 : throw StateError('Cannot add event while adding stream.'); 104 : } 105 0 : if (_isDisconnected) return; 106 : 107 0 : _inner.addError(error, stackTrace); 108 : } 109 : 110 0 : @override 111 : Future<void> addStream(Stream<T> stream) { 112 0 : if (_closed) throw StateError('Cannot add stream after closing.'); 113 0 : if (_inAddStream) { 114 0 : throw StateError('Cannot add stream while adding stream.'); 115 : } 116 0 : if (_isDisconnected) return Future.value(); 117 : 118 0 : _addStreamCompleter = Completer.sync(); 119 0 : _addStreamSubscription = stream.listen(_inner.add, 120 0 : onError: _inner.addError, onDone: _addStreamCompleter!.complete); 121 0 : return _addStreamCompleter!.future.then((_) { 122 0 : _addStreamCompleter = null; 123 0 : _addStreamSubscription = null; 124 : }); 125 : } 126 : 127 0 : @override 128 : Future<void> close() { 129 0 : if (_inAddStream) { 130 0 : throw StateError('Cannot close sink while adding stream.'); 131 : } 132 : 133 0 : _closed = true; 134 0 : return _inner.close(); 135 : } 136 : 137 : /// Disconnects this sink. 138 : /// 139 : /// This closes the underlying sink and stops forwarding events. It returns 140 : /// the [StreamSink.close] future for the underlying sink. 141 0 : Future<void> _disconnect() { 142 0 : _isDisconnected = true; 143 0 : var future = _inner.close(); 144 : 145 0 : if (_inAddStream) { 146 0 : _addStreamCompleter!.complete(_addStreamSubscription!.cancel()); 147 0 : _addStreamCompleter = null; 148 0 : _addStreamSubscription = null; 149 : } 150 : 151 : return future; 152 : } 153 : }