Files previews: the cache is one database file, <data dir>/thumbs.db

bbolt instead of a tree of hash-named JPEGs: pure Go, so the binary stays static; one
file per machine; an entry carries where the preview came from (engine, codec, size,
when) beside the picture, and a count rides along so the tab can say what it holds
without walking anything. A miss never creates the file, so a machine nobody browses
with previews on never has one; a cache that cannot be opened is logged and previews
are still made and served, just not kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-10-11 16:04:12 -07:00
co-authored by Claude Fable 5.1
parent 51c718e8e3
commit e198471f8c
7 changed files with 259 additions and 68 deletions
+35 -11
View File
@@ -107,30 +107,54 @@ func TestAttempts(t *testing.T) {
}
func TestCache(t *testing.T) {
c := Cache{Dir: t.TempDir()}
c := NewCache(filepath.Join(t.TempDir(), "thumbs.db"))
k := Key("/a", 1, 2)
if _, _, found := c.Get(k); found {
t.Fatal("empty cache found something")
if _, found, err := c.Get(k); found || err != nil {
t.Fatal("an empty cache found something, or complained")
}
if err := c.Put(k, []byte("jpeg")); err != nil {
if _, err := os.Stat(c.Path); err == nil {
t.Fatal("a miss must not create the database")
}
if n, b := c.Stats(); n != 0 || b != 0 {
t.Fatal("stats of nothing")
}
if err := c.Put(k, Entry{Path: "/a", Engine: "cpu", Codec: "png", Width: 640, Height: 360, JPEG: []byte("jpeg")}); err != nil {
t.Fatal(err)
}
if b, _, found := c.Get(k); !found || string(b) != "jpeg" {
t.Fatal("put then get")
e, found, err := c.Get(k)
if err != nil || !found || string(e.JPEG) != "jpeg" || e.Engine != "cpu" || e.Width != 640 || e.Path != "/a" || e.Made.IsZero() {
t.Fatalf("put then get: %+v %v %v", e, found, err)
}
k2 := Key("/b", 1, 2)
if err := c.PutFailure(k2, "no video stream"); err != nil {
if err := c.Put(k2, Entry{Path: "/b", Failure: "no video stream"}); err != nil {
t.Fatal(err)
}
if b, why, found := c.Get(k2); !found || b != nil || why != "no video stream" {
if e, found, _ := c.Get(k2); !found || e.JPEG != nil || e.Failure != "no video stream" {
t.Fatal("a failure is remembered with its reason")
}
if err := c.Put(k, nil); err == nil {
if err := c.Put(k, Entry{}); err == nil {
t.Fatal("an empty preview must not be kept")
}
if left, _ := filepath.Glob(filepath.Join(c.Dir, "*", ".tmp-*")); len(left) != 0 {
t.Fatal("temp files left behind")
// replacing an entry does not count it twice
if err := c.Put(k, Entry{Path: "/a", JPEG: []byte("jpeg2")}); err != nil {
t.Fatal(err)
}
n, b := c.Stats()
if n != 2 || b == 0 {
t.Fatalf("stats = %d entries, %d bytes", n, b)
}
// survives a reopen
if err := c.Close(); err != nil {
t.Fatal(err)
}
c2 := NewCache(c.Path)
if e, found, _ := c2.Get(k); !found || string(e.JPEG) != "jpeg2" {
t.Fatal("not there after reopening")
}
if n, _ := c2.Stats(); n != 2 {
t.Fatal("count lost on reopen")
}
c2.Close()
}
// The real thing, when ffmpeg is here: a PNG and a generated video each come back as a