diff --git a/dlna/contenddirectoryservice.go b/dlna/contenddirectoryservice.go index 444dd665f..f2da92029 100644 --- a/dlna/contenddirectoryservice.go +++ b/dlna/contenddirectoryservice.go @@ -21,6 +21,7 @@ import ( "github.com/navidrome/navidrome/dlna/upnpav" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" + "github.com/oriser/regroup" ) type contentDirectoryService struct { @@ -89,91 +90,143 @@ func (cds *contentDirectoryService) readContainer(o object, host string) (ret [] return ret, nil } - pathComponents := strings.Split(o.Path, "/") - log.Debug(fmt.Sprintf("ReadContainer pathComponents %+v %d", pathComponents, len(pathComponents))) + filesRegex := regroup.MustCompile("\\/Music\\/Files[\\/]?((?P.+))?") + artistRegex := regroup.MustCompile("\\/Music\\/Artists[\\/]?(?P[^\\/]+)?[\\/]?(?[^\\/]+)?[\\/]?(?[^\\/]+)?") + albumRegex := regroup.MustCompile("\\/Music\\/Albums[\\/]?(?P[^\\/]+)?[\\/]?(?[^\\/]+)?") + genresRegex := regroup.MustCompile("\\/Music\\/Genres[\\/]?(?P[^\\/]+)?[\\/]?(?P[^/]+)?[\\/]?(?P[^\\/]+)?") + recentRegex := regroup.MustCompile("\\/Music\\/Recently Added[\\/]?(?P[^\\/]+)?") + playlistRegex := regroup.MustCompile("\\/Music\\/Playlist[\\/]?(?P[^\\/]+)?[\\/]?(?P[^\\/]+)?") - //TODO: something other than this - switch len(pathComponents) { - case 2: - switch pathComponents[1] { - case "Music": - ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Files"}, true, host)) - ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Artists"}, true, host)) - ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Albums"}, true, host)) - ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Genres"}, true, host)) - ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Recently Added"}, true, host)) - ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Playlists"}, true, host)) + if o.Path == "/Music" { + ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Files"}, true, host)) + ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Artists"}, true, host)) + ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Albums"}, true, host)) + ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Genres"}, true, host)) + ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Recently Added"}, true, host)) + ret = append(ret, cds.cdsObjectToUpnpavObject(object{Path: "/Music/Playlists"}, true, host)) + return ret, nil + } else if _, err := filesRegex.Groups(o.Path); err == nil { + return cds.doFiles(ret, o.Path, host) + } else if matchResults, err := artistRegex.Groups(o.Path); err == nil { + log.Debug(fmt.Sprintf("Artist MATCH: %+v", matchResults)) + if matchResults["ArtistAlbumTrack"] != "" { + //TODO + log.Debug("Artist Get a track ") + } else if matchResults["ArtistAlbum"] != "" { + log.Debug("Artist Get an album ") + album := matchResults["ArtistAlbum"] + + albumResponse, _ := cds.ds.Album(cds.ctx).Get(album) + log.Debug(fmt.Sprintf("Album Returned: %+v for %s", albumResponse, album)) + basePath := path.Join("/Music/Artists", matchResults["Artist"], matchResults["ArtistAlbum"]) + return cds.doAlbum(albumResponse, basePath, ret, host) + + } else if matchResults["Artist"] != "" { + log.Debug(fmt.Sprintf("Artist Get an Artist: %s", matchResults["Artist"])) + allAlbumsForThisArtist, _ := cds.ds.Album(cds.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"album_artist_id": matchResults["Artist"]}}) + basePath := path.Join("/Music/Artists", matchResults["Artist"]) + return cds.doAlbums(allAlbumsForThisArtist, basePath, ret, host) + + } else { + indexes, err := cds.ds.Artist(cds.ctx).GetIndex() + if err != nil { + fmt.Printf("Error retrieving Indexes: %+v", err) + return nil, err + } + for letterIndex := range indexes { + for artist := range indexes[letterIndex].Artists { + artistId := indexes[letterIndex].Artists[artist].ID + child := object{ + Path: path.Join(o.Path, indexes[letterIndex].Artists[artist].Name), + Id: path.Join(o.Path, artistId), + } + ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) + } + } return ret, nil } - case 3: - switch pathComponents[1] { - case "Music": - switch pathComponents[2] { - case "Files": - return cds.doFiles(ret, o.Path, host) - case "Artists": - indexes, err := cds.ds.Artist(cds.ctx).GetIndex() - if err != nil { - fmt.Printf("Error retrieving Indexes: %+v", err) - return nil, err - } - for letterIndex := range indexes { - for artist := range indexes[letterIndex].Artists { - artistId := indexes[letterIndex].Artists[artist].ID - child := object{ - Path: path.Join(o.Path, indexes[letterIndex].Artists[artist].Name), - Id: path.Join(o.Path, artistId), - } - ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) - } - } - return ret, nil - case "Albums": - indexes, err := cds.ds.Album(cds.ctx).GetAllWithoutGenres() - if err != nil { - fmt.Printf("Error retrieving Indexes: %+v", err) - return nil, err - } - for indexItem := range indexes { - child := object{ - Path: path.Join(o.Path, indexes[indexItem].Name), - Id: path.Join(o.Path, indexes[indexItem].ID), - } - ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) - } - return ret, nil - case "Genres": - indexes, err := cds.ds.Genre(cds.ctx).GetAll() - if err != nil { - fmt.Printf("Error retrieving Indexes: %+v", err) - return nil, err - } - for indexItem := range indexes { - child := object{ - Path: path.Join(o.Path, indexes[indexItem].Name), - Id: path.Join(o.Path, indexes[indexItem].ID), - } - ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) - } - return ret, nil - case "Playlists": - indexes, err := cds.ds.Playlist(cds.ctx).GetAll() - if err != nil { - fmt.Printf("Error retrieving Indexes: %+v", err) - return nil, err - } - for indexItem := range indexes { - child := object{ - Path: path.Join(o.Path, indexes[indexItem].Name), - Id: path.Join(o.Path, indexes[indexItem].ID), - } - ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) - } - return ret, nil + } else if matchResults, err := albumRegex.Groups(o.Path); err == nil { + log.Debug("Album MATCH") + if matchResults["AlbumTrack"] != "" { + log.Debug("AlbumTrack MATCH") + //TODO + } else if matchResults["AlbumTitle"] != "" { + log.Debug("AlbumTitle MATCH") + x, _ := cds.ds.Album(cds.ctx).Get(matchResults["AlbumTitle"]) + basePath := "/Music/Albums" + return cds.doAlbum(x, basePath, ret, host) + } else { + log.Debug("albumRegex else MATCH") + indexes, err := cds.ds.Album(cds.ctx).GetAllWithoutGenres() + if err != nil { + fmt.Printf("Error retrieving Indexes: %+v", err) + return nil, err } + for indexItem := range indexes { + child := object{ + Path: path.Join(o.Path, indexes[indexItem].Name), + Id: path.Join(o.Path, indexes[indexItem].ID), + } + ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) + } + return ret, nil } - default: - + } else if matchResults, err := genresRegex.Groups(o.Path); err == nil { + log.Debug("Genre MATCH") + if _, exists := matchResults["GenreTrack"]; exists { + log.Debug("GenreTrack MATCH") + //TODO + } else if _, exists := matchResults["GenreArtist"]; exists { + log.Debug("GenreArtist MATCH") + //TODO + } else if genre, exists := matchResults["Genre"]; exists { + log.Debug("Genre only MATCH") + x, xerr := cds.ds.Album(cds.ctx).Get(genre) + log.Debug(fmt.Sprintf("Genre: %+v", x), xerr) + } else { + log.Debug("Genre else MATCH") + indexes, err := cds.ds.Genre(cds.ctx).GetAll() + if err != nil { + fmt.Printf("Error retrieving Indexes: %+v", err) + return nil, err + } + for indexItem := range indexes { + child := object{ + Path: path.Join(o.Path, indexes[indexItem].Name), + Id: path.Join(o.Path, indexes[indexItem].ID), + } + ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) + } + return ret, nil + } + } else if matchResults, err := recentRegex.Groups(o.Path); err == nil { + log.Debug("recent MATCH") + fmt.Printf("%+v",matchResults) + } else if matchResults, err := playlistRegex.Groups(o.Path); err == nil { + log.Debug("Playlist MATCH") + if _, exists := matchResults["PlaylistTrack"]; exists { + log.Debug("PlaylistTrack MATCH") + } else if playlist, exists := matchResults["Playlist"]; exists { + log.Debug("Playlist only MATCH") + x, xerr := cds.ds.Playlist(cds.ctx).Get(playlist) + log.Debug(fmt.Sprintf("Playlist: %+v", x), xerr) + } else { + log.Debug("Playlist else MATCH") + indexes, err := cds.ds.Playlist(cds.ctx).GetAll() + if err != nil { + fmt.Printf("Error retrieving Indexes: %+v", err) + return nil, err + } + for indexItem := range indexes { + child := object{ + Path: path.Join(o.Path, indexes[indexItem].Name), + Id: path.Join(o.Path, indexes[indexItem].ID), + } + ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) + } + return ret, nil + } + } /* deluan — @@ -194,39 +247,19 @@ func (cds *contentDirectoryService) readContainer(o object, host string) (ret [] Today at 18:31 This is a limitation of Squirrel. It is string based. YOu have to use the name of the columns in the DB */ - if len(pathComponents) >= 4 { - switch pathComponents[2] { - case "Files": - return cds.doFiles(ret, o.Path, host) - case "Artists": - allAlbumsForThisArtist, _ := cds.ds.Album(cds.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"album_artist_id": pathComponents[3]}}) - return cds.doAlbums(allAlbumsForThisArtist, ret, host) - case "Albums": - x, _ := cds.ds.Album(cds.ctx).Get(pathComponents[3]) - return cds.doAlbum(x, ret, host) - case "Genres": - x, xerr := cds.ds.Album(cds.ctx).Get(pathComponents[3]) - log.Debug(fmt.Sprintf("Genre: %+v", x), xerr) - case "Playlists": - x, xerr := cds.ds.Playlist(cds.ctx).Get(pathComponents[3]) - log.Debug(fmt.Sprintf("Playlist: %+v", x), xerr) - } - } - } - - return + return } -func (cds *contentDirectoryService) doAlbum(x *model.Album, ret []interface{}, host string) ([]interface{}, error) { - //TODO +func (cds *contentDirectoryService) doAlbum(album *model.Album, basepath string, ret []interface{}, host string) ([]interface{}, error) { + log.Debug(fmt.Sprintf("TODO: doAlbum Called with : '%+v', '%s'", album, basepath)) return ret, nil } -func (cds *contentDirectoryService) doAlbums(albums model.Albums, ret []interface{}, host string) ([]interface{}, error) { +func (cds *contentDirectoryService) doAlbums(albums model.Albums, basepath string, ret []interface{}, host string) ([]interface{}, error) { for _, album := range albums { child := object { - Path: path.Join("/Music/Albums", album.Name), - Id: album.ID, + Path: path.Join(basepath, album.Name), + Id: path.Join(basepath, album.ID), } ret = append(ret, cds.cdsObjectToUpnpavObject(child, true, host)) } diff --git a/go.mod b/go.mod index 91e14f8ff..9a848b742 100644 --- a/go.mod +++ b/go.mod @@ -97,6 +97,7 @@ require ( github.com/mfridman/interpolate v0.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oriser/regroup v0.0.0-20240925165441-f6bb0e08289e // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect diff --git a/go.sum b/go.sum index 78122dfdd..7af6924bb 100644 --- a/go.sum +++ b/go.sum @@ -4,14 +4,14 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/RaveNoX/go-jsoncommentstrip v1.0.0 h1:t527LHHE3HmiHrq74QMpNPZpGCIJzTx+apLkMKt4HC0= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= -github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/anacrolix/dms v1.7.1 h1:XVOpT3eoO5Ds34B1X+TE3R2ApfqGGeqotEoCVNP8BaI= github.com/anacrolix/dms v1.7.1/go.mod h1:excFJW5MKBhn5yt5ZMyeE9iFVqnO6tEGQl7YG/2tUoQ= github.com/anacrolix/generics v0.0.1 h1:4WVhK6iLb3UAAAQP6I3uYlMOHcp9FqJC9j4n81Wv9Ks= github.com/anacrolix/generics v0.0.1/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= github.com/anacrolix/log v0.15.2 h1:LTSf5Wm6Q4GNWPFMBP7NPYV6UBVZzZLKckL+/Lj72Oo= github.com/anacrolix/log v0.15.2/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA= +github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= +github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -160,6 +160,8 @@ github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/oriser/regroup v0.0.0-20240925165441-f6bb0e08289e h1:cL0lMYYEbfEUBghQd4ytnl8B8Ktdm+JremTyAagegZ0= +github.com/oriser/regroup v0.0.0-20240925165441-f6bb0e08289e/go.mod h1:tUOeYZJlwO7jSmM5ko1jTCiQaWQMvh58IENEfjwYzh8= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -257,6 +259,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=