diff --git a/.gitignore b/.gitignore index c94b16f..ffeb437 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.venv -*__pycache__ \ No newline at end of file +*__pycache__ +*.bat +*.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4e4495b..c5cc551 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,9 @@ FROM python:3.13-slim # Set the working directory in the container WORKDIR /app +# Create the data dir (this should be a volume on the local machine to store data) +RUN mkdir -p data + # Copy the requirements file into the container COPY requirements.txt . diff --git a/Makefile b/Makefile index bf6ae2d..8d8482a 100644 --- a/Makefile +++ b/Makefile @@ -14,5 +14,6 @@ run: build -e SERVER_WS_URI=${SERVER_WS_URI} \ -e SERVER_API_URL=${SERVER_API_URL} \ -e CLIENT_API_URL=${CLIENT_API_URL} \ + -v ./data:/data \ --network=host \ $(CLIENT_IMAGE) diff --git a/app/client.py b/app/client.py index 69b3e03..ad466ef 100644 --- a/app/client.py +++ b/app/client.py @@ -7,14 +7,19 @@ from drb_cdb_api import DRBCDBAPI from drb_cdb_types import ConfigGenerator from server_api import RadioAPIClient from enum import Enum +from config import Config + +app_conf = Config() # --- Client Configuration --- SERVER_WS_URI = os.getenv("SERVER_WS_URI", "ws://localhost:8765") SERVER_API_URL = os.getenv("SERVER_API_URL", "http://localhost:5000") CLIENT_API_URL = os.getenv("CLIENT_API_URL", "http://localhost:8001") -# Generate or define a unique ID for THIS client instance -# In a real app, this might come from config or a login process -CLIENT_ID = f"client-{uuid.uuid4().hex[:8]}" # TODO - Implement persistent ID + +# Get/set the ID of this node +if not app_conf.get("client_id"): + app_conf.set("client_id", f"client-{uuid.uuid4().hex[:8]}") +CLIENT_ID = app_conf.client_id # ---------------------------- # Dictionary mapping command names (strings) to local client functions @@ -38,7 +43,6 @@ def command(func): return func # --- Define Client-Side Command Handlers (The "API" functions) --- - # Join server @command async def join_server(system_id, guild_id, channel_id): @@ -114,13 +118,6 @@ async def leave_server(guild_id): print("Leave server completed") -@command -async def set_status(status_text): - """Example command: Sets or displays a status.""" - print(f"\n--- Server Command: set_status ---") - print(f"Status updated to: {status_text}") - print("----------------------------------") - @command async def run_task(task_id, duration_seconds): """Example command: Simulates running a task.""" @@ -130,11 +127,7 @@ async def run_task(task_id, duration_seconds): print(f"Task {task_id} finished.") print("------------------------------") -# Add more command handlers as needed... - # ------------------------------------------------------------------ - - async def receive_commands(websocket): """Listens for and processes command messages from the server.""" async for message in websocket: diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..f26afe6 --- /dev/null +++ b/app/config.py @@ -0,0 +1,139 @@ +import os +import json + +class Config: + """ + Manages application configuration stored in a JSON file. + + Provides attribute-style access to configuration values. + """ + def __init__(self, file_path='/data/config.json'): + """ + Initializes the Config manager. + + Loads configuration from the JSON file. Creates the file with an + empty JSON object if it doesn't exist. + + Args: + file_path (str): The path to the JSON configuration file. + """ + self.file_path = file_path + self._config_data = {} # Internal dictionary to hold config data + self._load_config() + + def _load_config(self): + """Loads configuration key-value pairs from the JSON file.""" + # Check if the file exists. If not, create it with an empty JSON object. + if not os.path.exists(self.file_path): + self._save_config() # Create the file with empty data + + try: + with open(self.file_path, 'r') as f: + # Load data from the file. Handle empty file case. + content = f.read() + if content: + self._config_data = json.loads(content) + else: + self._config_data = {} # Initialize as empty if file is empty + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Error loading config file {self.file_path}: {e}") + # If there's an error loading, initialize with empty data + self._config_data = {} + # Optionally, you might want to back up the problematic file + # or log the error more severely in a real application. + + def _save_config(self): + """Saves the current configuration data to the JSON file.""" + try: + with open(self.file_path, 'w') as f: + json.dump(self._config_data, f, indent=4) # Use indent for readability + except IOError as e: + print(f"Error saving config file {self.file_path}: {e}") + except TypeError as e: + print(f"Error serializing config data to JSON: {e}. Ensure all values are JSON serializable.") + + def get(self, key, default=None): + """ + Retrieves a configuration value by key. + + Args: + key (str): The configuration key. + default: The value to return if the key is not found. + + Returns: + The configuration value or the default value if not found. + """ + return self._config_data.get(key, default) + + def set(self, key, value): + """ + Sets or updates a configuration value in memory and saves to the file. + + Args: + key (str): The configuration key. + value: The configuration value. Must be JSON serializable. + """ + self._config_data[key] = value + self._save_config() + + def delete(self, key): + """ + Deletes a configuration key-value pair from memory and saves to the file. + + Args: + key (str): The configuration key to delete. + """ + if key in self._config_data: + del self._config_data[key] + self._save_config() + else: + print(f"Warning: Key '{key}' not found in config.") + + def __getattr__(self, name): + """ + Allows accessing configuration values using attribute notation (e.g., config.my_key). + + Args: + name (str): The attribute name (configuration key). + + Returns: + The configuration value associated with the key. + + Raises: + AttributeError: If the key is not found in the configuration. + """ + if name in self._config_data: + return self._config_data[name] + # Fallback for standard attributes if not found in config data + try: + return self.__getattribute__(name) + except AttributeError: + raise AttributeError(f"'Config' object has no attribute '{name}' and key '{name}' not found in config.") + + def __setattr__(self, name, value): + """ + Allows setting configuration values using attribute notation (e.g., config.my_key = value). + + Args: + name (str): The attribute name (configuration key). + value: The value to set. Must be JSON serializable. + """ + # Handle setting internal attributes like file_path or _config_data + if name in ('file_path', '_config_data'): + super().__setattr__(name, value) + else: + # Treat other attribute assignments as setting config values + self.set(name, value) + + def __delattr__(self, name): + """ + Allows deleting configuration values using attribute notation (e.g., del config.my_key). + + Args: + name (str): The attribute name (configuration key) to delete. + """ + if name in self._config_data: + self.delete(name) + else: + # Fallback for standard attributes + super().__delattr__(name) \ No newline at end of file