feat(cli): say über den Daemon, Steuer-Befehle als Daemon-Aufrufe, Degraded Mode
- say/Default erkennt den Daemon via GET /status: enqueue + sofort zurück (stdout = Item-ID), --wait pollt bis zur Fertigstellung, --now springt vor - mute/unmute/status/cancel mit plain Output; mute/unmute schreiben ohne Daemon die Mute-Datei direkt (gleiche Quelle, wirkt sofort im Daemon) - Degraded Mode: Lock-Datei talk.lock im Config-Dir (Kollision = warten, verwaist nach 60 s), sequenzielles lokales Abspielen, mute respektiert - Handler-Signatur der Befehlstabelle erhält stdout (say/Steuer-Befehle geben Ergebnisse aus); --priority wird validiert
This commit is contained in:
1 parent
d09aff6874
commit
e1a3981d17
9 files changed
+524
-81
No files matched your search
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"code.m3ta.dev/m3tam3re/talk/internal/api"
|
||||
"code.m3ta.dev/m3tam3re/talk/internal/config"
|
||||
"code.m3ta.dev/m3tam3re/talk/internal/queue"
|
||||
)
|
||||
|
||||
// Die Steuer-Befehle (Task 04) sind Daemon-Aufrufe mit plain Output:
|
||||
// stdout führt eine maschinenlesbare Zeile (muted/unmuted, cancelled: N
|
||||
// bzw. den Status-Block), stderr menschliche Hinweise.
|
||||
|
||||
// runMute implementiert `talk mute`: POST /mute an den Daemon. Ist kein
|
||||
// Daemon erreichbar, wird die Mute-Datei direkt geschrieben — dieselbe
|
||||
// Persistenz-Quelle, die der Daemon bei jeder Ansage neu liest; der
|
||||
// Zustand greift also auch offline und überlebt Daemon-Restarts.
|
||||
func runMute(args []string, opts *sayOptions, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
return setMute(true, stdout, stderr)
|
||||
}
|
||||
|
||||
// runUnmute implementiert `talk unmute` (analog zu runMute).
|
||||
func runUnmute(args []string, opts *sayOptions, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
return setMute(false, stdout, stderr)
|
||||
}
|
||||
|
||||
// setMute ist der gemeinsame Kern von mute/unmute.
|
||||
func setMute(muted bool, stdout, stderr io.Writer) int {
|
||||
cfg, err := config.Load(config.Options{Getenv: getenv})
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "talk: %v\n", err)
|
||||
return exitFailure
|
||||
}
|
||||
client := api.NewClient(cfg.Server.Port)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonCallTimeout)
|
||||
defer cancel()
|
||||
if err := client.SetMute(ctx, muted); err == nil {
|
||||
printMuteState(muted, stdout)
|
||||
return exitOK
|
||||
}
|
||||
|
||||
fmt.Fprintf(stderr, "talk: Daemon nicht erreichbar (%v) — Mute-Datei wird direkt geschrieben\n", err)
|
||||
dir, err := config.Dir(getenv)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "talk: %v\n", err)
|
||||
return exitFailure
|
||||
}
|
||||
if err := (queue.MuteStore{Path: filepath.Join(dir, queue.MuteFileName)}).Set(muted); err != nil {
|
||||
fmt.Fprintf(stderr, "talk: Mute-Datei schreiben: %v\n", err)
|
||||
return exitFailure
|
||||
}
|
||||
printMuteState(muted, stdout)
|
||||
return exitOK
|
||||
}
|
||||
|
||||
// printMuteState schreibt den Zustand als plain Token auf stdout.
|
||||
func printMuteState(muted bool, stdout io.Writer) {
|
||||
if muted {
|
||||
fmt.Fprintln(stdout, "muted")
|
||||
} else {
|
||||
fmt.Fprintln(stdout, "unmuted")
|
||||
}
|
||||
}
|
||||
|
||||
// runStatus implementiert `talk status`: plain Block aus GET /status —
|
||||
// Daemon-Adresse + Version, Queue-Länge, aktuelles Item, Mute-State,
|
||||
// aktiver Provider. Ohne Daemon: klarer Fehler (status ist eine Anfrage an
|
||||
// den Daemon; der Degraded Mode hat keinen Status).
|
||||
func runStatus(args []string, opts *sayOptions, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
cfg, err := config.Load(config.Options{Getenv: getenv})
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "talk: %v\n", err)
|
||||
return exitFailure
|
||||
}
|
||||
client := api.NewClient(cfg.Server.Port)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonCallTimeout)
|
||||
defer cancel()
|
||||
st, err := client.Status(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "talk: Daemon nicht erreichbar unter %s (läuft talkd? say fällt in den Degraded Mode)\n", client.BaseURL)
|
||||
return exitFailure
|
||||
}
|
||||
|
||||
fmt.Fprintf(stdout, "daemon: %s (version %s)\n", client.BaseURL, st.Version)
|
||||
fmt.Fprintf(stdout, "queue: %d\n", st.Queue)
|
||||
if st.Current != nil {
|
||||
fmt.Fprintf(stdout, "current: %s %q (Priorität %s)\n", st.Current.ID, st.Current.Text, st.Current.Priority)
|
||||
} else {
|
||||
fmt.Fprintln(stdout, "current: –")
|
||||
}
|
||||
muteState := "aus"
|
||||
if st.Muted {
|
||||
muteState = "an"
|
||||
}
|
||||
fmt.Fprintf(stdout, "mute: %s\n", muteState)
|
||||
fmt.Fprintf(stdout, "provider: %s\n", st.Provider)
|
||||
return exitOK
|
||||
}
|
||||
|
||||
// runCancel implementiert `talk cancel`: POST /cancel — bricht die laufende
|
||||
// Ansage ab und leert die Queue; stdout meldet die Zahl der abgebrochenen
|
||||
// Ansagen. Ohne Daemon gibt es nichts, was talk abbrechen könnte.
|
||||
func runCancel(args []string, opts *sayOptions, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
cfg, err := config.Load(config.Options{Getenv: getenv})
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "talk: %v\n", err)
|
||||
return exitFailure
|
||||
}
|
||||
client := api.NewClient(cfg.Server.Port)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonCallTimeout)
|
||||
defer cancel()
|
||||
n, err := client.Cancel(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "talk: Daemon nicht erreichbar unter %s — nichts zu abbrechen\n", client.BaseURL)
|
||||
return exitFailure
|
||||
}
|
||||
fmt.Fprintf(stdout, "cancelled: %d\n", n)
|
||||
return exitOK
|
||||
}
|
||||
Reference in new issue
Block a user