use hierarchical property layers to make config changes effective

immediately
This commit is contained in:
Jakob Ketterl 2021-02-23 23:23:37 +01:00
parent 631232fe7c
commit dfaecdb357
9 changed files with 53 additions and 33 deletions

View File

@ -17,14 +17,19 @@ class ClassicConfig(PropertyReadOnly):
except FileNotFoundError: except FileNotFoundError:
pass pass
@staticmethod
def _toLayer(dictionary: dict):
layer = PropertyLayer()
for k, v in dictionary.items():
if isinstance(v, dict):
layer[k] = ClassicConfig._toLayer(v)
else:
layer[k] = v
return layer
@staticmethod @staticmethod
def _loadPythonFile(file): def _loadPythonFile(file):
spec = importlib.util.spec_from_file_location("config_webrx", file) spec = importlib.util.spec_from_file_location("config_webrx", file)
cfg = importlib.util.module_from_spec(spec) cfg = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cfg) spec.loader.exec_module(cfg)
pm = PropertyLayer() return ClassicConfig._toLayer({k: v for k, v in cfg.__dict__.items() if not k.startswith("__")})
for name, value in cfg.__dict__.items():
if name.startswith("__"):
continue
pm[name] = value
return pm

View File

@ -8,7 +8,7 @@ defaultConfig = PropertyLayer(
receiver_location="Budapest, Hungary", receiver_location="Budapest, Hungary",
receiver_asl=200, receiver_asl=200,
receiver_admin="example@example.com", receiver_admin="example@example.com",
receiver_gps={"lat": 47.0, "lon": 19.0}, receiver_gps=PropertyLayer(lat=47.0, lon=19.0),
photo_title="Panorama of Budapest from Schönherz Zoltán Dormitory", photo_title="Panorama of Budapest from Schönherz Zoltán Dormitory",
photo_desc="", photo_desc="",
fft_fps=9, fft_fps=9,
@ -25,7 +25,7 @@ defaultConfig = PropertyLayer(
waterfall_scheme="GoogleTurboWaterfall", waterfall_scheme="GoogleTurboWaterfall",
waterfall_min_level=-88, waterfall_min_level=-88,
waterfall_max_level=-20, waterfall_max_level=-20,
waterfall_auto_level_margin={"min": 3, "max": 10, "min_range": 50}, waterfall_auto_level_margin=PropertyLayer(min=3, max=10, min_range=50),
frequency_display_precision=4, frequency_display_precision=4,
squelch_auto_margin=10, squelch_auto_margin=10,
nmux_memory=50, nmux_memory=50,
@ -34,7 +34,7 @@ defaultConfig = PropertyLayer(
decoding_queue_workers=2, decoding_queue_workers=2,
decoding_queue_length=10, decoding_queue_length=10,
wsjt_decoding_depth=3, wsjt_decoding_depth=3,
wsjt_decoding_depths={"jt65": 1}, wsjt_decoding_depths=PropertyLayer(jt65=1),
fst4_enabled_intervals=[15, 30], fst4_enabled_intervals=[15, 30],
fst4w_enabled_intervals=[120, 300], fst4w_enabled_intervals=[120, 300],
q65_enabled_combinations=["A30", "E120", "C60"], q65_enabled_combinations=["A30", "E120", "C60"],

View File

@ -1,6 +1,7 @@
from owrx.config.core import CoreConfig from owrx.config.core import CoreConfig
from owrx.config.migration import Migrator from owrx.config.migration import Migrator
from owrx.property import PropertyLayer from owrx.property import PropertyLayer
from owrx.jsons import Encoder
import json import json
@ -10,11 +11,24 @@ class DynamicConfig(PropertyLayer):
try: try:
with open(DynamicConfig._getSettingsFile(), "r") as f: with open(DynamicConfig._getSettingsFile(), "r") as f:
for k, v in json.load(f).items(): for k, v in json.load(f).items():
self[k] = v if isinstance(v, dict):
self[k] = DynamicConfig._toLayer(v)
else:
self[k] = v
except FileNotFoundError: except FileNotFoundError:
pass pass
Migrator.migrate(self) Migrator.migrate(self)
@staticmethod
def _toLayer(dictionary: dict):
layer = PropertyLayer()
for k, v in dictionary.items():
if isinstance(v, dict):
layer[k] = DynamicConfig._toLayer(v)
else:
layer[k] = v
return layer
@staticmethod @staticmethod
def _getSettingsFile(): def _getSettingsFile():
coreConfig = CoreConfig() coreConfig = CoreConfig()
@ -22,6 +36,6 @@ class DynamicConfig(PropertyLayer):
def store(self): def store(self):
# don't write directly to file to avoid corruption on exceptions # don't write directly to file to avoid corruption on exceptions
jsonContent = json.dumps(self.__dict__(), indent=4) jsonContent = json.dumps(self.__dict__(), indent=4, cls=Encoder)
with open(DynamicConfig._getSettingsFile(), "w") as file: with open(DynamicConfig._getSettingsFile(), "w") as file:
file.write(jsonContent) file.write(jsonContent)

View File

@ -1,4 +1,5 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from owrx.jsons import Encoder
import json import json
@ -68,7 +69,7 @@ class EnumConverter(Converter):
class JsonConverter(Converter): class JsonConverter(Converter):
def convert_to_form(self, value): def convert_to_form(self, value):
return json.dumps(value) return json.dumps(value, cls=Encoder)
def convert_from_form(self, value): def convert_from_form(self, value):
return json.loads(value) return json.loads(value)

9
owrx/jsons.py Normal file
View File

@ -0,0 +1,9 @@
from owrx.property import PropertyManager
import json
class Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, PropertyManager):
return o.__dict__()
return super().default(o)

View File

@ -53,6 +53,12 @@ class PropertyManager(ABC):
def keys(self): def keys(self):
pass pass
def items(self):
return self.__dict__().items()
def __len__(self):
return self.__dict__().__len__()
def filter(self, *props): def filter(self, *props):
return PropertyFilter(self, *props) return PropertyFilter(self, *props)

View File

@ -19,12 +19,6 @@ class SdrService(object):
pm = Config.get() pm = Config.get()
featureDetector = FeatureDetector() featureDetector = FeatureDetector()
def loadIntoPropertyManager(dict: dict):
propertyManager = PropertyLayer()
for (name, value) in dict.items():
propertyManager[name] = value
return propertyManager
def sdrTypeAvailable(value): def sdrTypeAvailable(value):
try: try:
if not featureDetector.is_available(value["type"]): if not featureDetector.is_available(value["type"]):
@ -43,11 +37,11 @@ class SdrService(object):
# transform all dictionary items into PropertyManager object, filtering out unavailable ones # transform all dictionary items into PropertyManager object, filtering out unavailable ones
SdrService.sdrProps = { SdrService.sdrProps = {
name: loadIntoPropertyManager(value) for (name, value) in pm["sdrs"].items() if sdrTypeAvailable(value) name: value for (name, value) in pm["sdrs"].items() if sdrTypeAvailable(value)
} }
logger.info( logger.info(
"SDR sources loaded. Available SDRs: {0}".format( "SDR sources loaded. Available SDRs: {0}".format(
", ".join(map(lambda x: x["name"], SdrService.sdrProps.values())) ", ".join(x["name"] for x in SdrService.sdrProps.values())
) )
) )

View File

@ -100,7 +100,7 @@ class SdrSource(ABC):
props = PropertyStack() props = PropertyStack()
props.addLayer(1, self.props) props.addLayer(1, self.props)
for id, p in self.props["profiles"].items(): for id, p in self.props["profiles"].items():
props.replaceLayer(0, self._getProfilePropertyLayer(p)) props.replaceLayer(0, p)
if "center_freq" not in props: if "center_freq" not in props:
logger.warning('Profile "%s" does not specify a center_freq', id) logger.warning('Profile "%s" does not specify a center_freq', id)
continue continue
@ -114,15 +114,6 @@ class SdrSource(ABC):
if start_freq < center_freq - srh or start_freq > center_freq + srh: if start_freq < center_freq - srh or start_freq > center_freq + srh:
logger.warning('start_freq for profile "%s" is out of range', id) logger.warning('start_freq for profile "%s" is out of range', id)
def _getProfilePropertyLayer(self, profile):
layer = PropertyLayer()
for (key, value) in profile.items():
# skip the name, that would overwrite the source name.
if key == "name":
continue
layer[key] = value
return layer
def isAlwaysOn(self): def isAlwaysOn(self):
return "always-on" in self.props and self.props["always-on"] return "always-on" in self.props and self.props["always-on"]
@ -164,8 +155,7 @@ class SdrSource(ABC):
profile = profiles[profile_id] profile = profiles[profile_id]
self.profile_id = profile_id self.profile_id = profile_id
layer = self._getProfilePropertyLayer(profile) self.props.replaceLayer(0, profile)
self.props.replaceLayer(0, layer)
def getId(self): def getId(self):
return self.id return self.id

View File

@ -1,3 +1,4 @@
from owrx.jsons import Encoder
import base64 import base64
import hashlib import hashlib
import json import json
@ -106,7 +107,7 @@ class WebSocketConnection(object):
# convenience # convenience
if type(data) == dict: if type(data) == dict:
# allow_nan = False disallows NaN and Infinty to be encoded. Browser JSON will not parse them anyway. # allow_nan = False disallows NaN and Infinty to be encoded. Browser JSON will not parse them anyway.
data = json.dumps(data, allow_nan=False) data = json.dumps(data, allow_nan=False, cls=Encoder)
# string-type messages are sent as text frames # string-type messages are sent as text frames
if type(data) == str: if type(data) == str: