2013-02-21 12:08:34 +00:00
|
|
|
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
|
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
part of dart.io;
|
|
|
|
|
|
|
|
/**
|
2013-04-11 18:11:40 +00:00
|
|
|
* Helper class to wrap a [StreamConsumer<List<int>>] and provide
|
2013-03-08 10:06:28 +00:00
|
|
|
* utility functions for writing to the StreamConsumer directly. The
|
2013-04-15 07:35:45 +00:00
|
|
|
* [IOSink] buffers the input given by all [StringSink] methods and will delay
|
2014-03-18 12:30:54 +00:00
|
|
|
* an [addStream] until the buffer is flushed.
|
2013-02-21 12:08:34 +00:00
|
|
|
*
|
2013-04-15 07:35:45 +00:00
|
|
|
* When the [IOSink] is bound to a stream (through [addStream]) any call
|
2013-07-23 10:37:47 +00:00
|
|
|
* to the [IOSink] will throw a [StateError]. When the [addStream] completes,
|
2014-03-26 12:32:58 +00:00
|
|
|
* the [IOSink] will again be open for all calls.
|
2013-07-23 10:37:47 +00:00
|
|
|
*
|
|
|
|
* If data is added to the [IOSink] after the sink is closed, the data will be
|
|
|
|
* ignored. Use the [done] future to be notified when the [IOSink] is closed.
|
2013-02-21 12:08:34 +00:00
|
|
|
*/
|
2013-04-15 13:38:09 +00:00
|
|
|
abstract class IOSink implements StreamSink<List<int>>, StringSink {
|
2014-03-18 12:30:54 +00:00
|
|
|
// TODO(ajohnsen): Make _encodingMutable an argument.
|
2013-04-11 18:11:40 +00:00
|
|
|
factory IOSink(StreamConsumer<List<int>> target,
|
2013-08-26 10:37:25 +00:00
|
|
|
{Encoding encoding: UTF8})
|
2013-03-08 10:06:28 +00:00
|
|
|
=> new _IOSinkImpl(target, encoding);
|
2013-03-01 13:33:19 +00:00
|
|
|
|
|
|
|
/**
|
2013-03-08 10:06:28 +00:00
|
|
|
* The [Encoding] used when writing strings. Depending on the
|
|
|
|
* underlying consumer this property might be mutable.
|
2013-03-01 13:33:19 +00:00
|
|
|
*/
|
2013-03-08 10:06:28 +00:00
|
|
|
Encoding encoding;
|
2013-03-01 13:33:19 +00:00
|
|
|
|
|
|
|
/**
|
2014-03-18 12:30:54 +00:00
|
|
|
* Adds [data] to the target consumer, ignoring [encoding].
|
|
|
|
*
|
|
|
|
* The [encoding] does not apply to this method, and the `data` list is passed
|
|
|
|
* directly to the target consumer as a stream event.
|
|
|
|
*
|
|
|
|
* This function must not be called when a stream is currently being added
|
|
|
|
* using [addStream].
|
|
|
|
*
|
|
|
|
* This operation is non-blocking. See [flush] or [done] for how to get any
|
|
|
|
* errors generated by this call.
|
|
|
|
*
|
|
|
|
* The data list should not be modified after it has been passed to `add`.
|
2013-03-01 13:33:19 +00:00
|
|
|
*/
|
2013-04-11 12:54:19 +00:00
|
|
|
void add(List<int> data);
|
|
|
|
|
|
|
|
/**
|
2014-03-18 12:30:54 +00:00
|
|
|
* Converts [obj] to a String by invoking [Object.toString] and
|
|
|
|
* [add]s the encoding of the result to the target consumer.
|
|
|
|
*
|
|
|
|
* This operation is non-blocking. See [flush] or [done] for how to get any
|
|
|
|
* errors generated by this call.
|
|
|
|
*/
|
|
|
|
void write(Object obj);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterates over the given [objects] and [write]s them in sequence.
|
|
|
|
*
|
|
|
|
* If [separator] is provided, a `write` with the `separator` is performed
|
|
|
|
* between any two elements of `objects`.
|
|
|
|
*
|
|
|
|
* This operation is non-blocking. See [flush] or [done] for how to get any
|
|
|
|
* errors generated by this call.
|
|
|
|
*/
|
|
|
|
void writeAll(Iterable objects, [String separator = ""]);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts [obj] to a String by invoking [Object.toString] and
|
|
|
|
* writes the result to `this`, followed by a newline.
|
|
|
|
*
|
|
|
|
* This operation is non-blocking. See [flush] or [done] for how to get any
|
|
|
|
* errors generated by this call.
|
|
|
|
*/
|
|
|
|
void writeln([Object obj = ""]);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes the [charCode] to `this`.
|
|
|
|
*
|
|
|
|
* This method is equivalent to `write(new String.fromCharCode(charCode))`.
|
|
|
|
*
|
|
|
|
* This operation is non-blocking. See [flush] or [done] for how to get any
|
|
|
|
* errors generated by this call.
|
|
|
|
*/
|
|
|
|
void writeCharCode(int charCode);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Passes the error to the target consumer as an error event.
|
|
|
|
*
|
|
|
|
* This function must not be called when a stream is currently being added
|
|
|
|
* using [addStream].
|
|
|
|
*
|
|
|
|
* This operation is non-blocking. See [flush] or [done] for how to get any
|
|
|
|
* errors generated by this call.
|
2013-04-11 12:54:19 +00:00
|
|
|
*/
|
2013-10-29 14:30:36 +00:00
|
|
|
void addError(error, [StackTrace stackTrace]);
|
2013-03-01 13:33:19 +00:00
|
|
|
|
2013-04-05 16:22:11 +00:00
|
|
|
/**
|
|
|
|
* Adds all elements of the given [stream] to `this`.
|
|
|
|
*/
|
2013-04-15 07:35:45 +00:00
|
|
|
Future addStream(Stream<List<int>> stream);
|
2013-03-01 13:33:19 +00:00
|
|
|
|
2013-10-30 14:50:22 +00:00
|
|
|
/**
|
|
|
|
* Returns a [Future] that completes once all buffered data is accepted by the
|
|
|
|
* to underlying [StreamConsumer].
|
|
|
|
*
|
|
|
|
* It's an error to call this method, while an [addStream] is incomplete.
|
|
|
|
*
|
|
|
|
* NOTE: This is not necessarily the same as the data being flushed by the
|
|
|
|
* operating system.
|
|
|
|
*/
|
|
|
|
Future flush();
|
|
|
|
|
2013-03-01 13:33:19 +00:00
|
|
|
/**
|
2014-03-18 12:30:54 +00:00
|
|
|
* Close the target consumer.
|
2013-03-01 13:33:19 +00:00
|
|
|
*/
|
2013-04-05 16:22:11 +00:00
|
|
|
Future close();
|
2013-03-01 13:33:19 +00:00
|
|
|
|
|
|
|
/**
|
2014-03-18 12:30:54 +00:00
|
|
|
* Get a future that will complete when the consumer closes, or when an
|
|
|
|
* error occurs. This future is identical to the future returned by
|
|
|
|
* [close].
|
2013-03-01 13:33:19 +00:00
|
|
|
*/
|
2013-04-15 07:35:45 +00:00
|
|
|
Future get done;
|
2013-03-01 13:33:19 +00:00
|
|
|
}
|
|
|
|
|
2014-03-26 12:32:58 +00:00
|
|
|
class _StreamSinkImpl<T> implements StreamSink<T> {
|
|
|
|
final StreamConsumer<T> _target;
|
|
|
|
Completer _doneCompleter = new Completer();
|
|
|
|
Future _doneFuture;
|
|
|
|
StreamController<T> _controllerInstance;
|
|
|
|
Completer _controllerCompleter;
|
|
|
|
bool _isClosed = false;
|
|
|
|
bool _isBound = false;
|
|
|
|
bool _hasError = false;
|
2013-02-21 12:08:34 +00:00
|
|
|
|
2014-03-26 12:32:58 +00:00
|
|
|
_StreamSinkImpl(this._target) {
|
|
|
|
_doneFuture = _doneCompleter.future;
|
|
|
|
}
|
|
|
|
|
|
|
|
void add(T data) {
|
|
|
|
if (_isClosed) return;
|
|
|
|
_controller.add(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addError(error, [StackTrace stackTrace]) =>
|
|
|
|
_controller.addError(error, stackTrace);
|
|
|
|
|
|
|
|
Future addStream(Stream<T> stream) {
|
|
|
|
if (_isBound) {
|
|
|
|
throw new StateError("StreamSink is already bound to a stream");
|
|
|
|
}
|
|
|
|
_isBound = true;
|
|
|
|
if (_hasError) return done;
|
|
|
|
// Wait for any sync operations to complete.
|
|
|
|
Future targetAddStream() {
|
|
|
|
return _target.addStream(stream)
|
|
|
|
.whenComplete(() {
|
|
|
|
_isBound = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (_controllerInstance == null) return targetAddStream();
|
|
|
|
var future = _controllerCompleter.future;
|
|
|
|
_controllerInstance.close();
|
|
|
|
return future.then((_) => targetAddStream());
|
|
|
|
}
|
|
|
|
|
|
|
|
Future flush() {
|
|
|
|
if (_isBound) {
|
|
|
|
throw new StateError("StreamSink is bound to a stream");
|
|
|
|
}
|
|
|
|
if (_controllerInstance == null) return new Future.value(this);
|
|
|
|
// Adding an empty stream-controller will return a future that will complete
|
|
|
|
// when all data is done.
|
|
|
|
_isBound = true;
|
|
|
|
var future = _controllerCompleter.future;
|
|
|
|
_controllerInstance.close();
|
|
|
|
return future.whenComplete(() {
|
|
|
|
_isBound = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future close() {
|
|
|
|
if (_isBound) {
|
|
|
|
throw new StateError("StreamSink is bound to a stream");
|
|
|
|
}
|
|
|
|
if (!_isClosed) {
|
|
|
|
_isClosed = true;
|
|
|
|
if (_controllerInstance != null) {
|
|
|
|
_controllerInstance.close();
|
|
|
|
} else {
|
|
|
|
_closeTarget();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return done;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _closeTarget() {
|
|
|
|
_target.close()
|
|
|
|
.then((value) => _completeDone(value: value),
|
|
|
|
onError: (error) => _completeDone(error: error));
|
|
|
|
}
|
|
|
|
|
|
|
|
Future get done => _doneFuture;
|
|
|
|
|
|
|
|
void _completeDone({value, error}) {
|
|
|
|
if (_doneCompleter == null) return;
|
|
|
|
if (error == null) {
|
|
|
|
_doneCompleter.complete(value);
|
|
|
|
} else {
|
|
|
|
_hasError = true;
|
|
|
|
_doneCompleter.completeError(error);
|
|
|
|
}
|
|
|
|
_doneCompleter = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
StreamController<T> get _controller {
|
|
|
|
if (_isBound) {
|
|
|
|
throw new StateError("StreamSink is bound to a stream");
|
|
|
|
}
|
|
|
|
if (_isClosed) {
|
|
|
|
throw new StateError("StreamSink is closed");
|
|
|
|
}
|
|
|
|
if (_controllerInstance == null) {
|
|
|
|
_controllerInstance = new StreamController<T>(sync: true);
|
|
|
|
_controllerCompleter = new Completer();
|
|
|
|
_target.addStream(_controller.stream)
|
|
|
|
.then(
|
|
|
|
(_) {
|
|
|
|
if (_isBound) {
|
|
|
|
// A new stream takes over - forward values to that stream.
|
|
|
|
_controllerCompleter.complete(this);
|
|
|
|
_controllerCompleter = null;
|
|
|
|
_controllerInstance = null;
|
|
|
|
} else {
|
|
|
|
// No new stream, .close was called. Close _target.
|
|
|
|
_closeTarget();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onError: (error) {
|
|
|
|
if (_isBound) {
|
|
|
|
// A new stream takes over - forward errors to that stream.
|
|
|
|
_controllerCompleter.completeError(error);
|
|
|
|
_controllerCompleter = null;
|
|
|
|
_controllerInstance = null;
|
|
|
|
} else {
|
|
|
|
// No new stream. No need to close target, as it have already
|
|
|
|
// failed.
|
|
|
|
_completeDone(error: error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return _controllerInstance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _IOSinkImpl extends _StreamSinkImpl<List<int>> implements IOSink {
|
2013-04-16 15:08:21 +00:00
|
|
|
Encoding _encoding;
|
|
|
|
bool _encodingMutable = true;
|
|
|
|
|
|
|
|
_IOSinkImpl(StreamConsumer<List<int>> target, this._encoding)
|
|
|
|
: super(target);
|
|
|
|
|
|
|
|
Encoding get encoding => _encoding;
|
|
|
|
|
|
|
|
void set encoding(Encoding value) {
|
|
|
|
if (!_encodingMutable) {
|
|
|
|
throw new StateError("IOSink encoding is not mutable");
|
|
|
|
}
|
|
|
|
_encoding = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void write(Object obj) {
|
|
|
|
// This comment is copied from runtime/lib/string_buffer_patch.dart.
|
|
|
|
// TODO(srdjan): The following four lines could be replaced by
|
|
|
|
// '$obj', but apparently this is too slow on the Dart VM.
|
|
|
|
String string;
|
|
|
|
if (obj is String) {
|
|
|
|
string = obj;
|
|
|
|
} else {
|
|
|
|
string = obj.toString();
|
|
|
|
if (string is! String) {
|
|
|
|
throw new ArgumentError('toString() did not return a string');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (string.isEmpty) return;
|
2014-03-04 16:23:45 +00:00
|
|
|
add(_encoding.encode(string));
|
2013-04-16 15:08:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void writeAll(Iterable objects, [String separator = ""]) {
|
|
|
|
Iterator iterator = objects.iterator;
|
|
|
|
if (!iterator.moveNext()) return;
|
|
|
|
if (separator.isEmpty) {
|
|
|
|
do {
|
|
|
|
write(iterator.current);
|
|
|
|
} while (iterator.moveNext());
|
|
|
|
} else {
|
|
|
|
write(iterator.current);
|
|
|
|
while (iterator.moveNext()) {
|
|
|
|
write(separator);
|
|
|
|
write(iterator.current);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeln([Object obj = ""]) {
|
|
|
|
write(obj);
|
|
|
|
write("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeCharCode(int charCode) {
|
|
|
|
write(new String.fromCharCode(charCode));
|
|
|
|
}
|
|
|
|
}
|