2020-01-19 14:21:44 -09:00
|
|
|
package subsonic
|
2016-03-09 06:09:15 -09:00
|
|
|
|
|
|
|
|
import (
|
2020-03-16 08:56:15 -08:00
|
|
|
"context"
|
2020-01-21 12:35:57 -09:00
|
|
|
"errors"
|
2016-03-24 09:28:20 -08:00
|
|
|
"fmt"
|
2020-01-07 10:56:26 -09:00
|
|
|
"net/http"
|
2023-05-25 05:14:00 -08:00
|
|
|
"time"
|
2016-03-24 09:28:20 -08:00
|
|
|
|
2020-01-23 15:44:08 -09:00
|
|
|
"github.com/navidrome/navidrome/log"
|
|
|
|
|
"github.com/navidrome/navidrome/model"
|
|
|
|
|
"github.com/navidrome/navidrome/server/subsonic/responses"
|
2023-12-21 13:41:09 -09:00
|
|
|
"github.com/navidrome/navidrome/utils/req"
|
2016-03-09 06:09:15 -09:00
|
|
|
)
|
|
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) GetPlaylists(r *http.Request) (*responses.Subsonic, error) {
|
2020-10-27 09:52:01 -08:00
|
|
|
ctx := r.Context()
|
2022-11-21 08:57:56 -09:00
|
|
|
allPls, err := api.ds.Playlist(ctx).GetAll(model.QueryOptions{Sort: "name"})
|
2016-03-09 06:09:15 -09:00
|
|
|
if err != nil {
|
2020-01-08 16:45:07 -09:00
|
|
|
log.Error(r, err)
|
2020-10-27 11:23:29 -08:00
|
|
|
return nil, err
|
2016-03-09 06:09:15 -09:00
|
|
|
}
|
2016-03-20 09:14:04 -08:00
|
|
|
playlists := make([]responses.Playlist, len(allPls))
|
2016-03-21 08:26:55 -08:00
|
|
|
for i, p := range allPls {
|
2022-11-21 08:57:56 -09:00
|
|
|
playlists[i] = *api.buildPlaylist(p)
|
2016-03-09 06:09:15 -09:00
|
|
|
}
|
2020-08-13 18:11:18 -08:00
|
|
|
response := newResponse()
|
2016-03-09 06:09:15 -09:00
|
|
|
response.Playlists = &responses.Playlists{Playlist: playlists}
|
2020-01-07 10:56:26 -09:00
|
|
|
return response, nil
|
2016-03-09 06:09:15 -09:00
|
|
|
}
|
2016-03-09 14:28:11 -09:00
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) GetPlaylist(r *http.Request) (*responses.Subsonic, error) {
|
2020-10-27 09:52:01 -08:00
|
|
|
ctx := r.Context()
|
2023-12-21 13:41:09 -09:00
|
|
|
p := req.Params(r)
|
|
|
|
|
id, err := p.String("id")
|
2020-01-07 10:56:26 -09:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-11-21 08:57:56 -09:00
|
|
|
return api.getPlaylist(ctx, id)
|
2020-10-28 08:46:06 -08:00
|
|
|
}
|
|
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) getPlaylist(ctx context.Context, id string) (*responses.Subsonic, error) {
|
2023-01-01 16:28:03 -09:00
|
|
|
pls, err := api.ds.Playlist(ctx).GetWithTracks(id, true)
|
2022-09-30 14:54:25 -08:00
|
|
|
if errors.Is(err, model.ErrNotFound) {
|
2020-10-28 08:46:06 -08:00
|
|
|
log.Error(ctx, err.Error(), "id", id)
|
2020-08-13 18:11:18 -08:00
|
|
|
return nil, newError(responses.ErrorDataNotFound, "Directory not found")
|
2022-09-30 14:54:25 -08:00
|
|
|
}
|
|
|
|
|
if err != nil {
|
2020-10-28 08:46:06 -08:00
|
|
|
log.Error(ctx, err)
|
2020-10-27 11:23:29 -08:00
|
|
|
return nil, err
|
2016-03-09 14:28:11 -09:00
|
|
|
}
|
|
|
|
|
|
2020-08-13 18:11:18 -08:00
|
|
|
response := newResponse()
|
2022-11-21 08:57:56 -09:00
|
|
|
response.Playlist = api.buildPlaylistWithSongs(ctx, pls)
|
2020-01-07 10:56:26 -09:00
|
|
|
return response, nil
|
2016-03-09 14:28:11 -09:00
|
|
|
}
|
|
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) create(ctx context.Context, playlistId, name string, ids []string) (string, error) {
|
|
|
|
|
err := api.ds.WithTx(func(tx model.DataStore) error {
|
2020-10-27 09:52:01 -08:00
|
|
|
owner := getUser(ctx)
|
|
|
|
|
var pls *model.Playlist
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
if playlistId != "" {
|
|
|
|
|
pls, err = tx.Playlist(ctx).Get(playlistId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-10-29 18:55:28 -08:00
|
|
|
if owner.ID != pls.OwnerID {
|
2020-10-27 09:52:01 -08:00
|
|
|
return model.ErrNotAuthorized
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2021-10-29 18:55:28 -08:00
|
|
|
pls = &model.Playlist{Name: name}
|
|
|
|
|
pls.OwnerID = owner.ID
|
2020-10-27 09:52:01 -08:00
|
|
|
}
|
2021-10-16 18:44:44 -08:00
|
|
|
pls.Tracks = nil
|
|
|
|
|
pls.AddTracks(ids)
|
2020-10-27 09:52:01 -08:00
|
|
|
|
2020-10-28 08:46:06 -08:00
|
|
|
err = tx.Playlist(ctx).Put(pls)
|
|
|
|
|
playlistId = pls.ID
|
|
|
|
|
return err
|
2020-10-27 09:52:01 -08:00
|
|
|
})
|
2020-10-28 08:46:06 -08:00
|
|
|
return playlistId, err
|
2020-10-27 09:52:01 -08:00
|
|
|
}
|
|
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) CreatePlaylist(r *http.Request) (*responses.Subsonic, error) {
|
2020-10-28 08:46:06 -08:00
|
|
|
ctx := r.Context()
|
2023-12-21 13:41:09 -09:00
|
|
|
p := req.Params(r)
|
|
|
|
|
songIds, _ := p.Strings("songId")
|
|
|
|
|
playlistId, _ := p.String("playlistId")
|
|
|
|
|
name, _ := p.String("name")
|
2020-01-21 12:35:57 -09:00
|
|
|
if playlistId == "" && name == "" {
|
2020-10-27 09:52:01 -08:00
|
|
|
return nil, errors.New("required parameter name is missing")
|
2020-01-07 10:56:26 -09:00
|
|
|
}
|
2022-11-21 08:57:56 -09:00
|
|
|
id, err := api.create(ctx, playlistId, name, songIds)
|
2016-03-24 08:06:39 -08:00
|
|
|
if err != nil {
|
2020-01-08 16:45:07 -09:00
|
|
|
log.Error(r, err)
|
2020-10-27 11:23:29 -08:00
|
|
|
return nil, err
|
2016-03-24 08:06:39 -08:00
|
|
|
}
|
2022-11-21 08:57:56 -09:00
|
|
|
return api.getPlaylist(ctx, id)
|
2016-03-24 08:06:39 -08:00
|
|
|
}
|
|
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) DeletePlaylist(r *http.Request) (*responses.Subsonic, error) {
|
2023-12-21 13:41:09 -09:00
|
|
|
p := req.Params(r)
|
|
|
|
|
id, err := p.String("id")
|
2020-01-07 10:56:26 -09:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-11-21 08:57:56 -09:00
|
|
|
err = api.ds.Playlist(r.Context()).Delete(id)
|
2022-09-30 14:54:25 -08:00
|
|
|
if errors.Is(err, model.ErrNotAuthorized) {
|
2020-08-13 18:11:18 -08:00
|
|
|
return nil, newError(responses.ErrorAuthorizationFail)
|
2020-01-21 13:18:46 -09:00
|
|
|
}
|
2016-03-24 08:17:35 -08:00
|
|
|
if err != nil {
|
2020-01-08 16:45:07 -09:00
|
|
|
log.Error(r, err)
|
2020-10-27 11:23:29 -08:00
|
|
|
return nil, err
|
2016-03-24 08:17:35 -08:00
|
|
|
}
|
2020-08-13 18:11:18 -08:00
|
|
|
return newResponse(), nil
|
2016-03-24 08:17:35 -08:00
|
|
|
}
|
|
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) UpdatePlaylist(r *http.Request) (*responses.Subsonic, error) {
|
2023-12-21 13:41:09 -09:00
|
|
|
p := req.Params(r)
|
|
|
|
|
playlistId, err := p.String("playlistId")
|
2020-01-07 10:56:26 -09:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-12-21 13:41:09 -09:00
|
|
|
songsToAdd, _ := p.Strings("songIdToAdd")
|
|
|
|
|
songIndexesToRemove, _ := p.Ints("songIndexToRemove")
|
2020-10-27 09:52:01 -08:00
|
|
|
var plsName *string
|
2023-12-21 13:41:09 -09:00
|
|
|
if s, err := p.String("name"); err == nil {
|
|
|
|
|
plsName = &s
|
2021-05-29 13:16:56 -08:00
|
|
|
}
|
|
|
|
|
var comment *string
|
2023-12-21 13:41:09 -09:00
|
|
|
if s, err := p.String("comment"); err == nil {
|
|
|
|
|
comment = &s
|
2021-05-29 13:16:56 -08:00
|
|
|
}
|
|
|
|
|
var public *bool
|
2023-12-21 13:41:09 -09:00
|
|
|
if p, err := p.Bool("public"); err == nil {
|
2021-05-29 13:16:56 -08:00
|
|
|
public = &p
|
2016-03-24 09:28:20 -08:00
|
|
|
}
|
|
|
|
|
|
2020-05-26 18:03:25 -08:00
|
|
|
log.Debug(r, "Updating playlist", "id", playlistId)
|
2020-10-27 09:52:01 -08:00
|
|
|
if plsName != nil {
|
|
|
|
|
log.Trace(r, fmt.Sprintf("-- New Name: '%s'", *plsName))
|
2016-03-24 09:28:20 -08:00
|
|
|
}
|
2020-05-26 18:03:25 -08:00
|
|
|
log.Trace(r, fmt.Sprintf("-- Adding: '%v'", songsToAdd))
|
|
|
|
|
log.Trace(r, fmt.Sprintf("-- Removing: '%v'", songIndexesToRemove))
|
2016-03-24 09:28:20 -08:00
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
err = api.playlists.Update(r.Context(), playlistId, plsName, comment, public, songsToAdd, songIndexesToRemove)
|
2022-09-30 14:54:25 -08:00
|
|
|
if errors.Is(err, model.ErrNotAuthorized) {
|
2020-08-13 18:11:18 -08:00
|
|
|
return nil, newError(responses.ErrorAuthorizationFail)
|
2020-01-21 13:18:46 -09:00
|
|
|
}
|
2016-03-24 09:28:20 -08:00
|
|
|
if err != nil {
|
2020-01-08 16:45:07 -09:00
|
|
|
log.Error(r, err)
|
2020-10-27 11:23:29 -08:00
|
|
|
return nil, err
|
2016-03-24 09:28:20 -08:00
|
|
|
}
|
2020-08-13 18:11:18 -08:00
|
|
|
return newResponse(), nil
|
2016-03-24 09:28:20 -08:00
|
|
|
}
|
|
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) buildPlaylistWithSongs(ctx context.Context, p *model.Playlist) *responses.PlaylistWithSongs {
|
2020-04-11 12:45:21 -08:00
|
|
|
pls := &responses.PlaylistWithSongs{
|
2022-11-21 08:57:56 -09:00
|
|
|
Playlist: *api.buildPlaylist(*p),
|
2020-04-11 12:45:21 -08:00
|
|
|
}
|
2021-10-16 18:44:44 -08:00
|
|
|
pls.Entry = childrenFromMediaFiles(ctx, p.MediaFiles())
|
2020-04-11 12:45:21 -08:00
|
|
|
return pls
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-21 08:57:56 -09:00
|
|
|
func (api *Router) buildPlaylist(p model.Playlist) *responses.Playlist {
|
2020-04-11 12:45:21 -08:00
|
|
|
pls := &responses.Playlist{}
|
2020-10-27 09:52:01 -08:00
|
|
|
pls.Id = p.ID
|
|
|
|
|
pls.Name = p.Name
|
|
|
|
|
pls.Comment = p.Comment
|
2023-03-14 05:48:52 -08:00
|
|
|
pls.SongCount = int32(p.SongCount)
|
2021-10-29 18:55:28 -08:00
|
|
|
pls.Owner = p.OwnerName
|
2023-03-14 05:48:52 -08:00
|
|
|
pls.Duration = int32(p.Duration)
|
2020-10-27 09:52:01 -08:00
|
|
|
pls.Public = p.Public
|
|
|
|
|
pls.Created = p.CreatedAt
|
2023-01-01 15:35:19 -09:00
|
|
|
pls.CoverArt = p.CoverArtID().String()
|
2023-05-25 05:14:00 -08:00
|
|
|
if p.IsSmartPlaylist() {
|
|
|
|
|
pls.Changed = time.Now()
|
|
|
|
|
} else {
|
|
|
|
|
pls.Changed = p.UpdatedAt
|
|
|
|
|
}
|
2016-03-09 14:28:11 -09:00
|
|
|
return pls
|
|
|
|
|
}
|