split config and property code, first test

This commit is contained in:
Jakob Ketterl 2020-03-21 22:40:39 +01:00
parent 7948b7bfa1
commit 541c38151f
24 changed files with 210 additions and 193 deletions

View File

@ -1,6 +1,6 @@
from http.server import HTTPServer
from owrx.http import RequestHandler
from owrx.config import PropertyManager, Config
from owrx.config import Config
from owrx.feature import FeatureDetector
from owrx.sdr import SdrService
from socketserver import ThreadingMixIn
@ -36,7 +36,7 @@ Support and info: https://groups.io/g/openwebrx
logger.info("OpenWebRX version {0} starting up...".format(openwebrx_version))
pm = PropertyManager.getSharedInstance().loadConfig()
pm = Config.get()
configErrors = Config.validateConfig()
if configErrors:

View File

@ -1,4 +1,4 @@
from owrx.config import PropertyManager
from owrx.config import Config
from owrx.metrics import Metrics, DirectMetric
import threading
@ -33,7 +33,7 @@ class ClientRegistry(object):
c.write_clients(n)
def addClient(self, client):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
if len(self.clients) >= pm["max_clients"]:
raise TooManyClientsException()
self.clients.append(client)

View File

@ -1,155 +1,15 @@
from owrx.property import PropertyManager
import importlib.util
import logging
import os
import logging
logger = logging.getLogger(__name__)
class Subscription(object):
def __init__(self, subscriptee, subscriber):
self.subscriptee = subscriptee
self.subscriber = subscriber
def call(self, *args, **kwargs):
self.subscriber(*args, **kwargs)
def cancel(self):
self.subscriptee.unwire(self)
class Property(object):
def __init__(self, value=None):
self.value = value
self.subscribers = []
def getValue(self):
return self.value
def setValue(self, value):
if self.value == value:
return self
self.value = value
for c in self.subscribers:
try:
c.call(self.value)
except Exception as e:
logger.exception(e)
return self
def wire(self, callback):
sub = Subscription(self, callback)
self.subscribers.append(sub)
if not self.value is None:
sub.call(self.value)
return sub
def unwire(self, sub):
try:
self.subscribers.remove(sub)
except ValueError:
# happens when already removed before
pass
return self
class ConfigNotFoundException(Exception):
pass
class PropertyManager(object):
sharedInstance = None
@staticmethod
def getSharedInstance():
if PropertyManager.sharedInstance is None:
PropertyManager.sharedInstance = PropertyManager()
return PropertyManager.sharedInstance
def collect(self, *props):
return PropertyManager(
{name: self.getProperty(name) if self.hasProperty(name) else Property() for name in props}
)
def __init__(self, properties=None):
self.properties = {}
self.subscribers = []
if properties is not None:
for (name, prop) in properties.items():
self.add(name, prop)
def add(self, name, prop):
self.properties[name] = prop
def fireCallbacks(value):
for c in self.subscribers:
try:
c.call(name, value)
except Exception as e:
logger.exception(e)
prop.wire(fireCallbacks)
return self
def __contains__(self, name):
return self.hasProperty(name)
def __getitem__(self, name):
return self.getPropertyValue(name)
def __setitem__(self, name, value):
if not self.hasProperty(name):
self.add(name, Property())
self.getProperty(name).setValue(value)
def __dict__(self):
return {k: v.getValue() for k, v in self.properties.items()}
def hasProperty(self, name):
return name in self.properties
def getProperty(self, name):
if not self.hasProperty(name):
self.add(name, Property())
return self.properties[name]
def getPropertyValue(self, name):
return self.getProperty(name).getValue()
def wire(self, callback):
sub = Subscription(self, callback)
self.subscribers.append(sub)
return sub
def unwire(self, sub):
try:
self.subscribers.remove(sub)
except ValueError:
# happens when already removed before
pass
return self
def defaults(self, other_pm):
for (key, p) in self.properties.items():
if p.getValue() is None:
p.setValue(other_pm[key])
return self
def loadConfig(self):
for file in ["/etc/openwebrx/config_webrx.py", "./config_webrx.py"]:
try:
spec = importlib.util.spec_from_file_location("config_webrx", file)
cfg = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cfg)
for name, value in cfg.__dict__.items():
if name.startswith("__"):
continue
self[name] = value
return self
except FileNotFoundError:
pass
raise ConfigNotFoundException("no usable config found! please make sure you have a valid configuration file!")
class ConfigError(object):
def __init__(self, key, message):
self.key = key
@ -160,9 +20,34 @@ class ConfigError(object):
class Config:
sharedConfig = None
@staticmethod
def _loadConfig():
pm = PropertyManager()
for file in ["/etc/openwebrx/config_webrx.py", "./config_webrx.py"]:
try:
spec = importlib.util.spec_from_file_location("config_webrx", file)
cfg = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cfg)
for name, value in cfg.__dict__.items():
if name.startswith("__"):
continue
pm[name] = value
return pm
except FileNotFoundError:
pass
raise ConfigNotFoundException("no usable config found! please make sure you have a valid configuration file!")
@staticmethod
def get():
if Config.sharedConfig is None:
Config.sharedConfig = Config._loadConfig()
return Config.sharedConfig
@staticmethod
def validateConfig():
pm = PropertyManager.getSharedInstance()
pm = Config.get()
errors = [
Config.checkTempDirectory(pm)
]
@ -172,7 +57,7 @@ class Config:
@staticmethod
def checkTempDirectory(pm: PropertyManager):
key = "temporary_directory"
if not key in pm or pm[key] is None:
if key not in pm or pm[key] is None:
return ConfigError(key, "temporary directory is not set")
if not os.path.exists(pm[key]):
return ConfigError(key, "temporary directory doesn't exist")

