package subsonicimport ("context""errors""fmt""net/http""github.com/navidrome/navidrome/conf""github.com/navidrome/navidrome/log""github.com/navidrome/navidrome/model""github.com/navidrome/navidrome/model/request""github.com/navidrome/navidrome/server/subsonic/responses""github.com/navidrome/navidrome/utils/req""github.com/navidrome/navidrome/utils/slice")func (api *Router) GetPlaylists(r *http.Request) (*responses.Subsonic, error) {ctx := r.Context()allPls, err := api.playlists.GetAll(ctx, model.QueryOptions{Sort: "name"})if err != nil {log.Error(r, err)return nil, err}response := newResponse()response.Playlists = &responses.Playlists{Playlist: slice.MapWithArg(allPls, ctx, api.buildPlaylist),}return response, nil}func (api *Router) GetPlaylist(r *http.Request) (*responses.Subsonic, error) {ctx := r.Context()p := req.Params(r)id, err := p.String("id")if err != nil {return nil, err}return api.getPlaylist(ctx, id)}func (api *Router) getPlaylist(ctx context.Context, id string) (*responses.Subsonic, error) {pls, err := api.playlists.GetWithTracks(ctx, id)if errors.Is(err, model.ErrNotFound) {log.Error(ctx, err.Error(), "id", id)return nil, newError(responses.ErrorDataNotFound, "playlist not found")}if err != nil {log.Error(ctx, err)return nil, err}response := newResponse()response.Playlist = &responses.PlaylistWithSongs{Playlist: api.buildPlaylist(ctx, *pls),}response.Playlist.Entry = slice.MapWithArg(pls.MediaFiles(), ctx, childFromMediaFile)return response, nil}func (api *Router) CreatePlaylist(r *http.Request) (*responses.Subsonic, error) {ctx := r.Context()p := req.Params(r)songIds := p.Strings("songId")playlistId, _ := p.String("playlistId")name, _ := p.String("name")if playlistId == "" && name == "" {return nil, errors.New("required parameter name is missing")}id, err := api.playlists.Create(ctx, playlistId, name, songIds)if err != nil {log.Error(r, err)return nil, err}return api.getPlaylist(ctx, id)}func (api *Router) DeletePlaylist(r *http.Request) (*responses.Subsonic, error) {p := req.Params(r)id, err := p.String("id")if err != nil {return nil, err}err = api.playlists.Delete(r.Context(), id)if errors.Is(err, model.ErrNotAuthorized) {return nil, newError(responses.ErrorAuthorizationFail)}if err != nil {log.Error(r, err)return nil, err}return newResponse(), nil}func (api *Router) UpdatePlaylist(r *http.Request) (*responses.Subsonic, error) {p := req.Params(r)playlistId, err := p.String("playlistId")if err != nil {return nil, err}songsToAdd := p.Strings("songIdToAdd")songIndexesToRemove, _ := p.Ints("songIndexToRemove")var plsName *stringif s, err := p.String("name"); err == nil {plsName = &s}comment := p.StringPtr("comment")public := p.BoolPtr("public")log.Debug(r, "Updating playlist", "id", playlistId)if plsName != nil {log.Trace(r, fmt.Sprintf("-- New Name: '%s'", *plsName))}log.Trace(r, fmt.Sprintf("-- Adding: '%v'", songsToAdd))log.Trace(r, fmt.Sprintf("-- Removing: '%v'", songIndexesToRemove))err = api.playlists.Update(r.Context(), playlistId, plsName, comment, public, songsToAdd, songIndexesToRemove)if errors.Is(err, model.ErrNotAuthorized) {return nil, newError(responses.ErrorAuthorizationFail)}if err != nil {log.Error(r, "Error updating playlist", "id", playlistId, err)return nil, err}return newResponse(), nil}func (api *Router) buildPlaylist(ctx context.Context, p model.Playlist) responses.Playlist {pls := responses.Playlist{}pls.Id = p.IDpls.Name = p.Namepls.SongCount = int32(p.SongCount)pls.Duration = int32(p.Duration)pls.Created = p.CreatedAtpls.Changed = p.UpdatedAtplayer, ok := request.PlayerFrom(ctx)if ok && isClientInList(conf.Server.Subsonic.MinimalClients, player.Client) {return pls}pls.Comment = p.Commentpls.Owner = p.OwnerNamepls.Public = p.Publicpls.CoverArt = coverArtOrEmpty(p.CoverArtID(), p.ImageAbsent)pls.OpenSubsonicPlaylist = buildOSPlaylist(ctx, p)return pls}func buildOSPlaylist(ctx context.Context, p model.Playlist) *responses.OpenSubsonicPlaylist {player, ok := request.PlayerFrom(ctx)if ok && isClientInList(conf.Server.Subsonic.LegacyClients, player.Client) {return nil}pls := responses.OpenSubsonicPlaylist{}if p.IsSmartPlaylist() {pls.Readonly = trueif p.EvaluatedAt != nil {pls.ValidUntil = new(p.EvaluatedAt.Add(p.RefreshDelay()))}} else {user, ok := request.UserFrom(ctx)pls.Readonly = !ok || p.OwnerID != user.ID || !p.TracksEditable()}return &pls}