mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-18 07:30:29 +00:00
fix(persistence): move scrobbles and buffered scrobbles off a discarded media file
Both tables carry ON DELETE CASCADE on media_file_id, so 'missing fix' deleting the target erased its play history and dropped scrobbles still waiting on an external service. scrobble_buffer needs OR IGNORE for its unique (user_id, service, media_file_id, play_time).
This commit is contained in:
parent
9f216f624a
commit
f5996711f4
@ -411,6 +411,15 @@ func (r *mediaFileRepository) ReassignReferences(prevID, newID string) error {
|
||||
if _, err := r.executeSQL(upd); err != nil {
|
||||
return fmt.Errorf("reassigning playlist tracks: %w", err)
|
||||
}
|
||||
upd = Update("scrobbles").Set("media_file_id", newID).Where(Eq{"media_file_id": prevID})
|
||||
if _, err := r.executeSQL(upd); err != nil {
|
||||
return fmt.Errorf("reassigning scrobbles: %w", err)
|
||||
}
|
||||
// OR IGNORE: scrobble_buffer is unique on (user_id, service, media_file_id, play_time)
|
||||
buf := Expr("update or ignore scrobble_buffer set media_file_id = ? where media_file_id = ?", newID, prevID)
|
||||
if _, err := r.executeSQL(buf); err != nil {
|
||||
return fmt.Errorf("reassigning buffered scrobbles: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"github.com/navidrome/navidrome/model/criteria"
|
||||
"github.com/navidrome/navidrome/model/id"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pocketbase/dbx"
|
||||
@ -941,6 +942,8 @@ var _ = Describe("MediaRepository", func() {
|
||||
_ = mr.Delete(next.ID)
|
||||
_, _ = mr.(*mediaFileRepository).executeSQL(squirrel.Delete("annotation").Where(squirrel.Eq{"item_id": []string{prev.ID, next.ID}}))
|
||||
_, _ = mr.(*mediaFileRepository).executeSQL(squirrel.Delete("bookmark").Where(squirrel.Eq{"item_id": []string{prev.ID, next.ID}}))
|
||||
_, _ = mr.(*mediaFileRepository).executeSQL(squirrel.Delete("scrobbles").Where(squirrel.Eq{"media_file_id": []string{prev.ID, next.ID}}))
|
||||
_, _ = mr.(*mediaFileRepository).executeSQL(squirrel.Delete("scrobble_buffer").Where(squirrel.Eq{"media_file_id": []string{prev.ID, next.ID}}))
|
||||
})
|
||||
|
||||
It("moves annotations, bookmarks and playlist entries onto the new id", func() {
|
||||
@ -963,6 +966,29 @@ var _ = Describe("MediaRepository", func() {
|
||||
Expect(withTracks.Tracks[0].MediaFileID).To(Equal(next.ID))
|
||||
})
|
||||
|
||||
It("moves scrobbles and buffered scrobbles onto the new id", func() {
|
||||
ctx := request.WithUser(log.NewContext(context.TODO()), model.User{ID: "userid"})
|
||||
scrobbles := NewScrobbleRepository(ctx, GetDBXBuilder())
|
||||
buffer := NewScrobbleBufferRepository(ctx, GetDBXBuilder())
|
||||
Expect(scrobbles.RecordScrobble(prev.ID, time.Now())).To(Succeed())
|
||||
Expect(buffer.Enqueue("lastfm", "userid", prev.ID, time.Now())).To(Succeed())
|
||||
|
||||
Expect(mr.ReassignReferences(prev.ID, next.ID)).To(Succeed())
|
||||
Expect(mr.Delete(prev.ID)).To(Succeed())
|
||||
|
||||
all, err := scrobbles.GetAll()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
mine := slice.Map(slice.Filter(all, func(sc model.Scrobble) bool {
|
||||
return sc.MediaFileID == prev.ID || sc.MediaFileID == next.ID
|
||||
}), func(sc model.Scrobble) string { return sc.MediaFileID })
|
||||
Expect(mine).To(ConsistOf(next.ID))
|
||||
|
||||
entry, err := buffer.Next("lastfm", "userid")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).ToNot(BeNil())
|
||||
Expect(entry.MediaFile.ID).To(Equal(next.ID))
|
||||
})
|
||||
|
||||
It("keeps the new id's own annotation and bookmark when both exist", func() {
|
||||
Expect(mr.SetRating(5, prev.ID)).To(Succeed())
|
||||
Expect(mr.SetRating(1, next.ID)).To(Succeed())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user