""" Device configuration management (owner name, car model, avatar, sync status). Stores in SQLite with option to sync from server (Phase 25.5a). """ import json import os from . import app_sql def get_device_config(): """Get current device config including sync status.""" sync_enabled = _get_sync_enabled() config = { "owner_name": app_sql.get_setting("device_owner_name", ""), "car_model": app_sql.get_setting("device_car_model", ""), "device_name": app_sql.get_setting("device_device_name", ""), "avatar_path": app_sql.get_setting("device_avatar_path", ""), "sync_enabled": sync_enabled, } # Fill in any missing values from config file or defaults config = _apply_defaults(config) return config def update_device_config(body, errors): """Update device config from request body. Returns errors dict if invalid.""" if not isinstance(body, dict): errors["error"] = "Invalid request body" return errors # Check if sync is enabled; if so, only allow sync-related updates sync_enabled = _get_sync_enabled() if sync_enabled: # User cannot edit these fields if syncing from server if any(k in body for k in ["owner_name", "car_model", "avatar_path"]): errors["error"] = "Cannot edit device config while server sync is enabled. Server is source of truth." # Allow toggling sync_enabled off if "sync_enabled" in body and not body["sync_enabled"]: _set_sync_enabled(False) return errors # Not syncing: allow all edits if "owner_name" in body: value = str(body["owner_name"]).strip() if not value or len(value) > 100: errors["owner_name"] = "Owner name must be 1-100 characters." else: app_sql.set_setting("device_owner_name", value) if "car_model" in body: value = str(body["car_model"]).strip() if not value or len(value) > 100: errors["car_model"] = "Car model must be 1-100 characters." else: app_sql.set_setting("device_car_model", value) if "device_name" in body: value = str(body["device_name"]).strip() if not value or len(value) > 100: errors["device_name"] = "Device name must be 1-100 characters." else: app_sql.set_setting("device_device_name", value) if "avatar_path" in body: value = str(body["avatar_path"]).strip() if not value or len(value) > 255: errors["avatar_path"] = "Avatar path must be 1-255 characters." else: app_sql.set_setting("device_avatar_path", value) if "sync_enabled" in body: if not isinstance(body["sync_enabled"], bool): errors["sync_enabled"] = "Must be true or false." elif body["sync_enabled"]: from . import app_remote_config remote = app_remote_config.get_remote_config() if not remote["server_url"] or not remote["device_name"] or not remote["api_key_configured"]: errors["sync_enabled"] = "Configure server URL, device name, and API key before enabling server sync." else: _set_sync_enabled(body["sync_enabled"]) if "sync_enabled" in body and body["sync_enabled"] and "sync_enabled" not in errors: _set_sync_enabled(True) return errors def _apply_defaults(config): """Fill in any empty config values from config/device.json or hardcoded defaults.""" config_file_path = os.path.join( os.path.dirname(os.path.dirname(__file__)), "config", "device.json" ) file_config = {} try: if os.path.exists(config_file_path): with open(config_file_path, "r") as f: file_config = json.load(f) except (IOError, json.JSONDecodeError): pass # Use file config as source of truth for initial values, fallback to hardcoded defaults = { "owner_name": "quoije", "car_model": "Yaris 2013", "device_name": "Yaris OBD", "avatar_path": "img/yaris.png", } for key in defaults: if not config[key]: config[key] = file_config.get(key, defaults[key]) return config def _get_sync_enabled(): """Check if device config sync from server is enabled.""" return app_sql.get_setting("device_sync_enabled", "0") == "1" def _set_sync_enabled(enabled): """Set device config sync status.""" app_sql.set_setting("device_sync_enabled", "1" if enabled else "0")