diff --git a/core/share.go b/core/share.go index ca534adaf..adf13a31c 100644 --- a/core/share.go +++ b/core/share.go @@ -10,7 +10,6 @@ import ( gonanoid "github.com/matoous/go-nanoid/v2" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" - "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/utils/slice" ) @@ -31,11 +30,10 @@ type shareService struct { func (s *shareService) Load(ctx context.Context, id string) (*model.Share, error) { repo := s.ds.Share(ctx) - entity, err := repo.(rest.Repository).Read(id) + share, err := repo.Get(id) if err != nil { return nil, err } - share := entity.(*model.Share) if !share.ExpiresAt.IsZero() && share.ExpiresAt.Before(time.Now()) { return nil, model.ErrNotAvailable } @@ -46,35 +44,7 @@ func (s *shareService) Load(ctx context.Context, id string) (*model.Share, error if err != nil { log.Warn(ctx, "Could not increment visit count for share", "share", share.ID) } - - idList := strings.Split(share.ResourceIDs, ",") - var mfs model.MediaFiles - switch share.ResourceType { - case "album": - mfs, err = s.loadMediafiles(ctx, squirrel.Eq{"album_id": idList}, "album") - case "playlist": - mfs, err = s.loadPlaylistTracks(ctx, share.ResourceIDs) - } - if err != nil { - return nil, err - } - share.Tracks = mfs - return entity.(*model.Share), nil -} - -func (s *shareService) loadMediafiles(ctx context.Context, filter squirrel.Eq, sort string) (model.MediaFiles, error) { - return s.ds.MediaFile(ctx).GetAll(model.QueryOptions{Filters: filter, Sort: sort}) -} - -func (s *shareService) loadPlaylistTracks(ctx context.Context, id string) (model.MediaFiles, error) { - // Create a context with a fake admin user, to be able to access playlists - ctx = request.WithUser(ctx, model.User{IsAdmin: true}) - - tracks, err := s.ds.Playlist(ctx).Tracks(id, true).GetAll(model.QueryOptions{Sort: "id"}) - if err != nil { - return nil, err - } - return tracks.MediaFiles(), nil + return share, nil } func (s *shareService) NewRepository(ctx context.Context) rest.Repository { diff --git a/model/share.go b/model/share.go index ce3822878..aa31711fc 100644 --- a/model/share.go +++ b/model/share.go @@ -20,11 +20,13 @@ type Share struct { CreatedAt time.Time `structs:"created_at" json:"createdAt,omitempty"` UpdatedAt time.Time `structs:"updated_at" json:"updatedAt,omitempty"` Tracks MediaFiles `structs:"-" json:"tracks,omitempty" orm:"-"` + Albums Albums `structs:"-" json:"albums,omitempty" orm:"-"` } type Shares []Share type ShareRepository interface { Exists(id string) (bool, error) + Get(id string) (*Share, error) GetAll(options ...QueryOptions) (Shares, error) } diff --git a/persistence/share_repository.go b/persistence/share_repository.go index 03a2e1b6d..8d0413ae2 100644 --- a/persistence/share_repository.go +++ b/persistence/share_repository.go @@ -3,12 +3,16 @@ package persistence import ( "context" "errors" + "fmt" + "strings" "time" . "github.com/Masterminds/squirrel" "github.com/beego/beego/v2/client/orm" "github.com/deluan/rest" + "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/model/request" ) type shareRepository struct { @@ -40,13 +44,66 @@ func (r *shareRepository) selectShare(options ...model.QueryOptions) SelectBuild func (r *shareRepository) Exists(id string) (bool, error) { return r.exists(Select().Where(Eq{"id": id})) } +func (r *shareRepository) Get(id string) (*model.Share, error) { + sel := r.selectShare().Where(Eq{"share.id": id}) + var res model.Share + err := r.queryOne(sel, &res) + if err != nil { + return nil, err + } + err = r.loadMedia(&res) + return &res, err +} + func (r *shareRepository) GetAll(options ...model.QueryOptions) (model.Shares, error) { sq := r.selectShare(options...) res := model.Shares{} err := r.queryAll(sq, &res) + if err != nil { + return nil, err + } + for i := range res { + err = r.loadMedia(&res[i]) + if err != nil { + return nil, fmt.Errorf("error loading media for share %s: %w", res[i].ID, err) + } + } return res, err } +func (r *shareRepository) loadMedia(share *model.Share) error { + var err error + ids := strings.Split(share.ResourceIDs, ",") + if len(ids) == 0 { + return nil + } + switch share.ResourceType { + case "album": + albumRepo := NewAlbumRepository(r.ctx, r.ormer) + share.Albums, err = albumRepo.GetAll(model.QueryOptions{Filters: Eq{"id": ids}}) + if err != nil { + return err + } + mfRepo := NewMediaFileRepository(r.ctx, r.ormer) + share.Tracks, err = mfRepo.GetAll(model.QueryOptions{Filters: Eq{"album_id": ids}, Sort: "album"}) + return err + case "playlist": + // Create a context with a fake admin user, to be able to access all playlists + ctx := request.WithUser(r.ctx, model.User{IsAdmin: true}) + plsRepo := NewPlaylistRepository(ctx, r.ormer) + tracks, err := plsRepo.Tracks(ids[0], true).GetAll(model.QueryOptions{Sort: "id"}) + if err != nil { + return err + } + if len(tracks) >= 0 { + share.Tracks = tracks.MediaFiles() + } + return nil + } + log.Warn(r.ctx, "Unsupported Share ResourceType", "share", share.ID, "resourceType", share.ResourceType) + return nil +} + func (r *shareRepository) Update(id string, entity interface{}, cols ...string) error { s := entity.(*model.Share) // TODO Validate record @@ -92,19 +149,18 @@ func (r *shareRepository) NewInstance() interface{} { return &model.Share{} } -func (r *shareRepository) Get(id string) (*model.Share, error) { +func (r *shareRepository) Read(id string) (interface{}, error) { sel := r.selectShare().Where(Eq{"share.id": id}) var res model.Share err := r.queryOne(sel, &res) return &res, err } -func (r *shareRepository) Read(id string) (interface{}, error) { - return r.Get(id) -} - func (r *shareRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { - return r.GetAll(r.parseRestOptions(options...)) + sq := r.selectShare(r.parseRestOptions(options...)) + res := model.Shares{} + err := r.queryAll(sq, &res) + return res, err } var _ model.ShareRepository = (*shareRepository)(nil) diff --git a/server/subsonic/sharing.go b/server/subsonic/sharing.go index d07d90130..4f19dec90 100644 --- a/server/subsonic/sharing.go +++ b/server/subsonic/sharing.go @@ -13,12 +13,11 @@ import ( ) func (api *Router) GetShares(r *http.Request) (*responses.Subsonic, error) { - repo := api.share.NewRepository(r.Context()) - entity, err := repo.ReadAll() + repo := api.share.NewRepository(r.Context()).(model.ShareRepository) + shares, err := repo.GetAll() if err != nil { return nil, err } - shares := entity.(model.Shares) response := newResponse() response.Shares = &responses.Shares{} @@ -29,8 +28,7 @@ func (api *Router) GetShares(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) buildShare(r *http.Request, share model.Share) responses.Share { - return responses.Share{ - Entry: childrenFromMediaFiles(r.Context(), share.Tracks), + resp := responses.Share{ ID: share.ID, Url: public.ShareURL(r, share.ID), Description: share.Description, @@ -40,6 +38,12 @@ func (api *Router) buildShare(r *http.Request, share model.Share) responses.Shar LastVisited: share.LastVisitedAt, VisitCount: share.VisitCount, } + if len(share.Albums) > 0 { + resp.Entry = childrenFromAlbums(r.Context(), share.Albums) + } else { + resp.Entry = childrenFromMediaFiles(r.Context(), share.Tracks) + } + return resp } func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) { @@ -63,11 +67,10 @@ func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) { return nil, err } - entity, err := repo.Read(id) + share, err = repo.(model.ShareRepository).Get(id) if err != nil { return nil, err } - share = entity.(*model.Share) response := newResponse() response.Shares = &responses.Shares{Share: []responses.Share{api.buildShare(r, *share)}}