package models import ( "strings" "sync" "time" ) // RunState is the in-memory, non-persisted status of a backup target's current // or most recent run. It intentionally lives outside cfg/persist(): a crash or // restart should never leave a stale "still running" flag behind. type RunState struct { Running bool `json:"running"` TargetID string `json:"target_id"` Method BackupMethod `json:"method,omitempty"` Percent float64 `json:"percent"` ETA string `json:"eta,omitempty"` Message string `json:"message,omitempty"` StartedAt *time.Time `json:"started_at,omitempty"` } var ( progressMu sync.RWMutex progress = map[string]RunState{} ) // progressKey builds the scoped key a target's run state is stored under. // scope is "node" or "svc:". func progressKey(scope, targetID string) string { return scope + ":" + targetID } func setProgress(key string, s RunState) { progressMu.Lock() defer progressMu.Unlock() progress[key] = s } // GetProgress returns the current run state for a single target, if any is known. func GetProgress(scope, targetID string) (RunState, bool) { progressMu.RLock() defer progressMu.RUnlock() s, ok := progress[progressKey(scope, targetID)] return s, ok } // GetProgressForScope returns all known run states for a scope ("node" or "svc:"). func GetProgressForScope(scope string) []RunState { prefix := scope + ":" progressMu.RLock() defer progressMu.RUnlock() result := make([]RunState, 0) for k, s := range progress { if strings.HasPrefix(k, prefix) { result = append(result, s) } } return result }