package jetstream import ( "sync" "time" ) // HoldRepoStats represents stats for a single owner+repo from a specific hold type HoldRepoStats struct { OwnerDID string Repository string PullCount int64 PushCount int64 LastPull *time.Time LastPush *time.Time } // StatsCache provides in-memory caching of per-hold stats with aggregation // This allows summing stats across multiple holds for the same owner+repo type StatsCache struct { mu sync.RWMutex // holdDID -> (ownerDID/repo -> stats) holds map[string]map[string]*HoldRepoStats } // NewStatsCache creates a new in-memory stats cache func NewStatsCache() *StatsCache { return &StatsCache{ holds: make(map[string]map[string]*HoldRepoStats), } } // makeKey creates a cache key from ownerDID and repository func makeKey(ownerDID, repo string) string { return ownerDID + "/" + repo } // Update stores or updates stats for a hold+owner+repo combination func (c *StatsCache) Update(holdDID, ownerDID, repo string, pullCount, pushCount int64, lastPull, lastPush *time.Time) { c.mu.Lock() defer c.mu.Unlock() // Ensure hold map exists if c.holds[holdDID] == nil { c.holds[holdDID] = make(map[string]*HoldRepoStats) } key := makeKey(ownerDID, repo) c.holds[holdDID][key] = &HoldRepoStats{ OwnerDID: ownerDID, Repository: repo, PullCount: pullCount, PushCount: pushCount, LastPull: lastPull, LastPush: lastPush, } } // Delete removes stats for a hold+owner+repo combination func (c *StatsCache) Delete(holdDID, ownerDID, repo string) { c.mu.Lock() defer c.mu.Unlock() if c.holds[holdDID] != nil { key := makeKey(ownerDID, repo) delete(c.holds[holdDID], key) } } // GetAggregated returns aggregated stats for an owner+repo by summing across all holds // Returns (pullCount, pushCount, lastPull, lastPush) func (c *StatsCache) GetAggregated(ownerDID, repo string) (int64, int64, *time.Time, *time.Time) { c.mu.RLock() defer c.mu.RUnlock() key := makeKey(ownerDID, repo) var totalPull, totalPush int64 var latestPull, latestPush *time.Time for _, holdStats := range c.holds { if stats, ok := holdStats[key]; ok { totalPull += stats.PullCount totalPush += stats.PushCount // Track latest timestamps if stats.LastPull != nil { if latestPull == nil || stats.LastPull.After(*latestPull) { latestPull = stats.LastPull } } if stats.LastPush != nil { if latestPush == nil || stats.LastPush.After(*latestPush) { latestPush = stats.LastPush } } } } return totalPull, totalPush, latestPull, latestPush }