From 3e9f7d5314cd5ca7ab950bef4d423252ca4ac903 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 4 May 2024 21:31:12 +1200 Subject: [PATCH] move config to database --- database.py | 15 ++++++- web.py | 115 ++++++++++++++++++++++++++-------------------------- 2 files changed, 71 insertions(+), 59 deletions(-) diff --git a/database.py b/database.py index 8f03b36..d9d6e9d 100644 --- a/database.py +++ b/database.py @@ -10,6 +10,19 @@ class BaseModel(Model): database = database +class Config(BaseModel): + + id = BigAutoField() + key = CharField(unique=True) + value = TextField() + created_at = DateTimeField(default=datetime.now) + updated_at = DateTimeField(default=datetime.now) + + # define table name + class Meta: + table_name = "config" + + class LxmfMessage(BaseModel): id = BigAutoField() @@ -26,6 +39,6 @@ class LxmfMessage(BaseModel): created_at = DateTimeField(default=datetime.now) updated_at = DateTimeField(default=datetime.now) - # define database and table name + # define table name class Meta: table_name = "lxmf_messages" diff --git a/web.py b/web.py index a83547f..9a13afb 100644 --- a/web.py +++ b/web.py @@ -23,17 +23,12 @@ class ReticulumWebChat: def __init__(self, identity: RNS.Identity, storage_dir, reticulum_config_dir): - # default values before loading config - self.display_name = "Anonymous Peer" - # when providing a custom storage_dir, files will be saved as # /identities// - # /identities//config.json # /identities//database.db # if storage_dir is not provided, we will use ./storage instead # ./storage/identities// - # ./storage/identities//config.json # ./storage/identities//database.db # ensure a storage path exists for the loaded identity @@ -43,22 +38,21 @@ class ReticulumWebChat: os.makedirs(storage_path, exist_ok=True) # define path to files based on storage path - config_path = os.path.join(storage_path, "config.json") database_path = os.path.join(storage_path, "database.db") lxmf_router_path = os.path.join(storage_path, "lxmf_router") - # load config - self.config_file = config_path - self.load_config() - # init database database.database.initialize(SqliteDatabase(database_path)) self.db = database.database self.db.connect() self.db.create_tables([ + database.Config, database.LxmfMessage, ]) + # init config + self.config = Config() + # init reticulum self.reticulum = RNS.Reticulum(reticulum_config_dir) self.identity = identity @@ -78,46 +72,6 @@ class ReticulumWebChat: # remember websocket clients self.websocket_clients: List[web.WebSocketResponse] = [] - def load_config(self): - - # default config - config = { - - } - - # attempt to load config and override default values - try: - with open(self.config_file, 'r') as f: - custom_config = json.load(f) - config |= custom_config - - # config is broken, fallback to defaults - except: - print("failed to load config, defaults will be used") - - # update display name from config - if "display_name" in config: - self.display_name = config["display_name"] - - # return loaded config - return config - - def save_config(self): - - # build config - config = { - "display_name": self.display_name, - } - - # attempt to save config - try: - with open(self.config_file, 'w') as f: - json.dump(config, f, indent=4) - - # config is broken, fallback to defaults - except: - print("failed to save config") - # web server has shutdown, likely ctrl+c, but if we don't do the following, the script never exits async def shutdown(self, app): @@ -133,7 +87,6 @@ class ReticulumWebChat: self.reticulum.exit_handler() RNS.exit() - def run(self, host, port): # create route table @@ -255,11 +208,8 @@ class ReticulumWebChat: # update display name in state if "display_name" in config and config["display_name"] != "": - self.display_name = config["display_name"] - print("updated display name to: " + self.display_name) - - # save config - self.save_config() + self.config.display_name.set(config["display_name"]) + print("updated display name to: " + self.config.display_name.get()) # send config to websocket clients await self.send_config_to_websocket_clients() @@ -281,7 +231,7 @@ class ReticulumWebChat: elif _type == "announce": # send announce for lxmf - self.local_lxmf_destination.announce(app_data=self.display_name.encode("utf-8")) + self.local_lxmf_destination.announce(app_data=self.config.display_name.get().encode("utf-8")) # handle downloading a file from a nomadnet node elif _type == "nomadnet.file.download": @@ -400,7 +350,7 @@ class ReticulumWebChat: await self.websocket_broadcast(json.dumps({ "type": "config", "config": { - "display_name": self.display_name, + "display_name": self.config.display_name.get(), "identity_hash": self.identity.hexhash, "lxmf_address_hash": self.local_lxmf_destination.hexhash, }, @@ -654,6 +604,55 @@ class ReticulumWebChat: }))) +# class to manage config stored in database +class Config: + + @staticmethod + def get(key: str, default_value=None): + + # get config value from database + config_item = database.Config.get_or_none(database.Config.key == key) + + # return value if available + if config_item is not None: + return config_item.value + + # fallback to returning default value + return default_value + + @staticmethod + def set(key: str, value: str): + + # prepare data to insert or update + data = { + "key": key, + "value": value, + "updated_at": datetime.now(), + } + + # upsert to database + query = database.Config.insert(data) + query = query.on_conflict(conflict_target=[database.Config.key], update=data) + query.execute() + + # handle config values that should be strings + class StringConfig: + + def __init__(self, key: str, default_value: str = None): + self.key = key + self.default_value = default_value + + def get(self, default_value: str = None): + _default_value = default_value or self.default_value + return Config.get(self.key, default_value=_default_value) + + def set(self, value: str): + return Config.set(self.key, value) + + # all possible config items + display_name = StringConfig("display_name", "Anonymous Peer") + + # an announce handler for lxmf.delivery aspect that just forwards to a provided callback class LXMFAnnounceHandler: