mirror of
https://github.com/jketterl/openwebrx.git
synced 2026-03-06 05:24:04 +01:00
implement advanced filtering
This commit is contained in:
parent
d0c3d2efea
commit
1f7673e328
|
|
@ -65,6 +65,26 @@ class BasicTransformation(ActiveListTransformation):
|
|||
return self.transformation(value)
|
||||
|
||||
|
||||
class ActiveListFilter(ABC):
|
||||
@abstractmethod
|
||||
def predicate(self, value) -> bool:
|
||||
pass
|
||||
|
||||
def monitor(self, member, callback: callable):
|
||||
pass
|
||||
|
||||
def unmonitor(self, member):
|
||||
pass
|
||||
|
||||
|
||||
class BasicFilter(ActiveListFilter):
|
||||
def __init__(self, predicate: callable):
|
||||
self.predicate = predicate
|
||||
|
||||
def predicate(self, value) -> bool:
|
||||
return self.predicate(value)
|
||||
|
||||
|
||||
class ActiveListTransformationListener(ActiveListListener):
|
||||
def __init__(self, transformation: ActiveListTransformation, source: "ActiveList", target: "ActiveList"):
|
||||
self.transformation = transformation
|
||||
|
|
@ -92,33 +112,51 @@ class ActiveListTransformationListener(ActiveListListener):
|
|||
|
||||
|
||||
class ActiveListFilterListener(ActiveListListener):
|
||||
def __init__(self, filter: callable, keyMap: list, target: "ActiveList"):
|
||||
def __init__(self, filter: ActiveListFilter, source: "ActiveList", target: "ActiveList"):
|
||||
self.filter = filter
|
||||
self.keyMap = keyMap
|
||||
self.source = source
|
||||
self.keyMap = [idx for idx, val in enumerate(self.source) if self.filter.predicate(val)]
|
||||
for v in self.source:
|
||||
self.filter.monitor(v, partial(self._onMonitor, v))
|
||||
self.target = target
|
||||
|
||||
def onListChange(self, source: "ActiveList", changes: list[ActiveListChange]):
|
||||
for change in changes:
|
||||
if isinstance(change, ActiveListIndexAdded):
|
||||
if self.filter(change.newValue):
|
||||
if self.filter.predicate(change.newValue):
|
||||
idx = len([x for x in self.keyMap if x < change.index])
|
||||
self.target.insert(idx, change.newValue)
|
||||
self.keyMap.insert(idx, change.index)
|
||||
self.target.insert(idx, change.newValue)
|
||||
self.filter.monitor(change.newValue, partial(self._onMonitor, change.newValue))
|
||||
elif isinstance(change, ActiveListIndexUpdated):
|
||||
if change.index in self.keyMap and not self.filter(change.newValue):
|
||||
self.filter.unmonitor(change.oldValue)
|
||||
if change.index in self.keyMap and not self.filter.predicate(change.newValue):
|
||||
idx = self.keyMap.index(change.index)
|
||||
del self.target[idx]
|
||||
del self.keyMap[idx]
|
||||
elif change.index not in self.keyMap and self.filter(change.newValue):
|
||||
elif change.index not in self.keyMap and self.filter.predicate(change.newValue):
|
||||
idx = len([x for x in self.keyMap if x < change.index])
|
||||
self.target.insert(idx, change.newValue)
|
||||
self.keyMap.insert(idx, change.index)
|
||||
self.target.insert(idx, change.newValue)
|
||||
self.filter.monitor(change.newValue, partial(self._onMonitor, change.newValue))
|
||||
elif isinstance(change, ActiveListIndexDeleted):
|
||||
self.filter.unmonitor(change.oldValue)
|
||||
if change.index in self.keyMap:
|
||||
idx = self.keyMap.index(change.index)
|
||||
del self.target[idx]
|
||||
del self.keyMap[idx]
|
||||
|
||||
def _onMonitor(self, value):
|
||||
idx = self.source.index(value)
|
||||
if idx in self.keyMap and not self.filter.predicate(value):
|
||||
idx = self.keyMap.index(idx)
|
||||
del self.target[idx]
|
||||
del self.keyMap[idx]
|
||||
elif idx not in self.keyMap and self.filter.predicate(value):
|
||||
newIndex = len([x for x in self.keyMap if x < idx])
|
||||
self.keyMap.insert(newIndex, idx)
|
||||
self.target.insert(newIndex, value)
|
||||
|
||||
|
||||
class ActiveListFlattenListener(ActiveListListener):
|
||||
def __init__(self, source: "ActiveList", target: "ActiveList"):
|
||||
|
|
@ -183,7 +221,7 @@ class ActiveList:
|
|||
self.__fireChanges([ActiveListIndexAppended(len(self) - 1, value)])
|
||||
|
||||
def __fireChanges(self, changes: list[ActiveListChange]):
|
||||
for listener in self.listeners:
|
||||
for listener in self.listeners.copy():
|
||||
try:
|
||||
listener.onListChange(self, changes)
|
||||
except Exception:
|
||||
|
|
@ -206,14 +244,11 @@ class ActiveList:
|
|||
self.addListener(ActiveListTransformationListener(transformation, self, res))
|
||||
return res
|
||||
|
||||
def filter(self, filter: callable):
|
||||
res = ActiveList()
|
||||
keyMap = []
|
||||
for idx, val in enumerate(self):
|
||||
if filter(val):
|
||||
res.append(val)
|
||||
keyMap.append(idx)
|
||||
self.addListener(ActiveListFilterListener(filter, keyMap, res))
|
||||
def filter(self, filter: Union[callable, ActiveListFilter]):
|
||||
if not isinstance(filter, ActiveListFilter):
|
||||
filter = BasicFilter(filter)
|
||||
res = ActiveList([val for val in self if filter.predicate(val)])
|
||||
self.addListener(ActiveListFilterListener(filter, self, res))
|
||||
return res
|
||||
|
||||
def flatten(self):
|
||||
|
|
|
|||
59
owrx/sdr.py
59
owrx/sdr.py
|
|
@ -1,7 +1,7 @@
|
|||
from owrx.config import Config
|
||||
from owrx.source import SdrSource
|
||||
from owrx.feature import FeatureDetector, UnknownFeatureException
|
||||
from owrx.active.list import ActiveListTransformation
|
||||
from owrx.active.list import ActiveListTransformation, ActiveListFilter, ActiveListListener, ActiveList, ActiveListChange
|
||||
|
||||
import logging
|
||||
|
||||
|
|
@ -12,36 +12,53 @@ class ProfileNameMapper(ActiveListTransformation):
|
|||
def __init__(self, source_id, source_name):
|
||||
self.source_id = source_id
|
||||
self.source_name = source_name
|
||||
self.subscriptions = []
|
||||
self.subscriptions = {}
|
||||
|
||||
def transform(self, profile):
|
||||
return {"id": "{}|{}".format(self.source_id, profile["id"]), "name": "{} {}".format(self.source_name, profile["name"])}
|
||||
|
||||
def monitor(self, profile, callback: callable):
|
||||
self.subscriptions.append(profile.filter("name").wire(lambda _: callback()))
|
||||
self.subscriptions[id(profile)] = profile.filter("name").wire(lambda _: callback())
|
||||
|
||||
def unmonitor(self, member):
|
||||
affected = [sub for sub in self.subscriptions if sub.subscriptee is member]
|
||||
for sub in affected:
|
||||
sub.cancel()
|
||||
self.subscriptions.remove(sub)
|
||||
def unmonitor(self, profile):
|
||||
self.subscriptions[id(profile)].cancel()
|
||||
|
||||
|
||||
class ProfileMapper(ActiveListTransformation):
|
||||
def __init__(self):
|
||||
self.subscriptions = []
|
||||
self.subscriptions = {}
|
||||
|
||||
def transform(self, source):
|
||||
return source.getProfiles().map(ProfileNameMapper(source.getId(), source.getName()))
|
||||
|
||||
def monitor(self, source, callback: callable):
|
||||
self.subscriptions.append(source.getProps().filter("name").wire(lambda _: callback()))
|
||||
self.subscriptions[id(source)] = source.getProps().filter("name").wire(lambda _: callback())
|
||||
|
||||
def unmonitor(self, member):
|
||||
affected = [sub for sub in self.subscriptions if sub.subscriptee is member]
|
||||
for sub in affected:
|
||||
sub.cancel()
|
||||
self.subscriptions.remove(sub)
|
||||
def unmonitor(self, source):
|
||||
self.subscriptions[id(source)].cancel()
|
||||
|
||||
|
||||
class ProfileChangeListener(ActiveListListener):
|
||||
def __init__(self, callback: callable):
|
||||
self.callback = callback
|
||||
|
||||
def onListChange(self, source: ActiveList, changes: list[ActiveListChange]):
|
||||
self.callback()
|
||||
|
||||
|
||||
class HasProfilesFilter(ActiveListFilter):
|
||||
def __init__(self):
|
||||
self.monitors = {}
|
||||
|
||||
def predicate(self, device) -> bool:
|
||||
return "profiles" in device and device["profiles"] and len(device["profiles"]) > 0
|
||||
|
||||
def monitor(self, device, callback: callable):
|
||||
self.monitors[id(device)] = monitor = ProfileChangeListener(callback)
|
||||
device["profiles"].addListener(monitor)
|
||||
|
||||
def unmonitor(self, device):
|
||||
device["profiles"].removeListener(self.monitors[id(device)])
|
||||
|
||||
|
||||
class SdrService(object):
|
||||
|
|
@ -76,9 +93,6 @@ class SdrService(object):
|
|||
|
||||
@staticmethod
|
||||
def getAllSources():
|
||||
def hasProfiles(device):
|
||||
return "profiles" in device and device["profiles"] and len(device["profiles"]) > 0
|
||||
|
||||
def sdrTypeAvailable(value):
|
||||
featureDetector = FeatureDetector()
|
||||
try:
|
||||
|
|
@ -106,17 +120,16 @@ class SdrService(object):
|
|||
if SdrService.sources is None:
|
||||
SdrService.sources = Config.get()["sdrs"] \
|
||||
.filter(sdrTypeAvailable) \
|
||||
.filter(hasProfiles) \
|
||||
.filter(HasProfilesFilter()) \
|
||||
.map(buildNewSource)
|
||||
return SdrService.sources
|
||||
|
||||
@staticmethod
|
||||
def getActiveSources():
|
||||
def isAvailable(source: SdrSource):
|
||||
return source.isEnabled() and not source.isFailed()
|
||||
|
||||
if SdrService.activeSources is None:
|
||||
SdrService.activeSources = SdrService.getAllSources().filter(isAvailable)
|
||||
SdrService.activeSources = SdrService.getAllSources() \
|
||||
.filter(lambda source: source.isEnabled()) \
|
||||
.filter(lambda source: not source.isFailed())
|
||||
return SdrService.activeSources
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
34
test/owrx/active/list/test_advanced_filter.py
Normal file
34
test/owrx/active/list/test_advanced_filter.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from unittest import TestCase
|
||||
from owrx.active.list import ActiveList, ActiveListFilter
|
||||
|
||||
|
||||
class AdvancedFilter(ActiveListFilter):
|
||||
def __init__(self, result: bool):
|
||||
self.result = result
|
||||
self.callback = None
|
||||
|
||||
def predicate(self, value) -> bool:
|
||||
return self.result
|
||||
|
||||
def monitor(self, member, callback: callable):
|
||||
self.callback = callback
|
||||
|
||||
def trigger(self, newResult: bool):
|
||||
self.result = newResult
|
||||
self.callback()
|
||||
|
||||
|
||||
class AdvancedFilterTest(TestCase):
|
||||
def testAdvancedFilter(self):
|
||||
list = ActiveList([1, 2, 3])
|
||||
filteredList = list.filter(AdvancedFilter(True))
|
||||
self.assertEqual(len(filteredList), 3)
|
||||
filteredList = list.filter(AdvancedFilter(False))
|
||||
self.assertEqual(len(filteredList), 0)
|
||||
|
||||
def testListMonitor(self):
|
||||
list = ActiveList([1, 2, 3])
|
||||
filter = AdvancedFilter(True)
|
||||
filteredList = list.filter(filter)
|
||||
filter.trigger(False)
|
||||
self.assertEqual(len(filteredList), 2)
|
||||
Loading…
Reference in a new issue