diff --git a/server/subsonic/browsing.go b/server/subsonic/browsing.go index 5b9c4f3c9..397e98cec 100644 --- a/server/subsonic/browsing.go +++ b/server/subsonic/browsing.go @@ -400,6 +400,31 @@ func (api *Router) GetTopSongs(r *http.Request) (*responses.Subsonic, error) { return response, nil } +func (api *Router) buildFolderDirectory(ctx context.Context, folder *model.Folder) (*responses.Directory, error) { + dir := &responses.Directory{} + dir.Id = folder.ID + dir.Name = folder.Name + dir.Parent = folder.ParentID + dir.CoverArt = folder.CoverArtID().String() + + childFolders, err := api.ds.Folder(ctx).GetAll(filter.FoldersByParent(folder.ID)) + if err != nil { + return nil, err + } + + mfs, err := api.ds.MediaFile(ctx).GetAll(filter.SongsByFolder(folder.ID)) + if err != nil { + return nil, err + } + + dir.Child = make([]responses.Child, 0, len(childFolders)+len(mfs)) + for _, f := range childFolders { + dir.Child = append(dir.Child, childFromFolder(ctx, f)) + } + dir.Child = append(dir.Child, slice.MapWithArg(mfs, ctx, childFromMediaFile)...) + return dir, nil +} + func (api *Router) buildArtistDirectory(ctx context.Context, artist *model.Artist) (*responses.Directory, error) { dir := &responses.Directory{} dir.Id = artist.ID diff --git a/server/subsonic/browsing_test.go b/server/subsonic/browsing_test.go index b8f510aed..2109faa69 100644 --- a/server/subsonic/browsing_test.go +++ b/server/subsonic/browsing_test.go @@ -60,6 +60,89 @@ var _ = Describe("Browsing", func() { }) }) + Describe("buildFolderDirectory", func() { + var folder model.Folder + + BeforeEach(func() { + folder = model.Folder{ + ID: "folder-1", + ParentID: "root-1", + Name: "Jazz", + } + }) + + It("returns a directory with correct id, name and parent", func() { + ctx = contextWithUser(ctx, "user-id", 1) + ds.Folder(ctx).(*tests.MockFolderRepo).SetData([]model.Folder{}) + + dir, err := api.buildFolderDirectory(ctx, &folder) + Expect(err).ToNot(HaveOccurred()) + Expect(dir.Id).To(Equal("folder-1")) + Expect(dir.Name).To(Equal("Jazz")) + Expect(dir.Parent).To(Equal("root-1")) + }) + + It("includes child folders as directory entries with IsDir=true", func() { + ctx = contextWithUser(ctx, "user-id", 1) + childFolder := model.Folder{ID: "folder-2", ParentID: "folder-1", Name: "Blues"} + ds.Folder(ctx).(*tests.MockFolderRepo).SetData([]model.Folder{childFolder}) + + dir, err := api.buildFolderDirectory(ctx, &folder) + Expect(err).ToNot(HaveOccurred()) + Expect(dir.Child).To(HaveLen(1)) + Expect(dir.Child[0].Id).To(Equal("folder-2")) + Expect(dir.Child[0].IsDir).To(BeTrue()) + }) + + It("includes media files as directory entries with IsDir=false", func() { + ctx = contextWithUser(ctx, "user-id", 1) + ds.Folder(ctx).(*tests.MockFolderRepo).SetData([]model.Folder{}) + ds.MediaFile(ctx).(*tests.MockMediaFileRepo).SetData(model.MediaFiles{ + {ID: "mf-1", Title: "Track 1", FolderID: "folder-1"}, + }) + + dir, err := api.buildFolderDirectory(ctx, &folder) + Expect(err).ToNot(HaveOccurred()) + Expect(dir.Child).To(HaveLen(1)) + Expect(dir.Child[0].Id).To(Equal("mf-1")) + Expect(dir.Child[0].IsDir).To(BeFalse()) + }) + + It("lists child folders before media files", func() { + ctx = contextWithUser(ctx, "user-id", 1) + childFolder := model.Folder{ID: "folder-2", ParentID: "folder-1", Name: "Sub"} + ds.Folder(ctx).(*tests.MockFolderRepo).SetData([]model.Folder{childFolder}) + ds.MediaFile(ctx).(*tests.MockMediaFileRepo).SetData(model.MediaFiles{ + {ID: "mf-1", Title: "Track 1", FolderID: "folder-1"}, + }) + + dir, err := api.buildFolderDirectory(ctx, &folder) + Expect(err).ToNot(HaveOccurred()) + Expect(dir.Child).To(HaveLen(2)) + Expect(dir.Child[0].IsDir).To(BeTrue()) + Expect(dir.Child[1].IsDir).To(BeFalse()) + }) + + It("sets cover art when folder has image files", func() { + ctx = contextWithUser(ctx, "user-id", 1) + folder.ImageFiles = []string{"cover.jpg"} + ds.Folder(ctx).(*tests.MockFolderRepo).SetData([]model.Folder{}) + + dir, err := api.buildFolderDirectory(ctx, &folder) + Expect(err).ToNot(HaveOccurred()) + Expect(dir.CoverArt).ToNot(BeEmpty()) + }) + + It("returns empty cover art when folder has no image files", func() { + ctx = contextWithUser(ctx, "user-id", 1) + ds.Folder(ctx).(*tests.MockFolderRepo).SetData([]model.Folder{}) + + dir, err := api.buildFolderDirectory(ctx, &folder) + Expect(err).ToNot(HaveOccurred()) + Expect(dir.CoverArt).To(BeEmpty()) + }) + }) + Describe("GetIndexes", func() { It("should validate user access to the specified musicFolderId", func() { // Create mock user with access to library 1 only diff --git a/server/subsonic/filter/filters.go b/server/subsonic/filter/filters.go index 17cb32d41..a3f8e0ccb 100644 --- a/server/subsonic/filter/filters.go +++ b/server/subsonic/filter/filters.go @@ -97,6 +97,13 @@ func SongsByFolder(folderID string) Options { }) } +func FoldersByParent(parentID string) Options { + return Options{ + Filters: And{Eq{"parent_id": parentID}, Eq{"missing": false}}, + Sort: "name", + } +} + func SongsByRandom(genre string, fromYear, toYear int) Options { options := Options{ Sort: "random",