blob: 8e5aa244f2e7625370b2702592d973c31e4b3550 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:fidl_fuchsia_setui/fidl.dart';
import 'package:lib_setui_settings_common/setting_adapter.dart';
import 'package:lib_setui_settings_common/setting_source.dart';
typedef LogAction = void Function();
typedef LogExecutor = void Function(Duration delay, LogAction action);
/// An adapter that can be preconfigured to return set values.
class MockSettingAdapter implements SettingAdapter {
final Map<SettingType, SettingSource> _sources = {};
final Map<int, Completer<UpdateResponse>> _activeResponses = {};
final Map<int, Completer<MutationResponse>> _activeMutationResponses = {};
final List<AdapterLog> logs;
final LogExecutor executor;
/// Default constructor to execute provided [AdapterLog]s. A [LogExecutor] may
/// be provided to override the default behavior of executing server logs
/// based on their time relationship with the closest user action keyframe.
MockSettingAdapter(this.logs, {this.executor = _logExecute});
@override
SettingSource<T> fetch<T>(SettingType settingType) {
if (!_sources.containsKey(settingType)) {
_sources[settingType] = SettingSource<T>();
}
// Immediately unwind to the matching fetch
_unwindTo((AdapterLog log) =>
log.type == AdapterLogType.fetch && log.fetchLog.type == settingType);
if (logs.isNotEmpty) {
// Unwind in relation to the found fetch log.
final AdapterLog log = logs.removeAt(0);
_unwindTo((AdapterLog log) => log.fromClient, keyFrame: log.time);
}
return _sources[settingType];
}
@override
Future<UpdateResponse> update(SettingsObject updatedSetting) {
final Completer<UpdateResponse> completer = Completer<UpdateResponse>();
// Unwind to the matching update log.
_unwindTo((AdapterLog log) {
return log.type == AdapterLogType.update &&
log.updateLog.settings.settingType == updatedSetting.settingType;
});
if (logs.isEmpty) {
completer.complete(null);
} else {
final AdapterLog log = logs.removeAt(0);
_activeResponses[log.updateLog.id] = completer;
// Unwind to the next client log.
_unwindTo((AdapterLog log) => log.fromClient, keyFrame: log.time);
}
return completer.future;
}
@override
Future<MutationResponse> mutate(SettingType settingType, Mutation mutation,
{MutationHandles handles}) {
final Completer<MutationResponse> completer = Completer<MutationResponse>();
// Unwind to the matching update log.
_unwindTo((AdapterLog log) {
return log.type == AdapterLogType.mutation &&
log.mutationLog.settingType == settingType;
});
if (logs.isEmpty) {
completer.complete(null);
} else {
final AdapterLog log = logs.removeAt(0);
_activeMutationResponses[log.mutationLog.id] = completer;
// Unwind to the next client log.
_unwindTo((AdapterLog log) => log.fromClient, keyFrame: log.time);
}
return completer.future;
}
/// Replays logs up to the log which matches the conditions provided by the
/// input match function. The keyframe provides a time reference which events
/// will be temporally executed against.
void _unwindTo(bool match(AdapterLog log), {DateTime keyFrame}) {
while (logs.isNotEmpty && !match(logs.first)) {
_processLog(keyFrame, logs.removeAt(0));
}
}
void _processLog(DateTime keyFrame, AdapterLog log) {
LogAction action;
switch (log.type) {
case AdapterLogType.response:
action = () {
final Completer completer =
_activeResponses[log.responseLog.updateId];
if (completer == null) {
return;
}
completer.complete(log.responseLog.response);
};
break;
case AdapterLogType.mutationResponse:
action = () {
final Completer completer =
_activeMutationResponses[log.mutationResponseLog.mutationId];
if (completer == null) {
return;
}
completer.complete(log.mutationResponseLog.response);
};
break;
case AdapterLogType.setting:
action = () => _sources[log.settingLog.settings.settingType]
.notify(log.settingLog.settings);
break;
}
if (action != null) {
executor(keyFrame != null ? log.time.difference(keyFrame) : Duration.zero,
action);
}
}
/// Default playback mechanism, executing action after specified delay.
static void _logExecute(Duration delay, LogAction action) {
Future.delayed(delay, action);
}
}