144 lines
3.0 KiB
Go
144 lines
3.0 KiB
Go
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]
|
|
}
|
|
}
|
|
}
|
|
}
|