diff --git a/server/subsonic/browsing.go b/server/subsonic/browsing.go index cfce7c1fc..2ad03a113 100644 --- a/server/subsonic/browsing.go +++ b/server/subsonic/browsing.go @@ -105,7 +105,9 @@ func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request) func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { id := utils.ParamString(r, "id") - dir, err := c.browser.Directory(r.Context(), id) + ctx := r.Context() + + entity, err := getEntityByID(ctx, c.ds, id) switch { case err == model.ErrNotFound: log.Error(r, "Requested ID not found ", "id", id) @@ -115,8 +117,25 @@ func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Re return nil, NewError(responses.ErrorGeneric, "Internal Error") } + var dir *responses.Directory + + switch v := entity.(type) { + case *model.Artist: + dir, err = c.buildArtistDirectory(ctx, v) + case *model.Album: + dir, err = c.buildAlbumDirectory(ctx, v) + default: + log.Error(r, "Requested ID of invalid type", "id", id, "entity", v) + return nil, NewError(responses.ErrorDataNotFound, "Directory not found") + } + + if err != nil { + log.Error(err) + return nil, NewError(responses.ErrorGeneric, "Internal Error") + } + response := NewResponse() - response.Directory = c.buildDirectory(r.Context(), dir) + response.Directory = dir return response, nil } @@ -233,21 +252,24 @@ func (c *BrowsingController) GetTopSongs(w http.ResponseWriter, r *http.Request) return response, nil } -func (c *BrowsingController) buildDirectory(ctx context.Context, d *engine.DirectoryInfo) *responses.Directory { - dir := &responses.Directory{ - Id: d.Id, - Name: d.Name, - Parent: d.Parent, - PlayCount: d.PlayCount, - AlbumCount: d.AlbumCount, - UserRating: d.UserRating, - } - if !d.Starred.IsZero() { - dir.Starred = &d.Starred +func (c *BrowsingController) buildArtistDirectory(ctx context.Context, artist *model.Artist) (*responses.Directory, error) { + dir := &responses.Directory{} + dir.Id = artist.ID + dir.Name = artist.Name + dir.PlayCount = artist.PlayCount + dir.AlbumCount = artist.AlbumCount + dir.UserRating = artist.Rating + if artist.Starred { + dir.Starred = &artist.StarredAt } - dir.Child = ToChildren(ctx, d.Entries) - return dir + albums, err := c.ds.Album(ctx).FindByArtist(artist.ID) + if err != nil { + return nil, err + } + + dir.Child = ChildrenFromAlbums(ctx, albums) + return dir, nil } func (c *BrowsingController) buildArtist(ctx context.Context, artist *model.Artist, albums model.Albums) *responses.ArtistWithAlbumsID3 { @@ -263,6 +285,28 @@ func (c *BrowsingController) buildArtist(ctx context.Context, artist *model.Arti return dir } +func (c *BrowsingController) buildAlbumDirectory(ctx context.Context, album *model.Album) (*responses.Directory, error) { + dir := &responses.Directory{} + dir.Id = album.ID + dir.Name = album.Name + dir.Parent = album.AlbumArtistID + dir.PlayCount = album.PlayCount + dir.UserRating = album.Rating + dir.SongCount = album.SongCount + dir.CoverArt = album.CoverArtId + if album.Starred { + dir.Starred = &album.StarredAt + } + + mfs, err := c.ds.MediaFile(ctx).FindByAlbum(album.ID) + if err != nil { + return nil, err + } + + dir.Child = ChildrenFromMediaFiles(ctx, mfs) + return dir, nil +} + func (c *BrowsingController) buildAlbum(ctx context.Context, album *model.Album, mfs model.MediaFiles) *responses.AlbumWithSongsID3 { dir := &responses.AlbumWithSongsID3{} dir.Id = album.ID diff --git a/server/subsonic/engine/browser.go b/server/subsonic/engine/browser.go index 635d7483c..4ccd22bdd 100644 --- a/server/subsonic/engine/browser.go +++ b/server/subsonic/engine/browser.go @@ -2,18 +2,13 @@ package engine import ( "context" - "fmt" "sort" "strings" - "time" - "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" ) type Browser interface { - Directory(ctx context.Context, id string) (*DirectoryInfo, error) - Album(ctx context.Context, id string) (*DirectoryInfo, error) GetSong(ctx context.Context, id string) (*Entry, error) GetGenres(ctx context.Context) (model.Genres, error) } @@ -26,55 +21,6 @@ type browser struct { ds model.DataStore } -type DirectoryInfo struct { - Id string - Name string - Entries Entries - Parent string - Starred time.Time - PlayCount int64 - UserRating int - AlbumCount int - CoverArt string - Artist string - ArtistId string - SongCount int - Duration int - Created time.Time - Year int - Genre string -} - -func (b *browser) Artist(ctx context.Context, id string) (*DirectoryInfo, error) { - a, albums, err := b.retrieveArtist(ctx, id) - if err != nil { - return nil, err - } - log.Debug(ctx, "Found Artist", "id", id, "name", a.Name) - return b.buildArtistDir(a, albums), nil -} - -func (b *browser) Album(ctx context.Context, id string) (*DirectoryInfo, error) { - al, tracks, err := b.retrieveAlbum(ctx, id) - if err != nil { - return nil, err - } - log.Debug(ctx, "Found Album", "id", id, "name", al.Name) - return b.buildAlbumDir(al, tracks), nil -} - -func (b *browser) Directory(ctx context.Context, id string) (*DirectoryInfo, error) { - switch { - case b.isArtist(ctx, id): - return b.Artist(ctx, id) - case b.isAlbum(ctx, id): - return b.Album(ctx, id) - default: - log.Debug(ctx, "Directory not found", "id", id) - return nil, model.ErrNotFound - } -} - func (b *browser) GetSong(ctx context.Context, id string) (*Entry, error) { mf, err := b.ds.MediaFile(ctx).Get(id) if err != nil { @@ -97,88 +43,3 @@ func (b *browser) GetGenres(ctx context.Context) (model.Genres, error) { }) return genres, err } - -func (b *browser) buildArtistDir(a *model.Artist, albums model.Albums) *DirectoryInfo { - dir := &DirectoryInfo{ - Id: a.ID, - Name: a.Name, - AlbumCount: a.AlbumCount, - } - - dir.Entries = make(Entries, len(albums)) - for i := range albums { - al := albums[i] - dir.Entries[i] = FromAlbum(&al) - dir.PlayCount += al.PlayCount - } - return dir -} - -func (b *browser) buildAlbumDir(al *model.Album, tracks model.MediaFiles) *DirectoryInfo { - dir := &DirectoryInfo{ - Id: al.ID, - Name: al.Name, - Parent: al.AlbumArtistID, - Artist: al.AlbumArtist, - ArtistId: al.AlbumArtistID, - SongCount: al.SongCount, - Duration: int(al.Duration), - Created: al.CreatedAt, - Year: al.MaxYear, - Genre: al.Genre, - CoverArt: al.CoverArtId, - PlayCount: al.PlayCount, - UserRating: al.Rating, - } - - if al.Starred { - dir.Starred = al.StarredAt - } - - dir.Entries = FromMediaFiles(tracks) - return dir -} - -func (b *browser) isArtist(ctx context.Context, id string) bool { - found, err := b.ds.Artist(ctx).Exists(id) - if err != nil { - log.Debug(ctx, "Error searching for Artist", "id", id, err) - return false - } - return found -} - -func (b *browser) isAlbum(ctx context.Context, id string) bool { - found, err := b.ds.Album(ctx).Exists(id) - if err != nil { - log.Debug(ctx, "Error searching for Album", "id", id, err) - return false - } - return found -} - -func (b *browser) retrieveArtist(ctx context.Context, id string) (a *model.Artist, as model.Albums, err error) { - a, err = b.ds.Artist(ctx).Get(id) - if err != nil { - err = fmt.Errorf("Error reading Artist %s from DB: %v", id, err) - return - } - - if as, err = b.ds.Album(ctx).FindByArtist(id); err != nil { - err = fmt.Errorf("Error reading %s's albums from DB: %v", a.Name, err) - } - return -} - -func (b *browser) retrieveAlbum(ctx context.Context, id string) (al *model.Album, mfs model.MediaFiles, err error) { - al, err = b.ds.Album(ctx).Get(id) - if err != nil { - err = fmt.Errorf("Error reading Album %s from DB: %v", id, err) - return - } - - if mfs, err = b.ds.MediaFile(ctx).FindByAlbum(id); err != nil { - err = fmt.Errorf("Error reading %s's tracks from DB: %v", al.Name, err) - } - return -} diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index 86bc6bf33..e0f4adfde 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -255,3 +255,20 @@ func ChildrenFromAlbums(ctx context.Context, als model.Albums) []responses.Child } return children } + +// TODO: Should the type be encoded in the ID? +func getEntityByID(ctx context.Context, ds model.DataStore, id string) (interface{}, error) { + ar, err := ds.Artist(ctx).Get(id) + if err == nil { + return ar, nil + } + al, err := ds.Album(ctx).Get(id) + if err == nil { + return al, nil + } + mf, err := ds.MediaFile(ctx).Get(id) + if err == nil { + return mf, nil + } + return nil, err +}