implement property deletion handling; activate scheduler deletion
This commit is contained in:
parent
91c4d6f568
commit
412e0a51c7
@ -1,13 +1,11 @@
|
||||
from owrx.config.core import CoreConfig
|
||||
from owrx.config.migration import Migrator
|
||||
from owrx.property import PropertyLayer
|
||||
from owrx.property import PropertyLayer, PropertyDeleted
|
||||
from owrx.jsons import Encoder
|
||||
import json
|
||||
|
||||
|
||||
class DynamicConfig(PropertyLayer):
|
||||
_deleted = object()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
try:
|
||||
@ -43,12 +41,12 @@ class DynamicConfig(PropertyLayer):
|
||||
file.write(jsonContent)
|
||||
|
||||
def __delitem__(self, key):
|
||||
self.__setitem__(key, DynamicConfig._deleted)
|
||||
self.__setitem__(key, PropertyDeleted)
|
||||
|
||||
def __contains__(self, item):
|
||||
if not super().__contains__(item):
|
||||
return False
|
||||
if super().__getitem__(item) is DynamicConfig._deleted:
|
||||
if super().__getitem__(item) is PropertyDeleted:
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -58,7 +56,7 @@ class DynamicConfig(PropertyLayer):
|
||||
raise KeyError('Key "{key}" does not exist'.format(key=item))
|
||||
|
||||
def __dict__(self):
|
||||
return {k: v for k, v in super().__dict__().items() if v is not DynamicConfig._deleted}
|
||||
return {k: v for k, v in super().__dict__().items() if v is not PropertyDeleted}
|
||||
|
||||
def keys(self):
|
||||
return [k for k in super().keys() if self.__contains__(k)]
|
||||
|
@ -9,7 +9,7 @@ from owrx.version import openwebrx_version
|
||||
from owrx.bands import Bandplan
|
||||
from owrx.bookmarks import Bookmarks
|
||||
from owrx.map import Map
|
||||
from owrx.property import PropertyStack
|
||||
from owrx.property import PropertyStack, PropertyDeleted
|
||||
from owrx.modes import Modes, DigitalMode
|
||||
from owrx.config import Config
|
||||
from owrx.waterfall import WaterfallOptions
|
||||
@ -176,7 +176,8 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient):
|
||||
if changes is None:
|
||||
config = configProps.__dict__()
|
||||
else:
|
||||
config = changes
|
||||
# transform deletions into Nones
|
||||
config = {k: v if v is not PropertyDeleted else None for k, v in changes.items()}
|
||||
if (
|
||||
(changes is None or "start_freq" in changes or "center_freq" in changes)
|
||||
and "start_freq" in configProps
|
||||
|
@ -10,6 +10,15 @@ class PropertyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PropertyDeletion(object):
|
||||
pass
|
||||
|
||||
|
||||
# a special object that will be sent in events when a deletion occured
|
||||
# it can also represent deletion of a key in internal storage, but should not be return from standard dict apis
|
||||
PropertyDeleted = PropertyDeletion()
|
||||
|
||||
|
||||
class Subscription(object):
|
||||
def __init__(self, subscriptee, name, subscriber):
|
||||
self.subscriptee = subscriptee
|
||||
@ -126,7 +135,8 @@ class PropertyLayer(PropertyManager):
|
||||
return {k: v for k, v in self.properties.items()}
|
||||
|
||||
def __delitem__(self, key):
|
||||
return self.properties.__delitem__(key)
|
||||
self.properties.__delitem__(key)
|
||||
self._fireCallbacks({key: PropertyDeleted})
|
||||
|
||||
def keys(self):
|
||||
return self.properties.keys()
|
||||
@ -273,7 +283,7 @@ class PropertyStack(PropertyManager):
|
||||
if self[key] != pm[key]:
|
||||
changes[key] = self[key]
|
||||
else:
|
||||
changes[key] = None
|
||||
changes[key] = PropertyDeleted
|
||||
return changes
|
||||
|
||||
def replaceLayer(self, priority: int, pm: PropertyManager):
|
||||
@ -289,15 +299,21 @@ class PropertyStack(PropertyManager):
|
||||
|
||||
def receiveEvent(self, layer, changes):
|
||||
changesToForward = {name: value for name, value in changes.items() if layer == self._getTopLayer(name)}
|
||||
self._fireCallbacks(changesToForward)
|
||||
# deletions need to be handled separately: only send them if deleted in all layers
|
||||
deletionsToForward = {
|
||||
name: value
|
||||
for name, value in changes.items()
|
||||
if value is PropertyDeleted and self._getTopLayer(name, False) is None
|
||||
}
|
||||
self._fireCallbacks({**changesToForward, **deletionsToForward})
|
||||
|
||||
def _getTopLayer(self, item):
|
||||
def _getTopLayer(self, item, fallback=True):
|
||||
layers = [la["props"] for la in sorted(self.layers, key=lambda l: l["priority"])]
|
||||
for m in layers:
|
||||
if item in m:
|
||||
return m
|
||||
# return top layer by default
|
||||
if layers:
|
||||
# return top layer as fallback
|
||||
if fallback and layers:
|
||||
return layers[0]
|
||||
|
||||
def __getitem__(self, item):
|
||||
|
@ -213,7 +213,8 @@ class ServiceScheduler(SdrSourceEventClient):
|
||||
props = self.source.getProps()
|
||||
props.filter("center_freq", "samp_rate").wire(self.onFrequencyChange)
|
||||
props.wireProperty("scheduler", self.parseSchedule)
|
||||
self.parseSchedule()
|
||||
# wireProperty calls parseSchedule with the initial value
|
||||
# self.parseSchedule()
|
||||
|
||||
def parseSchedule(self, *args):
|
||||
props = self.source.getProps()
|
||||
@ -259,6 +260,12 @@ class ServiceScheduler(SdrSourceEventClient):
|
||||
if self.source.hasClients(SdrClientClass.USER):
|
||||
logger.debug("source has active users; not touching")
|
||||
return
|
||||
|
||||
if self.schedule is None:
|
||||
logger.debug("no active schedule. releasing source...")
|
||||
self.source.removeClient(self)
|
||||
return
|
||||
|
||||
logger.debug("source seems to be idle, selecting profile for background services")
|
||||
entry = self.schedule.getCurrentEntry()
|
||||
|
||||
@ -269,6 +276,7 @@ class ServiceScheduler(SdrSourceEventClient):
|
||||
nextEntry = self.schedule.getNextEntry()
|
||||
if nextEntry is not None:
|
||||
self.scheduleSelection(nextEntry.getNextActivation())
|
||||
else:
|
||||
logger.debug("no next entry available, scheduler standing by for external events.")
|
||||
return
|
||||
|
||||
|
@ -314,10 +314,10 @@ class SdrSource(ABC):
|
||||
self.setBusyState(SdrBusyState.BUSY if hasUsers else SdrBusyState.IDLE)
|
||||
|
||||
def removeClient(self, c: SdrSourceEventClient):
|
||||
try:
|
||||
if c not in self.clients:
|
||||
return
|
||||
|
||||
self.clients.remove(c)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
hasUsers = self.hasClients(SdrClientClass.USER)
|
||||
self.setBusyState(SdrBusyState.BUSY if hasUsers else SdrBusyState.IDLE)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock
|
||||
from owrx.property import PropertyLayer, PropertyFilter
|
||||
from owrx.property import PropertyLayer, PropertyFilter, PropertyDeleted
|
||||
|
||||
|
||||
class PropertyFilterTest(TestCase):
|
||||
@ -70,3 +70,13 @@ class PropertyFilterTest(TestCase):
|
||||
with self.assertRaises(KeyError):
|
||||
pf["testkey"] = "new value"
|
||||
self.assertEqual(pm["testkey"], "old value")
|
||||
|
||||
def testPropagatesDeletion(self):
|
||||
pm = PropertyLayer(testkey="somevalue")
|
||||
filter_mock = Mock()
|
||||
filter_mock.apply.return_value = True
|
||||
pf = PropertyFilter(pm, filter_mock)
|
||||
mock = Mock()
|
||||
pf.wire(mock.method)
|
||||
del pf["testkey"]
|
||||
mock.method.assert_called_once_with({"testkey": PropertyDeleted})
|
||||
|
@ -1,4 +1,4 @@
|
||||
from owrx.property import PropertyLayer
|
||||
from owrx.property import PropertyLayer, PropertyDeleted
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock
|
||||
|
||||
@ -67,3 +67,27 @@ class PropertyLayerTest(TestCase):
|
||||
pm.wire(mock.method)
|
||||
pm["testkey"] = "testvalue"
|
||||
mock.method.assert_not_called()
|
||||
|
||||
def testDeletionIsSent(self):
|
||||
pm = PropertyLayer(testkey="somevalue")
|
||||
mock = Mock()
|
||||
pm.wireProperty("testkey", mock.method)
|
||||
mock.method.reset_mock()
|
||||
del pm["testkey"]
|
||||
mock.method.assert_called_once_with(PropertyDeleted)
|
||||
|
||||
def testDeletionInGeneralWiring(self):
|
||||
pm = PropertyLayer(testkey="somevalue")
|
||||
mock = Mock()
|
||||
pm.wire(mock.method)
|
||||
del pm["testkey"]
|
||||
mock.method.assert_called_once_with({"testkey": PropertyDeleted})
|
||||
|
||||
def testNoDeletionEventWhenPropertyDoesntExist(self):
|
||||
pm = PropertyLayer(otherkey="somevalue")
|
||||
mock = Mock()
|
||||
pm.wireProperty("testkey", mock.method)
|
||||
mock.method.reset_mock()
|
||||
with self.assertRaises(KeyError):
|
||||
del pm["testkey"]
|
||||
mock.method.assert_not_called()
|
||||
|
@ -1,6 +1,6 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import Mock
|
||||
from owrx.property import PropertyLayer, PropertyStack
|
||||
from owrx.property import PropertyLayer, PropertyStack, PropertyDeleted
|
||||
|
||||
|
||||
class PropertyStackTest(TestCase):
|
||||
@ -135,7 +135,7 @@ class PropertyStackTest(TestCase):
|
||||
mock.method.assert_called_once_with("unique value")
|
||||
mock.reset_mock()
|
||||
stack.removeLayer(high_layer)
|
||||
mock.method.assert_called_once_with(None)
|
||||
mock.method.assert_called_once_with(PropertyDeleted)
|
||||
|
||||
def testReplaceLayer(self):
|
||||
first_layer = PropertyLayer()
|
||||
@ -162,7 +162,7 @@ class PropertyStackTest(TestCase):
|
||||
mock = Mock()
|
||||
stack.wire(mock.method)
|
||||
stack.removeLayer(layer)
|
||||
mock.method.assert_called_once_with({"testkey": None})
|
||||
mock.method.assert_called_once_with({"testkey": PropertyDeleted})
|
||||
mock.reset_mock()
|
||||
|
||||
layer["testkey"] = "after"
|
||||
@ -195,3 +195,23 @@ class PropertyStackTest(TestCase):
|
||||
om.addLayer(0, high_pm)
|
||||
om["testkey"] = "new value"
|
||||
self.assertEqual(low_pm["testkey"], "new value")
|
||||
|
||||
def testDeletionEvent(self):
|
||||
ps = PropertyStack()
|
||||
pm = PropertyLayer(testkey="testvalue")
|
||||
ps.addLayer(0, pm)
|
||||
mock = Mock()
|
||||
ps.wire(mock.method)
|
||||
del ps["testkey"]
|
||||
mock.method.assert_called_once_with({"testkey": PropertyDeleted})
|
||||
|
||||
def testDeletionWithSecondLayer(self):
|
||||
ps = PropertyStack()
|
||||
low_pm = PropertyLayer(testkey="testvalue")
|
||||
high_pm = PropertyLayer()
|
||||
ps.addLayer(0, high_pm)
|
||||
ps.addLayer(1, low_pm)
|
||||
mock = Mock()
|
||||
ps.wire(mock.method)
|
||||
del low_pm["testkey"]
|
||||
mock.method.assert_called_once_with({"testkey": PropertyDeleted})
|
||||
|
Loading…
Reference in New Issue
Block a user