View File

@ -1,4 +1,4 @@
from owrx.config import PropertyManager
from owrx.config import Config
from owrx.dsp import DspManager
from owrx.cpu import CpuUsageThread
from owrx.sdr import SdrService
@ -93,7 +93,7 @@ class OpenWebRxReceiverClient(Client):
self.close()
raise
pm = PropertyManager.getSharedInstance()
pm = Config.get()
self.setSdr()
@ -198,7 +198,7 @@ class OpenWebRxReceiverClient(Client):
configProps = (
self.sdr.getProps()
.collect(*OpenWebRxReceiverClient.config_keys)
.defaults(PropertyManager.getSharedInstance())
.defaults(Config.get())
)
def sendConfig(key, value):
@ -247,15 +247,16 @@ class OpenWebRxReceiverClient(Client):
self.sdr.removeSpectrumClient(self)
def setParams(self, params):
config = Config.get()
# allow direct configuration only if enabled in the config
keys = PropertyManager.getSharedInstance()["configurable_keys"]
keys = config["configurable_keys"]
if not keys:
return
# only the keys in the protected property manager can be overridden from the web
protected = (
self.sdr.getProps()
.collect(*keys)
.defaults(PropertyManager.getSharedInstance())
.defaults(config)
)
for key, value in params.items():
protected[key] = value
@ -333,7 +334,7 @@ class MapConnection(Client):
def __init__(self, conn):
super().__init__(conn)
pm = PropertyManager.getSharedInstance()
pm = Config.get()
self.write_config(pm.collect("google_maps_api_key", "receiver_gps", "map_position_retention_time").__dict__())
Map.getSharedInstance().addClient(self)

View File

@ -1,5 +1,5 @@
from . import Controller
from owrx.config import PropertyManager
from owrx.config import Config
from datetime import datetime
import mimetypes
import os
@ -47,7 +47,7 @@ class OwrxAssetsController(AssetsController):
class AprsSymbolsController(AssetsController):
def __init__(self, handler, request, options):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
path = pm["aprs_symbols_path"]
if not path.endswith("/"):
path += "/"

View File

