use hierarchical property layers to make config changes effective
immediately
This commit is contained in:
		| @@ -17,14 +17,19 @@ class ClassicConfig(PropertyReadOnly): | ||||
|             except FileNotFoundError: | ||||
|                 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 | ||||
|     def _loadPythonFile(file): | ||||
|         spec = importlib.util.spec_from_file_location("config_webrx", file) | ||||
|         cfg = importlib.util.module_from_spec(spec) | ||||
|         spec.loader.exec_module(cfg) | ||||
|         pm = PropertyLayer() | ||||
|         for name, value in cfg.__dict__.items(): | ||||
|             if name.startswith("__"): | ||||
|                 continue | ||||
|             pm[name] = value | ||||
|         return pm | ||||
|         return ClassicConfig._toLayer({k: v for k, v in cfg.__dict__.items() if not k.startswith("__")}) | ||||
|   | ||||
| @@ -8,7 +8,7 @@ defaultConfig = PropertyLayer( | ||||
|     receiver_location="Budapest, Hungary", | ||||
|     receiver_asl=200, | ||||
|     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_desc="", | ||||
|     fft_fps=9, | ||||
| @@ -25,7 +25,7 @@ defaultConfig = PropertyLayer( | ||||
|     waterfall_scheme="GoogleTurboWaterfall", | ||||
|     waterfall_min_level=-88, | ||||
|     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, | ||||
|     squelch_auto_margin=10, | ||||
|     nmux_memory=50, | ||||
| @@ -34,7 +34,7 @@ defaultConfig = PropertyLayer( | ||||
|     decoding_queue_workers=2, | ||||
|     decoding_queue_length=10, | ||||
|     wsjt_decoding_depth=3, | ||||
|     wsjt_decoding_depths={"jt65": 1}, | ||||
|     wsjt_decoding_depths=PropertyLayer(jt65=1), | ||||
|     fst4_enabled_intervals=[15, 30], | ||||
|     fst4w_enabled_intervals=[120, 300], | ||||
|     q65_enabled_combinations=["A30", "E120", "C60"], | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from owrx.config.core import CoreConfig | ||||
| from owrx.config.migration import Migrator | ||||
| from owrx.property import PropertyLayer | ||||
| from owrx.jsons import Encoder | ||||
| import json | ||||
|  | ||||
|  | ||||
| @@ -10,11 +11,24 @@ class DynamicConfig(PropertyLayer): | ||||
|         try: | ||||
|             with open(DynamicConfig._getSettingsFile(), "r") as f: | ||||
|                 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: | ||||
|             pass | ||||
|         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 | ||||
|     def _getSettingsFile(): | ||||
|         coreConfig = CoreConfig() | ||||
| @@ -22,6 +36,6 @@ class DynamicConfig(PropertyLayer): | ||||
|  | ||||
|     def store(self): | ||||
|         # 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: | ||||
|             file.write(jsonContent) | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| from abc import ABC, abstractmethod | ||||
| from owrx.jsons import Encoder | ||||
| import json | ||||
|  | ||||
|  | ||||
| @@ -68,7 +69,7 @@ class EnumConverter(Converter): | ||||
|  | ||||
| class JsonConverter(Converter): | ||||
|     def convert_to_form(self, value): | ||||
|         return json.dumps(value) | ||||
|         return json.dumps(value, cls=Encoder) | ||||
|  | ||||
|     def convert_from_form(self, value): | ||||
|         return json.loads(value) | ||||
|   | ||||
							
								
								
									
										9
									
								
								owrx/jsons.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								owrx/jsons.py
									
									
									
									
									
										Normal 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) | ||||
| @@ -53,6 +53,12 @@ class PropertyManager(ABC): | ||||
|     def keys(self): | ||||
|         pass | ||||
|  | ||||
|     def items(self): | ||||
|         return self.__dict__().items() | ||||
|  | ||||
|     def __len__(self): | ||||
|         return self.__dict__().__len__() | ||||
|  | ||||
|     def filter(self, *props): | ||||
|         return PropertyFilter(self, *props) | ||||
|  | ||||
|   | ||||
							
								
								
									
										10
									
								
								owrx/sdr.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								owrx/sdr.py
									
									
									
									
									
								
							| @@ -19,12 +19,6 @@ class SdrService(object): | ||||
|             pm = Config.get() | ||||
|             featureDetector = FeatureDetector() | ||||
|  | ||||
|             def loadIntoPropertyManager(dict: dict): | ||||
|                 propertyManager = PropertyLayer() | ||||
|                 for (name, value) in dict.items(): | ||||
|                     propertyManager[name] = value | ||||
|                 return propertyManager | ||||
|  | ||||
|             def sdrTypeAvailable(value): | ||||
|                 try: | ||||
|                     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 | ||||
|             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( | ||||
|                 "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()) | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|   | ||||
| @@ -100,7 +100,7 @@ class SdrSource(ABC): | ||||
|         props = PropertyStack() | ||||
|         props.addLayer(1, self.props) | ||||
|         for id, p in self.props["profiles"].items(): | ||||
|             props.replaceLayer(0, self._getProfilePropertyLayer(p)) | ||||
|             props.replaceLayer(0, p) | ||||
|             if "center_freq" not in props: | ||||
|                 logger.warning('Profile "%s" does not specify a center_freq', id) | ||||
|                 continue | ||||
| @@ -114,15 +114,6 @@ class SdrSource(ABC): | ||||
|                 if start_freq < center_freq - srh or start_freq > center_freq + srh: | ||||
|                     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): | ||||
|         return "always-on" in self.props and self.props["always-on"] | ||||
|  | ||||
| @@ -164,8 +155,7 @@ class SdrSource(ABC): | ||||
|         profile = profiles[profile_id] | ||||
|         self.profile_id = profile_id | ||||
|  | ||||
|         layer = self._getProfilePropertyLayer(profile) | ||||
|         self.props.replaceLayer(0, layer) | ||||
|         self.props.replaceLayer(0, profile) | ||||
|  | ||||
|     def getId(self): | ||||
|         return self.id | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| from owrx.jsons import Encoder | ||||
| import base64 | ||||
| import hashlib | ||||
| import json | ||||
| @@ -106,7 +107,7 @@ class WebSocketConnection(object): | ||||
|         # convenience | ||||
|         if type(data) == dict: | ||||
|             # 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 | ||||
|         if type(data) == str: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jakob Ketterl
					Jakob Ketterl