mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
fix(artwork): store backing-file provenance per item, not per hash
This commit is contained in:
parent
c04c8ee02a
commit
1f818e7633
@ -6,8 +6,6 @@ CREATE TABLE artwork (
|
||||
height INTEGER NOT NULL DEFAULT 0,
|
||||
size_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
blur_hash TEXT NOT NULL DEFAULT '',
|
||||
source_path TEXT NOT NULL DEFAULT '',
|
||||
ref_mtime INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
@ -17,6 +15,8 @@ CREATE TABLE item_artwork (
|
||||
image_type TEXT NOT NULL DEFAULT 'primary',
|
||||
hash TEXT NOT NULL DEFAULT '',
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
source_path TEXT NOT NULL DEFAULT '',
|
||||
ref_mtime INTEGER NOT NULL DEFAULT 0,
|
||||
attempted_at TIMESTAMP,
|
||||
updated_at TIMESTAMP,
|
||||
PRIMARY KEY (item_kind, item_id, image_type)
|
||||
|
||||
@ -4,15 +4,13 @@ import "time"
|
||||
|
||||
// Artwork is one unique image, identified by the XXH3-64 hash of its bytes.
|
||||
type Artwork struct {
|
||||
Hash string `structs:"hash"`
|
||||
Mime string `structs:"mime"`
|
||||
Width int `structs:"width"`
|
||||
Height int `structs:"height"`
|
||||
SizeBytes int64 `structs:"size_bytes"`
|
||||
BlurHash string `structs:"blur_hash"`
|
||||
SourcePath string `structs:"source_path"`
|
||||
RefMtime int64 `structs:"ref_mtime"`
|
||||
CreatedAt time.Time `structs:"created_at"`
|
||||
Hash string `structs:"hash"`
|
||||
Mime string `structs:"mime"`
|
||||
Width int `structs:"width"`
|
||||
Height int `structs:"height"`
|
||||
SizeBytes int64 `structs:"size_bytes"`
|
||||
BlurHash string `structs:"blur_hash"`
|
||||
CreatedAt time.Time `structs:"created_at"`
|
||||
}
|
||||
|
||||
const ImageTypePrimary = "primary"
|
||||
@ -24,6 +22,10 @@ type ItemArtwork struct {
|
||||
ImageType string `structs:"image_type"`
|
||||
Hash string `structs:"hash"`
|
||||
Source string `structs:"source"`
|
||||
// SourcePath is the backing file (folder/upload: the image; embedded: the audio file); "" otherwise.
|
||||
SourcePath string `structs:"source_path"`
|
||||
// RefMtime is SourcePath's mtime at resolution; 0 when there is no SourcePath.
|
||||
RefMtime int64 `structs:"ref_mtime"`
|
||||
// attempted_at/updated_at are nullable in the schema but always set by PutItemArtwork;
|
||||
// raw inserts must set them too, since these non-pointer time.Time fields fail to scan NULL.
|
||||
AttemptedAt time.Time `structs:"attempted_at"`
|
||||
|
||||
@ -53,8 +53,7 @@ func (r *artworkRepository) PutImage(a *model.Artwork) error {
|
||||
}
|
||||
// created_at=excluded.created_at: reacquiring an orphan must reset the prune grace window.
|
||||
ins := Insert(r.tableName).SetMap(values).Suffix(`ON CONFLICT (hash) DO UPDATE SET mime=excluded.mime, width=excluded.width,
|
||||
height=excluded.height, size_bytes=excluded.size_bytes, blur_hash=excluded.blur_hash,
|
||||
source_path=excluded.source_path, ref_mtime=excluded.ref_mtime, created_at=excluded.created_at`)
|
||||
height=excluded.height, size_bytes=excluded.size_bytes, blur_hash=excluded.blur_hash, created_at=excluded.created_at`)
|
||||
_, err = r.executeSQL(ins)
|
||||
return err
|
||||
}
|
||||
@ -139,7 +138,7 @@ func (r *artworkRepository) PutItemArtwork(ia *model.ItemArtwork) error {
|
||||
return err
|
||||
}
|
||||
ins := Insert(itemArtworkTable).SetMap(values).Suffix(`ON CONFLICT (item_kind, item_id, image_type) DO UPDATE SET
|
||||
hash=excluded.hash, source=excluded.source,
|
||||
hash=excluded.hash, source=excluded.source, source_path=excluded.source_path, ref_mtime=excluded.ref_mtime,
|
||||
attempted_at=excluded.attempted_at, updated_at=excluded.updated_at`)
|
||||
_, err = r.items.executeSQL(ins)
|
||||
return err
|
||||
|
||||
@ -137,16 +137,20 @@ var _ = Describe("ArtworkRepository", func() {
|
||||
})
|
||||
|
||||
Context("item state", func() {
|
||||
It("upserts and reads state", func() {
|
||||
It("upserts and reads state, including per-item provenance", func() {
|
||||
ia := &model.ItemArtwork{ItemKind: "al", ItemID: "al1", ImageType: model.ImageTypePrimary,
|
||||
Hash: "h1", Source: "folder", AttemptedAt: time.Now()}
|
||||
Hash: "h1", Source: "folder", SourcePath: "/music/a/cover.jpg", RefMtime: 111, AttemptedAt: time.Now()}
|
||||
Expect(repo.PutItemArtwork(ia)).To(Succeed())
|
||||
ia.Source = "embedded"
|
||||
ia.SourcePath = "/music/a/track.mp3"
|
||||
ia.RefMtime = 222
|
||||
Expect(repo.PutItemArtwork(ia)).To(Succeed())
|
||||
|
||||
got, err := repo.GetItemArtwork("al", "al1", model.ImageTypePrimary)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(got.Source).To(Equal("embedded"))
|
||||
Expect(got.SourcePath).To(Equal("/music/a/track.mp3"))
|
||||
Expect(got.RefMtime).To(Equal(int64(222)))
|
||||
Expect(got.UpdatedAt).ToNot(BeZero())
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user