@ -2,14 +2,14 @@ from . import Controller
from owrx.client import ClientRegistry
from owrx.version import openwebrx_version
from owrx.sdr import SdrService
from owrx.config import PropertyManager
from owrx.config import Config
import os
import json
class StatusController(Controller):
def indexAction(self):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
# TODO keys that have been left out since they are no longer simple strings: sdr_hw, bands, antenna
vars = {
"status": "active",
@ -42,7 +42,7 @@ class StatusController(Controller):
return stats
def jsonAction(self):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
gps = pm["receiver_gps"]
status = {

View File

@ -1,4 +1,4 @@
from owrx.config import PropertyManager
from owrx.config import Config
from owrx.meta import MetaParser
from owrx.wsjt import WsjtParser
from owrx.aprs import AprsParser
@ -39,7 +39,7 @@ class DspManager(csdr.output):
"temporary_directory",
"center_freq",
)
.defaults(PropertyManager.getSharedInstance())
.defaults(Config.get())
)
self.dsp = csdr.dsp(self)

View File

@ -4,7 +4,7 @@ from operator import and_, or_
import re
from distutils.version import LooseVersion
import inspect
from owrx.config import PropertyManager
from owrx.config import Config
import shlex
import logging
@ -95,7 +95,7 @@ class FeatureDetector(object):
return inspect.getdoc(self._get_requirement_method(requirement))
def command_is_runnable(self, command):
tmp_dir = PropertyManager.getSharedInstance()["temporary_directory"]
tmp_dir = Config.get()["temporary_directory"]
cmd = shlex.split(command)
try:
process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=tmp_dir)

View File

@ -1,4 +1,4 @@
from owrx.config import PropertyManager
from owrx.config import Config
from csdr import csdr
import threading
from owrx.source import SdrSource
@ -23,7 +23,7 @@ class SpectrumThread(csdr.output):
"csdr_print_bufsizes",
"csdr_through",
"temporary_directory",
).defaults(PropertyManager.getSharedInstance())
).defaults(Config.get())
self.dsp = dsp = csdr.dsp(self)
dsp.nc_port = self.sdrSource.getPort()

View File

