From a704e86ac1c2e7395b53588c6563be19de2f0c9c Mon Sep 17 00:00:00 2001 From: Maximilian <16063560+pattmax00@users.noreply.github.com> Date: Sun, 8 Feb 2026 08:57:30 -0600 Subject: [PATCH] refactor: run Go modernize (#5002) --- adapters/deezer/client_auth_test.go | 4 +-- adapters/gotaglib/gotaglib.go | 2 +- adapters/lastfm/auth_router.go | 2 +- adapters/listenbrainz/auth_router.go | 4 +-- adapters/listenbrainz/auth_router_test.go | 6 ++--- adapters/listenbrainz/client.go | 4 +-- adapters/spotify/client.go | 4 +-- conf/configuration.go | 15 ++++------- core/agents/agents_test.go | 26 +++++++++---------- core/artwork/cache_warmer_test.go | 2 +- core/artwork/reader_album.go | 2 +- core/artwork/reader_artist.go | 4 +-- core/auth/auth.go | 11 +++----- core/auth/auth_test.go | 6 ++--- core/external/extdata_helper_test.go | 6 ++--- core/external/provider.go | 4 +-- core/library.go | 4 +-- core/maintenance.go | 6 ++--- core/playback/queue.go | 7 ++--- core/playlists.go | 8 +++--- core/scrobbler/play_tracker.go | 5 +--- core/share.go | 4 +-- core/storage/storagetest/fake_storage.go | 13 +++------- core/user.go | 4 +-- db/db.go | 16 ++++++------ log/log.go | 24 ++++++++--------- model/datastore.go | 2 +- model/get_entity.go | 2 +- model/mediafile.go | 2 +- model/metadata/metadata.go | 4 +-- model/metadata/persistent_ids.go | 4 +-- model/scanner.go | 8 +++--- model/share.go | 4 +-- model/tag.go | 6 ++--- persistence/album_repository.go | 18 ++++++------- persistence/album_repository_test.go | 8 +++--- persistence/artist_repository.go | 8 +++--- persistence/folder_repository.go | 5 ++-- persistence/genre_repository.go | 6 ++--- persistence/genre_repository_test.go | 4 +-- persistence/helpers.go | 4 +-- persistence/library_repository.go | 10 +++---- persistence/mediafile_repository.go | 6 ++--- persistence/mediafile_repository_test.go | 6 ++--- persistence/persistence.go | 2 +- persistence/player_repository.go | 10 +++---- persistence/playlist_repository.go | 14 +++++----- persistence/playlist_track_repository.go | 6 ++--- persistence/playqueue_repository.go | 4 +-- persistence/radio_repository.go | 12 ++++----- persistence/scrobble_buffer_repository.go | 2 +- .../scrobble_buffer_repository_test.go | 2 +- persistence/scrobble_repository.go | 2 +- persistence/share_repository.go | 10 +++---- persistence/share_repository_test.go | 6 ++--- persistence/sql_annotations.go | 8 +++--- persistence/sql_annotations_test.go | 10 +++---- persistence/sql_base_repository.go | 14 +++++----- persistence/sql_bookmarks.go | 2 +- persistence/sql_restful_test.go | 14 +++++----- persistence/sql_tags.go | 8 +++--- persistence/tag_library_filtering_test.go | 12 ++++----- persistence/tag_repository_test.go | 8 +++--- persistence/transcoding_repository.go | 10 +++---- plugins/capabilities.go | 16 ++++-------- plugins/host_websocket.go | 5 ++-- scanner/ignore_checker.go | 4 +-- scanner/metadata_old/metadata.go | 4 +-- scheduler/log_adapter.go | 8 +++--- server/auth.go | 6 ++--- server/auth_test.go | 14 +++++----- server/middlewares.go | 2 +- server/nativeapi/config.go | 19 +++++++------- server/nativeapi/config_test.go | 8 +++--- server/nativeapi/native_api.go | 2 +- server/nativeapi/playlists.go | 2 +- server/nativeapi/translations.go | 8 +++--- server/nativeapi/translations_test.go | 4 +-- server/serve_index.go | 6 ++--- server/server.go | 4 +-- server/subsonic/api.go | 2 +- server/subsonic/helpers.go | 8 +++--- server/subsonic/media_retrieval.go | 7 ++--- server/subsonic/responses/responses.go | 2 +- .../responses/responses_suite_test.go | 6 ++--- server/subsonic/searching_test.go | 2 +- tests/mock_library_repo.go | 10 +++---- tests/mock_mediafile_repo.go | 6 ++--- tests/mock_plugin_repo.go | 10 +++---- tests/mock_share_repo.go | 6 ++--- tests/mock_user_repo.go | 4 +-- utils/cache/file_haunter_test.go | 2 +- utils/hasher/hasher_test.go | 2 +- utils/index_group_parser.go | 4 +-- utils/pl/pipelines.go | 2 +- utils/pl/pipelines_test.go | 8 +++--- utils/random/number_test.go | 2 +- utils/random/weighted_random_chooser_test.go | 10 +++---- utils/singleton/singleton.go | 2 +- utils/singleton/singleton_test.go | 2 +- utils/str/sanitize_strings.go | 4 +-- utils/str/str.go | 5 +--- 102 files changed, 322 insertions(+), 352 deletions(-) diff --git a/adapters/deezer/client_auth_test.go b/adapters/deezer/client_auth_test.go index 915da39b..59add709 100644 --- a/adapters/deezer/client_auth_test.go +++ b/adapters/deezer/client_auth_test.go @@ -252,7 +252,7 @@ var _ = Describe("JWT Authentication", func() { // Writer goroutine wg.Go(func() { - for i := 0; i < 100; i++ { + for i := range 100 { cache.set(fmt.Sprintf("token-%d", i), 1*time.Hour) time.Sleep(1 * time.Millisecond) } @@ -260,7 +260,7 @@ var _ = Describe("JWT Authentication", func() { // Reader goroutine wg.Go(func() { - for i := 0; i < 100; i++ { + for range 100 { cache.get() time.Sleep(1 * time.Millisecond) } diff --git a/adapters/gotaglib/gotaglib.go b/adapters/gotaglib/gotaglib.go index 1d5e45a7..8d166d9a 100644 --- a/adapters/gotaglib/gotaglib.go +++ b/adapters/gotaglib/gotaglib.go @@ -255,7 +255,7 @@ func parseTIPL(tags map[string][]string) { } var currentRole string var currentValue []string - for _, part := range strings.Split(tipl[0], " ") { + for part := range strings.SplitSeq(tipl[0], " ") { if _, ok := tiplMapping[part]; ok { addRole(currentRole, currentValue) currentRole = part diff --git a/adapters/lastfm/auth_router.go b/adapters/lastfm/auth_router.go index 5a9b060e..0052f73d 100644 --- a/adapters/lastfm/auth_router.go +++ b/adapters/lastfm/auth_router.go @@ -65,7 +65,7 @@ func (s *Router) routes() http.Handler { } func (s *Router) getLinkStatus(w http.ResponseWriter, r *http.Request) { - resp := map[string]interface{}{ + resp := map[string]any{ "apiKey": s.apiKey, } u, _ := request.UserFrom(r.Context()) diff --git a/adapters/listenbrainz/auth_router.go b/adapters/listenbrainz/auth_router.go index 2382aeb7..7cb9eb16 100644 --- a/adapters/listenbrainz/auth_router.go +++ b/adapters/listenbrainz/auth_router.go @@ -60,7 +60,7 @@ func (s *Router) routes() http.Handler { } func (s *Router) getLinkStatus(w http.ResponseWriter, r *http.Request) { - resp := map[string]interface{}{} + resp := map[string]any{} u, _ := request.UserFrom(r.Context()) key, err := s.sessionKeys.Get(r.Context(), u.ID) if err != nil && !errors.Is(err, model.ErrNotFound) { @@ -107,7 +107,7 @@ func (s *Router) link(w http.ResponseWriter, r *http.Request) { return } - _ = rest.RespondWithJSON(w, http.StatusOK, map[string]interface{}{"status": resp.Valid, "user": resp.UserName}) + _ = rest.RespondWithJSON(w, http.StatusOK, map[string]any{"status": resp.Valid, "user": resp.UserName}) } func (s *Router) unlink(w http.ResponseWriter, r *http.Request) { diff --git a/adapters/listenbrainz/auth_router_test.go b/adapters/listenbrainz/auth_router_test.go index dc705dbc..c3861799 100644 --- a/adapters/listenbrainz/auth_router_test.go +++ b/adapters/listenbrainz/auth_router_test.go @@ -37,7 +37,7 @@ var _ = Describe("ListenBrainz Auth Router", func() { req = httptest.NewRequest("GET", "/listenbrainz/link", nil) r.getLinkStatus(resp, req) Expect(resp.Code).To(Equal(http.StatusOK)) - var parsed map[string]interface{} + var parsed map[string]any Expect(json.Unmarshal(resp.Body.Bytes(), &parsed)).To(BeNil()) Expect(parsed["status"]).To(Equal(false)) }) @@ -47,7 +47,7 @@ var _ = Describe("ListenBrainz Auth Router", func() { req = httptest.NewRequest("GET", "/listenbrainz/link", nil) r.getLinkStatus(resp, req) Expect(resp.Code).To(Equal(http.StatusOK)) - var parsed map[string]interface{} + var parsed map[string]any Expect(json.Unmarshal(resp.Body.Bytes(), &parsed)).To(BeNil()) Expect(parsed["status"]).To(Equal(true)) }) @@ -80,7 +80,7 @@ var _ = Describe("ListenBrainz Auth Router", func() { req = httptest.NewRequest("PUT", "/listenbrainz/link", strings.NewReader(`{"token": "tok-1"}`)) r.link(resp, req) Expect(resp.Code).To(Equal(http.StatusOK)) - var parsed map[string]interface{} + var parsed map[string]any Expect(json.Unmarshal(resp.Body.Bytes(), &parsed)).To(BeNil()) Expect(parsed["status"]).To(Equal(true)) Expect(parsed["user"]).To(Equal("ListenBrainzUser")) diff --git a/adapters/listenbrainz/client.go b/adapters/listenbrainz/client.go index 76e47cdc..0427ed07 100644 --- a/adapters/listenbrainz/client.go +++ b/adapters/listenbrainz/client.go @@ -75,14 +75,14 @@ const ( type listenInfo struct { ListenedAt int `json:"listened_at,omitempty"` - TrackMetadata trackMetadata `json:"track_metadata,omitempty"` + TrackMetadata trackMetadata `json:"track_metadata"` } type trackMetadata struct { ArtistName string `json:"artist_name,omitempty"` TrackName string `json:"track_name,omitempty"` ReleaseName string `json:"release_name,omitempty"` - AdditionalInfo additionalInfo `json:"additional_info,omitempty"` + AdditionalInfo additionalInfo `json:"additional_info"` } type additionalInfo struct { diff --git a/adapters/spotify/client.go b/adapters/spotify/client.go index 25b1f9ed..97517593 100644 --- a/adapters/spotify/client.go +++ b/adapters/spotify/client.go @@ -73,7 +73,7 @@ func (c *client) authorize(ctx context.Context) (string, error) { auth := c.id + ":" + c.secret req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) - response := map[string]interface{}{} + response := map[string]any{} err := c.makeRequest(req, &response) if err != nil { return "", err @@ -86,7 +86,7 @@ func (c *client) authorize(ctx context.Context) (string, error) { return "", errors.New("invalid response") } -func (c *client) makeRequest(req *http.Request, response interface{}) error { +func (c *client) makeRequest(req *http.Request, response any) error { log.Trace(req.Context(), fmt.Sprintf("Sending Spotify %s request", req.Method), "url", req.URL) resp, err := c.hc.Do(req) if err != nil { diff --git a/conf/configuration.go b/conf/configuration.go index 3ed1d653..e2aca6c1 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "runtime" + "slices" "strings" "time" @@ -433,7 +434,7 @@ func mapDeprecatedOption(legacyName, newName string) { func parseIniFileConfiguration() { cfgFile := viper.ConfigFileUsed() if strings.ToLower(filepath.Ext(cfgFile)) == ".ini" { - var iniConfig map[string]interface{} + var iniConfig map[string]any err := viper.Unmarshal(&iniConfig) if err != nil { _, _ = fmt.Fprintln(os.Stderr, "FATAL: Error parsing config:", err) @@ -466,7 +467,7 @@ func disableExternalServices() { } func validatePlaylistsPath() error { - for _, path := range strings.Split(Server.PlaylistsPath, string(filepath.ListSeparator)) { + for path := range strings.SplitSeq(Server.PlaylistsPath, string(filepath.ListSeparator)) { _, err := doublestar.Match(path, "") if err != nil { log.Error("Invalid PlaylistsPath", "path", path, err) @@ -480,7 +481,7 @@ func validatePlaylistsPath() error { // It trims whitespace from each entry and ensures at least [DefaultInfoLanguage] is returned. func parseLanguages(lang string) []string { var languages []string - for _, l := range strings.Split(lang, ",") { + for l := range strings.SplitSeq(lang, ",") { l = strings.TrimSpace(l) if l != "" { languages = append(languages, l) @@ -494,13 +495,7 @@ func parseLanguages(lang string) []string { func validatePurgeMissingOption() error { allowedValues := []string{consts.PurgeMissingNever, consts.PurgeMissingAlways, consts.PurgeMissingFull} - valid := false - for _, v := range allowedValues { - if v == Server.Scanner.PurgeMissing { - valid = true - break - } - } + valid := slices.Contains(allowedValues, Server.Scanner.PurgeMissing) if !valid { err := fmt.Errorf("invalid Scanner.PurgeMissing value: '%s'. Must be one of: %v", Server.Scanner.PurgeMissing, allowedValues) log.Error(err.Error()) diff --git a/core/agents/agents_test.go b/core/agents/agents_test.go index 1d5ae464..50285a08 100644 --- a/core/agents/agents_test.go +++ b/core/agents/agents_test.go @@ -365,7 +365,7 @@ var _ = Describe("Agents", func() { }) type mockAgent struct { - Args []interface{} + Args []any Err error } @@ -374,7 +374,7 @@ func (a *mockAgent) AgentName() string { } func (a *mockAgent) GetArtistMBID(_ context.Context, id string, name string) (string, error) { - a.Args = []interface{}{id, name} + a.Args = []any{id, name} if a.Err != nil { return "", a.Err } @@ -382,7 +382,7 @@ func (a *mockAgent) GetArtistMBID(_ context.Context, id string, name string) (st } func (a *mockAgent) GetArtistURL(_ context.Context, id, name, mbid string) (string, error) { - a.Args = []interface{}{id, name, mbid} + a.Args = []any{id, name, mbid} if a.Err != nil { return "", a.Err } @@ -390,7 +390,7 @@ func (a *mockAgent) GetArtistURL(_ context.Context, id, name, mbid string) (stri } func (a *mockAgent) GetArtistBiography(_ context.Context, id, name, mbid string) (string, error) { - a.Args = []interface{}{id, name, mbid} + a.Args = []any{id, name, mbid} if a.Err != nil { return "", a.Err } @@ -398,7 +398,7 @@ func (a *mockAgent) GetArtistBiography(_ context.Context, id, name, mbid string) } func (a *mockAgent) GetArtistImages(_ context.Context, id, name, mbid string) ([]ExternalImage, error) { - a.Args = []interface{}{id, name, mbid} + a.Args = []any{id, name, mbid} if a.Err != nil { return nil, a.Err } @@ -409,7 +409,7 @@ func (a *mockAgent) GetArtistImages(_ context.Context, id, name, mbid string) ([ } func (a *mockAgent) GetSimilarArtists(_ context.Context, id, name, mbid string, limit int) ([]Artist, error) { - a.Args = []interface{}{id, name, mbid, limit} + a.Args = []any{id, name, mbid, limit} if a.Err != nil { return nil, a.Err } @@ -420,7 +420,7 @@ func (a *mockAgent) GetSimilarArtists(_ context.Context, id, name, mbid string, } func (a *mockAgent) GetArtistTopSongs(_ context.Context, id, artistName, mbid string, count int) ([]Song, error) { - a.Args = []interface{}{id, artistName, mbid, count} + a.Args = []any{id, artistName, mbid, count} if a.Err != nil { return nil, a.Err } @@ -431,7 +431,7 @@ func (a *mockAgent) GetArtistTopSongs(_ context.Context, id, artistName, mbid st } func (a *mockAgent) GetAlbumInfo(ctx context.Context, name, artist, mbid string) (*AlbumInfo, error) { - a.Args = []interface{}{name, artist, mbid} + a.Args = []any{name, artist, mbid} if a.Err != nil { return nil, a.Err } @@ -444,7 +444,7 @@ func (a *mockAgent) GetAlbumInfo(ctx context.Context, name, artist, mbid string) } func (a *mockAgent) GetSimilarSongsByTrack(_ context.Context, id, name, artist, mbid string, count int) ([]Song, error) { - a.Args = []interface{}{id, name, artist, mbid, count} + a.Args = []any{id, name, artist, mbid, count} if a.Err != nil { return nil, a.Err } @@ -455,7 +455,7 @@ func (a *mockAgent) GetSimilarSongsByTrack(_ context.Context, id, name, artist, } func (a *mockAgent) GetSimilarSongsByAlbum(_ context.Context, id, name, artist, mbid string, count int) ([]Song, error) { - a.Args = []interface{}{id, name, artist, mbid, count} + a.Args = []any{id, name, artist, mbid, count} if a.Err != nil { return nil, a.Err } @@ -466,7 +466,7 @@ func (a *mockAgent) GetSimilarSongsByAlbum(_ context.Context, id, name, artist, } func (a *mockAgent) GetSimilarSongsByArtist(_ context.Context, id, name, mbid string, count int) ([]Song, error) { - a.Args = []interface{}{id, name, mbid, count} + a.Args = []any{id, name, mbid, count} if a.Err != nil { return nil, a.Err } @@ -488,12 +488,12 @@ type testImageAgent struct { Name string Images []ExternalImage Err error - Args []interface{} + Args []any } func (t *testImageAgent) AgentName() string { return t.Name } func (t *testImageAgent) GetArtistImages(_ context.Context, id, name, mbid string) ([]ExternalImage, error) { - t.Args = []interface{}{id, name, mbid} + t.Args = []any{id, name, mbid} return t.Images, t.Err } diff --git a/core/artwork/cache_warmer_test.go b/core/artwork/cache_warmer_test.go index 7ae3a16e..abf4f259 100644 --- a/core/artwork/cache_warmer_test.go +++ b/core/artwork/cache_warmer_test.go @@ -143,7 +143,7 @@ var _ = Describe("CacheWarmer", func() { It("processes items in batches", func() { cw := NewCacheWarmer(aw, fc).(*cacheWarmer) - for i := 0; i < 5; i++ { + for i := range 5 { cw.PreCache(model.MustParseArtworkID(fmt.Sprintf("al-%d", i))) } diff --git a/core/artwork/reader_album.go b/core/artwork/reader_album.go index cb4db97f..9fc9262c 100644 --- a/core/artwork/reader_album.go +++ b/core/artwork/reader_album.go @@ -79,7 +79,7 @@ func (a *albumArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, func (a *albumArtworkReader) fromCoverArtPriority(ctx context.Context, ffmpeg ffmpeg.FFmpeg, priority string) []sourceFunc { var ff []sourceFunc - for _, pattern := range strings.Split(strings.ToLower(priority), ",") { + for pattern := range strings.SplitSeq(strings.ToLower(priority), ",") { pattern = strings.TrimSpace(pattern) switch { case pattern == "embedded": diff --git a/core/artwork/reader_artist.go b/core/artwork/reader_artist.go index da8141a2..9905039b 100644 --- a/core/artwork/reader_artist.go +++ b/core/artwork/reader_artist.go @@ -99,7 +99,7 @@ func (a *artistReader) Reader(ctx context.Context) (io.ReadCloser, string, error func (a *artistReader) fromArtistArtPriority(ctx context.Context, priority string) []sourceFunc { var ff []sourceFunc - for _, pattern := range strings.Split(strings.ToLower(priority), ",") { + for pattern := range strings.SplitSeq(strings.ToLower(priority), ",") { pattern = strings.TrimSpace(pattern) switch { case pattern == "external": @@ -116,7 +116,7 @@ func (a *artistReader) fromArtistArtPriority(ctx context.Context, priority strin func fromArtistFolder(ctx context.Context, artistFolder string, pattern string) sourceFunc { return func() (io.ReadCloser, string, error) { current := artistFolder - for i := 0; i < maxArtistFolderTraversalDepth; i++ { + for range maxArtistFolderTraversalDepth { if reader, path, err := findImageInFolder(ctx, current, pattern); err == nil { return reader, path, nil } diff --git a/core/auth/auth.go b/core/auth/auth.go index ddd12767..e03820a1 100644 --- a/core/auth/auth.go +++ b/core/auth/auth.go @@ -4,6 +4,7 @@ import ( "cmp" "context" "crypto/sha256" + "maps" "sync" "time" @@ -53,9 +54,7 @@ func createBaseClaims() map[string]any { func CreatePublicToken(claims map[string]any) (string, error) { tokenClaims := createBaseClaims() - for k, v := range claims { - tokenClaims[k] = v - } + maps.Copy(tokenClaims, claims) _, token, err := TokenAuth.Encode(tokenClaims) return token, err @@ -66,9 +65,7 @@ func CreateExpiringPublicToken(exp time.Time, claims map[string]any) (string, er if !exp.IsZero() { tokenClaims[jwt.ExpirationKey] = exp.UTC().Unix() } - for k, v := range claims { - tokenClaims[k] = v - } + maps.Copy(tokenClaims, claims) _, token, err := TokenAuth.Encode(tokenClaims) return token, err @@ -100,7 +97,7 @@ func TouchToken(token jwt.Token) (string, error) { return newToken, err } -func Validate(tokenStr string) (map[string]interface{}, error) { +func Validate(tokenStr string) (map[string]any, error) { token, err := jwtauth.VerifyToken(TokenAuth, tokenStr) if err != nil { return nil, err diff --git a/core/auth/auth_test.go b/core/auth/auth_test.go index 504e56a5..38f6820f 100644 --- a/core/auth/auth_test.go +++ b/core/auth/auth_test.go @@ -45,7 +45,7 @@ var _ = Describe("Auth", func() { }) It("returns the claims from a valid JWT token", func() { - claims := map[string]interface{}{} + claims := map[string]any{} claims["iss"] = "issuer" claims["iat"] = time.Now().Unix() claims["exp"] = time.Now().Add(1 * time.Minute).Unix() @@ -58,7 +58,7 @@ var _ = Describe("Auth", func() { }) It("returns ErrExpired if the `exp` field is in the past", func() { - claims := map[string]interface{}{} + claims := map[string]any{} claims["iss"] = "issuer" claims["exp"] = time.Now().Add(-1 * time.Minute).Unix() _, tokenStr, err := auth.TokenAuth.Encode(claims) @@ -93,7 +93,7 @@ var _ = Describe("Auth", func() { Describe("TouchToken", func() { It("updates the expiration time", func() { yesterday := time.Now().Add(-oneDay) - claims := map[string]interface{}{} + claims := map[string]any{} claims["iss"] = "issuer" claims["exp"] = yesterday.Unix() token, _, err := auth.TokenAuth.Encode(claims) diff --git a/core/external/extdata_helper_test.go b/core/external/extdata_helper_test.go index 185a568b..8fabf449 100644 --- a/core/external/extdata_helper_test.go +++ b/core/external/extdata_helper_test.go @@ -40,7 +40,7 @@ func (m *mockArtistRepo) Get(id string) (*model.Artist, error) { // GetAll implements model.ArtistRepository. func (m *mockArtistRepo) GetAll(options ...model.QueryOptions) (model.Artists, error) { - argsSlice := make([]interface{}, len(options)) + argsSlice := make([]any, len(options)) for i, v := range options { argsSlice[i] = v } @@ -99,7 +99,7 @@ func (m *mockMediaFileRepo) GetAllByTags(_ model.TagName, _ []string, options .. // GetAll implements model.MediaFileRepository. func (m *mockMediaFileRepo) GetAll(options ...model.QueryOptions) (model.MediaFiles, error) { - argsSlice := make([]interface{}, len(options)) + argsSlice := make([]any, len(options)) for i, v := range options { argsSlice[i] = v } @@ -152,7 +152,7 @@ func (m *mockAlbumRepo) Get(id string) (*model.Album, error) { // GetAll implements model.AlbumRepository. func (m *mockAlbumRepo) GetAll(options ...model.QueryOptions) (model.Albums, error) { - argsSlice := make([]interface{}, len(options)) + argsSlice := make([]any, len(options)) for i, v := range options { argsSlice[i] = v } diff --git a/core/external/provider.go b/core/external/provider.go index 13a4caf1..6a4f4f5e 100644 --- a/core/external/provider.go +++ b/core/external/provider.go @@ -93,7 +93,7 @@ func NewProvider(ds model.DataStore, agents Agents) Provider { } func (e *provider) getAlbum(ctx context.Context, id string) (auxAlbum, error) { - var entity interface{} + var entity any entity, err := model.GetEntityByID(ctx, e.ds, id) if err != nil { return auxAlbum{}, err @@ -187,7 +187,7 @@ func (e *provider) populateAlbumInfo(ctx context.Context, album auxAlbum) (auxAl } func (e *provider) getArtist(ctx context.Context, id string) (auxArtist, error) { - var entity interface{} + var entity any entity, err := model.GetEntityByID(ctx, e.ds, id) if err != nil { return auxArtist{}, err diff --git a/core/library.go b/core/library.go index bb81a0a8..0bf3be9f 100644 --- a/core/library.go +++ b/core/library.go @@ -159,7 +159,7 @@ type libraryRepositoryWrapper struct { pluginManager PluginUnloader } -func (r *libraryRepositoryWrapper) Save(entity interface{}) (string, error) { +func (r *libraryRepositoryWrapper) Save(entity any) (string, error) { lib := entity.(*model.Library) if err := r.validateLibrary(lib); err != nil { return "", err @@ -191,7 +191,7 @@ func (r *libraryRepositoryWrapper) Save(entity interface{}) (string, error) { return strconv.Itoa(lib.ID), nil } -func (r *libraryRepositoryWrapper) Update(id string, entity interface{}, _ ...string) error { +func (r *libraryRepositoryWrapper) Update(id string, entity any, _ ...string) error { lib := entity.(*model.Library) libID, err := strconv.Atoi(id) if err != nil { diff --git a/core/maintenance.go b/core/maintenance.go index 750fd3a9..13d1141d 100644 --- a/core/maintenance.go +++ b/core/maintenance.go @@ -196,9 +196,7 @@ func (s *maintenanceService) getAffectedAlbumIDs(ctx context.Context, ids []stri // refreshStatsAsync refreshes artist and album statistics in background goroutines func (s *maintenanceService) refreshStatsAsync(ctx context.Context, affectedAlbumIDs []string) { // Refresh artist stats in background - s.wg.Add(1) - go func() { - defer s.wg.Done() + s.wg.Go(func() { bgCtx := request.AddValues(context.Background(), ctx) if _, err := s.ds.Artist(bgCtx).RefreshStats(true); err != nil { log.Error(bgCtx, "Error refreshing artist stats after deleting missing files", err) @@ -214,7 +212,7 @@ func (s *maintenanceService) refreshStatsAsync(ctx context.Context, affectedAlbu log.Debug(bgCtx, "Successfully refreshed album stats after deleting missing files", "count", len(affectedAlbumIDs)) } } - }() + }) } // Wait waits for all background goroutines to complete. diff --git a/core/playback/queue.go b/core/playback/queue.go index 0c230a61..d15eaad9 100644 --- a/core/playback/queue.go +++ b/core/playback/queue.go @@ -3,6 +3,7 @@ package playback import ( "fmt" "math/rand" + "strings" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" @@ -21,11 +22,11 @@ func NewQueue() *Queue { } func (pd *Queue) String() string { - filenames := "" + var filenames strings.Builder for idx, item := range pd.Items { - filenames += fmt.Sprint(idx) + ":" + item.Path + " " + filenames.WriteString(fmt.Sprint(idx) + ":" + item.Path + " ") } - return fmt.Sprintf("#Items: %d, idx: %d, files: %s", len(pd.Items), pd.Index, filenames) + return fmt.Sprintf("#Items: %d, idx: %d, files: %s", len(pd.Items), pd.Index, filenames.String()) } // returns the current mediafile or nil diff --git a/core/playlists.go b/core/playlists.go index 8ec5991c..1c070a11 100644 --- a/core/playlists.go +++ b/core/playlists.go @@ -45,7 +45,7 @@ func InPlaylistsPath(folder model.Folder) bool { return true } rel, _ := filepath.Rel(folder.LibraryPath, folder.AbsolutePath()) - for _, path := range strings.Split(conf.Server.PlaylistsPath, string(filepath.ListSeparator)) { + for path := range strings.SplitSeq(conf.Server.PlaylistsPath, string(filepath.ListSeparator)) { if match, _ := doublestar.Match(path, rel); match { return true } @@ -193,8 +193,8 @@ func (s *playlists) parseM3U(ctx context.Context, pls *model.Playlist, folder *m if line == "" || strings.HasPrefix(line, "#") { continue } - if strings.HasPrefix(line, "file://") { - line = strings.TrimPrefix(line, "file://") + if after, ok := strings.CutPrefix(line, "file://"); ok { + line = after line, _ = url.QueryUnescape(line) } if !model.IsAudioFile(line) { @@ -533,7 +533,7 @@ type nspFile struct { } func (i *nspFile) UnmarshalJSON(data []byte) error { - m := map[string]interface{}{} + m := map[string]any{} err := json.Unmarshal(data, &m) if err != nil { return err diff --git a/core/scrobbler/play_tracker.go b/core/scrobbler/play_tracker.go index a4080800..d1338ca3 100644 --- a/core/scrobbler/play_tracker.go +++ b/core/scrobbler/play_tracker.go @@ -212,10 +212,7 @@ func (p *playTracker) NowPlaying(ctx context.Context, playerId string, playerNam // Calculate TTL based on remaining track duration. If position exceeds track duration, // remaining is set to 0 to avoid negative TTL. - remaining := int(mf.Duration) - position - if remaining < 0 { - remaining = 0 - } + remaining := max(int(mf.Duration)-position, 0) // Add 5 seconds buffer to ensure the NowPlaying info is available slightly longer than the track duration. ttl := time.Duration(remaining+5) * time.Second _ = p.playMap.AddWithTTL(playerId, info, ttl) diff --git a/core/share.go b/core/share.go index eb5e6679..fa43a95d 100644 --- a/core/share.go +++ b/core/share.go @@ -87,7 +87,7 @@ func (r *shareRepositoryWrapper) newId() (string, error) { } } -func (r *shareRepositoryWrapper) Save(entity interface{}) (string, error) { +func (r *shareRepositoryWrapper) Save(entity any) (string, error) { s := entity.(*model.Share) id, err := r.newId() if err != nil { @@ -127,7 +127,7 @@ func (r *shareRepositoryWrapper) Save(entity interface{}) (string, error) { return id, err } -func (r *shareRepositoryWrapper) Update(id string, entity interface{}, _ ...string) error { +func (r *shareRepositoryWrapper) Update(id string, entity any, _ ...string) error { cols := []string{"description", "downloadable"} // TODO Better handling of Share expiration diff --git a/core/storage/storagetest/fake_storage.go b/core/storage/storagetest/fake_storage.go index 009b37d2..79ed3193 100644 --- a/core/storage/storagetest/fake_storage.go +++ b/core/storage/storagetest/fake_storage.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io/fs" + "maps" "net/url" "path" "testing/fstest" @@ -135,9 +136,7 @@ func (ffs *FakeFS) UpdateTags(filePath string, newTags map[string]any, when ...t if err != nil { panic(err) } - for k, v := range newTags { - tags[k] = v - } + maps.Copy(tags, newTags) data, _ := json.Marshal(tags) f.Data = data ffs.Touch(filePath, when...) @@ -180,9 +179,7 @@ func Track(num int, title string, tags ...map[string]any) map[string]any { ts["title"] = title ts["track"] = num for _, t := range tags { - for k, v := range t { - ts[k] = v - } + maps.Copy(ts, t) } return ts } @@ -200,9 +197,7 @@ func MP3(tags ...map[string]any) *fstest.MapFile { func File(tags ...map[string]any) *fstest.MapFile { ts := map[string]any{} for _, t := range tags { - for k, v := range t { - ts[k] = v - } + maps.Copy(ts, t) } modTime := time.Now() if mt, ok := ts[fakeFileInfoModTime]; !ok { diff --git a/core/user.go b/core/user.go index a0a5f537..f13e9016 100644 --- a/core/user.go +++ b/core/user.go @@ -50,12 +50,12 @@ type userRepositoryWrapper struct { } // Save implements rest.Persistable by delegating to the underlying repository. -func (r *userRepositoryWrapper) Save(entity interface{}) (string, error) { +func (r *userRepositoryWrapper) Save(entity any) (string, error) { return r.UserRepository.(rest.Persistable).Save(entity) } // Update implements rest.Persistable by delegating to the underlying repository. -func (r *userRepositoryWrapper) Update(id string, entity interface{}, cols ...string) error { +func (r *userRepositoryWrapper) Update(id string, entity any, cols ...string) error { return r.UserRepository.(rest.Persistable).Update(id, entity, cols...) } diff --git a/db/db.go b/db/db.go index 71bc082b..0945d1a0 100644 --- a/db/db.go +++ b/db/db.go @@ -126,7 +126,7 @@ func Optimize(ctx context.Context) { } log.Debug(ctx, "Optimizing open connections", "numConns", numConns) var conns []*sql.Conn - for i := 0; i < numConns; i++ { + for range numConns { conn, err := Db().Conn(ctx) conns = append(conns, conn) if err != nil { @@ -147,8 +147,8 @@ func Optimize(ctx context.Context) { type statusLogger struct{ numPending int } -func (*statusLogger) Fatalf(format string, v ...interface{}) { log.Fatal(fmt.Sprintf(format, v...)) } -func (l *statusLogger) Printf(format string, v ...interface{}) { +func (*statusLogger) Fatalf(format string, v ...any) { log.Fatal(fmt.Sprintf(format, v...)) } +func (l *statusLogger) Printf(format string, v ...any) { if len(v) < 1 { return } @@ -183,27 +183,27 @@ type logAdapter struct { silent bool } -func (l *logAdapter) Fatal(v ...interface{}) { +func (l *logAdapter) Fatal(v ...any) { log.Fatal(l.ctx, fmt.Sprint(v...)) } -func (l *logAdapter) Fatalf(format string, v ...interface{}) { +func (l *logAdapter) Fatalf(format string, v ...any) { log.Fatal(l.ctx, fmt.Sprintf(format, v...)) } -func (l *logAdapter) Print(v ...interface{}) { +func (l *logAdapter) Print(v ...any) { if !l.silent { log.Info(l.ctx, fmt.Sprint(v...)) } } -func (l *logAdapter) Println(v ...interface{}) { +func (l *logAdapter) Println(v ...any) { if !l.silent { log.Info(l.ctx, fmt.Sprintln(v...)) } } -func (l *logAdapter) Printf(format string, v ...interface{}) { +func (l *logAdapter) Printf(format string, v ...any) { if !l.silent { log.Info(l.ctx, fmt.Sprintf(format, v...)) } diff --git a/log/log.go b/log/log.go index 3e8597bd..7d294792 100644 --- a/log/log.go +++ b/log/log.go @@ -19,7 +19,7 @@ import ( type Level uint32 -type LevelFunc = func(ctx interface{}, msg interface{}, keyValuePairs ...interface{}) +type LevelFunc = func(ctx any, msg any, keyValuePairs ...any) var redacted = &Hook{ AcceptedLevels: logrus.AllLevels, @@ -152,7 +152,7 @@ func Redact(msg string) string { return r } -func NewContext(ctx context.Context, keyValuePairs ...interface{}) context.Context { +func NewContext(ctx context.Context, keyValuePairs ...any) context.Context { if ctx == nil { ctx = context.Background() } @@ -184,32 +184,32 @@ func IsGreaterOrEqualTo(level Level) bool { return shouldLog(level, 2) } -func Fatal(args ...interface{}) { +func Fatal(args ...any) { Log(LevelFatal, args...) os.Exit(1) } -func Error(args ...interface{}) { +func Error(args ...any) { Log(LevelError, args...) } -func Warn(args ...interface{}) { +func Warn(args ...any) { Log(LevelWarn, args...) } -func Info(args ...interface{}) { +func Info(args ...any) { Log(LevelInfo, args...) } -func Debug(args ...interface{}) { +func Debug(args ...any) { Log(LevelDebug, args...) } -func Trace(args ...interface{}) { +func Trace(args ...any) { Log(LevelTrace, args...) } -func Log(level Level, args ...interface{}) { +func Log(level Level, args ...any) { if !shouldLog(level, 3) { return } @@ -250,7 +250,7 @@ func shouldLog(requiredLevel Level, skip int) bool { return false } -func parseArgs(args []interface{}) (*logrus.Entry, string) { +func parseArgs(args []any) (*logrus.Entry, string) { var l *logrus.Entry var err error if args[0] == nil { @@ -289,7 +289,7 @@ func parseArgs(args []interface{}) (*logrus.Entry, string) { return l, "" } -func addFields(logger *logrus.Entry, keyValuePairs []interface{}) *logrus.Entry { +func addFields(logger *logrus.Entry, keyValuePairs []any) *logrus.Entry { for i := 0; i < len(keyValuePairs); i += 2 { switch name := keyValuePairs[i].(type) { case error: @@ -316,7 +316,7 @@ func addFields(logger *logrus.Entry, keyValuePairs []interface{}) *logrus.Entry return logger } -func extractLogger(ctx interface{}) (*logrus.Entry, error) { +func extractLogger(ctx any) (*logrus.Entry, error) { switch ctx := ctx.(type) { case *logrus.Entry: return ctx, nil diff --git a/model/datastore.go b/model/datastore.go index a187c495..94c3c362 100644 --- a/model/datastore.go +++ b/model/datastore.go @@ -41,7 +41,7 @@ type DataStore interface { Scrobble(ctx context.Context) ScrobbleRepository Plugin(ctx context.Context) PluginRepository - Resource(ctx context.Context, model interface{}) ResourceRepository + Resource(ctx context.Context, model any) ResourceRepository WithTx(block func(tx DataStore) error, scope ...string) error WithTxImmediate(block func(tx DataStore) error, scope ...string) error diff --git a/model/get_entity.go b/model/get_entity.go index f51d8c36..26f71839 100644 --- a/model/get_entity.go +++ b/model/get_entity.go @@ -5,7 +5,7 @@ import ( ) // TODO: Should the type be encoded in the ID? -func GetEntityByID(ctx context.Context, ds DataStore, id string) (interface{}, error) { +func GetEntityByID(ctx context.Context, ds DataStore, id string) (any, error) { ar, err := ds.Artist(ctx).Get(id) if err == nil { return ar, nil diff --git a/model/mediafile.go b/model/mediafile.go index 1d34cd76..a2fec6a1 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -140,7 +140,7 @@ func (mf MediaFile) Hash() string { } hash, _ := hashstructure.Hash(mf, opts) sum := md5.New() - sum.Write([]byte(fmt.Sprintf("%d", hash))) + sum.Write(fmt.Appendf(nil, "%d", hash)) sum.Write(mf.Tags.Hash()) sum.Write(mf.Participants.Hash()) return fmt.Sprintf("%x", sum.Sum(nil)) diff --git a/model/metadata/metadata.go b/model/metadata/metadata.go index 00ddcb2a..954505c9 100644 --- a/model/metadata/metadata.go +++ b/model/metadata/metadata.go @@ -268,8 +268,8 @@ func parseID3Pairs(name model.TagName, lowered model.Tags) []string { prefix := string(name) + ":" for tagKey, tagValues := range lowered { keyStr := string(tagKey) - if strings.HasPrefix(keyStr, prefix) { - keyPart := strings.TrimPrefix(keyStr, prefix) + if after, ok := strings.CutPrefix(keyStr, prefix); ok { + keyPart := after if keyPart == string(name) { keyPart = "" } diff --git a/model/metadata/persistent_ids.go b/model/metadata/persistent_ids.go index d4222441..70dfe053 100644 --- a/model/metadata/persistent_ids.go +++ b/model/metadata/persistent_ids.go @@ -49,8 +49,8 @@ func createGetPID(hash hashFunc) getPIDFunc { } getPID = func(mf model.MediaFile, md Metadata, spec string, prependLibId bool) string { pid := "" - fields := strings.Split(spec, "|") - for _, field := range fields { + fields := strings.SplitSeq(spec, "|") + for field := range fields { attributes := strings.Split(field, ",") hasValue := false values := slice.Map(attributes, func(attr string) string { diff --git a/model/scanner.go b/model/scanner.go index 389c77f8..54f81037 100644 --- a/model/scanner.go +++ b/model/scanner.go @@ -51,13 +51,13 @@ func ParseTargets(libFolders []string) ([]ScanTarget, error) { } // Split by the first colon - colonIdx := strings.Index(part, ":") - if colonIdx == -1 { + before, after, ok := strings.Cut(part, ":") + if !ok { return nil, fmt.Errorf("invalid target format: %q (expected libraryID:folderPath)", part) } - libIDStr := part[:colonIdx] - folderPath := part[colonIdx+1:] + libIDStr := before + folderPath := after libID, err := strconv.Atoi(libIDStr) if err != nil { diff --git a/model/share.go b/model/share.go index acb5fb42..ce0846d6 100644 --- a/model/share.go +++ b/model/share.go @@ -22,8 +22,8 @@ type Share struct { Format string `structs:"format" json:"format,omitempty"` MaxBitRate int `structs:"max_bit_rate" json:"maxBitRate,omitempty"` VisitCount int `structs:"visit_count" json:"visitCount,omitempty"` - CreatedAt time.Time `structs:"created_at" json:"createdAt,omitempty"` - UpdatedAt time.Time `structs:"updated_at" json:"updatedAt,omitempty"` + CreatedAt time.Time `structs:"created_at" json:"createdAt"` + UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"` Tracks MediaFiles `structs:"-" json:"tracks,omitempty"` Albums Albums `structs:"-" json:"albums,omitempty"` URL string `structs:"-" json:"-"` diff --git a/model/tag.go b/model/tag.go index 674f688c..1f6b24d2 100644 --- a/model/tag.go +++ b/model/tag.go @@ -144,10 +144,8 @@ func (t Tags) Merge(tags Tags) { } func (t Tags) Add(name TagName, v string) { - for _, existing := range t[name] { - if existing == v { - return - } + if slices.Contains(t[name], v) { + return } t[name] = append(t[name], v) } diff --git a/persistence/album_repository.go b/persistence/album_repository.go index adca058c..651953a1 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -145,11 +145,11 @@ func recentlyAddedSort() string { return "created_at" } -func recentlyPlayedFilter(string, interface{}) Sqlizer { +func recentlyPlayedFilter(string, any) Sqlizer { return Gt{"play_count": 0} } -func yearFilter(_ string, value interface{}) Sqlizer { +func yearFilter(_ string, value any) Sqlizer { return Or{ And{ Gt{"min_year": 0}, @@ -160,14 +160,14 @@ func yearFilter(_ string, value interface{}) Sqlizer { } } -func artistFilter(_ string, value interface{}) Sqlizer { +func artistFilter(_ string, value any) Sqlizer { return Or{ Exists("json_tree(participants, '$.albumartist')", Eq{"value": value}), Exists("json_tree(participants, '$.artist')", Eq{"value": value}), } } -func artistRoleFilter(name string, value interface{}) Sqlizer { +func artistRoleFilter(name string, value any) Sqlizer { roleName := strings.TrimSuffix(strings.TrimPrefix(name, "role_"), "_id") // Check if the role name is valid. If not, return an invalid filter @@ -177,7 +177,7 @@ func artistRoleFilter(name string, value interface{}) Sqlizer { return Exists(fmt.Sprintf("json_tree(participants, '$.%s')", roleName), Eq{"value": value}) } -func allRolesFilter(_ string, value interface{}) Sqlizer { +func allRolesFilter(_ string, value any) Sqlizer { return Like{"participants": fmt.Sprintf(`%%"%s"%%`, value)} } @@ -248,7 +248,7 @@ func (r *albumRepository) CopyAttributes(fromID, toID string, columns ...string) if err != nil { return fmt.Errorf("getting album to copy fields from: %w", err) } - to := make(map[string]interface{}) + to := make(map[string]any) for _, col := range columns { to[col] = from[col] } @@ -370,11 +370,11 @@ func (r *albumRepository) Count(options ...rest.QueryOptions) (int64, error) { return r.CountAll(r.parseRestOptions(r.ctx, options...)) } -func (r *albumRepository) Read(id string) (interface{}, error) { +func (r *albumRepository) Read(id string) (any, error) { return r.Get(id) } -func (r *albumRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *albumRepository) ReadAll(options ...rest.QueryOptions) (any, error) { return r.GetAll(r.parseRestOptions(r.ctx, options...)) } @@ -382,7 +382,7 @@ func (r *albumRepository) EntityName() string { return "album" } -func (r *albumRepository) NewInstance() interface{} { +func (r *albumRepository) NewInstance() any { return &model.Album{} } diff --git a/persistence/album_repository_test.go b/persistence/album_repository_test.go index 2705653a..8c82dc47 100644 --- a/persistence/album_repository_test.go +++ b/persistence/album_repository_test.go @@ -162,7 +162,7 @@ var _ = Describe("AlbumRepository", func() { newID := id.NewRandom() Expect(albumRepo.Put(&model.Album{LibraryID: 1, ID: newID, Name: "name", SongCount: songCount})).To(Succeed()) - for i := 0; i < playCount; i++ { + for range playCount { Expect(albumRepo.IncPlayCount(newID, time.Now())).To(Succeed()) } @@ -185,7 +185,7 @@ var _ = Describe("AlbumRepository", func() { newID := id.NewRandom() Expect(albumRepo.Put(&model.Album{LibraryID: 1, ID: newID, Name: "name", SongCount: songCount})).To(Succeed()) - for i := 0; i < playCount; i++ { + for range playCount { Expect(albumRepo.IncPlayCount(newID, time.Now())).To(Succeed()) } @@ -406,7 +406,7 @@ var _ = Describe("AlbumRepository", func() { sql, args, err := sqlizer.ToSql() Expect(err).ToNot(HaveOccurred()) Expect(sql).To(Equal(expectedSQL)) - Expect(args).To(Equal([]interface{}{artistID})) + Expect(args).To(Equal([]any{artistID})) }, Entry("artist role", "role_artist_id", "123", "exists (select 1 from json_tree(participants, '$.artist') where value = ?)"), @@ -428,7 +428,7 @@ var _ = Describe("AlbumRepository", func() { sql, args, err := sqlizer.ToSql() Expect(err).ToNot(HaveOccurred()) Expect(sql).To(Equal(fmt.Sprintf("exists (select 1 from json_tree(participants, '$.%s') where value = ?)", roleName))) - Expect(args).To(Equal([]interface{}{"test-id"})) + Expect(args).To(Equal([]any{"test-id"})) } }) diff --git a/persistence/artist_repository.go b/persistence/artist_repository.go index 5c34ace5..b888256a 100644 --- a/persistence/artist_repository.go +++ b/persistence/artist_repository.go @@ -164,7 +164,7 @@ func roleFilter(_ string, role any) Sqlizer { } // artistLibraryIdFilter filters artists based on library access through the library_artist table -func artistLibraryIdFilter(_ string, value interface{}) Sqlizer { +func artistLibraryIdFilter(_ string, value any) Sqlizer { return Eq{"library_artist.library_id": value} } @@ -534,11 +534,11 @@ func (r *artistRepository) Count(options ...rest.QueryOptions) (int64, error) { return r.CountAll(r.parseRestOptions(r.ctx, options...)) } -func (r *artistRepository) Read(id string) (interface{}, error) { +func (r *artistRepository) Read(id string) (any, error) { return r.Get(id) } -func (r *artistRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *artistRepository) ReadAll(options ...rest.QueryOptions) (any, error) { role := "total" if len(options) > 0 { if v, ok := options[0].Filters["role"].(string); ok { @@ -555,7 +555,7 @@ func (r *artistRepository) EntityName() string { return "artist" } -func (r *artistRepository) NewInstance() interface{} { +func (r *artistRepository) NewInstance() any { return &model.Artist{} } diff --git a/persistence/folder_repository.go b/persistence/folder_repository.go index f80cbde6..a4e20346 100644 --- a/persistence/folder_repository.go +++ b/persistence/folder_repository.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "maps" "os" "path/filepath" "slices" @@ -117,9 +118,7 @@ func (r folderRepository) GetFolderUpdateInfo(lib model.Library, targetPaths ... if err != nil { return nil, err } - for id, info := range batchResult { - result[id] = info - } + maps.Copy(result, batchResult) } return result, nil diff --git a/persistence/genre_repository.go b/persistence/genre_repository.go index 5857350a..53f324bf 100644 --- a/persistence/genre_repository.go +++ b/persistence/genre_repository.go @@ -33,18 +33,18 @@ func (r *genreRepository) GetAll(opt ...model.QueryOptions) (model.Genres, error // Override ResourceRepository methods to return Genre objects instead of Tag objects -func (r *genreRepository) Read(id string) (interface{}, error) { +func (r *genreRepository) Read(id string) (any, error) { sel := r.selectGenre().Where(Eq{"tag.id": id}) var res model.Genre err := r.queryOne(sel, &res) return &res, err } -func (r *genreRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *genreRepository) ReadAll(options ...rest.QueryOptions) (any, error) { return r.GetAll(r.parseRestOptions(r.ctx, options...)) } -func (r *genreRepository) NewInstance() interface{} { +func (r *genreRepository) NewInstance() any { return &model.Genre{} } diff --git a/persistence/genre_repository_test.go b/persistence/genre_repository_test.go index 67e84ce5..e3779725 100644 --- a/persistence/genre_repository_test.go +++ b/persistence/genre_repository_test.go @@ -182,7 +182,7 @@ var _ = Describe("GenreRepository", func() { It("should filter by name using like match", func() { // Test filtering by partial name match using the "name" filter which maps to containsFilter("tag_value") options := rest.QueryOptions{ - Filters: map[string]interface{}{"name": "%rock%"}, + Filters: map[string]any{"name": "%rock%"}, } count, err := restRepo.Count(options) Expect(err).ToNot(HaveOccurred()) @@ -289,7 +289,7 @@ var _ = Describe("GenreRepository", func() { It("should allow headless processes to apply explicit library_id filters", func() { // Filter by specific library genres, err := headlessRestRepo.ReadAll(rest.QueryOptions{ - Filters: map[string]interface{}{"library_id": 2}, + Filters: map[string]any{"library_id": 2}, }) Expect(err).ToNot(HaveOccurred()) diff --git a/persistence/helpers.go b/persistence/helpers.go index 73815ae4..fd6a9a4c 100644 --- a/persistence/helpers.go +++ b/persistence/helpers.go @@ -15,7 +15,7 @@ type PostMapper interface { PostMapArgs(map[string]any) error } -func toSQLArgs(rec interface{}) (map[string]interface{}, error) { +func toSQLArgs(rec any) (map[string]any, error) { m := structs.Map(rec) for k, v := range m { switch t := v.(type) { @@ -71,7 +71,7 @@ type existsCond struct { not bool } -func (e existsCond) ToSql() (string, []interface{}, error) { +func (e existsCond) ToSql() (string, []any, error) { sql, args, err := e.cond.ToSql() sql = fmt.Sprintf("exists (select 1 from %s where %s)", e.subTable, sql) if e.not { diff --git a/persistence/library_repository.go b/persistence/library_repository.go index f9ea6500..1d8e6f35 100644 --- a/persistence/library_repository.go +++ b/persistence/library_repository.go @@ -305,7 +305,7 @@ func (r *libraryRepository) Count(options ...rest.QueryOptions) (int64, error) { return r.CountAll(r.parseRestOptions(r.ctx, options...)) } -func (r *libraryRepository) Read(id string) (interface{}, error) { +func (r *libraryRepository) Read(id string) (any, error) { idInt, err := strconv.Atoi(id) if err != nil { log.Trace(r.ctx, "invalid library id: %s", id, err) @@ -314,7 +314,7 @@ func (r *libraryRepository) Read(id string) (interface{}, error) { return r.Get(idInt) } -func (r *libraryRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *libraryRepository) ReadAll(options ...rest.QueryOptions) (any, error) { return r.GetAll(r.parseRestOptions(r.ctx, options...)) } @@ -322,11 +322,11 @@ func (r *libraryRepository) EntityName() string { return "library" } -func (r *libraryRepository) NewInstance() interface{} { +func (r *libraryRepository) NewInstance() any { return &model.Library{} } -func (r *libraryRepository) Save(entity interface{}) (string, error) { +func (r *libraryRepository) Save(entity any) (string, error) { lib := entity.(*model.Library) lib.ID = 0 // Reset ID to ensure we create a new library err := r.Put(lib) @@ -336,7 +336,7 @@ func (r *libraryRepository) Save(entity interface{}) (string, error) { return strconv.Itoa(lib.ID), nil } -func (r *libraryRepository) Update(id string, entity interface{}, cols ...string) error { +func (r *libraryRepository) Update(id string, entity any, cols ...string) error { lib := entity.(*model.Library) idInt, err := strconv.Atoi(id) if err != nil { diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index 2054a34b..9b59a4bd 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -443,11 +443,11 @@ func (r *mediaFileRepository) Count(options ...rest.QueryOptions) (int64, error) return r.CountAll(r.parseRestOptions(r.ctx, options...)) } -func (r *mediaFileRepository) Read(id string) (interface{}, error) { +func (r *mediaFileRepository) Read(id string) (any, error) { return r.Get(id) } -func (r *mediaFileRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *mediaFileRepository) ReadAll(options ...rest.QueryOptions) (any, error) { return r.GetAll(r.parseRestOptions(r.ctx, options...)) } @@ -455,7 +455,7 @@ func (r *mediaFileRepository) EntityName() string { return "mediafile" } -func (r *mediaFileRepository) NewInstance() interface{} { +func (r *mediaFileRepository) NewInstance() any { return &model.MediaFile{} } diff --git a/persistence/mediafile_repository_test.go b/persistence/mediafile_repository_test.go index 6b646819..6b4d0db3 100644 --- a/persistence/mediafile_repository_test.go +++ b/persistence/mediafile_repository_test.go @@ -310,7 +310,7 @@ var _ = Describe("MediaRepository", func() { // Update "Old Song": created long ago, updated recently _, err := db.Update("media_file", - map[string]interface{}{ + map[string]any{ "created_at": oldTime, "updated_at": newTime, }, @@ -319,7 +319,7 @@ var _ = Describe("MediaRepository", func() { // Update "Middle Song": created and updated at the same middle time _, err = db.Update("media_file", - map[string]interface{}{ + map[string]any{ "created_at": middleTime, "updated_at": middleTime, }, @@ -328,7 +328,7 @@ var _ = Describe("MediaRepository", func() { // Update "New Song": created recently, updated long ago _, err = db.Update("media_file", - map[string]interface{}{ + map[string]any{ "created_at": newTime, "updated_at": oldTime, }, diff --git a/persistence/persistence.go b/persistence/persistence.go index afc7537e..83211bdd 100644 --- a/persistence/persistence.go +++ b/persistence/persistence.go @@ -97,7 +97,7 @@ func (s *SQLStore) Plugin(ctx context.Context) model.PluginRepository { return NewPluginRepository(ctx, s.getDBXBuilder()) } -func (s *SQLStore) Resource(ctx context.Context, m interface{}) model.ResourceRepository { +func (s *SQLStore) Resource(ctx context.Context, m any) model.ResourceRepository { switch m.(type) { case model.User: return s.User(ctx).(model.ResourceRepository) diff --git a/persistence/player_repository.go b/persistence/player_repository.go index 73c82075..6c833937 100644 --- a/persistence/player_repository.go +++ b/persistence/player_repository.go @@ -103,14 +103,14 @@ func (r *playerRepository) Count(options ...rest.QueryOptions) (int64, error) { return r.CountAll(r.parseRestOptions(r.ctx, options...)) } -func (r *playerRepository) Read(id string) (interface{}, error) { +func (r *playerRepository) Read(id string) (any, error) { sel := r.newRestSelect().Where(Eq{"player.id": id}) var res model.Player err := r.queryOne(sel, &res) return &res, err } -func (r *playerRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *playerRepository) ReadAll(options ...rest.QueryOptions) (any, error) { sel := r.newRestSelect(r.parseRestOptions(r.ctx, options...)) res := model.Players{} err := r.queryAll(sel, &res) @@ -121,7 +121,7 @@ func (r *playerRepository) EntityName() string { return "player" } -func (r *playerRepository) NewInstance() interface{} { +func (r *playerRepository) NewInstance() any { return &model.Player{} } @@ -130,7 +130,7 @@ func (r *playerRepository) isPermitted(p *model.Player) bool { return u.IsAdmin || p.UserId == u.ID } -func (r *playerRepository) Save(entity interface{}) (string, error) { +func (r *playerRepository) Save(entity any) (string, error) { t := entity.(*model.Player) if !r.isPermitted(t) { return "", rest.ErrPermissionDenied @@ -142,7 +142,7 @@ func (r *playerRepository) Save(entity interface{}) (string, error) { return id, err } -func (r *playerRepository) Update(id string, entity interface{}, cols ...string) error { +func (r *playerRepository) Update(id string, entity any, cols ...string) error { t := entity.(*model.Player) t.ID = id if !r.isPermitted(t) { diff --git a/persistence/playlist_repository.go b/persistence/playlist_repository.go index 8bdcea8e..220c1210 100644 --- a/persistence/playlist_repository.go +++ b/persistence/playlist_repository.go @@ -61,14 +61,14 @@ func NewPlaylistRepository(ctx context.Context, db dbx.Builder) model.PlaylistRe return r } -func playlistFilter(_ string, value interface{}) Sqlizer { +func playlistFilter(_ string, value any) Sqlizer { return Or{ substringFilter("playlist.name", value), substringFilter("playlist.comment", value), } } -func smartPlaylistFilter(string, interface{}) Sqlizer { +func smartPlaylistFilter(string, any) Sqlizer { return Or{ Eq{"rules": ""}, Eq{"rules": nil}, @@ -421,11 +421,11 @@ func (r *playlistRepository) Count(options ...rest.QueryOptions) (int64, error) return r.CountAll(r.parseRestOptions(r.ctx, options...)) } -func (r *playlistRepository) Read(id string) (interface{}, error) { +func (r *playlistRepository) Read(id string) (any, error) { return r.Get(id) } -func (r *playlistRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *playlistRepository) ReadAll(options ...rest.QueryOptions) (any, error) { return r.GetAll(r.parseRestOptions(r.ctx, options...)) } @@ -433,11 +433,11 @@ func (r *playlistRepository) EntityName() string { return "playlist" } -func (r *playlistRepository) NewInstance() interface{} { +func (r *playlistRepository) NewInstance() any { return &model.Playlist{} } -func (r *playlistRepository) Save(entity interface{}) (string, error) { +func (r *playlistRepository) Save(entity any) (string, error) { pls := entity.(*model.Playlist) pls.OwnerID = loggedUser(r.ctx).ID pls.ID = "" // Make sure we don't override an existing playlist @@ -448,7 +448,7 @@ func (r *playlistRepository) Save(entity interface{}) (string, error) { return pls.ID, err } -func (r *playlistRepository) Update(id string, entity interface{}, cols ...string) error { +func (r *playlistRepository) Update(id string, entity any, cols ...string) error { pls := dbPlaylist{Playlist: *entity.(*model.Playlist)} current, err := r.Get(id) if err != nil { diff --git a/persistence/playlist_track_repository.go b/persistence/playlist_track_repository.go index 666f227e..c72abb18 100644 --- a/persistence/playlist_track_repository.go +++ b/persistence/playlist_track_repository.go @@ -84,7 +84,7 @@ func (r *playlistTrackRepository) Count(options ...rest.QueryOptions) (int64, er return r.count(query, r.parseRestOptions(r.ctx, options...)) } -func (r *playlistTrackRepository) Read(id string) (interface{}, error) { +func (r *playlistTrackRepository) Read(id string) (any, error) { userID := loggedUser(r.ctx).ID sel := r.newSelect(). LeftJoin("annotation on ("+ @@ -128,7 +128,7 @@ func (r *playlistTrackRepository) GetAlbumIDs(options ...model.QueryOptions) ([] return ids, nil } -func (r *playlistTrackRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *playlistTrackRepository) ReadAll(options ...rest.QueryOptions) (any, error) { return r.GetAll(r.parseRestOptions(r.ctx, options...)) } @@ -136,7 +136,7 @@ func (r *playlistTrackRepository) EntityName() string { return "playlist_tracks" } -func (r *playlistTrackRepository) NewInstance() interface{} { +func (r *playlistTrackRepository) NewInstance() any { return &model.PlaylistTrack{} } diff --git a/persistence/playqueue_repository.go b/persistence/playqueue_repository.go index 74c80ee9..c952b42b 100644 --- a/persistence/playqueue_repository.go +++ b/persistence/playqueue_repository.go @@ -122,8 +122,8 @@ func (r *playQueueRepository) toModel(pq *playQueue) model.PlayQueue { UpdatedAt: pq.UpdatedAt, } if strings.TrimSpace(pq.Items) != "" { - tracks := strings.Split(pq.Items, ",") - for _, t := range tracks { + tracks := strings.SplitSeq(pq.Items, ",") + for t := range tracks { q.Items = append(q.Items, model.MediaFile{ID: t}) } } diff --git a/persistence/radio_repository.go b/persistence/radio_repository.go index cf253d06..543b76c5 100644 --- a/persistence/radio_repository.go +++ b/persistence/radio_repository.go @@ -63,7 +63,7 @@ func (r *radioRepository) Put(radio *model.Radio) error { return rest.ErrPermissionDenied } - var values map[string]interface{} + var values map[string]any radio.UpdatedAt = time.Now() @@ -97,19 +97,19 @@ func (r *radioRepository) EntityName() string { return "radio" } -func (r *radioRepository) NewInstance() interface{} { +func (r *radioRepository) NewInstance() any { return &model.Radio{} } -func (r *radioRepository) Read(id string) (interface{}, error) { +func (r *radioRepository) Read(id string) (any, error) { return r.Get(id) } -func (r *radioRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *radioRepository) ReadAll(options ...rest.QueryOptions) (any, error) { return r.GetAll(r.parseRestOptions(r.ctx, options...)) } -func (r *radioRepository) Save(entity interface{}) (string, error) { +func (r *radioRepository) Save(entity any) (string, error) { t := entity.(*model.Radio) if !r.isPermitted() { return "", rest.ErrPermissionDenied @@ -121,7 +121,7 @@ func (r *radioRepository) Save(entity interface{}) (string, error) { return t.ID, err } -func (r *radioRepository) Update(id string, entity interface{}, cols ...string) error { +func (r *radioRepository) Update(id string, entity any, cols ...string) error { t := entity.(*model.Radio) t.ID = id if !r.isPermitted() { diff --git a/persistence/scrobble_buffer_repository.go b/persistence/scrobble_buffer_repository.go index d0f88903..3cfb836b 100644 --- a/persistence/scrobble_buffer_repository.go +++ b/persistence/scrobble_buffer_repository.go @@ -51,7 +51,7 @@ func (r *scrobbleBufferRepository) UserIDs(service string) ([]string, error) { } func (r *scrobbleBufferRepository) Enqueue(service, userId, mediaFileId string, playTime time.Time) error { - ins := Insert(r.tableName).SetMap(map[string]interface{}{ + ins := Insert(r.tableName).SetMap(map[string]any{ "id": id.NewRandom(), "user_id": userId, "service": service, diff --git a/persistence/scrobble_buffer_repository_test.go b/persistence/scrobble_buffer_repository_test.go index 62423ff4..edf59ce4 100644 --- a/persistence/scrobble_buffer_repository_test.go +++ b/persistence/scrobble_buffer_repository_test.go @@ -24,7 +24,7 @@ var _ = Describe("ScrobbleBufferRepository", func() { id := id.NewRandom() ids = append(ids, id) - ins := squirrel.Insert("scrobble_buffer").SetMap(map[string]interface{}{ + ins := squirrel.Insert("scrobble_buffer").SetMap(map[string]any{ "id": id, "user_id": userId, "service": service, diff --git a/persistence/scrobble_repository.go b/persistence/scrobble_repository.go index dda98b76..219a4819 100644 --- a/persistence/scrobble_repository.go +++ b/persistence/scrobble_repository.go @@ -23,7 +23,7 @@ func NewScrobbleRepository(ctx context.Context, db dbx.Builder) model.ScrobbleRe func (r *scrobbleRepository) RecordScrobble(mediaFileID string, submissionTime time.Time) error { userID := loggedUser(r.ctx).ID - values := map[string]interface{}{ + values := map[string]any{ "media_file_id": mediaFileID, "user_id": userID, "submission_time": submissionTime.Unix(), diff --git a/persistence/share_repository.go b/persistence/share_repository.go index d943943e..9343e3e7 100644 --- a/persistence/share_repository.go +++ b/persistence/share_repository.go @@ -138,7 +138,7 @@ func sortByIdPosition(mfs model.MediaFiles, ids []string) model.MediaFiles { return sorted } -func (r *shareRepository) Update(id string, entity interface{}, cols ...string) error { +func (r *shareRepository) Update(id string, entity any, cols ...string) error { s := entity.(*model.Share) // TODO Validate record s.ID = id @@ -151,7 +151,7 @@ func (r *shareRepository) Update(id string, entity interface{}, cols ...string) return err } -func (r *shareRepository) Save(entity interface{}) (string, error) { +func (r *shareRepository) Save(entity any) (string, error) { s := entity.(*model.Share) // TODO Validate record u := loggedUser(r.ctx) @@ -179,18 +179,18 @@ func (r *shareRepository) EntityName() string { return "share" } -func (r *shareRepository) NewInstance() interface{} { +func (r *shareRepository) NewInstance() any { return &model.Share{} } -func (r *shareRepository) Read(id string) (interface{}, error) { +func (r *shareRepository) Read(id string) (any, error) { sel := r.selectShare().Where(Eq{"share.id": id}) var res model.Share err := r.queryOne(sel, &res) return &res, err } -func (r *shareRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *shareRepository) ReadAll(options ...rest.QueryOptions) (any, error) { sq := r.selectShare(r.parseRestOptions(r.ctx, options...)) res := model.Shares{} err := r.queryAll(sq, &res) diff --git a/persistence/share_repository_test.go b/persistence/share_repository_test.go index 25211517..96fd0c2b 100644 --- a/persistence/share_repository_test.go +++ b/persistence/share_repository_test.go @@ -47,7 +47,7 @@ var _ = Describe("ShareRepository", func() { _, err := GetDBXBuilder().NewQuery(` INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at) VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated}) - `).Bind(map[string]interface{}{ + `).Bind(map[string]any{ "id": shareID, "user": adminUser.ID, "desc": "Headless Test Share", @@ -79,7 +79,7 @@ var _ = Describe("ShareRepository", func() { _, err := GetDBXBuilder().NewQuery(` INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at) VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated}) - `).Bind(map[string]interface{}{ + `).Bind(map[string]any{ "id": shareID, "user": adminUser.ID, "desc": "Headless Get Share", @@ -110,7 +110,7 @@ var _ = Describe("ShareRepository", func() { _, err := GetDBXBuilder().NewQuery(` INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at) VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated}) - `).Bind(map[string]interface{}{ + `).Bind(map[string]any{ "id": shareID, "user": adminUser.ID, "desc": "SQL Test Share", diff --git a/persistence/sql_annotations.go b/persistence/sql_annotations.go index cf95d39a..07bd9697 100644 --- a/persistence/sql_annotations.go +++ b/persistence/sql_annotations.go @@ -66,7 +66,7 @@ func (r sqlRepository) annId(itemID ...string) And { } } -func (r sqlRepository) annUpsert(values map[string]interface{}, itemIDs ...string) error { +func (r sqlRepository) annUpsert(values map[string]any, itemIDs ...string) error { upd := Update(annotationTable).Where(r.annId(itemIDs...)) for f, v := range values { upd = upd.Set(f, v) @@ -90,12 +90,12 @@ func (r sqlRepository) annUpsert(values map[string]interface{}, itemIDs ...strin func (r sqlRepository) SetStar(starred bool, ids ...string) error { starredAt := time.Now() - return r.annUpsert(map[string]interface{}{"starred": starred, "starred_at": starredAt}, ids...) + return r.annUpsert(map[string]any{"starred": starred, "starred_at": starredAt}, ids...) } func (r sqlRepository) SetRating(rating int, itemID string) error { ratedAt := time.Now() - err := r.annUpsert(map[string]interface{}{"rating": rating, "rated_at": ratedAt}, itemID) + err := r.annUpsert(map[string]any{"rating": rating, "rated_at": ratedAt}, itemID) if err != nil { return err } @@ -121,7 +121,7 @@ func (r sqlRepository) IncPlayCount(itemID string, ts time.Time) error { if c == 0 || errors.Is(err, sql.ErrNoRows) { userID := loggedUser(r.ctx).ID - values := map[string]interface{}{} + values := map[string]any{} values["user_id"] = userID values["item_type"] = r.tableName values["item_id"] = itemID diff --git a/persistence/sql_annotations_test.go b/persistence/sql_annotations_test.go index 1848bbc8..15efc5dc 100644 --- a/persistence/sql_annotations_test.go +++ b/persistence/sql_annotations_test.go @@ -32,17 +32,17 @@ var _ = Describe("Annotation Filters", func() { Describe("annotationBoolFilter", func() { DescribeTable("creates correct SQL expressions", - func(field, value string, expectedSQL string, expectedArgs []interface{}) { + func(field, value string, expectedSQL string, expectedArgs []any) { sqlizer := annotationBoolFilter(field)(field, value) sql, args, err := sqlizer.ToSql() Expect(err).ToNot(HaveOccurred()) Expect(sql).To(Equal(expectedSQL)) Expect(args).To(Equal(expectedArgs)) }, - Entry("starred=true", "starred", "true", "COALESCE(starred, 0) > 0", []interface{}(nil)), - Entry("starred=false", "starred", "false", "COALESCE(starred, 0) = 0", []interface{}(nil)), - Entry("starred=True (case insensitive)", "starred", "True", "COALESCE(starred, 0) > 0", []interface{}(nil)), - Entry("rating=true", "rating", "true", "COALESCE(rating, 0) > 0", []interface{}(nil)), + Entry("starred=true", "starred", "true", "COALESCE(starred, 0) > 0", []any(nil)), + Entry("starred=false", "starred", "false", "COALESCE(starred, 0) = 0", []any(nil)), + Entry("starred=True (case insensitive)", "starred", "True", "COALESCE(starred, 0) > 0", []any(nil)), + Entry("rating=true", "rating", "true", "COALESCE(rating, 0) > 0", []any(nil)), ) It("returns nil if value is not a string", func() { diff --git a/persistence/sql_base_repository.go b/persistence/sql_base_repository.go index ce026a3c..fd263d37 100644 --- a/persistence/sql_base_repository.go +++ b/persistence/sql_base_repository.go @@ -196,7 +196,7 @@ func (r *sqlRepository) withTableName(filter filterFunc) filterFunc { } // libraryIdFilter is a filter function to be added to resources that have a library_id column. -func libraryIdFilter(_ string, value interface{}) Sqlizer { +func libraryIdFilter(_ string, value any) Sqlizer { return Eq{"library_id": value} } @@ -281,7 +281,7 @@ func (r sqlRepository) toSQL(sq Sqlizer) (string, dbx.Params, error) { return result, params, nil } -func (r sqlRepository) queryOne(sq Sqlizer, response interface{}) error { +func (r sqlRepository) queryOne(sq Sqlizer, response any) error { query, args, err := r.toSQL(sq) if err != nil { return err @@ -328,7 +328,7 @@ func queryWithStableResults[T any](r sqlRepository, sq SelectBuilder, options .. }, nil } -func (r sqlRepository) queryAll(sq SelectBuilder, response interface{}, options ...model.QueryOptions) error { +func (r sqlRepository) queryAll(sq SelectBuilder, response any, options ...model.QueryOptions) error { if len(options) > 0 && options[0].Offset > 0 { sq = r.optimizePagination(sq, options[0]) } @@ -347,7 +347,7 @@ func (r sqlRepository) queryAll(sq SelectBuilder, response interface{}, options } // queryAllSlice is a helper function to query a single column and return the result in a slice -func (r sqlRepository) queryAllSlice(sq SelectBuilder, response interface{}) error { +func (r sqlRepository) queryAllSlice(sq SelectBuilder, response any) error { query, args, err := r.toSQL(sq) if err != nil { return err @@ -394,7 +394,7 @@ func (r sqlRepository) count(countQuery SelectBuilder, options ...model.QueryOpt return res.Count, err } -func (r sqlRepository) putByMatch(filter Sqlizer, id string, m interface{}, colsToUpdate ...string) (string, error) { +func (r sqlRepository) putByMatch(filter Sqlizer, id string, m any, colsToUpdate ...string) (string, error) { if id != "" { return r.put(id, m, colsToUpdate...) } @@ -408,14 +408,14 @@ func (r sqlRepository) putByMatch(filter Sqlizer, id string, m interface{}, cols return r.put(res.ID, m, colsToUpdate...) } -func (r sqlRepository) put(id string, m interface{}, colsToUpdate ...string) (newId string, err error) { +func (r sqlRepository) put(id string, m any, colsToUpdate ...string) (newId string, err error) { values, err := toSQLArgs(m) if err != nil { return "", fmt.Errorf("error preparing values to write to DB: %w", err) } // If there's an ID, try to update first if id != "" { - updateValues := map[string]interface{}{} + updateValues := map[string]any{} // This is a map of the columns that need to be updated, if specified c2upd := slice.ToMap(colsToUpdate, func(s string) (string, struct{}) { diff --git a/persistence/sql_bookmarks.go b/persistence/sql_bookmarks.go index 9164aed9..19f16b23 100644 --- a/persistence/sql_bookmarks.go +++ b/persistence/sql_bookmarks.go @@ -37,7 +37,7 @@ func (r sqlRepository) bmkID(itemID ...string) And { func (r sqlRepository) bmkUpsert(itemID, comment string, position int64) error { client, _ := request.ClientFrom(r.ctx) user, _ := request.UserFrom(r.ctx) - values := map[string]interface{}{ + values := map[string]any{ "comment": comment, "position": position, "updated_at": time.Now(), diff --git a/persistence/sql_restful_test.go b/persistence/sql_restful_test.go index fd95fbb3..e3399df4 100644 --- a/persistence/sql_restful_test.go +++ b/persistence/sql_restful_test.go @@ -30,7 +30,7 @@ var _ = Describe("sqlRestful", func() { r.filterMappings = map[string]filterFunc{ "name": fullTextFilter("table"), } - options.Filters = map[string]interface{}{"name": "'"} + options.Filters = map[string]any{"name": "'"} Expect(r.parseRestFilters(context.Background(), options)).To(BeEmpty()) }) @@ -40,32 +40,32 @@ var _ = Describe("sqlRestful", func() { return nil }, } - options.Filters = map[string]interface{}{"name": "joe"} + options.Filters = map[string]any{"name": "joe"} Expect(r.parseRestFilters(context.Background(), options)).To(BeEmpty()) }) It("returns a '=' condition for 'id' filter", func() { - options.Filters = map[string]interface{}{"id": "123"} + options.Filters = map[string]any{"id": "123"} Expect(r.parseRestFilters(context.Background(), options)).To(Equal(squirrel.And{squirrel.Eq{"id": "123"}})) }) It("returns a 'in' condition for multiples 'id' filters", func() { - options.Filters = map[string]interface{}{"id": []string{"123", "456"}} + options.Filters = map[string]any{"id": []string{"123", "456"}} Expect(r.parseRestFilters(context.Background(), options)).To(Equal(squirrel.And{squirrel.Eq{"id": []string{"123", "456"}}})) }) It("returns a 'like' condition for other filters", func() { - options.Filters = map[string]interface{}{"name": "joe"} + options.Filters = map[string]any{"name": "joe"} Expect(r.parseRestFilters(context.Background(), options)).To(Equal(squirrel.And{squirrel.Like{"name": "joe%"}})) }) It("uses the custom filter", func() { r.filterMappings = map[string]filterFunc{ - "test": func(field string, value interface{}) squirrel.Sqlizer { + "test": func(field string, value any) squirrel.Sqlizer { return squirrel.Gt{field: value} }, } - options.Filters = map[string]interface{}{"test": 100} + options.Filters = map[string]any{"test": 100} Expect(r.parseRestFilters(context.Background(), options)).To(Equal(squirrel.And{squirrel.Gt{"test": 100}})) }) }) diff --git a/persistence/sql_tags.go b/persistence/sql_tags.go index 8c3c1e89..88acebb7 100644 --- a/persistence/sql_tags.go +++ b/persistence/sql_tags.go @@ -60,7 +60,7 @@ func tagIDFilter(name string, idValue any) Sqlizer { } // tagLibraryIdFilter filters tags based on library access through the library_tag table -func tagLibraryIdFilter(_ string, value interface{}) Sqlizer { +func tagLibraryIdFilter(_ string, value any) Sqlizer { return Eq{"library_tag.library_id": value} } @@ -142,14 +142,14 @@ func (r *baseTagRepository) Count(options ...rest.QueryOptions) (int64, error) { return r.count(sq, r.parseRestOptions(r.ctx, options...)) } -func (r *baseTagRepository) Read(id string) (interface{}, error) { +func (r *baseTagRepository) Read(id string) (any, error) { query := r.newSelect().Where(Eq{"id": id}) var res model.Tag err := r.queryOne(query, &res) return &res, err } -func (r *baseTagRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *baseTagRepository) ReadAll(options ...rest.QueryOptions) (any, error) { query := r.newSelect(r.parseRestOptions(r.ctx, options...)) var res model.TagList err := r.queryAll(query, &res) @@ -160,7 +160,7 @@ func (r *baseTagRepository) EntityName() string { return "tag" } -func (r *baseTagRepository) NewInstance() interface{} { +func (r *baseTagRepository) NewInstance() any { return model.Tag{} } diff --git a/persistence/tag_library_filtering_test.go b/persistence/tag_library_filtering_test.go index 77b91847..ddd89716 100644 --- a/persistence/tag_library_filtering_test.go +++ b/persistence/tag_library_filtering_test.go @@ -165,7 +165,7 @@ var _ = Describe("Tag Library Filtering", func() { It("should respect explicit library_id filters within accessible libraries", func() { tags := readAllTags(®ularUser, rest.QueryOptions{ - Filters: map[string]interface{}{"library_id": libraryID2}, + Filters: map[string]any{"library_id": libraryID2}, }) // Should see only tags from library 2: pop and rock(lib2) Expect(tags).To(HaveLen(2)) @@ -174,7 +174,7 @@ var _ = Describe("Tag Library Filtering", func() { It("should not return tags when filtering by inaccessible library", func() { tags := readAllTags(®ularUser, rest.QueryOptions{ - Filters: map[string]interface{}{"library_id": libraryID3}, + Filters: map[string]any{"library_id": libraryID3}, }) // Should return no tags since user can't access library 3 Expect(tags).To(HaveLen(0)) @@ -182,7 +182,7 @@ var _ = Describe("Tag Library Filtering", func() { It("should filter by library 1 correctly", func() { tags := readAllTags(®ularUser, rest.QueryOptions{ - Filters: map[string]interface{}{"library_id": libraryID1}, + Filters: map[string]any{"library_id": libraryID1}, }) // Should see only rock from library 1 Expect(tags).To(HaveLen(1)) @@ -227,7 +227,7 @@ var _ = Describe("Tag Library Filtering", func() { It("should allow headless processes to apply explicit library_id filters", func() { tags := readAllTags(nil, rest.QueryOptions{ - Filters: map[string]interface{}{"library_id": libraryID3}, + Filters: map[string]any{"library_id": libraryID3}, }) // Should see only jazz from library 3 Expect(tags).To(HaveLen(1)) @@ -243,7 +243,7 @@ var _ = Describe("Tag Library Filtering", func() { It("should respect explicit library_id filters", func() { tags := readAllTags(&adminUser, rest.QueryOptions{ - Filters: map[string]interface{}{"library_id": libraryID3}, + Filters: map[string]any{"library_id": libraryID3}, }) // Should see only jazz from library 3 Expect(tags).To(HaveLen(1)) @@ -252,7 +252,7 @@ var _ = Describe("Tag Library Filtering", func() { It("should filter by library 2 correctly", func() { tags := readAllTags(&adminUser, rest.QueryOptions{ - Filters: map[string]interface{}{"library_id": libraryID2}, + Filters: map[string]any{"library_id": libraryID2}, }) // Should see pop and rock from library 2 Expect(tags).To(HaveLen(2)) diff --git a/persistence/tag_repository_test.go b/persistence/tag_repository_test.go index c3947a9f..9a019c30 100644 --- a/persistence/tag_repository_test.go +++ b/persistence/tag_repository_test.go @@ -234,7 +234,7 @@ var _ = Describe("TagRepository", func() { It("should filter tags by partial value correctly", func() { options := rest.QueryOptions{ - Filters: map[string]interface{}{"name": "%rock%"}, // Tags containing 'rock' + Filters: map[string]any{"name": "%rock%"}, // Tags containing 'rock' } result, err := restRepo.ReadAll(options) Expect(err).ToNot(HaveOccurred()) @@ -249,7 +249,7 @@ var _ = Describe("TagRepository", func() { It("should filter tags by partial value using LIKE", func() { options := rest.QueryOptions{ - Filters: map[string]interface{}{"name": "%e%"}, // Tags containing 'e' + Filters: map[string]any{"name": "%e%"}, // Tags containing 'e' } result, err := restRepo.ReadAll(options) Expect(err).ToNot(HaveOccurred()) @@ -264,7 +264,7 @@ var _ = Describe("TagRepository", func() { It("should sort tags by value ascending", func() { options := rest.QueryOptions{ - Filters: map[string]interface{}{"name": "%r%"}, // Tags containing 'r' + Filters: map[string]any{"name": "%r%"}, // Tags containing 'r' Sort: "name", Order: "asc", } @@ -280,7 +280,7 @@ var _ = Describe("TagRepository", func() { It("should sort tags by value descending", func() { options := rest.QueryOptions{ - Filters: map[string]interface{}{"name": "%r%"}, // Tags containing 'r' + Filters: map[string]any{"name": "%r%"}, // Tags containing 'r' Sort: "name", Order: "desc", } diff --git a/persistence/transcoding_repository.go b/persistence/transcoding_repository.go index 125f5754..870da61c 100644 --- a/persistence/transcoding_repository.go +++ b/persistence/transcoding_repository.go @@ -52,11 +52,11 @@ func (r *transcodingRepository) Count(options ...rest.QueryOptions) (int64, erro return r.count(Select(), r.parseRestOptions(r.ctx, options...)) } -func (r *transcodingRepository) Read(id string) (interface{}, error) { +func (r *transcodingRepository) Read(id string) (any, error) { return r.Get(id) } -func (r *transcodingRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (r *transcodingRepository) ReadAll(options ...rest.QueryOptions) (any, error) { sel := r.newSelect(r.parseRestOptions(r.ctx, options...)).Columns("*") res := model.Transcodings{} err := r.queryAll(sel, &res) @@ -67,11 +67,11 @@ func (r *transcodingRepository) EntityName() string { return "transcoding" } -func (r *transcodingRepository) NewInstance() interface{} { +func (r *transcodingRepository) NewInstance() any { return &model.Transcoding{} } -func (r *transcodingRepository) Save(entity interface{}) (string, error) { +func (r *transcodingRepository) Save(entity any) (string, error) { if !loggedUser(r.ctx).IsAdmin { return "", rest.ErrPermissionDenied } @@ -83,7 +83,7 @@ func (r *transcodingRepository) Save(entity interface{}) (string, error) { return id, err } -func (r *transcodingRepository) Update(id string, entity interface{}, cols ...string) error { +func (r *transcodingRepository) Update(id string, entity any, cols ...string) error { if !loggedUser(r.ctx).IsAdmin { return rest.ErrPermissionDenied } diff --git a/plugins/capabilities.go b/plugins/capabilities.go index d52b27b0..81e683b6 100644 --- a/plugins/capabilities.go +++ b/plugins/capabilities.go @@ -1,5 +1,7 @@ package plugins +import "slices" + // Capability represents a plugin capability type. // Capabilities are detected by checking which functions a plugin exports. type Capability string @@ -25,11 +27,8 @@ func detectCapabilities(plugin functionExistsChecker) []Capability { var capabilities []Capability for cap, functions := range capabilityFunctions { - for _, fn := range functions { - if plugin.FunctionExists(fn) { - capabilities = append(capabilities, cap) - break // Found at least one function, plugin has this capability - } + if slices.ContainsFunc(functions, plugin.FunctionExists) { + capabilities = append(capabilities, cap) // Found at least one function, plugin has this capability } } @@ -38,10 +37,5 @@ func detectCapabilities(plugin functionExistsChecker) []Capability { // hasCapability checks if the given capabilities slice contains a specific capability. func hasCapability(capabilities []Capability, cap Capability) bool { - for _, c := range capabilities { - if c == cap { - return true - } - } - return false + return slices.Contains(capabilities, cap) } diff --git a/plugins/host_websocket.go b/plugins/host_websocket.go index 7d1b93de..06d905b4 100644 --- a/plugins/host_websocket.go +++ b/plugins/host_websocket.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "errors" "fmt" + "maps" "net/http" "net/url" "strings" @@ -200,9 +201,7 @@ func (s *webSocketServiceImpl) CloseConnection(ctx context.Context, connectionID func (s *webSocketServiceImpl) Close() error { s.mu.Lock() connections := make(map[string]*wsConnection, len(s.connections)) - for k, v := range s.connections { - connections[k] = v - } + maps.Copy(connections, s.connections) s.connections = make(map[string]*wsConnection) s.mu.Unlock() diff --git a/scanner/ignore_checker.go b/scanner/ignore_checker.go index da74293f..f0aedb07 100644 --- a/scanner/ignore_checker.go +++ b/scanner/ignore_checker.go @@ -65,8 +65,8 @@ func (ic *IgnoreChecker) PushAllParents(ctx context.Context, targetPath string) // Load patterns for each parent directory currentPath := "." - parts := strings.Split(path.Clean(targetPath), "/") - for _, part := range parts { + parts := strings.SplitSeq(path.Clean(targetPath), "/") + for part := range parts { if part == "." || part == "" { continue } diff --git a/scanner/metadata_old/metadata.go b/scanner/metadata_old/metadata.go index 6530ee8d..3ccbd896 100644 --- a/scanner/metadata_old/metadata.go +++ b/scanner/metadata_old/metadata.go @@ -215,8 +215,8 @@ func (t Tags) Lyrics() string { } for tag, value := range t.Tags { - if strings.HasPrefix(tag, "lyrics-") { - language := strings.TrimSpace(strings.TrimPrefix(tag, "lyrics-")) + if after, ok := strings.CutPrefix(tag, "lyrics-"); ok { + language := strings.TrimSpace(after) if language == "" { language = "xxx" diff --git a/scheduler/log_adapter.go b/scheduler/log_adapter.go index ccaab0cd..05781878 100644 --- a/scheduler/log_adapter.go +++ b/scheduler/log_adapter.go @@ -6,16 +6,16 @@ import ( type logger struct{} -func (l *logger) Info(msg string, keysAndValues ...interface{}) { - args := []interface{}{ +func (l *logger) Info(msg string, keysAndValues ...any) { + args := []any{ "Scheduler: " + msg, } args = append(args, keysAndValues...) log.Debug(args...) } -func (l *logger) Error(err error, msg string, keysAndValues ...interface{}) { - args := []interface{}{ +func (l *logger) Error(err error, msg string, keysAndValues ...any) { + args := []any{ "Scheduler: " + msg, } args = append(args, keysAndValues...) diff --git a/server/auth.go b/server/auth.go index 8588549a..86e63722 100644 --- a/server/auth.go +++ b/server/auth.go @@ -68,8 +68,8 @@ func doLogin(ds model.DataStore, username string, password string, w http.Respon _ = rest.RespondWithJSON(w, http.StatusOK, payload) } -func buildAuthPayload(user *model.User) map[string]interface{} { - payload := map[string]interface{}{ +func buildAuthPayload(user *model.User) map[string]any { + payload := map[string]any{ "id": user.ID, "name": user.Name, "username": user.UserName, @@ -288,7 +288,7 @@ func JWTRefresher(next http.Handler) http.Handler { }) } -func handleLoginFromHeaders(ds model.DataStore, r *http.Request) map[string]interface{} { +func handleLoginFromHeaders(ds model.DataStore, r *http.Request) map[string]any { username := UsernameFromConfig(r) if username == "" { username = UsernameFromExtAuthHeader(r) diff --git a/server/auth_test.go b/server/auth_test.go index 63329909..f6af6f0d 100644 --- a/server/auth_test.go +++ b/server/auth_test.go @@ -53,7 +53,7 @@ var _ = Describe("Auth", func() { It("returns the expected payload", func() { Expect(resp.Code).To(Equal(http.StatusOK)) - var parsed map[string]interface{} + var parsed map[string]any Expect(json.Unmarshal(resp.Body.Bytes(), &parsed)).To(BeNil()) Expect(parsed["isAdmin"]).To(Equal(true)) Expect(parsed["username"]).To(Equal("johndoe")) @@ -88,7 +88,7 @@ var _ = Describe("Auth", func() { serveIndex(ds, fs, nil)(resp, req) config := extractAppConfig(resp.Body.String()) - parsed := config["auth"].(map[string]interface{}) + parsed := config["auth"].(map[string]any) Expect(parsed["id"]).To(Equal("111")) }) @@ -106,7 +106,7 @@ var _ = Describe("Auth", func() { serveIndex(ds, fs, nil)(resp, req) config := extractAppConfig(resp.Body.String()) - parsed := config["auth"].(map[string]interface{}) + parsed := config["auth"].(map[string]any) Expect(parsed["id"]).To(Equal("111")) }) @@ -127,7 +127,7 @@ var _ = Describe("Auth", func() { serveIndex(ds, fs, nil)(resp, req) config := extractAppConfig(resp.Body.String()) - parsed := config["auth"].(map[string]interface{}) + parsed := config["auth"].(map[string]any) Expect(parsed["username"]).To(Equal(newUser)) }) @@ -137,7 +137,7 @@ var _ = Describe("Auth", func() { serveIndex(ds, fs, nil)(resp, req) config := extractAppConfig(resp.Body.String()) - parsed := config["auth"].(map[string]interface{}) + parsed := config["auth"].(map[string]any) Expect(parsed["id"]).To(Equal("111")) Expect(parsed["isAdmin"]).To(BeFalse()) @@ -182,7 +182,7 @@ var _ = Describe("Auth", func() { serveIndex(ds, fs, nil)(resp, req) config := extractAppConfig(resp.Body.String()) - parsed := config["auth"].(map[string]interface{}) + parsed := config["auth"].(map[string]any) Expect(parsed["id"]).To(Equal("111")) }) @@ -206,7 +206,7 @@ var _ = Describe("Auth", func() { login(ds)(resp, req) Expect(resp.Code).To(Equal(http.StatusOK)) - var parsed map[string]interface{} + var parsed map[string]any Expect(json.Unmarshal(resp.Body.Bytes(), &parsed)).To(BeNil()) Expect(parsed["isAdmin"]).To(Equal(false)) Expect(parsed["username"]).To(Equal("janedoe")) diff --git a/server/middlewares.go b/server/middlewares.go index 0ac2f3b4..5d6a1e59 100644 --- a/server/middlewares.go +++ b/server/middlewares.go @@ -37,7 +37,7 @@ func requestLogger(next http.Handler) http.Handler { status := ww.Status() message := fmt.Sprintf("HTTP: %s %s://%s%s", r.Method, scheme, r.Host, r.RequestURI) - logArgs := []interface{}{ + logArgs := []any{ r.Context(), message, "remoteAddr", r.RemoteAddr, diff --git a/server/nativeapi/config.go b/server/nativeapi/config.go index 9a86a9ad..086e8d3c 100644 --- a/server/nativeapi/config.go +++ b/server/nativeapi/config.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "slices" "strings" "github.com/navidrome/navidrome/conf" @@ -35,9 +36,9 @@ var sensitiveFieldsFullMask = []string{ } type configResponse struct { - ID string `json:"id"` - ConfigFile string `json:"configFile"` - Config map[string]interface{} `json:"config"` + ID string `json:"id"` + ConfigFile string `json:"configFile"` + Config map[string]any `json:"config"` } func redactValue(key string, value string) string { @@ -47,10 +48,8 @@ func redactValue(key string, value string) string { } // Check if this field should be fully masked - for _, field := range sensitiveFieldsFullMask { - if field == key { - return "****" - } + if slices.Contains(sensitiveFieldsFullMask, key) { + return "****" } // Check if this field should be partially masked @@ -69,7 +68,7 @@ func redactValue(key string, value string) string { } // applySensitiveFieldMasking recursively applies masking to sensitive fields in the configuration map -func applySensitiveFieldMasking(ctx context.Context, config map[string]interface{}, prefix string) { +func applySensitiveFieldMasking(ctx context.Context, config map[string]any, prefix string) { for key, value := range config { fullKey := key if prefix != "" { @@ -77,7 +76,7 @@ func applySensitiveFieldMasking(ctx context.Context, config map[string]interface } switch v := value.(type) { - case map[string]interface{}: + case map[string]any: // Recursively process nested maps applySensitiveFieldMasking(ctx, v, fullKey) case string: @@ -108,7 +107,7 @@ func getConfig(w http.ResponseWriter, r *http.Request) { } // Unmarshal back to map to get the structure with proper field names - var configMap map[string]interface{} + var configMap map[string]any err = json.Unmarshal(configBytes, &configMap) if err != nil { log.Error(ctx, "Error unmarshaling config to map", err) diff --git a/server/nativeapi/config_test.go b/server/nativeapi/config_test.go index 3b4e331a..546dd4f1 100644 --- a/server/nativeapi/config_test.go +++ b/server/nativeapi/config_test.go @@ -93,12 +93,12 @@ var _ = Describe("Config API", func() { Expect(json.Unmarshal(w.Body.Bytes(), &resp)).To(Succeed()) // Check LastFM.ApiKey (partially masked) - lastfm, ok := resp.Config["LastFM"].(map[string]interface{}) + lastfm, ok := resp.Config["LastFM"].(map[string]any) Expect(ok).To(BeTrue()) Expect(lastfm["ApiKey"]).To(Equal("s*************3")) // Check Spotify.Secret (partially masked) - spotify, ok := resp.Config["Spotify"].(map[string]interface{}) + spotify, ok := resp.Config["Spotify"].(map[string]any) Expect(ok).To(BeTrue()) Expect(spotify["Secret"]).To(Equal("s**************6")) @@ -109,7 +109,7 @@ var _ = Describe("Config API", func() { Expect(resp.Config["DevAutoCreateAdminPassword"]).To(Equal("****")) // Check Prometheus.Password (fully masked) - prometheus, ok := resp.Config["Prometheus"].(map[string]interface{}) + prometheus, ok := resp.Config["Prometheus"].(map[string]any) Expect(ok).To(BeTrue()) Expect(prometheus["Password"]).To(Equal("****")) }) @@ -128,7 +128,7 @@ var _ = Describe("Config API", func() { Expect(json.Unmarshal(w.Body.Bytes(), &resp)).To(Succeed()) // Check LastFM.ApiKey - should be preserved because it's sensitive - lastfm, ok := resp.Config["LastFM"].(map[string]interface{}) + lastfm, ok := resp.Config["LastFM"].(map[string]any) Expect(ok).To(BeTrue()) Expect(lastfm["ApiKey"]).To(Equal("")) diff --git a/server/nativeapi/native_api.go b/server/nativeapi/native_api.go index b9153409..91ddd0fa 100644 --- a/server/nativeapi/native_api.go +++ b/server/nativeapi/native_api.go @@ -95,7 +95,7 @@ func (api *Router) routes() http.Handler { return r } -func (api *Router) R(r chi.Router, pathPrefix string, model interface{}, persistable bool) { +func (api *Router) R(r chi.Router, pathPrefix string, model any, persistable bool) { constructor := func(ctx context.Context) rest.Repository { return api.ds.Resource(ctx, model) } diff --git a/server/nativeapi/playlists.go b/server/nativeapi/playlists.go index 17af1947..cc000692 100644 --- a/server/nativeapi/playlists.go +++ b/server/nativeapi/playlists.go @@ -218,7 +218,7 @@ func reorderItem(ds model.DataStore) http.HandlerFunc { return } - _, err = w.Write([]byte(fmt.Sprintf(`{"id":"%d"}`, id))) + _, err = w.Write(fmt.Appendf(nil, `{"id":"%d"}`, id)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } diff --git a/server/nativeapi/translations.go b/server/nativeapi/translations.go index d47b6e22..68571308 100644 --- a/server/nativeapi/translations.go +++ b/server/nativeapi/translations.go @@ -28,7 +28,7 @@ func newTranslationRepository(context.Context) rest.Repository { type translationRepository struct{} -func (r *translationRepository) Read(id string) (interface{}, error) { +func (r *translationRepository) Read(id string) (any, error) { translations, _ := loadTranslations() if t, ok := translations[id]; ok { return t, nil @@ -43,7 +43,7 @@ func (r *translationRepository) Count(...rest.QueryOptions) (int64, error) { } // ReadAll simple implementation, only returns IDs. Does not support any `options` -func (r *translationRepository) ReadAll(...rest.QueryOptions) (interface{}, error) { +func (r *translationRepository) ReadAll(...rest.QueryOptions) (any, error) { translations, _ := loadTranslations() var result []translation for _, t := range translations { @@ -57,7 +57,7 @@ func (r *translationRepository) EntityName() string { return "translation" } -func (r *translationRepository) NewInstance() interface{} { +func (r *translationRepository) NewInstance() any { return &translation{} } @@ -103,7 +103,7 @@ func loadTranslation(fsys fs.FS, fileName string) (translation translation, err if err != nil { return } - var out map[string]interface{} + var out map[string]any if err = json.Unmarshal(data, &out); err != nil { return } diff --git a/server/nativeapi/translations_test.go b/server/nativeapi/translations_test.go index c49c26c6..06ad7add 100644 --- a/server/nativeapi/translations_test.go +++ b/server/nativeapi/translations_test.go @@ -24,7 +24,7 @@ var _ = Describe("Translations", func() { filePath := filepath.Join(consts.I18nFolder, name) file, _ := fsys.Open(filePath) data, _ := io.ReadAll(file) - var out map[string]interface{} + var out map[string]any Expect(filepath.Ext(filePath)).To(Equal(".json"), filePath) Expect(json.Unmarshal(data, &out)).To(BeNil(), filePath) @@ -40,7 +40,7 @@ var _ = Describe("Translations", func() { Expect(err).To(BeNil()) Expect(tr.ID).To(Equal("en")) Expect(tr.Name).To(Equal("English")) - var out map[string]interface{} + var out map[string]any Expect(json.Unmarshal([]byte(tr.Data), &out)).To(BeNil()) }) }) diff --git a/server/serve_index.go b/server/serve_index.go index d70bf1d8..b9a3b3a2 100644 --- a/server/serve_index.go +++ b/server/serve_index.go @@ -39,7 +39,7 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl http.NotFound(w, r) return } - appConfig := map[string]interface{}{ + appConfig := map[string]any{ "version": consts.Version, "firstTime": firstTime, "variousArtistsId": consts.VariousArtistsID, @@ -95,7 +95,7 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl if version != "dev" { version = "v" + version } - data := map[string]interface{}{ + data := map[string]any{ "AppConfig": string(appConfigJson), "Version": version, } @@ -145,7 +145,7 @@ type shareTrack struct { Duration float32 `json:"duration,omitempty"` } -func addShareData(r *http.Request, data map[string]interface{}, shareInfo *model.Share) { +func addShareData(r *http.Request, data map[string]any, shareInfo *model.Share) { ctx := r.Context() if shareInfo == nil || shareInfo.ID == "" { return diff --git a/server/server.go b/server/server.go index 79cc5191..aa9043ba 100644 --- a/server/server.go +++ b/server/server.go @@ -80,8 +80,8 @@ func (s *Server) Run(ctx context.Context, addr string, port int, tlsCert string, // Create a listener based on the address type (either Unix socket or TCP) var listener net.Listener var err error - if strings.HasPrefix(addr, "unix:") { - socketPath := strings.TrimPrefix(addr, "unix:") + if after, ok := strings.CutPrefix(addr, "unix:"); ok { + socketPath := after listener, err = createUnixSocketFile(socketPath, conf.Server.UnixSocketPerm) if err != nil { return err diff --git a/server/subsonic/api.go b/server/subsonic/api.go index 6c46c825..aa7d0be9 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -319,7 +319,7 @@ func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub callback, _ := p.String("callback") wrapper := &responses.JsonWrapper{Subsonic: *payload} response, err = json.Marshal(wrapper) - response = []byte(fmt.Sprintf("%s(%s)", callback, response)) + response = fmt.Appendf(nil, "%s(%s)", callback, response) default: w.Header().Set("Content-Type", "application/xml") response, err = xml.Marshal(payload) diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index 5bc709ec..107e2313 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -34,10 +34,10 @@ func newResponse() *responses.Subsonic { type subError struct { code int32 - messages []interface{} + messages []any } -func newError(code int32, message ...interface{}) error { +func newError(code int32, message ...any) error { return subError{ code: code, messages: message, @@ -176,8 +176,8 @@ func isClientInList(clientList, client string) bool { if clientList == "" || client == "" { return false } - clients := strings.Split(clientList, ",") - for _, c := range clients { + clients := strings.SplitSeq(clientList, ",") + for c := range clients { if strings.TrimSpace(c) == client { return true } diff --git a/server/subsonic/media_retrieval.go b/server/subsonic/media_retrieval.go index a72e4865..c16779e3 100644 --- a/server/subsonic/media_retrieval.go +++ b/server/subsonic/media_retrieval.go @@ -5,6 +5,7 @@ import ( "errors" "io" "net/http" + "strings" "time" "github.com/navidrome/navidrome/conf" @@ -120,12 +121,12 @@ func (api *Router) GetLyrics(r *http.Request) (*responses.Subsonic, error) { lyricsResponse.Artist = artist lyricsResponse.Title = title - lyricsText := "" + var lyricsText strings.Builder for _, line := range structuredLyrics[0].Line { - lyricsText += line.Value + "\n" + lyricsText.WriteString(line.Value + "\n") } - lyricsResponse.Value = lyricsText + lyricsResponse.Value = lyricsText.String() return response, nil } diff --git a/server/subsonic/responses/responses.go b/server/subsonic/responses/responses.go index 4ddcbe00..8c4e330b 100644 --- a/server/subsonic/responses/responses.go +++ b/server/subsonic/responses/responses.go @@ -459,7 +459,7 @@ type PlayQueueByIndex struct { } type Bookmark struct { - Entry Child `xml:"entry,omitempty" json:"entry,omitempty"` + Entry Child `xml:"entry,omitempty" json:"entry"` Position int64 `xml:"position,attr,omitempty" json:"position,omitempty"` Username string `xml:"username,attr" json:"username"` Comment string `xml:"comment,attr" json:"comment"` diff --git a/server/subsonic/responses/responses_suite_test.go b/server/subsonic/responses/responses_suite_test.go index 8728b949..e10957af 100644 --- a/server/subsonic/responses/responses_suite_test.go +++ b/server/subsonic/responses/responses_suite_test.go @@ -26,17 +26,17 @@ type snapshotMatcher struct { c *cupaloy.Config } -func (matcher snapshotMatcher) Match(actual interface{}) (success bool, err error) { +func (matcher snapshotMatcher) Match(actual any) (success bool, err error) { actualJson := strings.TrimSpace(string(actual.([]byte))) err = matcher.c.SnapshotWithName(ginkgo.CurrentSpecReport().FullText(), actualJson) success = err == nil return } -func (matcher snapshotMatcher) FailureMessage(_ interface{}) (message string) { +func (matcher snapshotMatcher) FailureMessage(_ any) (message string) { return "Expected to match saved snapshot\n" } -func (matcher snapshotMatcher) NegatedFailureMessage(_ interface{}) (message string) { +func (matcher snapshotMatcher) NegatedFailureMessage(_ any) (message string) { return "Expected to not match saved snapshot\n" } diff --git a/server/subsonic/searching_test.go b/server/subsonic/searching_test.go index dfe3a45c..7f7de381 100644 --- a/server/subsonic/searching_test.go +++ b/server/subsonic/searching_test.go @@ -30,7 +30,7 @@ var _ = Describe("Search", func() { }) Context("musicFolderId parameter", func() { - assertQueryOptions := func(filter squirrel.Sqlizer, expectedQuery string, expectedArgs ...interface{}) { + assertQueryOptions := func(filter squirrel.Sqlizer, expectedQuery string, expectedArgs ...any) { GinkgoHelper() query, args, err := filter.ToSql() Expect(err).ToNot(HaveOccurred()) diff --git a/tests/mock_library_repo.go b/tests/mock_library_repo.go index 4d7539aa..3f0e576e 100644 --- a/tests/mock_library_repo.go +++ b/tests/mock_library_repo.go @@ -168,7 +168,7 @@ func (m *MockLibraryRepo) Count(options ...rest.QueryOptions) (int64, error) { return m.CountAll() } -func (m *MockLibraryRepo) Read(id string) (interface{}, error) { +func (m *MockLibraryRepo) Read(id string) (any, error) { idInt, _ := strconv.Atoi(id) mf, err := m.Get(idInt) if errors.Is(err, model.ErrNotFound) { @@ -177,7 +177,7 @@ func (m *MockLibraryRepo) Read(id string) (interface{}, error) { return mf, err } -func (m *MockLibraryRepo) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (m *MockLibraryRepo) ReadAll(options ...rest.QueryOptions) (any, error) { return m.GetAll() } @@ -185,13 +185,13 @@ func (m *MockLibraryRepo) EntityName() string { return "library" } -func (m *MockLibraryRepo) NewInstance() interface{} { +func (m *MockLibraryRepo) NewInstance() any { return &model.Library{} } // REST Repository methods (string-based IDs) -func (m *MockLibraryRepo) Save(entity interface{}) (string, error) { +func (m *MockLibraryRepo) Save(entity any) (string, error) { lib := entity.(*model.Library) if m.Err != nil { return "", m.Err @@ -216,7 +216,7 @@ func (m *MockLibraryRepo) Save(entity interface{}) (string, error) { return strconv.Itoa(lib.ID), nil } -func (m *MockLibraryRepo) Update(id string, entity interface{}, cols ...string) error { +func (m *MockLibraryRepo) Update(id string, entity any, cols ...string) error { lib := entity.(*model.Library) if m.Err != nil { return m.Err diff --git a/tests/mock_mediafile_repo.go b/tests/mock_mediafile_repo.go index 8542fc8d..2812fd50 100644 --- a/tests/mock_mediafile_repo.go +++ b/tests/mock_mediafile_repo.go @@ -218,7 +218,7 @@ func (m *MockMediaFileRepo) Count(...rest.QueryOptions) (int64, error) { return m.CountAll() } -func (m *MockMediaFileRepo) Read(id string) (interface{}, error) { +func (m *MockMediaFileRepo) Read(id string) (any, error) { mf, err := m.Get(id) if errors.Is(err, model.ErrNotFound) { return nil, rest.ErrNotFound @@ -226,7 +226,7 @@ func (m *MockMediaFileRepo) Read(id string) (interface{}, error) { return mf, err } -func (m *MockMediaFileRepo) ReadAll(...rest.QueryOptions) (interface{}, error) { +func (m *MockMediaFileRepo) ReadAll(...rest.QueryOptions) (any, error) { return m.GetAll() } @@ -234,7 +234,7 @@ func (m *MockMediaFileRepo) EntityName() string { return "mediafile" } -func (m *MockMediaFileRepo) NewInstance() interface{} { +func (m *MockMediaFileRepo) NewInstance() any { return &model.MediaFile{} } diff --git a/tests/mock_plugin_repo.go b/tests/mock_plugin_repo.go index 213d8300..dd08f6de 100644 --- a/tests/mock_plugin_repo.go +++ b/tests/mock_plugin_repo.go @@ -54,7 +54,7 @@ func (m *MockPluginRepo) Get(id string) (*model.Plugin, error) { return nil, model.ErrNotFound } -func (m *MockPluginRepo) Read(id string) (interface{}, error) { +func (m *MockPluginRepo) Read(id string) (any, error) { p, err := m.Get(id) if errors.Is(err, model.ErrNotFound) { return nil, rest.ErrNotFound @@ -151,21 +151,21 @@ func (m *MockPluginRepo) EntityName() string { return "plugin" } -func (m *MockPluginRepo) NewInstance() interface{} { +func (m *MockPluginRepo) NewInstance() any { return &model.Plugin{} } -func (m *MockPluginRepo) ReadAll(options ...rest.QueryOptions) (interface{}, error) { +func (m *MockPluginRepo) ReadAll(options ...rest.QueryOptions) (any, error) { return m.GetAll() } -func (m *MockPluginRepo) Save(entity interface{}) (string, error) { +func (m *MockPluginRepo) Save(entity any) (string, error) { p := entity.(*model.Plugin) err := m.Put(p) return p.ID, err } -func (m *MockPluginRepo) Update(id string, entity interface{}, cols ...string) error { +func (m *MockPluginRepo) Update(id string, entity any, cols ...string) error { p := entity.(*model.Plugin) p.ID = id return m.Put(p) diff --git a/tests/mock_share_repo.go b/tests/mock_share_repo.go index ef026ca3..22eb9446 100644 --- a/tests/mock_share_repo.go +++ b/tests/mock_share_repo.go @@ -10,13 +10,13 @@ type MockShareRepo struct { rest.Repository rest.Persistable - Entity interface{} + Entity any ID string Cols []string Error error } -func (m *MockShareRepo) Save(entity interface{}) (string, error) { +func (m *MockShareRepo) Save(entity any) (string, error) { if m.Error != nil { return "", m.Error } @@ -28,7 +28,7 @@ func (m *MockShareRepo) Save(entity interface{}) (string, error) { return s.ID, nil } -func (m *MockShareRepo) Update(id string, entity interface{}, cols ...string) error { +func (m *MockShareRepo) Update(id string, entity any, cols ...string) error { if m.Error != nil { return m.Error } diff --git a/tests/mock_user_repo.go b/tests/mock_user_repo.go index b74ae74d..cc05829f 100644 --- a/tests/mock_user_repo.go +++ b/tests/mock_user_repo.go @@ -149,7 +149,7 @@ func (u *MockedUserRepo) Delete(id string) error { return model.ErrNotFound } -func (u *MockedUserRepo) Save(entity interface{}) (string, error) { +func (u *MockedUserRepo) Save(entity any) (string, error) { usr := entity.(*model.User) if err := u.Put(usr); err != nil { return "", err @@ -157,7 +157,7 @@ func (u *MockedUserRepo) Save(entity interface{}) (string, error) { return usr.ID, nil } -func (u *MockedUserRepo) Update(id string, entity interface{}, cols ...string) error { +func (u *MockedUserRepo) Update(id string, entity any, cols ...string) error { if u.Error != nil { return u.Error } diff --git a/utils/cache/file_haunter_test.go b/utils/cache/file_haunter_test.go index bd1eb568..47440cc2 100644 --- a/utils/cache/file_haunter_test.go +++ b/utils/cache/file_haunter_test.go @@ -70,7 +70,7 @@ var _ = Describe("FileHaunter", func() { func createTestFiles(c *fscache.FSCache) error { // Create 5 normal files and 1 empty - for i := 0; i < 6; i++ { + for i := range 6 { name := fmt.Sprintf("stream-%v", i) var r fscache.ReadAtCloser if i < 5 { diff --git a/utils/hasher/hasher_test.go b/utils/hasher/hasher_test.go index 30cda3d0..7ecfe698 100644 --- a/utils/hasher/hasher_test.go +++ b/utils/hasher/hasher_test.go @@ -57,7 +57,7 @@ var _ = Describe("HashFunc", func() { }) It("does not cause race conditions", func() { - for i := 0; i < 1000; i++ { + for i := range 1000 { go func() { hashFunc := hasher.HashFunc() sum := hashFunc(strconv.Itoa(i), input) diff --git a/utils/index_group_parser.go b/utils/index_group_parser.go index 5622164c..786f5c45 100644 --- a/utils/index_group_parser.go +++ b/utils/index_group_parser.go @@ -23,8 +23,8 @@ var indexGroupsRx = regexp.MustCompile(`(.+)\((.+)\)`) func ParseIndexGroups(spec string) IndexGroups { parsed := make(IndexGroups) - split := strings.Split(spec, " ") - for _, g := range split { + split := strings.SplitSeq(spec, " ") + for g := range split { sub := indexGroupsRx.FindStringSubmatch(g) if len(sub) > 0 { for _, c := range sub[2] { diff --git a/utils/pl/pipelines.go b/utils/pl/pipelines.go index ed85c6b9..981b8688 100644 --- a/utils/pl/pipelines.go +++ b/utils/pl/pipelines.go @@ -152,7 +152,7 @@ func Tee[T any](ctx context.Context, in <-chan T) (<-chan T, <-chan T) { defer close(out2) for val := range ReadOrDone(ctx, in) { var out1, out2 = out1, out2 - for i := 0; i < 2; i++ { + for range 2 { select { case <-ctx.Done(): case out1 <- val: diff --git a/utils/pl/pipelines_test.go b/utils/pl/pipelines_test.go index f5da6e49..aa0b7faf 100644 --- a/utils/pl/pipelines_test.go +++ b/utils/pl/pipelines_test.go @@ -22,7 +22,7 @@ var _ = Describe("Pipeline", func() { Context("happy path", func() { It("calls the 'transform' function and returns values and errors", func() { inC := make(chan int, 4) - for i := 0; i < 4; i++ { + for i := range 4 { inC <- i } close(inC) @@ -48,7 +48,7 @@ var _ = Describe("Pipeline", func() { const numJobs = 100 It("starts multiple workers, respecting the limit", func() { inC := make(chan int, numJobs) - for i := 0; i < numJobs; i++ { + for i := range numJobs { inC <- i } close(inC) @@ -94,7 +94,7 @@ var _ = Describe("Pipeline", func() { BeforeEach(func() { in1 = make(chan int, 4) in2 = make(chan int, 4) - for i := 0; i < 4; i++ { + for i := range 4 { in1 <- i in2 <- i + 4 } @@ -126,7 +126,7 @@ var _ = Describe("Pipeline", func() { It("copies them to its output channel", func() { in := make(chan int) out := pl.ReadOrDone(context.Background(), in) - for i := 0; i < 4; i++ { + for i := range 4 { in <- i j := <-out Expect(i).To(Equal(j)) diff --git a/utils/random/number_test.go b/utils/random/number_test.go index b591ae25..985a3942 100644 --- a/utils/random/number_test.go +++ b/utils/random/number_test.go @@ -16,7 +16,7 @@ func TestRandom(t *testing.T) { var _ = Describe("number package", func() { Describe("Int64N", func() { It("should return a random int64", func() { - for i := 0; i < 10000; i++ { + for range 10000 { Expect(random.Int64N(100)).To(BeNumerically("<", 100)) } }) diff --git a/utils/random/weighted_random_chooser_test.go b/utils/random/weighted_random_chooser_test.go index 026ee92c..b336e6ca 100644 --- a/utils/random/weighted_random_chooser_test.go +++ b/utils/random/weighted_random_chooser_test.go @@ -9,7 +9,7 @@ var _ = Describe("WeightedChooser", func() { var w *WeightedChooser[int] BeforeEach(func() { w = NewWeightedChooser[int]() - for i := 0; i < 10; i++ { + for i := range 10 { w.Add(i, i+1) } }) @@ -23,7 +23,7 @@ var _ = Describe("WeightedChooser", func() { It("removes items", func() { Expect(w.Size()).To(Equal(10)) - for i := 0; i < 10; i++ { + for range 10 { Expect(w.Remove(0)).To(Succeed()) } Expect(w.Size()).To(Equal(0)) @@ -43,7 +43,7 @@ var _ = Describe("WeightedChooser", func() { }) It("returns all items from the list", func() { - for i := 0; i < 10; i++ { + for range 10 { Expect(w.Pick()).To(BeElementOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) } Expect(w.Size()).To(Equal(0)) @@ -61,11 +61,11 @@ var _ = Describe("WeightedChooser", func() { It("chooses based on weights", func() { counts := [10]int{} - for i := 0; i < 200000; i++ { + for range 200000 { c, _ := w.weightedChoice() counts[c] = counts[c] + 1 } - for i := 0; i < 9; i++ { + for i := range 9 { Expect(counts[i]).To(BeNumerically("<", counts[i+1])) } }) diff --git a/utils/singleton/singleton.go b/utils/singleton/singleton.go index 1066ae61..83f8c53a 100644 --- a/utils/singleton/singleton.go +++ b/utils/singleton/singleton.go @@ -9,7 +9,7 @@ import ( ) var ( - instances = map[string]interface{}{} + instances = map[string]any{} pending = map[string]chan struct{}{} lock sync.RWMutex ) diff --git a/utils/singleton/singleton_test.go b/utils/singleton/singleton_test.go index c58bafd9..08e63565 100644 --- a/utils/singleton/singleton_test.go +++ b/utils/singleton/singleton_test.go @@ -69,7 +69,7 @@ var _ = Describe("GetInstance", func() { done.Add(numCallsToDo) numInstancesCreated = 0 - for i := 0; i < numCallsToDo; i++ { + for range numCallsToDo { go func() { // This is needed to make sure the test does not hang if it fails defer GinkgoRecover() diff --git a/utils/str/sanitize_strings.go b/utils/str/sanitize_strings.go index ff8b2fb4..73608112 100644 --- a/utils/str/sanitize_strings.go +++ b/utils/str/sanitize_strings.go @@ -54,8 +54,8 @@ func SanitizeFieldForSortingNoArticle(originalValue string) string { } func RemoveArticle(name string) string { - articles := strings.Split(conf.Server.IgnoredArticles, " ") - for _, a := range articles { + articles := strings.SplitSeq(conf.Server.IgnoredArticles, " ") + for a := range articles { n := strings.TrimPrefix(name, a+" ") if n != name { return n diff --git a/utils/str/str.go b/utils/str/str.go index f662473d..177b4819 100644 --- a/utils/str/str.go +++ b/utils/str/str.go @@ -50,10 +50,7 @@ func TruncateRunes(s string, maxRunes int, suffix string) string { } suffixRunes := utf8.RuneCountInString(suffix) - truncateAt := maxRunes - suffixRunes - if truncateAt < 0 { - truncateAt = 0 - } + truncateAt := max(maxRunes-suffixRunes, 0) runes := []rune(s) if truncateAt >= len(runes) {