nsfwapp/backend/autostart_pause.go
2026-01-13 14:00:05 +01:00

223 lines
4.9 KiB
Go

// backend\autostart_pause.go
package main
import (
"encoding/json"
"io"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
)
var autostartPaused int32 // 0=false, 1=true
// --- SSE subscribers für Autostart-State ---
var autostartSubsMu sync.Mutex
var autostartSubs = map[chan bool]struct{}{}
func broadcastAutostartPaused(paused bool) {
autostartSubsMu.Lock()
defer autostartSubsMu.Unlock()
for ch := range autostartSubs {
// non-blocking: wenn Client langsam ist, droppen wir Updates (neuester Zustand kommt eh wieder)
select {
case ch <- paused:
default:
}
}
}
func isAutostartPaused() bool {
return atomic.LoadInt32(&autostartPaused) == 1
}
func setAutostartPaused(v bool) {
old := isAutostartPaused()
if v {
atomic.StoreInt32(&autostartPaused, 1)
} else {
atomic.StoreInt32(&autostartPaused, 0)
}
// nur wenn sich der Zustand wirklich geändert hat -> pushen
if old != v {
broadcastAutostartPaused(v)
}
}
type autostartPauseReq struct {
Paused *bool `json:"paused"`
}
func autostartPauseHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
_ = json.NewEncoder(w).Encode(map[string]any{
"paused": isAutostartPaused(),
})
return
case http.MethodPost:
// erlaubt: JSON body { "paused": true/false }
// oder Query ?paused=1/0/true/false
var val *bool
ct := strings.ToLower(r.Header.Get("Content-Type"))
if strings.Contains(ct, "application/json") {
var req autostartPauseReq
_ = json.NewDecoder(r.Body).Decode(&req)
val = req.Paused
}
if val == nil {
q := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("paused")))
if q != "" {
b := q == "1" || q == "true" || q == "yes" || q == "on"
val = &b
}
}
if val == nil {
http.Error(w, "missing 'paused' (json body or query)", http.StatusBadRequest)
return
}
setAutostartPaused(*val)
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
_ = json.NewEncoder(w).Encode(map[string]any{
"paused": isAutostartPaused(),
})
return
default:
http.Error(w, "Nur GET/POST erlaubt", http.StatusMethodNotAllowed)
return
}
}
func writeAutostartState(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
_ = json.NewEncoder(w).Encode(map[string]any{
"paused": isAutostartPaused(),
})
}
// GET /api/autostart/state
func autostartStateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Nur GET erlaubt", http.StatusMethodNotAllowed)
return
}
writeAutostartState(w)
}
// GET/POST /api/autostart/pause (POST ohne Body pausiert)
func autostartPauseQuickHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
writeAutostartState(w)
return
case http.MethodPost:
setAutostartPaused(true)
writeAutostartState(w)
return
default:
http.Error(w, "Nur GET/POST erlaubt", http.StatusMethodNotAllowed)
return
}
}
// POST /api/autostart/resume (optional auch GET)
func autostartResumeHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
writeAutostartState(w)
return
case http.MethodPost:
setAutostartPaused(false)
writeAutostartState(w)
return
default:
http.Error(w, "Nur GET/POST erlaubt", http.StatusMethodNotAllowed)
return
}
}
// GET /api/autostart/state/stream (SSE)
func autostartStateStreamHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Nur GET erlaubt", http.StatusMethodNotAllowed)
return
}
fl, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming nicht unterstützt", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no") // wichtig falls Proxy/Nginx
ch := make(chan bool, 1)
// subscribe
autostartSubsMu.Lock()
autostartSubs[ch] = struct{}{}
autostartSubsMu.Unlock()
// cleanup
defer func() {
autostartSubsMu.Lock()
delete(autostartSubs, ch)
autostartSubsMu.Unlock()
close(ch)
}()
send := func(paused bool) {
payload := map[string]any{
"paused": paused,
"ts": time.Now().UTC().Format(time.RFC3339Nano),
}
b, _ := json.Marshal(payload)
_, _ = io.WriteString(w, "event: autostart\n")
_, _ = io.WriteString(w, "data: ")
_, _ = w.Write(b)
_, _ = io.WriteString(w, "\n\n")
fl.Flush()
}
// initial state sofort senden
send(isAutostartPaused())
ctx := r.Context()
hb := time.NewTicker(15 * time.Second)
defer hb.Stop()
for {
select {
case <-ctx.Done():
return
case v := <-ch:
send(v)
case <-hb.C:
// heartbeat gegen Proxy timeouts
_, _ = io.WriteString(w, ": keep-alive\n\n")
fl.Flush()
}
}
}