package subsonic import ( "cmp" "net/http" "strings" "time" "github.com/deluan/rest" "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server/public" "github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/utils/req" "github.com/navidrome/navidrome/utils/slice" ) func (api *Router) GetShares(r *http.Request) (*responses.Subsonic, error) { repo := api.share.NewRepository(r.Context()).(model.ShareRepository) shares, err := repo.GetAll(model.QueryOptions{Sort: "created_at desc"}) if err != nil { return nil, err } response := newResponse() response.Shares = &responses.Shares{} for _, share := range shares { response.Shares.Share = append(response.Shares.Share, api.buildShare(r, share)) } return response, nil } func (api *Router) buildShare(r *http.Request, share model.Share) responses.Share { resp := responses.Share{ ID: share.ID, Url: public.ShareURL(r.Context(), share.ID), Description: share.Description, Username: share.Username, Created: share.CreatedAt, Expires: share.ExpiresAt, LastVisited: share.LastVisitedAt, VisitCount: int32(share.VisitCount), } if resp.Description == "" { resp.Description = share.Contents } if len(share.Albums) > 0 { resp.Entry = slice.MapWithArg(share.Albums, r.Context(), childFromAlbum) } else { resp.Entry = slice.MapWithArg(share.Tracks, r.Context(), childFromMediaFile) } return resp } func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) { p := req.Params(r) ids := p.Strings("id") if len(ids) == 0 { return nil, newError(responses.ErrorMissingParameter, "missing parameter: 'id'") } description, _ := p.String("description") repo := api.share.NewRepository(r.Context()) share := &model.Share{ Description: description, Downloadable: p.BoolOr("downloadable", conf.Server.DefaultDownloadableShare && conf.Server.EnableDownloads), ExpiresAt: new(p.TimeOr("expires", time.Time{})), ResourceIDs: strings.Join(ids, ","), } id, err := repo.(rest.Persistable).Save(share) if err != nil { return nil, err } share, err = repo.(model.ShareRepository).Get(id) if err != nil { return nil, err } response := newResponse() response.Shares = &responses.Shares{Share: []responses.Share{api.buildShare(r, *share)}} return response, nil } func (api *Router) UpdateShare(r *http.Request) (*responses.Subsonic, error) { p := req.Params(r) id, err := p.String("id") if err != nil { return nil, err } repo := api.share.NewRepository(r.Context()) // The update always writes description and downloadable, so read back the // stored value for whichever one the client omitted. description := p.StringPtr("description") downloadable := p.BoolPtr("downloadable") if description == nil || downloadable == nil { current, err := repo.Read(id) if err != nil { return nil, err } cur := current.(*model.Share) description = cmp.Or(description, &cur.Description) downloadable = cmp.Or(downloadable, &cur.Downloadable) } share := &model.Share{ ID: id, Description: *description, Downloadable: *downloadable, ExpiresAt: new(p.TimeOr("expires", time.Time{})), } err = repo.(rest.Persistable).Update(id, share) if err != nil { return nil, err } return newResponse(), nil } func (api *Router) DeleteShare(r *http.Request) (*responses.Subsonic, error) { p := req.Params(r) id, err := p.String("id") if err != nil { return nil, err } repo := api.share.NewRepository(r.Context()) err = repo.(rest.Persistable).Delete(id) if err != nil { return nil, err } return newResponse(), nil }