diff --git a/example/viam_example_app/ios/Flutter/AppFrameworkInfo.plist b/example/viam_example_app/ios/Flutter/AppFrameworkInfo.plist index 9625e105df3..7c569640062 100644 --- a/example/viam_example_app/ios/Flutter/AppFrameworkInfo.plist +++ b/example/viam_example_app/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/example/viam_example_app/ios/Runner.xcodeproj/project.pbxproj b/example/viam_example_app/ios/Runner.xcodeproj/project.pbxproj index f14dc1888f8..ded87b019f0 100644 --- a/example/viam_example_app/ios/Runner.xcodeproj/project.pbxproj +++ b/example/viam_example_app/ios/Runner.xcodeproj/project.pbxproj @@ -216,7 +216,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { @@ -453,7 +453,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -580,7 +580,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -629,7 +629,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/viam_example_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/viam_example_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a09bea..8e3ca5dfe19 100644 --- a/example/viam_example_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/viam_example_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ 'Address not on local network'; } + +class DuplicateResourceException implements Exception { + final ResourceName name; + + const DuplicateResourceException(this.name); + + @override + String toString() => 'Duplicate registration of resource in manager: $name'; +} + +class ResourceNotFoundException implements Exception { + final ResourceName name; + + const ResourceNotFoundException(this.name); + + @override + String toString() => 'Resource not found in manager: $name'; +} + +class MultipleRemoteResourcesSameNameException implements Exception { + final Iterable names; + + const MultipleRemoteResourcesSameNameException(this.names); + + @override + String toString() => 'Multiple remote resources found with the name ${names.first.localName}: $names'; +} diff --git a/lib/src/resource/base.dart b/lib/src/resource/base.dart index f85b13da002..db2345adc71 100644 --- a/lib/src/resource/base.dart +++ b/lib/src/resource/base.dart @@ -51,7 +51,9 @@ class Subtype { ..namespace = namespace ..type = resourceType ..subtype = resourceSubtype - ..name = name; + ..remotePath.addAll(name.split(':')..removeLast()) + ..name = name + ..localName = name.split(':').last; } @override diff --git a/lib/src/resource/manager.dart b/lib/src/resource/manager.dart index ec6a5ff3225..13b9840dfb2 100644 --- a/lib/src/resource/manager.dart +++ b/lib/src/resource/manager.dart @@ -1,3 +1,6 @@ +import 'package:protobuf/protobuf.dart'; + +import '../exceptions.dart'; import '../gen/common/v1/common.pb.dart'; import 'base.dart'; @@ -5,31 +8,40 @@ import 'base.dart'; class ResourceManager { /// The available resources Map resources = {}; - final Map> _shortToLongName = {}; + final Map> _resourceNamesWithoutRemotes = {}; /// Register a new [Resource] with the manager. void register(ResourceName name, Resource resource) { if (resources.containsKey(name)) { - throw Exception('Duplicate registration of resource in manager'); + throw DuplicateResourceException(name); } - final shortName = name.name.split(':').last; - final names = _shortToLongName[shortName] ?? []; + final rnWithoutRemote = name.deepCopy() + ..remotePath.clear() + ..name = name.localName; + final names = _resourceNamesWithoutRemotes[rnWithoutRemote] ?? []; names.add(name); - _shortToLongName[shortName] = names; + _resourceNamesWithoutRemotes[rnWithoutRemote] = names; resources[name] = resource; } /// Get a resource with the given [ResourceName] T getResource(ResourceName name) { - final resource = resources[name]; - if (resource == null) throw Exception('Resource not found in manager'); + Resource? resource; + if (resources.containsKey(name)) { + resource = resources[name]; + } else { + final resourceNames = _resourceNamesWithoutRemotes[name] ?? []; + // If multiple ResourceNames map to this name-without-remotes, + // that means there are multiple remote resources with this same short name. + // Without any means to disambiguate, we should not select any. + if (resourceNames.length > 1) { + throw MultipleRemoteResourcesSameNameException(resourceNames); + } + if (resourceNames.length == 1) { + resource = resources[resourceNames.first]; + } + } + if (resource == null) throw ResourceNotFoundException(name); return resource as T; } - - /// Get a resource by its name only - T getResourceByName(String name) { - final names = _shortToLongName[name] ?? []; - if (names.isEmpty) throw Exception('Resource not found in manager'); - return getResource(names.first); - } } diff --git a/pubspec.yaml b/pubspec.yaml index f65e1acc9de..dab64efa690 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_webrtc: ^0.11.0 + flutter_webrtc: ^0.12.1+hotfix.1 grpc: ^4.0.1 protobuf: ^3.0.0 image: ^4.0.16 diff --git a/test/unit_test/resource/base_test.dart b/test/unit_test/resource/base_test.dart new file mode 100644 index 00000000000..bf50f67903e --- /dev/null +++ b/test/unit_test/resource/base_test.dart @@ -0,0 +1,30 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:viam_sdk/src/components/sensor/sensor.dart'; + +void main() { + group('Subtype Tests', () { + test('getResourceName', () { + // Local + final localName = 'my-sensor'; + final localRN = Sensor.subtype.getResourceName(localName); + expect(localRN.namespace, Sensor.subtype.namespace); + expect(localRN.type, Sensor.subtype.resourceType); + expect(localRN.subtype, Sensor.subtype.resourceSubtype); + expect(localRN.remotePath, []); + expect(localRN.name, localName); + expect(localRN.localName, localName); + + // Remote + final remoteName = 'my-sensor'; + final remotePath = 'remote2:remote1'; + final fullRemoteName = '$remotePath:$remoteName'; + final remoteRN = Sensor.subtype.getResourceName(fullRemoteName); + expect(remoteRN.namespace, Sensor.subtype.namespace); + expect(remoteRN.type, Sensor.subtype.resourceType); + expect(remoteRN.subtype, Sensor.subtype.resourceSubtype); + expect(remoteRN.remotePath, remotePath.split(':')); + expect(remoteRN.name, fullRemoteName); + expect(remoteRN.localName, remoteName); + }); + }); +} diff --git a/test/unit_test/resource/manager_test.dart b/test/unit_test/resource/manager_test.dart new file mode 100644 index 00000000000..b676a1ac8cb --- /dev/null +++ b/test/unit_test/resource/manager_test.dart @@ -0,0 +1,85 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:viam_sdk/src/components/sensor/sensor.dart'; +import 'package:viam_sdk/src/exceptions.dart'; +import 'package:viam_sdk/src/resource/manager.dart'; + +import '../components/sensor_test.dart'; + +void main() { + group('ResourceManager Tests', () { + group('getResource', () { + test('Local', () { + final localName = 'local-sensor'; + final localRN = Sensor.getResourceName(localName); + final localResource = FakeSensor(localName); + final manager = ResourceManager(); + manager.register(localRN, localResource); + + expect(manager.getResource(Sensor.getResourceName(localName)), localResource); + }); + + test('Remote', () { + final remoteName = 'remote-sensor'; + final remotePath = 'remote2:remote1'; + final fullRemoteName = '$remotePath:$remoteName'; + final remoteRN = Sensor.subtype.getResourceName(fullRemoteName); + final remoteResource = FakeSensor(fullRemoteName); + final manager = ResourceManager(); + manager.register(remoteRN, remoteResource); + + // Works with full name + expect(manager.getResource(Sensor.getResourceName(fullRemoteName)), remoteResource); + + // Works with short name -- no collisions + expect(manager.getResource(Sensor.getResourceName(remoteName)), remoteResource); + }); + + test('Local and Remote - Same Names', () { + final manager = ResourceManager(); + + final localName = 'my-sensor'; + final localRN = Sensor.getResourceName(localName); + final localResource = FakeSensor(localName); + + final remoteName = 'my-sensor'; + final remotePath = 'remote2:remote1'; + final fullRemoteName = '$remotePath:$remoteName'; + final remoteRN = Sensor.subtype.getResourceName(fullRemoteName); + final remoteResource = FakeSensor(fullRemoteName); + + manager.register(localRN, localResource); + manager.register(remoteRN, remoteResource); + + // When using fully qualified names, it should return appropriately + expect(manager.getResource(Sensor.getResourceName(localName)), localResource); + expect(manager.getResource(Sensor.getResourceName(fullRemoteName)), remoteResource); + + // When using just `my-sensor`, it should always return the local + expect(manager.getResource(Sensor.getResourceName(localName)), localResource); + expect(manager.getResource(Sensor.getResourceName(remoteName)), localResource); + }); + + test('Multiple Remotes - Same Names', () { + final remoteName = 'my-sensor'; + + final remotePath1 = 'remote2:remote1'; + final fullRemoteName1 = '$remotePath1:$remoteName'; + final remoteRN1 = Sensor.subtype.getResourceName(fullRemoteName1); + final remoteResource1 = FakeSensor(fullRemoteName1); + + final remotePath2 = 'remote4:remote3'; + final fullRemoteName2 = '$remotePath2:$remoteName'; + final remoteRN2 = Sensor.subtype.getResourceName(fullRemoteName2); + final remoteResource2 = FakeSensor(fullRemoteName2); + + final manager = ResourceManager(); + manager.register(remoteRN1, remoteResource1); + manager.register(remoteRN2, remoteResource2); + + // Error when using short name only + expect(() => manager.getResource(Sensor.getResourceName(remoteName)), + throwsA(isA())); + }); + }); + }); +}