2023-01-14 13:53:33 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:meta/meta.dart';
|
|
|
|
import 'package:synchronized/synchronized.dart';
|
|
|
|
import 'package:uuid/uuid.dart';
|
2022-04-27 19:49:36 +00:00
|
|
|
|
|
|
|
/// Interface to allow arbitrary data to be sent as long as it can be
|
|
|
|
/// JSON serialized/deserialized.
|
|
|
|
class JsonImplementation {
|
|
|
|
JsonImplementation();
|
|
|
|
|
2023-01-14 13:53:33 +00:00
|
|
|
// ignore: avoid_unused_constructor_parameters
|
2022-04-27 19:49:36 +00:00
|
|
|
factory JsonImplementation.fromJson(Map<String, dynamic> json) {
|
|
|
|
return JsonImplementation();
|
|
|
|
}
|
2023-01-14 13:53:33 +00:00
|
|
|
|
|
|
|
Map<String, dynamic> toJson() => {};
|
2022-04-27 19:49:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Wrapper class that adds an ID to the data packet to be sent.
|
|
|
|
class DataWrapper<T extends JsonImplementation> {
|
|
|
|
const DataWrapper(
|
|
|
|
this.id,
|
2023-01-14 13:53:33 +00:00
|
|
|
this.data,
|
2022-04-27 19:49:36 +00:00
|
|
|
);
|
|
|
|
|
2023-01-14 13:53:33 +00:00
|
|
|
/// The id of the data packet.
|
|
|
|
final String id;
|
|
|
|
|
|
|
|
/// The actual data.
|
|
|
|
final T data;
|
|
|
|
|
2022-04-27 19:49:36 +00:00
|
|
|
Map<String, dynamic> toJson() => {
|
2023-01-14 13:53:33 +00:00
|
|
|
'id': id,
|
|
|
|
'data': data.toJson()
|
2022-04-27 19:49:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static DataWrapper fromJson<T extends JsonImplementation>(Map<String, dynamic> json) => DataWrapper<T>(
|
2023-01-14 13:53:33 +00:00
|
|
|
json['id']! as String,
|
|
|
|
json['data']! as T,
|
2022-04-27 19:49:36 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
DataWrapper reply(T newData) => DataWrapper(id, newData);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This class is useful in contexts where data is sent between two parties, e.g. the
|
|
|
|
/// UI and the background service and a correlation between requests and responses is
|
|
|
|
/// to be enabled.
|
|
|
|
///
|
|
|
|
/// awaiting [sendData] will return a [Future] that will resolve to the reresponse when
|
|
|
|
/// received via [onData].
|
|
|
|
abstract class AwaitableDataSender<
|
|
|
|
S extends JsonImplementation,
|
|
|
|
R extends JsonImplementation
|
|
|
|
> {
|
|
|
|
@mustCallSuper
|
2023-01-14 13:53:33 +00:00
|
|
|
AwaitableDataSender();
|
|
|
|
|
|
|
|
/// A mapping of ID to completer for pending requests.
|
|
|
|
final Map<String, Completer<R>> _awaitables = {};
|
|
|
|
|
|
|
|
/// Critical section for accessing [AwaitableDataSender._awaitables].
|
|
|
|
final Lock _lock = Lock();
|
|
|
|
|
|
|
|
/// A UUID object for generating UUIDs.
|
|
|
|
final Uuid _uuid = const Uuid();
|
2022-04-27 19:49:36 +00:00
|
|
|
|
2023-01-14 13:53:33 +00:00
|
|
|
/// A logger.
|
|
|
|
final Logger _log = Logger('AwaitableDataSender');
|
|
|
|
|
2022-04-27 19:49:36 +00:00
|
|
|
@visibleForTesting
|
2023-01-14 13:53:33 +00:00
|
|
|
Map<String, Completer<R>> getAwaitables() => _awaitables;
|
2022-04-27 19:49:36 +00:00
|
|
|
|
|
|
|
/// Called after an awaitable has been added.
|
|
|
|
@visibleForTesting
|
|
|
|
void onAdd() {}
|
|
|
|
|
|
|
|
/// NOTE: Must be overwritten by the actual implementation
|
|
|
|
@visibleForOverriding
|
|
|
|
Future<void> sendDataImpl(DataWrapper data);
|
|
|
|
|
|
|
|
/// Sends [data] using [sendDataImpl]. If [awaitable] is true, then a
|
|
|
|
/// Future will be returned that can be used to await a response. If it
|
|
|
|
/// is false, then null will be imediately resolved.
|
|
|
|
Future<R?> sendData(S data, { bool awaitable = true, @visibleForTesting String? id }) async {
|
2022-11-05 12:21:17 +00:00
|
|
|
// ignore: no_leading_underscores_for_local_identifiers
|
2022-04-27 19:49:36 +00:00
|
|
|
final _id = id ?? _uuid.v4();
|
2023-01-14 13:53:33 +00:00
|
|
|
var future = Future<R?>.value();
|
|
|
|
_log.fine('sendData: Waiting to acquire lock...');
|
2022-04-27 19:49:36 +00:00
|
|
|
await _lock.synchronized(() async {
|
2023-01-14 13:53:33 +00:00
|
|
|
_log.fine('sendData: Done');
|
2022-04-27 19:49:36 +00:00
|
|
|
if (awaitable) {
|
|
|
|
_awaitables[_id] = Completer();
|
|
|
|
onAdd();
|
|
|
|
}
|
|
|
|
|
|
|
|
await sendDataImpl(
|
|
|
|
DataWrapper<S>(
|
|
|
|
_id,
|
2023-01-14 13:53:33 +00:00
|
|
|
data,
|
|
|
|
),
|
2022-04-27 19:49:36 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if (awaitable) {
|
|
|
|
future = _awaitables[_id]!.future;
|
|
|
|
}
|
|
|
|
|
2023-01-14 13:53:33 +00:00
|
|
|
_log.fine('sendData: Releasing lock...');
|
2022-04-27 19:49:36 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return future;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Should be called when a [DataWrapper] has been received. Will resolve
|
|
|
|
/// the promise received from [sendData].
|
|
|
|
Future<bool> onData(DataWrapper<R> data) async {
|
2023-01-14 13:53:33 +00:00
|
|
|
_log.fine('onData: Waiting to acquire lock...');
|
|
|
|
final completer = await _lock.synchronized(() async {
|
|
|
|
_log.fine('onData: Done');
|
|
|
|
final c = _awaitables[data.id];
|
|
|
|
if (c != null) {
|
|
|
|
_awaitables.remove(data.id);
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
_log.fine('onData: Releasing lock');
|
|
|
|
return null;
|
2022-04-27 19:49:36 +00:00
|
|
|
});
|
|
|
|
|
2023-01-14 13:53:33 +00:00
|
|
|
completer?.complete(data.data);
|
2022-04-27 19:49:36 +00:00
|
|
|
|
2023-01-14 13:53:33 +00:00
|
|
|
return completer != null;
|
2022-04-27 19:49:36 +00:00
|
|
|
}
|
|
|
|
}
|