From 84489a572a41e645e7f99f39665e6257584c4791 Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Sat, 27 Dec 2025 03:21:24 -0600 Subject: [PATCH] Add avatar cache management and cleanup functionality - Introduced constants for avatar cache limit and cleanup interval. - Implemented a background cleanup process to manage the avatar cache size, removing the oldest files when the limit is exceeded. - Updated the AvatarHandler to refresh the modification time of cached avatars for better cache management. --- internal/api/constants.go | 6 ++++ internal/api/handlers.go | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/internal/api/constants.go b/internal/api/constants.go index a3f09dc..ba8ae6a 100644 --- a/internal/api/constants.go +++ b/internal/api/constants.go @@ -1,5 +1,7 @@ package api +import "time" + const ( CompressionLevel = 5 @@ -7,4 +9,8 @@ const ( PrivacyFile = "privacy.txt" TermsFile = "terms.txt" DisclaimerFile = "disclaimer.txt" + + // Avatar Cache + AvatarCacheLimit = 100 * 1024 * 1024 // 100MB + AvatarCacheInterval = 1 * time.Hour ) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 063ccab..d63e267 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -83,9 +83,69 @@ func NewServer(token string, initialSoftware []models.Software, statsService *st log.Printf("Warning: failed to create avatar cache directory: %v", err) } + go s.startAvatarCleanup() + return s } +func (s *Server) startAvatarCleanup() { + ticker := time.NewTicker(AvatarCacheInterval) + for range ticker.C { + s.cleanupAvatarCache() + } +} + +func (s *Server) cleanupAvatarCache() { + files, err := os.ReadDir(s.avatarCache) + if err != nil { + return + } + + var totalSize int64 + type fileInfo struct { + path string + size int64 + mod time.Time + } + var infos []fileInfo + + for _, f := range files { + if f.IsDir() { + continue + } + path := filepath.Join(s.avatarCache, f.Name()) + info, err := f.Info() + if err != nil { + continue + } + totalSize += info.Size() + infos = append(infos, fileInfo{ + path: path, + size: info.Size(), + mod: info.ModTime(), + }) + } + + if totalSize <= AvatarCacheLimit { + return + } + + // Sort by modification time (oldest first) + sort.Slice(infos, func(i, j int) bool { + return infos[i].mod.Before(infos[j].mod) + }) + + for _, info := range infos { + if totalSize <= AvatarCacheLimit { + break + } + if err := os.Remove(info.path); err == nil { + totalSize -= info.size + } + } + log.Printf("Avatar cache cleaned up. Current size: %v bytes", totalSize) +} + func (s *Server) RegisterURL(targetURL string) string { h := sha256.New() h.Write(s.salt) @@ -304,6 +364,10 @@ func (s *Server) AvatarHandler(w http.ResponseWriter, r *http.Request) { cachePath := filepath.Join(s.avatarCache, id) if _, err := os.Stat(cachePath); err == nil { + // Update modification time for LRU cleanup + now := time.Now() + _ = os.Chtimes(cachePath, now, now) + // Serve from cache w.Header().Set("Cache-Control", "public, max-age=86400") http.ServeFile(w, r, cachePath)