From 932152eb7e8817e0fe388e984a47a30fae8368cf Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 17 Dec 2023 13:14:14 -0500 Subject: [PATCH 01/27] Change required fields in Subsonic Jukebox endpoint See discussion here: https://gitlab.com/ultrasonic/ultrasonic/-/issues/1266#note_1621953651 --- server/subsonic/jukebox.go | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/server/subsonic/jukebox.go b/server/subsonic/jukebox.go index f3d94b592..2a33fa1ea 100644 --- a/server/subsonic/jukebox.go +++ b/server/subsonic/jukebox.go @@ -1,13 +1,13 @@ package subsonic import ( - "fmt" "net/http" "strconv" "github.com/navidrome/navidrome/core/playback" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/server/subsonic/responses" + "github.com/navidrome/navidrome/utils" ) const ( @@ -38,7 +38,7 @@ func (api *Router) JukeboxControl(r *http.Request) (*responses.Subsonic, error) if err != nil { return nil, err } - log.Debug(fmt.Sprintf("processing action: %s", actionString)) + log.Info(ctx, "JukeboxControl request received", "action", actionString) switch actionString { case ActionGet: @@ -58,15 +58,8 @@ func (api *Router) JukeboxControl(r *http.Request) (*responses.Subsonic, error) case ActionStatus: return createResponse(pb.Status(ctx)) case ActionSet: - ids, err := requiredParamStrings(r, "id") - if err != nil { - return nil, newError(responses.ErrorMissingParameter, "missing parameter id, err: %s", err) - } - status, err := pb.Set(ctx, ids) - if err != nil { - return nil, err - } - return statusResponse(status), nil + ids := utils.ParamStrings(r, "id") + return createResponse(pb.Set(ctx, ids)) case ActionStart: return createResponse(pb.Start(ctx)) case ActionStop: @@ -77,18 +70,14 @@ func (api *Router) JukeboxControl(r *http.Request) (*responses.Subsonic, error) return nil, newError(responses.ErrorMissingParameter, "missing parameter index, err: %s", err) } - offset, err := requiredParamInt(r, "offset") + offset := utils.ParamInt(r, "offset", 0) if err != nil { offset = 0 } return createResponse(pb.Skip(ctx, index, offset)) case ActionAdd: - ids, err := requiredParamStrings(r, "id") - if err != nil { - return nil, newError(responses.ErrorMissingParameter, "missing parameter id, err: %s", err) - } - + ids := utils.ParamStrings(r, "id") return createResponse(pb.Add(ctx, ids)) case ActionClear: return createResponse(pb.Clear(ctx)) @@ -109,7 +98,7 @@ func (api *Router) JukeboxControl(r *http.Request) (*responses.Subsonic, error) gain, err := strconv.ParseFloat(gainStr, 32) if err != nil { - return nil, newError(responses.ErrorMissingParameter, "error parsing gain integer value, err: %s", err) + return nil, newError(responses.ErrorMissingParameter, "error parsing gain float value, err: %s", err) } return createResponse(pb.SetGain(ctx, float32(gain))) From 12aae5e9511a9c9cbeb66cfcb825f7898ceea1dd Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 17 Dec 2023 13:14:34 -0500 Subject: [PATCH 02/27] Some cleanup in the jukebox code, specially log messages --- core/playback/device.go | 75 ++++++++++++++++++--------------- core/playback/mpv/track.go | 32 +++++++------- core/playback/playbackserver.go | 26 ++++++------ server/subsonic/album_lists.go | 16 +++---- 4 files changed, 78 insertions(+), 71 deletions(-) diff --git a/core/playback/device.go b/core/playback/device.go index d7c7355f0..2ece8906e 100644 --- a/core/playback/device.go +++ b/core/playback/device.go @@ -2,6 +2,7 @@ package playback import ( "context" + "errors" "fmt" "github.com/navidrome/navidrome/core/playback/mpv" @@ -17,9 +18,10 @@ type Track interface { Position() int SetPosition(offset int) error Close() + String() string } -type PlaybackDevice struct { +type playbackDevice struct { ParentPlaybackServer PlaybackServer Default bool User string @@ -43,7 +45,7 @@ const DefaultGain float32 = 1.0 var EmptyStatus = DeviceStatus{CurrentIndex: -1, Playing: false, Gain: DefaultGain, Position: 0} -func (pd *PlaybackDevice) getStatus() DeviceStatus { +func (pd *playbackDevice) getStatus() DeviceStatus { pos := 0 if pd.ActiveTrack != nil { pos = pd.ActiveTrack.Position() @@ -59,8 +61,8 @@ func (pd *PlaybackDevice) getStatus() DeviceStatus { // NewPlaybackDevice creates a new playback device which implements all the basic Jukebox mode commands defined here: // http://www.subsonic.org/pages/api.jsp#jukeboxControl // Starts the trackSwitcher goroutine for the device. -func NewPlaybackDevice(playbackServer PlaybackServer, name string, deviceName string) *PlaybackDevice { - return &PlaybackDevice{ +func NewPlaybackDevice(playbackServer PlaybackServer, name string, deviceName string) *playbackDevice { + return &playbackDevice{ ParentPlaybackServer: playbackServer, User: "", Name: name, @@ -72,22 +74,24 @@ func NewPlaybackDevice(playbackServer PlaybackServer, name string, deviceName st } } -func (pd *PlaybackDevice) String() string { +func (pd *playbackDevice) String() string { return fmt.Sprintf("Name: %s, Gain: %.4f, Loaded track: %s", pd.Name, pd.Gain, pd.ActiveTrack) } -func (pd *PlaybackDevice) Get(ctx context.Context) (model.MediaFiles, DeviceStatus, error) { - log.Debug(ctx, "processing Get action") +func (pd *playbackDevice) Get(ctx context.Context) (model.MediaFiles, DeviceStatus, error) { + log.Debug(ctx, "Processing Get action", "device", pd) return pd.PlaybackQueue.Get(), pd.getStatus(), nil } -func (pd *PlaybackDevice) Status(ctx context.Context) (DeviceStatus, error) { +func (pd *playbackDevice) Status(ctx context.Context) (DeviceStatus, error) { log.Debug(ctx, fmt.Sprintf("processing Status action on: %s, queue: %s", pd, pd.PlaybackQueue)) return pd.getStatus(), nil } -// set is similar to a clear followed by a add, but will not change the currently playing track. -func (pd *PlaybackDevice) Set(ctx context.Context, ids []string) (DeviceStatus, error) { +// Set is similar to a clear followed by a add, but will not change the currently playing track. +func (pd *playbackDevice) Set(ctx context.Context, ids []string) (DeviceStatus, error) { + log.Debug(ctx, "Processing Set action", "ids", ids, "device", pd) + _, err := pd.Clear(ctx) if err != nil { log.Error(ctx, "error setting tracks", ids) @@ -96,8 +100,8 @@ func (pd *PlaybackDevice) Set(ctx context.Context, ids []string) (DeviceStatus, return pd.Add(ctx, ids) } -func (pd *PlaybackDevice) Start(ctx context.Context) (DeviceStatus, error) { - log.Debug(ctx, "processing Start action") +func (pd *playbackDevice) Start(ctx context.Context) (DeviceStatus, error) { + log.Debug(ctx, "Processing Start action", "device", pd) if !pd.TrackSwitcherStarted { log.Info(ctx, "Starting trackSwitcher goroutine") @@ -127,16 +131,16 @@ func (pd *PlaybackDevice) Start(ctx context.Context) (DeviceStatus, error) { return pd.getStatus(), nil } -func (pd *PlaybackDevice) Stop(ctx context.Context) (DeviceStatus, error) { - log.Debug(ctx, "processing Stop action") +func (pd *playbackDevice) Stop(ctx context.Context) (DeviceStatus, error) { + log.Debug(ctx, "Processing Stop action", "device", pd) if pd.ActiveTrack != nil { pd.ActiveTrack.Pause() } return pd.getStatus(), nil } -func (pd *PlaybackDevice) Skip(ctx context.Context, index int, offset int) (DeviceStatus, error) { - log.Debug(ctx, "processing Skip action", "index", index, "offset", offset) +func (pd *playbackDevice) Skip(ctx context.Context, index int, offset int) (DeviceStatus, error) { + log.Debug(ctx, "Processing Skip action", "index", index, "offset", offset, "device", pd) wasPlaying := pd.isPlaying() @@ -173,8 +177,11 @@ func (pd *PlaybackDevice) Skip(ctx context.Context, index int, offset int) (Devi return pd.getStatus(), nil } -func (pd *PlaybackDevice) Add(ctx context.Context, ids []string) (DeviceStatus, error) { - log.Debug(ctx, "processing Add action") +func (pd *playbackDevice) Add(ctx context.Context, ids []string) (DeviceStatus, error) { + log.Debug(ctx, "Processing Add action", "ids", ids, "device", pd) + if len(ids) < 1 { + return pd.getStatus(), nil + } items := model.MediaFiles{} @@ -191,8 +198,8 @@ func (pd *PlaybackDevice) Add(ctx context.Context, ids []string) (DeviceStatus, return pd.getStatus(), nil } -func (pd *PlaybackDevice) Clear(ctx context.Context) (DeviceStatus, error) { - log.Debug(ctx, fmt.Sprintf("processing Clear action on: %s", pd)) +func (pd *playbackDevice) Clear(ctx context.Context) (DeviceStatus, error) { + log.Debug(ctx, "Processing Clear action", "device", pd) if pd.ActiveTrack != nil { pd.ActiveTrack.Pause() pd.ActiveTrack.Close() @@ -202,8 +209,8 @@ func (pd *PlaybackDevice) Clear(ctx context.Context) (DeviceStatus, error) { return pd.getStatus(), nil } -func (pd *PlaybackDevice) Remove(ctx context.Context, index int) (DeviceStatus, error) { - log.Debug(ctx, "processing Remove action") +func (pd *playbackDevice) Remove(ctx context.Context, index int) (DeviceStatus, error) { + log.Debug(ctx, "Processing Remove action", "index", index, "device", pd) // pausing if attempting to remove running track if pd.isPlaying() && pd.PlaybackQueue.Index == index { _, err := pd.Stop(ctx) @@ -221,17 +228,17 @@ func (pd *PlaybackDevice) Remove(ctx context.Context, index int) (DeviceStatus, return pd.getStatus(), nil } -func (pd *PlaybackDevice) Shuffle(ctx context.Context) (DeviceStatus, error) { - log.Debug(ctx, "processing Shuffle action") +func (pd *playbackDevice) Shuffle(ctx context.Context) (DeviceStatus, error) { + log.Debug(ctx, "Processing Shuffle action", "device", pd) if pd.PlaybackQueue.Size() > 1 { pd.PlaybackQueue.Shuffle() } return pd.getStatus(), nil } -// Used to control the playback volume. A float value between 0.0 and 1.0. -func (pd *PlaybackDevice) SetGain(ctx context.Context, gain float32) (DeviceStatus, error) { - log.Debug(ctx, fmt.Sprintf("processing SetGain action. Actual gain: %f, gain to set: %f", pd.Gain, gain)) +// SetGain is used to control the playback volume. A float value between 0.0 and 1.0. +func (pd *playbackDevice) SetGain(ctx context.Context, gain float32) (DeviceStatus, error) { + log.Debug(ctx, "Processing SetGain action", "newGain", gain, "device", pd) if pd.ActiveTrack != nil { pd.ActiveTrack.SetVolume(gain) @@ -241,15 +248,15 @@ func (pd *PlaybackDevice) SetGain(ctx context.Context, gain float32) (DeviceStat return pd.getStatus(), nil } -func (pd *PlaybackDevice) isPlaying() bool { +func (pd *playbackDevice) isPlaying() bool { return pd.ActiveTrack != nil && pd.ActiveTrack.IsPlaying() } -func (pd *PlaybackDevice) trackSwitcherGoroutine() { - log.Info("Starting trackSwitcher goroutine") +func (pd *playbackDevice) trackSwitcherGoroutine() { + log.Debug("Started trackSwitcher goroutine", "device", pd) for { <-pd.PlaybackDone - log.Info("track switching detected") + log.Debug("Track switching detected") if pd.ActiveTrack != nil { pd.ActiveTrack.Close() pd.ActiveTrack = nil @@ -260,7 +267,7 @@ func (pd *PlaybackDevice) trackSwitcherGoroutine() { log.Debug("Switching to next song", "queue", pd.PlaybackQueue.String()) err := pd.switchActiveTrackByIndex(pd.PlaybackQueue.Index) if err != nil { - log.Error("error switching track", "error", err) + log.Error("Error switching track", err) } pd.ActiveTrack.Unpause() } else { @@ -269,11 +276,11 @@ func (pd *PlaybackDevice) trackSwitcherGoroutine() { } } -func (pd *PlaybackDevice) switchActiveTrackByIndex(index int) error { +func (pd *playbackDevice) switchActiveTrackByIndex(index int) error { pd.PlaybackQueue.SetIndex(index) currentTrack := pd.PlaybackQueue.Current() if currentTrack == nil { - return fmt.Errorf("could not get current track") + return errors.New("could not get current track") } track, err := mpv.NewTrack(pd.PlaybackDone, pd.DeviceName, *currentTrack) diff --git a/core/playback/mpv/track.go b/core/playback/mpv/track.go index 1008debc6..d574508cc 100644 --- a/core/playback/mpv/track.go +++ b/core/playback/mpv/track.go @@ -25,7 +25,7 @@ type MpvTrack struct { } func NewTrack(playbackDoneChannel chan bool, deviceName string, mf model.MediaFile) (*MpvTrack, error) { - log.Debug("loading track", "trackname", mf.Path, "mediatype", mf.ContentType()) + log.Debug("Loading track", "trackPath", mf.Path, "mediaType", mf.ContentType()) if _, err := mpvCommand(); err != nil { return nil, err @@ -36,14 +36,14 @@ func NewTrack(playbackDoneChannel chan bool, deviceName string, mf model.MediaFi args := createMPVCommand(mpvComdTemplate, deviceName, mf.Path, tmpSocketName) exe, err := start(args) if err != nil { - log.Error("error starting mpv process", "error", err) + log.Error("Error starting mpv process", err) return nil, err } // wait for socket to show up - err = waitForFile(tmpSocketName, 3*time.Second, 100*time.Millisecond) + err = waitForSocket(tmpSocketName, 3*time.Second, 100*time.Millisecond) if err != nil { - log.Error("error or timeout waiting for control socket", "socketname", tmpSocketName, "error", err) + log.Error("Error or timeout waiting for control socket", "socketname", tmpSocketName, err) return nil, err } @@ -51,7 +51,7 @@ func NewTrack(playbackDoneChannel chan bool, deviceName string, mf model.MediaFi err = conn.Open() if err != nil { - log.Error("error opening new connection", "error", err) + log.Error("Error opening new connection", err) return nil, err } @@ -92,7 +92,7 @@ func (t *MpvTrack) Unpause() { if err != nil { log.Error(err) } - log.Info("unpaused track") + log.Debug("Unpaused track", "track", t) } func (t *MpvTrack) Pause() { @@ -111,7 +111,7 @@ func (t *MpvTrack) Close() { log.Debug("sending shutdown command") _, err := t.Conn.Call("quit") if err != nil { - log.Error("error sending quit command to mpv-ipc socket", "error", err) + log.Error("Error sending quit command to mpv-ipc socket", err) if t.Exe != nil { log.Debug("cancelling executor") @@ -141,16 +141,16 @@ func (t *MpvTrack) isSocketfilePresent() bool { return err == nil && fileInfo != nil && !fileInfo.IsDir() } -// Position returns the playback position in seconds -// every now and then the mpv IPC interface returns "mpv error: property unavailable" +// Position returns the playback position in seconds. +// Every now and then the mpv IPC interface returns "mpv error: property unavailable" // in this case we have to retry func (t *MpvTrack) Position() int { retryCount := 0 for { position, err := t.Conn.Get("time-pos") if err != nil && err.Error() == "mpv error: property unavailable" { - log.Debug("got the mpv error: property unavailable error, retry ...") retryCount += 1 + log.Debug("Got mpv error, retrying...", "retries", retryCount, err) if retryCount > 5 { return 0 } @@ -158,13 +158,13 @@ func (t *MpvTrack) Position() int { } if err != nil { - log.Error("error getting position in track", "error", err) + log.Error("Error getting position in track", err) return 0 } pos, ok := position.(float64) if !ok { - log.Error("could not cast position from mpv into float64") + log.Error("Could not cast position from mpv into float64", "position", position) return 0 } else { return int(pos) @@ -181,7 +181,7 @@ func (t *MpvTrack) SetPosition(offset int) error { } err := t.Conn.Set("time-pos", float64(offset)) if err != nil { - log.Error("could not set the position in track", "offset", offset, "error", err) + log.Error("could not set the position in track", "offset", offset, err) return err } log.Info("set position", "offset", offset) @@ -191,7 +191,7 @@ func (t *MpvTrack) SetPosition(offset int) error { func (t *MpvTrack) IsPlaying() bool { pausing, err := t.Conn.Get("pause") if err != nil { - log.Error("problem getting paused status", "error", err) + log.Error("problem getting paused status", err) return false } @@ -203,7 +203,7 @@ func (t *MpvTrack) IsPlaying() bool { return !pause } -func waitForFile(path string, timeout time.Duration, pause time.Duration) error { +func waitForSocket(path string, timeout time.Duration, pause time.Duration) error { start := time.Now() end := start.Add(timeout) var retries int = 0 @@ -211,7 +211,7 @@ func waitForFile(path string, timeout time.Duration, pause time.Duration) error for { fileInfo, err := os.Stat(path) if err == nil && fileInfo != nil && !fileInfo.IsDir() { - log.Debug("file found", "retries", retries, "waittime", time.Since(start).Microseconds()) + log.Debug("Socket found", "retries", retries, "waitTime", time.Since(start)) return nil } if time.Now().After(end) { diff --git a/core/playback/playbackserver.go b/core/playback/playbackserver.go index 4281700a3..c0b98ae72 100644 --- a/core/playback/playbackserver.go +++ b/core/playback/playbackserver.go @@ -19,7 +19,7 @@ import ( type PlaybackServer interface { Run(ctx context.Context) error - GetDeviceForUser(user string) (*PlaybackDevice, error) + GetDeviceForUser(user string) (*playbackDevice, error) GetMediaFile(id string) (*model.MediaFile, error) GetCtx() *context.Context } @@ -27,7 +27,7 @@ type PlaybackServer interface { type playbackServer struct { ctx *context.Context datastore model.DataStore - playbackDevices []PlaybackDevice + playbackDevices []playbackDevice } // GetInstance returns the playback-server singleton @@ -63,8 +63,8 @@ func (ps *playbackServer) GetCtx() *context.Context { return ps.ctx } -func (ps *playbackServer) initDeviceStatus(devices []conf.AudioDeviceDefinition, defaultDevice string) ([]PlaybackDevice, error) { - pbDevices := make([]PlaybackDevice, max(1, len(devices))) +func (ps *playbackServer) initDeviceStatus(devices []conf.AudioDeviceDefinition, defaultDevice string) ([]playbackDevice, error) { + pbDevices := make([]playbackDevice, max(1, len(devices))) defaultDeviceFound := false if defaultDevice == "" { @@ -76,13 +76,13 @@ func (ps *playbackServer) initDeviceStatus(devices []conf.AudioDeviceDefinition, // if there is but only one entry and no default given, just use that. if len(devices) == 1 { if len(devices[0]) != 2 { - return []PlaybackDevice{}, fmt.Errorf("audio device definition ought to contain 2 fields, found: %d ", len(devices[0])) + return []playbackDevice{}, fmt.Errorf("audio device definition ought to contain 2 fields, found: %d ", len(devices[0])) } pbDevices[0] = *NewPlaybackDevice(ps, devices[0][0], devices[0][1]) } if len(devices) > 1 { - return []PlaybackDevice{}, fmt.Errorf("number of audio device found is %d, but no default device defined. Set Jukebox.Default", len(devices)) + return []playbackDevice{}, fmt.Errorf("number of audio device found is %d, but no default device defined. Set Jukebox.Default", len(devices)) } pbDevices[0].Default = true @@ -91,7 +91,7 @@ func (ps *playbackServer) initDeviceStatus(devices []conf.AudioDeviceDefinition, for idx, audioDevice := range devices { if len(audioDevice) != 2 { - return []PlaybackDevice{}, fmt.Errorf("audio device definition ought to contain 2 fields, found: %d ", len(audioDevice)) + return []playbackDevice{}, fmt.Errorf("audio device definition ought to contain 2 fields, found: %d ", len(audioDevice)) } pbDevices[idx] = *NewPlaybackDevice(ps, audioDevice[0], audioDevice[1]) @@ -103,18 +103,18 @@ func (ps *playbackServer) initDeviceStatus(devices []conf.AudioDeviceDefinition, } if !defaultDeviceFound { - return []PlaybackDevice{}, fmt.Errorf("default device name not found: %s ", defaultDevice) + return []playbackDevice{}, fmt.Errorf("default device name not found: %s ", defaultDevice) } return pbDevices, nil } -func (ps *playbackServer) getDefaultDevice() (*PlaybackDevice, error) { +func (ps *playbackServer) getDefaultDevice() (*playbackDevice, error) { for idx, audioDevice := range ps.playbackDevices { if audioDevice.Default { return &ps.playbackDevices[idx], nil } } - return &PlaybackDevice{}, fmt.Errorf("no default device found") + return &playbackDevice{}, fmt.Errorf("no default device found") } // GetMediaFile retrieves the MediaFile given by the id parameter @@ -123,12 +123,12 @@ func (ps *playbackServer) GetMediaFile(id string) (*model.MediaFile, error) { } // GetDeviceForUser returns the audio playback device for the given user. As of now this is but only the default device. -func (ps *playbackServer) GetDeviceForUser(user string) (*PlaybackDevice, error) { - log.Debug("processing GetDevice") +func (ps *playbackServer) GetDeviceForUser(user string) (*playbackDevice, error) { + log.Debug("Processing GetDevice", "user", user) // README: here we might plug-in the user-device mapping one fine day device, err := ps.getDefaultDevice() if err != nil { - return &PlaybackDevice{}, err + return &playbackDevice{}, err } device.User = user return device, nil diff --git a/server/subsonic/album_lists.go b/server/subsonic/album_lists.go index 7f1ecea6b..6fdabff1f 100644 --- a/server/subsonic/album_lists.go +++ b/server/subsonic/album_lists.go @@ -64,13 +64,13 @@ func (api *Router) getAlbumList(r *http.Request) (model.Albums, int64, error) { albums, err := api.ds.Album(r.Context()).GetAllWithoutGenres(opts) if err != nil { - log.Error(r, "Error retrieving albums", "error", err) + log.Error(r, "Error retrieving albums", err) return nil, 0, newError(responses.ErrorGeneric, "internal error") } count, err := api.ds.Album(r.Context()).CountAll(opts) if err != nil { - log.Error(r, "Error counting albums", "error", err) + log.Error(r, "Error counting albums", err) return nil, 0, newError(responses.ErrorGeneric, "internal error") } @@ -108,17 +108,17 @@ func (api *Router) GetStarred(r *http.Request) (*responses.Subsonic, error) { options := filter.Starred() artists, err := api.ds.Artist(ctx).GetAll(options) if err != nil { - log.Error(r, "Error retrieving starred artists", "error", err) + log.Error(r, "Error retrieving starred artists", err) return nil, err } albums, err := api.ds.Album(ctx).GetAllWithoutGenres(options) if err != nil { - log.Error(r, "Error retrieving starred albums", "error", err) + log.Error(r, "Error retrieving starred albums", err) return nil, err } mediaFiles, err := api.ds.MediaFile(ctx).GetAll(options) if err != nil { - log.Error(r, "Error retrieving starred mediaFiles", "error", err) + log.Error(r, "Error retrieving starred mediaFiles", err) return nil, err } @@ -145,7 +145,7 @@ func (api *Router) GetNowPlaying(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() npInfo, err := api.scrobbler.GetNowPlaying(ctx) if err != nil { - log.Error(r, "Error retrieving now playing list", "error", err) + log.Error(r, "Error retrieving now playing list", err) return nil, err } @@ -170,7 +170,7 @@ func (api *Router) GetRandomSongs(r *http.Request) (*responses.Subsonic, error) songs, err := api.getSongs(r.Context(), 0, size, filter.SongsByRandom(genre, fromYear, toYear)) if err != nil { - log.Error(r, "Error retrieving random songs", "error", err) + log.Error(r, "Error retrieving random songs", err) return nil, err } @@ -187,7 +187,7 @@ func (api *Router) GetSongsByGenre(r *http.Request) (*responses.Subsonic, error) songs, err := api.getSongs(r.Context(), offset, count, filter.SongsByGenre(genre)) if err != nil { - log.Error(r, "Error retrieving random songs", "error", err) + log.Error(r, "Error retrieving random songs", err) return nil, err } From 421ce91a9efacc6d845f7e6715bb4093cdef221d Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 17 Dec 2023 13:57:15 -0500 Subject: [PATCH 03/27] Fix mpvipc dependency --- core/playback/mpv/track.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/playback/mpv/track.go b/core/playback/mpv/track.go index d574508cc..b94f907d7 100644 --- a/core/playback/mpv/track.go +++ b/core/playback/mpv/track.go @@ -10,7 +10,7 @@ import ( "os" "time" - "github.com/DexterLB/mpvipc" + "github.com/dexterlb/mpvipc" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" ) diff --git a/go.mod b/go.mod index 1a56daab8..5f5d182e1 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/navidrome/navidrome go 1.21 require ( - github.com/DexterLB/mpvipc v0.0.0-20230829142118-145d6eabdc37 github.com/Masterminds/squirrel v1.5.4 github.com/ReneKroon/ttlcache/v2 v2.11.0 github.com/bradleyjkemp/cupaloy/v2 v2.8.0 github.com/deluan/rest v0.0.0-20211102003136-6260bc399cbf github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 + github.com/dexterlb/mpvipc v0.0.0-20230829142118-145d6eabdc37 github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 github.com/disintegration/imaging v1.6.2 github.com/djherbis/atime v1.1.0 diff --git a/go.sum b/go.sum index d396d3aa1..4b7d743b4 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DexterLB/mpvipc v0.0.0-20230829142118-145d6eabdc37 h1:/oQBAuySCcme0DLhicWkr7FaAT5nh1XbbbnCMR2WdPA= -github.com/DexterLB/mpvipc v0.0.0-20230829142118-145d6eabdc37/go.mod h1:nMVB54ifXmC1hpgfq7gTpotbv891pd2wAX/whuUj1q4= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM= @@ -74,6 +72,8 @@ github.com/deluan/rest v0.0.0-20211102003136-6260bc399cbf h1:tb246l2Zmpt/GpF9EcH github.com/deluan/rest v0.0.0-20211102003136-6260bc399cbf/go.mod h1:tSgDythFsl0QgS/PFWfIZqcJKnkADWneY80jaVRlqK8= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E= +github.com/dexterlb/mpvipc v0.0.0-20230829142118-145d6eabdc37 h1:s+qNFsO3VsdsKroqapcogQxcQBHrRPDK1nVxGc+HBbg= +github.com/dexterlb/mpvipc v0.0.0-20230829142118-145d6eabdc37/go.mod h1:CXCwawNJCtFDip7gvbaQVgw0cGjldpyHDIp7oA5prOg= github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 h1:simG0vMYFvNriGhaaat7QVVkaVkXzvqcohaBoLZl9Hg= github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25/go.mod h1:Z3Lomva4pyMWYezjMAU5QWRh0p1VvO4199OHlFnyKkM= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= From 39e92a1918eb5c3f2a181febc003d7161e414936 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:17:57 +0000 Subject: [PATCH 04/27] Bump github.com/mattn/go-sqlite3 from 1.14.18 to 1.14.19 Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.18 to 1.14.19. - [Release notes](https://github.com/mattn/go-sqlite3/releases) - [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.18...v1.14.19) --- updated-dependencies: - dependency-name: github.com/mattn/go-sqlite3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5f5d182e1..2203fe0e4 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/kr/pretty v0.3.1 github.com/lestrrat-go/jwx/v2 v2.0.18 github.com/matoous/go-nanoid/v2 v2.0.0 - github.com/mattn/go-sqlite3 v1.14.18 + github.com/mattn/go-sqlite3 v1.14.19 github.com/mattn/go-zglob v0.0.4 github.com/microcosm-cc/bluemonday v1.0.26 github.com/mileusna/useragent v1.3.4 diff --git a/go.sum b/go.sum index 4b7d743b4..01369be22 100644 --- a/go.sum +++ b/go.sum @@ -266,8 +266,8 @@ github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjt github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= -github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM= github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= From 03a9f22ed9b8eb87af3e99ee531ddf965a434a78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:09:41 +0000 Subject: [PATCH 05/27] Bump @material-ui/icons from 4.11.2 to 4.11.3 in /ui Bumps [@material-ui/icons](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui-icons) from 4.11.2 to 4.11.3. - [Release notes](https://github.com/mui-org/material-ui/releases) - [Changelog](https://github.com/mui/material-ui/blob/v4.11.3/CHANGELOG.md) - [Commits](https://github.com/mui-org/material-ui/commits/v4.11.3/packages/material-ui-icons) --- updated-dependencies: - dependency-name: "@material-ui/icons" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ui/package-lock.json | 25 ++++++++++++++++++------- ui/package.json | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 06ac9d72a..ec0167faf 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@material-ui/core": "^4.11.4", - "@material-ui/icons": "^4.11.2", + "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.58", "@material-ui/styles": "^4.11.5", "blueimp-md5": "^2.19.0", @@ -3304,14 +3304,25 @@ } }, "node_modules/@material-ui/icons": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", - "integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", + "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==", "dependencies": { "@babel/runtime": "^7.4.4" }, "engines": { "node": ">=8.0.0" + }, + "peerDependencies": { + "@material-ui/core": "^4.0.0", + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@material-ui/lab": { @@ -23177,9 +23188,9 @@ } }, "@material-ui/icons": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", - "integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", + "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==", "requires": { "@babel/runtime": "^7.4.4" } diff --git a/ui/package.json b/ui/package.json index ae5f8dbca..286c267eb 100644 --- a/ui/package.json +++ b/ui/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@material-ui/core": "^4.11.4", - "@material-ui/icons": "^4.11.2", + "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.58", "@material-ui/styles": "^4.11.5", "blueimp-md5": "^2.19.0", From 30179146c389ac9b475a4427b26ae9046a8081f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:09:24 +0000 Subject: [PATCH 06/27] Bump deepmerge from 4.2.2 to 4.3.1 in /ui Bumps [deepmerge](https://github.com/TehShrike/deepmerge) from 4.2.2 to 4.3.1. - [Changelog](https://github.com/TehShrike/deepmerge/blob/master/changelog.md) - [Commits](https://github.com/TehShrike/deepmerge/compare/v4.2.2...v4.3.1) --- updated-dependencies: - dependency-name: deepmerge dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- ui/package-lock.json | 14 +++++++------- ui/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index ec0167faf..8a609da71 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -15,7 +15,7 @@ "blueimp-md5": "^2.19.0", "clsx": "^1.1.1", "connected-react-router": "^6.9.1", - "deepmerge": "^4.2.2", + "deepmerge": "^4.3.1", "history": "^4.10.1", "inflection": "^1.13.1", "jwt-decode": "^3.1.2", @@ -7247,9 +7247,9 @@ "dev": true }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "engines": { "node": ">=0.10.0" } @@ -26262,9 +26262,9 @@ "dev": true }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, "default-gateway": { "version": "6.0.3", diff --git a/ui/package.json b/ui/package.json index 286c267eb..2656e454f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,7 +10,7 @@ "blueimp-md5": "^2.19.0", "clsx": "^1.1.1", "connected-react-router": "^6.9.1", - "deepmerge": "^4.2.2", + "deepmerge": "^4.3.1", "history": "^4.10.1", "inflection": "^1.13.1", "jwt-decode": "^3.1.2", From 735d670a5bc792e4a42894ff6f54d36c24d8830e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:10:07 +0000 Subject: [PATCH 07/27] Bump prettier from 2.8.2 to 3.1.1 in /ui Bumps [prettier](https://github.com/prettier/prettier) from 2.8.2 to 3.1.1. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.8.2...3.1.1) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- ui/package-lock.json | 18 +++++++++--------- ui/package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 8a609da71..61f58658a 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -48,7 +48,7 @@ "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", "css-mediaquery": "^0.1.2", - "prettier": "2.8.2", + "prettier": "3.1.1", "ra-test": "^3.19.12", "react-scripts": "5.0.1", "workbox-cli": "^6.3.0" @@ -16079,15 +16079,15 @@ } }, "node_modules/prettier": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", - "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -32880,9 +32880,9 @@ "dev": true }, "prettier": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", - "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true }, "pretty-bytes": { diff --git a/ui/package.json b/ui/package.json index 2656e454f..d3e450a73 100644 --- a/ui/package.json +++ b/ui/package.json @@ -43,7 +43,7 @@ "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", "css-mediaquery": "^0.1.2", - "prettier": "2.8.2", + "prettier": "3.1.1", "ra-test": "^3.19.12", "react-scripts": "5.0.1", "workbox-cli": "^6.3.0" From 86757663d666d08093d4d3e2a6195238addaaec0 Mon Sep 17 00:00:00 2001 From: Caio Cotts Date: Mon, 18 Dec 2023 14:56:03 -0500 Subject: [PATCH 08/27] Reformat code with Prettier's new rules. --- ui/src/album/AlbumDetails.js | 16 +++---- ui/src/album/AlbumExternalLinks.js | 4 +- ui/src/album/AlbumGridView.js | 54 +++++++++++----------- ui/src/album/AlbumList.js | 2 +- ui/src/album/AlbumListActions.js | 2 +- ui/src/album/AlbumShow.js | 2 +- ui/src/album/AlbumSongs.js | 2 +- ui/src/album/AlbumTableView.js | 2 +- ui/src/artist/ArtistExternalLink.js | 8 ++-- ui/src/artist/ArtistList.js | 4 +- ui/src/artist/DesktopArtistDetails.js | 4 +- ui/src/artist/MobileArtistDetails.js | 4 +- ui/src/audioplayer/Player.js | 28 +++++------ ui/src/audioplayer/keyHandlers.js | 4 +- ui/src/audioplayer/styles.js | 2 +- ui/src/common/AddToPlaylistButton.js | 5 +- ui/src/common/ArtistLinkField.js | 45 +++++++++--------- ui/src/common/ArtistSimpleList.js | 4 +- ui/src/common/BatchPlayButton.js | 2 +- ui/src/common/BatchShareButton.js | 4 +- ui/src/common/ContextMenus.js | 8 ++-- ui/src/common/Linkify.js | 6 +-- ui/src/common/LoveButton.js | 2 +- ui/src/common/MultiLineTextField.js | 4 +- ui/src/common/MultiLineTextField.test.js | 4 +- ui/src/common/PlayButton.js | 2 +- ui/src/common/QualityInfo.js | 2 +- ui/src/common/QuickFilter.test.js | 2 +- ui/src/common/RatingField.js | 4 +- ui/src/common/SimpleList.js | 2 +- ui/src/common/SongContextMenu.js | 10 ++-- ui/src/common/SongDatagrid.js | 18 ++++---- ui/src/common/SongSimpleList.js | 4 +- ui/src/common/ToggleFieldsMenu.js | 6 +-- ui/src/common/Writable.js | 2 +- ui/src/common/useAlbumsPerPage.js | 2 +- ui/src/common/useResourceRefresh.js | 2 +- ui/src/common/useSelectedFields.js | 6 +-- ui/src/consts.js | 2 +- ui/src/dialogs/AboutDialog.test.js | 4 +- ui/src/dialogs/AddToPlaylistDialog.js | 4 +- ui/src/dialogs/AddToPlaylistDialog.test.js | 8 ++-- ui/src/dialogs/DownloadMenuDialog.js | 2 +- ui/src/dialogs/HelpDialog.js | 2 +- ui/src/dialogs/ListenBrainzTokenDialog.js | 4 +- ui/src/dialogs/SelectPlaylistInput.js | 2 +- ui/src/dialogs/SelectPlaylistInput.test.js | 2 +- ui/src/dialogs/ShareDialog.js | 6 +-- ui/src/dialogs/useTranscodingOptions.js | 6 +-- ui/src/eventStream.js | 2 +- ui/src/i18n/useGetLanguageChoices.js | 2 +- ui/src/layout/AppBar.js | 4 +- ui/src/layout/DynamicMenuIcon.test.js | 6 +-- ui/src/layout/Login.js | 18 ++++---- ui/src/layout/Menu.js | 2 +- ui/src/layout/PlaylistsSubMenu.js | 2 +- ui/src/layout/SubMenu.js | 2 +- ui/src/layout/UserMenu.js | 2 +- ui/src/personal/LastfmScrobbleToggle.js | 4 +- ui/src/personal/SelectTheme.js | 2 +- ui/src/playlist/PlaylistActions.js | 6 +-- ui/src/playlist/PlaylistDetails.js | 2 +- ui/src/playlist/PlaylistList.js | 4 +- ui/src/playlist/PlaylistShow.js | 2 +- ui/src/playlist/PlaylistSongs.js | 8 ++-- ui/src/radio/StreamField.js | 2 +- ui/src/reducers/albumView.js | 2 +- ui/src/reducers/dialogReducer.js | 10 ++-- ui/src/reducers/playerReducer.js | 4 +- ui/src/reducers/replayGainReducer.js | 2 +- ui/src/reducers/themeReducer.js | 4 +- ui/src/serviceWorker.js | 10 ++-- ui/src/share/ShareList.js | 2 +- ui/src/store/createAdminStore.js | 6 ++- ui/src/subsonic/index.js | 4 +- ui/src/themes/useCurrentTheme.js | 2 +- ui/src/user/DeleteUserButton.js | 2 +- ui/src/user/UserCreate.js | 4 +- ui/src/user/UserEdit.js | 4 +- ui/src/utils/formatters.test.js | 2 +- ui/src/utils/intersperse.js | 2 +- ui/src/utils/urls.js | 2 +- 82 files changed, 236 insertions(+), 222 deletions(-) diff --git a/ui/src/album/AlbumDetails.js b/ui/src/album/AlbumDetails.js index 5e2ee9b65..e5fcb5eb7 100644 --- a/ui/src/album/AlbumDetails.js +++ b/ui/src/album/AlbumDetails.js @@ -113,7 +113,7 @@ const useStyles = makeStyles( }), { name: 'NDAlbumDetails', - } + }, ) const AlbumComment = ({ record }) => { @@ -141,7 +141,7 @@ const AlbumComment = ({ record }) => { timeout={'auto'} className={clsx( classes.commentBlock, - lines.length > 1 && classes.pointerCursor + lines.length > 1 && classes.pointerCursor, )} > @@ -216,9 +216,9 @@ const Details = (props) => { addDetail( <> {[translate('resources.album.fields.originalDate'), originalDate].join( - ' ' + ' ', )} - + , ) yearRange && addDetail(<>{['♫', !isXsmall ? date : yearRange].join(' ')}) @@ -230,7 +230,7 @@ const Details = (props) => { ? [translate('resources.album.fields.releaseDate'), releaseDate] : ['○', record.releaseDate.substring(0, 4)] ).join(' ')} - + , ) const showReleases = record.releases > 1 @@ -245,7 +245,7 @@ const Details = (props) => { }), ].join(' ') : ['(', record.releases, ')))'].join(' ')} - + , ) addDetail( @@ -255,7 +255,7 @@ const Details = (props) => { translate('resources.song.name', { smart_count: record.songCount, })} - + , ) !isXsmall && addDetail() !isXsmall && addDetail() @@ -299,7 +299,7 @@ const AlbumDetails = (props) => { const handleOpenLightbox = React.useCallback(() => setLightboxOpen(true), []) const handleCloseLightbox = React.useCallback( () => setLightboxOpen(false), - [] + [], ) return ( diff --git a/ui/src/album/AlbumExternalLinks.js b/ui/src/album/AlbumExternalLinks.js index caf4c82ea..4b956b496 100644 --- a/ui/src/album/AlbumExternalLinks.js +++ b/ui/src/album/AlbumExternalLinks.js @@ -35,7 +35,7 @@ const AlbumExternalLinks = (props) => { encodeURIComponent(record.name) }`, 'message.openIn.lastfm', - + , ) } @@ -43,7 +43,7 @@ const AlbumExternalLinks = (props) => { addLink( `https://musicbrainz.org/release/${record.mbzAlbumId}`, 'message.openIn.musicbrainz', - + , ) return
{intersperse(links, ' ')}
diff --git a/ui/src/album/AlbumGridView.js b/ui/src/album/AlbumGridView.js index 2c01e8517..765722254 100644 --- a/ui/src/album/AlbumGridView.js +++ b/ui/src/album/AlbumGridView.js @@ -78,7 +78,7 @@ const useStyles = makeStyles( albumContainer: {}, albumPlayButton: { color: 'white' }, }), - { name: 'NDAlbumGridView' } + { name: 'NDAlbumGridView' }, ) const useCoverStyles = makeStyles({ @@ -98,32 +98,34 @@ const getColsForWidth = (width) => { return 9 } -const Cover = withContentRect('bounds')( - ({ record, measureRef, contentRect }) => { - // Force height to be the same as the width determined by the GridList - // noinspection JSSuspiciousNameCombination - const classes = useCoverStyles({ height: contentRect.bounds.width }) - const [, dragAlbumRef] = useDrag( - () => ({ - type: DraggableTypes.ALBUM, - item: { albumIds: [record.id] }, - options: { dropEffect: 'copy' }, - }), - [record] - ) - return ( -
-
- {record.name} -
+const Cover = withContentRect('bounds')(({ + record, + measureRef, + contentRect, +}) => { + // Force height to be the same as the width determined by the GridList + // noinspection JSSuspiciousNameCombination + const classes = useCoverStyles({ height: contentRect.bounds.width }) + const [, dragAlbumRef] = useDrag( + () => ({ + type: DraggableTypes.ALBUM, + item: { albumIds: [record.id] }, + options: { dropEffect: 'copy' }, + }), + [record], + ) + return ( +
+
+ {record.name}
- ) - } -) +
+ ) +}) const AlbumGridTile = ({ showArtist, record, basePath, ...props }) => { const classes = useStyles() diff --git a/ui/src/album/AlbumList.js b/ui/src/album/AlbumList.js index 10ba7e100..4bae75e47 100644 --- a/ui/src/album/AlbumList.js +++ b/ui/src/album/AlbumList.js @@ -104,7 +104,7 @@ const AlbumList = (props) => { 'size', 'createdAt', ], - ['createdAt', 'size'] + ['createdAt', 'size'], ) // If it does not have filter/sort params (usually coming from Menu), diff --git a/ui/src/album/AlbumListActions.js b/ui/src/album/AlbumListActions.js index 45825dd5d..102dd6072 100644 --- a/ui/src/album/AlbumListActions.js +++ b/ui/src/album/AlbumListActions.js @@ -64,7 +64,7 @@ const AlbumViewToggler = React.forwardRef(
) - } + }, ) const AlbumListActions = ({ diff --git a/ui/src/album/AlbumShow.js b/ui/src/album/AlbumShow.js index 92a0833cd..1b706295a 100644 --- a/ui/src/album/AlbumShow.js +++ b/ui/src/album/AlbumShow.js @@ -18,7 +18,7 @@ const useStyles = makeStyles( }), { name: 'NDAlbumShow', - } + }, ) const AlbumShowLayout = (props) => { diff --git a/ui/src/album/AlbumSongs.js b/ui/src/album/AlbumSongs.js index ef81bc02c..3ad0eb318 100644 --- a/ui/src/album/AlbumSongs.js +++ b/ui/src/album/AlbumSongs.js @@ -83,7 +83,7 @@ const useStyles = makeStyles( visibility: 'hidden', }, }), - { name: 'RaList' } + { name: 'RaList' }, ) const AlbumSongs = (props) => { diff --git a/ui/src/album/AlbumTableView.js b/ui/src/album/AlbumTableView.js index b11d55f0e..be8e49725 100644 --- a/ui/src/album/AlbumTableView.js +++ b/ui/src/album/AlbumTableView.js @@ -59,7 +59,7 @@ const AlbumDatagridRow = (props) => { item: { albumIds: [record?.id] }, options: { dropEffect: 'copy' }, }), - [record] + [record], ) return } diff --git a/ui/src/artist/ArtistExternalLink.js b/ui/src/artist/ArtistExternalLink.js index c25f19ffd..9efa3925b 100644 --- a/ui/src/artist/ArtistExternalLink.js +++ b/ui/src/artist/ArtistExternalLink.js @@ -11,7 +11,7 @@ const ArtistExternalLinks = ({ artistInfo, record }) => { const translate = useTranslate() let linkButtons = [] const lastFMlink = artistInfo?.biography?.match( - /]*?\s+)?href=(["'])(.*?)\1/ + /]*?\s+)?href=(["'])(.*?)\1/, ) const addLink = (url, title, icon) => { @@ -34,13 +34,13 @@ const ArtistExternalLinks = ({ artistInfo, record }) => { addLink( lastFMlink[2], 'message.openIn.lastfm', - + , ) } else if (artistInfo?.lastFmUrl) { addLink( artistInfo?.lastFmUrl, 'message.openIn.lastfm', - + , ) } } @@ -49,7 +49,7 @@ const ArtistExternalLinks = ({ artistInfo, record }) => { addLink( `https://musicbrainz.org/artist/${artistInfo.musicBrainzId}`, 'message.openIn.musicbrainz', - + , ) return
{intersperse(linkButtons, ' ')}
diff --git a/ui/src/artist/ArtistList.js b/ui/src/artist/ArtistList.js index dbc0e0ebd..546ade21c 100644 --- a/ui/src/artist/ArtistList.js +++ b/ui/src/artist/ArtistList.js @@ -90,7 +90,7 @@ const ArtistDatagridRow = (props) => { item: { artistIds: [record?.id] }, options: { dropEffect: 'copy' }, }), - [record] + [record], ) return } @@ -132,7 +132,7 @@ const ArtistListView = ({ hasShow, hasEdit, hasList, width, ...rest }) => { resource: 'artist', columns: toggleableFields, }, - ['size'] + ['size'], ) return isXsmall ? ( diff --git a/ui/src/artist/DesktopArtistDetails.js b/ui/src/artist/DesktopArtistDetails.js index d49eff8fd..5d56bdef7 100644 --- a/ui/src/artist/DesktopArtistDetails.js +++ b/ui/src/artist/DesktopArtistDetails.js @@ -65,7 +65,7 @@ const useStyles = makeStyles( wordBreak: 'break-word', }, }), - { name: 'NDDesktopArtistDetails' } + { name: 'NDDesktopArtistDetails' }, ) const DesktopArtistDetails = ({ artistInfo, record, biography }) => { @@ -77,7 +77,7 @@ const DesktopArtistDetails = ({ artistInfo, record, biography }) => { const handleOpenLightbox = React.useCallback(() => setLightboxOpen(true), []) const handleCloseLightbox = React.useCallback( () => setLightboxOpen(false), - [] + [], ) return ( diff --git a/ui/src/artist/MobileArtistDetails.js b/ui/src/artist/MobileArtistDetails.js index 683280d9e..4f1bac909 100644 --- a/ui/src/artist/MobileArtistDetails.js +++ b/ui/src/artist/MobileArtistDetails.js @@ -72,7 +72,7 @@ const useStyles = makeStyles( wordBreak: 'break-word', }, }), - { name: 'NDMobileArtistDetails' } + { name: 'NDMobileArtistDetails' }, ) const MobileArtistDetails = ({ artistInfo, biography, record }) => { @@ -85,7 +85,7 @@ const MobileArtistDetails = ({ artistInfo, biography, record }) => { const handleOpenLightbox = React.useCallback(() => setLightboxOpen(true), []) const handleCloseLightbox = React.useCallback( () => setLightboxOpen(false), - [] + [], ) return ( diff --git a/ui/src/audioplayer/Player.js b/ui/src/audioplayer/Player.js index 6b4720173..174940aa2 100644 --- a/ui/src/audioplayer/Player.js +++ b/ui/src/audioplayer/Player.js @@ -48,7 +48,7 @@ const Player = () => { const isDesktop = useMediaQuery('(min-width:810px)') const isMobilePlayer = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent + navigator.userAgent, ) const { authenticated } = useAuthState() @@ -60,7 +60,7 @@ const Player = () => { enableCoverAnimation: config.enableCoverAnimation, }) const showNotifications = useSelector( - (state) => state.settings.notifications || false + (state) => state.settings.notifications || false, ) const gainInfo = useSelector((state) => state.replayGain) const [context, setContext] = useState(null) @@ -100,7 +100,7 @@ const Player = () => { numericGain = calculateReplayGain( gainInfo.preAmp, song.rgAlbumGain, - song.rgAlbumPeak + song.rgAlbumPeak, ) break } @@ -108,7 +108,7 @@ const Player = () => { numericGain = calculateReplayGain( gainInfo.preAmp, song.rgTrackGain, - song.rgTrackPeak + song.rgTrackPeak, ) break } @@ -160,7 +160,7 @@ const Player = () => { ), locale: locale(translate), }), - [gainInfo, isDesktop, playerTheme, translate] + [gainInfo, isDesktop, playerTheme, translate], ) const options = useMemo(() => { @@ -181,12 +181,12 @@ const Player = () => { const onAudioListsChange = useCallback( (_, audioLists, audioInfo) => dispatch(syncQueue(audioInfo, audioLists)), - [dispatch] + [dispatch], ) const nextSong = useCallback(() => { const idx = playerState.queue.findIndex( - (item) => item.uuid === playerState.current.uuid + (item) => item.uuid === playerState.current.uuid, ) return idx !== null ? playerState.queue[idx + 1] : null }, [playerState]) @@ -221,13 +221,13 @@ const Player = () => { setScrobbled(true) } }, - [startTime, scrobbled, nextSong, preloaded] + [startTime, scrobbled, nextSong, preloaded], ) const onAudioVolumeChange = useCallback( // sqrt to compensate for the logarithmic volume (volume) => dispatch(setVolume(Math.sqrt(volume))), - [dispatch] + [dispatch], ) const onAudioPlay = useCallback( @@ -260,12 +260,12 @@ const Player = () => { sendNotification( song.title, `${song.artist} - ${song.album}`, - info.cover + info.cover, ) } } }, - [context, dispatch, showNotifications, startTime] + [context, dispatch, showNotifications, startTime], ) const onAudioPlayTrackChange = useCallback(() => { @@ -279,7 +279,7 @@ const Player = () => { const onAudioPause = useCallback( (info) => dispatch(currentPlaying(info)), - [dispatch] + [dispatch], ) const onAudioEnded = useCallback( @@ -291,7 +291,7 @@ const Player = () => { .getOne('keepalive', { id: info.trackId }) .catch((e) => console.log('Keepalive error:', e)) }, - [dispatch, dataProvider] + [dispatch, dataProvider], ) const onCoverClick = useCallback((mode, audioLists, audioInfo) => { @@ -313,7 +313,7 @@ const Player = () => { const handlers = useMemo( () => keyHandlers(audioInstance, playerState), - [audioInstance, playerState] + [audioInstance, playerState], ) useEffect(() => { diff --git a/ui/src/audioplayer/keyHandlers.js b/ui/src/audioplayer/keyHandlers.js index 444ea2c12..793276de0 100644 --- a/ui/src/audioplayer/keyHandlers.js +++ b/ui/src/audioplayer/keyHandlers.js @@ -1,14 +1,14 @@ const keyHandlers = (audioInstance, playerState) => { const nextSong = () => { const idx = playerState.queue.findIndex( - (item) => item.uuid === playerState.current.uuid + (item) => item.uuid === playerState.current.uuid, ) return idx !== null ? playerState.queue[idx + 1] : null } const prevSong = () => { const idx = playerState.queue.findIndex( - (item) => item.uuid === playerState.current.uuid + (item) => item.uuid === playerState.current.uuid, ) return idx !== null ? playerState.queue[idx - 1] : null } diff --git a/ui/src/audioplayer/styles.js b/ui/src/audioplayer/styles.js index 38b7831b9..30a14d4db 100644 --- a/ui/src/audioplayer/styles.js +++ b/ui/src/audioplayer/styles.js @@ -87,7 +87,7 @@ const useStyle = makeStyles( }, }, }), - { name: 'NDAudioPlayer' } + { name: 'NDAudioPlayer' }, ) export default useStyle diff --git a/ui/src/common/AddToPlaylistButton.js b/ui/src/common/AddToPlaylistButton.js index 021bd308a..9cddc7499 100644 --- a/ui/src/common/AddToPlaylistButton.js +++ b/ui/src/common/AddToPlaylistButton.js @@ -12,7 +12,10 @@ export const AddToPlaylistButton = ({ resource, selectedIds, className }) => { const handleClick = () => { dispatch( - openAddToPlaylist({ selectedIds, onSuccess: () => unselectAll(resource) }) + openAddToPlaylist({ + selectedIds, + onSuccess: () => unselectAll(resource), + }), ) } diff --git a/ui/src/common/ArtistLinkField.js b/ui/src/common/ArtistLinkField.js index 26623658a..09df55521 100644 --- a/ui/src/common/ArtistLinkField.js +++ b/ui/src/common/ArtistLinkField.js @@ -14,28 +14,31 @@ export const useGetHandleArtistClick = (width) => { } } -export const ArtistLinkField = withWidth()( - ({ record, className, width, source }) => { - const artistLink = useGetHandleArtistClick(width) +export const ArtistLinkField = withWidth()(({ + record, + className, + width, + source, +}) => { + const artistLink = useGetHandleArtistClick(width) - const id = record[source + 'Id'] - return ( - <> - {id ? ( - e.stopPropagation()} - className={className} - > - {record[source]} - - ) : ( - record[source] - )} - - ) - } -) + const id = record[source + 'Id'] + return ( + <> + {id ? ( + e.stopPropagation()} + className={className} + > + {record[source]} + + ) : ( + record[source] + )} + + ) +}) ArtistLinkField.propTypes = { record: PropTypes.object, diff --git a/ui/src/common/ArtistSimpleList.js b/ui/src/common/ArtistSimpleList.js index de6994479..476da992e 100644 --- a/ui/src/common/ArtistSimpleList.js +++ b/ui/src/common/ArtistSimpleList.js @@ -23,7 +23,7 @@ const useStyles = makeStyles( top: '26px', }, }, - { name: 'RaArtistSimpleList' } + { name: 'RaArtistSimpleList' }, ) export const ArtistSimpleList = ({ @@ -69,7 +69,7 @@ export const ArtistSimpleList = ({ - ) + ), )} ) diff --git a/ui/src/common/BatchPlayButton.js b/ui/src/common/BatchPlayButton.js index f3b68f35e..f4c136922 100644 --- a/ui/src/common/BatchPlayButton.js +++ b/ui/src/common/BatchPlayButton.js @@ -30,7 +30,7 @@ export const BatchPlayButton = ({ // Add tracks to a map for easy lookup by ID, needed for the next step const tracks = response.data.reduce( (acc, cur) => ({ ...acc, [cur.id]: cur }), - {} + {}, ) // Add the tracks to the queue in the selection order dispatch(action(tracks, selectedIds)) diff --git a/ui/src/common/BatchShareButton.js b/ui/src/common/BatchShareButton.js index bcf76c3d4..8294953bb 100644 --- a/ui/src/common/BatchShareButton.js +++ b/ui/src/common/BatchShareButton.js @@ -18,8 +18,8 @@ export const BatchShareButton = ({ resource, selectedIds, className }) => { _: 'ra.action.bulk_actions', smart_count: selectedIds.length, }), - 'message.shareBatchDialogTitle' - ) + 'message.shareBatchDialogTitle', + ), ) unselectAll(resource) } diff --git a/ui/src/common/ContextMenus.js b/ui/src/common/ContextMenus.js index a492548a4..ba41b70b0 100644 --- a/ui/src/common/ContextMenus.js +++ b/ui/src/common/ContextMenus.js @@ -97,8 +97,8 @@ const ContextMenu = ({ record, record.duration !== undefined ? DOWNLOAD_MENU_ALBUM - : DOWNLOAD_MENU_ARTIST - ) + : DOWNLOAD_MENU_ARTIST, + ), ) }, }, @@ -127,7 +127,7 @@ const ContextMenu = ({ let extractSongsData = function (response) { const data = response.data.reduce( (acc, cur) => ({ ...acc, [cur.id]: cur }), - {} + {}, ) const ids = response.data.map((r) => r.id) return { data, ids } @@ -186,7 +186,7 @@ const ContextMenu = ({ {options[key].label} - ) + ), )} diff --git a/ui/src/common/Linkify.js b/ui/src/common/Linkify.js index 87947905a..0a09e0d76 100644 --- a/ui/src/common/Linkify.js +++ b/ui/src/common/Linkify.js @@ -10,7 +10,7 @@ const useStyles = makeStyles( color: theme.palette.primary.main, }, }), - { name: 'RaLink' } + { name: 'RaLink' }, ) const Linkify = ({ text, ...rest }) => { @@ -45,7 +45,7 @@ const Linkify = ({ text, ...rest }) => { href={href} > {href} - + , ) lastIndex = match.index + href.length @@ -57,7 +57,7 @@ const Linkify = ({ text, ...rest }) => { + />, ) } diff --git a/ui/src/common/LoveButton.js b/ui/src/common/LoveButton.js index eea91e826..4f89fd57b 100644 --- a/ui/src/common/LoveButton.js +++ b/ui/src/common/LoveButton.js @@ -36,7 +36,7 @@ export const LoveButton = ({ toggleLove() e.stopPropagation() }, - [toggleLove] + [toggleLove], ) if (!config.enableFavourites) { diff --git a/ui/src/common/MultiLineTextField.js b/ui/src/common/MultiLineTextField.js index 5148482dd..8f45da04f 100644 --- a/ui/src/common/MultiLineTextField.js +++ b/ui/src/common/MultiLineTextField.js @@ -39,11 +39,11 @@ export const MultiLineTextField = memo( key={md5(line + idx)} dangerouslySetInnerHTML={{ __html: line }} /> - ) + ), )}
) - } + }, ) MultiLineTextField.defaultProps = { diff --git a/ui/src/common/MultiLineTextField.test.js b/ui/src/common/MultiLineTextField.test.js index 44dede43f..8f29166a3 100644 --- a/ui/src/common/MultiLineTextField.test.js +++ b/ui/src/common/MultiLineTextField.test.js @@ -20,9 +20,9 @@ describe('', () => { record={{ id: 123, body }} emptyText="NA" source="body" - /> + />, ) expect(screen.getByText('NA')).toBeInTheDocument() - } + }, ) }) diff --git a/ui/src/common/PlayButton.js b/ui/src/common/PlayButton.js index e40927ef2..ddc676004 100644 --- a/ui/src/common/PlayButton.js +++ b/ui/src/common/PlayButton.js @@ -10,7 +10,7 @@ export const PlayButton = ({ record, size, className }) => { let extractSongsData = function (response) { const data = response.data.reduce( (acc, cur) => ({ ...acc, [cur.id]: cur }), - {} + {}, ) const ids = response.data.map((r) => r.id) return { data, ids } diff --git a/ui/src/common/QualityInfo.js b/ui/src/common/QualityInfo.js index e8beb3236..e663d3092 100644 --- a/ui/src/common/QualityInfo.js +++ b/ui/src/common/QualityInfo.js @@ -16,7 +16,7 @@ const useStyle = makeStyles( }), { name: 'NDQualityInfo', - } + }, ) export const QualityInfo = ({ record, size, gainMode, preAmp, className }) => { diff --git a/ui/src/common/QuickFilter.test.js b/ui/src/common/QuickFilter.test.js index 56426af94..36df2aa0e 100644 --- a/ui/src/common/QuickFilter.test.js +++ b/ui/src/common/QuickFilter.test.js @@ -22,7 +22,7 @@ describe('QuickFilter', () => { resource={'song'} source={'name'} label={} - /> + />, ) expect(screen.getByTestId('label-icon-test')).not.toBeNull() }) diff --git a/ui/src/common/RatingField.js b/ui/src/common/RatingField.js index fe9171527..dc8acb7b0 100644 --- a/ui/src/common/RatingField.js +++ b/ui/src/common/RatingField.js @@ -40,7 +40,7 @@ export const RatingField = ({ (e, val) => { rate(val, e.target.name) }, - [rate] + [rate], ) return ( @@ -50,7 +50,7 @@ export const RatingField = ({ className={clsx( className, classes.rating, - rating > 0 ? classes.show : classes.hide + rating > 0 ? classes.show : classes.hide, )} value={rating} size={size} diff --git a/ui/src/common/SimpleList.js b/ui/src/common/SimpleList.js index 1a4f72fba..b803993a2 100644 --- a/ui/src/common/SimpleList.js +++ b/ui/src/common/SimpleList.js @@ -19,7 +19,7 @@ const useStyles = makeStyles( }, tertiary: { float: 'right', opacity: 0.541176 }, }, - { name: 'RaSimpleList' } + { name: 'RaSimpleList' }, ) const LinkOrNot = ({ diff --git a/ui/src/common/SongContextMenu.js b/ui/src/common/SongContextMenu.js index 3b8f60835..16a1c4cad 100644 --- a/ui/src/common/SongContextMenu.js +++ b/ui/src/common/SongContextMenu.js @@ -61,7 +61,7 @@ export const SongContextMenu = ({ openAddToPlaylist({ selectedIds: [record.mediaFileId || record.id], onSuccess: (id) => onAddToPlaylist(id), - }) + }), ), }, share: { @@ -69,7 +69,11 @@ export const SongContextMenu = ({ label: translate('ra.action.share'), action: (record) => dispatch( - openShareMenu([record.mediaFileId || record.id], 'song', record.title) + openShareMenu( + [record.mediaFileId || record.id], + 'song', + record.title, + ), ), }, download: { @@ -127,7 +131,7 @@ export const SongContextMenu = ({ {options[key].label} - ) + ), )} diff --git a/ui/src/common/SongDatagrid.js b/ui/src/common/SongDatagrid.js index 42a617a73..c2ace92a8 100644 --- a/ui/src/common/SongDatagrid.js +++ b/ui/src/common/SongDatagrid.js @@ -97,7 +97,7 @@ const ReleaseRow = forwardRef( ) - } + }, ) const DiscSubtitleRow = forwardRef( @@ -141,7 +141,7 @@ const DiscSubtitleRow = forwardRef( ) - } + }, ) export const SongDatagridRow = ({ @@ -156,7 +156,7 @@ export const SongDatagridRow = ({ }) => { const classes = useStyles() const fields = React.Children.toArray(children).filter((c) => - isValidElement(c) + isValidElement(c), ) const [, dragDiscRef] = useDrag( @@ -173,7 +173,7 @@ export const SongDatagridRow = ({ }, options: { dropEffect: 'copy' }, }), - [record] + [record], ) const [, dragSongRef] = useDrag( @@ -182,7 +182,7 @@ export const SongDatagridRow = ({ item: { ids: [record?.mediaFileId || record?.id] }, options: { dropEffect: 'copy' }, }), - [record] + [record], ) if (!record || !record.title) { @@ -251,14 +251,14 @@ const SongDatagridBody = ({ idsToPlay = ids.filter( (id) => data[id].releaseDate === releaseDate && - data[id].discNumber === discNumber + data[id].discNumber === discNumber, ) } else { idsToPlay = ids.filter((id) => data[id].releaseDate === releaseDate) } dispatch(playTracks(data, idsToPlay)) }, - [dispatch, data, ids] + [dispatch, data, ids], ) const firstTracksOfDiscs = useMemo(() => { @@ -280,7 +280,7 @@ const SongDatagridBody = ({ acc.push(id) } return acc - }, []) + }, []), ) if (!showDiscSubtitles || (set.size < 2 && !foundSubtitle)) { set.clear() @@ -304,7 +304,7 @@ const SongDatagridBody = ({ acc.push(id) } return acc - }, []) + }, []), ) if (!showReleaseDivider || set.size < 2) { set.clear() diff --git a/ui/src/common/SongSimpleList.js b/ui/src/common/SongSimpleList.js index 61a2893f7..d398d3455 100644 --- a/ui/src/common/SongSimpleList.js +++ b/ui/src/common/SongSimpleList.js @@ -47,7 +47,7 @@ const useStyles = makeStyles( top: '26px', }, }, - { name: 'RaSongSimpleList' } + { name: 'RaSongSimpleList' }, ) export const SongSimpleList = ({ @@ -108,7 +108,7 @@ export const SongSimpleList = ({ - ) + ), )} ) diff --git a/ui/src/common/ToggleFieldsMenu.js b/ui/src/common/ToggleFieldsMenu.js index f8ee30d0f..32ae51def 100644 --- a/ui/src/common/ToggleFieldsMenu.js +++ b/ui/src/common/ToggleFieldsMenu.js @@ -36,7 +36,7 @@ export const ToggleFieldsMenu = ({ const dispatch = useDispatch() const translate = useTranslate() const toggleableColumns = useSelector( - (state) => state.settings.toggleableFields[resource] + (state) => state.settings.toggleableFields[resource], ) const omittedColumns = useSelector((state) => state.settings.omittedFields[resource]) || [] @@ -58,7 +58,7 @@ export const ToggleFieldsMenu = ({ ...toggleableColumns, [selectedColumn]: !toggleableColumns[selectedColumn], }, - }) + }), ) } @@ -95,7 +95,7 @@ export const ToggleFieldsMenu = ({ {translate(`resources.${resource}.fields.${key}`)} - ) : null + ) : null, )} diff --git a/ui/src/common/Writable.js b/ui/src/common/Writable.js index 27a46f9e0..0dc625460 100644 --- a/ui/src/common/Writable.js +++ b/ui/src/common/Writable.js @@ -15,7 +15,7 @@ export const Writable = (props) => { const { record = {}, children } = props if (isWritable(record.ownerId)) { return Children.map(children, (child) => - isValidElement(child) ? cloneElement(child, props) : child + isValidElement(child) ? cloneElement(child, props) : child, ) } return null diff --git a/ui/src/common/useAlbumsPerPage.js b/ui/src/common/useAlbumsPerPage.js index 6d3edf046..6a02bdeb7 100644 --- a/ui/src/common/useAlbumsPerPage.js +++ b/ui/src/common/useAlbumsPerPage.js @@ -19,7 +19,7 @@ const getPerPageOptions = (width) => { export const useAlbumsPerPage = (width) => { const perPage = useSelector( - (state) => state?.admin.resources?.album?.list?.params?.perPage + (state) => state?.admin.resources?.album?.list?.params?.perPage, ) || getPerPage(width) return [perPage, getPerPageOptions(width)] diff --git a/ui/src/common/useResourceRefresh.js b/ui/src/common/useResourceRefresh.js index 4c2008089..d9f6aee52 100644 --- a/ui/src/common/useResourceRefresh.js +++ b/ui/src/common/useResourceRefresh.js @@ -7,7 +7,7 @@ export const useResourceRefresh = (...visibleResources) => { const refresh = useRefresh() const dataProvider = useDataProvider() const refreshData = useSelector( - (state) => state.activity?.refresh || { lastReceived: lastTime } + (state) => state.activity?.refresh || { lastReceived: lastTime }, ) const { resources, lastReceived } = refreshData diff --git a/ui/src/common/useSelectedFields.js b/ui/src/common/useSelectedFields.js index 07ccc9180..0560de523 100644 --- a/ui/src/common/useSelectedFields.js +++ b/ui/src/common/useSelectedFields.js @@ -12,7 +12,7 @@ export const useSelectedFields = ({ }) => { const dispatch = useDispatch() const resourceFields = useSelector( - (state) => state.settings.toggleableFields + (state) => state.settings.toggleableFields, )?.[resource] const omittedFields = useSelector((state) => state.settings.omittedFields)?.[ resource @@ -81,7 +81,7 @@ useSelectedFields.propTypes = { export const useSetToggleableFields = ( resource, toggleableColumns, - defaultOff = [] + defaultOff = [], ) => { const current = useSelector((state) => state.settings.toggleableFields)?.album const dispatch = useDispatch() @@ -95,7 +95,7 @@ export const useSetToggleableFields = ( ...{ [cur]: true }, } }, {}), - }) + }), ) dispatch(setOmittedFields({ [resource]: defaultOff })) } diff --git a/ui/src/consts.js b/ui/src/consts.js index 20c8bac2b..b0524669e 100644 --- a/ui/src/consts.js +++ b/ui/src/consts.js @@ -16,7 +16,7 @@ DraggableTypes.ALL.push( DraggableTypes.SONG, DraggableTypes.ALBUM, DraggableTypes.DISC, - DraggableTypes.ARTIST + DraggableTypes.ARTIST, ) export const DEFAULT_SHARE_BITRATE = 128 diff --git a/ui/src/dialogs/AboutDialog.test.js b/ui/src/dialogs/AboutDialog.test.js index 389034fec..5c4e2f77f 100644 --- a/ui/src/dialogs/AboutDialog.test.js +++ b/ui/src/dialogs/AboutDialog.test.js @@ -30,7 +30,7 @@ describe('', () => { const link = screen.queryByRole('link') expect(link.href).toBe( - 'https://github.com/navidrome/navidrome/releases/tag/v0.40.0' + 'https://github.com/navidrome/navidrome/releases/tag/v0.40.0', ) expect(link.textContent).toBe('0.40.0') @@ -44,7 +44,7 @@ describe('', () => { const link = screen.queryByRole('link') expect(link.href).toBe( - 'https://github.com/navidrome/navidrome/compare/v0.40.0...300a0292' + 'https://github.com/navidrome/navidrome/compare/v0.40.0...300a0292', ) expect(link.textContent).toBe('0.40.0-SNAPSHOT') diff --git a/ui/src/dialogs/AddToPlaylistDialog.js b/ui/src/dialogs/AddToPlaylistDialog.js index 47098f22f..6033d1031 100644 --- a/ui/src/dialogs/AddToPlaylistDialog.js +++ b/ui/src/dialogs/AddToPlaylistDialog.js @@ -72,7 +72,7 @@ export const AddToPlaylistDialog = () => { const tracks = res.json if (tracks) { const dupSng = tracks.filter((song) => - selectedIds.some((id) => id === song.mediaFileId) + selectedIds.some((id) => id === song.mediaFileId), ) if (dupSng.length) { @@ -128,7 +128,7 @@ export const AddToPlaylistDialog = () => { } const handleSkip = () => { const distinctSongs = selectedIds.filter( - (id) => duplicateIds.indexOf(id) < 0 + (id) => duplicateIds.indexOf(id) < 0, ) value.slice(-1).pop().distinctIds = distinctSongs dispatch(closeDuplicateSongDialog()) diff --git a/ui/src/dialogs/AddToPlaylistDialog.test.js b/ui/src/dialogs/AddToPlaylistDialog.test.js index 0932af306..66f8a6687 100644 --- a/ui/src/dialogs/AddToPlaylistDialog.test.js +++ b/ui/src/dialogs/AddToPlaylistDialog.test.js @@ -59,7 +59,7 @@ const createTestUtils = (mockDataProvider) => > - + , ) jest.mock('../dataProvider', () => ({ @@ -104,7 +104,7 @@ describe('AddToPlaylistDialog', () => { { data: { ids: selectedIds }, filter: { playlist_id: 'sample-id1' }, - } + }, ) }) await waitFor(() => { @@ -114,7 +114,7 @@ describe('AddToPlaylistDialog', () => { { data: { ids: selectedIds }, filter: { playlist_id: 'sample-id2' }, - } + }, ) }) }) @@ -153,7 +153,7 @@ describe('AddToPlaylistDialog', () => { { data: { ids: selectedIds }, filter: { playlist_id: 'created-id1' }, - } + }, ) }) diff --git a/ui/src/dialogs/DownloadMenuDialog.js b/ui/src/dialogs/DownloadMenuDialog.js index 290431598..2104cbcad 100644 --- a/ui/src/dialogs/DownloadMenuDialog.js +++ b/ui/src/dialogs/DownloadMenuDialog.js @@ -14,7 +14,7 @@ import { useTranscodingOptions } from './useTranscodingOptions' const DownloadMenuDialog = () => { const { open, record, recordType } = useSelector( - (state) => state.downloadMenuDialog + (state) => state.downloadMenuDialog, ) const dispatch = useDispatch() const translate = useTranslate() diff --git a/ui/src/dialogs/HelpDialog.js b/ui/src/dialogs/HelpDialog.js index bba473813..adbce99b2 100644 --- a/ui/src/dialogs/HelpDialog.js +++ b/ui/src/dialogs/HelpDialog.js @@ -54,7 +54,7 @@ const HelpTable = (props) => { , - document.body + document.body, ) } diff --git a/ui/src/dialogs/ListenBrainzTokenDialog.js b/ui/src/dialogs/ListenBrainzTokenDialog.js index 571c5af17..8966675ff 100644 --- a/ui/src/dialogs/ListenBrainzTokenDialog.js +++ b/ui/src/dialogs/ListenBrainzTokenDialog.js @@ -58,7 +58,7 @@ export const ListenBrainzTokenDialog = ({ setLinked }) => { event.stopPropagation() }) }, - [dispatch, notify, setLinked, token] + [dispatch, notify, setLinked, token], ) const handleClickClose = (event) => { @@ -74,7 +74,7 @@ export const ListenBrainzTokenDialog = ({ setLinked }) => { handleSave(event) } }, - [token, handleSave] + [token, handleSave], ) return ( diff --git a/ui/src/dialogs/SelectPlaylistInput.js b/ui/src/dialogs/SelectPlaylistInput.js index e35af00fa..372d5f408 100644 --- a/ui/src/dialogs/SelectPlaylistInput.js +++ b/ui/src/dialogs/SelectPlaylistInput.js @@ -25,7 +25,7 @@ export const SelectPlaylistInput = ({ onChange }) => { 'playlist', { page: 1, perPage: -1 }, { field: 'name', order: 'ASC' }, - { smart: false } + { smart: false }, ) const options = diff --git a/ui/src/dialogs/SelectPlaylistInput.test.js b/ui/src/dialogs/SelectPlaylistInput.test.js index 13fab2afb..60daa90cd 100644 --- a/ui/src/dialogs/SelectPlaylistInput.test.js +++ b/ui/src/dialogs/SelectPlaylistInput.test.js @@ -65,7 +65,7 @@ describe('SelectPlaylistInput', () => { > - + , ) await waitFor(() => { diff --git a/ui/src/dialogs/ShareDialog.js b/ui/src/dialogs/ShareDialog.js index 3de15fe32..68156bf58 100644 --- a/ui/src/dialogs/ShareDialog.js +++ b/ui/src/dialogs/ShareDialog.js @@ -33,7 +33,7 @@ export const ShareDialog = () => { const translate = useTranslate() const [description, setDescription] = useState('') const [downloadable, setDownloadable] = useState( - config.defaultDownloadableShare && config.enableDownloads + config.defaultDownloadableShare && config.enableDownloads, ) useEffect(() => { setDescription('') @@ -66,7 +66,7 @@ export const ShareDialog = () => { type: 'warning', multiLine: true, duration: 0, - } + }, ) }) } else prompt(translate('message.shareCopyToClipboard'), url) @@ -75,7 +75,7 @@ export const ShareDialog = () => { notify(translate('ra.page.error') + ': ' + error.message, { type: 'warning', }), - } + }, ) const handleShare = (e) => { diff --git a/ui/src/dialogs/useTranscodingOptions.js b/ui/src/dialogs/useTranscodingOptions.js index 4a0e421d9..9019f0ab0 100644 --- a/ui/src/dialogs/useTranscodingOptions.js +++ b/ui/src/dialogs/useTranscodingOptions.js @@ -20,7 +20,7 @@ export const useTranscodingOptions = () => { page: 1, perPage: 1000, }, - { field: 'name', order: 'ASC' } + { field: 'name', order: 'ASC' }, ) const formatOptions = useMemo( @@ -30,7 +30,7 @@ export const useTranscodingOptions = () => { : Object.values(formats).map((f) => { return { id: f.targetFormat, name: f.name } }), - [formats, loadingFormats] + [formats, loadingFormats], ) const handleOriginal = useCallback( @@ -41,7 +41,7 @@ export const useTranscodingOptions = () => { setMaxBitRate(DEFAULT_SHARE_BITRATE) } }, - [setUseOriginalFormat, setFormat, setMaxBitRate] + [setUseOriginalFormat, setFormat, setMaxBitRate], ) const TranscodingOptionsInput = useMemo(() => { diff --git a/ui/src/eventStream.js b/ui/src/eventStream.js index d5df78971..481739d92 100644 --- a/ui/src/eventStream.js +++ b/ui/src/eventStream.js @@ -30,7 +30,7 @@ const startEventStream = async (dispatchFn) => { newStream.addEventListener('serverStart', eventHandler(dispatchFn)) newStream.addEventListener( 'scanStatus', - throttledEventHandler(dispatchFn) + throttledEventHandler(dispatchFn), ) newStream.addEventListener('refreshResource', eventHandler(dispatchFn)) newStream.addEventListener('keepAlive', eventHandler(dispatchFn)) diff --git a/ui/src/i18n/useGetLanguageChoices.js b/ui/src/i18n/useGetLanguageChoices.js index 0c6773ae6..0c708691f 100644 --- a/ui/src/i18n/useGetLanguageChoices.js +++ b/ui/src/i18n/useGetLanguageChoices.js @@ -6,7 +6,7 @@ const useGetLanguageChoices = () => { 'translation', { page: 1, perPage: -1 }, { field: '', order: '' }, - {} + {}, ) const choices = [{ id: 'en', name: 'English' }] diff --git a/ui/src/layout/AppBar.js b/ui/src/layout/AppBar.js index c72f36bff..47406c4c1 100644 --- a/ui/src/layout/AppBar.js +++ b/ui/src/layout/AppBar.js @@ -31,7 +31,7 @@ const useStyles = makeStyles( }), { name: 'NDAppBar', - } + }, ) const AboutMenuItem = forwardRef(({ onClick, ...rest }, ref) => { @@ -90,7 +90,7 @@ const CustomUserMenu = ({ onClick, ...rest }) => { } return renderSettingsMenuItemLink( userResource, - permissions !== 'admin' ? localStorage.getItem('userId') : null + permissions !== 'admin' ? localStorage.getItem('userId') : null, ) } diff --git a/ui/src/layout/DynamicMenuIcon.test.js b/ui/src/layout/DynamicMenuIcon.test.js index 3d8e238fb..51b0ba635 100644 --- a/ui/src/layout/DynamicMenuIcon.test.js +++ b/ui/src/layout/DynamicMenuIcon.test.js @@ -17,7 +17,7 @@ describe('', () => { render( - + , ) expect(screen.getByTestId('icon')).not.toBeNull() }) @@ -34,7 +34,7 @@ describe('', () => { activeIcon={StarBorderIcon} path={'otherpath'} /> - + , ) expect(screen.getByTestId('icon')).not.toBeNull() }) @@ -51,7 +51,7 @@ describe('', () => { activeIcon={StarBorderIcon} path={'path'} /> - + , ) expect(screen.getByTestId('activeIcon')).not.toBeNull() }) diff --git a/ui/src/layout/Login.js b/ui/src/layout/Login.js index a197b495b..aac04faf4 100644 --- a/ui/src/layout/Login.js +++ b/ui/src/layout/Login.js @@ -82,7 +82,7 @@ const useStyles = makeStyles( textDecoration: 'none', }, }), - { name: 'NDLogin' } + { name: 'NDLogin' }, ) const renderInput = ({ @@ -263,14 +263,14 @@ const Login = ({ location }) => { typeof error === 'string' ? error : typeof error === 'undefined' || !error.message - ? 'ra.auth.sign_in_error' - : error.message, - 'warning' + ? 'ra.auth.sign_in_error' + : error.message, + 'warning', ) - } + }, ) }, - [dispatch, login, notify, setLoading, location] + [dispatch, login, notify, setLoading, location], ) const validateLogin = useCallback( @@ -284,7 +284,7 @@ const Login = ({ location }) => { } return errors }, - [translate] + [translate], ) const validateSignup = useCallback( @@ -302,7 +302,7 @@ const Login = ({ location }) => { } return errors }, - [translate, validateLogin] + [translate, validateLogin], ) if (config.firstTime) { @@ -348,7 +348,7 @@ const LoginWithTheme = (props) => { }) .catch((e) => { throw new Error( - 'Cannot load language "' + config.defaultLanguage + '": ' + e + 'Cannot load language "' + config.defaultLanguage + '": ' + e, ) }) } diff --git a/ui/src/layout/Menu.js b/ui/src/layout/Menu.js index 7ed21ae8f..50670a168 100644 --- a/ui/src/layout/Menu.js +++ b/ui/src/layout/Menu.js @@ -121,7 +121,7 @@ const Menu = ({ dense = false }) => { dense={dense} > {Object.keys(albumLists).map((type) => - renderAlbumMenuItemLink(type, albumLists[type]) + renderAlbumMenuItemLink(type, albumLists[type]), )} {resources.filter(subItems(undefined)).map(renderResourceMenuItemLink)} diff --git a/ui/src/layout/PlaylistsSubMenu.js b/ui/src/layout/PlaylistsSubMenu.js index e16aa7e18..a9f70b875 100644 --- a/ui/src/layout/PlaylistsSubMenu.js +++ b/ui/src/layout/PlaylistsSubMenu.js @@ -93,7 +93,7 @@ const PlaylistsSubMenu = ({ state, setState, sidebarIsOpen, dense }) => { const onPlaylistConfig = useCallback( () => history.push('/playlist'), - [history] + [history], ) return ( diff --git a/ui/src/layout/SubMenu.js b/ui/src/layout/SubMenu.js index d06d61bbd..418f4c651 100644 --- a/ui/src/layout/SubMenu.js +++ b/ui/src/layout/SubMenu.js @@ -42,7 +42,7 @@ const useStyles = makeStyles( }), { name: 'NDSubMenu', - } + }, ) const SubMenu = ({ diff --git a/ui/src/layout/UserMenu.js b/ui/src/layout/UserMenu.js index 980a82b32..c7a3deaf4 100644 --- a/ui/src/layout/UserMenu.js +++ b/ui/src/layout/UserMenu.js @@ -118,7 +118,7 @@ const UserMenu = (props) => { ? cloneElement(menuItem, { onClick: handleClose, }) - : null + : null, )} {!config.auth && logout} diff --git a/ui/src/personal/LastfmScrobbleToggle.js b/ui/src/personal/LastfmScrobbleToggle.js index 92faf307f..84532de36 100644 --- a/ui/src/personal/LastfmScrobbleToggle.js +++ b/ui/src/personal/LastfmScrobbleToggle.js @@ -20,11 +20,11 @@ const Progress = (props) => { useEffect(() => { const callbackEndpoint = baseUrl( - `/api/lastfm/link/callback?uid=${localStorage.getItem('userId')}` + `/api/lastfm/link/callback?uid=${localStorage.getItem('userId')}`, ) const callbackUrl = `${window.location.origin}${callbackEndpoint}` openedTab.current = openInNewTab( - `https://www.last.fm/api/auth/?api_key=${config.lastFMApiKey}&cb=${callbackUrl}` + `https://www.last.fm/api/auth/?api_key=${config.lastFMApiKey}&cb=${callbackUrl}`, ) }, []) diff --git a/ui/src/personal/SelectTheme.js b/ui/src/personal/SelectTheme.js index e69d996ff..6ec39b09b 100644 --- a/ui/src/personal/SelectTheme.js +++ b/ui/src/personal/SelectTheme.js @@ -21,7 +21,7 @@ export const SelectTheme = (props) => { themeChoices.push( ...Object.keys(themes).map((key) => { return { id: key, name: themes[key].themeName } - }) + }), ) themeChoices.push({ id: helpKey, diff --git a/ui/src/playlist/PlaylistActions.js b/ui/src/playlist/PlaylistActions.js index d98596c3b..1e7bef9b8 100644 --- a/ui/src/playlist/PlaylistActions.js +++ b/ui/src/playlist/PlaylistActions.js @@ -59,7 +59,7 @@ const PlaylistActions = ({ className, ids, data, record, ...rest }) => { .then((res) => { const data = res.data.reduce( (acc, curr) => ({ ...acc, [curr.id]: curr }), - {} + {}, ) dispatch(action(data)) }) @@ -67,7 +67,7 @@ const PlaylistActions = ({ className, ids, data, record, ...rest }) => { notify('ra.page.error', 'warning') }) }, - [dataProvider, dispatch, record, data, ids, notify] + [dataProvider, dispatch, record, data, ids, notify], ) const handlePlay = React.useCallback(() => { @@ -108,7 +108,7 @@ const PlaylistActions = ({ className, ids, data, record, ...rest }) => { link.click() link.parentNode.removeChild(link) }), - [record] + [record], ) return ( diff --git a/ui/src/playlist/PlaylistDetails.js b/ui/src/playlist/PlaylistDetails.js index 954f0c841..4e80b666f 100644 --- a/ui/src/playlist/PlaylistDetails.js +++ b/ui/src/playlist/PlaylistDetails.js @@ -38,7 +38,7 @@ const useStyles = makeStyles( }), { name: 'NDPlaylistDetails', - } + }, ) const PlaylistDetails = (props) => { diff --git a/ui/src/playlist/PlaylistList.js b/ui/src/playlist/PlaylistList.js index 9f5667d7b..71a8c75db 100644 --- a/ui/src/playlist/PlaylistList.js +++ b/ui/src/playlist/PlaylistList.js @@ -65,7 +65,7 @@ const TogglePublicInput = ({ resource, source }) => { console.log(error) notify('ra.page.error', 'warning') }, - } + }, ) const handleClick = (e) => { @@ -108,7 +108,7 @@ const PlaylistList = (props) => { ), comment: , }), - [isDesktop, isXsmall] + [isDesktop, isXsmall], ) const columns = useSelectedFields({ diff --git a/ui/src/playlist/PlaylistShow.js b/ui/src/playlist/PlaylistShow.js index 66fb383a0..ca300bbd0 100644 --- a/ui/src/playlist/PlaylistShow.js +++ b/ui/src/playlist/PlaylistShow.js @@ -20,7 +20,7 @@ const useStyles = makeStyles( }), { name: 'NDPlaylistShow', - } + }, ) const PlaylistShowLayout = (props) => { diff --git a/ui/src/playlist/PlaylistSongs.js b/ui/src/playlist/PlaylistSongs.js index a968eefa6..f249c9793 100644 --- a/ui/src/playlist/PlaylistSongs.js +++ b/ui/src/playlist/PlaylistSongs.js @@ -72,7 +72,7 @@ const useStyles = makeStyles( visibility: (props) => (props.isDesktop ? 'hidden' : 'visible'), }, }), - { name: 'RaList' } + { name: 'RaList' }, ) const ReorderableList = ({ readOnly, children, ...rest }) => { @@ -99,7 +99,7 @@ const PlaylistSongs = ({ playlistId, readOnly, actions, ...props }) => { refetch() } }, - [playlistId, refetch] + [playlistId, refetch], ) const reorder = useCallback( @@ -117,7 +117,7 @@ const PlaylistSongs = ({ playlistId, readOnly, actions, ...props }) => { notify('ra.page.error', 'warning') }) }, - [dataProvider, notify, refetch] + [dataProvider, notify, refetch], ) const handleDragEnd = useCallback( @@ -126,7 +126,7 @@ const PlaylistSongs = ({ playlistId, readOnly, actions, ...props }) => { const fromId = ids[from] reorder(playlistId, fromId, toId) }, - [playlistId, reorder, ids] + [playlistId, reorder, ids], ) const toggleableFields = useMemo(() => { diff --git a/ui/src/radio/StreamField.js b/ui/src/radio/StreamField.js index 302a7f199..2327f3c1a 100644 --- a/ui/src/radio/StreamField.js +++ b/ui/src/radio/StreamField.js @@ -26,7 +26,7 @@ export const StreamField = (props) => { evt.preventDefault() dispatch(setTrack(await songFromRadio(record))) }, - [dispatch, record] + [dispatch, record], ) return ( diff --git a/ui/src/reducers/albumView.js b/ui/src/reducers/albumView.js index ec47a1d5e..ef6833471 100644 --- a/ui/src/reducers/albumView.js +++ b/ui/src/reducers/albumView.js @@ -4,7 +4,7 @@ export const albumViewReducer = ( previousState = { grid: true, }, - payload + payload, ) => { const { type } = payload switch (type) { diff --git a/ui/src/reducers/dialogReducer.js b/ui/src/reducers/dialogReducer.js index f80cb7350..e43f46b6f 100644 --- a/ui/src/reducers/dialogReducer.js +++ b/ui/src/reducers/dialogReducer.js @@ -24,7 +24,7 @@ export const shareDialogReducer = ( resource: '', name: '', }, - payload + payload, ) => { const { type, ids, resource, name, label } = payload switch (type) { @@ -52,7 +52,7 @@ export const addToPlaylistDialogReducer = ( open: false, duplicateSong: false, }, - payload + payload, ) => { const { type } = payload switch (type) { @@ -82,7 +82,7 @@ export const downloadMenuDialogReducer = ( previousState = { open: false, }, - payload + payload, ) => { const { type } = payload switch (type) { @@ -125,7 +125,7 @@ export const expandInfoDialogReducer = ( previousState = { open: false, }, - payload + payload, ) => { const { type } = payload switch (type) { @@ -149,7 +149,7 @@ export const listenBrainzTokenDialogReducer = ( previousState = { open: false, }, - payload + payload, ) => { const { type } = payload switch (type) { diff --git a/ui/src/reducers/playerReducer.js b/ui/src/reducers/playerReducer.js index be8259f26..a6597ef36 100644 --- a/ui/src/reducers/playerReducer.js +++ b/ui/src/reducers/playerReducer.js @@ -55,7 +55,7 @@ const mapToAudioLists = (item) => { updatedAt: item.updatedAt, album: item.album, }, - 300 + 300, ), } } @@ -140,7 +140,7 @@ const reduceSyncQueue = (state, { data: { audioInfo, audioLists } }) => { const reduceCurrent = (state, { data }) => { const current = data.ended ? {} : data const savedPlayIndex = state.queue.findIndex( - (item) => item.uuid === current.uuid + (item) => item.uuid === current.uuid, ) return { ...state, diff --git a/ui/src/reducers/replayGainReducer.js b/ui/src/reducers/replayGainReducer.js index 49d9e0ba0..6d51db2bf 100644 --- a/ui/src/reducers/replayGainReducer.js +++ b/ui/src/reducers/replayGainReducer.js @@ -18,7 +18,7 @@ const initialState = { export const replayGainReducer = ( previousState = initialState, - { type, payload } + { type, payload }, ) => { switch (type) { case CHANGE_GAIN: { diff --git a/ui/src/reducers/themeReducer.js b/ui/src/reducers/themeReducer.js index ef5ccc7ce..2a5d5bac6 100644 --- a/ui/src/reducers/themeReducer.js +++ b/ui/src/reducers/themeReducer.js @@ -5,14 +5,14 @@ import themes from '../themes' const defaultTheme = () => { return ( Object.keys(themes).find( - (t) => themes[t].themeName === config.defaultTheme + (t) => themes[t].themeName === config.defaultTheme, ) || 'DarkTheme' ) } export const themeReducer = ( previousState = defaultTheme(), - { type, payload } + { type, payload }, ) => { if (type === CHANGE_THEME) { return payload diff --git a/ui/src/serviceWorker.js b/ui/src/serviceWorker.js index 420ee107e..deb3b798a 100644 --- a/ui/src/serviceWorker.js +++ b/ui/src/serviceWorker.js @@ -16,8 +16,8 @@ const isLocalhost = Boolean( window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/, + ), ) export function register(config) { @@ -43,7 +43,7 @@ export function register(config) { navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' + 'worker. To learn more, visit https://bit.ly/CRA-PWA', ) }) } else { @@ -71,7 +71,7 @@ function registerValidSW(swUrl, config) { // content until all client tabs are closed. console.log( 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', ) // Execute callback @@ -123,7 +123,7 @@ function checkValidServiceWorker(swUrl, config) { }) .catch(() => { console.log( - 'No internet connection found. App is running in offline mode.' + 'No internet connection found. App is running in offline mode.', ) }) } diff --git a/ui/src/share/ShareList.js b/ui/src/share/ShareList.js index 0219e49db..b7b9edb5a 100644 --- a/ui/src/share/ShareList.js +++ b/ui/src/share/ShareList.js @@ -48,7 +48,7 @@ const ShareList = (props) => { type: 'warning', multiLine: true, duration: 0, - } + }, ) }) } else prompt(translate('message.shareCopyToClipboard'), url) diff --git a/ui/src/store/createAdminStore.js b/ui/src/store/createAdminStore.js index ba8d40771..82545f634 100644 --- a/ui/src/store/createAdminStore.js +++ b/ui/src/store/createAdminStore.js @@ -48,7 +48,9 @@ const createAdminStore = ({ const store = createStore( resettableAppReducer, persistedState, - composeEnhancers(applyMiddleware(sagaMiddleware, routerMiddleware(history))) + composeEnhancers( + applyMiddleware(sagaMiddleware, routerMiddleware(history)), + ), ) store.subscribe( @@ -61,7 +63,7 @@ const createAdminStore = ({ settings: state.settings, }) }), - 1000 + 1000, ) sagaMiddleware.run(saga) diff --git a/ui/src/subsonic/index.js b/ui/src/subsonic/index.js index 1616a70d5..674135a1f 100644 --- a/ui/src/subsonic/index.js +++ b/ui/src/subsonic/index.js @@ -27,7 +27,7 @@ const scrobble = (id, time, submission = true) => url('scrobble', id, { ...(submission && time && { time }), submission, - }) + }), ) const nowPlaying = (id) => scrobble(id, null, false) @@ -74,7 +74,7 @@ const streamUrl = (id, options) => { url('stream', id, { ts: true, ...options, - }) + }), ) } diff --git a/ui/src/themes/useCurrentTheme.js b/ui/src/themes/useCurrentTheme.js index 61138d7b4..9793d1e15 100644 --- a/ui/src/themes/useCurrentTheme.js +++ b/ui/src/themes/useCurrentTheme.js @@ -14,7 +14,7 @@ const useCurrentTheme = () => { const themeName = Object.keys(themes).find((t) => t === state.theme) || Object.keys(themes).find( - (t) => themes[t].themeName === config.defaultTheme + (t) => themes[t].themeName === config.defaultTheme, ) || 'DarkTheme' return themes[themeName] diff --git a/ui/src/user/DeleteUserButton.js b/ui/src/user/DeleteUserButton.js index 11b8e7473..9bf133fb4 100644 --- a/ui/src/user/DeleteUserButton.js +++ b/ui/src/user/DeleteUserButton.js @@ -24,7 +24,7 @@ const useStyles = makeStyles( }, }, }), - { name: 'RaDeleteWithConfirmButton' } + { name: 'RaDeleteWithConfirmButton' }, ) const DeleteUserButton = (props) => { diff --git a/ui/src/user/UserCreate.js b/ui/src/user/UserCreate.js index 5d817dd01..42ea1ce94 100644 --- a/ui/src/user/UserCreate.js +++ b/ui/src/user/UserCreate.js @@ -33,7 +33,7 @@ const UserCreate = (props) => { resource: 'user', payload: { data: values }, }, - { returnPromise: true } + { returnPromise: true }, ) notify('resources.user.notifications.created', 'info', { smart_count: 1, @@ -45,7 +45,7 @@ const UserCreate = (props) => { } } }, - [mutate, notify, redirect] + [mutate, notify, redirect], ) return ( diff --git a/ui/src/user/UserEdit.js b/ui/src/user/UserEdit.js index 81883f0ce..9b3013961 100644 --- a/ui/src/user/UserEdit.js +++ b/ui/src/user/UserEdit.js @@ -85,7 +85,7 @@ const UserEdit = (props) => { resource: 'user', payload: { id: values.id, data: values }, }, - { returnPromise: true } + { returnPromise: true }, ) notify('resources.user.notifications.updated', 'info', { smart_count: 1, @@ -97,7 +97,7 @@ const UserEdit = (props) => { } } }, - [mutate, notify, permissions, redirect, refresh] + [mutate, notify, permissions, redirect, refresh], ) return ( diff --git a/ui/src/utils/formatters.test.js b/ui/src/utils/formatters.test.js index b3e898edc..59538ec32 100644 --- a/ui/src/utils/formatters.test.js +++ b/ui/src/utils/formatters.test.js @@ -25,7 +25,7 @@ describe('formatDuration', () => { it('formats days, hours and minutes', () => { expect(formatDuration(hour + minute + 1)).toEqual('01:01:01') expect(formatDuration(3 * day + 3 * hour + 7 * minute)).toEqual( - '3:03:07:00' + '3:03:07:00', ) expect(formatDuration(day)).toEqual('1:00:00:00') expect(formatDuration(day + minute + 0.6)).toEqual('1:00:01:01') diff --git a/ui/src/utils/intersperse.js b/ui/src/utils/intersperse.js index 01ff7f87c..ce48f5e86 100644 --- a/ui/src/utils/intersperse.js +++ b/ui/src/utils/intersperse.js @@ -15,6 +15,6 @@ export const intersperse = (arr, sep) => { function (xs, x, i) { return xs.concat([sep, x]) }, - [arr[0]] + [arr[0]], ) } diff --git a/ui/src/utils/urls.js b/ui/src/utils/urls.js index fe6c19c6f..e9173f089 100644 --- a/ui/src/utils/urls.js +++ b/ui/src/utils/urls.js @@ -10,7 +10,7 @@ export const baseUrl = (path) => { export const shareUrl = (id) => { const url = new URL( baseUrl(config.publicBaseUrl + '/' + id), - window.location.href + window.location.href, ) return url.href } From df3de047ca2ff392a04700bb05f998a000538aca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 19:24:39 +0000 Subject: [PATCH 09/27] Bump clsx from 1.1.1 to 2.0.0 in /ui Bumps [clsx](https://github.com/lukeed/clsx) from 1.1.1 to 2.0.0. - [Release notes](https://github.com/lukeed/clsx/releases) - [Commits](https://github.com/lukeed/clsx/compare/v1.1.1...v2.0.0) --- updated-dependencies: - dependency-name: clsx dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- ui/package-lock.json | 59 ++++++++++++++++++++++++++++++++++++++------ ui/package.json | 2 +- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 61f58658a..0855eea14 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -13,7 +13,7 @@ "@material-ui/lab": "^4.0.0-alpha.58", "@material-ui/styles": "^4.11.5", "blueimp-md5": "^2.19.0", - "clsx": "^1.1.1", + "clsx": "^2.0.0", "connected-react-router": "^6.9.1", "deepmerge": "^4.3.1", "history": "^4.10.1", @@ -3303,6 +3303,14 @@ } } }, + "node_modules/@material-ui/core/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/@material-ui/icons": { "version": "4.11.3", "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", @@ -3340,6 +3348,14 @@ "node": ">=8.0.0" } }, + "node_modules/@material-ui/lab/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/@material-ui/styles": { "version": "4.11.5", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz", @@ -3381,6 +3397,14 @@ } } }, + "node_modules/@material-ui/styles/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/@material-ui/system": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz", @@ -6282,9 +6306,9 @@ } }, "node_modules/clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", "engines": { "node": ">=6" } @@ -23185,6 +23209,13 @@ "prop-types": "^15.7.2", "react-is": "^16.8.0 || ^17.0.0", "react-transition-group": "^4.4.0" + }, + "dependencies": { + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + } } }, "@material-ui/icons": { @@ -23205,6 +23236,13 @@ "clsx": "^1.0.4", "prop-types": "^15.7.2", "react-is": "^16.8.0 || ^17.0.0" + }, + "dependencies": { + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + } } }, "@material-ui/styles": { @@ -23228,6 +23266,13 @@ "jss-plugin-rule-value-function": "^10.5.1", "jss-plugin-vendor-prefixer": "^10.5.1", "prop-types": "^15.7.2" + }, + "dependencies": { + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + } } }, "@material-ui/system": { @@ -25540,9 +25585,9 @@ } }, "clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" }, "co": { "version": "4.6.0", diff --git a/ui/package.json b/ui/package.json index d3e450a73..be71847f9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -8,7 +8,7 @@ "@material-ui/lab": "^4.0.0-alpha.58", "@material-ui/styles": "^4.11.5", "blueimp-md5": "^2.19.0", - "clsx": "^1.1.1", + "clsx": "^2.0.0", "connected-react-router": "^6.9.1", "deepmerge": "^4.3.1", "history": "^4.10.1", From 4ccc0a92bfb7d43ce98cbeddf3a933e016fbeb82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:28:42 -0500 Subject: [PATCH 10/27] Bump jwt-decode from 3.1.2 to 4.0.0 in /ui (#2714) * Bump jwt-decode from 3.1.2 to 4.0.0 in /ui Bumps [jwt-decode](https://github.com/auth0/jwt-decode) from 3.1.2 to 4.0.0. - [Release notes](https://github.com/auth0/jwt-decode/releases) - [Changelog](https://github.com/auth0/jwt-decode/blob/main/CHANGELOG.md) - [Commits](https://github.com/auth0/jwt-decode/compare/v3.1.2...v4.0.0) --- updated-dependencies: - dependency-name: jwt-decode dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Make jwt-decode a named import. --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Caio Cotts --- ui/package-lock.json | 17 ++++++++++------- ui/package.json | 2 +- ui/src/authProvider.js | 2 +- ui/src/dataProvider/httpClient.js | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 0855eea14..2ec04cee4 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -18,7 +18,7 @@ "deepmerge": "^4.3.1", "history": "^4.10.1", "inflection": "^1.13.1", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", "lodash.pick": "^4.4.0", "lodash.throttle": "^4.1.1", "navidrome-music-player": "4.25.1", @@ -13324,9 +13324,12 @@ } }, "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } }, "node_modules/keyv": { "version": "3.1.0", @@ -31000,9 +31003,9 @@ } }, "jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" }, "keyv": { "version": "3.1.0", diff --git a/ui/package.json b/ui/package.json index be71847f9..50f3a4879 100644 --- a/ui/package.json +++ b/ui/package.json @@ -13,7 +13,7 @@ "deepmerge": "^4.3.1", "history": "^4.10.1", "inflection": "^1.13.1", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", "lodash.pick": "^4.4.0", "lodash.throttle": "^4.1.1", "navidrome-music-player": "4.25.1", diff --git a/ui/src/authProvider.js b/ui/src/authProvider.js index 86d45b446..c22e9345d 100644 --- a/ui/src/authProvider.js +++ b/ui/src/authProvider.js @@ -1,4 +1,4 @@ -import jwtDecode from 'jwt-decode' +import { jwtDecode } from 'jwt-decode' import { baseUrl } from './utils' import config from './config' diff --git a/ui/src/dataProvider/httpClient.js b/ui/src/dataProvider/httpClient.js index 0e9f5433a..5ade3c0ce 100644 --- a/ui/src/dataProvider/httpClient.js +++ b/ui/src/dataProvider/httpClient.js @@ -2,7 +2,7 @@ import { fetchUtils } from 'react-admin' import { v4 as uuidv4 } from 'uuid' import { baseUrl } from '../utils' import config from '../config' -import jwtDecode from 'jwt-decode' +import { jwtDecode } from 'jwt-decode' const customAuthorizationHeader = 'X-ND-Authorization' const clientUniqueIdHeader = 'X-ND-Client-Unique-Id' From 92a88ad4d9eadb9e1e40d3ad6367a75a12cf716c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:45:37 -0500 Subject: [PATCH 11/27] Bump golang.org/x/crypto from 0.16.0 to 0.17.0 (#2722) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2203fe0e4..997781dc7 100644 --- a/go.mod +++ b/go.mod @@ -99,7 +99,7 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect go.uber.org/goleak v1.1.11 // indirect - golang.org/x/crypto v0.16.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp/shiny v0.0.0-20230515195305-f3d0a9c9a5cc // indirect golang.org/x/mobile v0.0.0-20230427221453-e8d11dd0ba41 // indirect golang.org/x/net v0.18.0 // indirect diff --git a/go.sum b/go.sum index 01369be22..e72048297 100644 --- a/go.sum +++ b/go.sum @@ -382,8 +382,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 6c28c111bb627374a4d60583d1735adacd363b7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 06:42:57 +0000 Subject: [PATCH 12/27] Bump @adobe/css-tools from 4.3.1 to 4.3.2 in /ui Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2. - [Changelog](https://github.com/adobe/css-tools/blob/main/History.md) - [Commits](https://github.com/adobe/css-tools/commits) --- updated-dependencies: - dependency-name: "@adobe/css-tools" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- ui/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 2ec04cee4..e56e19854 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -55,9 +55,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "node_modules/@ampproject/remapping": { @@ -20885,9 +20885,9 @@ }, "dependencies": { "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "@ampproject/remapping": { From 515efe37f0d520652dee8c15f03d5993c54683c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 17:10:48 +0000 Subject: [PATCH 13/27] Bump @testing-library/user-event from 13.5.0 to 14.5.1 in /ui Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.5.0 to 14.5.1. - [Release notes](https://github.com/testing-library/user-event/releases) - [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md) - [Commits](https://github.com/testing-library/user-event/compare/v13.5.0...v14.5.1) --- updated-dependencies: - dependency-name: "@testing-library/user-event" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- ui/package-lock.json | 23 +++++++++-------------- ui/package.json | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index e56e19854..7eb693436 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -46,7 +46,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^7.0.2", - "@testing-library/user-event": "^13.5.0", + "@testing-library/user-event": "^14.5.1", "css-mediaquery": "^0.1.2", "prettier": "3.1.1", "ra-test": "^3.19.12", @@ -4052,15 +4052,12 @@ } }, "node_modules/@testing-library/user-event": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", - "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "version": "14.5.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", + "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5" - }, "engines": { - "node": ">=10", + "node": ">=12", "npm": ">=6" }, "peerDependencies": { @@ -23772,13 +23769,11 @@ } }, "@testing-library/user-event": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", - "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "version": "14.5.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", + "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", "dev": true, - "requires": { - "@babel/runtime": "^7.12.5" - } + "requires": {} }, "@tootallnate/once": { "version": "1.1.2", diff --git a/ui/package.json b/ui/package.json index 50f3a4879..74786c085 100644 --- a/ui/package.json +++ b/ui/package.json @@ -41,7 +41,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^7.0.2", - "@testing-library/user-event": "^13.5.0", + "@testing-library/user-event": "^14.5.1", "css-mediaquery": "^0.1.2", "prettier": "3.1.1", "ra-test": "^3.19.12", From a6ed0442f2f4ccd7c6fe5d0efb9b6f7a9db05bc4 Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 20 Dec 2023 16:25:42 -0500 Subject: [PATCH 14/27] Name `mapDates` return values --- scanner/mapping.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scanner/mapping.go b/scanner/mapping.go index 734fd2e5e..d05cc5a8a 100644 --- a/scanner/mapping.go +++ b/scanner/mapping.go @@ -174,10 +174,13 @@ func (s mediaFileMapper) mapGenres(genres []string) (string, model.Genres) { return result[0].Name, result } -func (s mediaFileMapper) mapDates(md metadata.Tags) (int, string, int, string, int, string) { - year, date := md.Date() - originalYear, originalDate := md.OriginalDate() - releaseYear, releaseDate := md.ReleaseDate() +func (s mediaFileMapper) mapDates(md metadata.Tags) (year int, date string, + originalYear int, originalDate string, + releaseYear int, releaseDate string) { + // Start with defaults + year, date = md.Date() + originalYear, originalDate = md.OriginalDate() + releaseYear, releaseDate = md.ReleaseDate() // MusicBrainz Picard writes the Release Date of an album to the Date tag, and leaves the Release Date tag empty taggedLikePicard := (originalYear != 0) && From 781ff404649ff0addad914ef35ba2b7cf6ff0440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Wed, 20 Dec 2023 20:02:40 -0500 Subject: [PATCH 15/27] Bump Go version to 1.21.5 (#2729) --- .github/workflows/pipeline.yml | 6 +++--- Makefile | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 0d06ec1e0..a8c69daf3 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -126,7 +126,7 @@ jobs: path: ui/build - name: Config /github/workspace folder as trusted - uses: docker://deluan/ci-goreleaser:1.21.4-2 + uses: docker://deluan/ci-goreleaser:1.21.5-1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -134,7 +134,7 @@ jobs: - name: Run GoReleaser - SNAPSHOT if: startsWith(github.ref, 'refs/tags/') != true - uses: docker://deluan/ci-goreleaser:1.21.4-2 + uses: docker://deluan/ci-goreleaser:1.21.5-1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -142,7 +142,7 @@ jobs: - name: Run GoReleaser - RELEASE if: startsWith(github.ref, 'refs/tags/') - uses: docker://deluan/ci-goreleaser:1.21.4-2 + uses: docker://deluan/ci-goreleaser:1.21.5-1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/Makefile b/Makefile index 8b0b07f5e..09f46c554 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ GIT_SHA=source_archive GIT_TAG=$(patsubst navidrome-%,v%,$(notdir $(PWD))) endif -CI_RELEASER_VERSION=1.21.4-2 ## https://github.com/navidrome/ci-goreleaser +CI_RELEASER_VERSION=1.21.5-1 ## https://github.com/navidrome/ci-goreleaser setup: check_env download-deps setup-git ##@1_Run_First Install dependencies and prepare development environment @echo Downloading Node dependencies... From 965fc9d9be41ebc2fa8118d91beb2abdebe76a15 Mon Sep 17 00:00:00 2001 From: Dany Marcoux Date: Thu, 21 Dec 2023 14:00:31 +0100 Subject: [PATCH 16/27] Remove beep and the files where it was imported (#2731) Beep isn't needed anymore since we rely on MPV instead. The changes to `go.mod` and `go.sum` were done with: ``` go get github.com/faiface/beep@none go mod tidy ``` Signed-off-by: Dany Marcoux --- core/playback/beepaudio/decoder.go | 66 ------------ core/playback/beepaudio/track.go | 162 ----------------------------- core/playback/playbackserver.go | 2 +- go.mod | 9 -- go.sum | 40 ------- 5 files changed, 1 insertion(+), 278 deletions(-) delete mode 100644 core/playback/beepaudio/decoder.go delete mode 100644 core/playback/beepaudio/track.go diff --git a/core/playback/beepaudio/decoder.go b/core/playback/beepaudio/decoder.go deleted file mode 100644 index 17ce803f8..000000000 --- a/core/playback/beepaudio/decoder.go +++ /dev/null @@ -1,66 +0,0 @@ -//go:build beep - -package beepaudio - -import ( - "context" - "io" - "os" - - "github.com/faiface/beep" - "github.com/faiface/beep/flac" - "github.com/faiface/beep/mp3" - "github.com/faiface/beep/wav" - "github.com/navidrome/navidrome/core/ffmpeg" - "github.com/navidrome/navidrome/log" -) - -func DecodeMp3(path string) (s beep.StreamSeekCloser, format beep.Format, err error) { - f, err := os.Open(path) - if err != nil { - return nil, beep.Format{}, err - } - return mp3.Decode(f) -} - -func DecodeWAV(path string) (s beep.StreamSeekCloser, format beep.Format, err error) { - f, err := os.Open(path) - if err != nil { - return nil, beep.Format{}, err - } - return wav.Decode(f) -} - -func DecodeFLAC(path string) (s beep.StreamSeekCloser, format beep.Format, fileToCleanup string, err error) { - // TODO: Turn this into a semi-parallel operation: start playing while still transcoding/copying - log.Debug("decode to FLAC", "filename", path) - fFmpeg := ffmpeg.New() - readCloser, err := fFmpeg.ConvertToFLAC(context.TODO(), path) - if err != nil { - log.Error("error converting file to FLAC", path, err) - return nil, beep.Format{}, "", err - } - - tempFile, err := os.CreateTemp("", "*.flac") - - if err != nil { - log.Error("error creating temp file", err) - return nil, beep.Format{}, "", err - } - log.Debug("created tempfile", "filename", tempFile.Name()) - - written, err := io.Copy(tempFile, readCloser) - if err != nil { - log.Error("error coping file", "dest", tempFile.Name()) - } - log.Debug("copy pipe into tempfile", "bytes written", written, "filename", tempFile.Name()) - - f, err := os.Open(tempFile.Name()) - if err != nil { - log.Error("could not re-open tempfile", "filename", tempFile.Name()) - return nil, beep.Format{}, "", err - } - - s, format, err = flac.Decode(f) - return s, format, tempFile.Name(), err -} diff --git a/core/playback/beepaudio/track.go b/core/playback/beepaudio/track.go deleted file mode 100644 index 693607e86..000000000 --- a/core/playback/beepaudio/track.go +++ /dev/null @@ -1,162 +0,0 @@ -//go:build beep - -package beepaudio - -import ( - "fmt" - "os" - "time" - - "github.com/faiface/beep" - "github.com/faiface/beep/effects" - "github.com/faiface/beep/speaker" - "github.com/navidrome/navidrome/log" - "github.com/navidrome/navidrome/model" -) - -type BeepTrack struct { - MediaFile model.MediaFile - Ctrl *beep.Ctrl - Volume *effects.Volume - ActiveStream beep.StreamSeekCloser - TempfileToCleanup string - SampleRate beep.SampleRate - PlaybackDone chan bool -} - -func NewTrack(playbackDoneChannel chan bool, mf model.MediaFile) (*BeepTrack, error) { - t := BeepTrack{} - - contentType := mf.ContentType() - log.Debug("loading track", "trackname", mf.Path, "mediatype", contentType) - - var streamer beep.StreamSeekCloser - var format beep.Format - var err error - var tmpfileToCleanup = "" - - switch contentType { - case "audio/mpeg": - streamer, format, err = DecodeMp3(mf.Path) - case "audio/x-wav": - streamer, format, err = DecodeWAV(mf.Path) - case "audio/mp4": - streamer, format, tmpfileToCleanup, err = DecodeFLAC(mf.Path) - default: - return nil, fmt.Errorf("unsupported content type: %s", contentType) - } - - if err != nil { - log.Error(err) - return nil, err - } - - // save running stream for closing when switching tracks - t.ActiveStream = streamer - t.TempfileToCleanup = tmpfileToCleanup - - log.Debug("Setting up audio device") - t.Ctrl = &beep.Ctrl{Streamer: streamer, Paused: true} - t.Volume = &effects.Volume{Streamer: t.Ctrl, Base: 2} - t.SampleRate = format.SampleRate - t.PlaybackDone = playbackDoneChannel - t.MediaFile = mf - - err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) - if err != nil { - log.Error(err) - } - log.Debug("speaker.Init() finished") - - go func() { - speaker.Play(beep.Seq(t.Volume, beep.Callback(func() { - log.Info("Hitting end-of-stream, signalling on channel") - t.PlaybackDone <- true - log.Debug("Signalling finished") - }))) - log.Debug("dropping out of speaker.Play()") - }() - return &t, nil -} - -func (t *BeepTrack) String() string { - return fmt.Sprintf("Name: %s", t.MediaFile.Path) -} - -func (t *BeepTrack) SetVolume(value float64) { - speaker.Lock() - t.Volume.Volume += value - speaker.Unlock() -} - -func (t *BeepTrack) Unpause() { - speaker.Lock() - if t.Ctrl.Paused { - t.Ctrl.Paused = false - } else { - log.Debug("tried to unpause while not paused") - } - speaker.Unlock() -} - -func (t *BeepTrack) Pause() { - speaker.Lock() - if t.Ctrl.Paused { - log.Debug("tried to pause while already paused") - } else { - t.Ctrl.Paused = true - } - speaker.Unlock() -} - -func (t *BeepTrack) Close() { - if t.ActiveStream != nil { - log.Debug("closing activ stream") - t.ActiveStream.Close() - t.ActiveStream = nil - } - - speaker.Close() - - if t.TempfileToCleanup != "" { - log.Debug("Removing tempfile", "tmpfilename", t.TempfileToCleanup) - err := os.Remove(t.TempfileToCleanup) - if err != nil { - log.Error("error cleaning up tempfile: ", t.TempfileToCleanup) - } - } -} - -// Position returns the playback position in seconds -func (t *BeepTrack) Position() int { - if t.Ctrl.Streamer == nil { - log.Debug("streamer is not setup (nil), could not get position") - return 0 - } - - streamer, ok := t.Ctrl.Streamer.(beep.StreamSeeker) - if ok { - position := t.SampleRate.D(streamer.Position()) - posSecs := position.Round(time.Second).Seconds() - return int(posSecs) - } else { - log.Debug("streamer is no beep.StreamSeeker, could not get position") - return 0 - } -} - -// offset = pd.PlaybackQueue.Offset -func (t *BeepTrack) SetPosition(offset int) error { - streamer, ok := t.Ctrl.Streamer.(beep.StreamSeeker) - if ok { - sampleRatePerSecond := t.SampleRate.N(time.Second) - nextPosition := sampleRatePerSecond * offset - log.Debug("SetPosition", "samplerate", sampleRatePerSecond, "nextPosition", nextPosition) - return streamer.Seek(nextPosition) - } - return fmt.Errorf("streamer is not seekable") -} - -func (t *BeepTrack) IsPlaying() bool { - return t.Ctrl != nil && !t.Ctrl.Paused -} diff --git a/core/playback/playbackserver.go b/core/playback/playbackserver.go index c0b98ae72..3fbe3ced5 100644 --- a/core/playback/playbackserver.go +++ b/core/playback/playbackserver.go @@ -1,5 +1,5 @@ // Package playback implements audio playback using PlaybackDevices. It is used to implement the Jukebox mode in turn. -// It makes use of the BEEP library to do the playback. Major parts are: +// It makes use of the MPV library to do the playback. Major parts are: // - decoder which includes decoding and transcoding of various audio file formats // - device implementing the basic functions to work with audio devices like set, play, stop, skip, ... // - queue a simple playlist diff --git a/go.mod b/go.mod index 997781dc7..5b35aeabb 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/djherbis/stream v1.4.0 github.com/djherbis/times v1.6.0 github.com/dustin/go-humanize v1.0.1 - github.com/faiface/beep v1.1.0 github.com/fatih/structs v1.1.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 @@ -65,11 +64,8 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/hajimehoshi/go-mp3 v0.3.4 // indirect - github.com/hajimehoshi/oto v1.0.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/icza/bitio v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect @@ -81,11 +77,8 @@ require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mewkiz/flac v1.0.7 // indirect - github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect @@ -100,8 +93,6 @@ require ( github.com/subosito/gotenv v1.4.2 // indirect go.uber.org/goleak v1.1.11 // indirect golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp/shiny v0.0.0-20230515195305-f3d0a9c9a5cc // indirect - golang.org/x/mobile v0.0.0-20230427221453-e8d11dd0ba41 // indirect golang.org/x/net v0.18.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/tools v0.15.0 // indirect diff --git a/go.sum b/go.sum index e72048297..81be4a826 100644 --- a/go.sum +++ b/go.sum @@ -38,7 +38,6 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM= @@ -61,7 +60,6 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -94,19 +92,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c= -github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= -github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= -github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= -github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= @@ -198,14 +189,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= -github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68= -github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo= -github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= -github.com/hajimehoshi/oto v1.0.1 h1:8AMnq0Yr2YmzaiqTg/k1Yzd6IygUGk2we9nmjgbgPn4= -github.com/hajimehoshi/oto v1.0.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= -github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -216,14 +199,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8= -github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A= -github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k= -github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= -github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -257,7 +234,6 @@ github.com/lestrrat-go/jwx/v2 v2.0.18/go.mod h1:fAJ+k5eTgKdDqanzCuK6DAt3W7n3cs2/ github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= @@ -265,17 +241,12 @@ github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM= github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mewkiz/flac v1.0.7 h1:uIXEjnuXqdRaZttmSFM5v5Ukp4U6orrZsnYGGR3yow8= -github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU= -github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2 h1:EyTNMdePWaoWsRSGQnXiSoQu0r6RS1eA557AwJhlzHU= -github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/mileusna/useragent v1.3.4 h1:MiuRRuvGjEie1+yZHO88UBYg8YBC/ddF6T7F56i3PCk= @@ -289,8 +260,6 @@ github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8P github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -397,9 +366,6 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= -golang.org/x/exp/shiny v0.0.0-20230515195305-f3d0a9c9a5cc h1:JMi0oO0NoPZTAzHSdkdUoHbdcLfo9nPtK37kzE6I3Hk= -golang.org/x/exp/shiny v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= -golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -417,10 +383,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20230427221453-e8d11dd0ba41 h1:539vykMVJsmdiucRtMmdeLLZaTVhWhaAHFcPabj2lws= -golang.org/x/mobile v0.0.0-20230427221453-e8d11dd0ba41/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -500,12 +463,10 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -537,7 +498,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 00597e01e914a60850db76859602f6a870f37792 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 21 Dec 2023 16:32:37 -0500 Subject: [PATCH 17/27] Add `req.Params` to replace `utils.Param*` --- server/subsonic/api.go | 37 ++--- server/subsonic/helpers.go | 8 ++ server/subsonic/media_annotation.go | 39 +++--- utils/req/req.go | 140 +++++++++++++++++++ utils/req/req_test.go | 208 ++++++++++++++++++++++++++++ 5 files changed, 393 insertions(+), 39 deletions(-) create mode 100644 utils/req/req.go create mode 100644 utils/req/req_test.go diff --git a/server/subsonic/api.go b/server/subsonic/api.go index ae28e4ec0..467d1d7e1 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -19,6 +19,7 @@ import ( "github.com/navidrome/navidrome/server/events" "github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) const Version = "1.16.1" @@ -211,15 +212,6 @@ func hr(r chi.Router, path string, f handlerRaw) { handle := func(w http.ResponseWriter, r *http.Request) { res, err := f(w, r) if err != nil { - // If it is not a Subsonic error, convert it to an ErrorGeneric - var subErr subError - if !errors.As(err, &subErr) { - if errors.Is(err, model.ErrNotFound) { - err = newError(responses.ErrorDataNotFound, "data not found") - } else { - err = newError(responses.ErrorGeneric, fmt.Sprintf("Internal Server Error: %s", err)) - } - } sendError(w, r, err) return } @@ -264,15 +256,28 @@ func addHandler(r chi.Router, path string, handle func(w http.ResponseWriter, r r.HandleFunc("/"+path+".view", handle) } -func sendError(w http.ResponseWriter, r *http.Request, err error) { - response := newResponse() - code := responses.ErrorGeneric - var subErr subError - if errors.As(err, &subErr) { - code = subErr.code +func mapToSubsonicError(err error) subError { + switch { + case errors.Is(err, errSubsonic): // do nothing + case errors.Is(err, req.ErrMissingParam): + err = newError(responses.ErrorMissingParameter, err.Error()) + case errors.Is(err, req.ErrInvalidParam): + err = newError(responses.ErrorGeneric, err.Error()) + case errors.Is(err, model.ErrNotFound): + err = newError(responses.ErrorDataNotFound, "data not found") + default: + err = newError(responses.ErrorGeneric, fmt.Sprintf("Internal Server Error: %s", err)) } + var subErr subError + errors.As(err, &subErr) + return subErr +} + +func sendError(w http.ResponseWriter, r *http.Request, err error) { + subErr := mapToSubsonicError(err) + response := newResponse() response.Status = "failed" - response.Error = &responses.Error{Code: int32(code), Message: err.Error()} + response.Error = &responses.Error{Code: int32(subErr.code), Message: subErr.Error()} sendResponse(w, r, response) } diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index 87ace90b9..d006bd7a4 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -2,6 +2,7 @@ package subsonic import ( "context" + "errors" "fmt" "mime" "net/http" @@ -62,6 +63,13 @@ func newError(code int, message ...interface{}) error { } } +// errSubsonic and Unwrap are used to allow `errors.Is(err, errSubsonic)` to work +var errSubsonic = errors.New("subsonic API error") + +func (e subError) Unwrap() error { + return fmt.Errorf("%w: %d", errSubsonic, e.code) +} + func (e subError) Error() string { var msg string if len(e.messages) == 0 { diff --git a/server/subsonic/media_annotation.go b/server/subsonic/media_annotation.go index f6576b157..36c67373b 100644 --- a/server/subsonic/media_annotation.go +++ b/server/subsonic/media_annotation.go @@ -2,7 +2,6 @@ package subsonic import ( "context" - "errors" "fmt" "net/http" "time" @@ -13,26 +12,22 @@ import ( "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/server/events" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) SetRating(r *http.Request) (*responses.Subsonic, error) { - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } - rating, err := requiredParamInt(r, "rating") + rating, err := p.Int("rating") if err != nil { return nil, err } log.Debug(r, "Setting rating", "rating", rating, "id", id) err = api.setRating(r.Context(), id, rating) - - if errors.Is(err, model.ErrNotFound) { - log.Error(r, err) - return nil, newError(responses.ErrorDataNotFound, "ID not found") - } if err != nil { log.Error(r, err) return nil, err @@ -70,9 +65,10 @@ func (api *Router) setRating(ctx context.Context, id string, rating int) error { } func (api *Router) Star(r *http.Request) (*responses.Subsonic, error) { - ids := utils.ParamStrings(r, "id") - albumIds := utils.ParamStrings(r, "albumId") - artistIds := utils.ParamStrings(r, "artistId") + p := req.Params(r) + ids, _ := p.Strings("id") + albumIds, _ := p.Strings("albumId") + artistIds, _ := p.Strings("artistId") if len(ids)+len(albumIds)+len(artistIds) == 0 { return nil, newError(responses.ErrorMissingParameter, "Required id parameter is missing") } @@ -88,9 +84,10 @@ func (api *Router) Star(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) Unstar(r *http.Request) (*responses.Subsonic, error) { - ids := utils.ParamStrings(r, "id") - albumIds := utils.ParamStrings(r, "albumId") - artistIds := utils.ParamStrings(r, "artistId") + p := req.Params(r) + ids, _ := p.Strings("id") + albumIds, _ := p.Strings("albumId") + artistIds, _ := p.Strings("artistId") if len(ids)+len(albumIds)+len(artistIds) == 0 { return nil, newError(responses.ErrorMissingParameter, "Required id parameter is missing") } @@ -150,11 +147,6 @@ func (api *Router) setStar(ctx context.Context, star bool, ids ...string) error api.broker.SendMessage(ctx, event) return nil }) - - if errors.Is(err, model.ErrNotFound) { - log.Error(ctx, err) - return newError(responses.ErrorDataNotFound, "ID not found") - } if err != nil { log.Error(ctx, err) return err @@ -163,15 +155,16 @@ func (api *Router) setStar(ctx context.Context, star bool, ids ...string) error } func (api *Router) Scrobble(r *http.Request) (*responses.Subsonic, error) { - ids, err := requiredParamStrings(r, "id") + p := req.Params(r) + ids, err := p.Strings("id") if err != nil { return nil, err } - times := utils.ParamTimes(r, "time") + times := p.Times("time") if len(times) > 0 && len(times) != len(ids) { return nil, newError(responses.ErrorGeneric, "Wrong number of timestamps: %d, should be %d", len(times), len(ids)) } - submission := utils.ParamBool(r, "submission", true) + submission := p.BoolOr("submission", true) ctx := r.Context() if submission { diff --git a/utils/req/req.go b/utils/req/req.go new file mode 100644 index 000000000..a314556cc --- /dev/null +++ b/utils/req/req.go @@ -0,0 +1,140 @@ +package req + +import ( + "errors" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/utils" +) + +type Values struct { + *http.Request +} + +func Params(r *http.Request) *Values { + return &Values{r} +} + +var ( + ErrMissingParam = errors.New("missing parameter") + ErrInvalidParam = errors.New("invalid parameter") +) + +func newError(err error, param string) error { + return fmt.Errorf("%w: '%s'", err, param) +} +func (r *Values) String(param string) (string, error) { + v := r.URL.Query().Get(param) + if v == "" { + return "", newError(ErrMissingParam, param) + } + return v, nil +} + +func (r *Values) StringOr(param, def string) string { + v, _ := r.String(param) + if v == "" { + return def + } + return v +} + +func (r *Values) Strings(param string) ([]string, error) { + values := r.URL.Query()[param] + if len(values) == 0 { + return nil, newError(ErrMissingParam, param) + } + return values, nil +} + +func (r *Values) TimeOr(param string, def time.Time) time.Time { + v, _ := r.String(param) + if v == "" || v == "-1" { + return def + } + value, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return def + } + t := utils.ToTime(value) + if t.Before(time.Date(1970, time.January, 2, 0, 0, 0, 0, time.UTC)) { + return def + } + return t +} + +func (r *Values) Times(param string) []time.Time { + pStr, _ := r.Strings(param) + times := make([]time.Time, len(pStr)) + for i, t := range pStr { + ti, err := strconv.ParseInt(t, 10, 64) + if err != nil { + log.Warn(r.Context(), "Ignoring invalid time param", "time", t, err) + times[i] = time.Now() + continue + } + times[i] = utils.ToTime(ti) + } + return times +} + +func (r *Values) Int64(param string) (int64, error) { + v, err := r.String(param) + if err != nil { + return 0, err + } + value, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return 0, fmt.Errorf("%w '%s': expected integer, got '%s'", ErrInvalidParam, param, v) + } + return value, nil +} + +func (r *Values) Int(param string) (int, error) { + v, err := r.Int64(param) + if err != nil { + return 0, err + } + return int(v), nil +} + +func (r *Values) IntOr(param string, def int) int { + v, err := r.Int64(param) + if err != nil { + return def + } + return int(v) +} + +func (r *Values) Int64Or(param string, def int64) int64 { + v, err := r.Int64(param) + if err != nil { + return def + } + return v +} + +func (r *Values) Ints(param string) []int { + pStr, _ := r.Strings(param) + ints := make([]int, 0, len(pStr)) + for _, s := range pStr { + i, err := strconv.ParseInt(s, 10, 64) + if err == nil { + ints = append(ints, int(i)) + } + } + return ints +} + +func (r *Values) BoolOr(param string, def bool) bool { + v, _ := r.String(param) + if v == "" { + return def + } + return strings.Contains("/true/on/1/", "/"+strings.ToLower(v)+"/") +} diff --git a/utils/req/req_test.go b/utils/req/req_test.go new file mode 100644 index 000000000..0433ae6d9 --- /dev/null +++ b/utils/req/req_test.go @@ -0,0 +1,208 @@ +package req_test + +import ( + "fmt" + "net/http/httptest" + "testing" + "time" + + "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestUtils(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Request Helpers Suite") +} + +var _ = Describe("Request Helpers", func() { + var r *req.Values + + Describe("ParamString", func() { + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", "/ping?a=123", nil)) + }) + + It("returns param as string", func() { + Expect(r.String("a")).To(Equal("123")) + }) + + It("returns empty string if param does not exist", func() { + v, err := r.String("NON_EXISTENT_PARAM") + Expect(err).To(MatchError(req.ErrMissingParam)) + Expect(err.Error()).To(ContainSubstring("NON_EXISTENT_PARAM")) + Expect(v).To(BeEmpty()) + }) + }) + + Describe("ParamStringDefault", func() { + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", "/ping?a=123", nil)) + }) + + It("returns param as string", func() { + Expect(r.StringOr("a", "default_value")).To(Equal("123")) + }) + + It("returns default string if param does not exist", func() { + Expect(r.StringOr("xx", "default_value")).To(Equal("default_value")) + }) + }) + + Describe("ParamStrings", func() { + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", "/ping?a=123&a=456", nil)) + }) + + It("returns all param occurrences as []string", func() { + Expect(r.Strings("a")).To(Equal([]string{"123", "456"})) + }) + + It("returns empty array if param does not exist", func() { + v, err := r.Strings("xx") + Expect(err).To(MatchError(req.ErrMissingParam)) + Expect(v).To(BeEmpty()) + }) + }) + + Describe("ParamTime", func() { + d := time.Date(2002, 8, 9, 12, 11, 13, 1000000, time.Local) + t := utils.ToMillis(d) + now := time.Now() + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", fmt.Sprintf("/ping?t=%d&inv=abc", t), nil)) + }) + + It("returns parsed time", func() { + Expect(r.TimeOr("t", now)).To(Equal(d)) + }) + + It("returns default time if param does not exist", func() { + Expect(r.TimeOr("xx", now)).To(Equal(now)) + }) + + It("returns default time if param is an invalid timestamp", func() { + Expect(r.TimeOr("inv", now)).To(Equal(now)) + }) + }) + + Describe("ParamTimes", func() { + d1 := time.Date(2002, 8, 9, 12, 11, 13, 1000000, time.Local) + d2 := time.Date(2002, 8, 9, 12, 13, 56, 0000000, time.Local) + t1 := utils.ToMillis(d1) + t2 := utils.ToMillis(d2) + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", fmt.Sprintf("/ping?t=%d&t=%d", t1, t2), nil)) + }) + + It("returns all param occurrences as []time.Time", func() { + Expect(r.Times("t")).To(Equal([]time.Time{d1, d2})) + }) + + It("returns empty string if param does not exist", func() { + Expect(r.Times("xx")).To(BeEmpty()) + }) + + It("returns current time as default if param is invalid", func() { + now := time.Now() + r = req.Params(httptest.NewRequest("GET", "/ping?t=null", nil)) + times := r.Times("t") + Expect(times).To(HaveLen(1)) + Expect(times[0]).To(BeTemporally(">=", now)) + }) + }) + + Describe("ParamInt", func() { + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", "/ping?i=123&inv=123.45", nil)) + }) + Context("int", func() { + It("returns parsed int", func() { + Expect(r.IntOr("i", 999)).To(Equal(123)) + }) + + It("returns default value if param does not exist", func() { + Expect(r.IntOr("xx", 999)).To(Equal(999)) + }) + + It("returns default value if param is an invalid int", func() { + Expect(r.IntOr("inv", 999)).To(Equal(999)) + }) + }) + Context("int64", func() { + It("returns parsed int64", func() { + Expect(r.IntOr("i", 999)).To(Equal(123)) + }) + + It("returns default value if param does not exist", func() { + Expect(r.IntOr("xx", 999)).To(Equal(999)) + }) + + It("returns default value if param is an invalid int", func() { + Expect(r.IntOr("inv", 999)).To(Equal(999)) + }) + }) + }) + + Describe("ParamInts", func() { + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", "/ping?i=123&i=456", nil)) + }) + + It("returns array of occurrences found", func() { + Expect(r.Ints("i")).To(Equal([]int{123, 456})) + }) + + It("returns empty array if param does not exist", func() { + Expect(r.Ints("xx")).To(BeEmpty()) + }) + }) + + Describe("ParamBool", func() { + Context("value is true", func() { + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", "/ping?b=true&c=on&d=1&e=True", nil)) + }) + + It("parses 'true'", func() { + Expect(r.BoolOr("b", false)).To(BeTrue()) + }) + + It("parses 'on'", func() { + Expect(r.BoolOr("c", false)).To(BeTrue()) + }) + + It("parses '1'", func() { + Expect(r.BoolOr("d", false)).To(BeTrue()) + }) + + It("parses 'True'", func() { + Expect(r.BoolOr("e", false)).To(BeTrue()) + }) + }) + + Context("value is false", func() { + BeforeEach(func() { + r = req.Params(httptest.NewRequest("GET", "/ping?b=false&c=off&d=0", nil)) + }) + + It("parses 'false'", func() { + Expect(r.BoolOr("b", true)).To(BeFalse()) + }) + + It("parses 'off'", func() { + Expect(r.BoolOr("c", true)).To(BeFalse()) + }) + + It("parses '0'", func() { + Expect(r.BoolOr("d", true)).To(BeFalse()) + }) + + It("returns default value if param does not exist", func() { + Expect(r.BoolOr("xx", true)).To(BeTrue()) + }) + }) + }) +}) From dfcc189cffbea00bcbe283fcc88becb713748cce Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 21 Dec 2023 17:41:09 -0500 Subject: [PATCH 18/27] Replace all `utils.Param*` with `req.Params` --- core/agents/lastfm/auth_router.go | 13 +- server/nativeapi/playlists.go | 15 ++- server/public/handle_downloads.go | 12 +- server/public/handle_images.go | 12 +- server/public/handle_shares.go | 21 +-- server/public/handle_streams.go | 11 +- server/public/public.go | 14 +- server/subsonic/album_lists.go | 31 +++-- server/subsonic/album_lists_test.go | 22 ++-- server/subsonic/api.go | 6 +- server/subsonic/bookmarks.go | 19 +-- server/subsonic/browsing.go | 41 +++--- server/subsonic/helpers.go | 25 ---- server/subsonic/jukebox.go | 17 +-- server/subsonic/library_scanning.go | 5 +- server/subsonic/media_annotation.go | 2 +- server/subsonic/media_retrieval.go | 15 ++- server/subsonic/middlewares.go | 30 ++--- server/subsonic/playlists.go | 33 ++--- server/subsonic/radio.go | 21 +-- server/subsonic/searching.go | 17 +-- server/subsonic/sharing.go | 35 ++--- server/subsonic/stream.go | 20 +-- utils/req/req.go | 32 +++-- utils/req/req_test.go | 27 +++- utils/request_helpers.go | 90 ------------- utils/request_helpers_test.go | 196 ---------------------------- 27 files changed, 269 insertions(+), 513 deletions(-) delete mode 100644 utils/request_helpers.go delete mode 100644 utils/request_helpers_test.go diff --git a/core/agents/lastfm/auth_router.go b/core/agents/lastfm/auth_router.go index 372b5b632..ebcf7bcb7 100644 --- a/core/agents/lastfm/auth_router.go +++ b/core/agents/lastfm/auth_router.go @@ -18,7 +18,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/server" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) //go:embed token_received.html @@ -89,13 +89,14 @@ func (s *Router) unlink(w http.ResponseWriter, r *http.Request) { } func (s *Router) callback(w http.ResponseWriter, r *http.Request) { - token := utils.ParamString(r, "token") - if token == "" { + p := req.Params(r) + token, err := p.String("token") + if err != nil { _ = rest.RespondWithError(w, http.StatusBadRequest, "token not received") return } - uid := utils.ParamString(r, "uid") - if uid == "" { + uid, err := p.String("uid") + if err != nil { _ = rest.RespondWithError(w, http.StatusBadRequest, "uid not received") return } @@ -103,7 +104,7 @@ func (s *Router) callback(w http.ResponseWriter, r *http.Request) { // Need to add user to context, as this is a non-authenticated endpoint, so it does not // automatically contain any user info ctx := request.WithUser(r.Context(), model.User{ID: uid}) - err := s.fetchSessionKey(ctx, uid, token) + err = s.fetchSessionKey(ctx, uid, token) if err != nil { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusBadRequest) diff --git a/server/nativeapi/playlists.go b/server/nativeapi/playlists.go index 9abf80e4a..8f625aba1 100644 --- a/server/nativeapi/playlists.go +++ b/server/nativeapi/playlists.go @@ -14,7 +14,7 @@ import ( "github.com/navidrome/navidrome/core" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) type restHandler = func(rest.RepositoryConstructor, ...rest.Logger) http.HandlerFunc @@ -95,8 +95,9 @@ func handleExportPlaylist(ds model.DataStore) http.HandlerFunc { func deleteFromPlaylist(ds model.DataStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - playlistId := utils.ParamString(r, ":playlistId") - ids := r.URL.Query()["id"] + p := req.Params(r) + playlistId, _ := p.String(":playlistId") + ids, _ := p.Strings("id") err := ds.WithTx(func(tx model.DataStore) error { tracksRepo := tx.Playlist(r.Context()).Tracks(playlistId, true) return tracksRepo.Delete(ids...) @@ -139,7 +140,8 @@ func addToPlaylist(ds model.DataStore) http.HandlerFunc { } return func(w http.ResponseWriter, r *http.Request) { - playlistId := utils.ParamString(r, ":playlistId") + p := req.Params(r) + playlistId, _ := p.String(":playlistId") var payload addTracksPayload err := json.NewDecoder(r.Body).Decode(&payload) if err != nil { @@ -183,8 +185,9 @@ func reorderItem(ds model.DataStore) http.HandlerFunc { } return func(w http.ResponseWriter, r *http.Request) { - playlistId := utils.ParamString(r, ":playlistId") - id := utils.ParamInt(r, ":id", 0) + p := req.Params(r) + playlistId, _ := p.String(":playlistId") + id := p.IntOr(":id", 0) if id == 0 { http.Error(w, "invalid id", http.StatusBadRequest) return diff --git a/server/public/handle_downloads.go b/server/public/handle_downloads.go index c6cf5a52f..6aa35c341 100644 --- a/server/public/handle_downloads.go +++ b/server/public/handle_downloads.go @@ -2,15 +2,17 @@ package public import ( "net/http" + + "github.com/navidrome/navidrome/utils/req" ) -func (p *Router) handleDownloads(w http.ResponseWriter, r *http.Request) { - id := r.URL.Query().Get(":id") - if id == "" { - http.Error(w, "invalid id", http.StatusBadRequest) +func (pub *Router) handleDownloads(w http.ResponseWriter, r *http.Request) { + id, err := req.Params(r).String(":id") + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) return } - err := p.archiver.ZipShare(r.Context(), id, w) + err = pub.archiver.ZipShare(r.Context(), id, w) checkShareError(r.Context(), w, err, id) } diff --git a/server/public/handle_images.go b/server/public/handle_images.go index 7d73c8b95..2e6ee31a7 100644 --- a/server/public/handle_images.go +++ b/server/public/handle_images.go @@ -10,10 +10,10 @@ import ( "github.com/navidrome/navidrome/core/artwork" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) -func (p *Router) handleImages(w http.ResponseWriter, r *http.Request) { +func (pub *Router) handleImages(w http.ResponseWriter, r *http.Request) { // If context is already canceled, discard request without further processing if r.Context().Err() != nil { return @@ -21,7 +21,9 @@ func (p *Router) handleImages(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() - id := r.URL.Query().Get(":id") + + p := req.Params(r) + id, _ := p.String(":id") if id == "" { http.Error(w, "invalid id", http.StatusBadRequest) return @@ -32,9 +34,9 @@ func (p *Router) handleImages(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) return } - size := utils.ParamInt(r, "size", 0) + size := p.IntOr("size", 0) - imgReader, lastUpdate, err := p.artwork.Get(ctx, artId, size) + imgReader, lastUpdate, err := pub.artwork.Get(ctx, artId, size) switch { case errors.Is(err, context.Canceled): return diff --git a/server/public/handle_shares.go b/server/public/handle_shares.go index aa09cfa17..a4fa99d82 100644 --- a/server/public/handle_shares.go +++ b/server/public/handle_shares.go @@ -10,31 +10,32 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server" "github.com/navidrome/navidrome/ui" + "github.com/navidrome/navidrome/utils/req" ) -func (p *Router) handleShares(w http.ResponseWriter, r *http.Request) { - id := r.URL.Query().Get(":id") - if id == "" { - http.Error(w, "invalid id", http.StatusBadRequest) +func (pub *Router) handleShares(w http.ResponseWriter, r *http.Request) { + id, err := req.Params(r).String(":id") + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) return } // If requested file is a UI asset, just serve it - _, err := ui.BuildAssets().Open(id) + _, err = ui.BuildAssets().Open(id) if err == nil { - p.assetsHandler.ServeHTTP(w, r) + pub.assetsHandler.ServeHTTP(w, r) return } // If it is not, consider it a share ID - s, err := p.share.Load(r.Context(), id) + s, err := pub.share.Load(r.Context(), id) if err != nil { checkShareError(r.Context(), w, err, id) return } - s = p.mapShareInfo(r, *s) - server.IndexWithShare(p.ds, ui.BuildAssets(), s)(w, r) + s = pub.mapShareInfo(r, *s) + server.IndexWithShare(pub.ds, ui.BuildAssets(), s)(w, r) } func checkShareError(ctx context.Context, w http.ResponseWriter, err error, id string) { @@ -54,7 +55,7 @@ func checkShareError(ctx context.Context, w http.ResponseWriter, err error, id s } } -func (p *Router) mapShareInfo(r *http.Request, s model.Share) *model.Share { +func (pub *Router) mapShareInfo(r *http.Request, s model.Share) *model.Share { s.URL = ShareURL(r, s.ID) s.ImageURL = ImageURL(r, s.CoverArtID(), consts.UICoverArtSize) for i := range s.Tracks { diff --git a/server/public/handle_streams.go b/server/public/handle_streams.go index e9f60d7a5..c36e4b44d 100644 --- a/server/public/handle_streams.go +++ b/server/public/handle_streams.go @@ -10,12 +10,13 @@ import ( "github.com/lestrrat-go/jwx/v2/jwt" "github.com/navidrome/navidrome/core/auth" "github.com/navidrome/navidrome/log" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) -func (p *Router) handleStream(w http.ResponseWriter, r *http.Request) { +func (pub *Router) handleStream(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - tokenId := r.URL.Query().Get(":id") + p := req.Params(r) + tokenId, _ := p.String(":id") info, err := decodeStreamInfo(tokenId) if err != nil { log.Error(ctx, "Error parsing shared stream info", err) @@ -23,7 +24,7 @@ func (p *Router) handleStream(w http.ResponseWriter, r *http.Request) { return } - stream, err := p.streamer.NewStream(ctx, info.id, info.format, info.bitrate, 0) + stream, err := pub.streamer.NewStream(ctx, info.id, info.format, info.bitrate, 0) if err != nil { log.Error(ctx, "Error starting shared stream", err) http.Error(w, "invalid request", http.StatusInternalServerError) @@ -46,7 +47,7 @@ func (p *Router) handleStream(w http.ResponseWriter, r *http.Request) { w.Header().Set("Accept-Ranges", "none") w.Header().Set("Content-Type", stream.ContentType()) - estimateContentLength := utils.ParamBool(r, "estimateContentLength", false) + estimateContentLength := p.BoolOr("estimateContentLength", false) // if Client requests the estimated content-length, send it if estimateContentLength { diff --git a/server/public/public.go b/server/public/public.go index 5ead6b320..825e76335 100644 --- a/server/public/public.go +++ b/server/public/public.go @@ -35,7 +35,7 @@ func New(ds model.DataStore, artwork artwork.Artwork, streamer core.MediaStreame return p } -func (p *Router) routes() http.Handler { +func (pub *Router) routes() http.Handler { r := chi.NewRouter() r.Group(func(r chi.Router) { @@ -48,16 +48,16 @@ func (p *Router) routes() http.Handler { r.Use(middleware.ThrottleBacklog(conf.Server.DevArtworkMaxRequests, conf.Server.DevArtworkThrottleBacklogLimit, conf.Server.DevArtworkThrottleBacklogTimeout)) } - r.HandleFunc("/img/{id}", p.handleImages) + r.HandleFunc("/img/{id}", pub.handleImages) }) if conf.Server.EnableSharing { - r.HandleFunc("/s/{id}", p.handleStream) + r.HandleFunc("/s/{id}", pub.handleStream) if conf.Server.EnableDownloads { - r.HandleFunc("/d/{id}", p.handleDownloads) + r.HandleFunc("/d/{id}", pub.handleDownloads) } - r.HandleFunc("/{id}", p.handleShares) - r.HandleFunc("/", p.handleShares) - r.Handle("/*", p.assetsHandler) + r.HandleFunc("/{id}", pub.handleShares) + r.HandleFunc("/", pub.handleShares) + r.Handle("/*", pub.assetsHandler) } }) return r diff --git a/server/subsonic/album_lists.go b/server/subsonic/album_lists.go index 6fdabff1f..792553d42 100644 --- a/server/subsonic/album_lists.go +++ b/server/subsonic/album_lists.go @@ -10,12 +10,13 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server/subsonic/filter" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils/number" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) getAlbumList(r *http.Request) (model.Albums, int64, error) { - typ, err := requiredParamString(r, "type") + p := req.Params(r) + typ, err := p.String("type") if err != nil { return nil, 0, err } @@ -39,17 +40,17 @@ func (api *Router) getAlbumList(r *http.Request) (model.Albums, int64, error) { case "highest": opts = filter.AlbumsByRating() case "byGenre": - genre, err := requiredParamString(r, "genre") + genre, err := p.String("genre") if err != nil { return nil, 0, err } opts = filter.AlbumsByGenre(genre) case "byYear": - fromYear, err := requiredParamInt(r, "fromYear") + fromYear, err := p.Int("fromYear") if err != nil { return nil, 0, err } - toYear, err := requiredParamInt(r, "toYear") + toYear, err := p.Int("toYear") if err != nil { return nil, 0, err } @@ -59,8 +60,8 @@ func (api *Router) getAlbumList(r *http.Request) (model.Albums, int64, error) { return nil, 0, newError(responses.ErrorGeneric, "type '%s' not implemented", typ) } - opts.Offset = utils.ParamInt(r, "offset", 0) - opts.Max = number.Min(utils.ParamInt(r, "size", 10), 500) + opts.Offset = p.IntOr("offset", 0) + opts.Max = number.Min(p.IntOr("size", 10), 500) albums, err := api.ds.Album(r.Context()).GetAllWithoutGenres(opts) if err != nil { @@ -163,10 +164,11 @@ func (api *Router) GetNowPlaying(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) GetRandomSongs(r *http.Request) (*responses.Subsonic, error) { - size := number.Min(utils.ParamInt(r, "size", 10), 500) - genre := utils.ParamString(r, "genre") - fromYear := utils.ParamInt(r, "fromYear", 0) - toYear := utils.ParamInt(r, "toYear", 0) + p := req.Params(r) + size := number.Min(p.IntOr("size", 10), 500) + genre, _ := p.String("genre") + fromYear := p.IntOr("fromYear", 0) + toYear := p.IntOr("toYear", 0) songs, err := api.getSongs(r.Context(), 0, size, filter.SongsByRandom(genre, fromYear, toYear)) if err != nil { @@ -181,9 +183,10 @@ func (api *Router) GetRandomSongs(r *http.Request) (*responses.Subsonic, error) } func (api *Router) GetSongsByGenre(r *http.Request) (*responses.Subsonic, error) { - count := number.Min(utils.ParamInt(r, "count", 10), 500) - offset := utils.ParamInt(r, "offset", 0) - genre := utils.ParamString(r, "genre") + p := req.Params(r) + count := number.Min(p.IntOr("count", 10), 500) + offset := p.IntOr("offset", 0) + genre, _ := p.String("genre") songs, err := api.getSongs(r.Context(), offset, count, filter.SongsByGenre(genre)) if err != nil { diff --git a/server/subsonic/album_lists_test.go b/server/subsonic/album_lists_test.go index 85a994486..88f2e0aca 100644 --- a/server/subsonic/album_lists_test.go +++ b/server/subsonic/album_lists_test.go @@ -6,6 +6,7 @@ import ( "net/http/httptest" "github.com/navidrome/navidrome/server/subsonic/responses" + "github.com/navidrome/navidrome/utils/req" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" @@ -36,7 +37,7 @@ var _ = Describe("Album Lists", func() { }) resp, err := router.GetAlbumList(w, r) - Expect(err).To(BeNil()) + Expect(err).ToNot(HaveOccurred()) Expect(resp.AlbumList.Album[0].Id).To(Equal("1")) Expect(resp.AlbumList.Album[1].Id).To(Equal("2")) Expect(w.Header().Get("x-total-count")).To(Equal("2")) @@ -47,12 +48,8 @@ var _ = Describe("Album Lists", func() { It("should fail if missing type parameter", func() { r := newGetRequest() _, err := router.GetAlbumList(w, r) - var subErr subError - isSubError := errors.As(err, &subErr) - Expect(isSubError).To(BeTrue()) - Expect(subErr).To(MatchError("required 'type' parameter is missing")) - Expect(subErr.code).To(Equal(responses.ErrorMissingParameter)) + Expect(err).To(MatchError(req.ErrMissingParam)) }) It("should return error if call fails", func() { @@ -61,7 +58,7 @@ var _ = Describe("Album Lists", func() { _, err := router.GetAlbumList(w, r) - Expect(err).ToNot(BeNil()) + Expect(err).To(MatchError(errSubsonic)) var subErr subError errors.As(err, &subErr) Expect(subErr.code).To(Equal(responses.ErrorGeneric)) @@ -76,7 +73,7 @@ var _ = Describe("Album Lists", func() { }) resp, err := router.GetAlbumList2(w, r) - Expect(err).To(BeNil()) + Expect(err).ToNot(HaveOccurred()) Expect(resp.AlbumList2.Album[0].Id).To(Equal("1")) Expect(resp.AlbumList2.Album[1].Id).To(Equal("2")) Expect(w.Header().Get("x-total-count")).To(Equal("2")) @@ -86,13 +83,10 @@ var _ = Describe("Album Lists", func() { It("should fail if missing type parameter", func() { r := newGetRequest() + _, err := router.GetAlbumList2(w, r) - var subErr subError - errors.As(err, &subErr) - - Expect(subErr).To(MatchError("required 'type' parameter is missing")) - Expect(subErr.code).To(Equal(responses.ErrorMissingParameter)) + Expect(err).To(MatchError(req.ErrMissingParam)) }) It("should return error if call fails", func() { @@ -101,9 +95,9 @@ var _ = Describe("Album Lists", func() { _, err := router.GetAlbumList2(w, r) + Expect(err).To(MatchError(errSubsonic)) var subErr subError errors.As(err, &subErr) - Expect(subErr).ToNot(BeNil()) Expect(subErr.code).To(Equal(responses.ErrorGeneric)) }) }) diff --git a/server/subsonic/api.go b/server/subsonic/api.go index 467d1d7e1..640224915 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -18,7 +18,6 @@ import ( "github.com/navidrome/navidrome/scanner" "github.com/navidrome/navidrome/server/events" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils/req" ) @@ -283,7 +282,8 @@ func sendError(w http.ResponseWriter, r *http.Request, err error) { } func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Subsonic) { - f := utils.ParamString(r, "f") + p := req.Params(r) + f, _ := p.String("f") var response []byte switch f { case "json": @@ -292,7 +292,7 @@ func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub response, _ = json.Marshal(wrapper) case "jsonp": w.Header().Set("Content-Type", "application/javascript") - callback := utils.ParamString(r, "callback") + callback, _ := p.String("callback") wrapper := &responses.JsonWrapper{Subsonic: *payload} data, _ := json.Marshal(wrapper) response = []byte(fmt.Sprintf("%s(%s)", callback, data)) diff --git a/server/subsonic/bookmarks.go b/server/subsonic/bookmarks.go index 86d5d768b..42fe9e176 100644 --- a/server/subsonic/bookmarks.go +++ b/server/subsonic/bookmarks.go @@ -7,7 +7,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) GetBookmarks(r *http.Request) (*responses.Subsonic, error) { @@ -36,13 +36,14 @@ func (api *Router) GetBookmarks(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) CreateBookmark(r *http.Request) (*responses.Subsonic, error) { - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } - comment := utils.ParamString(r, "comment") - position := utils.ParamInt(r, "position", int64(0)) + comment, _ := p.String("comment") + position := p.Int64Or("position", 0) repo := api.ds.MediaFile(r.Context()) err = repo.AddBookmark(id, comment, position) @@ -53,7 +54,8 @@ func (api *Router) CreateBookmark(r *http.Request) (*responses.Subsonic, error) } func (api *Router) DeleteBookmark(r *http.Request) (*responses.Subsonic, error) { - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } @@ -88,13 +90,14 @@ func (api *Router) GetPlayQueue(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) SavePlayQueue(r *http.Request) (*responses.Subsonic, error) { - ids, err := requiredParamStrings(r, "id") + p := req.Params(r) + ids, err := p.Strings("id") if err != nil { return nil, err } - current := utils.ParamString(r, "current") - position := utils.ParamInt(r, "position", int64(0)) + current, _ := p.String("current") + position := p.Int64Or("position", 0) user, _ := request.UserFrom(r.Context()) client, _ := request.ClientFrom(r.Context()) diff --git a/server/subsonic/browsing.go b/server/subsonic/browsing.go index b33715799..88afa6d6d 100644 --- a/server/subsonic/browsing.go +++ b/server/subsonic/browsing.go @@ -14,6 +14,7 @@ import ( "github.com/navidrome/navidrome/server/subsonic/filter" "github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) GetMusicFolders(r *http.Request) (*responses.Subsonic, error) { @@ -67,8 +68,9 @@ func (api *Router) getArtistIndex(r *http.Request, mediaFolderId int, ifModified } func (api *Router) GetIndexes(r *http.Request) (*responses.Subsonic, error) { - musicFolderId := utils.ParamInt(r, "musicFolderId", 0) - ifModifiedSince := utils.ParamTime(r, "ifModifiedSince", time.Time{}) + p := req.Params(r) + musicFolderId := p.IntOr("musicFolderId", 0) + ifModifiedSince := p.TimeOr("ifModifiedSince", time.Time{}) res, err := api.getArtistIndex(r, musicFolderId, ifModifiedSince) if err != nil { @@ -81,7 +83,8 @@ func (api *Router) GetIndexes(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) GetArtists(r *http.Request) (*responses.Subsonic, error) { - musicFolderId := utils.ParamInt(r, "musicFolderId", 0) + p := req.Params(r) + musicFolderId := p.IntOr("musicFolderId", 0) res, err := api.getArtistIndex(r, musicFolderId, time.Time{}) if err != nil { return nil, err @@ -93,7 +96,8 @@ func (api *Router) GetArtists(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) GetMusicDirectory(r *http.Request) (*responses.Subsonic, error) { - id := utils.ParamString(r, "id") + p := req.Params(r) + id, _ := p.String("id") ctx := r.Context() entity, err := model.GetEntityByID(ctx, api.ds, id) @@ -129,7 +133,8 @@ func (api *Router) GetMusicDirectory(r *http.Request) (*responses.Subsonic, erro } func (api *Router) GetArtist(r *http.Request) (*responses.Subsonic, error) { - id := utils.ParamString(r, "id") + p := req.Params(r) + id, _ := p.String("id") ctx := r.Context() artist, err := api.ds.Artist(ctx).Get(id) @@ -151,7 +156,8 @@ func (api *Router) GetArtist(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) GetAlbum(r *http.Request) (*responses.Subsonic, error) { - id := utils.ParamString(r, "id") + p := req.Params(r) + id, _ := p.String("id") ctx := r.Context() @@ -177,7 +183,8 @@ func (api *Router) GetAlbum(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) GetAlbumInfo(r *http.Request) (*responses.Subsonic, error) { - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") ctx := r.Context() if err != nil { @@ -204,7 +211,8 @@ func (api *Router) GetAlbumInfo(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) GetSong(r *http.Request) (*responses.Subsonic, error) { - id := utils.ParamString(r, "id") + p := req.Params(r) + id, _ := p.String("id") ctx := r.Context() mf, err := api.ds.MediaFile(ctx).Get(id) @@ -243,12 +251,13 @@ func (api *Router) GetGenres(r *http.Request) (*responses.Subsonic, error) { func (api *Router) GetArtistInfo(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } - count := utils.ParamInt(r, "count", 20) - includeNotPresent := utils.ParamBool(r, "includeNotPresent", false) + count := p.IntOr("count", 20) + includeNotPresent := p.BoolOr("includeNotPresent", false) artist, err := api.externalMetadata.UpdateArtistInfo(ctx, id, count, includeNotPresent) if err != nil { @@ -295,11 +304,12 @@ func (api *Router) GetArtistInfo2(r *http.Request) (*responses.Subsonic, error) func (api *Router) GetSimilarSongs(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } - count := utils.ParamInt(r, "count", 50) + count := p.IntOr("count", 50) songs, err := api.externalMetadata.SimilarSongs(ctx, id, count) if err != nil { @@ -328,11 +338,12 @@ func (api *Router) GetSimilarSongs2(r *http.Request) (*responses.Subsonic, error func (api *Router) GetTopSongs(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - artist, err := requiredParamString(r, "artist") + p := req.Params(r) + artist, err := p.String("artist") if err != nil { return nil, err } - count := utils.ParamInt(r, "count", 50) + count := p.IntOr("count", 50) songs, err := api.externalMetadata.TopSongs(ctx, artist, count) if err != nil { diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index d006bd7a4..706a12c8b 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -14,7 +14,6 @@ import ( "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/server/public" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" ) func newResponse() *responses.Subsonic { @@ -27,30 +26,6 @@ func newResponse() *responses.Subsonic { } } -func requiredParamString(r *http.Request, param string) (string, error) { - p := utils.ParamString(r, param) - if p == "" { - return "", newError(responses.ErrorMissingParameter, "required '%s' parameter is missing", param) - } - return p, nil -} - -func requiredParamStrings(r *http.Request, param string) ([]string, error) { - ps := utils.ParamStrings(r, param) - if len(ps) == 0 { - return nil, newError(responses.ErrorMissingParameter, "required '%s' parameter is missing", param) - } - return ps, nil -} - -func requiredParamInt(r *http.Request, param string) (int, error) { - p := utils.ParamString(r, param) - if p == "" { - return 0, newError(responses.ErrorMissingParameter, "required '%s' parameter is missing", param) - } - return utils.ParamInt(r, param, 0), nil -} - type subError struct { code int messages []interface{} diff --git a/server/subsonic/jukebox.go b/server/subsonic/jukebox.go index 2a33fa1ea..b61d64294 100644 --- a/server/subsonic/jukebox.go +++ b/server/subsonic/jukebox.go @@ -7,7 +7,7 @@ import ( "github.com/navidrome/navidrome/core/playback" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) const ( @@ -27,8 +27,9 @@ const ( func (api *Router) JukeboxControl(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() user := getUser(ctx) + p := req.Params(r) - actionString, err := requiredParamString(r, "action") + actionString, err := p.String("action") if err != nil { return nil, err } @@ -58,31 +59,31 @@ func (api *Router) JukeboxControl(r *http.Request) (*responses.Subsonic, error) case ActionStatus: return createResponse(pb.Status(ctx)) case ActionSet: - ids := utils.ParamStrings(r, "id") + 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 := requiredParamInt(r, "index") + index, err := p.Int("index") if err != nil { return nil, newError(responses.ErrorMissingParameter, "missing parameter index, err: %s", err) } - offset := utils.ParamInt(r, "offset", 0) + offset := p.IntOr("offset", 0) if err != nil { offset = 0 } return createResponse(pb.Skip(ctx, index, offset)) case ActionAdd: - ids := utils.ParamStrings(r, "id") + ids, _ := p.Strings("id") return createResponse(pb.Add(ctx, ids)) case ActionClear: return createResponse(pb.Clear(ctx)) case ActionRemove: - index, err := requiredParamInt(r, "index") + index, err := p.Int("index") if err != nil { return nil, err } @@ -91,7 +92,7 @@ func (api *Router) JukeboxControl(r *http.Request) (*responses.Subsonic, error) case ActionShuffle: return createResponse(pb.Shuffle(ctx)) case ActionSetGain: - gainStr, err := requiredParamString(r, "gain") + gainStr, err := p.String("gain") if err != nil { return nil, newError(responses.ErrorMissingParameter, "missing parameter gain, err: %s", err) } diff --git a/server/subsonic/library_scanning.go b/server/subsonic/library_scanning.go index dc0838a93..2ef75887b 100644 --- a/server/subsonic/library_scanning.go +++ b/server/subsonic/library_scanning.go @@ -8,7 +8,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) GetScanStatus(r *http.Request) (*responses.Subsonic, error) { @@ -41,7 +41,8 @@ func (api *Router) StartScan(r *http.Request) (*responses.Subsonic, error) { return nil, newError(responses.ErrorAuthorizationFail) } - fullScan := utils.ParamBool(r, "fullScan", false) + p := req.Params(r) + fullScan := p.BoolOr("fullScan", false) go func() { start := time.Now() diff --git a/server/subsonic/media_annotation.go b/server/subsonic/media_annotation.go index 36c67373b..c9656d065 100644 --- a/server/subsonic/media_annotation.go +++ b/server/subsonic/media_annotation.go @@ -160,7 +160,7 @@ func (api *Router) Scrobble(r *http.Request) (*responses.Subsonic, error) { if err != nil { return nil, err } - times := p.Times("time") + times, _ := p.Times("time") if len(times) > 0 && len(times) != len(ids) { return nil, newError(responses.ErrorGeneric, "Wrong number of timestamps: %d, should be %d", len(times), len(ids)) } diff --git a/server/subsonic/media_retrieval.go b/server/subsonic/media_retrieval.go index 1190c6306..5d4383d62 100644 --- a/server/subsonic/media_retrieval.go +++ b/server/subsonic/media_retrieval.go @@ -15,15 +15,16 @@ import ( "github.com/navidrome/navidrome/resources" "github.com/navidrome/navidrome/server/subsonic/filter" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils/gravatar" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) GetAvatar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { if !conf.Server.EnableGravatar { return api.getPlaceHolderAvatar(w, r) } - username, err := requiredParamString(r, "username") + p := req.Params(r) + username, err := p.String("username") if err != nil { return nil, err } @@ -61,8 +62,9 @@ func (api *Router) GetCoverArt(w http.ResponseWriter, r *http.Request) (*respons ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) defer cancel() - id := utils.ParamString(r, "id") - size := utils.ParamInt(r, "size", 0) + p := req.Params(r) + id, _ := p.String("id") + size := p.IntOr("size", 0) imgReader, lastUpdate, err := api.artwork.GetOrPlaceholder(ctx, id, size) w.Header().Set("cache-control", "public, max-age=315360000") @@ -99,8 +101,9 @@ func isSynced(rawLyrics string) bool { } func (api *Router) GetLyrics(r *http.Request) (*responses.Subsonic, error) { - artist := utils.ParamString(r, "artist") - title := utils.ParamString(r, "title") + p := req.Params(r) + artist, _ := p.String("artist") + title, _ := p.String("title") response := newResponse() lyrics := responses.Lyrics{} response.Lyrics = &lyrics diff --git a/server/subsonic/middlewares.go b/server/subsonic/middlewares.go index 1efe61663..a5f368328 100644 --- a/server/subsonic/middlewares.go +++ b/server/subsonic/middlewares.go @@ -20,8 +20,8 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" . "github.com/navidrome/navidrome/utils/gg" + "github.com/navidrome/navidrome/utils/req" ) func postFormToQueryParams(next http.Handler) http.Handler { @@ -45,19 +45,18 @@ func postFormToQueryParams(next http.Handler) http.Handler { func checkRequiredParameters(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requiredParameters := []string{"u", "v", "c"} - - for _, p := range requiredParameters { - if utils.ParamString(r, p) == "" { - msg := fmt.Sprintf(`Missing required parameter "%s"`, p) - log.Warn(r, msg) - sendError(w, r, newError(responses.ErrorMissingParameter, msg)) + p := req.Params(r) + for _, param := range requiredParameters { + if _, err := p.String(param); err != nil { + log.Warn(r, err) + sendError(w, r, err) return } } - username := utils.ParamString(r, "u") - client := utils.ParamString(r, "c") - version := utils.ParamString(r, "v") + username, _ := p.String("u") + client, _ := p.String("c") + version, _ := p.String("v") ctx := r.Context() ctx = request.WithUsername(ctx, username) ctx = request.WithClient(ctx, client) @@ -73,12 +72,13 @@ func authenticate(ds model.DataStore) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - username := utils.ParamString(r, "u") + p := req.Params(r) + username, _ := p.String("u") - pass := utils.ParamString(r, "p") - token := utils.ParamString(r, "t") - salt := utils.ParamString(r, "s") - jwt := utils.ParamString(r, "jwt") + pass, _ := p.String("p") + token, _ := p.String("t") + salt, _ := p.String("s") + jwt, _ := p.String("jwt") usr, err := validateUser(ctx, ds, username, pass, token, salt, jwt) if errors.Is(err, model.ErrInvalidAuth) { diff --git a/server/subsonic/playlists.go b/server/subsonic/playlists.go index 2927f2197..4b01d5e8f 100644 --- a/server/subsonic/playlists.go +++ b/server/subsonic/playlists.go @@ -10,7 +10,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) GetPlaylists(r *http.Request) (*responses.Subsonic, error) { @@ -31,7 +31,8 @@ func (api *Router) GetPlaylists(r *http.Request) (*responses.Subsonic, error) { func (api *Router) GetPlaylist(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } @@ -84,9 +85,10 @@ func (api *Router) create(ctx context.Context, playlistId, name string, ids []st func (api *Router) CreatePlaylist(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - songIds := utils.ParamStrings(r, "songId") - playlistId := utils.ParamString(r, "playlistId") - name := utils.ParamString(r, "name") + 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") } @@ -99,7 +101,8 @@ func (api *Router) CreatePlaylist(r *http.Request) (*responses.Subsonic, error) } func (api *Router) DeletePlaylist(r *http.Request) (*responses.Subsonic, error) { - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } @@ -115,23 +118,23 @@ func (api *Router) DeletePlaylist(r *http.Request) (*responses.Subsonic, error) } func (api *Router) UpdatePlaylist(r *http.Request) (*responses.Subsonic, error) { - playlistId, err := requiredParamString(r, "playlistId") + p := req.Params(r) + playlistId, err := p.String("playlistId") if err != nil { return nil, err } - songsToAdd := utils.ParamStrings(r, "songIdToAdd") - songIndexesToRemove := utils.ParamInts(r, "songIndexToRemove") + songsToAdd, _ := p.Strings("songIdToAdd") + songIndexesToRemove, _ := p.Ints("songIndexToRemove") var plsName *string - if s, ok := r.URL.Query()["name"]; ok { - plsName = &s[0] + if s, err := p.String("name"); err == nil { + plsName = &s } var comment *string - if c, ok := r.URL.Query()["comment"]; ok { - comment = &c[0] + if s, err := p.String("comment"); err == nil { + comment = &s } var public *bool - if _, ok := r.URL.Query()["public"]; ok { - p := utils.ParamBool(r, "public", false) + if p, err := p.Bool("public"); err == nil { public = &p } diff --git a/server/subsonic/radio.go b/server/subsonic/radio.go index bc2497f54..9f2cd48f6 100644 --- a/server/subsonic/radio.go +++ b/server/subsonic/radio.go @@ -5,21 +5,22 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) CreateInternetRadio(r *http.Request) (*responses.Subsonic, error) { - streamUrl, err := requiredParamString(r, "streamUrl") + p := req.Params(r) + streamUrl, err := p.String("streamUrl") if err != nil { return nil, err } - name, err := requiredParamString(r, "name") + name, err := p.String("name") if err != nil { return nil, err } - homepageUrl := utils.ParamString(r, "homepageUrl") + homepageUrl, _ := p.String("homepageUrl") ctx := r.Context() radio := &model.Radio{ @@ -36,7 +37,8 @@ func (api *Router) CreateInternetRadio(r *http.Request) (*responses.Subsonic, er } func (api *Router) DeleteInternetRadio(r *http.Request) (*responses.Subsonic, error) { - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err @@ -75,22 +77,23 @@ func (api *Router) GetInternetRadios(r *http.Request) (*responses.Subsonic, erro } func (api *Router) UpdateInternetRadio(r *http.Request) (*responses.Subsonic, error) { - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } - streamUrl, err := requiredParamString(r, "streamUrl") + streamUrl, err := p.String("streamUrl") if err != nil { return nil, err } - name, err := requiredParamString(r, "name") + name, err := p.String("name") if err != nil { return nil, err } - homepageUrl := utils.ParamString(r, "homepageUrl") + homepageUrl, _ := p.String("homepageUrl") ctx := r.Context() radio := &model.Radio{ diff --git a/server/subsonic/searching.go b/server/subsonic/searching.go index d40d42beb..a2ba2d1cc 100644 --- a/server/subsonic/searching.go +++ b/server/subsonic/searching.go @@ -14,7 +14,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server/public" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) type searchParams struct { @@ -28,18 +28,19 @@ type searchParams struct { } func (api *Router) getParams(r *http.Request) (*searchParams, error) { + p := req.Params(r) var err error sp := &searchParams{} - sp.query, err = requiredParamString(r, "query") + sp.query, err = p.String("query") if err != nil { return nil, err } - sp.artistCount = utils.ParamInt(r, "artistCount", 20) - sp.artistOffset = utils.ParamInt(r, "artistOffset", 0) - sp.albumCount = utils.ParamInt(r, "albumCount", 20) - sp.albumOffset = utils.ParamInt(r, "albumOffset", 0) - sp.songCount = utils.ParamInt(r, "songCount", 20) - sp.songOffset = utils.ParamInt(r, "songOffset", 0) + sp.artistCount = p.IntOr("artistCount", 20) + sp.artistOffset = p.IntOr("artistOffset", 0) + sp.albumCount = p.IntOr("albumCount", 20) + sp.albumOffset = p.IntOr("albumOffset", 0) + sp.songCount = p.IntOr("songCount", 20) + sp.songOffset = p.IntOr("songOffset", 0) return sp, nil } diff --git a/server/subsonic/sharing.go b/server/subsonic/sharing.go index 792ecfc4b..02110b084 100644 --- a/server/subsonic/sharing.go +++ b/server/subsonic/sharing.go @@ -9,7 +9,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server/public" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) GetShares(r *http.Request) (*responses.Subsonic, error) { @@ -50,13 +50,14 @@ func (api *Router) buildShare(r *http.Request, share model.Share) responses.Shar } func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) { - ids := utils.ParamStrings(r, "id") - if len(ids) == 0 { - return nil, newError(responses.ErrorMissingParameter, "Required id parameter is missing") + p := req.Params(r) + ids, err := p.Strings("id") + if err != nil { + return nil, err } - description := utils.ParamString(r, "description") - expires := utils.ParamTime(r, "expires", time.Time{}) + description, _ := p.String("description") + expires := p.TimeOr("expires", time.Time{}) repo := api.share.NewRepository(r.Context()) share := &model.Share{ @@ -81,13 +82,14 @@ func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) UpdateShare(r *http.Request) (*responses.Subsonic, error) { - id := utils.ParamString(r, "id") - if id == "" { - return nil, newError(responses.ErrorMissingParameter, "Required id parameter is missing") + p := req.Params(r) + id, err := p.String("id") + if err != nil { + return nil, err } - description := utils.ParamString(r, "description") - expires := utils.ParamTime(r, "expires", time.Time{}) + description, _ := p.String("description") + expires := p.TimeOr("expires", time.Time{}) repo := api.share.NewRepository(r.Context()) share := &model.Share{ @@ -96,7 +98,7 @@ func (api *Router) UpdateShare(r *http.Request) (*responses.Subsonic, error) { ExpiresAt: expires, } - err := repo.(rest.Persistable).Update(id, share) + err = repo.(rest.Persistable).Update(id, share) if err != nil { return nil, err } @@ -105,13 +107,14 @@ func (api *Router) UpdateShare(r *http.Request) (*responses.Subsonic, error) { } func (api *Router) DeleteShare(r *http.Request) (*responses.Subsonic, error) { - id := utils.ParamString(r, "id") - if id == "" { - return nil, newError(responses.ErrorMissingParameter, "Required id parameter is missing") + 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) + err = repo.(rest.Persistable).Delete(id) if err != nil { return nil, err } diff --git a/server/subsonic/stream.go b/server/subsonic/stream.go index 1da20159b..07ce8e18a 100644 --- a/server/subsonic/stream.go +++ b/server/subsonic/stream.go @@ -14,7 +14,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/server/subsonic/responses" - "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/req" ) func (api *Router) serveStream(ctx context.Context, w http.ResponseWriter, r *http.Request, stream *core.Stream, id string) { @@ -25,7 +25,7 @@ func (api *Router) serveStream(ctx context.Context, w http.ResponseWriter, r *ht w.Header().Set("Accept-Ranges", "none") w.Header().Set("Content-Type", stream.ContentType()) - estimateContentLength := utils.ParamBool(r, "estimateContentLength", false) + estimateContentLength := req.Params(r).BoolOr("estimateContentLength", false) // if Client requests the estimated content-length, send it if estimateContentLength { @@ -51,13 +51,14 @@ func (api *Router) serveStream(ctx context.Context, w http.ResponseWriter, r *ht func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } - maxBitRate := utils.ParamInt(r, "maxBitRate", 0) - format := utils.ParamString(r, "format") - timeOffset := utils.ParamInt(r, "timeOffset", 0) + maxBitRate := p.IntOr("maxBitRate", 0) + format, _ := p.String("format") + timeOffset := p.IntOr("timeOffset", 0) stream, err := api.streamer.NewStream(ctx, id, format, maxBitRate, timeOffset) if err != nil { @@ -82,7 +83,8 @@ func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Su func (api *Router) Download(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() username, _ := request.UsernameFrom(ctx) - id, err := requiredParamString(r, "id") + p := req.Params(r) + id, err := p.String("id") if err != nil { return nil, err } @@ -97,8 +99,8 @@ func (api *Router) Download(w http.ResponseWriter, r *http.Request) (*responses. return nil, err } - maxBitRate := utils.ParamInt(r, "bitrate", 0) - format := utils.ParamString(r, "format") + maxBitRate := p.IntOr("bitrate", 0) + format, _ := p.String("format") if format == "" { if conf.Server.AutoTranscodeDownload { diff --git a/utils/req/req.go b/utils/req/req.go index a314556cc..54cb7e5fc 100644 --- a/utils/req/req.go +++ b/utils/req/req.go @@ -68,8 +68,11 @@ func (r *Values) TimeOr(param string, def time.Time) time.Time { return t } -func (r *Values) Times(param string) []time.Time { - pStr, _ := r.Strings(param) +func (r *Values) Times(param string) ([]time.Time, error) { + pStr, err := r.Strings(param) + if err != nil { + return nil, err + } times := make([]time.Time, len(pStr)) for i, t := range pStr { ti, err := strconv.ParseInt(t, 10, 64) @@ -80,7 +83,7 @@ func (r *Values) Times(param string) []time.Time { } times[i] = utils.ToTime(ti) } - return times + return times, nil } func (r *Values) Int64(param string) (int64, error) { @@ -119,8 +122,11 @@ func (r *Values) Int64Or(param string, def int64) int64 { return v } -func (r *Values) Ints(param string) []int { - pStr, _ := r.Strings(param) +func (r *Values) Ints(param string) ([]int, error) { + pStr, err := r.Strings(param) + if err != nil { + return nil, err + } ints := make([]int, 0, len(pStr)) for _, s := range pStr { i, err := strconv.ParseInt(s, 10, 64) @@ -128,13 +134,21 @@ func (r *Values) Ints(param string) []int { ints = append(ints, int(i)) } } - return ints + return ints, nil +} + +func (r *Values) Bool(param string) (bool, error) { + v, err := r.String(param) + if err != nil { + return false, err + } + return strings.Contains("/true/on/1/", "/"+strings.ToLower(v)+"/"), nil } func (r *Values) BoolOr(param string, def bool) bool { - v, _ := r.String(param) - if v == "" { + v, err := r.Bool(param) + if err != nil { return def } - return strings.Contains("/true/on/1/", "/"+strings.ToLower(v)+"/") + return v } diff --git a/utils/req/req_test.go b/utils/req/req_test.go index 0433ae6d9..5e305c413 100644 --- a/utils/req/req_test.go +++ b/utils/req/req_test.go @@ -102,13 +102,16 @@ var _ = Describe("Request Helpers", func() { }) It("returns empty string if param does not exist", func() { - Expect(r.Times("xx")).To(BeEmpty()) + v, err := r.Times("xx") + Expect(err).To(MatchError(req.ErrMissingParam)) + Expect(v).To(BeEmpty()) }) It("returns current time as default if param is invalid", func() { now := time.Now() r = req.Params(httptest.NewRequest("GET", "/ping?t=null", nil)) - times := r.Times("t") + times, err := r.Times("t") + Expect(err).ToNot(HaveOccurred()) Expect(times).To(HaveLen(1)) Expect(times[0]).To(BeTemporally(">=", now)) }) @@ -130,18 +133,28 @@ var _ = Describe("Request Helpers", func() { It("returns default value if param is an invalid int", func() { Expect(r.IntOr("inv", 999)).To(Equal(999)) }) + + It("returns error if param is an invalid int", func() { + _, err := r.Int("inv") + Expect(err).To(MatchError(req.ErrInvalidParam)) + }) }) Context("int64", func() { It("returns parsed int64", func() { - Expect(r.IntOr("i", 999)).To(Equal(123)) + Expect(r.Int64Or("i", 999)).To(Equal(int64(123))) }) It("returns default value if param does not exist", func() { - Expect(r.IntOr("xx", 999)).To(Equal(999)) + Expect(r.Int64Or("xx", 999)).To(Equal(int64(999))) }) It("returns default value if param is an invalid int", func() { - Expect(r.IntOr("inv", 999)).To(Equal(999)) + Expect(r.Int64Or("inv", 999)).To(Equal(int64(999))) + }) + + It("returns error if param is an invalid int", func() { + _, err := r.Int64("inv") + Expect(err).To(MatchError(req.ErrInvalidParam)) }) }) }) @@ -156,7 +169,9 @@ var _ = Describe("Request Helpers", func() { }) It("returns empty array if param does not exist", func() { - Expect(r.Ints("xx")).To(BeEmpty()) + v, err := r.Ints("xx") + Expect(err).To(MatchError(req.ErrMissingParam)) + Expect(v).To(BeEmpty()) }) }) diff --git a/utils/request_helpers.go b/utils/request_helpers.go deleted file mode 100644 index 104260de4..000000000 --- a/utils/request_helpers.go +++ /dev/null @@ -1,90 +0,0 @@ -package utils - -import ( - "net/http" - "strconv" - "strings" - "time" - - "github.com/navidrome/navidrome/log" - "golang.org/x/exp/constraints" -) - -func ParamString(r *http.Request, param string) string { - return r.URL.Query().Get(param) -} - -func ParamStringDefault(r *http.Request, param, def string) string { - v := ParamString(r, param) - if v == "" { - return def - } - return v -} - -func ParamStrings(r *http.Request, param string) []string { - return r.URL.Query()[param] -} - -func ParamTimes(r *http.Request, param string) []time.Time { - pStr := ParamStrings(r, param) - times := make([]time.Time, len(pStr)) - for i, t := range pStr { - ti, err := strconv.ParseInt(t, 10, 64) - if err != nil { - log.Warn(r.Context(), "Ignoring invalid time param", "time", t, err) - times[i] = time.Now() - continue - } - times[i] = ToTime(ti) - } - return times -} - -func ParamTime(r *http.Request, param string, def time.Time) time.Time { - v := ParamString(r, param) - if v == "" || v == "-1" { - return def - } - value, err := strconv.ParseInt(v, 10, 64) - if err != nil { - return def - } - t := ToTime(value) - if t.Before(time.Date(1970, time.January, 2, 0, 0, 0, 0, time.UTC)) { - return def - } - return t -} - -func ParamInt[T constraints.Integer](r *http.Request, param string, def T) T { - v := ParamString(r, param) - if v == "" { - return def - } - value, err := strconv.ParseInt(v, 10, 64) - if err != nil { - return def - } - return T(value) -} - -func ParamInts(r *http.Request, param string) []int { - pStr := ParamStrings(r, param) - ints := make([]int, 0, len(pStr)) - for _, s := range pStr { - i, err := strconv.ParseInt(s, 10, 32) - if err == nil { - ints = append(ints, int(i)) - } - } - return ints -} - -func ParamBool(r *http.Request, param string, def bool) bool { - p := strings.ToLower(ParamString(r, param)) - if p == "" { - return def - } - return strings.Contains("/true/on/1/", "/"+p+"/") -} diff --git a/utils/request_helpers_test.go b/utils/request_helpers_test.go deleted file mode 100644 index a7a82911d..000000000 --- a/utils/request_helpers_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package utils - -import ( - "fmt" - "net/http" - "net/http/httptest" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Request Helpers", func() { - var r *http.Request - - Describe("ParamString", func() { - BeforeEach(func() { - r = httptest.NewRequest("GET", "/ping?a=123", nil) - }) - - It("returns empty string if param does not exist", func() { - Expect(ParamString(r, "xx")).To(Equal("")) - }) - - It("returns param as string", func() { - Expect(ParamString(r, "a")).To(Equal("123")) - }) - }) - - Describe("ParamStringDefault", func() { - BeforeEach(func() { - r = httptest.NewRequest("GET", "/ping?a=123", nil) - }) - - It("returns default string if param does not exist", func() { - Expect(ParamStringDefault(r, "xx", "default_value")).To(Equal("default_value")) - }) - - It("returns param as string", func() { - Expect(ParamStringDefault(r, "a", "default_value")).To(Equal("123")) - }) - }) - - Describe("ParamStrings", func() { - BeforeEach(func() { - r = httptest.NewRequest("GET", "/ping?a=123&a=456", nil) - }) - - It("returns empty array if param does not exist", func() { - Expect(ParamStrings(r, "xx")).To(BeEmpty()) - }) - - It("returns all param occurrences as []string", func() { - Expect(ParamStrings(r, "a")).To(Equal([]string{"123", "456"})) - }) - }) - - Describe("ParamTime", func() { - d := time.Date(2002, 8, 9, 12, 11, 13, 1000000, time.Local) - t := ToMillis(d) - now := time.Now() - BeforeEach(func() { - r = httptest.NewRequest("GET", fmt.Sprintf("/ping?t=%d&inv=abc", t), nil) - }) - - It("returns default time if param does not exist", func() { - Expect(ParamTime(r, "xx", now)).To(Equal(now)) - }) - - It("returns default time if param is an invalid timestamp", func() { - Expect(ParamTime(r, "inv", now)).To(Equal(now)) - }) - - It("returns parsed time", func() { - Expect(ParamTime(r, "t", now)).To(Equal(d)) - }) - }) - - Describe("ParamTimes", func() { - d1 := time.Date(2002, 8, 9, 12, 11, 13, 1000000, time.Local) - d2 := time.Date(2002, 8, 9, 12, 13, 56, 0000000, time.Local) - t1 := ToMillis(d1) - t2 := ToMillis(d2) - BeforeEach(func() { - r = httptest.NewRequest("GET", fmt.Sprintf("/ping?t=%d&t=%d", t1, t2), nil) - }) - - It("returns empty string if param does not exist", func() { - Expect(ParamTimes(r, "xx")).To(BeEmpty()) - }) - - It("returns all param occurrences as []time.Time", func() { - Expect(ParamTimes(r, "t")).To(Equal([]time.Time{d1, d2})) - }) - It("returns current time as default if param is invalid", func() { - now := time.Now() - r = httptest.NewRequest("GET", "/ping?t=null", nil) - times := ParamTimes(r, "t") - Expect(times).To(HaveLen(1)) - Expect(times[0]).To(BeTemporally(">=", now)) - }) - }) - - Describe("ParamInt", func() { - BeforeEach(func() { - r = httptest.NewRequest("GET", "/ping?i=123&inv=123.45", nil) - }) - Context("int", func() { - It("returns default value if param does not exist", func() { - Expect(ParamInt(r, "xx", 999)).To(Equal(999)) - }) - - It("returns default value if param is an invalid int", func() { - Expect(ParamInt(r, "inv", 999)).To(Equal(999)) - }) - - It("returns parsed time", func() { - Expect(ParamInt(r, "i", 999)).To(Equal(123)) - }) - }) - Context("int64", func() { - It("returns default value if param does not exist", func() { - Expect(ParamInt(r, "xx", int64(999))).To(Equal(int64(999))) - }) - - It("returns default value if param is an invalid int", func() { - Expect(ParamInt(r, "inv", int64(999))).To(Equal(int64(999))) - }) - - It("returns parsed time", func() { - Expect(ParamInt(r, "i", int64(999))).To(Equal(int64(123))) - }) - - }) - }) - - Describe("ParamInts", func() { - BeforeEach(func() { - r = httptest.NewRequest("GET", "/ping?i=123&i=456", nil) - }) - - It("returns empty array if param does not exist", func() { - Expect(ParamInts(r, "xx")).To(BeEmpty()) - }) - - It("returns array of occurrences found", func() { - Expect(ParamInts(r, "i")).To(Equal([]int{123, 456})) - }) - }) - - Describe("ParamBool", func() { - Context("value is true", func() { - BeforeEach(func() { - r = httptest.NewRequest("GET", "/ping?b=true&c=on&d=1&e=True", nil) - }) - - It("parses 'true'", func() { - Expect(ParamBool(r, "b", false)).To(BeTrue()) - }) - - It("parses 'on'", func() { - Expect(ParamBool(r, "c", false)).To(BeTrue()) - }) - - It("parses '1'", func() { - Expect(ParamBool(r, "d", false)).To(BeTrue()) - }) - - It("parses 'True'", func() { - Expect(ParamBool(r, "e", false)).To(BeTrue()) - }) - }) - - Context("value is false", func() { - BeforeEach(func() { - r = httptest.NewRequest("GET", "/ping?b=false&c=off&d=0", nil) - }) - - It("returns default value if param does not exist", func() { - Expect(ParamBool(r, "xx", true)).To(BeTrue()) - }) - - It("parses 'false'", func() { - Expect(ParamBool(r, "b", true)).To(BeFalse()) - }) - - It("parses 'off'", func() { - Expect(ParamBool(r, "c", true)).To(BeFalse()) - }) - - It("parses '0'", func() { - Expect(ParamBool(r, "d", true)).To(BeFalse()) - }) - }) - }) -}) From 3f349b1b582b9d60a87a6cd05a84a6c7ad50bef5 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 21 Dec 2023 19:17:33 -0500 Subject: [PATCH 19/27] Add todo as a reminder to replace min/max in Go 1.22 --- utils/number/number.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/number/number.go b/utils/number/number.go index cc8aa7112..1ae4b9c59 100644 --- a/utils/number/number.go +++ b/utils/number/number.go @@ -7,6 +7,7 @@ import ( "golang.org/x/exp/constraints" ) +// TODO Remove on Go 1.22, in favor of builtin `min` function. func Min[T constraints.Ordered](vs ...T) T { if len(vs) == 0 { var zero T @@ -21,6 +22,7 @@ func Min[T constraints.Ordered](vs ...T) T { return min } +// TODO Remove on Go 1.22, in favor of builtin `max` function. func Max[T constraints.Ordered](vs ...T) T { if len(vs) == 0 { var zero T From 15e1394fa337fbb6d54f51d5358507bfba41a615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Fri, 22 Dec 2023 21:03:55 -0500 Subject: [PATCH 20/27] Implement `originalReleaseDate` in OpenSubsonic responses. (#2733) See https://github.com/opensubsonic/open-subsonic-api/pull/80 --- server/subsonic/helpers.go | 20 ++++++++++++++++++ server/subsonic/helpers_test.go | 11 ++++++++++ ...mWithSongsID3 with data should match .JSON | 5 +++++ ...umWithSongsID3 with data should match .XML | 1 + ...thSongsID3 without data should match .JSON | 3 ++- ...ithSongsID3 without data should match .XML | 4 +++- server/subsonic/responses/responses.go | 21 ++++++++++++------- server/subsonic/responses/responses_test.go | 3 ++- 8 files changed, 58 insertions(+), 10 deletions(-) diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index 706a12c8b..46d1ab310 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -7,6 +7,7 @@ import ( "mime" "net/http" "sort" + "strconv" "strings" "github.com/navidrome/navidrome/consts" @@ -244,6 +245,24 @@ func childrenFromAlbums(ctx context.Context, als model.Albums) []responses.Child return children } +// toItemDate converts a string date in the formats 'YYYY-MM-DD', 'YYYY-MM' or 'YYYY' to an OS ItemDate +func toItemDate(date string) responses.ItemDate { + itemDate := responses.ItemDate{} + if date == "" { + return itemDate + } + parts := strings.Split(date, "-") + if len(parts) > 2 { + itemDate.Day, _ = strconv.Atoi(parts[2]) + } + if len(parts) > 1 { + itemDate.Month, _ = strconv.Atoi(parts[1]) + } + itemDate.Year, _ = strconv.Atoi(parts[0]) + + return itemDate +} + func buildItemGenres(genres model.Genres) []responses.ItemGenre { itemGenres := make([]responses.ItemGenre, len(genres)) for i, g := range genres { @@ -301,5 +320,6 @@ func buildAlbumID3(ctx context.Context, album model.Album) responses.AlbumID3 { dir.MusicBrainzId = album.MbzAlbumID dir.IsCompilation = album.Compilation dir.SortName = album.SortAlbumName + dir.OriginalReleaseDate = toItemDate(album.OriginalDate) return dir } diff --git a/server/subsonic/helpers_test.go b/server/subsonic/helpers_test.go index 898ca2a6c..8b33ebda4 100644 --- a/server/subsonic/helpers_test.go +++ b/server/subsonic/helpers_test.go @@ -57,4 +57,15 @@ var _ = Describe("helpers", func() { Expect(buildDiscSubtitles(context.Background(), album)).To(Equal(expected)) }) }) + + DescribeTable("toItemDate", + func(date string, expected responses.ItemDate) { + Expect(toItemDate(date)).To(Equal(expected)) + }, + Entry("1994-02-04", "1994-02-04", responses.ItemDate{Year: 1994, Month: 2, Day: 4}), + Entry("1994-02", "1994-02", responses.ItemDate{Year: 1994, Month: 2}), + Entry("1994", "1994", responses.ItemDate{Year: 1994}), + Entry("19940201", "", responses.ItemDate{}), + Entry("", "", responses.ItemDate{}), + ) }) diff --git a/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 with data should match .JSON b/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 with data should match .JSON index 15ddbbcea..b7ba2d1dd 100644 --- a/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 with data should match .JSON @@ -31,6 +31,11 @@ "title": "disc 2" } ], + "originalReleaseDate": { + "year": 1994, + "month": 2, + "day": 4 + }, "song": [ { "id": "1", diff --git a/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 with data should match .XML b/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 with data should match .XML index cfe3da99d..fd37f83f9 100644 --- a/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 with data should match .XML @@ -4,6 +4,7 @@ + diff --git a/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 without data should match .JSON b/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 without data should match .JSON index 9508e14ba..8a5b87f24 100644 --- a/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 without data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 without data should match .JSON @@ -12,6 +12,7 @@ "musicBrainzId": "", "isCompilation": false, "sortName": "", - "discTitles": [] + "discTitles": [], + "originalReleaseDate": {} } } diff --git a/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 without data should match .XML b/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 without data should match .XML index 58e03c04b..2b748953a 100644 --- a/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 without data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses AlbumWithSongsID3 without data should match .XML @@ -1,3 +1,5 @@ - + + + diff --git a/server/subsonic/responses/responses.go b/server/subsonic/responses/responses.go index f93a5967b..eb5a8c883 100644 --- a/server/subsonic/responses/responses.go +++ b/server/subsonic/responses/responses.go @@ -218,13 +218,14 @@ type AlbumID3 struct { Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` // OpenSubsonic extensions - Played *time.Time `xml:"played,attr,omitempty" json:"played,omitempty"` - UserRating int32 `xml:"userRating,attr" json:"userRating"` - Genres ItemGenres `xml:"genres" json:"genres"` - MusicBrainzId string `xml:"musicBrainzId,attr" json:"musicBrainzId"` - IsCompilation bool `xml:"isCompilation,attr" json:"isCompilation"` - SortName string `xml:"sortName,attr" json:"sortName"` - DiscTitles DiscTitles `xml:"discTitles" json:"discTitles"` + Played *time.Time `xml:"played,attr,omitempty" json:"played,omitempty"` + UserRating int32 `xml:"userRating,attr" json:"userRating"` + Genres ItemGenres `xml:"genres" json:"genres"` + MusicBrainzId string `xml:"musicBrainzId,attr" json:"musicBrainzId"` + IsCompilation bool `xml:"isCompilation,attr" json:"isCompilation"` + SortName string `xml:"sortName,attr" json:"sortName"` + DiscTitles DiscTitles `xml:"discTitles" json:"discTitles"` + OriginalReleaseDate ItemDate `xml:"originalReleaseDate" json:"originalReleaseDate"` } type ArtistWithAlbumsID3 struct { @@ -492,3 +493,9 @@ func marshalJSONArray[T any](v []T) ([]byte, error) { a := v return json.Marshal(a) } + +type ItemDate struct { + Year int `xml:"year,attr,omitempty" json:"year,omitempty"` + Month int `xml:"month,attr,omitempty" json:"month,omitempty"` + Day int `xml:"day,attr,omitempty" json:"day,omitempty"` +} diff --git a/server/subsonic/responses/responses_test.go b/server/subsonic/responses/responses_test.go index 17ea9ec3d..47804d6ba 100644 --- a/server/subsonic/responses/responses_test.go +++ b/server/subsonic/responses/responses_test.go @@ -176,7 +176,8 @@ var _ = Describe("Responses", func() { Id: "1", Name: "album", Artist: "artist", Genre: "rock", Genres: []ItemGenre{{Name: "rock"}, {Name: "progressive"}}, MusicBrainzId: "1234", IsCompilation: true, SortName: "sorted album", - DiscTitles: DiscTitles{{Disc: 1, Title: "disc 1"}, {Disc: 2, Title: "disc 2"}}, + DiscTitles: DiscTitles{{Disc: 1, Title: "disc 1"}, {Disc: 2, Title: "disc 2"}}, + OriginalReleaseDate: ItemDate{Year: 1994, Month: 2, Day: 4}, } t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC) songs := []Child{{ From 03119e5ccfaebc6a618831bb7c312e5ce007ed2b Mon Sep 17 00:00:00 2001 From: Deluan Date: Sat, 23 Dec 2023 14:10:38 -0500 Subject: [PATCH 21/27] Add more trace log to TagLib Wrapper --- scanner/metadata/taglib/taglib_wrapper.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/scanner/metadata/taglib/taglib_wrapper.go b/scanner/metadata/taglib/taglib_wrapper.go index 2f866a530..ada16ed05 100644 --- a/scanner/metadata/taglib/taglib_wrapper.go +++ b/scanner/metadata/taglib/taglib_wrapper.go @@ -11,6 +11,7 @@ package taglib */ import "C" import ( + "encoding/json" "fmt" "os" "runtime/debug" @@ -39,6 +40,7 @@ func Read(filename string) (tags map[string][]string, err error) { id, m := newMap() defer deleteMap(id) + log.Trace("TagLib: reading tags", "filename", filename, "map_id", id) res := C.taglib_read(fp, C.ulong(id)) switch res { case C.TAGLIB_ERR_PARSE: @@ -56,7 +58,13 @@ func Read(filename string) (tags map[string][]string, err error) { case C.TAGLIB_ERR_AUDIO_PROPS: return nil, fmt.Errorf("can't get audio properties from file") } - log.Trace("TagLib: read tags", "tags", m, "filename", filename, "id", id) + if log.CurrentLevel() >= log.LevelDebug { + j, _ := json.Marshal(m) + log.Trace("TagLib: read tags", "tags", string(j), "filename", filename, "id", id) + } else { + log.Trace("TagLib: read tags", "tags", m, "filename", filename, "id", id) + } + return m, nil } @@ -107,6 +115,10 @@ func do_put_map(id C.ulong, key string, val *C.char) { m[key] = append(m[key], v) } +/* +As I'm working on the new scanner, I see that the `properties` from TagLib is ill-suited to extract multi-valued ID3 frames. I'll have to change the way we do it for ID3, probably by sending the raw frames to Go and mapping there, instead of relying on the auto-mapped `properties`. I think this would reduce our reliance on C++, while also giving us more flexibility, including parsing the USLT / SYLT frames in Go +*/ + //export go_map_put_int func go_map_put_int(id C.ulong, key *C.char, val C.int) { valStr := strconv.Itoa(int(val)) From 51e07d4cb5dac62aae3fe54356678c85c6adb5d1 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 25 Dec 2023 16:29:59 -0500 Subject: [PATCH 22/27] Add log.IsGreaterOrEqualTo, that take into consideration path-scoped log levels --- conf/configuration.go | 2 +- core/agents/agents.go | 2 +- core/archiver.go | 2 +- core/ffmpeg/ffmpeg.go | 2 +- core/playback/mpv/mpv.go | 2 +- log/log.go | 6 +++++ log/log_test.go | 31 +++++++++++++++++++++++ scanner/metadata/taglib/taglib_wrapper.go | 2 +- server/events/sse.go | 14 ++++++---- server/middlewares.go | 2 +- server/public/handle_streams.go | 4 +-- server/subsonic/api.go | 4 +-- server/subsonic/stream.go | 6 ++--- 13 files changed, 60 insertions(+), 19 deletions(-) diff --git a/conf/configuration.go b/conf/configuration.go index 1af859558..eb0222abe 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -205,7 +205,7 @@ func Load() { } // Print current configuration if log level is Debug - if log.CurrentLevel() >= log.LevelDebug { + if log.IsGreaterOrEqualTo(log.LevelDebug) { prettyConf := pretty.Sprintf("Loaded configuration from '%s': %# v", Server.ConfigFile, Server) if Server.EnableLogRedacting { prettyConf = log.Redact(prettyConf) diff --git a/core/agents/agents.go b/core/agents/agents.go index 1f9b8c0dd..0a11297c3 100644 --- a/core/agents/agents.go +++ b/core/agents/agents.go @@ -134,7 +134,7 @@ func (a *Agents) GetSimilarArtists(ctx context.Context, id, name, mbid string, l } similar, err := agent.GetSimilarArtists(ctx, id, name, mbid, limit) if len(similar) > 0 && err == nil { - if log.CurrentLevel() >= log.LevelTrace { + if log.IsGreaterOrEqualTo(log.LevelTrace) { log.Debug(ctx, "Got Similar Artists", "agent", ag.AgentName(), "artist", name, "similar", similar, "elapsed", time.Since(start)) } else { log.Debug(ctx, "Got Similar Artists", "agent", ag.AgentName(), "artist", name, "similarReceived", len(similar), "elapsed", time.Since(start)) diff --git a/core/archiver.go b/core/archiver.go index 2ca155be2..c48f292f9 100644 --- a/core/archiver.go +++ b/core/archiver.go @@ -160,7 +160,7 @@ func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.Med } defer func() { - if err := r.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug { + if err := r.Close(); err != nil && log.IsGreaterOrEqualTo(log.LevelDebug) { log.Error(ctx, "Error closing stream", "id", mf.ID, "file", mf.Path, err) } }() diff --git a/core/ffmpeg/ffmpeg.go b/core/ffmpeg/ffmpeg.go index 628f41986..f81435e09 100644 --- a/core/ffmpeg/ffmpeg.go +++ b/core/ffmpeg/ffmpeg.go @@ -100,7 +100,7 @@ type ffCmd struct { func (j *ffCmd) start() error { cmd := exec.Command(j.args[0], j.args[1:]...) // #nosec cmd.Stdout = j.out - if log.CurrentLevel() >= log.LevelTrace { + if log.IsGreaterOrEqualTo(log.LevelTrace) { cmd.Stderr = os.Stderr } else { cmd.Stderr = io.Discard diff --git a/core/playback/mpv/mpv.go b/core/playback/mpv/mpv.go index 85505f11d..957d779ce 100644 --- a/core/playback/mpv/mpv.go +++ b/core/playback/mpv/mpv.go @@ -54,7 +54,7 @@ func (j *Executor) start() error { j.ctx = ctx cmd := exec.CommandContext(ctx, j.args[0], j.args[1:]...) // #nosec cmd.Stdout = j.out - if log.CurrentLevel() >= log.LevelTrace { + if log.IsGreaterOrEqualTo(log.LevelTrace) { cmd.Stderr = os.Stderr } else { cmd.Stderr = io.Discard diff --git a/log/log.go b/log/log.go index f4b58aae8..c1d058453 100644 --- a/log/log.go +++ b/log/log.go @@ -106,6 +106,7 @@ func levelFromString(l string) Level { return level } +// SetLogLevels sets the log levels for specific paths in the codebase. func SetLogLevels(levels map[string]string) { for k, v := range levels { logLevels = append(logLevels, levelPath{path: k, level: levelFromString(v)}) @@ -154,6 +155,11 @@ func CurrentLevel() Level { return currentLevel } +// IsGreaterOrEqualTo returns true if the caller's current log level is equal or greater than the provided level. +func IsGreaterOrEqualTo(level Level) bool { + return shouldLog(level) +} + func Fatal(args ...interface{}) { log(LevelFatal, args...) os.Exit(1) diff --git a/log/log_test.go b/log/log_test.go index 545803a79..e54fa95fe 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -137,6 +137,37 @@ var _ = Describe("Logger", func() { }) }) + Describe("IsGreaterOrEqualTo", func() { + It("returns false if log level is below provided level", func() { + SetLevel(LevelError) + Expect(IsGreaterOrEqualTo(LevelWarn)).To(BeFalse()) + }) + + It("returns true if log level is equal to provided level", func() { + SetLevel(LevelWarn) + Expect(IsGreaterOrEqualTo(LevelWarn)).To(BeTrue()) + }) + + It("returns true if log level is above provided level", func() { + SetLevel(LevelTrace) + Expect(IsGreaterOrEqualTo(LevelDebug)).To(BeTrue()) + }) + + It("returns true if log level for the current code path is equal provided level", func() { + SetLevel(LevelError) + SetLogLevels(map[string]string{ + "log/log_test": "debug", + }) + + // Need to nest it in a function to get the correct code path + var result = func() bool { + return IsGreaterOrEqualTo(LevelDebug) + }() + + Expect(result).To(BeTrue()) + }) + }) + Describe("extractLogger", func() { It("returns an error if the context is nil", func() { _, err := extractLogger(nil) diff --git a/scanner/metadata/taglib/taglib_wrapper.go b/scanner/metadata/taglib/taglib_wrapper.go index ada16ed05..c631d0b20 100644 --- a/scanner/metadata/taglib/taglib_wrapper.go +++ b/scanner/metadata/taglib/taglib_wrapper.go @@ -58,7 +58,7 @@ func Read(filename string) (tags map[string][]string, err error) { case C.TAGLIB_ERR_AUDIO_PROPS: return nil, fmt.Errorf("can't get audio properties from file") } - if log.CurrentLevel() >= log.LevelDebug { + if log.IsGreaterOrEqualTo(log.LevelDebug) { j, _ := json.Marshal(m) log.Trace("TagLib: read tags", "tags", string(j), "filename", filename, "id", id) } else { diff --git a/server/events/sse.go b/server/events/sse.go index 23720169f..b9285b27c 100644 --- a/server/events/sse.go +++ b/server/events/sse.go @@ -42,15 +42,13 @@ type ( username string userAgent string clientUniqueId string + displayString string msgC chan message } ) func (c client) String() string { - if log.CurrentLevel() >= log.LevelTrace { - return fmt.Sprintf("%s (%s - %s - %s - %s)", c.id, c.username, c.address, c.clientUniqueId, c.userAgent) - } - return fmt.Sprintf("%s (%s - %s - %s)", c.id, c.username, c.address, c.clientUniqueId) + return c.displayString } type broker struct { @@ -172,6 +170,12 @@ func (b *broker) subscribe(r *http.Request) client { userAgent: r.UserAgent(), clientUniqueId: clientUniqueId, } + if log.IsGreaterOrEqualTo(log.LevelTrace) { + c.displayString = fmt.Sprintf("%s (%s - %s - %s - %s)", c.id, c.username, c.address, c.clientUniqueId, c.userAgent) + } else { + c.displayString = fmt.Sprintf("%s (%s - %s - %s)", c.id, c.username, c.address, c.clientUniqueId) + } + c.msgC = make(chan message, bufferSize) // Signal the broker that we have a new client @@ -260,7 +264,7 @@ func sendOrDrop(client client, msg message) { select { case client.msgC <- msg: default: - if log.CurrentLevel() >= log.LevelTrace { + if log.IsGreaterOrEqualTo(log.LevelTrace) { log.Trace("Event dropped because client's channel is full", "event", msg, "client", client.String()) } } diff --git a/server/middlewares.go b/server/middlewares.go index cdbefacb8..7f912770b 100644 --- a/server/middlewares.go +++ b/server/middlewares.go @@ -42,7 +42,7 @@ func requestLogger(next http.Handler) http.Handler { "httpStatus", ww.Status(), "responseSize", ww.BytesWritten(), } - if log.CurrentLevel() >= log.LevelDebug { + if log.IsGreaterOrEqualTo(log.LevelDebug) { logArgs = append(logArgs, "userAgent", r.UserAgent()) } diff --git a/server/public/handle_streams.go b/server/public/handle_streams.go index c36e4b44d..cf120f0b5 100644 --- a/server/public/handle_streams.go +++ b/server/public/handle_streams.go @@ -32,7 +32,7 @@ func (pub *Router) handleStream(w http.ResponseWriter, r *http.Request) { // Make sure the stream will be closed at the end, to avoid leakage defer func() { - if err := stream.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug { + if err := stream.Close(); err != nil && log.IsGreaterOrEqualTo(log.LevelDebug) { log.Error("Error closing shared stream", "id", info.id, "file", stream.Name(), err) } }() @@ -60,7 +60,7 @@ func (pub *Router) handleStream(w http.ResponseWriter, r *http.Request) { go func() { _, _ = io.Copy(io.Discard, stream) }() } else { c, err := io.Copy(w, stream) - if log.CurrentLevel() >= log.LevelDebug { + if log.IsGreaterOrEqualTo(log.LevelDebug) { if err != nil { log.Error(ctx, "Error sending shared transcoded file", "id", info.id, err) } else { diff --git a/server/subsonic/api.go b/server/subsonic/api.go index 640224915..63064b306 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -215,7 +215,7 @@ func hr(r chi.Router, path string, f handlerRaw) { return } if r.Context().Err() != nil { - if log.CurrentLevel() >= log.LevelDebug { + if log.IsGreaterOrEqualTo(log.LevelDebug) { log.Warn(r.Context(), "Request was interrupted", "endpoint", r.URL.Path, r.Context().Err()) } return @@ -301,7 +301,7 @@ func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub response, _ = xml.Marshal(payload) } if payload.Status == "ok" { - if log.CurrentLevel() >= log.LevelTrace { + if log.IsGreaterOrEqualTo(log.LevelTrace) { log.Debug(r.Context(), "API: Successful response", "endpoint", r.URL.Path, "status", "OK", "body", string(response)) } else { log.Debug(r.Context(), "API: Successful response", "endpoint", r.URL.Path, "status", "OK") diff --git a/server/subsonic/stream.go b/server/subsonic/stream.go index 07ce8e18a..d0cbe2086 100644 --- a/server/subsonic/stream.go +++ b/server/subsonic/stream.go @@ -38,7 +38,7 @@ func (api *Router) serveStream(ctx context.Context, w http.ResponseWriter, r *ht go func() { _, _ = io.Copy(io.Discard, stream) }() } else { c, err := io.Copy(w, stream) - if log.CurrentLevel() >= log.LevelDebug { + if log.IsGreaterOrEqualTo(log.LevelDebug) { if err != nil { log.Error(ctx, "Error sending transcoded file", "id", id, err) } else { @@ -67,7 +67,7 @@ func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Su // Make sure the stream will be closed at the end, to avoid leakage defer func() { - if err := stream.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug { + if err := stream.Close(); err != nil && log.IsGreaterOrEqualTo(log.LevelDebug) { log.Error("Error closing stream", "id", id, "file", stream.Name(), err) } }() @@ -136,7 +136,7 @@ func (api *Router) Download(w http.ResponseWriter, r *http.Request) (*responses. // Make sure the stream will be closed at the end, to avoid leakage defer func() { - if err := stream.Close(); err != nil && log.CurrentLevel() >= log.LevelDebug { + if err := stream.Close(); err != nil && log.IsGreaterOrEqualTo(log.LevelDebug) { log.Error("Error closing stream", "id", id, "file", stream.Name(), err) } }() From b4815ecee58ed6ee3814c6004d00d0f99c4a60f2 Mon Sep 17 00:00:00 2001 From: Andrew Katsikas Date: Tue, 26 Dec 2023 18:39:15 -0500 Subject: [PATCH 23/27] Add TAK support (#2745) * bug(consts/mime_types): tak-support - 2514 Add tak to mime_types audioFormats Signed-off-by: apkatsikas * bug(scanner): tak-support - 2514 Add tak test fixture file and add fixes for tag_scanner and walk_dir_tree tests Signed-off-by: apkatsikas * Remove comment --------- Signed-off-by: apkatsikas --- consts/mime_types.go | 1 + scanner/tag_scanner_test.go | 3 ++- scanner/walk_dir_tree_test.go | 2 +- tests/fixtures/test.tak | Bin 0 -> 17339 bytes 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/test.tak diff --git a/consts/mime_types.go b/consts/mime_types.go index c05a08638..225e1857e 100644 --- a/consts/mime_types.go +++ b/consts/mime_types.go @@ -33,6 +33,7 @@ var audioFormats = map[string]format{ ".dsf": {typ: "audio/dsd", lossless: true}, ".wv": {typ: "audio/x-wavpack", lossless: true}, ".wvp": {typ: "audio/x-wavpack", lossless: true}, + ".tak": {typ: "audio/tak", lossless: true}, ".mka": {typ: "audio/x-matroska"}, } var imageFormats = map[string]string{ diff --git a/scanner/tag_scanner_test.go b/scanner/tag_scanner_test.go index 5629d2219..dc1e4575e 100644 --- a/scanner/tag_scanner_test.go +++ b/scanner/tag_scanner_test.go @@ -10,11 +10,12 @@ var _ = Describe("TagScanner", func() { It("return all audio files from the folder", func() { files, err := loadAllAudioFiles("tests/fixtures") Expect(err).ToNot(HaveOccurred()) - Expect(files).To(HaveLen(11)) + Expect(files).To(HaveLen(12)) Expect(files).To(HaveKey("tests/fixtures/test.aiff")) Expect(files).To(HaveKey("tests/fixtures/test.flac")) Expect(files).To(HaveKey("tests/fixtures/test.m4a")) Expect(files).To(HaveKey("tests/fixtures/test.mp3")) + Expect(files).To(HaveKey("tests/fixtures/test.tak")) Expect(files).To(HaveKey("tests/fixtures/test.ogg")) Expect(files).To(HaveKey("tests/fixtures/test.wav")) Expect(files).To(HaveKey("tests/fixtures/test.wma")) diff --git a/scanner/walk_dir_tree_test.go b/scanner/walk_dir_tree_test.go index 45b0dff56..bfe94300d 100644 --- a/scanner/walk_dir_tree_test.go +++ b/scanner/walk_dir_tree_test.go @@ -34,7 +34,7 @@ var _ = Describe("walk_dir_tree", func() { Expect(collected[baseDir]).To(MatchFields(IgnoreExtras, Fields{ "Images": BeEmpty(), "HasPlaylist": BeFalse(), - "AudioFilesCount": BeNumerically("==", 12), + "AudioFilesCount": BeNumerically("==", 13), })) Expect(collected[filepath.Join(baseDir, "artist", "an-album")]).To(MatchFields(IgnoreExtras, Fields{ "Images": ConsistOf("cover.jpg", "front.png", "artist.png"), diff --git a/tests/fixtures/test.tak b/tests/fixtures/test.tak new file mode 100644 index 0000000000000000000000000000000000000000..4ed8bb84309be456c041bc17ea79c556f633fbb1 GIT binary patch literal 17339 zcmeHu2|UzY+wdP_cZV2hV?vfu*^(C9)C}2Tn6b1ViJ~HrWej7qOoJqprOY(K7$PBM z8!9D{grs7qB2*tKi4_ecCPE3a|Q1` zY9sI)01zRv+5li~CoX6kaZgkd0Ce>L0Du92L?{s=3IIYvf`aQ<--SYjfh_>AR~T@# zvalFGA^^A`oKZffgJCjYa{=%VSqK0mR{=l}kO91p1|MC1wn7@f3L(JqFZc}r;wl&` zj6V3V+%e{4@Tubf#sReqPz)?U1O=Zw7Ytw=_t}6YBH+hBM^6{PAZ+*k{tDuhSIBAb z6?-SJKp6b!>F5I(q~kvD5o|A{udBBm!0fX>@O$1d;Fv#{w{rmVLg2q1fZ6L{2WG%` zh4l0dmZ2{XXBC)T=^OU@7_d&Z7_eLcEO&Kv1#H8EP98g_C-L(arVs41w_EOJ4OoBt zj4!Y+;PmO^XM*)4et!T5wfq_YD1!|gQ4Y2UH%o-o0S|B{SUZp9N5Ej4?hcsu-d{At zI3o7h{9!}bANA;fLf8Ph+w}Aebq#m?t;_$T<J~en{z@vn8Ofn$K4J+J58B)@ zsfP?cdevDbway~>jl2^Q@>x|Ya~oklFp9X)TQ{`LnRTcyV8@}}kNb+%Hl z9A{78Or%zsMq1y7(8n1MEk{o3L^C;6d-U5oKSsv}PpF4xB(j-n3fNmGj52R$?%dyM zsxX}8acQ2nVQ)uaP3KH+c>A;H-R#L=+>qz_rE~0xYahElYEM4e6BWk3(`)Kj{o-t{ zf9)$(+{;ku$}8NB6A`uYALMKUuT~wl`cVCKPhlYCR7F6&>hWhNr zw=1)vb^eREeCzZCx52maTb1#HKcbw;L3`Xhw}#b|lwQ=kwEk|a(c|{WNPdkgJ|KqK zJ^JYxB4W10fwQRJ%pR1X`HbBw;=ImOUuqA(nfK8~v~-GsV_y7J+v}e6qUXm`oV1m_ zIQ#6ap$zt9qx)n}?3vF|qSaYC7iS@Ix4vw1X`ek|GB&{GM1@-VR7P+EuC40*dSL1Y zkK;8{BfR8OdQMJssQbm9BenG@DCOz)E({A%WakqY*otY&Yk6%^p#qE!<{$q7w0i9Fk zSEu1OT@%tp4IpB4ZW*rqsN+e*akDJ@OLexm$*W2R4I<0;yG>k9d+5cOMCa9cI{KOwbSH zh0GPD_U=+7?K`?X1!Z+DmR`rV;Fbk!)eQM^IA({wV=|>%LQl*Ub51CR+uFlQ*@jQ; z{P?tr#cr?qmP{|+YsKfb4rbc6bPb?VCj;5zkHpQd-_2U!8t{6Jm(K64aiPU#rkir- zi?gS#ttS@rjxWq~k36})xn8fQwIg!lkoBzid2+X}8M`+`>z!%Ni=M=xmZe)yXc_Cp zK1Gi&c#gW*RsMTKF>b~a27V)b?QMunn_Pac=zwgv+$_?if*_6y`nT$u- z2Fwo1D|CJ`?^`;@G0L&=I6gBwG0x#m7tN~;7dX7-FHOy>J57azqjQWuRqZ(=cug%o zZ)wSvX_Uyk?Z8g`-sh9s8nrZ{S#y1Lb(C#-6n?hyzPDANEyuvn!?N-^o_O19w!5_| z^a#1!e&{UcSVXB=-hKKv5kU^6im7#E0bWpXIh4uD3vJOrdtAJ}#G$krrDqhsih~dI z_x7BXBdA%K%y4`cYQh)CE<_X~MU4#bvr`_HL#Zi!Gsl{yM@qXcZnhyFtEA-5?|z#) zDmN-$+8WhcGvZi?w@$25ZqMZHx`;9~l6l1c#IqZE>*7{pQqAR=*7%!VS%9ZZGx?FV zFFPWSxMQ@WIpbF|0X3`Pe5P4DyX8xOMh9;5?nmiEr4;^d>*BkRVr8dX4@%9pEpDqf zwDUl+Clz^9JoD1P0gWHI={4pQ@rjq#JKCzXh4DD1jyE_SV12W;X?U!^hs_x}GG*1_ za_26eH#lVn(qfWk9sVdZ^b-VTGKvw zLyo?@-j_D+>sNaeTb05WL+^?)UX?jQ5htl#-MCgU6H2jW#4u>AyFoc>F z=jn1!W&KkK_;lynxKX)^^@Ch@s@W9LFp3%ay_}lc^6W6bv+My!BjwPui z)({@*R0MQS3lgZbNS20(w>+vjJ!W1!ARtN*XA#7SYr=arslt^h(K&=5`L6&H9q!O4 zsbOcq6;y{!n$RY?*F0n*W!9my%%X1WtQ@ID5mcePzk zq)H*xm_A$pztNxLJmv@$8y+cO$L?i$m90NkT8HrncgKe9S7^av)wEi#EKKUmwIlRC zztt4PDb1L4S_QAB%3OL#oj!FnAT!(&ojuL4_&iuw25=iI5VkT^2BWo z+qOywVLPKc2Mwrxm;$mCvLX3q^mbEn{kqYEm`N(5nBjWq=D~6sER0@UTq`Ysk83*< z-y-OQ9|*_xZ*tr2f=#+>tTFtG*=H}>K(fge8x8N^V3eVaiUlY`Pt8DLaUxL!gFR9y z+?E%~{+JIZc6%jkyC?Qpj@9Mj?j%+zUsa|lg~9T%lvV-*eGjfL1GRTwYMav(@xSkn zei_TCWq7T1_v_q-jiJ`oE|_XgZ?&k>+aYN?lRAjKc-gz{Fcx)v@)li(Pc+*ouevQk z<^CX=<%Gw;juk3&67JNA*P2SKbHIiSDIh76H34}HF`qcY$}$y_eWZs+xZS4jj-zjB zx(-;L3*JYr0qU2JrL;R!d&K!YKsk? zA(OY4b~nP7qQqdcZzNB01M#nNg#ZD&xmtU%Mor=^1EJ8jPm{lC+}BuW6HVgQT@yu` zMlGcZHfc5rD5^}}savCN=ZhDR(=D-wm@Yh7kd#RheteENO2LN=7W>53Xw*;# zc%P)z(a#~DH;{9w;-E(Nxxp15=kDHTyWPqlGePPz`!e7qU32ZBbXUcx-GI!S3vBKM zLcB`jE`vY~6XTQMnnF{?%RH8r3OE1yWza*58ov(P7mc6v7P$cM04X6o`MJ-@Z{0lV zAo0zRzEX|CDM6$+Vq9bYx(OWTf&s@?#gj>aQPP0vQi?6@g7`COgyS0|5Q{f>#e;y0 z+a~q2yXiA6Qct*>E_6Rt+W+zL%mq6QbD0cJroB`nu#AZF5A!tADW3MT+0rVU7=(!6 zRZ28PX4rtLOgIX=B>MlIuJy;|63BvOsI4mr_t-D7pEfdn9rQBpD!rpjXTA&*{0UuvDM z$eM-PY-BG{hQ2-aL3N&xHE@^^Ff$Vt63u2P8URZ2*sprA)O97)1wk1oN?10n(7PF7 zd)Gx4t7e+~(wb4c`%?>(zqmV~xC}4Z$I0~JD6>=GUdU&?7uGab>LwgcW~>zkB;eAC z6u~$9v2cMilazh~-n~40gX4=TzHeoCksI3Z;4>-t$hubh`nq~L{J9iC6a@`$hS4py z&}#uC3XOoMYp*>gBR7OaLa(%XKe#?zuqt$lZN_~4QTsGYaE_PT&3myYhpZ@DV$_fN zA#1DJb=S{AfIw_|`Xvw8?wD##$XxWDvyC1(=~2@0W1OFUCd=O$lc1m{B&<9!<)j;^ z%OL9s^&!wGB?B>rtRzDRK%+4-F)yv3Z+M1`fTB^K4bo3p&0sHA&2uOogJY==0|XYM z+DDhZ+;&98lip`e(B{N)ll&bGvjKC&K>rUXAOn-Lx=)?I*0h}!@xf(FNHsS)H*+up z%CLw_iy=zS&1)&{RKzx<2~1GwDv4_$oh0GTH3YgQoK)w%`PeEil^QZ#y`hdhi}kh+*L^RDdVAsiB19q#Y9`rk+cuk?VXbegRgKL6Vil$+^ob*KZH9WBAAensj61 zD2ZQyb@>I7Nq)z(GlV%Twpr-t>7EZd-8d$n!@t^HRkJm}b~w-o^yf#aYu?UoC9ncA zdZLZ*{DP-`f#l*}%vQFu$-+=_oXLtFfB&(v+~WZ!iqGb6EDgo|F)Qyy|FH!9F<9^b z?+J0j675YY!|ddsR@`Cu+R3zpuevUiAntA>c(T(_U<5Ex5@g^~|9!&fXea^^$e< z0oS;b1XKHub1Vc=osharHovM=R&Hy3VT z*IByVQWqfA@4O~_RQHC(=lu$*`E`8j`LeC1n%GYBZ--P>d1dIaC_@>CFn26Uf|SsZ zAhBP51_c#vR5*S91@-9LIMd=<{*omm8dNshwRud|%BU6#yL=Ytq6m5&JDV-I8ihxr1RkI=d-P7EP19NniqnAY{$(c=NR4sq zP!(&}h6J02y6xEMa3z!}jIqvNYA=D)w&s9?F5R_+?6tbOacaSsB2vQW^s>Efj z(Y>xv{@wB68hQH(tgx_(1r`MX7K|4dw?$rhmXIa2v)5c4#A5Ff-n)LH6Od>R44QDN z%_mf9oqEmpsbOt(pk>|G3}KbY&)nOj)y`+XIO^%zLGf9ZNzx?7^L4Q@efa5%a`IAe z=R#T*I`zTc=LS6dc_8TumG~gTxD~owb7k@Z;O-iBQ(S7|0^k1a^Pf5=JWWv!*Im8p^e+lj-fTT4udt}v4 z{Y&`%|F6lD6A(}DuHmEc4G^kZHVO);=nRLd?0-*Qf0G<@lJR}Ym>g-2F?^?Z0bpnA z+@g9qlz2fK)WVVmXTnW?^THN1q^ZTwv*^X1YFquXsw zx9jy@&^68$Iufk~jQs*7C6CYoSFXY*m?q(?CG|bE+75?7T8B)|6))jio+#O~H>THU zNhcz2e2pUB6^Le;cz(+=k$uFnzb|bIEOgszm_LDhz6?3JUxk2R*#?=#;LYrG>(yEc zHE>_QG{3|pn=E`x?@AeA?R)k#bI+bTqDck@p1$0O>nDl02P^)Om_87=xX2~AQtmvS z5X)-EeRJn=GcKBB_3qKfIVmY!+46GPDOsw;)is;oR05fzNVA80swM!~2YJ1pURi8* zsvU7jy~5D2zTpLk#UeCCh0bcmUwWV^?ij;Qnmqb3(t)wR66dde3#QQc5!?d$>5z0g zHQAj!b7r>}Fp~p^lf}$+-MZ6+Y(bMsUR*V^5Q#rKxNNtAC)5?MW(Ai`s;Xr795M+Q zwVSj2A$!ZY$gQIpNLd5=BvIxF)l~PL9M)H6mD0W9S*YZTV1DcL(&5cml)$(E)MKJu zTvl%#7U4rMhBNApiNyIm1BPv|7ynv=y!nfu0cW zu{DD%_(=#}+m|P&;Otrmak!RFz( zJu4mwzNY5L!!;`g)$M6G776hTcw$CQ>?;duq4|hVt3e}uLP#P56Vd-EI+;HnQC183 z2H*jLSv&V!Dh4Y2NE8!UZ?A!Tb^;@uE-*=_3ns4J!jKiB>tNAnBtU%G^n8P-7a305 zuh?Do3#bf|SY7XhXwZ5*iH@h~3!&XH;bC(E!uS3dDjhBnC{3RuQVx(vZFC8Jp|5DP zu#$l*MpeYYgF&N_07!Cb!mhe!@>pb&?t`8ibe-`Prw=?TJm2jOL@`gE4}DueS5X0Q za!a5?l84eGH%TGG(F6VcqVi>qLl@8S?Ut%W+7|pgO2E?~zAd{IOJiu(nNUAVM(y6D zVv!ARl)7e#f`&DFq7xL&@%d!3ilB_G^Nw$ToD)VC^`vq3;V;<9di7jKzyY)Nd3P|M^FaRS?;`H4d&zoKh^O~;zh6}JF9J+C^Y zv*LX18Ny;@WdTzyzNRrTnU;n+E<&rOFVpoGP|1H1!!OCy4KQM(KU}l))Pwp50N{`^ zC^s*jxCevth*ixuN^eHBS?8>I%`=*37g>%J;2WOkfzC*c8R((^ zq2xTzJ?KVv<45y(y&rUXYgqg$*1$`3ZDJ5XOr9GzwVRYNTY z#PCdJduwp5OH(a|eU=%{571AKqorEw+~NjIvW_IabK#ON9;@X0Mno5>{8`CS(G(@Y zCNm_Owq%~V58iH+ADBl*?R8|R#dt7x@oWciIiof+N}Nm%Up@J;=E3~wCy)8+{GOQ; z`0`>`@SrFtm` zr9!@tWakVS0A!KwPKfm5$-mtY1_(4`N~8Uso9f+POjRk<@Aw@b9-qglihciafiB1& zN!AE}K^~kVa(AtLCiP%DZubxlvJ|C=^0tMYR#w)~8iI%k(ry|2m?MbM-fM_hm7dby z*p~$vlZs&r5#tFP%xa`b6Jk#;d&*psY>H>+SL7L_h%4?=)wJ|bgpnI#zedSy7VaH1 zJ=)41LcHNcDeO`qeD|fysu<_WL??SX=&G9EP0rHTqw$E6jA(}0l;$PZTK2Ioqmj=c zZ)bm;y?*@w0S0T@C6_2of|2oF0I#uxPX%vTc{~rRo_u=!vxw)L5G>XwgW-GfswW^5 zdln}7#B+<(bJ`XQ;U$;PdB@Kpvagm|5xq(F-dJz?$N!8#Q(wyHA!y9-R#OrY)fi$m^XzY%($C8L;I< z8=N0}&SvrXOROH*(+%%tC!(0FYRb?xM{IXi@f5#>A1X&;lxgL2Mw0*A;ClCJtRmm@ zZm?wX`8;No>Id?@v3_3E%{3P-37QP>EK3H1!+sTj(a8#^6s>u&KZoeWp6bloY@5>$ zRDz5G-scVZill5go#L-=s4-=F*5TAolC_6DS8oSm?Op`%Y z*;#PnO;bMvnRU|zZIImrmBsobe%7w0SmO~T3^70^ptq%}d%m_qU0Wq|>JmO9%E$cW zTZ_l)PJa$;@_&h);|@L(Bg%yDnhKXU1vI_h5cfvdw!Eyi-ynbJB{?yQ&*w&mT9!c% zeSFI0MNLKJ?z6@o$JM^(8}V6oFvi>5yS2^6C#%}BuE8CCe=^q+&9^(VzQ zkQr{o_8H_lW)_k@wXB~#xsWcf*=GGf#y=J;xHM;mmQLaG?VOS0-+tq6=QR_q=OhGy zmpy(Vyv%U3_DwmA2PPha{7gPuT?Tv7$ualqubA1tn3|6GN=Bgm9aD`PvA^|Fa#xUV@Z-yaovNOXeS7xBfqzTPXPABnxOS7^ zjFxFmbqp>Zj?90qV$gMM`WLHl#!-`UX5as(K@XB zH4=kQt41g`P~qwVCeq|dBI$ToHu;k0B&dRo2Clz*`yISn%jdI#Elq6lSF5b4Px01>snB`6Zvqm);@`K zsAOW8IcV!YBUQK2X#^yOw`SnCCz>KyJ@N114k>GpTb{1RmWio@u%ex|o!pv`H9N`S z^X)=AN>kv7fAoYE>c4`@A)smVy_`1izU>m9IdnDRhT~?^eg~%IK)h(+PPm0shTw-O z%}Ig)_PMJR^}mP0CuMCxSp3|>Zx60pz6;CeXO7c-wXhIb@nH`A)(>EnWdjM}WR6op zDd|$16ypR^(%`qKa0wYS%5bgKwASn#$?HO!SHd?Hr#TF?A!USA47bB3!&@@LvJH6G~^4@<@ zCCggG8D_-U_B&zaoi535C#wtpAS^p{vfm?(` zuULyng3RDBufu@L*#lKgf4BR&r5MsoaD4Ji%e{U&}!n%Lxouvqk+3^jMDZSWaMo2N)sp zdw|E^+N}h3{R{zF33LP7xuFn_fZnbhJC*}EmeWcA78qU!UIAW7mZ$@cC;;JgE;#7u zvEb#Pk>#`;S%x}*`CCxP-x`C(VALDRak)G&;N0;bfuGF*q?OaY16aGgV3i&?k(KoC zLfQU46A#XFc{uw5&L0n2j&*}+S_B203D)|LCEEhQxV9?}2P}V+SSJC#@|)Fuq$3#V z2fE1uTf~-;5stqa0{#e`bF|zG6usUs1ZE(<2s;D-lq5HrgWRR{ z9y}HYD)vrbn6NQ@;m%UG5wmMA<-*@M3{t@_b^W3!F&ms2i~K4m7aXFM=rX?0j`e&@HhT~E$JMi3$Jvg3Zj52nw) zK92?bur4KsBkcVlahr)V)Ptx~x3v#Q7OyhLQ*QKjnGhRR*GHJ&9v^+Y4YTI^P1D}= ztWCfnT6xI8f>fzt(G!xJ^rG;s_-ccYdd5WW_(F(MdW5nKJHYB9erPJ7vxU1S z3)d;kDL6x~Uha%{ZTxn3MO~F=C!%7y(s0HQgL(KVm_EPwk>$1+<~#KK{Dlp~awp!7 zBbmFeb@|K=JzZNLQS6+Vo%bR!s_0&&Y~gbCY5PY;A>=mUd#lEC5^*<5W3dlU&E9(4 zG;DD{NbKDKf6;HJi?N{-Ah_X2jHe_ji0h zz#hMsQrXq==-6z0eFl`gGR)KWA37eaQZNiU7&dER{d-%HkhY=m*?UL0j9oa@fl<#8 z+#5fi(?j1^I;V~UqpNDii@Lji4!h^|bM&lCA;LegJAT}it>m_AY6rjBv$MSVj34FQ zTmRNKD?=9O5!kt4>lu1*=ek-GACZDXM>?C|H=S+&y|UgkVyln0!GK#=h>vhVTu@7Q z*NEv)5)jHQ&ps?7SGGTC>-^yCD_XF3&+3AO1>d3~Yoq0vtt;>GJpE84cMrWa0y79g z=6HTRI+)>>_@%mQrFV-pY4`F1Ljr@(J(u{}=-Jujki=lyRd_|3`QB67vqDP0!j0E( zI@d!BlE|;#jwrPWk2L4A`>`ePmD+=CN7Pt2ne)*R1Mw!-F2V%|&ie8x2O>LGx`+0R z&DVrZSB`iYS}K1njSZP$4A-n6w+F3vFw>G_3lhRIpzs#7@F3d6hJI#+#vzs)gZ-zz zBf4)ZypuRS6oP9JbFLX&F!mkW{IOttebXk_&_WZkljyf>(E*UmNrwQRtkv&^e+-W} zvwt!Wf}{Bwy{EjY{Ykc|{SXn$&RM#R^ZU4u6UDMe@!V*gE29$*Ar!TswJGV*#2jvh z1?OmIy~$*uXGOrmXHalLZYsAJ@%lo}+cTE(fRpvgN zj;h#c;ClS)-bHLd;R-5i+Ihz>X%Dtlo%R*=|0?pNt&4B5f`*agO2T6E)c1=cpyXN* zqZ{Bq=S7J>Y*QJeWU}>^Y~lDSf4iE*oUW|=6~t0H1h}J9jT@WR*28-^KS(2I5B==Q zMzzZ$>!{LWk?q>9s@k2|6_Dqhg;Xi6Xct0lVhquXh`5he!@l|~6^f)v`-@vu? z{Qs_V7Aobex?L`C&gZ^0C%(Vg0|N5g)tl1Jp}Ti-1%EW2;hHQ9+Pn0;ij`ry?{+=1 z!KX;DcVyeau3?aK{Cz--OwWFA4Yvo$^(7~^bF8^`7=1ly=4%N*(pIi^s?D>V|Bg|J@a1T@L}0PgW9Vm%k$J(UpT(*6*e!{sHeRA-k#^hV$MUg zSJ$!zK?=NhB~nne-sp=FKO?Ee_Ry-b0;1uhs6=b+&wgKeCK!{azEjSHcQm@^ZtZ$s zB=N41vryQIUl#DNm!2WanTo74w+9+)Y(e?CRbFulvpm?%v7HzDhTH2iRqgYNq~105 zJ}_VouFPN3`N>dUOCxS09y8eFnVKD-a;j~)tH++5(t{t2UxBfr7rjqg7$u#r-;HZ^ zKQzja`^n*zQg>(d~PXk;vLs5f@uitYI_kpJW`~IKRnc7 z);d?o#xG@%&j_C$3bCX+y_Mj Date: Tue, 26 Dec 2023 19:35:14 -0500 Subject: [PATCH 24/27] Discard duplicated tags --- scanner/metadata/metadata.go | 16 ++++++++++++++++ scanner/metadata/metadata_internal_test.go | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/scanner/metadata/metadata.go b/scanner/metadata/metadata.go index e18d1b341..d12e875a2 100644 --- a/scanner/metadata/metadata.go +++ b/scanner/metadata/metadata.go @@ -57,6 +57,9 @@ func Extract(files ...string) (map[string]Tags, error) { } func NewTag(filePath string, fileInfo os.FileInfo, tags ParsedTags) Tags { + for t, values := range tags { + tags[t] = removeDuplicates(values) + } return Tags{ filePath: filePath, fileInfo: fileInfo, @@ -64,6 +67,19 @@ func NewTag(filePath string, fileInfo os.FileInfo, tags ParsedTags) Tags { } } +func removeDuplicates(values []string) []string { + encountered := map[string]struct{}{} + var result []string + for _, v := range values { + if _, ok := encountered[v]; ok { + continue + } + encountered[v] = struct{}{} + result = append(result, v) + } + return result +} + type ParsedTags map[string][]string func (p ParsedTags) Map(customMappings ParsedTags) ParsedTags { diff --git a/scanner/metadata/metadata_internal_test.go b/scanner/metadata/metadata_internal_test.go index 54b81b0b9..8831550f8 100644 --- a/scanner/metadata/metadata_internal_test.go +++ b/scanner/metadata/metadata_internal_test.go @@ -68,6 +68,19 @@ var _ = Describe("Tags", func() { }) }) + Describe("removeDuplicates", func() { + It("removes duplicates", func() { + md := NewTag("/music/artist/album01/Song.mp3", nil, ParsedTags{ + "genre": []string{"pop", "rock", "pop"}, + "date": []string{"2023-03-01", "2023-03-01"}, + "mood": []string{"happy", "sad"}, + }) + Expect(md.tags).To(HaveKeyWithValue("genre", []string{"pop", "rock"})) + Expect(md.tags).To(HaveKeyWithValue("date", []string{"2023-03-01"})) + Expect(md.tags).To(HaveKeyWithValue("mood", []string{"happy", "sad"})) + }) + }) + Describe("Bpm", func() { var t *Tags BeforeEach(func() { From 798b03eabd00d2153f44844d6715960afdb8d4ad Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 27 Dec 2023 12:41:08 -0500 Subject: [PATCH 25/27] Add "inspect" command to CLI --- cmd/inspect.go | 99 ++++++++++++++++++++++ scanner/mapping.go | 28 +++--- scanner/mapping_internal_test.go | 10 +-- scanner/metadata/metadata.go | 22 +++-- scanner/metadata/metadata_internal_test.go | 26 ++++-- scanner/tag_scanner.go | 6 +- 6 files changed, 154 insertions(+), 37 deletions(-) create mode 100644 cmd/inspect.go diff --git a/cmd/inspect.go b/cmd/inspect.go new file mode 100644 index 000000000..f53145e79 --- /dev/null +++ b/cmd/inspect.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/scanner" + "github.com/navidrome/navidrome/scanner/metadata" + "github.com/navidrome/navidrome/tests" + "github.com/pelletier/go-toml/v2" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +var ( + extractor string + format string +) + +func init() { + inspectCmd.Flags().StringVarP(&extractor, "extractor", "x", "", "extractor to use (ffmpeg or taglib, default: auto)") + inspectCmd.Flags().StringVarP(&format, "format", "f", "pretty", "output format (pretty, toml, yaml, json, jsonindent)") + rootCmd.AddCommand(inspectCmd) +} + +var inspectCmd = &cobra.Command{ + Use: "inspect [files to inspect]", + Short: "Inspect tags", + Long: "Show file tags as seen by Navidrome", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + runInspector(args) + }, +} + +var marshalers = map[string]func(interface{}) ([]byte, error){ + "pretty": prettyMarshal, + "toml": toml.Marshal, + "yaml": yaml.Marshal, + "json": json.Marshal, + "jsonindent": func(v interface{}) ([]byte, error) { + return json.MarshalIndent(v, "", " ") + }, +} + +func prettyMarshal(v interface{}) ([]byte, error) { + out := v.([]inspectorOutput) + var res strings.Builder + for i := range out { + res.WriteString(fmt.Sprintf("====================\nFile: %s\n\n", out[i].File)) + t, _ := toml.Marshal(out[i].RawTags) + res.WriteString(fmt.Sprintf("Raw tags:\n%s\n\n", t)) + t, _ = toml.Marshal(out[i].MappedTags) + res.WriteString(fmt.Sprintf("Mapped tags:\n%s\n\n", t)) + } + return []byte(res.String()), nil +} + +type inspectorOutput struct { + File string + RawTags metadata.ParsedTags + MappedTags model.MediaFile +} + +func runInspector(args []string) { + if extractor != "" { + conf.Server.Scanner.Extractor = extractor + } + log.Info("Using extractor", "extractor", conf.Server.Scanner.Extractor) + md, err := metadata.Extract(args...) + if err != nil { + log.Fatal("Error extracting tags", err) + } + mapper := scanner.NewMediaFileMapper(conf.Server.MusicFolder, &tests.MockedGenreRepo{}) + marshal := marshalers[format] + if marshal == nil { + log.Fatal("Invalid format", "format", format) + } + var out []inspectorOutput + for k, v := range md { + if !model.IsAudioFile(k) { + continue + } + if len(v.Tags) == 0 { + continue + } + out = append(out, inspectorOutput{ + File: k, + RawTags: v.Tags, + MappedTags: mapper.ToMediaFile(v), + }) + } + data, _ := marshal(out) + fmt.Println(string(data)) +} diff --git a/scanner/mapping.go b/scanner/mapping.go index d05cc5a8a..c9e255cbb 100644 --- a/scanner/mapping.go +++ b/scanner/mapping.go @@ -15,20 +15,20 @@ import ( "github.com/navidrome/navidrome/utils" ) -type mediaFileMapper struct { +type MediaFileMapper struct { rootFolder string genres model.GenreRepository } -func newMediaFileMapper(rootFolder string, genres model.GenreRepository) *mediaFileMapper { - return &mediaFileMapper{ +func NewMediaFileMapper(rootFolder string, genres model.GenreRepository) *MediaFileMapper { + return &MediaFileMapper{ rootFolder: rootFolder, genres: genres, } } // TODO Move most of these mapping functions to setters in the model.MediaFile -func (s mediaFileMapper) toMediaFile(md metadata.Tags) model.MediaFile { +func (s MediaFileMapper) ToMediaFile(md metadata.Tags) model.MediaFile { mf := &model.MediaFile{} mf.ID = s.trackID(md) mf.Year, mf.Date, mf.OriginalYear, mf.OriginalDate, mf.ReleaseYear, mf.ReleaseDate = s.mapDates(md) @@ -86,7 +86,7 @@ func sanitizeFieldForSorting(originalValue string) string { return utils.NoArticle(v) } -func (s mediaFileMapper) mapTrackTitle(md metadata.Tags) string { +func (s MediaFileMapper) mapTrackTitle(md metadata.Tags) string { if md.Title() == "" { s := strings.TrimPrefix(md.FilePath(), s.rootFolder+string(os.PathSeparator)) e := filepath.Ext(s) @@ -95,7 +95,7 @@ func (s mediaFileMapper) mapTrackTitle(md metadata.Tags) string { return md.Title() } -func (s mediaFileMapper) mapAlbumArtistName(md metadata.Tags) string { +func (s MediaFileMapper) mapAlbumArtistName(md metadata.Tags) string { switch { case md.AlbumArtist() != "": return md.AlbumArtist() @@ -108,14 +108,14 @@ func (s mediaFileMapper) mapAlbumArtistName(md metadata.Tags) string { } } -func (s mediaFileMapper) mapArtistName(md metadata.Tags) string { +func (s MediaFileMapper) mapArtistName(md metadata.Tags) string { if md.Artist() != "" { return md.Artist() } return consts.UnknownArtist } -func (s mediaFileMapper) mapAlbumName(md metadata.Tags) string { +func (s MediaFileMapper) mapAlbumName(md metadata.Tags) string { name := md.Album() if name == "" { return consts.UnknownAlbum @@ -123,11 +123,11 @@ func (s mediaFileMapper) mapAlbumName(md metadata.Tags) string { return name } -func (s mediaFileMapper) trackID(md metadata.Tags) string { +func (s MediaFileMapper) trackID(md metadata.Tags) string { return fmt.Sprintf("%x", md5.Sum([]byte(md.FilePath()))) } -func (s mediaFileMapper) albumID(md metadata.Tags, releaseDate string) string { +func (s MediaFileMapper) albumID(md metadata.Tags, releaseDate string) string { albumPath := strings.ToLower(fmt.Sprintf("%s\\%s", s.mapAlbumArtistName(md), s.mapAlbumName(md))) if !conf.Server.Scanner.GroupAlbumReleases { if len(releaseDate) != 0 { @@ -137,15 +137,15 @@ func (s mediaFileMapper) albumID(md metadata.Tags, releaseDate string) string { return fmt.Sprintf("%x", md5.Sum([]byte(albumPath))) } -func (s mediaFileMapper) artistID(md metadata.Tags) string { +func (s MediaFileMapper) artistID(md metadata.Tags) string { return fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(s.mapArtistName(md))))) } -func (s mediaFileMapper) albumArtistID(md metadata.Tags) string { +func (s MediaFileMapper) albumArtistID(md metadata.Tags) string { return fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(s.mapAlbumArtistName(md))))) } -func (s mediaFileMapper) mapGenres(genres []string) (string, model.Genres) { +func (s MediaFileMapper) mapGenres(genres []string) (string, model.Genres) { var result model.Genres unique := map[string]struct{}{} var all []string @@ -174,7 +174,7 @@ func (s mediaFileMapper) mapGenres(genres []string) (string, model.Genres) { return result[0].Name, result } -func (s mediaFileMapper) mapDates(md metadata.Tags) (year int, date string, +func (s MediaFileMapper) mapDates(md metadata.Tags) (year int, date string, originalYear int, originalDate string, releaseYear int, releaseDate string) { // Start with defaults diff --git a/scanner/mapping_internal_test.go b/scanner/mapping_internal_test.go index 4687efeab..b44f2bcfb 100644 --- a/scanner/mapping_internal_test.go +++ b/scanner/mapping_internal_test.go @@ -12,11 +12,11 @@ import ( ) var _ = Describe("mapping", func() { - Describe("mediaFileMapper", func() { - var mapper *mediaFileMapper + Describe("MediaFileMapper", func() { + var mapper *MediaFileMapper Describe("mapTrackTitle", func() { BeforeEach(func() { - mapper = newMediaFileMapper("/music", nil) + mapper = NewMediaFileMapper("/music", nil) }) It("returns the Title when it is available", func() { md := metadata.NewTag("/music/artist/album01/Song.mp3", nil, metadata.ParsedTags{"title": []string{"This is not a love song"}}) @@ -37,7 +37,7 @@ var _ = Describe("mapping", func() { ds := &tests.MockDataStore{} gr = ds.Genre(ctx) gr = newCachedGenreRepository(ctx, gr) - mapper = newMediaFileMapper("/", gr) + mapper = NewMediaFileMapper("/", gr) }) It("returns empty if no genres are available", func() { @@ -79,7 +79,7 @@ var _ = Describe("mapping", func() { Describe("mapDates", func() { var md metadata.Tags BeforeEach(func() { - mapper = newMediaFileMapper("/", nil) + mapper = NewMediaFileMapper("/", nil) }) Context("when all date fields are provided", func() { BeforeEach(func() { diff --git a/scanner/metadata/metadata.go b/scanner/metadata/metadata.go index d12e875a2..cb3d18ffb 100644 --- a/scanner/metadata/metadata.go +++ b/scanner/metadata/metadata.go @@ -58,25 +58,35 @@ func Extract(files ...string) (map[string]Tags, error) { func NewTag(filePath string, fileInfo os.FileInfo, tags ParsedTags) Tags { for t, values := range tags { - tags[t] = removeDuplicates(values) + values = removeDuplicatesAndEmpty(values) + if len(values) == 0 { + delete(tags, t) + continue + } + tags[t] = values } return Tags{ filePath: filePath, fileInfo: fileInfo, - tags: tags, + Tags: tags, } } -func removeDuplicates(values []string) []string { +func removeDuplicatesAndEmpty(values []string) []string { encountered := map[string]struct{}{} + empty := true var result []string for _, v := range values { if _, ok := encountered[v]; ok { continue } encountered[v] = struct{}{} + empty = empty && v == "" result = append(result, v) } + if empty { + return nil + } return result } @@ -100,7 +110,7 @@ func (p ParsedTags) Map(customMappings ParsedTags) ParsedTags { type Tags struct { filePath string fileInfo os.FileInfo - tags ParsedTags + Tags ParsedTags } // Common tags @@ -207,7 +217,7 @@ func (t Tags) getPeakValue(tagName string) float64 { func (t Tags) getTags(tagNames ...string) []string { for _, tag := range tagNames { - if v, ok := t.tags[tag]; ok { + if v, ok := t.Tags[tag]; ok { return v } } @@ -225,7 +235,7 @@ func (t Tags) getFirstTagValue(tagNames ...string) string { func (t Tags) getAllTagValues(tagNames ...string) []string { var values []string for _, tag := range tagNames { - if v, ok := t.tags[tag]; ok { + if v, ok := t.Tags[tag]; ok { values = append(values, v...) } } diff --git a/scanner/metadata/metadata_internal_test.go b/scanner/metadata/metadata_internal_test.go index 8831550f8..44967f9fe 100644 --- a/scanner/metadata/metadata_internal_test.go +++ b/scanner/metadata/metadata_internal_test.go @@ -9,7 +9,7 @@ var _ = Describe("Tags", func() { DescribeTable("getDate", func(tag string, expectedYear int, expectedDate string) { md := &Tags{} - md.tags = map[string][]string{"date": {tag}} + md.Tags = map[string][]string{"date": {tag}} testYear, testDate := md.Date() Expect(testYear).To(Equal(expectedYear)) Expect(testDate).To(Equal(expectedDate)) @@ -29,7 +29,7 @@ var _ = Describe("Tags", func() { Describe("getMbzID", func() { It("return a valid MBID", func() { md := &Tags{} - md.tags = map[string][]string{ + md.Tags = map[string][]string{ "musicbrainz_trackid": {"8f84da07-09a0-477b-b216-cc982dabcde1"}, "musicbrainz_releasetrackid": {"6caf16d3-0b20-3fe6-8020-52e31831bc11"}, "musicbrainz_albumid": {"f68c985d-f18b-4f4a-b7f0-87837cf3fbf9"}, @@ -44,7 +44,7 @@ var _ = Describe("Tags", func() { }) It("return empty string for invalid MBID", func() { md := &Tags{} - md.tags = map[string][]string{ + md.Tags = map[string][]string{ "musicbrainz_trackid": {"11406732-6"}, "musicbrainz_albumid": {"11406732"}, "musicbrainz_artistid": {"200455"}, @@ -60,7 +60,7 @@ var _ = Describe("Tags", func() { Describe("getAllTagValues", func() { It("returns values from all tag names", func() { md := &Tags{} - md.tags = map[string][]string{ + md.Tags = map[string][]string{ "genre": {"Rock", "Pop", "New Wave"}, } @@ -68,23 +68,31 @@ var _ = Describe("Tags", func() { }) }) - Describe("removeDuplicates", func() { + Describe("removeDuplicatesAndEmpty", func() { It("removes duplicates", func() { md := NewTag("/music/artist/album01/Song.mp3", nil, ParsedTags{ "genre": []string{"pop", "rock", "pop"}, "date": []string{"2023-03-01", "2023-03-01"}, "mood": []string{"happy", "sad"}, }) - Expect(md.tags).To(HaveKeyWithValue("genre", []string{"pop", "rock"})) - Expect(md.tags).To(HaveKeyWithValue("date", []string{"2023-03-01"})) - Expect(md.tags).To(HaveKeyWithValue("mood", []string{"happy", "sad"})) + Expect(md.Tags).To(HaveKeyWithValue("genre", []string{"pop", "rock"})) + Expect(md.Tags).To(HaveKeyWithValue("date", []string{"2023-03-01"})) + Expect(md.Tags).To(HaveKeyWithValue("mood", []string{"happy", "sad"})) + }) + It("removes empty tags", func() { + md := NewTag("/music/artist/album01/Song.mp3", nil, ParsedTags{ + "genre": []string{"pop", "rock", "pop"}, + "mood": []string{"", ""}, + }) + Expect(md.Tags).To(HaveKeyWithValue("genre", []string{"pop", "rock"})) + Expect(md.Tags).ToNot(HaveKey("mood")) }) }) Describe("Bpm", func() { var t *Tags BeforeEach(func() { - t = &Tags{tags: map[string][]string{ + t = &Tags{Tags: map[string][]string{ "fbpm": []string{"141.7"}, }} }) diff --git a/scanner/tag_scanner.go b/scanner/tag_scanner.go index 5a719acd4..9195544fd 100644 --- a/scanner/tag_scanner.go +++ b/scanner/tag_scanner.go @@ -27,7 +27,7 @@ type TagScanner struct { ds model.DataStore plsSync *playlistImporter cnt *counters - mapper *mediaFileMapper + mapper *MediaFileMapper cacheWarmer artwork.CacheWarmer } @@ -100,7 +100,7 @@ func (s *TagScanner) Scan(ctx context.Context, lastModifiedSince time.Time, prog var changedDirs []string s.cnt = &counters{} genres := newCachedGenreRepository(ctx, s.ds.Genre(ctx)) - s.mapper = newMediaFileMapper(s.rootFolder, genres) + s.mapper = NewMediaFileMapper(s.rootFolder, genres) refresher := newRefresher(s.ds, s.cacheWarmer, allFSDirs) log.Trace(ctx, "Loading directory tree from music folder", "folder", s.rootFolder) @@ -386,7 +386,7 @@ func (s *TagScanner) loadTracks(filePaths []string) (model.MediaFiles, error) { var mfs model.MediaFiles for _, md := range mds { - mf := s.mapper.toMediaFile(md) + mf := s.mapper.ToMediaFile(md) mfs = append(mfs, mf) } return mfs, nil From a186a795f68dd39371312d1c17baa4d42dc33969 Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 27 Dec 2023 12:44:25 -0500 Subject: [PATCH 26/27] Omit empty Genre attributes --- model/genre.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/model/genre.go b/model/genre.go index 3c3d6e559..f55c9953c 100644 --- a/model/genre.go +++ b/model/genre.go @@ -1,10 +1,10 @@ package model type Genre struct { - ID string `structs:"id" json:"id"` + ID string `structs:"id" json:"id,omitempty" toml:"id,omitempty" yaml:"id,omitempty"` Name string `structs:"name" json:"name"` - SongCount int `structs:"-" json:"-"` - AlbumCount int `structs:"-" json:"-"` + SongCount int `structs:"-" json:"-" toml:"-" yaml:"-"` + AlbumCount int `structs:"-" json:"-" toml:"-" yaml:"-"` } type Genres []Genre From 130ab76c79d1a33e78eab6c45bcccddff63641e2 Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 27 Dec 2023 13:04:26 -0500 Subject: [PATCH 27/27] go mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5b35aeabb..15c238a7d 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/mileusna/useragent v1.3.4 github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/gomega v1.30.0 + github.com/pelletier/go-toml/v2 v2.0.6 github.com/pocketbase/dbx v1.10.1 github.com/pressly/goose/v3 v3.15.1 github.com/prometheus/client_golang v1.17.0 @@ -78,7 +79,6 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect