package pbs import ( "context" "sync" "time" "gitea.dooplex.hu/admin/felhom-agent/internal/hub" ) // verify-state constants for the reported PBSSnapshot. const ( VerifyOK = "ok" VerifyFailed = "failed" VerifyNone = "none" // no verify has run yet (PBS omits the verification field) ) // ToHub maps a PBS API Snapshot to the hub.PBSSnapshot wire record (slice 6 Phase B). The // backup-time epoch becomes RFC3339; `encrypted` is derived from the data files' // crypt-mode (any "encrypt" → encrypted); verify_state is "none" until a verify runs. func (s Snapshot) ToHub() hub.PBSSnapshot { ns := s.Namespace if ns == "" { ns = "root" } out := hub.PBSSnapshot{ Namespace: ns, BackupType: s.BackupType, BackupID: s.BackupID, BackupTime: time.Unix(s.BackupTime, 0).UTC().Format(time.RFC3339), SizeBytes: s.Size, Owner: s.Owner, Protected: s.Protected, Encrypted: s.encrypted(), VerifyState: VerifyNone, } if s.Verification != nil { out.VerifyState = s.Verification.State out.VerifyUPID = s.Verification.UPID } return out } // encrypted reports whether the snapshot's DATA is client-side encrypted (any data file with // crypt-mode "encrypt"; index.json is "sign-only" and is ignored). func (s Snapshot) encrypted() bool { for _, f := range s.Files { if f.CryptMode == "encrypt" { return true } } return false } // SnapshotStore holds the latest reported PBS snapshots per datastore — the point-in-time // state the host-report surfaces. The verify loop writes it; the collector reads it via the // hub PBSReporter seam. Mutex-guarded (concurrent collector vs loop). type SnapshotStore struct { mu sync.Mutex byDatastore map[string][]hub.PBSSnapshot } // NewSnapshotStore builds an empty store. func NewSnapshotStore() *SnapshotStore { return &SnapshotStore{byDatastore: map[string][]hub.PBSSnapshot{}} } // Record replaces the snapshot set for a datastore. func (s *SnapshotStore) Record(datastore string, snaps []hub.PBSSnapshot) { s.mu.Lock() defer s.mu.Unlock() s.byDatastore[datastore] = snaps } // Get returns a copy of the last-known-good snapshot set for ONE datastore (nil when absent or // empty). It is the per-datastore fallback the live reporter reaches for when a live list fails — // distinct from PBSSnapshots, which aggregates every datastore. func (s *SnapshotStore) Get(datastore string) []hub.PBSSnapshot { s.mu.Lock() defer s.mu.Unlock() src := s.byDatastore[datastore] if len(src) == 0 { return nil } out := make([]hub.PBSSnapshot, len(src)) copy(out, src) return out } // PBSSnapshots implements hub.PBSReporter — all known snapshots across datastores. func (s *SnapshotStore) PBSSnapshots(context.Context) []hub.PBSSnapshot { s.mu.Lock() defer s.mu.Unlock() out := []hub.PBSSnapshot{} for _, snaps := range s.byDatastore { out = append(out, snaps...) } return out }