254 lines
6.1 KiB
Go
254 lines
6.1 KiB
Go
// backend\pending_autostart.go
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type PendingAutoStartItem struct {
|
|
ModelKey string `json:"modelKey"`
|
|
URL string `json:"url"`
|
|
Mode string `json:"mode,omitempty"` // wait_public | probe_retry
|
|
NextProbeAtMs int64 `json:"nextProbeAtMs,omitempty"` // nur bei probe_retry
|
|
CurrentShow string `json:"currentShow,omitempty"` // public/private/hidden/away/offline/unknown
|
|
ImageURL string `json:"imageUrl,omitempty"`
|
|
}
|
|
|
|
type PendingAutoStartResponse struct {
|
|
Items []PendingAutoStartItem `json:"items"`
|
|
}
|
|
|
|
type pendingAutoStartFile struct {
|
|
Items []PendingAutoStartItem `json:"items"`
|
|
}
|
|
|
|
var pendingAutoStartMu sync.Mutex
|
|
|
|
func normalizePendingModeServer(v string) string {
|
|
if strings.TrimSpace(strings.ToLower(v)) == "probe_retry" {
|
|
return "probe_retry"
|
|
}
|
|
return "wait_public"
|
|
}
|
|
|
|
func normalizePendingShowServer(v string) string {
|
|
switch strings.TrimSpace(strings.ToLower(v)) {
|
|
case "public":
|
|
return "public"
|
|
case "private":
|
|
return "private"
|
|
case "hidden":
|
|
return "hidden"
|
|
case "away":
|
|
return "away"
|
|
case "offline":
|
|
return "offline"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func safeUserKeyForFile(v string) string {
|
|
re := regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
|
|
return re.ReplaceAllString(v, "_")
|
|
}
|
|
|
|
func pendingAutoStartFilePath(userKey string) string {
|
|
return filepath.Join("data", "pending-autostart", safeUserKeyForFile(userKey)+".json")
|
|
}
|
|
|
|
func loadPendingAutoStartItems(userKey string) ([]PendingAutoStartItem, error) {
|
|
path := pendingAutoStartFilePath(userKey)
|
|
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return []PendingAutoStartItem{}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var f pendingAutoStartFile
|
|
if err := json.Unmarshal(raw, &f); err != nil {
|
|
// kaputte Datei => lieber leer zurückgeben statt alles zu crashen
|
|
return []PendingAutoStartItem{}, nil
|
|
}
|
|
|
|
if f.Items == nil {
|
|
f.Items = []PendingAutoStartItem{}
|
|
}
|
|
|
|
// noch einmal sauber normalisieren
|
|
out := make([]PendingAutoStartItem, 0, len(f.Items))
|
|
for _, it := range f.Items {
|
|
key := strings.ToLower(strings.TrimSpace(it.ModelKey))
|
|
u := strings.TrimSpace(it.URL)
|
|
if key == "" || u == "" {
|
|
continue
|
|
}
|
|
|
|
out = append(out, PendingAutoStartItem{
|
|
ModelKey: key,
|
|
URL: u,
|
|
Mode: normalizePendingModeServer(it.Mode),
|
|
NextProbeAtMs: it.NextProbeAtMs,
|
|
CurrentShow: normalizePendingShowServer(it.CurrentShow),
|
|
ImageURL: strings.TrimSpace(it.ImageURL),
|
|
})
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func savePendingAutoStartItems(userKey string, items []PendingAutoStartItem) error {
|
|
path := pendingAutoStartFilePath(userKey)
|
|
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
payload := pendingAutoStartFile{
|
|
Items: items,
|
|
}
|
|
|
|
raw, err := json.MarshalIndent(payload, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, raw, 0o644); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
func handlePendingAutoStart(auth *AuthManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
sess, _ := auth.getSession(r)
|
|
if sess == nil || !sess.Authed || strings.TrimSpace(sess.User) == "" {
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
userKey := strings.ToLower(strings.TrimSpace(sess.User))
|
|
|
|
pendingAutoStartMu.Lock()
|
|
defer pendingAutoStartMu.Unlock()
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(PendingAutoStartResponse{
|
|
Items: items,
|
|
})
|
|
return
|
|
|
|
case http.MethodPost:
|
|
var in PendingAutoStartItem
|
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
http.Error(w, "invalid json body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
in.ModelKey = strings.ToLower(strings.TrimSpace(in.ModelKey))
|
|
in.URL = strings.TrimSpace(in.URL)
|
|
in.Mode = normalizePendingModeServer(in.Mode)
|
|
in.CurrentShow = normalizePendingShowServer(in.CurrentShow)
|
|
in.ImageURL = strings.TrimSpace(in.ImageURL)
|
|
|
|
if in.ModelKey == "" {
|
|
http.Error(w, "missing modelKey", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if in.URL == "" {
|
|
http.Error(w, "missing url", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
u, err := url.Parse(in.URL)
|
|
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
|
|
http.Error(w, "invalid url", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
replaced := false
|
|
for i := range items {
|
|
if strings.ToLower(strings.TrimSpace(items[i].ModelKey)) == in.ModelKey {
|
|
items[i] = in
|
|
replaced = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !replaced {
|
|
items = append(items, in)
|
|
}
|
|
|
|
if err := savePendingAutoStartItems(userKey, items); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
|
|
case http.MethodDelete:
|
|
modelKey := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("modelKey")))
|
|
if modelKey == "" {
|
|
http.Error(w, "missing modelKey", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
items, err := loadPendingAutoStartItems(userKey)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
next := make([]PendingAutoStartItem, 0, len(items))
|
|
for _, item := range items {
|
|
if strings.ToLower(strings.TrimSpace(item.ModelKey)) == modelKey {
|
|
continue
|
|
}
|
|
next = append(next, item)
|
|
}
|
|
|
|
if err := savePendingAutoStartItems(userKey, next); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
|
|
default:
|
|
w.Header().Set("Allow", "GET, POST, DELETE")
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
}
|
|
}
|