package queue import ( "errors" "os" "path/filepath" "strings" ) // MuteFileName ist der Name der Mute-Datei im Config-Verzeichnis (bindender // Vertrag: „persistenten Mute-State toggeln (Datei mute im Config-Dir)"). const MuteFileName = "mute" // ErrNoMutePath meldet einen Set-Versuch ohne konfigurierten Datei-Pfad // (Config-Dir nicht bestimmbar — der Daemon deaktiviert dann Mute mit einer // Start-Warnung statt zu scheitern). var ErrNoMutePath = errors.New("mute: kein Datei-Pfad konfiguriert (Config-Dir nicht bestimmbar)") // MuteStore verwaltet den persistenten Mute-State als Datei im // Config-Verzeichnis: Inhalt „true" = stumm, alles andere (oder keine Datei) // = nicht stumm. // // Der Daemon liest die Datei bei jeder /speak-Anfrage neu — dadurch wirkt // auch ein im Degraded Mode (Daemon tot) von der CLI direkt geschriebener // State sofort, und der Zustand überlebt Daemon-Restarts. type MuteStore struct { // Path ist der vollständige Pfad der Mute-Datei. Leer (kein Config-Dir // bestimmbar): Muted() ist immer false, Set ein Fehler. Path string } // Muted meldet den persistenten Mute-State. Eine fehlende oder unlesbare // Datei heißt: nicht stumm (Fehlstart ohne Config-Dir wird nie „stumm aus // Versehen"). func (s MuteStore) Muted() bool { if s.Path == "" { return false } b, err := os.ReadFile(s.Path) if err != nil { return false } return strings.TrimSpace(string(b)) == "true" } // Set schreibt den Mute-State atomar (temporäre Datei + Rename, damit // Daemon und CLI nie eine halb geschriebene Datei lesen) und legt das // Config-Verzeichnis bei Bedarf an. func (s MuteStore) Set(muted bool) error { if s.Path == "" { return ErrNoMutePath } dir := filepath.Dir(s.Path) if err := os.MkdirAll(dir, 0o755); err != nil { return err } content := "false\n" if muted { content = "true\n" } tmp, err := os.CreateTemp(dir, ".mute-*") if err != nil { return err } name := tmp.Name() if _, err := tmp.WriteString(content); err != nil { tmp.Close() os.Remove(name) return err } if err := tmp.Close(); err != nil { os.Remove(name) return err } if err := os.Chmod(name, 0o644); err != nil { os.Remove(name) return err } if err := os.Rename(name, s.Path); err != nil { os.Remove(name) return err } return nil }