aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLudwig Schwiedrzik <ludwig.schwiedrzik@d-fine.com>2024-08-13 17:23:45 +0200
committerScott Murray <scott.murray@konsulko.com>2024-09-10 19:52:10 +0000
commita2dcd701777968a65d3176eaf28aa7023d97c16b (patch)
tree65a5861430a1fde81160cd116fd70fba40cd1fb3
parentd3ea8d7fa4518c258fca3c825ee895487fcaa8ec (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.dart47
-rw-r--r--lib/data/data_providers/app_provider.dart13
-rw-r--r--lib/data/data_providers/storage_client.dart89
-rw-r--r--protos/lib/src/generated/storage_api.pb.dart534
-rw-r--r--protos/lib/src/generated/storage_api.pbenum.dart11
-rw-r--r--protos/lib/src/generated/storage_api.pbgrpc.dart179
-rw-r--r--protos/lib/src/generated/storage_api.pbjson.dart126
-rw-r--r--protos/lib/storage-api.dart8
-rw-r--r--protos/protos/storage_api.proto66
-rw-r--r--test/StorageClient_test.dart557
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();
+ });
+}