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 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 = new AsyncMemoizer();
42 :
43 : StreamChannel<T> bind(StreamChannel<T> channel) {
44 0 : return channel.changeSink((innerSink) {
45 0 : var sink = new _DisconnectorSink<T>(innerSink);
46 :
47 0 : if (isDisconnected) {
48 : // Ignore errors here, because otherwise there would be no way for the
49 : // user to handle them gracefully.
50 0 : sink._disconnect().catchError((_) {});
51 : } else {
52 0 : _sinks.add(sink);
53 : }
54 :
55 : return sink;
56 : });
57 : }
58 : }
59 :
60 : /// A sink wrapper that can force a disconnection.
61 : class _DisconnectorSink<T> implements StreamSink<T> {
62 : /// The inner sink.
63 : final StreamSink<T> _inner;
64 :
65 0 : Future get done => _inner.done;
66 :
67 : /// Whether [Disconnector.disconnect] has been called.
68 : var _isDisconnected = false;
69 :
70 : /// Whether the user has called [close].
71 : var _closed = false;
72 :
73 : /// The subscription to the stream passed to [addStream], if a stream is
74 : /// currently being added.
75 : StreamSubscription<T> _addStreamSubscription;
76 :
77 : /// The completer for the future returned by [addStream], if a stream is
78 : /// currently being added.
79 : Completer _addStreamCompleter;
80 :
81 : /// Whether we're currently adding a stream with [addStream].
82 0 : bool get _inAddStream => _addStreamSubscription != null;
83 :
84 0 : _DisconnectorSink(this._inner);
85 :
86 : void add(T data) {
87 0 : if (_closed) throw new StateError("Cannot add event after closing.");
88 0 : if (_inAddStream) {
89 0 : throw new StateError("Cannot add event while adding stream.");
90 : }
91 0 : if (_isDisconnected) return;
92 :
93 0 : _inner.add(data);
94 : }
95 :
96 : void addError(error, [StackTrace stackTrace]) {
97 0 : if (_closed) throw new StateError("Cannot add event after closing.");
98 0 : if (_inAddStream) {
99 0 : throw new StateError("Cannot add event while adding stream.");
100 : }
101 0 : if (_isDisconnected) return;
102 :
103 0 : _inner.addError(error, stackTrace);
104 : }
105 :
106 : Future addStream(Stream<T> stream) {
107 0 : if (_closed) throw new StateError("Cannot add stream after closing.");
108 0 : if (_inAddStream) {
109 0 : throw new StateError("Cannot add stream while adding stream.");
110 : }
111 0 : if (_isDisconnected) return new Future.value();
112 :
113 0 : _addStreamCompleter = new Completer.sync();
114 0 : _addStreamSubscription = stream.listen(_inner.add,
115 0 : onError: _inner.addError, onDone: _addStreamCompleter.complete);
116 0 : return _addStreamCompleter.future.then((_) {
117 0 : _addStreamCompleter = null;
118 0 : _addStreamSubscription = null;
119 : });
120 : }
121 :
122 : Future close() {
123 0 : if (_inAddStream) {
124 0 : throw new StateError("Cannot close sink while adding stream.");
125 : }
126 :
127 0 : _closed = true;
128 0 : return _inner.close();
129 : }
130 :
131 : /// Disconnects this sink.
132 : ///
133 : /// This closes the underlying sink and stops forwarding events. It returns
134 : /// the [StreamSink.close] future for the underlying sink.
135 : Future _disconnect() {
136 0 : _isDisconnected = true;
137 0 : var future = _inner.close();
138 :
139 0 : if (_inAddStream) {
140 0 : _addStreamCompleter.complete(_addStreamSubscription.cancel());
141 0 : _addStreamCompleter = null;
142 0 : _addStreamSubscription = null;
143 : }
144 :
145 : return future;
146 : }
147 : }
|