From 2ba2ec38e0a50f8b642bf2f0a1b00c2b38b7d6b9 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 5 Mar 2021 18:57:09 +0100 Subject: [PATCH] new profile carousel implementation reacts to new profiles --- owrx/property/__init__.py | 6 ++++ owrx/source/__init__.py | 43 ++++++++++++++++--------- test/property/test_property_carousel.py | 16 +++++++++ 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/owrx/property/__init__.py b/owrx/property/__init__.py index 7e96440..d0d92d1 100644 --- a/owrx/property/__init__.py +++ b/owrx/property/__init__.py @@ -351,6 +351,12 @@ class PropertyCarousel(PropertyDelegator): def addLayer(self, key, value): self.layers[key] = value + def hasLayer(self, key): + return key in self.layers + + def removeLayer(self, key): + del self.layers[key] + def switch(self, key): before = self.pm self.subscription.cancel() diff --git a/owrx/source/__init__.py b/owrx/source/__init__.py index 706b576..37a713f 100644 --- a/owrx/source/__init__.py +++ b/owrx/source/__init__.py @@ -10,7 +10,7 @@ import pkgutil from abc import ABC, abstractmethod from owrx.command import CommandMapper from owrx.socket import getAvailablePort -from owrx.property import PropertyStack, PropertyLayer, PropertyFilter, PropertyCarousel +from owrx.property import PropertyStack, PropertyLayer, PropertyFilter, PropertyCarousel, PropertyDeleted from owrx.property.filter import ByLambda from owrx.form import Input, TextInput, NumberInput, CheckboxInput, ModesInput, ExponentialInput from owrx.form.converter import OptionalConverter @@ -61,6 +61,28 @@ class SdrSourceEventClient(ABC): return SdrClientClass.INACTIVE +class SdrProfileCarousel(PropertyCarousel): + def __init__(self, props): + super().__init__() + for profile_id, profile in props["profiles"].items(): + self.addLayer(profile_id, profile) + + props["profiles"].wire(self.handleProfileUpdate) + + def addLayer(self, profile_id, profile): + profile_stack = PropertyStack() + profile_stack.addLayer(0, PropertyLayer(profile_id=profile_id).readonly()) + profile_stack.addLayer(1, profile) + super().addLayer(profile_id, profile_stack) + + def handleProfileUpdate(self, changes): + for profile_id, profile in changes.items(): + if profile is PropertyDeleted: + self.removeLayer(profile_id) + elif not self.hasLayer(profile_id): + self.addLayer(profile_id, profile) + + class SdrSource(ABC): def __init__(self, id, props): self.id = id @@ -70,28 +92,19 @@ class SdrSource(ABC): self.props = PropertyStack() # layer 0 reserved for profile properties - self.profileCarousel = PropertyCarousel() - if "profiles" in props: - for profile_id, profile in props["profiles"].items(): - profile_stack = PropertyStack() - profile_stack.addLayer(0, PropertyLayer(profile_id=profile_id).readonly()) - profile_stack.addLayer(1, profile) - self.profileCarousel.addLayer(profile_id, profile_stack) + self.profileCarousel = SdrProfileCarousel(props) + # prevent profile names from overriding the device name self.props.addLayer(0, PropertyFilter(self.profileCarousel, ByLambda(lambda x: x != "name"))) - # layer for runtime writeable properties - # these may be set during runtime, but should not be persisted to disk with the configuration - self.props.addLayer(1, PropertyLayer().filter("profile_id")) - # props from our device config - self.props.addLayer(2, props) + self.props.addLayer(1, props) # the sdr_id is constant, so we put it in a separate layer # this is used to detect device changes, that are then sent to the client - self.props.addLayer(3, PropertyLayer(sdr_id=id).readonly()) + self.props.addLayer(2, PropertyLayer(sdr_id=id).readonly()) # finally, accept global config properties from the top-level config - self.props.addLayer(4, Config.get()) + self.props.addLayer(3, Config.get()) self.sdrProps = self.props.filter(*self.getEventNames()) diff --git a/test/property/test_property_carousel.py b/test/property/test_property_carousel.py index 6c5ff9f..b6ae264 100644 --- a/test/property/test_property_carousel.py +++ b/test/property/test_property_carousel.py @@ -76,3 +76,19 @@ class PropertyCarouselTest(TestCase): pc = PropertyCarousel() with self.assertRaises(KeyError): pc.switch("doesntmatter") + + def testHasLayer(self): + pc = PropertyCarousel() + pc.addLayer("testkey", PropertyLayer()) + self.assertTrue(pc.hasLayer("testkey")) + self.assertFalse(pc.hasLayer("otherkey")) + + def testRemoveLayer(self): + pc = PropertyCarousel() + pl = PropertyLayer(testkey="testvalue") + pc.addLayer("x", pl) + pc.switch("x") + self.assertEqual(pc["testkey"], "testvalue") + pc.removeLayer("x") + with self.assertRaises(KeyError): + pc.switch("x")