diff options
author | Ludwig Schwiedrzik <ludwig.schwiedrzik@d-fine.com> | 2024-08-13 17:23:45 +0200 |
---|---|---|
committer | Scott Murray <scott.murray@konsulko.com> | 2024-09-10 19:52:10 +0000 |
commit | a2dcd701777968a65d3176eaf28aa7023d97c16b (patch) | |
tree | 65a5861430a1fde81160cd116fd70fba40cd1fb3 | |
parent | d3ea8d7fa4518c258fca3c825ee895487fcaa8ec (diff) |
Implementation of Persistent Storage API to the flutter homescreen
Added protobuf definition of Persistent Storage API.
Generated grpc-compliant code from protobuf API definition.
Set up storage_client based on similar radio_client.
Updated app_confi_provider and app_provider to include new API features
and prepare for implementation of persistent storage of users, user
preferences.
Added unit tests for all API rpcs.
Bug-AGL: [SPEC-5227]
Change-Id: I759501bcb9de3a70a14718f8b3a87bedcf811baa
Signed-off-by: Tom Kronsbein <tom.kronsbein@d-fine.com>
Signed-off-by: Ludwig Schwiedrzik <ludwig.schwiedrzik@d-fine.com>
-rw-r--r-- | lib/data/data_providers/app_config_provider.dart | 47 | ||||
-rw-r--r-- | lib/data/data_providers/app_provider.dart | 13 | ||||
-rw-r--r-- | lib/data/data_providers/storage_client.dart | 89 | ||||
-rw-r--r-- | protos/lib/src/generated/storage_api.pb.dart | 534 | ||||
-rw-r--r-- | protos/lib/src/generated/storage_api.pbenum.dart | 11 | ||||
-rw-r--r-- | protos/lib/src/generated/storage_api.pbgrpc.dart | 179 | ||||
-rw-r--r-- | protos/lib/src/generated/storage_api.pbjson.dart | 126 | ||||
-rw-r--r-- | protos/lib/storage-api.dart | 8 | ||||
-rw-r--r-- | protos/protos/storage_api.proto | 66 | ||||
-rw-r--r-- | test/StorageClient_test.dart | 557 |
10 files changed, 1627 insertions, 3 deletions
diff --git a/lib/data/data_providers/app_config_provider.dart b/lib/data/data_providers/app_config_provider.dart index 6a4ea02..b82eb54 100644 --- a/lib/data/data_providers/app_config_provider.dart +++ b/lib/data/data_providers/app_config_provider.dart @@ -56,6 +56,23 @@ class RadioConfig { } } +class StorageConfig { + final String hostname; + final int port; + + static String defaultHostname = 'localhost'; + static int defaultPort = 50054; + + StorageConfig( + {required this.hostname, required this.port}); + + static StorageConfig defaultConfig() { + return StorageConfig( + hostname: StorageConfig.defaultHostname, + port: StorageConfig.defaultPort); + } +} + class MpdConfig { final String hostname; final int port; @@ -77,6 +94,7 @@ class AppConfig { final bool randomHybridAnimation; final KuksaConfig kuksaConfig; final RadioConfig radioConfig; + final StorageConfig storageConfig; final MpdConfig mpdConfig; static String configFilePath = '/etc/xdg/AGL/ics-homescreen.yaml'; @@ -87,6 +105,7 @@ class AppConfig { required this.randomHybridAnimation, required this.kuksaConfig, required this.radioConfig, + required this.storageConfig, required this.mpdConfig}); static KuksaConfig parseKuksaConfig(YamlMap kuksaMap) { @@ -182,6 +201,25 @@ class AppConfig { } } + static StorageConfig parseStorageConfig(YamlMap storageMap) { + try { + String hostname = StorageConfig.defaultHostname; + if (storageMap.containsKey('hostname')) { + hostname = storageMap['hostname']; + } + + int port = StorageConfig.defaultPort; + if (storageMap.containsKey('port')) { + port = storageMap['port']; + } + + return StorageConfig(hostname: hostname, port: port); + } catch (_) { + debugPrint("Invalid storage configuration, using defaults"); + return StorageConfig.defaultConfig(); + } + } + static MpdConfig parseMpdConfig(YamlMap mpdMap) { try { String hostname = MpdConfig.defaultHostname; @@ -229,6 +267,13 @@ final appConfigProvider = Provider((ref) { radioConfig = RadioConfig.defaultConfig(); } + StorageConfig storageConfig; + if (yamlMap.containsKey('storage')) { + storageConfig = AppConfig.parseStorageConfig(yamlMap['storage']); + } else { + storageConfig = StorageConfig.defaultConfig(); + } + MpdConfig mpdConfig; if (yamlMap.containsKey('mpd')) { mpdConfig = AppConfig.parseMpdConfig(yamlMap['mpd']); @@ -266,6 +311,7 @@ final appConfigProvider = Provider((ref) { randomHybridAnimation: randomHybridAnimation, kuksaConfig: kuksaConfig, radioConfig: radioConfig, + storageConfig: storageConfig, mpdConfig: mpdConfig); } catch (_) { return AppConfig( @@ -274,6 +320,7 @@ final appConfigProvider = Provider((ref) { randomHybridAnimation: false, kuksaConfig: KuksaConfig.defaultConfig(), radioConfig: RadioConfig.defaultConfig(), + storageConfig: StorageConfig.defaultConfig(), mpdConfig: MpdConfig.defaultConfig()); } }); diff --git a/lib/data/data_providers/app_provider.dart b/lib/data/data_providers/app_provider.dart index ca2c3d4..0f7ed0c 100644 --- a/lib/data/data_providers/app_provider.dart +++ b/lib/data/data_providers/app_provider.dart @@ -13,6 +13,7 @@ import 'package:flutter_ics_homescreen/data/data_providers/playlist_art_notifier import 'package:flutter_ics_homescreen/data/data_providers/val_client.dart'; import 'package:flutter_ics_homescreen/data/data_providers/app_launcher.dart'; import 'package:flutter_ics_homescreen/data/data_providers/radio_client.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/storage_client.dart'; import 'package:flutter_ics_homescreen/data/data_providers/mpd_client.dart'; import 'package:flutter_ics_homescreen/data/data_providers/play_controller.dart'; import 'package:flutter_ics_homescreen/export.dart'; @@ -85,6 +86,13 @@ final radioClientProvider = Provider((ref) { return RadioClient(config: config, ref: ref); }); + +final storageClientProvider = Provider((ref) { + StorageConfig config = ref.watch(appConfigProvider).storageConfig; + return StorageClient(config: config, ref: ref); +}); + + final mpdClientProvider = Provider((ref) { MpdConfig config = ref.watch(appConfigProvider).mpdConfig; return MpdClient(config: config, ref: ref); @@ -134,9 +142,8 @@ final playControllerProvider = Provider((ref) { return PlayController(ref: ref); }); -final usersProvider = StateNotifierProvider<UsersNotifier, Users>((ref) { - return UsersNotifier(Users.initial()); -}); +final usersProvider = + NotifierProvider<UsersNotifier, Users>(UsersNotifier.new); final hybridStateProvider = StateNotifierProvider<HybridNotifier, Hybrid>((ref) { diff --git a/lib/data/data_providers/storage_client.dart b/lib/data/data_providers/storage_client.dart new file mode 100644 index 0000000..5c72785 --- /dev/null +++ b/lib/data/data_providers/storage_client.dart @@ -0,0 +1,89 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:protos/storage-api.dart' as storage_api; + +class StorageClient{ + final StorageConfig config; + final Ref ref; + late storage_api.ClientChannel channel; + late storage_api.DatabaseClient stub; + + StorageClient({required this.config, required this.ref}) { + debugPrint( + "Connecting to storage service at ${config.hostname}:${config.port}"); + storage_api.ChannelCredentials creds = const storage_api.ChannelCredentials.insecure(); + channel = storage_api.ClientChannel(config.hostname, + port: config.port, options: storage_api.ChannelOptions(credentials: creds)); + stub = storage_api.DatabaseClient(channel); + } + + + Future<storage_api.StandardResponse> destroyDB() async { + try { + var response = await stub.destroyDB(storage_api.DestroyArguments()); + return response; + } catch (e) { + print(e); + rethrow; + } + } + + Future<storage_api.StandardResponse> write(storage_api.KeyValue keyValue) async { + try { + var response = await stub.write(keyValue); + return response; + } catch (e) { + print(e); + rethrow; + } + } + + Future<storage_api.ReadResponse> read(storage_api.Key key) async{ + try{ + var response = await stub.read(key); + return response; + } catch(e) { + print(e); + rethrow; + } + } + + Future<storage_api.StandardResponse> delete(storage_api.Key key) async{ + try{ + var response = await stub.delete(key); + return response; + } catch(e) { + print(e); + rethrow; + } + } + + Future<storage_api.ListResponse> search(storage_api.Key key) async{ + try{ + var response = await stub.search(key); + return response; + } catch(e) { + print(e); + rethrow; + } + } + + Future<storage_api.StandardResponse> deleteNodes(storage_api.Key key) async{ + try{ + var response = await stub.deleteNodes(key); + return response; + } catch(e) { + print(e); + rethrow; + } + } + + Future<storage_api.ListResponse> listNodes(storage_api.SubtreeInfo subtreeInfo) async{ + try{ + var response = await stub.listNodes(subtreeInfo); + return response; + } catch(e) { + print(e); + rethrow; + } + } +}
\ No newline at end of file diff --git a/protos/lib/src/generated/storage_api.pb.dart b/protos/lib/src/generated/storage_api.pb.dart new file mode 100644 index 0000000..fb027be --- /dev/null +++ b/protos/lib/src/generated/storage_api.pb.dart @@ -0,0 +1,534 @@ +// +// Generated code. Do not modify. +// source: protos/protos/storage_api.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class Key extends $pb.GeneratedMessage { + factory Key({ + $core.String? key, + $core.String? namespace, + }) { + final $result = create(); + if (key != null) { + $result.key = key; + } + if (namespace != null) { + $result.namespace = namespace; + } + return $result; + } + Key._() : super(); + factory Key.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Key.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Key', package: const $pb.PackageName(_omitMessageNames ? '' : 'storage_api'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'key') + ..aOS(2, _omitFieldNames ? '' : 'namespace') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Key clone() => Key()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Key copyWith(void Function(Key) updates) => super.copyWith((message) => updates(message as Key)) as Key; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Key create() => Key._(); + Key createEmptyInstance() => create(); + static $pb.PbList<Key> createRepeated() => $pb.PbList<Key>(); + @$core.pragma('dart2js:noInline') + static Key getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Key>(create); + static Key? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get key => $_getSZ(0); + @$pb.TagNumber(1) + set key($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasKey() => $_has(0); + @$pb.TagNumber(1) + void clearKey() => clearField(1); + + @$pb.TagNumber(2) + $core.String get namespace => $_getSZ(1); + @$pb.TagNumber(2) + set namespace($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasNamespace() => $_has(1); + @$pb.TagNumber(2) + void clearNamespace() => clearField(2); +} + +class Value extends $pb.GeneratedMessage { + factory Value({ + $core.String? value, + }) { + final $result = create(); + if (value != null) { + $result.value = value; + } + return $result; + } + Value._() : super(); + factory Value.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory Value.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Value', package: const $pb.PackageName(_omitMessageNames ? '' : 'storage_api'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'value') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + Value clone() => Value()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + Value copyWith(void Function(Value) updates) => super.copyWith((message) => updates(message as Value)) as Value; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Value create() => Value._(); + Value createEmptyInstance() => create(); + static $pb.PbList<Value> createRepeated() => $pb.PbList<Value>(); + @$core.pragma('dart2js:noInline') + static Value getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Value>(create); + static Value? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get value => $_getSZ(0); + @$pb.TagNumber(1) + set value($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasValue() => $_has(0); + @$pb.TagNumber(1) + void clearValue() => clearField(1); +} + +class KeyValue extends $pb.GeneratedMessage { + factory KeyValue({ + $core.String? key, + $core.String? value, + $core.String? namespace, + }) { + final $result = create(); + if (key != null) { + $result.key = key; + } + if (value != null) { + $result.value = value; + } + if (namespace != null) { + $result.namespace = namespace; + } + return $result; + } + KeyValue._() : super(); + factory KeyValue.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory KeyValue.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'KeyValue', package: const $pb.PackageName(_omitMessageNames ? '' : 'storage_api'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'key') + ..aOS(2, _omitFieldNames ? '' : 'value') + ..aOS(3, _omitFieldNames ? '' : 'namespace') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + KeyValue clone() => KeyValue()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + KeyValue copyWith(void Function(KeyValue) updates) => super.copyWith((message) => updates(message as KeyValue)) as KeyValue; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static KeyValue create() => KeyValue._(); + KeyValue createEmptyInstance() => create(); + static $pb.PbList<KeyValue> createRepeated() => $pb.PbList<KeyValue>(); + @$core.pragma('dart2js:noInline') + static KeyValue getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<KeyValue>(create); + static KeyValue? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get key => $_getSZ(0); + @$pb.TagNumber(1) + set key($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasKey() => $_has(0); + @$pb.TagNumber(1) + void clearKey() => clearField(1); + + @$pb.TagNumber(2) + $core.String get value => $_getSZ(1); + @$pb.TagNumber(2) + set value($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasValue() => $_has(1); + @$pb.TagNumber(2) + void clearValue() => clearField(2); + + @$pb.TagNumber(3) + $core.String get namespace => $_getSZ(2); + @$pb.TagNumber(3) + set namespace($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasNamespace() => $_has(2); + @$pb.TagNumber(3) + void clearNamespace() => clearField(3); +} + +class SubtreeInfo extends $pb.GeneratedMessage { + factory SubtreeInfo({ + $core.String? node, + $core.int? layers, + $core.String? namespace, + }) { + final $result = create(); + if (node != null) { + $result.node = node; + } + if (layers != null) { + $result.layers = layers; + } + if (namespace != null) { + $result.namespace = namespace; + } + return $result; + } + SubtreeInfo._() : super(); + factory SubtreeInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory SubtreeInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SubtreeInfo', package: const $pb.PackageName(_omitMessageNames ? '' : 'storage_api'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'node') + ..a<$core.int>(2, _omitFieldNames ? '' : 'layers', $pb.PbFieldType.O3) + ..aOS(3, _omitFieldNames ? '' : 'namespace') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + SubtreeInfo clone() => SubtreeInfo()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + SubtreeInfo copyWith(void Function(SubtreeInfo) updates) => super.copyWith((message) => updates(message as SubtreeInfo)) as SubtreeInfo; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubtreeInfo create() => SubtreeInfo._(); + SubtreeInfo createEmptyInstance() => create(); + static $pb.PbList<SubtreeInfo> createRepeated() => $pb.PbList<SubtreeInfo>(); + @$core.pragma('dart2js:noInline') + static SubtreeInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SubtreeInfo>(create); + static SubtreeInfo? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get node => $_getSZ(0); + @$pb.TagNumber(1) + set node($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasNode() => $_has(0); + @$pb.TagNumber(1) + void clearNode() => clearField(1); + + @$pb.TagNumber(2) + $core.int get layers => $_getIZ(1); + @$pb.TagNumber(2) + set layers($core.int v) { $_setSignedInt32(1, v); } + @$pb.TagNumber(2) + $core.bool hasLayers() => $_has(1); + @$pb.TagNumber(2) + void clearLayers() => clearField(2); + + @$pb.TagNumber(3) + $core.String get namespace => $_getSZ(2); + @$pb.TagNumber(3) + set namespace($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasNamespace() => $_has(2); + @$pb.TagNumber(3) + void clearNamespace() => clearField(3); +} + +class DestroyArguments extends $pb.GeneratedMessage { + factory DestroyArguments() => create(); + DestroyArguments._() : super(); + factory DestroyArguments.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory DestroyArguments.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DestroyArguments', package: const $pb.PackageName(_omitMessageNames ? '' : 'storage_api'), createEmptyInstance: create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + DestroyArguments clone() => DestroyArguments()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + DestroyArguments copyWith(void Function(DestroyArguments) updates) => super.copyWith((message) => updates(message as DestroyArguments)) as DestroyArguments; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static DestroyArguments create() => DestroyArguments._(); + DestroyArguments createEmptyInstance() => create(); + static $pb.PbList<DestroyArguments> createRepeated() => $pb.PbList<DestroyArguments>(); + @$core.pragma('dart2js:noInline') + static DestroyArguments getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DestroyArguments>(create); + static DestroyArguments? _defaultInstance; +} + +class StandardResponse extends $pb.GeneratedMessage { + factory StandardResponse({ + $core.bool? success, + $core.String? message, + }) { + final $result = create(); + if (success != null) { + $result.success = success; + } + if (message != null) { + $result.message = message; + } + return $result; + } + StandardResponse._() : super(); + factory StandardResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory StandardResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'StandardResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'storage_api'), createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..aOS(2, _omitFieldNames ? '' : 'message') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + StandardResponse clone() => StandardResponse()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + StandardResponse copyWith(void Function(StandardResponse) updates) => super.copyWith((message) => updates(message as StandardResponse)) as StandardResponse; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static StandardResponse create() => StandardResponse._(); + StandardResponse createEmptyInstance() => create(); + static $pb.PbList<StandardResponse> createRepeated() => $pb.PbList<StandardResponse>(); + @$core.pragma('dart2js:noInline') + static StandardResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<StandardResponse>(create); + static StandardResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool v) { $_setBool(0, v); } + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => clearField(1); + + @$pb.TagNumber(2) + $core.String get message => $_getSZ(1); + @$pb.TagNumber(2) + set message($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasMessage() => $_has(1); + @$pb.TagNumber(2) + void clearMessage() => clearField(2); +} + +class ReadResponse extends $pb.GeneratedMessage { + factory ReadResponse({ + $core.bool? success, + $core.String? message, + $core.String? result, + }) { + final $result = create(); + if (success != null) { + $result.success = success; + } + if (message != null) { + $result.message = message; + } + if (result != null) { + $result.result = result; + } + return $result; + } + ReadResponse._() : super(); + factory ReadResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ReadResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ReadResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'storage_api'), createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..aOS(2, _omitFieldNames ? '' : 'message') + ..aOS(3, _omitFieldNames ? '' : 'result') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ReadResponse clone() => ReadResponse()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ReadResponse copyWith(void Function(ReadResponse) updates) => super.copyWith((message) => updates(message as ReadResponse)) as ReadResponse; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ReadResponse create() => ReadResponse._(); + ReadResponse createEmptyInstance() => create(); + static $pb.PbList<ReadResponse> createRepeated() => $pb.PbList<ReadResponse>(); + @$core.pragma('dart2js:noInline') + static ReadResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ReadResponse>(create); + static ReadResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool v) { $_setBool(0, v); } + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => clearField(1); + + @$pb.TagNumber(2) + $core.String get message => $_getSZ(1); + @$pb.TagNumber(2) + set message($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasMessage() => $_has(1); + @$pb.TagNumber(2) + void clearMessage() => clearField(2); + + @$pb.TagNumber(3) + $core.String get result => $_getSZ(2); + @$pb.TagNumber(3) + set result($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasResult() => $_has(2); + @$pb.TagNumber(3) + void clearResult() => clearField(3); +} + +class ListResponse extends $pb.GeneratedMessage { + factory ListResponse({ + $core.bool? success, + $core.String? message, + $core.Iterable<$core.String>? result, + }) { + final $result = create(); + if (success != null) { + $result.success = success; + } + if (message != null) { + $result.message = message; + } + if (result != null) { + $result.result.addAll(result); + } + return $result; + } + ListResponse._() : super(); + factory ListResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ListResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ListResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'storage_api'), createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..aOS(2, _omitFieldNames ? '' : 'message') + ..pPS(3, _omitFieldNames ? '' : 'result') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ListResponse clone() => ListResponse()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ListResponse copyWith(void Function(ListResponse) updates) => super.copyWith((message) => updates(message as ListResponse)) as ListResponse; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ListResponse create() => ListResponse._(); + ListResponse createEmptyInstance() => create(); + static $pb.PbList<ListResponse> createRepeated() => $pb.PbList<ListResponse>(); + @$core.pragma('dart2js:noInline') + static ListResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ListResponse>(create); + static ListResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool v) { $_setBool(0, v); } + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => clearField(1); + + @$pb.TagNumber(2) + $core.String get message => $_getSZ(1); + @$pb.TagNumber(2) + set message($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasMessage() => $_has(1); + @$pb.TagNumber(2) + void clearMessage() => clearField(2); + + @$pb.TagNumber(3) + $core.List<$core.String> get result => $_getList(2); +} + + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/protos/lib/src/generated/storage_api.pbenum.dart b/protos/lib/src/generated/storage_api.pbenum.dart new file mode 100644 index 0000000..8c2646e --- /dev/null +++ b/protos/lib/src/generated/storage_api.pbenum.dart @@ -0,0 +1,11 @@ +// +// Generated code. Do not modify. +// source: protos/protos/storage_api.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + diff --git a/protos/lib/src/generated/storage_api.pbgrpc.dart b/protos/lib/src/generated/storage_api.pbgrpc.dart new file mode 100644 index 0000000..8bdd848 --- /dev/null +++ b/protos/lib/src/generated/storage_api.pbgrpc.dart @@ -0,0 +1,179 @@ +// +// Generated code. Do not modify. +// source: protos/protos/storage_api.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'storage_api.pb.dart' as $0; + +export 'storage_api.pb.dart'; + +@$pb.GrpcServiceName('storage_api.Database') +class DatabaseClient extends $grpc.Client { + static final _$destroyDB = $grpc.ClientMethod<$0.DestroyArguments, $0.StandardResponse>( + '/storage_api.Database/DestroyDB', + ($0.DestroyArguments value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.StandardResponse.fromBuffer(value)); + static final _$write = $grpc.ClientMethod<$0.KeyValue, $0.StandardResponse>( + '/storage_api.Database/Write', + ($0.KeyValue value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.StandardResponse.fromBuffer(value)); + static final _$read = $grpc.ClientMethod<$0.Key, $0.ReadResponse>( + '/storage_api.Database/Read', + ($0.Key value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.ReadResponse.fromBuffer(value)); + static final _$delete = $grpc.ClientMethod<$0.Key, $0.StandardResponse>( + '/storage_api.Database/Delete', + ($0.Key value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.StandardResponse.fromBuffer(value)); + static final _$search = $grpc.ClientMethod<$0.Key, $0.ListResponse>( + '/storage_api.Database/Search', + ($0.Key value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.ListResponse.fromBuffer(value)); + static final _$deleteNodes = $grpc.ClientMethod<$0.Key, $0.StandardResponse>( + '/storage_api.Database/DeleteNodes', + ($0.Key value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.StandardResponse.fromBuffer(value)); + static final _$listNodes = $grpc.ClientMethod<$0.SubtreeInfo, $0.ListResponse>( + '/storage_api.Database/ListNodes', + ($0.SubtreeInfo value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.ListResponse.fromBuffer(value)); + + DatabaseClient($grpc.ClientChannel channel, + {$grpc.CallOptions? options, + $core.Iterable<$grpc.ClientInterceptor>? interceptors}) + : super(channel, options: options, + interceptors: interceptors); + + $grpc.ResponseFuture<$0.StandardResponse> destroyDB($0.DestroyArguments request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$destroyDB, request, options: options); + } + + $grpc.ResponseFuture<$0.StandardResponse> write($0.KeyValue request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$write, request, options: options); + } + + $grpc.ResponseFuture<$0.ReadResponse> read($0.Key request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$read, request, options: options); + } + + $grpc.ResponseFuture<$0.StandardResponse> delete($0.Key request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$delete, request, options: options); + } + + $grpc.ResponseFuture<$0.ListResponse> search($0.Key request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$search, request, options: options); + } + + $grpc.ResponseFuture<$0.StandardResponse> deleteNodes($0.Key request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$deleteNodes, request, options: options); + } + + $grpc.ResponseFuture<$0.ListResponse> listNodes($0.SubtreeInfo request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$listNodes, request, options: options); + } +} + +@$pb.GrpcServiceName('storage_api.Database') +abstract class DatabaseServiceBase extends $grpc.Service { + $core.String get $name => 'storage_api.Database'; + + DatabaseServiceBase() { + $addMethod($grpc.ServiceMethod<$0.DestroyArguments, $0.StandardResponse>( + 'DestroyDB', + destroyDB_Pre, + false, + false, + ($core.List<$core.int> value) => $0.DestroyArguments.fromBuffer(value), + ($0.StandardResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.KeyValue, $0.StandardResponse>( + 'Write', + write_Pre, + false, + false, + ($core.List<$core.int> value) => $0.KeyValue.fromBuffer(value), + ($0.StandardResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.Key, $0.ReadResponse>( + 'Read', + read_Pre, + false, + false, + ($core.List<$core.int> value) => $0.Key.fromBuffer(value), + ($0.ReadResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.Key, $0.StandardResponse>( + 'Delete', + delete_Pre, + false, + false, + ($core.List<$core.int> value) => $0.Key.fromBuffer(value), + ($0.StandardResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.Key, $0.ListResponse>( + 'Search', + search_Pre, + false, + false, + ($core.List<$core.int> value) => $0.Key.fromBuffer(value), + ($0.ListResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.Key, $0.StandardResponse>( + 'DeleteNodes', + deleteNodes_Pre, + false, + false, + ($core.List<$core.int> value) => $0.Key.fromBuffer(value), + ($0.StandardResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.SubtreeInfo, $0.ListResponse>( + 'ListNodes', + listNodes_Pre, + false, + false, + ($core.List<$core.int> value) => $0.SubtreeInfo.fromBuffer(value), + ($0.ListResponse value) => value.writeToBuffer())); + } + + $async.Future<$0.StandardResponse> destroyDB_Pre($grpc.ServiceCall call, $async.Future<$0.DestroyArguments> request) async { + return destroyDB(call, await request); + } + + $async.Future<$0.StandardResponse> write_Pre($grpc.ServiceCall call, $async.Future<$0.KeyValue> request) async { + return write(call, await request); + } + + $async.Future<$0.ReadResponse> read_Pre($grpc.ServiceCall call, $async.Future<$0.Key> request) async { + return read(call, await request); + } + + $async.Future<$0.StandardResponse> delete_Pre($grpc.ServiceCall call, $async.Future<$0.Key> request) async { + return delete(call, await request); + } + + $async.Future<$0.ListResponse> search_Pre($grpc.ServiceCall call, $async.Future<$0.Key> request) async { + return search(call, await request); + } + + $async.Future<$0.StandardResponse> deleteNodes_Pre($grpc.ServiceCall call, $async.Future<$0.Key> request) async { + return deleteNodes(call, await request); + } + + $async.Future<$0.ListResponse> listNodes_Pre($grpc.ServiceCall call, $async.Future<$0.SubtreeInfo> request) async { + return listNodes(call, await request); + } + + $async.Future<$0.StandardResponse> destroyDB($grpc.ServiceCall call, $0.DestroyArguments request); + $async.Future<$0.StandardResponse> write($grpc.ServiceCall call, $0.KeyValue request); + $async.Future<$0.ReadResponse> read($grpc.ServiceCall call, $0.Key request); + $async.Future<$0.StandardResponse> delete($grpc.ServiceCall call, $0.Key request); + $async.Future<$0.ListResponse> search($grpc.ServiceCall call, $0.Key request); + $async.Future<$0.StandardResponse> deleteNodes($grpc.ServiceCall call, $0.Key request); + $async.Future<$0.ListResponse> listNodes($grpc.ServiceCall call, $0.SubtreeInfo request); +} diff --git a/protos/lib/src/generated/storage_api.pbjson.dart b/protos/lib/src/generated/storage_api.pbjson.dart new file mode 100644 index 0000000..f697b4b --- /dev/null +++ b/protos/lib/src/generated/storage_api.pbjson.dart @@ -0,0 +1,126 @@ +// +// Generated code. Do not modify. +// source: protos/protos/storage_api.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use keyDescriptor instead') +const Key$json = { + '1': 'Key', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'namespace', '3': 2, '4': 1, '5': 9, '10': 'namespace'}, + ], +}; + +/// Descriptor for `Key`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List keyDescriptor = $convert.base64Decode( + 'CgNLZXkSEAoDa2V5GAEgASgJUgNrZXkSHAoJbmFtZXNwYWNlGAIgASgJUgluYW1lc3BhY2U='); + +@$core.Deprecated('Use valueDescriptor instead') +const Value$json = { + '1': 'Value', + '2': [ + {'1': 'value', '3': 1, '4': 1, '5': 9, '10': 'value'}, + ], +}; + +/// Descriptor for `Value`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List valueDescriptor = $convert.base64Decode( + 'CgVWYWx1ZRIUCgV2YWx1ZRgBIAEoCVIFdmFsdWU='); + +@$core.Deprecated('Use keyValueDescriptor instead') +const KeyValue$json = { + '1': 'KeyValue', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'}, + {'1': 'namespace', '3': 3, '4': 1, '5': 9, '10': 'namespace'}, + ], +}; + +/// Descriptor for `KeyValue`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List keyValueDescriptor = $convert.base64Decode( + 'CghLZXlWYWx1ZRIQCgNrZXkYASABKAlSA2tleRIUCgV2YWx1ZRgCIAEoCVIFdmFsdWUSHAoJbm' + 'FtZXNwYWNlGAMgASgJUgluYW1lc3BhY2U='); + +@$core.Deprecated('Use subtreeInfoDescriptor instead') +const SubtreeInfo$json = { + '1': 'SubtreeInfo', + '2': [ + {'1': 'node', '3': 1, '4': 1, '5': 9, '10': 'node'}, + {'1': 'layers', '3': 2, '4': 1, '5': 5, '9': 0, '10': 'layers', '17': true}, + {'1': 'namespace', '3': 3, '4': 1, '5': 9, '10': 'namespace'}, + ], + '8': [ + {'1': '_layers'}, + ], +}; + +/// Descriptor for `SubtreeInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subtreeInfoDescriptor = $convert.base64Decode( + 'CgtTdWJ0cmVlSW5mbxISCgRub2RlGAEgASgJUgRub2RlEhsKBmxheWVycxgCIAEoBUgAUgZsYX' + 'llcnOIAQESHAoJbmFtZXNwYWNlGAMgASgJUgluYW1lc3BhY2VCCQoHX2xheWVycw=='); + +@$core.Deprecated('Use destroyArgumentsDescriptor instead') +const DestroyArguments$json = { + '1': 'DestroyArguments', +}; + +/// Descriptor for `DestroyArguments`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List destroyArgumentsDescriptor = $convert.base64Decode( + 'ChBEZXN0cm95QXJndW1lbnRz'); + +@$core.Deprecated('Use standardResponseDescriptor instead') +const StandardResponse$json = { + '1': 'StandardResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + {'1': 'message', '3': 2, '4': 1, '5': 9, '10': 'message'}, + ], +}; + +/// Descriptor for `StandardResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List standardResponseDescriptor = $convert.base64Decode( + 'ChBTdGFuZGFyZFJlc3BvbnNlEhgKB3N1Y2Nlc3MYASABKAhSB3N1Y2Nlc3MSGAoHbWVzc2FnZR' + 'gCIAEoCVIHbWVzc2FnZQ=='); + +@$core.Deprecated('Use readResponseDescriptor instead') +const ReadResponse$json = { + '1': 'ReadResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + {'1': 'message', '3': 2, '4': 1, '5': 9, '10': 'message'}, + {'1': 'result', '3': 3, '4': 1, '5': 9, '10': 'result'}, + ], +}; + +/// Descriptor for `ReadResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List readResponseDescriptor = $convert.base64Decode( + 'CgxSZWFkUmVzcG9uc2USGAoHc3VjY2VzcxgBIAEoCFIHc3VjY2VzcxIYCgdtZXNzYWdlGAIgAS' + 'gJUgdtZXNzYWdlEhYKBnJlc3VsdBgDIAEoCVIGcmVzdWx0'); + +@$core.Deprecated('Use listResponseDescriptor instead') +const ListResponse$json = { + '1': 'ListResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + {'1': 'message', '3': 2, '4': 1, '5': 9, '10': 'message'}, + {'1': 'result', '3': 3, '4': 3, '5': 9, '10': 'result'}, + ], +}; + +/// Descriptor for `ListResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List listResponseDescriptor = $convert.base64Decode( + 'CgxMaXN0UmVzcG9uc2USGAoHc3VjY2VzcxgBIAEoCFIHc3VjY2VzcxIYCgdtZXNzYWdlGAIgAS' + 'gJUgdtZXNzYWdlEhYKBnJlc3VsdBgDIAMoCVIGcmVzdWx0'); + diff --git a/protos/lib/storage-api.dart b/protos/lib/storage-api.dart new file mode 100644 index 0000000..eccfb04 --- /dev/null +++ b/protos/lib/storage-api.dart @@ -0,0 +1,8 @@ +library storage_api; + +export 'src/generated/storage_api.pb.dart'; +export 'src/generated/storage_api.pbenum.dart'; +export 'src/generated/storage_api.pbgrpc.dart'; +export 'src/generated/storage_api.pbjson.dart'; + +export 'package:grpc/grpc.dart';
\ No newline at end of file diff --git a/protos/protos/storage_api.proto b/protos/protos/storage_api.proto new file mode 100644 index 0000000..90272e8 --- /dev/null +++ b/protos/protos/storage_api.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package storage_api; + +service Database { + // Deletes the entire data base. + rpc DestroyDB(DestroyArguments) returns (StandardResponse); + + // Writes a key-value pair to the data base + rpc Write(KeyValue) returns (StandardResponse); + + // Reads the value for the given key from the data base. + rpc Read(Key) returns (ReadResponse); + + // Deletes the entry for the given key from the data base. + rpc Delete(Key) returns (StandardResponse); + + // Lists any keys that contain the given string. + rpc Search(Key) returns (ListResponse); + + // Deletes all keys in subtree of given root. Assumes that keys follow VSS-like tress structure. + rpc DeleteNodes(Key) returns (StandardResponse); + + // Lists all nodes in subtree of given root and depth. Assumes that keys follow VSS-like tress structure. + rpc ListNodes(SubtreeInfo) returns (ListResponse); +} + +message Key { + string key = 1; + string namespace = 2; +} + +message Value { + string value = 1; +} + +message KeyValue { + string key = 1; + string value = 2; + string namespace = 3; +} + +message SubtreeInfo { + string node = 1; + optional int32 layers = 2; + string namespace = 3; +} + +message DestroyArguments {} + +message StandardResponse { + bool success = 1; + string message = 2; +} + +message ReadResponse { + bool success = 1; + string message = 2; + string result = 3; +} + +message ListResponse { + bool success = 1; + string message = 2; + repeated string result = 3; +} diff --git a/test/StorageClient_test.dart b/test/StorageClient_test.dart new file mode 100644 index 0000000..1c36809 --- /dev/null +++ b/test/StorageClient_test.dart @@ -0,0 +1,557 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:protos/storage-api.dart' as storage_api; +import 'package:flutter_ics_homescreen/export.dart'; + +import 'package:flutter_ics_homescreen/data/data_providers/storage_client.dart'; + +// Mock implementation of Ref if necessary. +class MockRef extends Ref { + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +void main() { + late StorageClient storageClient; + + // Default namespace. + late storage_api.Key keyEmpty; + late storage_api.Key key1; + late storage_api.Key key2; + late storage_api.Key key3; + late storage_api.Key key4; + late storage_api.Key keyPartialInfotainment; + late storage_api.Key keyPartialVehicle; + + late storage_api.KeyValue keyValueEmpty; + late storage_api.KeyValue keyValue1; + late storage_api.KeyValue keyValue2; + late storage_api.KeyValue keyValue3; + late storage_api.KeyValue keyValue4; + + // Non-default "testSpace" namespace (TS) + late storage_api.Key key1TS; + late storage_api.Key key2TS; + late storage_api.Key key3TS; + late storage_api.Key key4TS; + late storage_api.Key key4PartialInfotainmentTS; + late storage_api.Key keyPartialVehicleTS; + + late storage_api.KeyValue keyValue1TS; + late storage_api.KeyValue keyValue2TS; + late storage_api.KeyValue keyValue3TS; + late storage_api.KeyValue keyValue4TS; + + + setUp(() { + storageClient = StorageClient( + config: StorageConfig.defaultConfig(), + ref: MockRef(), + ); + + // Default nameSpace. + keyEmpty = storage_api.Key(key: ''); + keyValueEmpty = storage_api.KeyValue(key: '', value: 'valueEmpty'); + + key1 = storage_api.Key(key: 'Vehicle.Infotainment.Radio.CurrentStation'); + keyValue1 = storage_api.KeyValue() + ..key = 'Vehicle.Infotainment.Radio.CurrentStation' + ..value = 'testStation'; + + key2 = storage_api.Key(key: 'Vehicle.Infotainment.Radio.Volume'); + keyValue2 = storage_api.KeyValue() + ..key = 'Vehicle.Infotainment.Radio.Volume' + ..value = '42'; + + key3 = storage_api.Key(key: 'Vehicle.Infotainment.HVAC.OutdoorTemperature'); + keyValue3 = storage_api.KeyValue() + ..key = 'Vehicle.Infotainment.HVAC.OutdoorTemperature' + ..value = '17'; + + key4 = storage_api.Key(key: 'Vehicle.Communication.Radio.Volume'); + keyValue4 = storage_api.KeyValue() + ..key = 'Vehicle.Communication.Radio.Volume' + ..value = '40'; + + keyPartialInfotainment = storage_api.Key(key: 'Vehicle.Infotainment'); + keyPartialVehicle = storage_api.Key(key: 'Vehicle'); + + + // Non-default "testSpace" namespace (TS) + key1TS = storage_api.Key(key: 'VehicleTS.Infotainment.Radio.CurrentStation', namespace: 'testSpace'); + keyValue1TS = storage_api.KeyValue() + ..key = 'VehicleTS.Infotainment.Radio.CurrentStation' + ..value = 'testStationTS' + ..namespace= 'testSpace'; + + key2TS = storage_api.Key(key: 'VehicleTS.Infotainment.Radio.Volume', namespace: 'testSpace'); + keyValue2TS = storage_api.KeyValue() + ..key = 'VehicleTS.Infotainment.Radio.Volume' + ..value = '42TS' + ..namespace = 'testSpace'; + + key3TS = storage_api.Key(key: 'VehicleTS.Infotainment.HVAC.OutdoorTemperature', namespace: 'testSpace'); + keyValue3TS = storage_api.KeyValue() + ..key = 'VehicleTS.Infotainment.HVAC.OutdoorTemperature' + ..value = '17TS' + ..namespace = 'testSpace'; + + key4TS = storage_api.Key(key: 'VehicleTS.Communication.Radio.Volume', namespace: 'testSpace'); + keyValue4TS = storage_api.KeyValue() + ..key = 'VehicleTS.Communication.Radio.Volume' + ..value = '40TS' + ..namespace = 'testSpace'; + + key4PartialInfotainmentTS = storage_api.Key(key: 'VehicleTS.Infotainment', namespace: 'testSpace'); + keyPartialVehicleTS = storage_api.Key(key: 'VehicleTS', namespace: 'testSpace'); + }); + + // PrepareTree. + Future prepareTree() async{ + await storageClient.destroyDB(); + + // Default namespace. + await storageClient.write(keyValue1); + await storageClient.write(keyValue2); + await storageClient.write(keyValue3); + await storageClient.write(keyValue4); + + // Non-default "testSpace" namespace + await storageClient.write(keyValue1TS); + await storageClient.write(keyValue2TS); + await storageClient.write(keyValue3TS); + await storageClient.write(keyValue4TS); + } + test('prepareTree ', () async{ + await prepareTree(); + // Default namespace. + final readResponse1 = await storageClient.read(key1); + final readResponse2 = await storageClient.read(key2); + final readResponse3 = await storageClient.read(key3); + final readResponse4 = await storageClient.read(key4); + + // Non-default "testSpace" namespace + final readResponse1TS = await storageClient.read(key1TS); + final readResponse2TS = await storageClient.read(key2TS); + final readResponse3TS = await storageClient.read(key3TS); + final readResponse4TS = await storageClient.read(key4TS); + + // Default namespace. + expect(readResponse1, isA<storage_api.ReadResponse>()); + expect(readResponse1.success, isTrue); + expect(readResponse1.result, equals(keyValue1.value)); + + expect(readResponse2, isA<storage_api.ReadResponse>()); + expect(readResponse2.success, isTrue); + expect(readResponse2.result, equals(keyValue2.value)); + + expect(readResponse3, isA<storage_api.ReadResponse>()); + expect(readResponse3.success, isTrue); + expect(readResponse3.result, equals(keyValue3.value)); + + expect(readResponse4, isA<storage_api.ReadResponse>()); + expect(readResponse4.success, isTrue); + expect(readResponse4.result, equals(keyValue4.value)); + + // Non-default "testSpace" namespace + expect(readResponse1TS, isA<storage_api.ReadResponse>()); + expect(readResponse1TS.success, isTrue); + expect(readResponse1TS.result, equals(keyValue1TS.value)); + + expect(readResponse2TS, isA<storage_api.ReadResponse>()); + expect(readResponse2TS.success, isTrue); + expect(readResponse2TS.result, equals(keyValue2TS.value)); + + expect(readResponse3TS, isA<storage_api.ReadResponse>()); + expect(readResponse3TS.success, isTrue); + expect(readResponse3TS.result, equals(keyValue3TS.value)); + + expect(readResponse4TS, isA<storage_api.ReadResponse>()); + expect(readResponse4TS.success, isTrue); + expect(readResponse4TS.result, equals(keyValue4TS.value)); + await storageClient.destroyDB(); + }); + + // Read and Write. + test('write and read node', () async { + await storageClient.destroyDB(); + // Act. + await storageClient.write(keyValue1); + final readResponse = await storageClient.read(key1); + + // Assert. + expect(readResponse, isA<storage_api.ReadResponse>()); // Checks if object is of right instance + expect(readResponse.success, isTrue); // Checks if read was successful + expect(readResponse.result, equals(keyValue1.value)); // Checks the result value + await storageClient.destroyDB(); + }); + + test('write and read node (non-default namespace) ', () async { + await storageClient.destroyDB(); + await storageClient.write(keyValue1TS); + final readResponse = await storageClient.read(key1TS); + + expect(readResponse, isA<storage_api.ReadResponse>()); + expect(readResponse.success, isTrue); + expect(readResponse.result, equals(keyValue1TS.value)); + await storageClient.destroyDB(); + }); + + test('write and read root', () async { + await storageClient.destroyDB(); + final storage_api.Key keyRoot = storage_api.Key(key: 'Vehicle'); + final storage_api.KeyValue keyValueRoot = storage_api.KeyValue(key: 'Vehicle', value: 'Car1'); + + await storageClient.write(keyValueRoot); + final readResponse = await storageClient.read(keyRoot); + + expect(readResponse, isA<storage_api.ReadResponse>()); + expect(readResponse.success, isTrue); + expect(readResponse.result, equals(keyValueRoot.value)); + await storageClient.destroyDB(); + }); + + test('write key with space', () async { + await storageClient.destroyDB(); + final storage_api.Key keySpace = storage_api.Key(key: 'My Vehicle'); + final storage_api.KeyValue keyValueSpace = storage_api.KeyValue(key: 'My Vehicle', value: 'my Car'); + + await storageClient.write(keyValueSpace); + final readResponse = await storageClient.read(keySpace); + + expect(readResponse, isA<storage_api.ReadResponse>()); + expect(readResponse.success, isTrue); + expect(readResponse.result, equals(keyValueSpace.value)); + await storageClient.destroyDB(); + }); + + test('write with empty key', () async{ + await storageClient.destroyDB(); + final writeResponse = await storageClient.write(keyValueEmpty); + expect(writeResponse.success, isFalse); + expect(writeResponse.message, 'Error when trying to write key \'\' and value \'${keyValueEmpty.value}\': Key cannot be empty string.'); + await storageClient.destroyDB(); + }); + + test('read non-existent key', () async{ + await storageClient.destroyDB(); + final writeResponse = await storageClient.read(storage_api.Key(key: 'Key.Absent')); + expect(writeResponse.success, isFalse); + expect(writeResponse.result, ''); + await storageClient.destroyDB(); + }); + + // Search. + test('search substring: Vehicle.Infotainment.Radio', () async { + await prepareTree(); + + final searchResponse = await storageClient.search(storage_api.Key(key: 'Vehicle.Infotainment.Radio')); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + expect(searchResponse.result, [ + 'Vehicle.Infotainment.Radio.CurrentStation', + 'Vehicle.Infotainment.Radio.Volume']); + await storageClient.destroyDB(); + }); + + test('search substring: Vehicle.Infotainment.Radio (non-default namespace)', () async { + await prepareTree(); + + final searchResponse = await storageClient.search(storage_api.Key(key: 'VehicleTS.Infotainment.Radio', namespace: 'testSpace')); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + expect(searchResponse.result, [ + 'VehicleTS.Infotainment.Radio.CurrentStation', + 'VehicleTS.Infotainment.Radio.Volume']); + await storageClient.destroyDB(); + }); + + test('search substring: Radio', () async { + await prepareTree(); + + final searchResponse = await storageClient.search(storage_api.Key(key: 'Radio')); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + expect(searchResponse.result, [ + 'Vehicle.Communication.Radio.Volume', + 'Vehicle.Infotainment.Radio.CurrentStation', + 'Vehicle.Infotainment.Radio.Volume' + ]); + await storageClient.destroyDB(); + }); + + test('search full key', () async{ + await storageClient.destroyDB(); + await storageClient.write(keyValue1); + + final searchResponse = await storageClient.search(storage_api.Key(key: 'Vehicle.Infotainment.Radio.CurrentStation')); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + + final keyList = searchResponse.result; + expect(keyList, contains('Vehicle.Infotainment.Radio.CurrentStation')); + await storageClient.destroyDB(); + }); + + test('search empty key', () async{ + await prepareTree(); + final searchResponse = await storageClient.search(keyEmpty); + + expect(searchResponse.success, isTrue); + // Every element of the namespace is returned. + expect((searchResponse.result), [ + 'Vehicle.Communication.Radio.Volume', + 'Vehicle.Infotainment.HVAC.OutdoorTemperature', + 'Vehicle.Infotainment.Radio.CurrentStation', + 'Vehicle.Infotainment.Radio.Volume' + ]); + await storageClient.destroyDB(); + }); + + // Delete. + test('delete verification of a single key with search ', () async { + await storageClient.destroyDB(); + await storageClient.write(keyValue1); + + // Check before deletion. + final searchResponseBeforeDelete = await storageClient.search(storage_api.Key(key: 'Vehicle.Infotainment.Radio.CurrentStation')); + expect(searchResponseBeforeDelete, isA<storage_api.ListResponse>()); + expect(searchResponseBeforeDelete.success, isTrue); + + final keyListBeforeDelete = searchResponseBeforeDelete.result; + expect(keyListBeforeDelete, contains('Vehicle.Infotainment.Radio.CurrentStation')); + + // Deletion. + await storageClient.delete(storage_api.Key(key: 'Vehicle.Infotainment.Radio.CurrentStation')); + + // Check after deletion. + final searchResponseAfterDelete = await storageClient.search(storage_api.Key(key: 'Vehicle.Infotainment.Radio.CurrentStation')); + expect(searchResponseAfterDelete, isA<storage_api.ListResponse>()); + expect(searchResponseAfterDelete.success, isTrue); + + final keyListAfterDelete = searchResponseAfterDelete.result; + expect(keyListAfterDelete, isNot(contains('Vehicle.Infotainment.Radio.CurrentStation'))); + }); + + test('delete verification of a single key with search (non-default namespace)', () async { + await storageClient.destroyDB(); + await storageClient.write(keyValue1TS); + + // Check before deletion. + final searchResponseBeforeDelete = await storageClient.search(storage_api.Key(key: 'VehicleTS.Infotainment.Radio.CurrentStation', namespace: 'testSpace')); + expect(searchResponseBeforeDelete, isA<storage_api.ListResponse>()); + expect(searchResponseBeforeDelete.success, isTrue); + + final keyListBeforeDelete = searchResponseBeforeDelete.result; + expect(keyListBeforeDelete, contains('VehicleTS.Infotainment.Radio.CurrentStation')); + + // Deletion. + await storageClient.delete(storage_api.Key(key: 'VehicleTS.Infotainment.Radio.CurrentStation', namespace: 'testSpace')); + + // Check after deletion. + final searchResponseAfterDelete = await storageClient.search(storage_api.Key(key: 'VehicleTS.Infotainment.Radio.CurrentStation', namespace: 'testSpace')); + expect(searchResponseAfterDelete, isA<storage_api.ListResponse>()); + expect(searchResponseAfterDelete.success, isTrue); + + final keyListAfterDelete = searchResponseAfterDelete.result; + expect(keyListAfterDelete, isNot(contains('VehicleTS.Infotainment.Radio.CurrentStation'))); + await storageClient.destroyDB(); + }); + + test('delete-key does not exist ', () async{ + await storageClient.destroyDB(); + final deleteResponse = await storageClient.delete(key1); + expect(deleteResponse.success, isFalse); + expect(deleteResponse.message, 'Key \'${key1.key}\' does not exist in namespace \'${key1.namespace}\'!'); + await storageClient.destroyDB(); + }); + + // DestroyDB. + test('DestroyDB', () async{ + await prepareTree(); + await storageClient.destroyDB(); + final searchResponse = await storageClient.search(keyPartialVehicle); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + expect(searchResponse.result, []); + await storageClient.destroyDB(); + }); + + // DeleteNodes. + test('Delete Infotainment node ', () async{ + await prepareTree(); + final deleteNodesResponse = await storageClient.deleteNodes(keyPartialInfotainment); + expect(deleteNodesResponse, isA<storage_api.StandardResponse>()); + expect(deleteNodesResponse.success, isTrue); + + final searchResponse = await storageClient.search(keyPartialVehicle); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + expect(searchResponse.result, [ + 'Vehicle.Communication.Radio.Volume']); + await storageClient.destroyDB(); + }); + + test('Delete Infotainment node (non-default namespace)', () async{ + await prepareTree(); + final deleteNodesResponse = await storageClient.deleteNodes(key4PartialInfotainmentTS); + expect(deleteNodesResponse, isA<storage_api.StandardResponse>()); + expect(deleteNodesResponse.success, isTrue); + + final searchResponse = await storageClient.search(keyPartialVehicleTS); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + expect(searchResponse.result, [ + 'VehicleTS.Communication.Radio.Volume']); + await storageClient.destroyDB(); + }); + + test('Delete Vehicle node ', () async{ + await prepareTree(); + final deleteNodesResponse = await storageClient.deleteNodes(keyPartialVehicle); + expect(deleteNodesResponse, isA<storage_api.StandardResponse>()); + expect(deleteNodesResponse.success, isTrue); + + final searchResponse = await storageClient.search(keyPartialVehicle); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + expect(searchResponse.result, []); + await storageClient.destroyDB(); + }); + + test('Delete node with empty key ', () async{ + await prepareTree(); + final deleteNodesResponse = await storageClient.deleteNodes(keyEmpty); + expect(deleteNodesResponse, isA<storage_api.StandardResponse>()); + expect(deleteNodesResponse.success, isFalse); + + final searchResponse = await storageClient.search(keyPartialVehicle); + expect(searchResponse, isA<storage_api.ListResponse>()); + expect(searchResponse.success, isTrue); + expect(searchResponse.result, [ + 'Vehicle.Communication.Radio.Volume', + 'Vehicle.Infotainment.HVAC.OutdoorTemperature', + 'Vehicle.Infotainment.Radio.CurrentStation', + 'Vehicle.Infotainment.Radio.Volume' + ]); + await storageClient.destroyDB(); + }); + + // ListNodes. + test('listNodes Vehicle layer 1 ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(node:'Vehicle'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isTrue); + expect(listNodesResponse.result, [ + 'Vehicle.Communication', + 'Vehicle.Infotainment']); + await storageClient.destroyDB(); + }); + + test('listNodes Vehicle layer 1 (non-default namespace)', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(node: 'VehicleTS', namespace: 'testSpace'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isTrue); + expect(listNodesResponse.result, [ + 'VehicleTS.Communication', + 'VehicleTS.Infotainment']); + await storageClient.destroyDB(); + }); + + test('listNodes Vehicle layer 2 ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(layers: 2, node: 'Vehicle'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isTrue); + expect(listNodesResponse.result, [ + 'Vehicle.Communication.Radio', + 'Vehicle.Infotainment.HVAC', + 'Vehicle.Infotainment.Radio']); + await storageClient.destroyDB(); + }); + + test('listNodes Vehicle.infotainment layer 1 ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(node: 'Vehicle.Infotainment'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isTrue); + expect(listNodesResponse.result, [ + 'Vehicle.Infotainment.HVAC', + 'Vehicle.Infotainment.Radio']); + await storageClient.destroyDB(); + }); + + test('listNodes Vehicle.infotainment layer -1 ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(layers: -1, node: 'Vehicle.Infotainment'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isFalse); + expect(listNodesResponse.result, []); + await storageClient.destroyDB(); + }); + + test('listNodes node non-existent ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(node: 'Vehicle.ADAS'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isFalse); + expect(listNodesResponse.result, []); + await storageClient.destroyDB(); + }); + + test('listNodes layer too large -> no children ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(layers: 3, node: 'Vehicle.Infotainment'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isTrue); + expect(listNodesResponse.result, []); + await storageClient.destroyDB(); + }); + + test('listNodes empty node ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo( node: ''); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isTrue); + expect(listNodesResponse.result, ['Vehicle']); + await storageClient.destroyDB(); + }); + + test('listNodes Vehicle layer 0 ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(layers: 0, node: 'Vehicle'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isTrue); + expect(listNodesResponse.result, [ + 'Vehicle.Communication.Radio.Volume', + 'Vehicle.Infotainment.HVAC.OutdoorTemperature', + 'Vehicle.Infotainment.Radio.CurrentStation', + 'Vehicle.Infotainment.Radio.Volume' + ]); + await storageClient.destroyDB(); + }); + + test('listNodes Vehicle.Infotainment layer 0 ', () async{ + await prepareTree(); + late storage_api.SubtreeInfo subtreeInfo = storage_api.SubtreeInfo(layers: 0, node: 'Vehicle.Infotainment'); + final listNodesResponse = await storageClient.listNodes(subtreeInfo); + expect(listNodesResponse, isA<storage_api.ListResponse>()); + expect(listNodesResponse.success, isTrue); + expect(listNodesResponse.result, [ + 'Vehicle.Infotainment.HVAC.OutdoorTemperature', + 'Vehicle.Infotainment.Radio.CurrentStation', + 'Vehicle.Infotainment.Radio.Volume' + ]); + await storageClient.destroyDB(); + }); +} |