package main import ( "math" "strings" ) func setJobProgress(job *RecordJob, phase string, pct int) { phase = strings.TrimSpace(phase) phaseLower := strings.ToLower(phase) // clamp pct 0..100 if pct < 0 { pct = 0 } if pct > 100 { pct = 100 } // "globale" Zielbereiche pro Phase (dein Pipeline-Modell) // postwork wartet: 70..72 // remuxing: 72..78 // moving: 78..84 // probe: 84..86 // assets: 86..99 type rng struct{ start, end int } rangeFor := func(ph string) rng { switch ph { case "postwork": return rng{70, 72} case "remuxing": return rng{72, 78} case "moving": return rng{78, 84} case "probe": return rng{84, 86} case "assets": return rng{86, 99} default: return rng{0, 100} } } jobsMu.Lock() defer jobsMu.Unlock() // Sobald Postwork läuft oder Aufnahme beendet ist -> Recorder darf NICHTS mehr überschreiben. inPostwork := job.EndedAt != nil || (strings.TrimSpace(job.Phase) != "" && strings.ToLower(strings.TrimSpace(job.Phase)) != "recording") if inPostwork { // harte Blockade: alte recording-Updates dürfen weder Phase noch Progress anfassen if phaseLower == "" || phaseLower == "recording" { return } } // Phase aktualisieren (aber nur wenn nicht leer) if phase != "" { job.Phase = phase } // Progress-Logik: // - wenn wir in Postwork sind und jemand phasenlokale 0..100 liefert (z.B. remuxing 25), // mappe das in den globalen Bereich der Phase. // - danach: niemals rückwärts. mapped := pct if inPostwork { r := rangeFor(phaseLower) if r.start > 0 && r.end >= r.start { // Wenn pct kleiner ist als unser globaler Einstiegspunkt, interpretieren wir ihn als lokal (0..100) // und mappen in [start..end]. if pct < r.start { width := float64(r.end - r.start) mapped = r.start + int(math.Round((float64(pct)/100.0)*width)) } else { // Wenn schon "global" geliefert wird, trotzdem in den Bereich begrenzen if mapped < r.start { mapped = r.start } if mapped > r.end { mapped = r.end } } } } // niemals rückwärts if mapped < job.Progress { mapped = job.Progress } job.Progress = mapped }