mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
refactor(artwork): unexport artwork.HashImage function
This commit is contained in:
parent
fa879e1576
commit
3a0190dff3
@ -225,7 +225,7 @@ func (s *service) serveResolution(ctx context.Context, res resolution, size int,
|
||||
if err != nil {
|
||||
return nil, ErrUnavailable
|
||||
}
|
||||
hash, err := HashImage(bytes.NewReader(data))
|
||||
hash, err := hashImage(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, ErrUnavailable
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ var _ = Describe("Artwork", func() {
|
||||
// seedFoundStore installs a store-backed found state (bytes in the content-addressed
|
||||
// store, no backing file) and returns the hash.
|
||||
seedFoundStore := func(kind, id string, imgBytes []byte) string {
|
||||
hash, err := HashImage(bytes.NewReader(imgBytes))
|
||||
hash, err := hashImage(bytes.NewReader(imgBytes))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(store.Write(hash, "image/jpeg", bytes.NewReader(imgBytes))).To(Succeed())
|
||||
Expect(artRepo.PutImage(&model.Artwork{Hash: hash, Mime: "image/jpeg"})).To(Succeed())
|
||||
@ -116,7 +116,7 @@ var _ = Describe("Artwork", func() {
|
||||
|
||||
// Delete the store file: a warm resize-cache entry must keep serving without
|
||||
// ever touching the original (the stale-serve self-heal).
|
||||
hash, _ := HashImage(bytes.NewReader(coverBytes))
|
||||
hash, _ := hashImage(bytes.NewReader(coverBytes))
|
||||
Expect(store.Remove(hash, "image/jpeg", time.Now().Add(time.Hour))).To(Succeed())
|
||||
Eventually(func(g Gomega) {
|
||||
img2, err := svc.Get(ctx, model.MustParseArtworkID("al-al1"), 100, false)
|
||||
|
||||
@ -16,14 +16,6 @@ import (
|
||||
"github.com/zeebo/xxh3"
|
||||
)
|
||||
|
||||
func HashImage(r io.Reader) (string, error) {
|
||||
d := xxh3.New()
|
||||
if _, err := io.Copy(d, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%016x", d.Sum64()), nil
|
||||
}
|
||||
|
||||
// ImageStore is the content-addressed store for artwork images that have no
|
||||
// library file backing them (external downloads, embedded extractions, generated).
|
||||
type ImageStore struct {
|
||||
@ -56,6 +48,14 @@ func extForMime(m string) string {
|
||||
return ".img"
|
||||
}
|
||||
|
||||
func hashImage(r io.Reader) (string, error) {
|
||||
d := xxh3.New()
|
||||
if _, err := io.Copy(d, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%016x", d.Sum64()), nil
|
||||
}
|
||||
|
||||
// validHash rejects anything but 16 lowercase hex chars: known-absent states carry "",
|
||||
// and malformed persisted hashes must never reach path sharding (slice panics, separators).
|
||||
func validHash(hash string) bool {
|
||||
|
||||
@ -24,18 +24,18 @@ var _ = Describe("ImageStore", func() {
|
||||
})
|
||||
|
||||
It("hashes deterministically", func() {
|
||||
h1, err := HashImage(bytes.NewReader([]byte("some image bytes")))
|
||||
h1, err := hashImage(bytes.NewReader([]byte("some image bytes")))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
h2, _ := HashImage(bytes.NewReader([]byte("some image bytes")))
|
||||
h2, _ := hashImage(bytes.NewReader([]byte("some image bytes")))
|
||||
Expect(h1).To(Equal(h2))
|
||||
Expect(h1).To(HaveLen(16))
|
||||
h3, _ := HashImage(bytes.NewReader([]byte("other bytes")))
|
||||
h3, _ := hashImage(bytes.NewReader([]byte("other bytes")))
|
||||
Expect(h3).ToNot(Equal(h1))
|
||||
})
|
||||
|
||||
It("writes sharded and reads back", func() {
|
||||
data := []byte("jpeg-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(data))).To(Succeed())
|
||||
|
||||
Expect(filepath.Join(root, h[0:2], h[2:4], h+".jpg")).To(BeAnExistingFile())
|
||||
@ -49,7 +49,7 @@ var _ = Describe("ImageStore", func() {
|
||||
|
||||
It("is idempotent on duplicate writes and preserves the original content", func() {
|
||||
data := []byte("dup")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/png", bytes.NewReader(data))).To(Succeed())
|
||||
// A duplicate write only touches mtime; passing different bytes under the same
|
||||
// hash proves the second reader is never consumed to overwrite the file.
|
||||
@ -65,7 +65,7 @@ var _ = Describe("ImageStore", func() {
|
||||
|
||||
It("refreshes the mtime on a duplicate write", func() {
|
||||
data := []byte("touch-me")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/png", bytes.NewReader(data))).To(Succeed())
|
||||
old := time.Now().Add(-2 * time.Hour)
|
||||
Expect(os.Chtimes(store.path(h, "image/png"), old, old)).To(Succeed())
|
||||
@ -79,7 +79,7 @@ var _ = Describe("ImageStore", func() {
|
||||
|
||||
It("rewrites the bytes when the existing file vanished before the liveness touch", func() {
|
||||
data := []byte("vanishing")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
for range 10 {
|
||||
Expect(store.Write(h, "image/png", bytes.NewReader(data))).To(Succeed())
|
||||
Expect(os.Remove(store.path(h, "image/png"))).To(Succeed())
|
||||
@ -113,11 +113,11 @@ var _ = Describe("ImageStore", func() {
|
||||
|
||||
It("spares a file newer than the cutoff, removes an aged one", func() {
|
||||
fresh := []byte("fresh")
|
||||
hf, _ := HashImage(bytes.NewReader(fresh))
|
||||
hf, _ := hashImage(bytes.NewReader(fresh))
|
||||
Expect(store.Write(hf, "image/jpeg", bytes.NewReader(fresh))).To(Succeed())
|
||||
|
||||
aged := []byte("aged")
|
||||
ha, _ := HashImage(bytes.NewReader(aged))
|
||||
ha, _ := hashImage(bytes.NewReader(aged))
|
||||
Expect(store.Write(ha, "image/jpeg", bytes.NewReader(aged))).To(Succeed())
|
||||
old := time.Now().Add(-2 * time.Hour)
|
||||
Expect(os.Chtimes(store.path(ha, "image/jpeg"), old, old)).To(Succeed())
|
||||
@ -135,10 +135,10 @@ var _ = Describe("ImageStore", func() {
|
||||
|
||||
It("sweeps unknown files, keeps known ones", func() {
|
||||
d1 := []byte("keep-me")
|
||||
h1, _ := HashImage(bytes.NewReader(d1))
|
||||
h1, _ := hashImage(bytes.NewReader(d1))
|
||||
Expect(store.Write(h1, "image/jpeg", bytes.NewReader(d1))).To(Succeed())
|
||||
d2 := []byte("orphan")
|
||||
h2, _ := HashImage(bytes.NewReader(d2))
|
||||
h2, _ := hashImage(bytes.NewReader(d2))
|
||||
Expect(store.Write(h2, "image/jpeg", bytes.NewReader(d2))).To(Succeed())
|
||||
|
||||
old := time.Now().Add(-2 * time.Hour)
|
||||
@ -156,7 +156,7 @@ var _ = Describe("ImageStore", func() {
|
||||
|
||||
It("sweeps a stale mime variant of a known hash, keeps the current one", func() {
|
||||
data := []byte("same-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/png", bytes.NewReader(data))).To(Succeed())
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(data))).To(Succeed())
|
||||
old := time.Now().Add(-2 * time.Hour)
|
||||
@ -178,7 +178,7 @@ var _ = Describe("ImageStore", func() {
|
||||
|
||||
It("keeps young unknown files inside the grace window", func() {
|
||||
d := []byte("fresh-orphan")
|
||||
h, _ := HashImage(bytes.NewReader(d))
|
||||
h, _ := hashImage(bytes.NewReader(d))
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(d))).To(Succeed())
|
||||
|
||||
removed, err := store.Sweep(ctx, time.Now().Add(-time.Hour), func(string, string) bool { return false })
|
||||
|
||||
@ -86,7 +86,7 @@ func (p *processor) acquire(ctx context.Context, item model.ArtworkQueueItem) (o
|
||||
}
|
||||
log.Debug(ctx, "Artwork: Read resolved image", "kind", item.ItemKind, "id", item.ItemID, "source", res.source, "bytes", len(data))
|
||||
|
||||
hash, err := HashImage(bytes.NewReader(data))
|
||||
hash, err := hashImage(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
log.Warn(ctx, "Artwork: Failed to hash image", "kind", item.ItemKind, "id", item.ItemID, err)
|
||||
return outcomeFailed, nil
|
||||
|
||||
@ -76,7 +76,7 @@ var _ = Describe("Prune", func() {
|
||||
|
||||
It("deletes orphan rows and their store files, keeps referenced ones", func() {
|
||||
data := []byte("orphan-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(data))).To(Succeed())
|
||||
old := time.Now().Add(-2 * time.Hour)
|
||||
Expect(os.Chtimes(store.path(h, "image/jpeg"), old, old)).To(Succeed())
|
||||
@ -85,7 +85,7 @@ var _ = Describe("Prune", func() {
|
||||
awRepo.OrphanHashes = []string{h}
|
||||
|
||||
kept := []byte("kept-bytes")
|
||||
hk, _ := HashImage(bytes.NewReader(kept))
|
||||
hk, _ := hashImage(bytes.NewReader(kept))
|
||||
Expect(store.Write(hk, "image/jpeg", bytes.NewReader(kept))).To(Succeed())
|
||||
Expect(awRepo.PutImage(&model.Artwork{Hash: hk, Mime: "image/jpeg"})).To(Succeed())
|
||||
|
||||
@ -102,7 +102,7 @@ var _ = Describe("Prune", func() {
|
||||
|
||||
It("spares a candidate reacquired between snapshot and delete", func() {
|
||||
data := []byte("reacquired-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(data))).To(Succeed())
|
||||
Expect(awRepo.PutImage(&model.Artwork{Hash: h, Mime: "image/jpeg"})).To(Succeed())
|
||||
ageArtwork(h, time.Now().Add(-2*time.Hour))
|
||||
@ -122,7 +122,7 @@ var _ = Describe("Prune", func() {
|
||||
|
||||
It("spares a candidate whose row was freshly recreated (created_at inside the grace window)", func() {
|
||||
data := []byte("fresh-reacquired-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(data))).To(Succeed())
|
||||
// Reacquisition refreshed created_at after the snapshot; still unreferenced.
|
||||
Expect(awRepo.PutImage(&model.Artwork{Hash: h, Mime: "image/jpeg"})).To(Succeed())
|
||||
@ -139,7 +139,7 @@ var _ = Describe("Prune", func() {
|
||||
|
||||
It("spares an orphan file freshly touched by an overlapping acquisition", func() {
|
||||
data := []byte("racing-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(data))).To(Succeed())
|
||||
Expect(awRepo.PutImage(&model.Artwork{Hash: h, Mime: "image/jpeg"})).To(Succeed())
|
||||
ageArtwork(h, time.Now().Add(-2*time.Hour))
|
||||
@ -156,7 +156,7 @@ var _ = Describe("Prune", func() {
|
||||
|
||||
It("sweeps store files that have no artwork row", func() {
|
||||
stray := []byte("no-row-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(stray))
|
||||
h, _ := hashImage(bytes.NewReader(stray))
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(stray))).To(Succeed())
|
||||
old := time.Now().Add(-2 * time.Hour)
|
||||
Expect(os.Chtimes(store.path(h, "image/jpeg"), old, old)).To(Succeed())
|
||||
@ -169,7 +169,7 @@ var _ = Describe("Prune", func() {
|
||||
|
||||
It("sweeps an obsolete mime variant of a reacquired hash", func() {
|
||||
data := []byte("variant-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/png", bytes.NewReader(data))).To(Succeed())
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(data))).To(Succeed())
|
||||
old := time.Now().Add(-2 * time.Hour)
|
||||
@ -195,14 +195,14 @@ var _ = Describe("Prune", func() {
|
||||
old := time.Now().Add(-2 * time.Hour)
|
||||
|
||||
blocked := []byte("blocked-bytes")
|
||||
hb, _ := HashImage(bytes.NewReader(blocked))
|
||||
hb, _ := hashImage(bytes.NewReader(blocked))
|
||||
Expect(store.Write(hb, "image/jpeg", bytes.NewReader(blocked))).To(Succeed())
|
||||
Expect(os.Chtimes(store.path(hb, "image/jpeg"), old, old)).To(Succeed())
|
||||
Expect(awRepo.PutImage(&model.Artwork{Hash: hb, Mime: "image/jpeg"})).To(Succeed())
|
||||
ageArtwork(hb, old)
|
||||
|
||||
good := []byte("good-bytes")
|
||||
hg, _ := HashImage(bytes.NewReader(good))
|
||||
hg, _ := hashImage(bytes.NewReader(good))
|
||||
Expect(store.Write(hg, "image/jpeg", bytes.NewReader(good))).To(Succeed())
|
||||
Expect(os.Chtimes(store.path(hg, "image/jpeg"), old, old)).To(Succeed())
|
||||
Expect(awRepo.PutImage(&model.Artwork{Hash: hg, Mime: "image/jpeg"})).To(Succeed())
|
||||
@ -242,7 +242,7 @@ var _ = Describe("Prune", func() {
|
||||
ds.MockedArtwork = &flakyGetArtworkRepo{MockArtworkRepo: tests.CreateMockArtworkRepo()}
|
||||
|
||||
data := []byte("live-bytes")
|
||||
h, _ := HashImage(bytes.NewReader(data))
|
||||
h, _ := hashImage(bytes.NewReader(data))
|
||||
Expect(store.Write(h, "image/jpeg", bytes.NewReader(data))).To(Succeed())
|
||||
|
||||
Expect(prune(context.Background(), ds, store)).ToNot(Succeed())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user