// backend\tasks_status.go package main import ( "encoding/json" "net/http" "strings" "sync" "time" ) type CleanupTaskState struct { Queued bool `json:"queued"` Running bool `json:"running"` QueuedAt time.Time `json:"queuedAt"` StartedAt time.Time `json:"startedAt"` FinishedAt *time.Time `json:"finishedAt,omitempty"` Text string `json:"text,omitempty"` Error string `json:"error,omitempty"` CurrentFile string `json:"currentFile,omitempty"` Done int `json:"done"` Total int `json:"total"` } type CheckVideosTaskState struct { Queued bool `json:"queued"` Running bool `json:"running"` QueuedAt time.Time `json:"queuedAt"` StartedAt time.Time `json:"startedAt"` FinishedAt *time.Time `json:"finishedAt,omitempty"` Text string `json:"text,omitempty"` Error string `json:"error,omitempty"` CurrentFile string `json:"currentFile,omitempty"` Done int `json:"done"` Total int `json:"total"` } type RegenerateAssetsTaskState struct { Running bool `json:"running"` File string `json:"file,omitempty"` Text string `json:"text,omitempty"` Error string `json:"error,omitempty"` } type TasksStatusResponse struct { GenerateAssets AssetsTaskState `json:"generateAssets"` RegenerateAssets RegenerateAssetsTaskState `json:"regenerateAssets"` RegenerateAssetsJobs []map[string]any `json:"regenerateAssetsJobs"` CheckVideos CheckVideosTaskState `json:"checkVideos"` Cleanup CleanupTaskState `json:"cleanup"` AnyRunning bool `json:"anyRunning"` } var ( cleanupTaskMu sync.Mutex cleanupTaskState CleanupTaskState regenerateAssetsTaskMu sync.Mutex regenerateAssetsTaskState RegenerateAssetsTaskState ) func cleanupNowTime() *time.Time { t := time.Now() return &t } func anyToString(v any) string { switch x := v.(type) { case string: return x case []byte: return string(x) default: return "" } } func snapshotRegenerateAssetsTaskState() RegenerateAssetsTaskState { regenerateAssetsTaskMu.Lock() st := regenerateAssetsTaskState regenerateAssetsTaskMu.Unlock() return st } func setRegenerateAssetsTaskRunning(file, text string) { regenerateAssetsTaskMu.Lock() regenerateAssetsTaskState = RegenerateAssetsTaskState{ Running: true, File: strings.TrimSpace(file), Text: strings.TrimSpace(text), Error: "", } regenerateAssetsTaskMu.Unlock() publishTaskState() } func setRegenerateAssetsTaskDone(file, text string) { regenerateAssetsTaskMu.Lock() regenerateAssetsTaskState = RegenerateAssetsTaskState{ Running: false, File: strings.TrimSpace(file), Text: strings.TrimSpace(text), Error: "", } regenerateAssetsTaskMu.Unlock() publishTaskState() } func setRegenerateAssetsTaskError(file, errText string) { regenerateAssetsTaskMu.Lock() regenerateAssetsTaskState = RegenerateAssetsTaskState{ Running: false, File: strings.TrimSpace(file), Text: "", Error: strings.TrimSpace(errText), } regenerateAssetsTaskMu.Unlock() publishTaskState() } func clearRegenerateAssetsTaskState() { regenerateAssetsTaskMu.Lock() regenerateAssetsTaskState = RegenerateAssetsTaskState{} regenerateAssetsTaskMu.Unlock() publishTaskState() } func snapshotCleanupTaskState() CleanupTaskState { cleanupTaskMu.Lock() st := cleanupTaskState cleanupTaskMu.Unlock() return st } func setCleanupTaskQueued(text string) { cleanupTaskMu.Lock() cleanupTaskState = CleanupTaskState{ Queued: true, Running: false, QueuedAt: time.Now(), StartedAt: time.Time{}, FinishedAt: nil, Text: strings.TrimSpace(text), Error: "", CurrentFile: "", Done: 0, Total: 0, } cleanupTaskMu.Unlock() publishTaskState() } func setCleanupTaskRunning(text string) { cleanupTaskMu.Lock() now := time.Now() queuedAt := cleanupTaskState.QueuedAt if queuedAt.IsZero() { queuedAt = now } cleanupTaskState = CleanupTaskState{ Queued: false, Running: true, QueuedAt: queuedAt, StartedAt: now, FinishedAt: nil, Text: strings.TrimSpace(text), Error: "", CurrentFile: "", Done: 0, Total: 0, } cleanupTaskMu.Unlock() publishTaskState() } func setCleanupTaskProgress(done, total int, currentFile, text string) { cleanupTaskMu.Lock() now := time.Now() if cleanupTaskState.QueuedAt.IsZero() { cleanupTaskState.QueuedAt = now } if cleanupTaskState.StartedAt.IsZero() { cleanupTaskState.StartedAt = now } cleanupTaskState.Queued = false cleanupTaskState.Running = true cleanupTaskState.Done = done cleanupTaskState.Total = total cleanupTaskState.CurrentFile = strings.TrimSpace(currentFile) cleanupTaskState.Text = strings.TrimSpace(text) cleanupTaskState.Error = "" cleanupTaskState.FinishedAt = nil cleanupTaskMu.Unlock() publishTaskState() } func setCleanupTaskDone(text string) { cleanupTaskMu.Lock() cleanupTaskState.Queued = false cleanupTaskState.Running = false cleanupTaskState.Text = strings.TrimSpace(text) cleanupTaskState.CurrentFile = "" cleanupTaskState.Error = "" cleanupTaskState.FinishedAt = cleanupNowTime() if cleanupTaskState.Total > 0 { cleanupTaskState.Done = cleanupTaskState.Total } cleanupTaskMu.Unlock() publishTaskState() } func setCleanupTaskError(errText string) { cleanupTaskMu.Lock() cleanupTaskState.Queued = false cleanupTaskState.Running = false cleanupTaskState.Text = "" cleanupTaskState.Error = strings.TrimSpace(errText) cleanupTaskState.CurrentFile = "" cleanupTaskState.FinishedAt = cleanupNowTime() cleanupTaskMu.Unlock() publishTaskState() } func tasksStatusHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Nur GET erlaubt", http.StatusMethodNotAllowed) return } assets := getGenerateAssetsTaskStatus() regenerate := snapshotRegenerateAssetsTaskState() regenerateJobs := getRegenerateAssetsJobsSnapshot() checkVideos := snapshotCheckVideosTaskState() cleanup := snapshotCleanupTaskState() anyRegenerateRunning := regenerate.Running if !anyRegenerateRunning { for _, job := range regenerateJobs { state := strings.TrimSpace(strings.ToLower(anyToString(job["state"]))) if state == "queued" || state == "running" { anyRegenerateRunning = true break } } } resp := TasksStatusResponse{ GenerateAssets: assets, RegenerateAssets: regenerate, RegenerateAssetsJobs: regenerateJobs, CheckVideos: checkVideos, Cleanup: cleanup, AnyRunning: assets.Queued || assets.Running || anyRegenerateRunning || checkVideos.Queued || checkVideos.Running || cleanup.Queued || cleanup.Running, } w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") _ = json.NewEncoder(w).Encode(resp) }