diff --git a/core/agents/agents.go b/core/agents/agents.go index 123eabb5c..348f7d4e7 100644 --- a/core/agents/agents.go +++ b/core/agents/agents.go @@ -141,11 +141,7 @@ type AlbumImageAgent struct { func (a *Agents) ArtistImageAgents() []ArtistImageAgent { var result []ArtistImageAgent for _, ea := range a.getEnabledAgentNames() { - ag := a.getAgent(ea) - if ag == nil { - continue - } - if retriever, ok := ag.(ArtistImageRetriever); ok { + if retriever, ok := a.getAgent(ea).(ArtistImageRetriever); ok { result = append(result, ArtistImageAgent{Name: ea.name, Retriever: retriever}) } } @@ -157,11 +153,7 @@ func (a *Agents) ArtistImageAgents() []ArtistImageAgent { func (a *Agents) AlbumImageAgents() []AlbumImageAgent { var result []AlbumImageAgent for _, ea := range a.getEnabledAgentNames() { - ag := a.getAgent(ea) - if ag == nil { - continue - } - if retriever, ok := ag.(AlbumImageRetriever); ok { + if retriever, ok := a.getAgent(ea).(AlbumImageRetriever); ok { result = append(result, AlbumImageAgent{Name: ea.name, Retriever: retriever}) } } diff --git a/core/artwork/blurhash/blurhash.go b/core/artwork/blurhash/blurhash.go index 3fe8efbec..44bf42cd4 100644 --- a/core/artwork/blurhash/blurhash.go +++ b/core/artwork/blurhash/blurhash.go @@ -57,13 +57,13 @@ func Encode(img image.Image, xComp, yComp int) (string, error) { lin := srgbToLinearTable() factors := make([][3]float64, xComp*yComp) - for y := 0; y < h; y++ { + for y := range h { row := rgba.Pix[y*rgba.Stride:] - for x := 0; x < w; x++ { + for x := range w { p := x * 4 lr, lg, lb := lin[row[p]], lin[row[p+1]], lin[row[p+2]] - for j := 0; j < yComp; j++ { - for i := 0; i < xComp; i++ { + for j := range yComp { + for i := range xComp { basis := cosX[i][x] * cosY[j][y] f := &factors[j*xComp+i] f[0] += basis * lr @@ -94,7 +94,7 @@ func Encode(img image.Image, xComp, yComp int) (string, error) { for _, f := range ac { actualMax = max(actualMax, math.Abs(f[0]), math.Abs(f[1]), math.Abs(f[2])) } - quantMax := int(math.Max(0, math.Min(82, math.Floor(actualMax*166-0.5)))) + quantMax := int(max(0, min(82, math.Floor(actualMax*166-0.5)))) maxVal = float64(quantMax+1) / 166 sb.WriteString(Encode83(quantMax, 1)) } else { @@ -141,7 +141,7 @@ func downscale(img image.Image) image.Image { } func quantAC(v, maxVal float64) int { - return int(math.Max(0, math.Min(18, math.Floor(signPow(v/maxVal, 0.5)*9+9.5)))) + return int(max(0, min(18, math.Floor(signPow(v/maxVal, 0.5)*9+9.5)))) } func signPow(v, exp float64) float64 { @@ -157,7 +157,7 @@ func srgbToLinear(v int) float64 { } func linearToSRGB(v float64) int { - v = math.Min(math.Max(0, v), 1) + v = min(max(0, v), 1) if v <= 0.0031308 { return int(v*12.92*255 + 0.5) } diff --git a/core/artwork/blurhash/blurhash_test.go b/core/artwork/blurhash/blurhash_test.go index b457eac64..1c937bc6f 100644 --- a/core/artwork/blurhash/blurhash_test.go +++ b/core/artwork/blurhash/blurhash_test.go @@ -22,8 +22,8 @@ func decode83(s string) int { func solidImage(w, h int, c color.NRGBA) image.Image { img := image.NewNRGBA(image.Rect(0, 0, w, h)) - for y := 0; y < h; y++ { - for x := 0; x < w; x++ { + for y := range h { + for x := range w { img.SetNRGBA(x, y, c) } } @@ -32,8 +32,8 @@ func solidImage(w, h int, c color.NRGBA) image.Image { func gradientImage(w, h int) image.Image { img := image.NewNRGBA(image.Rect(0, 0, w, h)) - for y := 0; y < h; y++ { - for x := 0; x < w; x++ { + for y := range h { + for x := range w { img.SetNRGBA(x, y, color.NRGBA{R: uint8(255 * x / w), G: uint8(255 * y / h), B: 128, A: 255}) } } diff --git a/core/artwork/disc.go b/core/artwork/disc.go index 049d2bf3f..21f596b60 100644 --- a/core/artwork/disc.go +++ b/core/artwork/disc.go @@ -15,6 +15,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/slice" ) // discArtworkReader resolves disc-level artwork from a library's folder images @@ -76,21 +77,17 @@ func newDiscArtworkReader(ctx context.Context, ds model.DataStore, artID model.A // Build disc folder set and find first track. mf.Path is already library-relative. var firstTrackRel string - allFolderIDs := make(map[string]bool) for _, mf := range mfs { - allFolderIDs[mf.FolderID] = true - if firstTrackRel == "" { + if mf.Path != "" { firstTrackRel = filepath.ToSlash(mf.Path) + break } } + folderIDs := slice.Unique(slice.Map(mfs, func(mf model.MediaFile) string { return mf.FolderID })) // Resolve folder IDs to library-relative paths discFoldersRel := make(map[string]bool) - if len(allFolderIDs) > 0 { - folderIDs := make([]string, 0, len(allFolderIDs)) - for id := range allFolderIDs { - folderIDs = append(folderIDs, id) - } + if len(folderIDs) > 0 { folders, err := ds.Folder(ctx).GetAll(model.QueryOptions{ Filters: squirrel.Eq{"folder.id": folderIDs}, }) @@ -144,8 +141,7 @@ func (d *discArtworkReader) fromDiscArtPriority(ctx context.Context, ffmpeg ffmp func (d *discArtworkReader) fromDiscSubtitle(ctx context.Context, subtitle string) sourceFunc { return func() (io.ReadCloser, string, error) { for _, file := range d.imgFiles { - name := path.Base(file) - stem := strings.TrimSuffix(name, path.Ext(name)) + stem := utils.BaseName(file) if !strings.EqualFold(stem, subtitle) { continue } diff --git a/core/artwork/e2e/playlist_test.go b/core/artwork/e2e/playlist_test.go index 2de601877..f5ac8a7e3 100644 --- a/core/artwork/e2e/playlist_test.go +++ b/core/artwork/e2e/playlist_test.go @@ -10,6 +10,7 @@ import ( "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/core/artwork" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/utils/slice" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -182,10 +183,7 @@ var _ = Describe("Playlist artwork resolution", func() { mfs, err := rds.MediaFile(rctx).GetAll(model.QueryOptions{}) Expect(err).ToNot(HaveOccurred()) Expect(mfs).To(HaveLen(4)) - ids := make([]string, 0, len(mfs)) - for _, mf := range mfs { - ids = append(ids, mf.ID) - } + ids := slice.Map(mfs, func(mf model.MediaFile) string { return mf.ID }) pl := model.Playlist{ID: "pl-8", Name: "Four", OwnerID: "admin-1"} pl.AddMediaFilesByID(ids) diff --git a/core/artwork/e2e/resolution_harness_test.go b/core/artwork/e2e/resolution_harness_test.go index ed27cd36b..e8e980909 100644 --- a/core/artwork/e2e/resolution_harness_test.go +++ b/core/artwork/e2e/resolution_harness_test.go @@ -9,6 +9,7 @@ import ( "image/color" "image/png" "io" + "maps" "os" "path/filepath" "strings" @@ -295,9 +296,7 @@ func pngBytes(label string) []byte { func trackFile(num int, title string, extra ...map[string]any) *fstest.MapFile { tags := storagetest.Track(num, title) for _, e := range extra { - for k, v := range e { - tags[k] = v - } + maps.Copy(tags, e) } return storagetest.MP3(tags) } diff --git a/core/artwork/folders_artist.go b/core/artwork/folders_artist.go index 9c27ac9ac..bf44b7e21 100644 --- a/core/artwork/folders_artist.go +++ b/core/artwork/folders_artist.go @@ -17,6 +17,8 @@ import ( "github.com/navidrome/navidrome/core" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/slice" "github.com/navidrome/navidrome/utils/str" ) @@ -72,13 +74,7 @@ func findImageInFolder(ctx context.Context, libFS fs.FS, relFolder, absFolder, p return nil, "", err } - var imagePaths []string - for _, m := range matches { - if !model.IsImageFile(m) { - continue - } - imagePaths = append(imagePaths, m) - } + imagePaths := slice.Filter(matches, model.IsImageFile) // Prefer base filenames over numeric-suffixed ones (artist.jpg before artist.1.jpg) slices.SortFunc(imagePaths, compareImageFiles) @@ -157,7 +153,7 @@ func findImageInArtistFolder(folder, mbzArtistID, artistName string) string { continue } name := entry.Name() - base := strings.TrimSuffix(name, filepath.Ext(name)) + base := utils.BaseName(name) if strings.EqualFold(base, candidate) && model.IsImageFile(name) { return filepath.Join(folder, name) } diff --git a/core/artwork/housekeeping.go b/core/artwork/housekeeping.go index 398256d03..366826ef5 100644 --- a/core/artwork/housekeeping.go +++ b/core/artwork/housekeeping.go @@ -13,6 +13,7 @@ import ( "github.com/navidrome/navidrome/core/auth" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/utils/slice" ) const staleAbsentAge = 24 * time.Hour @@ -85,12 +86,11 @@ func enqueueBackfillKind(ctx context.Context, ds model.DataStore, kind model.Kin if len(ids) == 0 { return nil } - items := make([]model.ArtworkQueueItem, len(ids)) - for i, id := range ids { - items[i] = model.ArtworkQueueItem{ + items := slice.Map(ids, func(id string) model.ArtworkQueueItem { + return model.ArtworkQueueItem{ ItemKind: kind.Prefix(), ItemID: id, ImageType: model.ImageTypePrimary, Priority: model.ArtworkPriorityBackfill, } - } + }) return ds.ArtworkQueue(ctx).Enqueue(items...) } diff --git a/core/artwork/playlist_cover.go b/core/artwork/playlist_cover.go index 68f4c4e10..e74b64808 100644 --- a/core/artwork/playlist_cover.go +++ b/core/artwork/playlist_cover.go @@ -13,6 +13,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/utils" xdraw "golang.org/x/image/draw" ) @@ -33,7 +34,7 @@ func findPlaylistSidecarPath(ctx context.Context, plsPath string) string { } for _, entry := range entries { name := entry.Name() - nameBase := strings.TrimSuffix(name, filepath.Ext(name)) + nameBase := utils.BaseName(name) if !entry.IsDir() && strings.EqualFold(nameBase, base) && model.IsImageFile(name) { return filepath.Join(dir, name) } diff --git a/core/artwork/resize.go b/core/artwork/resize.go index b64ad1adb..9536c3bb1 100644 --- a/core/artwork/resize.go +++ b/core/artwork/resize.go @@ -129,8 +129,7 @@ func resizeStaticImage(data []byte, size int, square bool) (io.Reader, int, erro return nil, originalSize, err } // Copy bytes before returning buffer to pool (pool may reuse the buffer) - encoded := make([]byte, buf.Len()) - copy(encoded, buf.Bytes()) + encoded := bytes.Clone(buf.Bytes()) bufPool.Put(buf) return bytes.NewReader(encoded), originalSize, nil } diff --git a/core/artwork/worker.go b/core/artwork/worker.go index c994e3320..fab0b96be 100644 --- a/core/artwork/worker.go +++ b/core/artwork/worker.go @@ -2,6 +2,7 @@ package artwork import ( "bytes" + "cmp" "context" "io" "math" @@ -253,9 +254,7 @@ func (w *Worker) broadcastRefresh(ctx context.Context, found []model.ArtworkQueu } func (w *Worker) process(ctx context.Context, item model.ArtworkQueueItem) (outcome, *acquired) { - if item.ImageType == "" { - item.ImageType = model.ImageTypePrimary - } + item.ImageType = cmp.Or(item.ImageType, model.ImageTypePrimary) out, got := w.proc.acquire(ctx, item) queue := w.proc.ds.ArtworkQueue(ctx) @@ -333,7 +332,7 @@ func (w *Worker) precache(ctx context.Context, got *acquired) { // backoffFor returns min(5s×4^n, giveUpAfter) scaled by (1+jitter), with jitter in [-0.4, 0.4]. func backoffFor(attempts int, jitter float64) time.Duration { - d := math.Min(float64(backoffBase)*math.Pow(4, float64(attempts)), float64(giveUpAfter)) + d := min(float64(backoffBase)*math.Pow(4, float64(attempts)), float64(giveUpAfter)) return time.Duration(d * (1 + jitter)) } diff --git a/core/artwork/worker_test.go b/core/artwork/worker_test.go index d2a2f0dac..bf5bf628e 100644 --- a/core/artwork/worker_test.go +++ b/core/artwork/worker_test.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "os" + "slices" "sync" "time" @@ -43,7 +44,7 @@ func (c *recordingCache) Get(ctx context.Context, arg cache.Item) (*cache.Cached func (c *recordingCache) getKeys() []string { c.mu.Lock() defer c.mu.Unlock() - return append([]string(nil), c.keys...) + return slices.Clone(c.keys) } // Simulates a concurrent Enqueue between DequeueBatch and the worker's delete, so @@ -88,7 +89,7 @@ func (f *fakeEventBroker) SendBroadcastMessage(_ context.Context, event events.E func (f *fakeEventBroker) getEvents() []events.Event { f.mu.Lock() defer f.mu.Unlock() - return f.events + return slices.Clone(f.events) } var _ events.Broker = (*fakeEventBroker)(nil) diff --git a/model/mediafile.go b/model/mediafile.go index bfb9d9058..ac1fad67b 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -144,9 +144,7 @@ func (mf MediaFile) CoverArtID() ArtworkID { // otherwise it returns the album artwork ID. func (mf MediaFile) DiscCoverArtID() ArtworkID { if mf.DiscNumber > 0 { - id := NewArtworkID(KindDiscArtwork, DiscArtworkID(mf.AlbumID, mf.DiscNumber), nil) - id.Hash = mf.ImageHash - return id + return ArtworkID{Kind: KindDiscArtwork, ID: DiscArtworkID(mf.AlbumID, mf.DiscNumber), Hash: mf.ImageHash} } return mf.AlbumCoverArtID() } diff --git a/persistence/album_repository_test.go b/persistence/album_repository_test.go index 32ee40e64..1ead89529 100644 --- a/persistence/album_repository_test.go +++ b/persistence/album_repository_test.go @@ -13,6 +13,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/id" "github.com/navidrome/navidrome/model/request" + "github.com/navidrome/navidrome/utils/slice" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -89,13 +90,9 @@ var _ = Describe("AlbumRepository", func() { want, err := albumRepo.GetAll() Expect(err).ToNot(HaveOccurred()) Expect(want).ToNot(BeEmpty()) - wantIDs := make([]string, 0, len(want)) - for _, a := range want { - wantIDs = append(wantIDs, a.ID) - } ids, err := albumRepo.GetAllIDs() Expect(err).ToNot(HaveOccurred()) - Expect(ids).To(ConsistOf(wantIDs)) + Expect(ids).To(ConsistOf(slice.Map(want, func(a model.Album) string { return a.ID }))) }) }) diff --git a/persistence/artist_repository_test.go b/persistence/artist_repository_test.go index 43a7f4928..25472ffe1 100644 --- a/persistence/artist_repository_test.go +++ b/persistence/artist_repository_test.go @@ -15,6 +15,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/utils" + "github.com/navidrome/navidrome/utils/slice" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -289,13 +290,9 @@ var _ = Describe("ArtistRepository", func() { want, err := repo.GetAll() Expect(err).ToNot(HaveOccurred()) Expect(want).ToNot(BeEmpty()) - wantIDs := make([]string, 0, len(want)) - for _, a := range want { - wantIDs = append(wantIDs, a.ID) - } ids, err := repo.GetAllIDs() Expect(err).ToNot(HaveOccurred()) - Expect(ids).To(ConsistOf(wantIDs)) + Expect(ids).To(ConsistOf(slice.Map(want, func(a model.Artist) string { return a.ID }))) }) }) diff --git a/persistence/artwork_hydration_test.go b/persistence/artwork_hydration_test.go index adf0e7e53..6948dfd30 100644 --- a/persistence/artwork_hydration_test.go +++ b/persistence/artwork_hydration_test.go @@ -66,12 +66,9 @@ var _ = Describe("Artwork hydration", func() { putInfo("al", albumAbbeyRoad.ID, "") // albumRadioactivity: no row -> unresolved - byID := map[string]model.Album{} all, err := repo.GetAll() Expect(err).ToNot(HaveOccurred()) - for _, a := range all { - byID[a.ID] = a - } + byID := slice.ToMap(all, func(a model.Album) (string, model.Album) { return a.ID, a }) Expect(byID[albumSgtPeppers.ID].ImageHash).To(Equal("althash11111111")) Expect(byID[albumSgtPeppers.ID].ImageAbsent).To(BeFalse()) @@ -118,12 +115,9 @@ var _ = Describe("Artwork hydration", func() { putInfo("ar", artistKraftwerk.ID, "") // artistCJK: no row -> unresolved - byID := map[string]model.Artist{} all, err := repo.GetAll() Expect(err).ToNot(HaveOccurred()) - for _, a := range all { - byID[a.ID] = a - } + byID := slice.ToMap(all, func(a model.Artist) (string, model.Artist) { return a.ID, a }) Expect(byID[artistBeatles.ID].ImageHash).To(Equal("arhash444444444")) Expect(byID[artistBeatles.ID].ImageAbsent).To(BeFalse()) @@ -157,12 +151,9 @@ var _ = Describe("Artwork hydration", func() { putInfo("pl", plsBest.ID, "plhash777777777") putInfo("pl", plsCool.ID, "") - byID := map[string]model.Playlist{} all, err := repo.GetAll() Expect(err).ToNot(HaveOccurred()) - for _, p := range all { - byID[p.ID] = p - } + byID := slice.ToMap(all, func(p model.Playlist) (string, model.Playlist) { return p.ID, p }) Expect(byID[plsBest.ID].ImageHash).To(Equal("plhash777777777")) Expect(byID[plsBest.ID].ImageAbsent).To(BeFalse()) @@ -185,10 +176,7 @@ var _ = Describe("Artwork hydration", func() { Expect(err).ToNot(HaveOccurred()) tracks := pls.Tracks Expect(tracks).ToNot(BeEmpty()) - byID := map[string]model.PlaylistTrack{} - for _, t := range tracks { - byID[t.MediaFile.ID] = t - } + byID := slice.ToMap(tracks, func(t model.PlaylistTrack) (string, model.PlaylistTrack) { return t.MediaFile.ID, t }) Expect(byID).To(HaveKey(songDayInALife.ID)) Expect(byID[songDayInALife.ID].AlbumImage.ImageHash).To(Equal("pltrackhash1234")) Expect(byID[songDayInALife.ID].BlurHash).To(Equal("LPLBLURhash")) @@ -216,12 +204,9 @@ var _ = Describe("Artwork hydration", func() { putInfo("ra", radioWithHomePage.ID, "rahash999999999") putInfo("ra", radioWithoutHomePage.ID, "") - byID := map[string]model.Radio{} all, err := repo.GetAll() Expect(err).ToNot(HaveOccurred()) - for _, rd := range all { - byID[rd.ID] = rd - } + byID := slice.ToMap(all, func(rd model.Radio) (string, model.Radio) { return rd.ID, rd }) Expect(byID[radioWithHomePage.ID].ImageHash).To(Equal("rahash999999999")) Expect(byID[radioWithHomePage.ID].ImageAbsent).To(BeFalse()) @@ -247,13 +232,9 @@ var _ = Describe("Artwork hydration", func() { } getByID := func() map[string]model.MediaFile { - byID := map[string]model.MediaFile{} all, err := repo.GetAll() Expect(err).ToNot(HaveOccurred()) - for _, mf := range all { - byID[mf.ID] = mf - } - return byID + return slice.ToMap(all, func(mf model.MediaFile) (string, model.MediaFile) { return mf.ID, mf }) } BeforeEach(func() { diff --git a/persistence/artwork_queue_repository.go b/persistence/artwork_queue_repository.go index 5da7f343c..0c278669f 100644 --- a/persistence/artwork_queue_repository.go +++ b/persistence/artwork_queue_repository.go @@ -1,6 +1,7 @@ package persistence import ( + "cmp" "context" "fmt" "slices" @@ -43,10 +44,7 @@ func (r *artworkQueueRepository) enqueue(conflict string, items []model.ArtworkQ for chunk := range slices.Chunk(items, enqueueChunkSize) { ins := Insert(r.tableName).Columns("item_kind", "item_id", "image_type", "priority", "attempts", "retry_at", "enqueued_at") for _, it := range chunk { - if it.ImageType == "" { - it.ImageType = model.ImageTypePrimary - } - ins = ins.Values(it.ItemKind, it.ItemID, it.ImageType, it.Priority, 0, now, now) + ins = ins.Values(it.ItemKind, it.ItemID, cmp.Or(it.ImageType, model.ImageTypePrimary), it.Priority, 0, now, now) } ins = ins.Suffix(conflict) if _, err := r.executeSQL(ins); err != nil { diff --git a/persistence/artwork_queue_repository_test.go b/persistence/artwork_queue_repository_test.go index 5baa482b6..86b5cd085 100644 --- a/persistence/artwork_queue_repository_test.go +++ b/persistence/artwork_queue_repository_test.go @@ -6,6 +6,7 @@ import ( "github.com/Masterminds/squirrel" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/utils/slice" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/pocketbase/dbx" @@ -193,10 +194,7 @@ var _ = Describe("ArtworkQueueRepository", func() { Expect(purged).To(Equal(int64(5))) got, _ := repo.DequeueBatch(100) - ids := make([]string, 0, len(got)) - for _, it := range got { - ids = append(ids, it.ItemID) - } + ids := slice.Map(got, func(it model.ArtworkQueueItem) string { return it.ItemID }) Expect(ids).To(ConsistOf(albumSgtPeppers.ID, artistKraftwerk.ID, plsBest.ID, radioWithHomePage.ID, songDayInALife.ID)) }) diff --git a/persistence/artwork_repository.go b/persistence/artwork_repository.go index 2fec7795d..0d98406ee 100644 --- a/persistence/artwork_repository.go +++ b/persistence/artwork_repository.go @@ -1,6 +1,7 @@ package persistence import ( + "cmp" "context" "slices" "time" @@ -155,9 +156,7 @@ func (r *artworkRepository) GetItemArtwork(kind model.Kind, id, imageType string } func (r *artworkRepository) PutItemArtwork(ia *model.ItemArtwork) error { - if ia.ImageType == "" { - ia.ImageType = model.ImageTypePrimary - } + ia.ImageType = cmp.Or(ia.ImageType, model.ImageTypePrimary) ia.UpdatedAt = time.Now() // PutItemArtwork records the outcome of an attempt, so an unset attempted_at is now. if ia.AttemptedAt.IsZero() { diff --git a/persistence/playlist_repository_test.go b/persistence/playlist_repository_test.go index f900e65ef..fc2d4ae3f 100644 --- a/persistence/playlist_repository_test.go +++ b/persistence/playlist_repository_test.go @@ -8,6 +8,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" + "github.com/navidrome/navidrome/utils/slice" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/pocketbase/dbx" @@ -42,13 +43,9 @@ var _ = Describe("PlaylistRepository", func() { want, err := repo.GetAll() Expect(err).ToNot(HaveOccurred()) Expect(want).ToNot(BeEmpty()) - wantIDs := make([]string, 0, len(want)) - for _, p := range want { - wantIDs = append(wantIDs, p.ID) - } ids, err := repo.GetAllIDs() Expect(err).ToNot(HaveOccurred()) - Expect(ids).To(ConsistOf(wantIDs)) + Expect(ids).To(ConsistOf(slice.Map(want, func(p model.Playlist) string { return p.ID }))) }) }) diff --git a/persistence/radio_repository_test.go b/persistence/radio_repository_test.go index 1ea8b971c..e2564455d 100644 --- a/persistence/radio_repository_test.go +++ b/persistence/radio_repository_test.go @@ -7,6 +7,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" + "github.com/navidrome/navidrome/utils/slice" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -83,13 +84,9 @@ var _ = Describe("RadioRepository", func() { want, err := repo.GetAll() Expect(err).To(BeNil()) Expect(want).ToNot(BeEmpty()) - wantIDs := make([]string, 0, len(want)) - for _, r := range want { - wantIDs = append(wantIDs, r.ID) - } ids, err := repo.GetAllIDs() Expect(err).To(BeNil()) - Expect(ids).To(ConsistOf(wantIDs)) + Expect(ids).To(ConsistOf(slice.Map(want, func(r model.Radio) string { return r.ID }))) }) }) diff --git a/scanner/phase_1_folders.go b/scanner/phase_1_folders.go index eeffe800e..7c51df5f0 100644 --- a/scanner/phase_1_folders.go +++ b/scanner/phase_1_folders.go @@ -401,10 +401,7 @@ func (p *phaseFolders) persistChanges(entry *folderEntry) (*folderEntry, error) // A re-imported track returns to unresolved so new embedded art is picked up lazily. if len(entry.tracks) > 0 { - trackIDs := make([]string, len(entry.tracks)) - for i := range entry.tracks { - trackIDs[i] = entry.tracks[i].ID - } + trackIDs := slice.Map(entry.tracks, func(t model.MediaFile) string { return t.ID }) if err := tx.Artwork(p.ctx).DeleteForItems(model.KindMediaFileArtwork, trackIDs); err != nil { log.Warn(p.ctx, "Scanner: could not invalidate media_file artwork", "folder", entry.path, err) } diff --git a/server/imghttp/headers.go b/server/imghttp/headers.go index 0277132fb..9308bcdab 100644 --- a/server/imghttp/headers.go +++ b/server/imghttp/headers.go @@ -56,7 +56,7 @@ func ifNoneMatch(header, hash string) bool { if header == "*" { return true } - for _, tag := range strings.Split(header, ",") { + for tag := range strings.SplitSeq(header, ",") { tag = strings.TrimSpace(tag) tag = strings.TrimPrefix(tag, "W/") if strings.Trim(tag, `"`) == hash { diff --git a/server/nativeapi/artwork.go b/server/nativeapi/artwork.go index ac7890661..3583c2db7 100644 --- a/server/nativeapi/artwork.go +++ b/server/nativeapi/artwork.go @@ -2,6 +2,7 @@ package nativeapi import ( "net/http" + "slices" "github.com/go-chi/chi/v5" "github.com/navidrome/navidrome/core/artwork" @@ -9,12 +10,12 @@ import ( "github.com/navidrome/navidrome/model" ) -var refreshableArtworkKinds = map[model.Kind]bool{ - model.KindAlbumArtwork: true, - model.KindArtistArtwork: true, - model.KindPlaylistArtwork: true, - model.KindRadioArtwork: true, - model.KindMediaFileArtwork: true, +var refreshableArtworkKinds = []model.Kind{ + model.KindAlbumArtwork, + model.KindArtistArtwork, + model.KindPlaylistArtwork, + model.KindRadioArtwork, + model.KindMediaFileArtwork, } func (api *Router) addArtworkRoute(r chi.Router) { @@ -27,7 +28,7 @@ func (api *Router) refreshArtwork() http.HandlerFunc { ctx := r.Context() kind, _ := model.ParseKind(chi.URLParam(r, "kind")) id := chi.URLParam(r, "id") - if !refreshableArtworkKinds[kind] { + if !slices.Contains(refreshableArtworkKinds, kind) { http.Error(w, "invalid artwork kind", http.StatusBadRequest) return } diff --git a/tests/mock_album_repo.go b/tests/mock_album_repo.go index aa72d9925..ff6f9cff6 100644 --- a/tests/mock_album_repo.go +++ b/tests/mock_album_repo.go @@ -7,6 +7,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/id" + "github.com/navidrome/navidrome/utils/slice" ) func CreateMockAlbumRepo() *MockAlbumRepo { @@ -86,11 +87,7 @@ func (m *MockAlbumRepo) GetAllIDs(qo ...model.QueryOptions) ([]string, error) { if err != nil { return nil, err } - ids := make([]string, len(all)) - for i, a := range all { - ids[i] = a.ID - } - return ids, nil + return slice.Map(all, func(a model.Album) string { return a.ID }), nil } func (m *MockAlbumRepo) GetCursor(qo ...model.QueryOptions) (model.AlbumCursor, error) { diff --git a/tests/mock_artist_repo.go b/tests/mock_artist_repo.go index 4fdd198fe..9691a6584 100644 --- a/tests/mock_artist_repo.go +++ b/tests/mock_artist_repo.go @@ -6,6 +6,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/id" + "github.com/navidrome/navidrome/utils/slice" ) func CreateMockArtistRepo() *MockArtistRepo { @@ -118,11 +119,7 @@ func (m *MockArtistRepo) GetAllIDs(options ...model.QueryOptions) ([]string, err if err != nil { return nil, err } - ids := make([]string, len(all)) - for i, a := range all { - ids[i] = a.ID - } - return ids, nil + return slice.Map(all, func(a model.Artist) string { return a.ID }), nil } func (m *MockArtistRepo) GetCursor(options ...model.QueryOptions) (model.ArtistCursor, error) { diff --git a/tests/mock_artwork_queue_repo.go b/tests/mock_artwork_queue_repo.go index 82661d728..6a188f344 100644 --- a/tests/mock_artwork_queue_repo.go +++ b/tests/mock_artwork_queue_repo.go @@ -1,8 +1,8 @@ package tests import ( + "cmp" "slices" - "sort" "sync" "time" @@ -67,11 +67,8 @@ func (m *MockArtworkQueueRepo) DequeueBatch(n int, kinds ...string) ([]model.Art res = append(res, it) } } - sort.Slice(res, func(i, j int) bool { - if res[i].Priority != res[j].Priority { - return res[i].Priority > res[j].Priority - } - return res[i].EnqueuedAt.Before(res[j].EnqueuedAt) + slices.SortFunc(res, func(a, b model.ArtworkQueueItem) int { + return cmp.Or(cmp.Compare(b.Priority, a.Priority), a.EnqueuedAt.Compare(b.EnqueuedAt)) }) if len(res) > n { res = res[:n] diff --git a/tests/mock_artwork_repo.go b/tests/mock_artwork_repo.go index 206f4031f..2688b2270 100644 --- a/tests/mock_artwork_repo.go +++ b/tests/mock_artwork_repo.go @@ -1,10 +1,12 @@ package tests import ( + "maps" "sync" "time" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/utils/slice" ) type MockArtworkRepo struct { @@ -167,11 +169,9 @@ func (m *MockArtworkRepo) DeleteForItem(kind model.Kind, id string) error { if m.Err != nil { return m.Err } - for k, ia := range m.ItemData { - if ia.ItemKind == kind.Prefix() && ia.ItemID == id { - delete(m.ItemData, k) - } - } + maps.DeleteFunc(m.ItemData, func(_ string, ia model.ItemArtwork) bool { + return ia.ItemKind == kind.Prefix() && ia.ItemID == id + }) return nil } @@ -181,15 +181,11 @@ func (m *MockArtworkRepo) DeleteForItems(kind model.Kind, ids []string) error { if m.Err != nil { return m.Err } - idSet := make(map[string]bool, len(ids)) - for _, id := range ids { - idSet[id] = true - } - for k, ia := range m.ItemData { - if ia.ItemKind == kind.Prefix() && idSet[ia.ItemID] { - delete(m.ItemData, k) - } - } + idSet := slice.ToSet(ids) + maps.DeleteFunc(m.ItemData, func(_ string, ia model.ItemArtwork) bool { + _, ok := idSet[ia.ItemID] + return ok && ia.ItemKind == kind.Prefix() + }) return nil } diff --git a/tests/mock_mediafile_repo.go b/tests/mock_mediafile_repo.go index 81837865e..d993af1a8 100644 --- a/tests/mock_mediafile_repo.go +++ b/tests/mock_mediafile_repo.go @@ -132,11 +132,7 @@ func (m *MockMediaFileRepo) GetAllIDs(qo ...model.QueryOptions) ([]string, error if err != nil { return nil, err } - ids := make([]string, len(all)) - for i, mf := range all { - ids[i] = mf.ID - } - return ids, nil + return slice.Map(all, func(mf model.MediaFile) string { return mf.ID }), nil } func (m *MockMediaFileRepo) Put(mf *model.MediaFile) error { diff --git a/tests/mock_playlist_repo.go b/tests/mock_playlist_repo.go index bb1fe3379..ee645e984 100644 --- a/tests/mock_playlist_repo.go +++ b/tests/mock_playlist_repo.go @@ -7,6 +7,7 @@ import ( "github.com/deluan/rest" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/id" + "github.com/navidrome/navidrome/utils/slice" ) func CreateMockPlaylistRepo() *MockPlaylistRepo { @@ -57,11 +58,7 @@ func (m *MockPlaylistRepo) GetAllIDs(options ...model.QueryOptions) ([]string, e if err != nil { return nil, err } - ids := make([]string, len(all)) - for i, p := range all { - ids[i] = p.ID - } - return ids, nil + return slice.Map(all, func(p model.Playlist) string { return p.ID }), nil } func (m *MockPlaylistRepo) GetCursor(options ...model.QueryOptions) (model.PlaylistCursor, error) { diff --git a/tests/mock_radio_repository.go b/tests/mock_radio_repository.go index 704d4f981..2baeadc5c 100644 --- a/tests/mock_radio_repository.go +++ b/tests/mock_radio_repository.go @@ -5,6 +5,7 @@ import ( "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/id" + "github.com/navidrome/navidrome/utils/slice" ) type MockedRadioRepo struct { @@ -78,11 +79,7 @@ func (m *MockedRadioRepo) GetAllIDs(qo ...model.QueryOptions) ([]string, error) if err != nil { return nil, err } - ids := make([]string, len(all)) - for i, r := range all { - ids[i] = r.ID - } - return ids, nil + return slice.Map(all, func(r model.Radio) string { return r.ID }), nil } func (m *MockedRadioRepo) Put(radio *model.Radio, _ ...string) error {