package services import ( "fmt" "os" "path/filepath" "time" ) // CacheCleanupResult holds the outcome of a cleanup operation. type CacheCleanupResult struct { RemovedCount int `json:"removed_count"` FreedBytes int64 `json:"freed_bytes"` RemovedPaths []string `json:"removed_paths"` } type repoEntry struct { name string path string modTime time.Time size int64 } // CleanupExpiredRepos removes repos older than maxAge days from reposDir. func CleanupExpiredRepos(reposDir string, maxAgeDays int) (*CacheCleanupResult, error) { result := &CacheCleanupResult{} cutoff := time.Now().AddDate(0, 0, -maxAgeDays) entries, err := os.ReadDir(reposDir) if err != nil { if os.IsNotExist(err) { return result, nil } return nil, fmt.Errorf("read repos dir: %w", err) } for _, entry := range entries { if !entry.IsDir() { continue } info, err := entry.Info() if err != nil { continue } if info.ModTime().Before(cutoff) { repoPath := filepath.Join(reposDir, entry.Name()) size := dirSize(repoPath) if err := os.RemoveAll(repoPath); err != nil { continue } result.RemovedCount++ result.FreedBytes += size result.RemovedPaths = append(result.RemovedPaths, entry.Name()) } } return result, nil } // CleanupBySize removes oldest repos until total size is under maxMB. func CleanupBySize(reposDir string, maxMB int) (*CacheCleanupResult, error) { result := &CacheCleanupResult{} maxBytes := int64(maxMB) * 1024 * 1024 entries, err := os.ReadDir(reposDir) if err != nil { if os.IsNotExist(err) { return result, nil } return nil, err } // Collect repo info with mod time var repos []repoEntry var totalSize int64 for _, entry := range entries { if !entry.IsDir() { continue } info, err := entry.Info() if err != nil { continue } repoPath := filepath.Join(reposDir, entry.Name()) size := dirSize(repoPath) repos = append(repos, repoEntry{ name: entry.Name(), path: repoPath, modTime: info.ModTime(), size: size, }) totalSize += size } if totalSize <= maxBytes { return result, nil } // Sort oldest first sortReposByModTime(repos) for _, repo := range repos { if totalSize <= maxBytes { break } if err := os.RemoveAll(repo.path); err != nil { continue } totalSize -= repo.size result.RemovedCount++ result.FreedBytes += repo.size result.RemovedPaths = append(result.RemovedPaths, repo.name) } return result, nil } // GetCacheStats returns current cache statistics. func GetCacheStats(reposDir string) (count int, totalBytes int64) { entries, err := os.ReadDir(reposDir) if err != nil { return 0, 0 } for _, entry := range entries { if !entry.IsDir() { continue } count++ totalBytes += dirSize(filepath.Join(reposDir, entry.Name())) } return } func sortReposByModTime(repos []repoEntry) { for i := 0; i < len(repos); i++ { for j := i + 1; j < len(repos); j++ { if repos[j].modTime.Before(repos[i].modTime) { repos[i], repos[j] = repos[j], repos[i] } } } }