- 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>
259 lines
8.2 KiB
Python
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()
|