mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
feat(persistence): add UpdateBlurHash targeted update to album/artist/playlist repos
This commit is contained in:
parent
a115726e71
commit
f763ebff5b
@ -1,9 +1,10 @@
|
||||
-- +goose Up
|
||||
alter table album add column blur_hash varchar;
|
||||
-- blur_hash is not null default '' so NULLs never reach the Go string field; '' means "not computed".
|
||||
alter table album add column blur_hash varchar not null default '';
|
||||
alter table album add column blur_hash_updated_at datetime;
|
||||
alter table artist add column blur_hash varchar;
|
||||
alter table artist add column blur_hash varchar not null default '';
|
||||
alter table artist add column blur_hash_updated_at datetime;
|
||||
alter table playlist add column blur_hash varchar;
|
||||
alter table playlist add column blur_hash varchar not null default '';
|
||||
alter table playlist add column blur_hash_updated_at datetime;
|
||||
|
||||
-- +goose Down
|
||||
|
||||
@ -156,6 +156,7 @@ type AlbumRepository interface {
|
||||
Exists(id string) (bool, error)
|
||||
Put(*Album) error
|
||||
UpdateExternalInfo(*Album) error
|
||||
UpdateBlurHash(id string, blurHash string, artworkUpdatedAt time.Time) error
|
||||
Get(id string) (*Album, error)
|
||||
GetAll(...QueryOptions) (Albums, error)
|
||||
GetCursor(...QueryOptions) (AlbumCursor, error)
|
||||
|
||||
@ -103,6 +103,7 @@ type ArtistRepository interface {
|
||||
Exists(id string) (bool, error)
|
||||
Put(m *Artist, colsToUpdate ...string) error
|
||||
UpdateExternalInfo(a *Artist) error
|
||||
UpdateBlurHash(id string, blurHash string, artworkUpdatedAt time.Time) error
|
||||
Get(id string) (*Artist, error)
|
||||
GetAll(options ...QueryOptions) (Artists, error)
|
||||
GetCursor(options ...QueryOptions) (ArtistCursor, error)
|
||||
|
||||
@ -142,6 +142,7 @@ type PlaylistRepository interface {
|
||||
GetAll(options ...QueryOptions) (Playlists, error)
|
||||
GetCursor(options ...QueryOptions) (PlaylistCursor, error)
|
||||
FindByPath(path string) (*Playlist, error)
|
||||
UpdateBlurHash(id string, blurHash string, artworkUpdatedAt time.Time) error
|
||||
Delete(id string) error
|
||||
Tracks(playlistId string, refreshSmartPlaylist bool) PlaylistTrackRepository
|
||||
GetPlaylists(mediaFileId string) (Playlists, error)
|
||||
|
||||
@ -213,6 +213,16 @@ func (r *albumRepository) Put(al *model.Album) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateBlurHash is a targeted update: a full-row put would race with the scanner. Deliberately
|
||||
// a plain UPDATE with no insert fallback — updating a just-deleted row must be a silent no-op.
|
||||
func (r *albumRepository) UpdateBlurHash(id, blurHash string, artworkUpdatedAt time.Time) error {
|
||||
upd := Update(r.tableName).Where(Eq{"id": id}).
|
||||
Set("blur_hash", blurHash).
|
||||
Set("blur_hash_updated_at", artworkUpdatedAt)
|
||||
_, err := r.executeSQL(upd)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO Move external metadata to a separated table
|
||||
func (r *albumRepository) UpdateExternalInfo(al *model.Album) error {
|
||||
_, err := r.put(al.ID, &dbAlbum{Album: al}, "description", "small_image_url", "medium_image_url", "large_image_url", "external_url", "external_info_updated_at")
|
||||
|
||||
@ -899,3 +899,34 @@ func _p(id, name string, sortName ...string) model.Participant {
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
var _ = Describe("AlbumRepository.UpdateBlurHash", func() {
|
||||
var repo model.AlbumRepository
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx := request.WithUser(GinkgoT().Context(), model.User{ID: "userid", UserName: "johndoe"})
|
||||
repo = NewAlbumRepository(ctx, GetDBXBuilder())
|
||||
DeferCleanup(func() {
|
||||
_, err := GetDBXBuilder().NewQuery("update album set blur_hash = '', blur_hash_updated_at = null").Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
It("persists the hash and its artwork version snapshot", func() {
|
||||
al, err := repo.Get("103")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(al.BlurHash).To(BeEmpty())
|
||||
|
||||
version := time.Date(2024, 5, 1, 10, 30, 0, 0, time.UTC)
|
||||
Expect(repo.UpdateBlurHash(al.ID, "LKO2?U%2Tw=w]~RBVZRi};RPxuwH", version)).To(Succeed())
|
||||
|
||||
updated, err := repo.Get(al.ID)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(updated.BlurHash).To(Equal("LKO2?U%2Tw=w]~RBVZRi};RPxuwH"))
|
||||
Expect(updated.BlurHashUpdatedAt).ToNot(BeNil())
|
||||
// Round-trip through SQLite must preserve equality — the DTO layer compares with Equal.
|
||||
Expect(updated.BlurHashUpdatedAt.Equal(version)).To(BeTrue())
|
||||
// The targeted update must not touch the row's own timestamps.
|
||||
Expect(updated.UpdatedAt).To(Equal(al.UpdatedAt))
|
||||
})
|
||||
})
|
||||
|
||||
@ -232,6 +232,16 @@ func (r *artistRepository) Put(a *model.Artist, colsToUpdate ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateBlurHash is a targeted update: a full-row put would race with the scanner. Deliberately
|
||||
// a plain UPDATE with no insert fallback — updating a just-deleted row must be a silent no-op.
|
||||
func (r *artistRepository) UpdateBlurHash(id, blurHash string, artworkUpdatedAt time.Time) error {
|
||||
upd := Update(r.tableName).Where(Eq{"id": id}).
|
||||
Set("blur_hash", blurHash).
|
||||
Set("blur_hash_updated_at", artworkUpdatedAt)
|
||||
_, err := r.executeSQL(upd)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *artistRepository) UpdateExternalInfo(a *model.Artist) error {
|
||||
dba := &dbArtist{Artist: a}
|
||||
_, err := r.put(a.ID, dba,
|
||||
|
||||
@ -155,6 +155,16 @@ func (r *playlistRepository) GetWithTracks(id string, refreshSmartPlaylist, incl
|
||||
return pls, nil
|
||||
}
|
||||
|
||||
// UpdateBlurHash is a targeted update: a full-row put would race with playlist sync. Deliberately
|
||||
// a plain UPDATE with no insert fallback — updating a just-deleted row must be a silent no-op.
|
||||
func (r *playlistRepository) UpdateBlurHash(id, blurHash string, artworkUpdatedAt time.Time) error {
|
||||
upd := Update(r.tableName).Where(Eq{"id": id}).
|
||||
Set("blur_hash", blurHash).
|
||||
Set("blur_hash_updated_at", artworkUpdatedAt)
|
||||
_, err := r.executeSQL(upd)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *playlistRepository) FindByPath(path string) (*model.Playlist, error) {
|
||||
return r.findBy(Eq{"path": path})
|
||||
}
|
||||
|
||||
@ -29,6 +29,17 @@ func (m *MockAlbumRepo) SetError(err bool) {
|
||||
m.Err = err
|
||||
}
|
||||
|
||||
func (m *MockAlbumRepo) UpdateBlurHash(id, blurHash string, artworkUpdatedAt time.Time) error {
|
||||
if m.Err {
|
||||
return errors.New("unexpected error")
|
||||
}
|
||||
if al, ok := m.Data[id]; ok {
|
||||
al.BlurHash = blurHash
|
||||
al.BlurHashUpdatedAt = &artworkUpdatedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockAlbumRepo) SetData(albums model.Albums) {
|
||||
m.Data = make(map[string]*model.Album, len(albums))
|
||||
m.All = albums
|
||||
|
||||
@ -32,6 +32,17 @@ func (m *MockArtistRepo) SetData(artists model.Artists) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockArtistRepo) UpdateBlurHash(id, blurHash string, artworkUpdatedAt time.Time) error {
|
||||
if m.Err {
|
||||
return errors.New("unexpected error")
|
||||
}
|
||||
if ar, ok := m.Data[id]; ok {
|
||||
ar.BlurHash = blurHash
|
||||
ar.BlurHashUpdatedAt = &artworkUpdatedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockArtistRepo) Exists(id string) (bool, error) {
|
||||
if m.Err {
|
||||
return false, errors.New("Error!")
|
||||
|
||||
@ -42,6 +42,17 @@ func (m *MockPlaylistRepo) SetData(playlists model.Playlists) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockPlaylistRepo) UpdateBlurHash(id, blurHash string, artworkUpdatedAt time.Time) error {
|
||||
if m.Err {
|
||||
return errors.New("unexpected error")
|
||||
}
|
||||
if pl, ok := m.Data[id]; ok {
|
||||
pl.BlurHash = blurHash
|
||||
pl.BlurHashUpdatedAt = &artworkUpdatedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPlaylistRepo) GetAll(options ...model.QueryOptions) (model.Playlists, error) {
|
||||
if len(options) > 0 {
|
||||
m.Options = options[0]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user