mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-03 20:47:35 +03:00
fix(subsonic): random albums not reshuffling.
See: https://github.com/navidrome/navidrome/issues/3277#issuecomment-2364269787
This commit is contained in:
parent
5b89bf747f
commit
ecf934feab
5 changed files with 54 additions and 26 deletions
|
@ -75,7 +75,7 @@ func NewAlbumRepository(ctx context.Context, db dbx.Builder) model.AlbumReposito
|
||||||
"artist": "compilation asc, COALESCE(NULLIF(sort_album_artist_name,''),order_album_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc",
|
"artist": "compilation asc, COALESCE(NULLIF(sort_album_artist_name,''),order_album_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc",
|
||||||
"album_artist": "compilation asc, COALESCE(NULLIF(sort_album_artist_name,''),order_album_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc",
|
"album_artist": "compilation asc, COALESCE(NULLIF(sort_album_artist_name,''),order_album_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc",
|
||||||
"max_year": "coalesce(nullif(original_date,''), cast(max_year as text)), release_date, name, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc",
|
"max_year": "coalesce(nullif(original_date,''), cast(max_year as text)), release_date, name, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc",
|
||||||
"random": r.seededRandomSort(),
|
"random": "random",
|
||||||
"recently_added": recentlyAddedSort(),
|
"recently_added": recentlyAddedSort(),
|
||||||
"starred_at": "starred, starred_at",
|
"starred_at": "starred, starred_at",
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ func NewAlbumRepository(ctx context.Context, db dbx.Builder) model.AlbumReposito
|
||||||
"artist": "compilation asc, order_album_artist_name asc, order_album_name asc",
|
"artist": "compilation asc, order_album_artist_name asc, order_album_name asc",
|
||||||
"album_artist": "compilation asc, order_album_artist_name asc, order_album_name asc",
|
"album_artist": "compilation asc, order_album_artist_name asc, order_album_name asc",
|
||||||
"max_year": "coalesce(nullif(original_date,''), cast(max_year as text)), release_date, name, order_album_name asc",
|
"max_year": "coalesce(nullif(original_date,''), cast(max_year as text)), release_date, name, order_album_name asc",
|
||||||
"random": r.seededRandomSort(),
|
"random": "random",
|
||||||
"recently_added": recentlyAddedSort(),
|
"recently_added": recentlyAddedSort(),
|
||||||
"starred_at": "starred, starred_at",
|
"starred_at": "starred, starred_at",
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ func NewMediaFileRepository(ctx context.Context, db dbx.Builder) *mediaFileRepos
|
||||||
"title": "COALESCE(NULLIF(sort_title,''),title)",
|
"title": "COALESCE(NULLIF(sort_title,''),title)",
|
||||||
"artist": "COALESCE(NULLIF(sort_artist_name,''),order_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc, release_date asc, disc_number asc, track_number asc",
|
"artist": "COALESCE(NULLIF(sort_artist_name,''),order_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc, release_date asc, disc_number asc, track_number asc",
|
||||||
"album": "COALESCE(NULLIF(sort_album_name,''),order_album_name) asc, release_date asc, disc_number asc, track_number asc, COALESCE(NULLIF(sort_artist_name,''),order_artist_name) asc, COALESCE(NULLIF(sort_title,''),title) asc",
|
"album": "COALESCE(NULLIF(sort_album_name,''),order_album_name) asc, release_date asc, disc_number asc, track_number asc, COALESCE(NULLIF(sort_artist_name,''),order_artist_name) asc, COALESCE(NULLIF(sort_title,''),title) asc",
|
||||||
"random": r.seededRandomSort(),
|
"random": "random",
|
||||||
"created_at": "media_file.created_at",
|
"created_at": "media_file.created_at",
|
||||||
"track_number": "album, release_date, disc_number, track_number",
|
"track_number": "album, release_date, disc_number, track_number",
|
||||||
"starred_at": "starred, starred_at",
|
"starred_at": "starred, starred_at",
|
||||||
|
@ -46,7 +46,7 @@ func NewMediaFileRepository(ctx context.Context, db dbx.Builder) *mediaFileRepos
|
||||||
"title": "order_title",
|
"title": "order_title",
|
||||||
"artist": "order_artist_name asc, order_album_name asc, release_date asc, disc_number asc, track_number asc",
|
"artist": "order_artist_name asc, order_album_name asc, release_date asc, disc_number asc, track_number asc",
|
||||||
"album": "order_album_name asc, release_date asc, disc_number asc, track_number asc, order_artist_name asc, title asc",
|
"album": "order_album_name asc, release_date asc, disc_number asc, track_number asc, order_artist_name asc, title asc",
|
||||||
"random": r.seededRandomSort(),
|
"random": "random",
|
||||||
"created_at": "media_file.created_at",
|
"created_at": "media_file.created_at",
|
||||||
"track_number": "album, release_date, disc_number, track_number",
|
"track_number": "album, release_date, disc_number, track_number",
|
||||||
"starred_at": "starred, starred_at",
|
"starred_at": "starred, starred_at",
|
||||||
|
|
|
@ -167,20 +167,15 @@ func (r sqlRepository) seedKey() string {
|
||||||
return r.tableName + userId(r.ctx)
|
return r.tableName + userId(r.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r sqlRepository) seededRandomSort() string {
|
|
||||||
return fmt.Sprintf("SEEDEDRAND('%s', %s.id)", r.seedKey(), r.tableName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r sqlRepository) resetSeededRandom(options []model.QueryOptions) {
|
func (r sqlRepository) resetSeededRandom(options []model.QueryOptions) {
|
||||||
if len(options) == 0 || !strings.HasPrefix(options[0].Sort, "SEEDEDRAND(") {
|
if len(options) == 0 || options[0].Sort != "random" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
options[0].Sort = fmt.Sprintf("SEEDEDRAND('%s', %s.id)", r.seedKey(), r.tableName)
|
||||||
if options[0].Seed != "" {
|
if options[0].Seed != "" {
|
||||||
hasher.SetSeed(r.seedKey(), options[0].Seed)
|
hasher.SetSeed(r.seedKey(), options[0].Seed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if options[0].Offset == 0 {
|
if options[0].Offset == 0 {
|
||||||
hasher.Reseed(r.seedKey())
|
hasher.Reseed(r.seedKey())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,25 @@ import (
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/request"
|
"github.com/navidrome/navidrome/model/request"
|
||||||
|
"github.com/navidrome/navidrome/utils/hasher"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("sqlRepository", func() {
|
var _ = Describe("sqlRepository", func() {
|
||||||
r := sqlRepository{}
|
var r sqlRepository
|
||||||
|
BeforeEach(func() {
|
||||||
|
r.ctx = request.WithUser(context.Background(), model.User{ID: "user-id"})
|
||||||
|
r.tableName = "table"
|
||||||
|
})
|
||||||
|
|
||||||
Describe("applyOptions", func() {
|
Describe("applyOptions", func() {
|
||||||
var sq squirrel.SelectBuilder
|
var sq squirrel.SelectBuilder
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
sq = squirrel.Select("*").From("test")
|
sq = squirrel.Select("*").From("test")
|
||||||
|
r.sortMappings = map[string]string{
|
||||||
|
"name": "title",
|
||||||
|
}
|
||||||
})
|
})
|
||||||
It("does not add any clauses when options is empty", func() {
|
It("does not add any clauses when options is empty", func() {
|
||||||
sq = r.applyOptions(sq, model.QueryOptions{})
|
sq = r.applyOptions(sq, model.QueryOptions{})
|
||||||
|
@ -30,17 +39,11 @@ var _ = Describe("sqlRepository", func() {
|
||||||
Offset: 2,
|
Offset: 2,
|
||||||
})
|
})
|
||||||
sql, _, _ := sq.ToSql()
|
sql, _, _ := sq.ToSql()
|
||||||
Expect(sql).To(Equal("SELECT * FROM test ORDER BY name desc LIMIT 1 OFFSET 2"))
|
Expect(sql).To(Equal("SELECT * FROM test ORDER BY title desc LIMIT 1 OFFSET 2"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("toSQL", func() {
|
Describe("toSQL", func() {
|
||||||
var r sqlRepository
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
r = sqlRepository{}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("returns error for invalid SQL", func() {
|
It("returns error for invalid SQL", func() {
|
||||||
sq := squirrel.Select("*").From("test").Where(1)
|
sq := squirrel.Select("*").From("test").Where(1)
|
||||||
_, _, err := r.toSQL(sq)
|
_, _, err := r.toSQL(sq)
|
||||||
|
@ -175,13 +178,37 @@ var _ = Describe("sqlRepository", func() {
|
||||||
Expect(sql).To(Equal("name asc, coalesce(nullif(release_date, ''), nullif(original_date, '')) desc, status desc"))
|
Expect(sql).To(Equal("name asc, coalesce(nullif(release_date, ''), nullif(original_date, '')) desc, status desc"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("seededRandomSort", func() {
|
|
||||||
It("returns a random sort order function call", func() {
|
|
||||||
r.ctx = request.WithUser(context.Background(), model.User{ID: "2"})
|
|
||||||
r.tableName = "media_file"
|
|
||||||
sql := r.seededRandomSort()
|
|
||||||
Expect(sql).To(ContainSubstring("SEEDEDRAND('media_file2', media_file.id)"))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("resetSeededRandom", func() {
|
||||||
|
var id string
|
||||||
|
BeforeEach(func() {
|
||||||
|
id = r.seedKey()
|
||||||
|
hasher.SetSeed(id, "")
|
||||||
|
})
|
||||||
|
It("does not reset seed if sort is not random", func() {
|
||||||
|
var options []model.QueryOptions
|
||||||
|
r.resetSeededRandom(options)
|
||||||
|
Expect(hasher.CurrentSeed(id)).To(BeEmpty())
|
||||||
|
})
|
||||||
|
It("resets seed if sort is random", func() {
|
||||||
|
options := []model.QueryOptions{{Sort: "random"}}
|
||||||
|
r.resetSeededRandom(options)
|
||||||
|
Expect(hasher.CurrentSeed(id)).NotTo(BeEmpty())
|
||||||
|
})
|
||||||
|
It("resets seed if sort is random and seed is provided", func() {
|
||||||
|
options := []model.QueryOptions{{Sort: "random", Seed: "seed"}}
|
||||||
|
r.resetSeededRandom(options)
|
||||||
|
Expect(hasher.CurrentSeed(id)).To(Equal("seed"))
|
||||||
|
})
|
||||||
|
It("keeps seed when paginating", func() {
|
||||||
|
options := []model.QueryOptions{{Sort: "random", Seed: "seed", Offset: 0}}
|
||||||
|
r.resetSeededRandom(options)
|
||||||
|
Expect(hasher.CurrentSeed(id)).To(Equal("seed"))
|
||||||
|
|
||||||
|
options = []model.QueryOptions{{Sort: "random", Offset: 1}}
|
||||||
|
r.resetSeededRandom(options)
|
||||||
|
Expect(hasher.CurrentSeed(id)).To(Equal("seed"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,6 +18,12 @@ func SetSeed(id string, seed string) {
|
||||||
instance.SetSeed(id, seed)
|
instance.SetSeed(id, seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CurrentSeed(id string) string {
|
||||||
|
instance.mutex.RLock()
|
||||||
|
defer instance.mutex.RUnlock()
|
||||||
|
return instance.seeds[id]
|
||||||
|
}
|
||||||
|
|
||||||
func HashFunc() func(id, str string) uint64 {
|
func HashFunc() func(id, str string) uint64 {
|
||||||
return instance.HashFunc()
|
return instance.HashFunc()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue