feat(backend): immutable cache dir scheme + stale sweep

This commit is contained in:
2026-09-05 00:23:57 +08:00
parent b02ca67d16
commit 4e84a8aeb7
4 changed files with 122 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
package bookfile
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
)
func DirKey(id int64, hash string) string { return fmt.Sprintf("%d-%s", id, hash) }
func CoverDir(cacheDir, key string) string { return filepath.Join(cacheDir, "covers", key) }
func PagesDir(cacheDir, key string) string { return filepath.Join(cacheDir, "pages", key) }
func SweepStale(cacheDir string, live map[string]bool) (int, error) {
removed := 0
for _, kind := range []string{"covers", "pages"} {
entries, err := os.ReadDir(filepath.Join(cacheDir, kind))
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
continue // 首次运行还没有该层目录,不算错
}
return removed, err
}
for _, e := range entries {
if !e.IsDir() || live[e.Name()] {
continue
}
if err := os.RemoveAll(filepath.Join(cacheDir, kind, e.Name())); err != nil {
return removed, err
}
removed++
}
}
return removed, nil
}
+53
View File
@@ -0,0 +1,53 @@
package bookfile
import (
"os"
"path/filepath"
"testing"
)
func mk(t *testing.T, cacheDir, kind, key, file string) {
t.Helper()
d := filepath.Join(cacheDir, kind, key)
os.MkdirAll(d, 0o755)
os.WriteFile(filepath.Join(d, file), []byte("x"), 0o644)
}
func TestSweepStale(t *testing.T) {
dir := t.TempDir()
mk(t, dir, "covers", "1-aaa", "cover.jpg")
mk(t, dir, "covers", "2-bbb", "cover.png")
mk(t, dir, "pages", "1-aaa", "0.jpg")
mk(t, dir, "pages", "3-ccc", "0.jpg")
os.WriteFile(filepath.Join(dir, "covers", "stray.txt"), []byte("x"), 0o644) // 非目录,忽略
n, err := SweepStale(dir, map[string]bool{"1-aaa": true})
if err != nil {
t.Fatal(err)
}
if n != 2 {
t.Fatalf("removed %d want 2", n)
}
if _, err := os.Stat(filepath.Join(dir, "covers", "1-aaa")); err != nil {
t.Fatal("live dir removed")
}
if _, err := os.Stat(filepath.Join(dir, "pages", "1-aaa")); err != nil {
t.Fatal("live pages removed")
}
if _, err := os.Stat(filepath.Join(dir, "covers", "2-bbb")); !os.IsNotExist(err) {
t.Fatal("stale cover remains")
}
// cacheDir 不存在 = 首次运行,不算错
if n, err := SweepStale(filepath.Join(dir, "nope"), nil); n != 0 || err != nil {
t.Fatalf("%d %v", n, err)
}
}
func TestPaths(t *testing.T) {
if DirKey(12, "ab") != "12-ab" {
t.Fatal("DirKey")
}
if CoverDir("/c", "12-ab") != filepath.Join("/c", "covers", "12-ab") {
t.Fatal("CoverDir")
}
}