Files
pkgstash/internal/cache/clean.go
T
2026-05-29 03:47:10 -06:00

116 lines
2.7 KiB
Go

package cache
import (
"log/slog"
"path/filepath"
"slices"
"strings"
"syscall"
"time"
"github.com/dustin/go-humanize"
)
type fileEntry struct {
path string
size int64
atime time.Time
}
func (c *Cache) Clean() error {
// age always triggers eviction
// if cache is still over max size after age eviction
// evict until size is sizeThreshold % of max size
sizeThreshold := c.cfg.MaxCacheSize * 3 / 4
cacheSize, cachedFiles, err := c.getCachedFiles()
if err != nil {
return err
}
// remove any pkgs over max age, update cache size while doing
evictBefore := time.Now().Add(-c.cfg.MaxCacheAge)
i := 0
rsize := int64(0)
for _, f := range cachedFiles {
if !f.atime.Before(evictBefore) {
break
}
if err := c.cr.Remove(f.path); err != nil {
slog.Error("failed to remove file: %v", "file", f.path, "err", err)
continue
}
cacheSize -= f.size
rsize += f.size
i++
}
cachedFiles = cachedFiles[i:]
slog.Info("evicted aged out pkgs", "num", i, "size", humanize.Bytes(uint64(rsize)))
// if cache size < max size we're done
if cacheSize < c.cfg.MaxCacheSize {
return nil
}
// remove oldest files until cache size < threshold
i = 0
rsize = 0
for i < len(cachedFiles) && cacheSize > sizeThreshold {
f := cachedFiles[i]
if err := c.cr.Remove(f.path); err != nil {
slog.Error("failed to remove file: %v", "file", f.path, "err", err)
continue
}
cacheSize -= f.size
rsize += f.size
i++
}
slog.Info("evicted aged out pkgs", "num", i, "size", humanize.Bytes(uint64(rsize)))
return nil
}
func (c *Cache) getCachedFiles() (int64, []fileEntry, error) {
// returns total cache size and sorted slice, oldest first,
// of all fileEntry where name is the relative path
// from the cacheroot
cacheSize := int64(0)
cachedFiles := []fileEntry{}
for _, repo := range c.cfg.mirroredRepos {
relPath := filepath.Join(repo, "os/x86_64")
f, err := c.cr.Open(relPath)
files, err := f.ReadDir(-1)
if err != nil {
return 0, nil, err
}
if err := f.Close(); err != nil {
return 0, nil, err
}
for _, f := range files {
fInfo, err := f.Info()
if err != nil {
return 0, nil, err
}
if strings.HasSuffix(f.Name(), ".gz") || strings.HasSuffix(f.Name(), ".db") {
continue
}
name := filepath.Join(repo, repoArch, f.Name())
size := fInfo.Size()
stat_t := fInfo.Sys().(*syscall.Stat_t)
atime := time.Unix(stat_t.Atim.Sec, stat_t.Atim.Nsec)
entry := fileEntry{
path: name,
size: size,
atime: atime,
}
cachedFiles = append(cachedFiles, entry)
cacheSize += size
}
}
slices.SortFunc(cachedFiles, func(a, b fileEntry) int {
return a.atime.Compare(b.atime)
})
return cacheSize, cachedFiles, nil
}