linux.sipsoftphone/utils/config_manager.py
data b0fd6889a3 feat: Autostart + Minimiert starten Funktionalität
- Autostart: Desktop-Entry wird in ~/.config/autostart/ erstellt
- Minimiert starten: App startet direkt im System-Tray
- Einstellungen lesen Config direkt, kein CLI-Flag nötig
- README.md aktualisiert

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 11:28:55 +01:00

259 lines
8.2 KiB
Python

"""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()