"""Keep app/converter/config.py and server.json in sync with setup choices.""" import json import re import urllib.parse from pathlib import Path from typing import Optional from backends import common from backends.common import CONFIG_PATH, url_with_port from converter import config from . import build from .constants import FALLBACK_PORT def config_port() -> int: """Return the port of AUDIOCPP_API_URL in app/converter/config.py.""" try: return urllib.parse.urlsplit(config.AUDIOCPP_API_URL).port or FALLBACK_PORT except ValueError: return FALLBACK_PORT def update_config_api_url_port(port: int, config_path: Optional[Path] = None) -> bool: """Rewrite the port inside AUDIOCPP_API_URL in app/converter/config.py. Reads the configured URL from the file (not from the imported module, which a long hub session can leave behind), swaps its port for PORT, and writes it back through ``common.update_config_value`` so the imported module mirrors the change immediately. Returns True when the file now holds the new URL. """ path = Path(config_path) if config_path is not None else CONFIG_PATH try: text = path.read_text(encoding="utf-8") except OSError: return False match = re.search(r'(?m)^\s*AUDIOCPP_API_URL\s*=\s*"([^"]*)"', text) if not match: return False return common.update_config_value("AUDIOCPP_API_URL", url_with_port(match.group(1), port), config_path=path) def update_server_config_port(port: int) -> bool: """Rewrite the 'port' in the audio.cpp checkout's server.json. Loads ``/server.json``, sets its ``port`` to PORT, and rewrites it with the same ``json.dump`` formatting the wizard uses. Returns True when the file now carries PORT (a no-op when it already does), and False when there is no checkout/server.json or the file cannot be read or written. """ checkout = build.find_local_checkout() if checkout is None: return False server_json = checkout / "server.json" if not server_json.exists(): return False try: data = json.loads(server_json.read_text(encoding="utf-8")) except (OSError, ValueError): return False if not isinstance(data, dict): return False if data.get("port") == port: return True data["port"] = port try: with server_json.open("w", encoding="utf-8") as handle: json.dump(data, handle, indent=2, ensure_ascii=False) handle.write("\n") except OSError: return False return True def _apply_port_sync(port: int, accepted: bool) -> None: """Write the port into app/converter/config.py, or report when declined.""" if accepted: if not update_config_api_url_port(port): print(f"[WARNING] Could not update {CONFIG_PATH}; edit " "AUDIOCPP_API_URL by hand so audiobook.py uses the " "new port") else: print("[WARNING] Left AUDIOCPP_API_URL unchanged; audiobook.py " f"will still use port {config_port()}") def update_server_backend(backend: str) -> bool: """Rewrite the 'backend' in the checkout's server.json, or True when none. Sets ``backend`` to BACKEND in ``/server.json`` (same ``json.dump`` formatting as the wizard). Returns True when the file now carries BACKEND, when there is no server.json (nothing to sync), or when it already does; False when the file exists but cannot be read/written. """ checkout = build.find_local_checkout() if checkout is None: return True server_json = checkout / "server.json" if not server_json.exists(): return True try: data = json.loads(server_json.read_text(encoding="utf-8")) except (OSError, ValueError): return False if not isinstance(data, dict): return False if data.get("backend") == backend: return True data["backend"] = backend try: with server_json.open("w", encoding="utf-8") as handle: json.dump(data, handle, indent=2, ensure_ascii=False) handle.write("\n") except OSError: return False return True