// Copyright (c) 2012, 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. library event_helper; import 'dart:async'; abstract class Event { void replay(EventSink sink); } class DataEvent implements Event { final data; DataEvent(this.data); void replay(EventSink sink) { sink.add(data); } int get hashCode => data.hashCode; bool operator ==(Object other) { if (other is! DataEvent) return false; DataEvent otherEvent = other; return data == otherEvent.data; } String toString() => "DataEvent: $data"; } class ErrorEvent implements Event { final error; ErrorEvent(this.error); void replay(EventSink sink) { sink.addError(error); } int get hashCode => error.error.hashCode; bool operator ==(Object other) { if (other is! ErrorEvent) return false; ErrorEvent otherEvent = other; return error == otherEvent.error; } String toString() => "ErrorEvent: ${error}"; } class DoneEvent implements Event { const DoneEvent(); void replay(EventSink sink) { sink.close(); } int get hashCode => 42; bool operator ==(Object other) => other is DoneEvent; String toString() => "DoneEvent"; } /** Collector of events. */ class Events implements EventSink { final List events = []; bool trace = false; Completer onDoneSignal = new Completer(); Events(); Events.fromIterable(Iterable iterable) { for (var value in iterable) add(value); close(); } /** Capture events from a stream into a new [Events] object. */ factory Events.capture(Stream stream, {bool cancelOnError}) = CaptureEvents; // EventSink interface. void add(var value) { if (trace) print("Events#$hashCode: add($value)"); events.add(new DataEvent(value)); } void addError(error, [StackTrace? stackTrace]) { if (trace) print("Events#$hashCode: addError($error)"); events.add(new ErrorEvent(error)); } void close() { if (trace) print("Events#$hashCode: close()"); events.add(const DoneEvent()); onDoneSignal.complete(); } /** * Error shorthand, for writing events manually. */ void error(var value, [StackTrace? stackTrace]) { addError(value, stackTrace); } /** Replay the captured events on a sink. */ void replay(EventSink sink) { for (int i = 0; i < events.length; i++) { events[i].replay(sink); } } /** * Create a new [Events] with the same captured events. * * This does not copy a subscription. */ Events copy() { Events result = new Events(); replay(result); return result; } // Operations that only work when there is a subscription feeding the Events. /** * Pauses the subscription that feeds this [Events]. * * Should only be used when there is a subscription. That is, after a * call to [subscribeTo]. */ void pause([Future? resumeSignal]) { throw new StateError("Not capturing events."); } /** Resumes after a call to [pause]. */ void resume() { throw new StateError("Not capturing events."); } /** * Sets an action to be called when this [Events] receives a 'done' event. * * The action will also be called if capturing events from a stream with * `cancelOnError` set to true and receiving an error. */ void onDone(void action()) { onDoneSignal.future.whenComplete(action); } } class CaptureEvents extends Events { late StreamSubscription subscription; bool cancelOnError = false; CaptureEvents(Stream stream, {bool cancelOnError: false}) { this.cancelOnError = cancelOnError; subscription = stream.listen(add, onError: addError, onDone: close, cancelOnError: cancelOnError); } void addError(error, [StackTrace? stackTrace]) { super.addError(error, stackTrace); if (cancelOnError) { onDoneSignal.complete(); } } void pause([Future? resumeSignal]) { if (trace) print("Events#$hashCode: pause"); subscription.pause(resumeSignal); } void resume() { if (trace) print("Events#$hashCode: resume"); subscription.resume(); } void onDone(void action()) { if (trace) print("Events#$hashCode: onDone"); super.onDone(action); } }