package cache import ( "fmt" "log" "sync" "time" ) // CacheEntry represents a cached item with expiration type cacheEntry struct { data interface{} expiration time.Time } // CVCache provides thread-safe caching for CV and UI data type CVCache struct { mu sync.RWMutex entries map[string]*cacheEntry ttl time.Duration stats CacheStats } // CacheStats tracks cache performance metrics type CacheStats struct { mu sync.RWMutex Hits int64 Misses int64 Size int } // New creates a new CVCache with the specified TTL func New(ttl time.Duration) *CVCache { if ttl <= 0 { ttl = 1 * time.Hour // Default TTL } cache := &CVCache{ entries: make(map[string]*cacheEntry), ttl: ttl, stats: CacheStats{}, } // Start background cleanup goroutine go cache.cleanupExpired() return cache } // Get retrieves an item from the cache func (c *CVCache) Get(key string) (interface{}, bool) { c.mu.RLock() entry, exists := c.entries[key] c.mu.RUnlock() if !exists { c.recordMiss() return nil, false } // Check if expired if time.Now().After(entry.expiration) { c.mu.Lock() delete(c.entries, key) c.mu.Unlock() c.recordMiss() return nil, false } c.recordHit() return entry.data, true } // Set stores an item in the cache with TTL func (c *CVCache) Set(key string, data interface{}) { c.mu.Lock() defer c.mu.Unlock() c.entries[key] = &cacheEntry{ data: data, expiration: time.Now().Add(c.ttl), } c.updateSize() } // Invalidate removes a specific key from cache func (c *CVCache) Invalidate(key string) { c.mu.Lock() defer c.mu.Unlock() delete(c.entries, key) c.updateSize() } // InvalidateAll clears the entire cache func (c *CVCache) InvalidateAll() { c.mu.Lock() defer c.mu.Unlock() c.entries = make(map[string]*cacheEntry) c.updateSize() log.Println("๐Ÿ—‘๏ธ Cache invalidated") } // Warm preloads cache with specific keys func (c *CVCache) Warm(key string, loader func() (interface{}, error)) error { data, err := loader() if err != nil { return fmt.Errorf("cache warm failed for key %s: %w", key, err) } c.Set(key, data) log.Printf("๐Ÿ”ฅ Cache warmed: %s", key) return nil } // GetStats returns current cache statistics func (c *CVCache) GetStats() CacheStats { c.stats.mu.RLock() defer c.stats.mu.RUnlock() return CacheStats{ Hits: c.stats.Hits, Misses: c.stats.Misses, Size: c.stats.Size, } } // HitRate returns the cache hit rate as a percentage func (c *CVCache) HitRate() float64 { stats := c.GetStats() total := stats.Hits + stats.Misses if total == 0 { return 0 } return (float64(stats.Hits) / float64(total)) * 100 } // recordHit increments the hit counter func (c *CVCache) recordHit() { c.stats.mu.Lock() c.stats.Hits++ c.stats.mu.Unlock() } // recordMiss increments the miss counter func (c *CVCache) recordMiss() { c.stats.mu.Lock() c.stats.Misses++ c.stats.mu.Unlock() } // updateSize updates the cached size count func (c *CVCache) updateSize() { c.stats.mu.Lock() c.stats.Size = len(c.entries) c.stats.mu.Unlock() } // cleanupExpired periodically removes expired entries func (c *CVCache) cleanupExpired() { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for range ticker.C { c.mu.Lock() now := time.Now() removed := 0 for key, entry := range c.entries { if now.After(entry.expiration) { delete(c.entries, key) removed++ } } if removed > 0 { c.updateSize() log.Printf("๐Ÿงน Cache cleanup: removed %d expired entries", removed) } c.mu.Unlock() } } // BuildKey creates a consistent cache key func BuildKey(prefix, lang string) string { return fmt.Sprintf("%s:%s", prefix, lang) }