feat(queue): serialisierte Ansage-Queue mit Prioritäten, now-Sprung, Mute-Store und Rate-Limit-Struktur
- Worker-Loop: eine Ansage gleichzeitig; now > high > normal > low, FIFO je Level - Cancel bricht die laufende Ansage ab (ctx) und leert die Queue (Zahl als Rückgabe) - Close verwirft wartende Items, bricht die laufende Ansage ab und wartet auf den Worker - MuteStore: persistenter Mute-State als Datei mute im Config-Dir (atomar geschrieben) - RateLimiter: Struktur + Semantik für Task 05, bewusst noch nicht angebunden (Task-04-Default unbegrenzt, dokumentierte Seam in rate.go)
This commit is contained in:
1 parent
716f6b2fc9
commit
e492baddac
5 files changed
+742
-3
No files matched your search
@@ -0,0 +1,84 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user