mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
feat: Add support for referencing playlists using paths
Signed-off-by: David <dvedvick@gmail.com>
This commit is contained in:
parent
2954c052f5
commit
c222efacd2
@ -164,6 +164,18 @@ func (c Criteria) ChildPlaylistIds() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Criteria) ChildPlaylistPaths() []string {
|
||||
if c.Expression == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if parent := c.Expression.(interface{ ChildPlaylistPaths() (paths []string) }); parent != nil {
|
||||
return parent.ChildPlaylistPaths()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Criteria) MarshalJSON() ([]byte, error) {
|
||||
aux := struct {
|
||||
All []Expression `json:"all,omitempty"`
|
||||
|
||||
@ -407,19 +407,23 @@ var _ = Describe("Criteria", func() {
|
||||
|
||||
Context("with child playlists", func() {
|
||||
var (
|
||||
topLevelInPlaylistID string
|
||||
topLevelNotInPlaylistID string
|
||||
nestedAnyInPlaylistID string
|
||||
nestedAnyNotInPlaylistID string
|
||||
nestedAllInPlaylistID string
|
||||
nestedAllNotInPlaylistID string
|
||||
topLevelInPlaylistID string
|
||||
topLevelInPlaylistPath string
|
||||
topLevelNotInPlaylistID string
|
||||
nestedAnyInPlaylistID string
|
||||
nestedAnyNotInPlaylistID string
|
||||
nestedAllInPlaylistID string
|
||||
nestedAllNotInPlaylistID string
|
||||
nestedAnyNotInPlaylistPath string
|
||||
)
|
||||
BeforeEach(func() {
|
||||
topLevelInPlaylistID = uuid.NewString()
|
||||
topLevelInPlaylistPath = "./test.nsp"
|
||||
topLevelNotInPlaylistID = uuid.NewString()
|
||||
|
||||
nestedAnyInPlaylistID = uuid.NewString()
|
||||
nestedAnyNotInPlaylistID = uuid.NewString()
|
||||
nestedAnyNotInPlaylistPath = "../not-in-playlist.m3u"
|
||||
|
||||
nestedAllInPlaylistID = uuid.NewString()
|
||||
nestedAllNotInPlaylistID = uuid.NewString()
|
||||
@ -427,10 +431,12 @@ var _ = Describe("Criteria", func() {
|
||||
goObj = Criteria{
|
||||
Expression: All{
|
||||
InPlaylist{"id": topLevelInPlaylistID},
|
||||
InPlaylist{"path": topLevelInPlaylistPath},
|
||||
NotInPlaylist{"id": topLevelNotInPlaylistID},
|
||||
Any{
|
||||
InPlaylist{"id": nestedAnyInPlaylistID},
|
||||
NotInPlaylist{"id": nestedAnyNotInPlaylistID},
|
||||
NotInPlaylist{"path": nestedAnyNotInPlaylistPath},
|
||||
},
|
||||
All{
|
||||
InPlaylist{"id": nestedAllInPlaylistID},
|
||||
@ -443,6 +449,10 @@ var _ = Describe("Criteria", func() {
|
||||
ids := goObj.ChildPlaylistIds()
|
||||
gomega.Expect(ids).To(gomega.ConsistOf(topLevelInPlaylistID, topLevelNotInPlaylistID, nestedAnyInPlaylistID, nestedAnyNotInPlaylistID, nestedAllInPlaylistID, nestedAllNotInPlaylistID))
|
||||
})
|
||||
It("extracts all child smart playlist paths from expression criteria", func() {
|
||||
ids := goObj.ChildPlaylistPaths()
|
||||
gomega.Expect(ids).To(gomega.ConsistOf(topLevelInPlaylistPath, nestedAnyNotInPlaylistPath))
|
||||
})
|
||||
It("extracts child smart playlist IDs from deeply nested expression", func() {
|
||||
goObj = Criteria{
|
||||
Expression: Any{
|
||||
|
||||
@ -27,6 +27,10 @@ func (all All) ChildPlaylistIds() (ids []string) {
|
||||
return extractPlaylistIds(all)
|
||||
}
|
||||
|
||||
func (all All) ChildPlaylistPaths() (paths []string) {
|
||||
return extractPlaylistPaths(all)
|
||||
}
|
||||
|
||||
type (
|
||||
Any squirrel.Or
|
||||
Or = Any
|
||||
@ -44,6 +48,10 @@ func (any Any) ChildPlaylistIds() (ids []string) {
|
||||
return extractPlaylistIds(any)
|
||||
}
|
||||
|
||||
func (any Any) ChildPlaylistPaths() (paths []string) {
|
||||
return extractPlaylistPaths(any)
|
||||
}
|
||||
|
||||
type Is squirrel.Eq
|
||||
type Eq = Is
|
||||
|
||||
@ -300,10 +308,13 @@ func (ipl NotInPlaylist) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func inList(m map[string]any, negate bool) (sql string, args []any, err error) {
|
||||
var playlistid string
|
||||
var ok bool
|
||||
if playlistid, ok = m["id"].(string); !ok {
|
||||
return "", nil, errors.New("playlist id not given")
|
||||
var condition squirrel.Sqlizer
|
||||
if playlistId, ok := m["id"].(string); ok {
|
||||
condition = squirrel.Eq{"pl.playlist_id": playlistId}
|
||||
} else if playlistPath, ok := m["path"].(string); ok {
|
||||
condition = squirrel.Eq{"playlist.path": playlistPath}
|
||||
} else {
|
||||
return "", nil, errors.New("playlist id or path not given")
|
||||
}
|
||||
|
||||
// Subquery to fetch all media files that are contained in given playlist
|
||||
@ -312,8 +323,9 @@ func inList(m map[string]any, negate bool) (sql string, args []any, err error) {
|
||||
From("playlist_tracks pl").
|
||||
LeftJoin("playlist on pl.playlist_id = playlist.id").
|
||||
Where(squirrel.And{
|
||||
squirrel.Eq{"pl.playlist_id": playlistid},
|
||||
condition,
|
||||
squirrel.Eq{"playlist.public": 1}})
|
||||
|
||||
subQText, subQArgs, err := subQuery.PlaceholderFormat(squirrel.Question).ToSql()
|
||||
|
||||
if err != nil {
|
||||
@ -326,28 +338,32 @@ func inList(m map[string]any, negate bool) (sql string, args []any, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func extractPlaylistIds(inputRule any) (ids []string) {
|
||||
var id string
|
||||
var ok bool
|
||||
|
||||
func extractPlaylistField(inputRule any, field string) (values []string) {
|
||||
switch rule := inputRule.(type) {
|
||||
case Any:
|
||||
for _, rules := range rule {
|
||||
ids = append(ids, extractPlaylistIds(rules)...)
|
||||
values = append(values, extractPlaylistField(rules, field)...)
|
||||
}
|
||||
case All:
|
||||
for _, rules := range rule {
|
||||
ids = append(ids, extractPlaylistIds(rules)...)
|
||||
values = append(values, extractPlaylistField(rules, field)...)
|
||||
}
|
||||
case InPlaylist:
|
||||
if id, ok = rule["id"].(string); ok {
|
||||
ids = append(ids, id)
|
||||
if value, ok := rule[field].(string); ok {
|
||||
values = append(values, value)
|
||||
}
|
||||
case NotInPlaylist:
|
||||
if id, ok = rule["id"].(string); ok {
|
||||
ids = append(ids, id)
|
||||
if value, ok := rule[field].(string); ok {
|
||||
values = append(values, value)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func extractPlaylistIds(inputRule any) (ids []string) {
|
||||
return extractPlaylistField(inputRule, "id")
|
||||
}
|
||||
|
||||
func extractPlaylistPaths(inputRule any) (paths []string) {
|
||||
return extractPlaylistField(inputRule, "path")
|
||||
}
|
||||
|
||||
@ -46,8 +46,10 @@ var _ = Describe("Operators", func() {
|
||||
Entry("after", After{"lastPlayed": rangeStart}, "annotation.play_date > ?", rangeStart),
|
||||
|
||||
// InPlaylist and NotInPlaylist are special cases
|
||||
Entry("inPlaylist", InPlaylist{"id": "deadbeef-dead-beef"}, "media_file.id IN "+
|
||||
Entry("inPlaylist [id]", InPlaylist{"id": "deadbeef-dead-beef"}, "media_file.id IN "+
|
||||
"(SELECT media_file_id FROM playlist_tracks pl LEFT JOIN playlist on pl.playlist_id = playlist.id WHERE (pl.playlist_id = ? AND playlist.public = ?))", "deadbeef-dead-beef", 1),
|
||||
Entry("inPlaylist [path]", InPlaylist{"path": "lacuslacus.nsp"}, "media_file.id IN "+
|
||||
"(SELECT media_file_id FROM playlist_tracks pl LEFT JOIN playlist on pl.playlist_id = playlist.id WHERE (playlist.path = ? AND playlist.public = ?))", "lacuslacus.nsp", 1),
|
||||
Entry("notInPlaylist", NotInPlaylist{"id": "deadbeef-dead-beef"}, "media_file.id NOT IN "+
|
||||
"(SELECT media_file_id FROM playlist_tracks pl LEFT JOIN playlist on pl.playlist_id = playlist.id WHERE (pl.playlist_id = ? AND playlist.public = ?))", "deadbeef-dead-beef", 1),
|
||||
|
||||
|
||||
@ -230,6 +230,15 @@ func (r *playlistRepository) refreshSmartPlaylist(pls *model.Playlist) bool {
|
||||
rules := *pls.Rules
|
||||
|
||||
// If the playlist depends on other playlists, recursively refresh them first
|
||||
childPlaylistPaths := rules.ChildPlaylistPaths()
|
||||
for _, path := range childPlaylistPaths {
|
||||
childPls, err := r.FindByPath(path)
|
||||
if err != nil {
|
||||
log.Error(r.ctx, "Error loading child playlist", "id", pls.ID, "childId", path, err)
|
||||
return false
|
||||
}
|
||||
r.refreshSmartPlaylist(childPls)
|
||||
}
|
||||
childPlaylistIds := rules.ChildPlaylistIds()
|
||||
for _, id := range childPlaylistIds {
|
||||
childPls, err := r.Get(id)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user