@ -2,7 +2,7 @@ import socket
import time
import logging
import random
from owrx.config import PropertyManager
from owrx.config import Config
logger = logging.getLogger(__name__)
@ -14,7 +14,7 @@ TFESC = 0xDD
class DirewolfConfig(object):
def getConfig(self, port, is_service):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
config = """
ACHANNELS 1

View File

@ -1,7 +1,8 @@
from datetime import datetime, timedelta
import threading, time
from owrx.config import PropertyManager
from owrx.config import Config
from owrx.bands import Band
import threading
import time
import sys
import logging
@ -105,7 +106,7 @@ class Map(object):
# TODO broadcast removal to clients
def removeOldPositions(self):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
retention = timedelta(seconds=pm["map_position_retention_time"])
cutoff = datetime.now() - retention

View File

@ -1,4 +1,4 @@
from owrx.config import PropertyManager
from owrx.config import Config
from urllib import request
import json
from datetime import datetime, timedelta
@ -54,7 +54,7 @@ class DmrMetaEnricher(object):
del self.threads[id]
def enrich(self, meta):
if not PropertyManager.getSharedInstance()["digital_voice_dmr_id_lookup"]:
if not Config.get()["digital_voice_dmr_id_lookup"]:
return None
if not "source" in meta:
return None

121
owrx/property/__init__.py Normal file
View File

@ -0,0 +1,121 @@
import logging
logger = logging.getLogger(__name__)
class Subscription(object):
def __init__(self, subscriptee, subscriber):
self.subscriptee = subscriptee
self.subscriber = subscriber
def call(self, *args, **kwargs):
self.subscriber(*args, **kwargs)
def cancel(self):
self.subscriptee.unwire(self)
class Property(object):
def __init__(self, value=None):
self.value = value
self.subscribers = []
def getValue(self):
return self.value
def setValue(self, value):
if self.value == value:
return self
self.value = value
for c in self.subscribers:
try:
c.call(self.value)
except Exception as e:
logger.exception(e)
return self
def wire(self, callback):
sub = Subscription(self, callback)
self.subscribers.append(sub)
if self.value is not None:
sub.call(self.value)
return sub
def unwire(self, sub):
try:
self.subscribers.remove(sub)
except ValueError:
# happens when already removed before
pass
return self
class PropertyManager(object):
def collect(self, *props):
return PropertyManager(
{name: self.getProperty(name) if self.hasProperty(name) else Property() for name in props}
)
def __init__(self, properties=None):
self.properties = {}
self.subscribers = []
if properties is not None:
for (name, prop) in properties.items():
self.add(name, prop)
def add(self, name, prop):
self.properties[name] = prop
def fireCallbacks(value):
for c in self.subscribers:
try:
c.call(name, value)
except Exception as e:
logger.exception(e)
prop.wire(fireCallbacks)
return self
def __contains__(self, name):
return self.hasProperty(name)
def __getitem__(self, name):
return self.getPropertyValue(name)
def __setitem__(self, name, value):
if not self.hasProperty(name):
self.add(name, Property())
self.getProperty(name).setValue(value)
def __dict__(self):
return {k: v.getValue() for k, v in self.properties.items()}
def hasProperty(self, name):
return name in self.properties
def getProperty(self, name):
if not self.hasProperty(name):
self.add(name, Property())
return self.properties[name]
def getPropertyValue(self, name):
return self.getProperty(name).getValue()
def wire(self, callback):
sub = Subscription(self, callback)
self.subscribers.append(sub)
return sub
def unwire(self, sub):
try:
self.subscribers.remove(sub)
except ValueError:
# happens when already removed before
pass
return self
def defaults(self, other_pm):
for (key, p) in self.properties.items():
if p.getValue() is None:
p.setValue(other_pm[key])
return self

View File

@ -5,7 +5,7 @@ import random
import socket
from functools import reduce
from operator import and_
from owrx.config import PropertyManager
from owrx.config import Config
from owrx.version import openwebrx_version
from owrx.locator import Locator
from owrx.metrics import Metrics, CounterMetric
@ -36,7 +36,7 @@ class PskReporter(object):
def getSharedInstance():
with PskReporter.creationLock:
if PskReporter.sharedInstance is None:
if PropertyManager.getSharedInstance()["pskreporter_enabled"]:
if Config.get()["pskreporter_enabled"]:
PskReporter.sharedInstance = PskReporter()
else:
PskReporter.sharedInstance = PskReporterDummy()
@ -181,7 +181,7 @@ class Uploader(object):
)
def getReceiverInformation(self):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
callsign = pm["pskreporter_callsign"]
locator = Locator.fromCoordinates(pm["receiver_gps"])
decodingSoftware = "OpenWebRX " + openwebrx_version

View File

@ -1,4 +1,5 @@
from owrx.config import PropertyManager
from owrx.config import Config
from owrx.property import PropertyManager
from owrx.feature import FeatureDetector, UnknownFeatureException
import logging
@ -14,7 +15,7 @@ class SdrService(object):
@staticmethod
def loadProps():
if SdrService.sdrProps is None:
pm = PropertyManager.getSharedInstance()
pm = Config.get()
featureDetector = FeatureDetector()
def loadIntoPropertyManager(dict: dict):

View File

@ -1,6 +1,6 @@
import threading
import time
from owrx.config import PropertyManager
from owrx.config import Config
from urllib import request, parse
import logging
@ -14,7 +14,7 @@ class SdrHuUpdater(threading.Thread):
super().__init__(daemon=True)
def update(self):
pm = PropertyManager.getSharedInstance().collect("server_hostname", "web_port", "sdrhu_key")
pm = Config.get().collect("server_hostname", "web_port", "sdrhu_key")
data = parse.urlencode({
"url": "http://{server_hostname}:{web_port}".format(**pm.__dict__()),
"apikey": pm["sdrhu_key"]

View File

@ -5,9 +5,10 @@ from owrx.bands import Bandplan
from csdr.csdr import dsp, output
from owrx.wsjt import WsjtParser
from owrx.aprs import AprsParser
from owrx.config import PropertyManager
from owrx.config import Config
from owrx.source.resampler import Resampler
from owrx.feature import FeatureDetector
from owrx.property import PropertyManager
from abc import ABCMeta, abstractmethod
from .schedule import ServiceScheduler
@ -96,7 +97,7 @@ class ServiceHandler(object):
# this looks overly complicated... but i'd like modes with no requirements to be always available without
# being listed in the hash above
unavailable = [mode for mode, req in requirements.items() if not fd.is_available(req)]
configured = PropertyManager.getSharedInstance()["services_decoders"]
configured = Config.get()["services_decoders"]
available = [mode for mode in configured if mode not in unavailable]
return mode in available
@ -260,7 +261,7 @@ class Services(object):
@staticmethod
def start():
if not PropertyManager.getSharedInstance()["services_enabled"]:
if not Config.get()["services_enabled"]:
return
for source in SdrService.getSources().values():
props = source.getProps()

View File

@ -1,6 +1,6 @@
from datetime import datetime, timezone, timedelta
from owrx.source import SdrSource
from owrx.config import PropertyManager
from owrx.config import Config
import threading
import math
from abc import ABC, ABCMeta, abstractmethod
@ -134,7 +134,7 @@ class DaylightSchedule(TimerangeSchedule):
self.schedule = scheduleDict
def getSunTimes(self, date):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
lat, lng = pm["receiver_gps"]
degtorad = math.pi / 180
radtodeg = 180 / math.pi

View File

@ -1,4 +1,4 @@
from owrx.config import PropertyManager
from owrx.config import Config
import threading
import subprocess
import os
@ -35,7 +35,7 @@ class SdrSource(ABC):
self.props = props
self.profile_id = None
self.activateProfile()
self.rtlProps = self.props.collect(*self.getEventNames()).defaults(PropertyManager.getSharedInstance())
self.rtlProps = self.props.collect(*self.getEventNames()).defaults(Config.get())
self.wireEvents()
self.commandMapper = None

View File

@ -7,8 +7,7 @@ from multiprocessing.connection import Pipe
from owrx.map import Map, LocatorLocation
import re
from queue import Queue, Full
from owrx.config import PropertyManager
from owrx.bands import Bandplan
from owrx.config import Config
from owrx.metrics import Metrics, CounterMetric, DirectMetric
from owrx.pskreporter import PskReporter
from owrx.parser import Parser
@ -45,7 +44,7 @@ class WsjtQueue(Queue):
def getSharedInstance():
with WsjtQueue.creationLock:
if WsjtQueue.sharedInstance is None:
pm = PropertyManager.getSharedInstance()
pm = Config.get()
WsjtQueue.sharedInstance = WsjtQueue(maxsize=pm["wsjt_queue_length"], workers=pm["wsjt_queue_workers"])
return WsjtQueue.sharedInstance
@ -89,7 +88,7 @@ class WsjtQueue(Queue):
class WsjtChopper(threading.Thread):
def __init__(self, source):
self.source = source
self.tmp_dir = PropertyManager.getSharedInstance()["temporary_directory"]
self.tmp_dir = Config.get()["temporary_directory"]
(self.wavefilename, self.wavefile) = self.getWaveFile()
self.switchingLock = threading.Lock()
self.timer = None
@ -193,7 +192,7 @@ class WsjtChopper(threading.Thread):
return None
def decoding_depth(self, mode):
pm = PropertyManager.getSharedInstance()
pm = Config.get()
# mode-specific setting?
if "wsjt_decoding_depths" in pm and mode in pm["wsjt_decoding_depths"]:
return pm["wsjt_decoding_depths"][mode]

View File

@ -22,14 +22,14 @@
"""
from owrx.sdrhu import SdrHuUpdater
from owrx.config import PropertyManager
from owrx.config import Config
import logging
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
if __name__ == "__main__":
pm = PropertyManager.getSharedInstance().loadConfig()
pm = Config.get()
if "sdrhu_public_listing" not in pm or not pm["sdrhu_public_listing"]:
logger.error('Public listing on sdr.hu is not activated. Please check "sdrhu_public_listing" in your config.')

0
test/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,8 @@
import unittest
from owrx.property import Property
class PropertyTest(unittest.TestCase):
def testSimple(self):
prop = Property("testvalue")
self.assertEqual(prop.getValue(), "testvalue")