Deluan Quintão 3158451b8d
refactor(server): drop redundant error return from req.Strings parsing (#5812)
* refactor(req): drop redundant error return from Strings

The error from Strings carried no information beyond emptiness — it fired
exactly when the param was absent — and nearly every caller discarded it with
a blank identifier. Strings now just returns the values (empty when absent),
making the common optional-list reads one clean expression.

The few required-param callers (scrobble, createShare) check for emptiness and
return the same Subsonic error code 10 as before; their e2e tests now pin that
code. Ints and Times keep their contracts by synthesizing ErrMissingParam
themselves, so selectedMusicFolderIds is untouched. The jellyfin parseFields
helper is inlined away, since ParseFields(p.Strings("fields")...) now
compiles directly.

* docs(req): clarify Strings returns nil when param is absent
2026-07-18 19:30:04 -04:00

137 lines
3.6 KiB
Go

package subsonic
import (
"net/http"
"strconv"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/core/playback"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils/req"
"github.com/navidrome/navidrome/utils/slice"
)
const (
ActionGet = "get"
ActionStatus = "status"
ActionSet = "set"
ActionStart = "start"
ActionStop = "stop"
ActionSkip = "skip"
ActionAdd = "add"
ActionClear = "clear"
ActionRemove = "remove"
ActionShuffle = "shuffle"
ActionSetGain = "setGain"
)
func (api *Router) JukeboxControl(r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
user := getUser(ctx)
p := req.Params(r)
if !conf.Server.Jukebox.Enabled {
return nil, newError(responses.ErrorGeneric, "Jukebox is disabled")
}
if conf.Server.Jukebox.AdminOnly && !user.IsAdmin {
return nil, newError(responses.ErrorAuthorizationFail, "Jukebox is admin only")
}
actionString, err := p.String("action")
if err != nil {
return nil, err
}
pb, err := api.playback.GetDeviceForUser(user.UserName)
if err != nil {
return nil, err
}
log.Info(ctx, "JukeboxControl request received", "action", actionString)
switch actionString {
case ActionGet:
mediafiles, status, err := pb.Get(ctx)
if err != nil {
return nil, err
}
playlist := responses.JukeboxPlaylist{
JukeboxStatus: *deviceStatusToJukeboxStatus(status),
Entry: slice.MapWithArg(mediafiles, ctx, childFromMediaFile),
}
response := newResponse()
response.JukeboxPlaylist = &playlist
return response, nil
case ActionStatus:
return createResponse(pb.Status(ctx))
case ActionSet:
ids := p.Strings("id")
return createResponse(pb.Set(ctx, ids))
case ActionStart:
return createResponse(pb.Start(ctx))
case ActionStop:
return createResponse(pb.Stop(ctx))
case ActionSkip:
index, err := p.Int("index")
if err != nil {
return nil, newError(responses.ErrorMissingParameter, "missing parameter index, err: %s", err)
}
offset := p.IntOr("offset", 0)
return createResponse(pb.Skip(ctx, index, offset))
case ActionAdd:
ids := p.Strings("id")
return createResponse(pb.Add(ctx, ids))
case ActionClear:
return createResponse(pb.Clear(ctx))
case ActionRemove:
index, err := p.Int("index")
if err != nil {
return nil, err
}
return createResponse(pb.Remove(ctx, index))
case ActionShuffle:
return createResponse(pb.Shuffle(ctx))
case ActionSetGain:
gainStr, err := p.String("gain")
if err != nil {
return nil, newError(responses.ErrorMissingParameter, "missing parameter gain, err: %s", err)
}
gain, err := strconv.ParseFloat(gainStr, 32)
if err != nil {
return nil, newError(responses.ErrorMissingParameter, "error parsing gain float value, err: %s", err)
}
return createResponse(pb.SetGain(ctx, float32(gain)))
default:
return nil, newError(responses.ErrorMissingParameter, "Unknown action: %s", actionString)
}
}
// createResponse is to shorten the case-switch in the JukeboxController
func createResponse(status playback.DeviceStatus, err error) (*responses.Subsonic, error) {
if err != nil {
return nil, err
}
return statusResponse(status), nil
}
func statusResponse(status playback.DeviceStatus) *responses.Subsonic {
response := newResponse()
response.JukeboxStatus = deviceStatusToJukeboxStatus(status)
return response
}
func deviceStatusToJukeboxStatus(status playback.DeviceStatus) *responses.JukeboxStatus {
return &responses.JukeboxStatus{
CurrentIndex: int32(status.CurrentIndex),
Playing: status.Playing,
Gain: status.Gain,
Position: int32(status.Position),
}
}