"""Konfigurationsverwaltung - Lädt und speichert Einstellungen in JSON.""" import json import os import shutil import sys from pathlib import Path # XDG-konformer Konfigurations-Pfad KONFIG_VERZEICHNIS = Path( os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config") ) / "sipwebapp" # Autostart-Verzeichnis (XDG-Standard) AUTOSTART_VERZEICHNIS = Path( os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config") ) / "autostart" AUTOSTART_DATEI = AUTOSTART_VERZEICHNIS / "sipwebapp.desktop" KONFIG_DATEI = KONFIG_VERZEICHNIS / "config.json" ANRUFLISTE_DATEI = KONFIG_VERZEICHNIS / "anrufliste.json" KONTAKTE_DATEI = KONFIG_VERZEICHNIS / "kontakte.json" # Standard-Konfiguration STANDARD_KONFIG = { "sip": { "server": "192.168.154.242", "port": 55444, "extension": "", "passwort": "", "transport": "udp", }, "audio": { "aufnahme_geraet": "", # PipeWire-Name (leer = KDE-Standard) "wiedergabe_geraet": "", # PipeWire-Name (leer = KDE-Standard) "klingelton_geraet": "", # PipeWire-Name fürs Klingeln (leer = Standard) "mikrofon_lautstaerke": 1.0, "lautsprecher_lautstaerke": 1.0, }, "allgemein": { "minimieren_in_tray": True, "autostart": False, "minimiert_starten": False, # Beim Autostart minimiert in Tray starten "klingelton": "", # Pfad zu WAV-Datei }, "carddav": { "accounts": [], # [{"name": "Privat", "url": "...", "benutzername": "...", "passwort": "..."}] "auto_sync": True, "sync_intervall": 3600, # Sekunden }, "blf": { "extensions": [], # Liste von Extensions die überwacht werden }, "favoriten": { "manuell": [], # [{"name": "...", "nummer": "..."}] "max_anzeige": 10, # Max angezeigte Favoriten }, } class ConfigManager: """Verwaltet die Anwendungs-Konfiguration als JSON-Datei.""" def __init__(self): self._konfig = {} self.laden() def laden(self): """Konfiguration aus JSON laden oder Standard erstellen.""" if KONFIG_DATEI.exists(): try: with open(KONFIG_DATEI, "r", encoding="utf-8") as f: gespeichert = json.load(f) # Standard-Werte mit gespeicherten zusammenführen self._konfig = self._zusammenfuehren(STANDARD_KONFIG, gespeichert) except (json.JSONDecodeError, OSError): self._konfig = STANDARD_KONFIG.copy() else: self._konfig = STANDARD_KONFIG.copy() # Migration: Alter einzelner CardDAV-Account → accounts-Liste cdav = self._konfig.get("carddav", {}) if cdav.get("url") and not cdav.get("accounts"): cdav["accounts"] = [{ "name": "Standard", "url": cdav.pop("url", ""), "benutzername": cdav.pop("benutzername", ""), "passwort": cdav.pop("passwort", ""), }] if "accounts" not in cdav: cdav["accounts"] = [] # Alte Einzelfelder entfernen for feld in ("url", "benutzername", "passwort"): cdav.pop(feld, None) def speichern(self): """Konfiguration als JSON speichern.""" KONFIG_VERZEICHNIS.mkdir(parents=True, exist_ok=True) with open(KONFIG_DATEI, "w", encoding="utf-8") as f: json.dump(self._konfig, f, indent=2, ensure_ascii=False) def get(self, *schluessel): """Wert aus der Konfiguration lesen. Beispiel: config.get("sip", "server") → "192.168.154.242" """ wert = self._konfig for s in schluessel: if isinstance(wert, dict) and s in wert: wert = wert[s] else: return None return wert def set(self, *args): """Wert in der Konfiguration setzen. Letztes Argument ist der Wert, davor die Schlüssel-Hierarchie. Beispiel: config.set("sip", "server", "192.168.1.100") """ if len(args) < 2: return schluessel = args[:-1] wert = args[-1] ziel = self._konfig for s in schluessel[:-1]: if s not in ziel: ziel[s] = {} ziel = ziel[s] ziel[schluessel[-1]] = wert @property def sip(self): """SIP-Konfiguration.""" return self._konfig.get("sip", {}) @property def audio(self): """Audio-Konfiguration.""" return self._konfig.get("audio", {}) @property def allgemein(self): """Allgemeine Konfiguration.""" return self._konfig.get("allgemein", {}) @property def carddav(self): """CardDAV-Konfiguration.""" return self._konfig.get("carddav", {}) @property def blf(self): """BLF-Konfiguration.""" return self._konfig.get("blf", {}) def hat_sip_zugangsdaten(self): """Prüft ob SIP-Zugangsdaten konfiguriert sind.""" sip = self.sip return bool(sip.get("extension") and sip.get("passwort")) def _zusammenfuehren(self, standard, gespeichert): """Zusammenführen: Standard-Werte + gespeicherte Werte.""" ergebnis = standard.copy() for schluessel, wert in gespeichert.items(): if (schluessel in ergebnis and isinstance(ergebnis[schluessel], dict) and isinstance(wert, dict)): ergebnis[schluessel] = self._zusammenfuehren( ergebnis[schluessel], wert ) else: ergebnis[schluessel] = wert return ergebnis # --- Anrufliste --- def anrufliste_laden(self): """Anrufliste aus JSON laden.""" if ANRUFLISTE_DATEI.exists(): try: with open(ANRUFLISTE_DATEI, "r", encoding="utf-8") as f: return json.load(f) except (json.JSONDecodeError, OSError): pass return [] def anrufliste_speichern(self, anrufe): """Anrufliste als JSON speichern.""" KONFIG_VERZEICHNIS.mkdir(parents=True, exist_ok=True) with open(ANRUFLISTE_DATEI, "w", encoding="utf-8") as f: json.dump(anrufe, f, indent=2, ensure_ascii=False) # --- Kontakte --- def kontakte_laden(self): """Kontaktliste aus JSON laden.""" if KONTAKTE_DATEI.exists(): try: with open(KONTAKTE_DATEI, "r", encoding="utf-8") as f: return json.load(f) except (json.JSONDecodeError, OSError): pass return [] def kontakte_speichern(self, kontakte): """Kontaktliste als JSON speichern.""" KONFIG_VERZEICHNIS.mkdir(parents=True, exist_ok=True) with open(KONTAKTE_DATEI, "w", encoding="utf-8") as f: json.dump(kontakte, f, indent=2, ensure_ascii=False) # --- Autostart --- def autostart_aktivieren(self): """Desktop-Entry für Autostart erstellen.""" AUTOSTART_VERZEICHNIS.mkdir(parents=True, exist_ok=True) # Executable ermitteln if getattr(sys, 'frozen', False): # PyInstaller/AppImage: sys.executable ist das Binary exec_path = sys.executable else: # Entwicklung: Python + main.py main_py = Path(__file__).parent.parent / "main.py" exec_path = f"{sys.executable} {main_py}" # Kein --minimiert Flag nötig - App liest selbst aus Config desktop_entry = f"""[Desktop Entry] Type=Application Version=1.0 Name=SIP Softphone Comment=SIP-Softphone für FreePBX/Asterisk GenericName=Internet-Telefonie Exec={exec_path} Icon=sipwebapp Categories=Network;Telephony; StartupNotify=false Terminal=false X-GNOME-Autostart-enabled=true """ with open(AUTOSTART_DATEI, "w", encoding="utf-8") as f: f.write(desktop_entry) def autostart_deaktivieren(self): """Desktop-Entry für Autostart entfernen.""" if AUTOSTART_DATEI.exists(): AUTOSTART_DATEI.unlink() def autostart_aktualisieren(self): """Autostart-Eintrag basierend auf Konfiguration erstellen oder entfernen.""" if self.allgemein.get("autostart", False): self.autostart_aktivieren() else: self.autostart_deaktivieren()