mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
141 lines
3.6 KiB
Go
141 lines
3.6 KiB
Go
// Package service — generic on-disk cleanup helper used by the
|
|
// scheduler. Public so handlers can call it for "purge transcode cache
|
|
// now" buttons.
|
|
package service
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// walkAndPrune recursively deletes every file under root whose mtime is
|
|
// older than cutoff. Empty directories left behind are removed too.
|
|
// Best-effort: per-file errors are ignored so a single permission denial
|
|
// doesn't abort the cleanup.
|
|
func walkAndPrune(root string, cutoff time.Time) error {
|
|
if root == "" {
|
|
return nil
|
|
}
|
|
if _, err := os.Stat(root); err != nil {
|
|
return nil // nothing to clean
|
|
}
|
|
dirs := []string{}
|
|
_ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if info.IsDir() {
|
|
if path != root {
|
|
dirs = append(dirs, path)
|
|
}
|
|
return nil
|
|
}
|
|
if info.ModTime().Before(cutoff) {
|
|
_ = os.Remove(path) // #nosec G122 -- cache pruning is best-effort under the configured cache root.
|
|
}
|
|
return nil
|
|
})
|
|
// Remove emptied directories from deepest to shallowest.
|
|
for i := len(dirs) - 1; i >= 0; i-- {
|
|
_ = os.Remove(dirs[i])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PruneImageCacheResult holds stats from an image cache prune operation.
|
|
type PruneImageCacheResult struct {
|
|
TotalFilesBefore int
|
|
TotalBytesBefore int64
|
|
DeletedFiles int
|
|
FreedBytes int64
|
|
RemainingBytes int64
|
|
}
|
|
|
|
type imageCacheFileEntry struct {
|
|
path string
|
|
size int64
|
|
modTime time.Time
|
|
}
|
|
|
|
// PruneImageCache scans imagesDir for cached image files. If the total disk usage
|
|
// exceeds maxSizeBytes, it removes files starting from the oldest (by ModTime)
|
|
// until disk usage falls to or below targetSizeBytes (80% of maxSizeBytes).
|
|
//
|
|
// In-flight temporary files (*.tmp) are skipped to avoid corrupting concurrent writes.
|
|
// Empty subdirectories left behind are best-effort removed.
|
|
func PruneImageCache(imagesDir string, maxSizeBytes int64) (PruneImageCacheResult, error) {
|
|
var result PruneImageCacheResult
|
|
if imagesDir == "" || maxSizeBytes <= 0 {
|
|
return result, nil
|
|
}
|
|
if _, err := os.Stat(imagesDir); err != nil {
|
|
return result, nil
|
|
}
|
|
|
|
var (
|
|
dirs []string
|
|
entries []imageCacheFileEntry
|
|
)
|
|
|
|
_ = filepath.Walk(imagesDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if info.IsDir() {
|
|
if path != imagesDir {
|
|
dirs = append(dirs, path)
|
|
}
|
|
return nil
|
|
}
|
|
// Skip temporary files created during image download.
|
|
name := info.Name()
|
|
if strings.HasSuffix(name, ".tmp") || strings.HasPrefix(name, "img-") && strings.Contains(name, ".tmp") {
|
|
return nil
|
|
}
|
|
size := info.Size()
|
|
result.TotalFilesBefore++
|
|
result.TotalBytesBefore += size
|
|
entries = append(entries, imageCacheFileEntry{
|
|
path: path,
|
|
size: size,
|
|
modTime: info.ModTime(),
|
|
})
|
|
return nil
|
|
})
|
|
|
|
result.RemainingBytes = result.TotalBytesBefore
|
|
if result.TotalBytesBefore <= maxSizeBytes {
|
|
return result, nil
|
|
}
|
|
|
|
// High/Low watermark: prune down to 80% of max size to leave headroom
|
|
// and prevent disk thrashing on consecutive writes.
|
|
targetSizeBytes := maxSizeBytes * 80 / 100
|
|
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
return entries[i].modTime.Before(entries[j].modTime)
|
|
})
|
|
|
|
for _, entry := range entries {
|
|
if result.RemainingBytes <= targetSizeBytes {
|
|
break
|
|
}
|
|
if err := os.Remove(entry.path); err == nil || errors.Is(err, os.ErrNotExist) {
|
|
result.DeletedFiles++
|
|
result.FreedBytes += entry.size
|
|
result.RemainingBytes -= entry.size
|
|
}
|
|
}
|
|
|
|
// Clean up emptied subdirectories from deepest to shallowest.
|
|
for i := len(dirs) - 1; i >= 0; i-- {
|
|
_ = os.Remove(dirs[i])
|
|
}
|
|
|
|
return result, nil
|
|
}
|