From d229ff39e5a9135e800057844e46323a33a9ec63 Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 19 Nov 2024 07:45:24 -0500 Subject: [PATCH] refactor: reduce GC pressure by pre-allocating slices Signed-off-by: Deluan --- model/album.go | 2 +- model/lyrics.go | 4 +- model/mediafile.go | 28 +- model/mediafile_test.go | 526 ++++++++++++++++------------- persistence/sql_base_repository.go | 5 +- scanner/mapping.go | 2 +- scanner/metadata/metadata.go | 7 +- scanner/tag_scanner.go | 2 +- scanner/walk_dir_tree.go | 7 +- utils/cache/simple_cache.go | 4 +- 10 files changed, 325 insertions(+), 262 deletions(-) diff --git a/model/album.go b/model/album.go index 76bd9f4fa..538b6234a 100644 --- a/model/album.go +++ b/model/album.go @@ -84,7 +84,7 @@ type Albums []Album // It assumes all albums have the same AlbumArtist, or else results are unpredictable. func (als Albums) ToAlbumArtist() Artist { a := Artist{AlbumCount: len(als)} - var mbzArtistIds []string + mbzArtistIds := make([]string, 0, len(als)) for _, al := range als { a.ID = al.AlbumArtistID a.Name = al.AlbumArtist diff --git a/model/lyrics.go b/model/lyrics.go index f7221f84f..948983009 100644 --- a/model/lyrics.go +++ b/model/lyrics.go @@ -39,11 +39,11 @@ func ToLyrics(language, text string) (*Lyrics, error) { text = str.SanitizeText(text) lines := strings.Split(text, "\n") + structuredLines := make([]Line, 0, len(lines)*2) artist := "" title := "" var offset *int64 = nil - structuredLines := []Line{} synced := syncRegex.MatchString(text) priorLine := "" @@ -105,7 +105,7 @@ func ToLyrics(language, text string) (*Lyrics, error) { Value: strings.TrimSpace(priorLine), }) } - timestamps = []int64{} + timestamps = nil } end := 0 diff --git a/model/mediafile.go b/model/mediafile.go index 8abc685fd..36f9bb505 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -108,11 +108,9 @@ type MediaFiles []MediaFile // Dirs returns a deduped list of all directories from the MediaFiles' paths func (mfs MediaFiles) Dirs() []string { - var dirs []string - for _, mf := range mfs { - dir, _ := filepath.Split(mf.Path) - dirs = append(dirs, filepath.Clean(dir)) - } + dirs := slice.Map(mfs, func(m MediaFile) string { + return filepath.Dir(m.Path) + }) slices.Sort(dirs) return slices.Compact(dirs) } @@ -121,16 +119,16 @@ func (mfs MediaFiles) Dirs() []string { // It assumes all mediafiles have the same Album, or else results are unpredictable. func (mfs MediaFiles) ToAlbum() Album { a := Album{SongCount: len(mfs)} - var fullText []string - var albumArtistIds []string - var songArtistIds []string - var mbzAlbumIds []string - var comments []string - var years []int - var dates []string - var originalYears []int - var originalDates []string - var releaseDates []string + fullText := make([]string, 0, len(mfs)) + albumArtistIds := make([]string, 0, len(mfs)) + songArtistIds := make([]string, 0, len(mfs)) + mbzAlbumIds := make([]string, 0, len(mfs)) + comments := make([]string, 0, len(mfs)) + years := make([]int, 0, len(mfs)) + dates := make([]string, 0, len(mfs)) + originalYears := make([]int, 0, len(mfs)) + originalDates := make([]string, 0, len(mfs)) + releaseDates := make([]string, 0, len(mfs)) for _, m := range mfs { // We assume these attributes are all the same for all songs on an album a.ID = m.AlbumID diff --git a/model/mediafile_test.go b/model/mediafile_test.go index 0aad38384..b80d3fe0a 100644 --- a/model/mediafile_test.go +++ b/model/mediafile_test.go @@ -1,6 +1,7 @@ package model_test import ( + "path/filepath" "time" "github.com/navidrome/navidrome/conf" @@ -13,258 +14,319 @@ import ( var _ = Describe("MediaFiles", func() { var mfs MediaFiles - - Context("Simple attributes", func() { - BeforeEach(func() { - mfs = MediaFiles{ - { - ID: "1", AlbumID: "AlbumID", Album: "Album", ArtistID: "ArtistID", Artist: "Artist", AlbumArtistID: "AlbumArtistID", AlbumArtist: "AlbumArtist", - SortAlbumName: "SortAlbumName", SortArtistName: "SortArtistName", SortAlbumArtistName: "SortAlbumArtistName", - OrderAlbumName: "OrderAlbumName", OrderAlbumArtistName: "OrderAlbumArtistName", - MbzAlbumArtistID: "MbzAlbumArtistID", MbzAlbumType: "MbzAlbumType", MbzAlbumComment: "MbzAlbumComment", - Compilation: false, CatalogNum: "", Path: "/music1/file1.mp3", - }, - { - ID: "2", Album: "Album", ArtistID: "ArtistID", Artist: "Artist", AlbumArtistID: "AlbumArtistID", AlbumArtist: "AlbumArtist", AlbumID: "AlbumID", - SortAlbumName: "SortAlbumName", SortArtistName: "SortArtistName", SortAlbumArtistName: "SortAlbumArtistName", - OrderAlbumName: "OrderAlbumName", OrderArtistName: "OrderArtistName", OrderAlbumArtistName: "OrderAlbumArtistName", - MbzAlbumArtistID: "MbzAlbumArtistID", MbzAlbumType: "MbzAlbumType", MbzAlbumComment: "MbzAlbumComment", - Compilation: true, CatalogNum: "CatalogNum", HasCoverArt: true, Path: "/music2/file2.mp3", - }, - } - }) - - It("sets the single values correctly", func() { - album := mfs.ToAlbum() - Expect(album.ID).To(Equal("AlbumID")) - Expect(album.Name).To(Equal("Album")) - Expect(album.Artist).To(Equal("Artist")) - Expect(album.ArtistID).To(Equal("ArtistID")) - Expect(album.AlbumArtist).To(Equal("AlbumArtist")) - Expect(album.AlbumArtistID).To(Equal("AlbumArtistID")) - Expect(album.SortAlbumName).To(Equal("SortAlbumName")) - Expect(album.SortAlbumArtistName).To(Equal("SortAlbumArtistName")) - Expect(album.OrderAlbumName).To(Equal("OrderAlbumName")) - Expect(album.OrderAlbumArtistName).To(Equal("OrderAlbumArtistName")) - Expect(album.MbzAlbumArtistID).To(Equal("MbzAlbumArtistID")) - Expect(album.MbzAlbumType).To(Equal("MbzAlbumType")) - Expect(album.MbzAlbumComment).To(Equal("MbzAlbumComment")) - Expect(album.CatalogNum).To(Equal("CatalogNum")) - Expect(album.Compilation).To(BeTrue()) - Expect(album.EmbedArtPath).To(Equal("/music2/file2.mp3")) - Expect(album.Paths).To(Equal("/music1" + consts.Zwsp + "/music2")) - }) - }) - Context("Aggregated attributes", func() { - When("we have only one song", func() { - BeforeEach(func() { - mfs = MediaFiles{ - {Duration: 100.2, Size: 1024, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:30"), CreatedAt: t("2022-12-19 08:30")}, - } - }) - It("calculates the aggregates correctly", func() { - album := mfs.ToAlbum() - Expect(album.Duration).To(Equal(float32(100.2))) - Expect(album.Size).To(Equal(int64(1024))) - Expect(album.MinYear).To(Equal(1985)) - Expect(album.MaxYear).To(Equal(1985)) - Expect(album.Date).To(Equal("1985-01-02")) - Expect(album.UpdatedAt).To(Equal(t("2022-12-19 09:30"))) - Expect(album.CreatedAt).To(Equal(t("2022-12-19 08:30"))) - }) - }) - - When("we have multiple songs with different dates", func() { - BeforeEach(func() { - mfs = MediaFiles{ - {Duration: 100.2, Size: 1024, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:30"), CreatedAt: t("2022-12-19 08:30")}, - {Duration: 200.2, Size: 2048, Year: 0, Date: "", UpdatedAt: t("2022-12-19 09:45"), CreatedAt: t("2022-12-19 08:30")}, - {Duration: 150.6, Size: 1000, Year: 1986, Date: "1986-01-02", UpdatedAt: t("2022-12-19 09:45"), CreatedAt: t("2022-12-19 07:30")}, - } - }) - It("calculates the aggregates correctly", func() { - album := mfs.ToAlbum() - Expect(album.Duration).To(Equal(float32(451.0))) - Expect(album.Size).To(Equal(int64(4072))) - Expect(album.MinYear).To(Equal(1985)) - Expect(album.MaxYear).To(Equal(1986)) - Expect(album.Date).To(BeEmpty()) - Expect(album.UpdatedAt).To(Equal(t("2022-12-19 09:45"))) - Expect(album.CreatedAt).To(Equal(t("2022-12-19 07:30"))) - }) - Context("MinYear", func() { - It("returns 0 when all values are 0", func() { - mfs = MediaFiles{{Year: 0}, {Year: 0}, {Year: 0}} - a := mfs.ToAlbum() - Expect(a.MinYear).To(Equal(0)) - }) - It("returns the smallest value from the list, not counting 0", func() { - mfs = MediaFiles{{Year: 2000}, {Year: 0}, {Year: 1999}} - a := mfs.ToAlbum() - Expect(a.MinYear).To(Equal(1999)) - }) - }) - }) - When("we have multiple songs with same dates", func() { - BeforeEach(func() { - mfs = MediaFiles{ - {Duration: 100.2, Size: 1024, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:30"), CreatedAt: t("2022-12-19 08:30")}, - {Duration: 200.2, Size: 2048, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:45"), CreatedAt: t("2022-12-19 08:30")}, - {Duration: 150.6, Size: 1000, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:45"), CreatedAt: t("2022-12-19 07:30")}, - } - }) - It("sets the date field correctly", func() { - album := mfs.ToAlbum() - Expect(album.Date).To(Equal("1985-01-02")) - Expect(album.MinYear).To(Equal(1985)) - Expect(album.MaxYear).To(Equal(1985)) - }) - }) - }) - Context("Calculated attributes", func() { - Context("Discs", func() { - When("we have no discs", func() { - BeforeEach(func() { - mfs = MediaFiles{{Album: "Album1"}, {Album: "Album1"}, {Album: "Album1"}} - }) - It("sets the correct Discs", func() { - album := mfs.ToAlbum() - Expect(album.Discs).To(BeEmpty()) - }) - }) - When("we have only one disc", func() { - BeforeEach(func() { - mfs = MediaFiles{{DiscNumber: 1, DiscSubtitle: "DiscSubtitle"}} - }) - It("sets the correct Discs", func() { - album := mfs.ToAlbum() - Expect(album.Discs).To(Equal(Discs{1: "DiscSubtitle"})) - }) - }) - When("we have multiple discs", func() { - BeforeEach(func() { - mfs = MediaFiles{{DiscNumber: 1, DiscSubtitle: "DiscSubtitle"}, {DiscNumber: 2, DiscSubtitle: "DiscSubtitle2"}, {DiscNumber: 1, DiscSubtitle: "DiscSubtitle"}} - }) - It("sets the correct Discs", func() { - album := mfs.ToAlbum() - Expect(album.Discs).To(Equal(Discs{1: "DiscSubtitle", 2: "DiscSubtitle2"})) - }) - }) - }) - - Context("Genres", func() { - When("we have only one Genre", func() { - BeforeEach(func() { - mfs = MediaFiles{{Genres: Genres{{ID: "g1", Name: "Rock"}}}} - }) - It("sets the correct Genre", func() { - album := mfs.ToAlbum() - Expect(album.Genre).To(Equal("Rock")) - Expect(album.Genres).To(ConsistOf(Genre{ID: "g1", Name: "Rock"})) - }) - }) - When("we have multiple Genres", func() { - BeforeEach(func() { - mfs = MediaFiles{{Genres: Genres{{ID: "g1", Name: "Rock"}, {ID: "g2", Name: "Punk"}, {ID: "g3", Name: "Alternative"}}}} - }) - It("sets the correct Genre", func() { - album := mfs.ToAlbum() - Expect(album.Genre).To(Equal("Rock")) - Expect(album.Genres).To(Equal(Genres{{ID: "g1", Name: "Rock"}, {ID: "g2", Name: "Punk"}, {ID: "g3", Name: "Alternative"}})) - }) - }) - When("we have one predominant Genre", func() { - var album Album - BeforeEach(func() { - mfs = MediaFiles{{Genres: Genres{{ID: "g2", Name: "Punk"}, {ID: "g1", Name: "Rock"}, {ID: "g2", Name: "Punk"}}}} - album = mfs.ToAlbum() - }) - It("sets the correct Genre", func() { - Expect(album.Genre).To(Equal("Punk")) - }) - It("removes duplications from Genres", func() { - Expect(album.Genres).To(Equal(Genres{{ID: "g1", Name: "Rock"}, {ID: "g2", Name: "Punk"}})) - }) - }) - }) - Context("Comments", func() { - When("we have only one Comment", func() { - BeforeEach(func() { - mfs = MediaFiles{{Comment: "comment1"}} - }) - It("sets the correct Comment", func() { - album := mfs.ToAlbum() - Expect(album.Comment).To(Equal("comment1")) - }) - }) - When("we have multiple equal comments", func() { - BeforeEach(func() { - mfs = MediaFiles{{Comment: "comment1"}, {Comment: "comment1"}, {Comment: "comment1"}} - }) - It("sets the correct Comment", func() { - album := mfs.ToAlbum() - Expect(album.Comment).To(Equal("comment1")) - }) - }) - When("we have different comments", func() { - BeforeEach(func() { - mfs = MediaFiles{{Comment: "comment1"}, {Comment: "not the same"}, {Comment: "comment1"}} - }) - It("sets the correct Genre", func() { - album := mfs.ToAlbum() - Expect(album.Comment).To(BeEmpty()) - }) - }) - }) - Context("AllArtistIds", func() { - BeforeEach(func() { - mfs = MediaFiles{ - {AlbumArtistID: "22", ArtistID: "11"}, - {AlbumArtistID: "22", ArtistID: "33"}, - {AlbumArtistID: "22", ArtistID: "11"}, - } - }) - It("removes duplications", func() { - album := mfs.ToAlbum() - Expect(album.AllArtistIDs).To(Equal("11 22 33")) - }) - }) - Context("FullText", func() { + Describe("ToAlbum", func() { + Context("Simple attributes", func() { BeforeEach(func() { mfs = MediaFiles{ { - Album: "Album1", AlbumArtist: "AlbumArtist1", Artist: "Artist1", DiscSubtitle: "DiscSubtitle1", - SortAlbumName: "SortAlbumName1", SortAlbumArtistName: "SortAlbumArtistName1", SortArtistName: "SortArtistName1", + ID: "1", AlbumID: "AlbumID", Album: "Album", ArtistID: "ArtistID", Artist: "Artist", AlbumArtistID: "AlbumArtistID", AlbumArtist: "AlbumArtist", + SortAlbumName: "SortAlbumName", SortArtistName: "SortArtistName", SortAlbumArtistName: "SortAlbumArtistName", + OrderAlbumName: "OrderAlbumName", OrderAlbumArtistName: "OrderAlbumArtistName", + MbzAlbumArtistID: "MbzAlbumArtistID", MbzAlbumType: "MbzAlbumType", MbzAlbumComment: "MbzAlbumComment", + Compilation: false, CatalogNum: "", Path: "/music1/file1.mp3", }, { - Album: "Album1", AlbumArtist: "AlbumArtist1", Artist: "Artist2", DiscSubtitle: "DiscSubtitle2", - SortAlbumName: "SortAlbumName1", SortAlbumArtistName: "SortAlbumArtistName1", SortArtistName: "SortArtistName2", + ID: "2", Album: "Album", ArtistID: "ArtistID", Artist: "Artist", AlbumArtistID: "AlbumArtistID", AlbumArtist: "AlbumArtist", AlbumID: "AlbumID", + SortAlbumName: "SortAlbumName", SortArtistName: "SortArtistName", SortAlbumArtistName: "SortAlbumArtistName", + OrderAlbumName: "OrderAlbumName", OrderArtistName: "OrderArtistName", OrderAlbumArtistName: "OrderAlbumArtistName", + MbzAlbumArtistID: "MbzAlbumArtistID", MbzAlbumType: "MbzAlbumType", MbzAlbumComment: "MbzAlbumComment", + Compilation: true, CatalogNum: "CatalogNum", HasCoverArt: true, Path: "/music2/file2.mp3", }, } }) - It("fills the fullText attribute correctly", func() { + + It("sets the single values correctly", func() { album := mfs.ToAlbum() - Expect(album.FullText).To(Equal(" album1 albumartist1 artist1 artist2 discsubtitle1 discsubtitle2 sortalbumartistname1 sortalbumname1 sortartistname1 sortartistname2")) + Expect(album.ID).To(Equal("AlbumID")) + Expect(album.Name).To(Equal("Album")) + Expect(album.Artist).To(Equal("Artist")) + Expect(album.ArtistID).To(Equal("ArtistID")) + Expect(album.AlbumArtist).To(Equal("AlbumArtist")) + Expect(album.AlbumArtistID).To(Equal("AlbumArtistID")) + Expect(album.SortAlbumName).To(Equal("SortAlbumName")) + Expect(album.SortAlbumArtistName).To(Equal("SortAlbumArtistName")) + Expect(album.OrderAlbumName).To(Equal("OrderAlbumName")) + Expect(album.OrderAlbumArtistName).To(Equal("OrderAlbumArtistName")) + Expect(album.MbzAlbumArtistID).To(Equal("MbzAlbumArtistID")) + Expect(album.MbzAlbumType).To(Equal("MbzAlbumType")) + Expect(album.MbzAlbumComment).To(Equal("MbzAlbumComment")) + Expect(album.CatalogNum).To(Equal("CatalogNum")) + Expect(album.Compilation).To(BeTrue()) + Expect(album.EmbedArtPath).To(Equal("/music2/file2.mp3")) + Expect(album.Paths).To(Equal("/music1" + consts.Zwsp + "/music2")) }) }) - Context("MbzAlbumID", func() { - When("we have only one MbzAlbumID", func() { + Context("Aggregated attributes", func() { + When("we have only one song", func() { BeforeEach(func() { - mfs = MediaFiles{{MbzAlbumID: "id1"}} + mfs = MediaFiles{ + {Duration: 100.2, Size: 1024, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:30"), CreatedAt: t("2022-12-19 08:30")}, + } }) - It("sets the correct MbzAlbumID", func() { + It("calculates the aggregates correctly", func() { album := mfs.ToAlbum() - Expect(album.MbzAlbumID).To(Equal("id1")) + Expect(album.Duration).To(Equal(float32(100.2))) + Expect(album.Size).To(Equal(int64(1024))) + Expect(album.MinYear).To(Equal(1985)) + Expect(album.MaxYear).To(Equal(1985)) + Expect(album.Date).To(Equal("1985-01-02")) + Expect(album.UpdatedAt).To(Equal(t("2022-12-19 09:30"))) + Expect(album.CreatedAt).To(Equal(t("2022-12-19 08:30"))) }) }) - When("we have multiple MbzAlbumID", func() { + + When("we have multiple songs with different dates", func() { BeforeEach(func() { - mfs = MediaFiles{{MbzAlbumID: "id1"}, {MbzAlbumID: "id2"}, {MbzAlbumID: "id1"}} + mfs = MediaFiles{ + {Duration: 100.2, Size: 1024, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:30"), CreatedAt: t("2022-12-19 08:30")}, + {Duration: 200.2, Size: 2048, Year: 0, Date: "", UpdatedAt: t("2022-12-19 09:45"), CreatedAt: t("2022-12-19 08:30")}, + {Duration: 150.6, Size: 1000, Year: 1986, Date: "1986-01-02", UpdatedAt: t("2022-12-19 09:45"), CreatedAt: t("2022-12-19 07:30")}, + } }) - It("sets the correct MbzAlbumID", func() { + It("calculates the aggregates correctly", func() { album := mfs.ToAlbum() - Expect(album.MbzAlbumID).To(Equal("id1")) + Expect(album.Duration).To(Equal(float32(451.0))) + Expect(album.Size).To(Equal(int64(4072))) + Expect(album.MinYear).To(Equal(1985)) + Expect(album.MaxYear).To(Equal(1986)) + Expect(album.Date).To(BeEmpty()) + Expect(album.UpdatedAt).To(Equal(t("2022-12-19 09:45"))) + Expect(album.CreatedAt).To(Equal(t("2022-12-19 07:30"))) }) + Context("MinYear", func() { + It("returns 0 when all values are 0", func() { + mfs = MediaFiles{{Year: 0}, {Year: 0}, {Year: 0}} + a := mfs.ToAlbum() + Expect(a.MinYear).To(Equal(0)) + }) + It("returns the smallest value from the list, not counting 0", func() { + mfs = MediaFiles{{Year: 2000}, {Year: 0}, {Year: 1999}} + a := mfs.ToAlbum() + Expect(a.MinYear).To(Equal(1999)) + }) + }) + }) + When("we have multiple songs with same dates", func() { + BeforeEach(func() { + mfs = MediaFiles{ + {Duration: 100.2, Size: 1024, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:30"), CreatedAt: t("2022-12-19 08:30")}, + {Duration: 200.2, Size: 2048, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:45"), CreatedAt: t("2022-12-19 08:30")}, + {Duration: 150.6, Size: 1000, Year: 1985, Date: "1985-01-02", UpdatedAt: t("2022-12-19 09:45"), CreatedAt: t("2022-12-19 07:30")}, + } + }) + It("sets the date field correctly", func() { + album := mfs.ToAlbum() + Expect(album.Date).To(Equal("1985-01-02")) + Expect(album.MinYear).To(Equal(1985)) + Expect(album.MaxYear).To(Equal(1985)) + }) + }) + }) + Context("Calculated attributes", func() { + Context("Discs", func() { + When("we have no discs", func() { + BeforeEach(func() { + mfs = MediaFiles{{Album: "Album1"}, {Album: "Album1"}, {Album: "Album1"}} + }) + It("sets the correct Discs", func() { + album := mfs.ToAlbum() + Expect(album.Discs).To(BeEmpty()) + }) + }) + When("we have only one disc", func() { + BeforeEach(func() { + mfs = MediaFiles{{DiscNumber: 1, DiscSubtitle: "DiscSubtitle"}} + }) + It("sets the correct Discs", func() { + album := mfs.ToAlbum() + Expect(album.Discs).To(Equal(Discs{1: "DiscSubtitle"})) + }) + }) + When("we have multiple discs", func() { + BeforeEach(func() { + mfs = MediaFiles{{DiscNumber: 1, DiscSubtitle: "DiscSubtitle"}, {DiscNumber: 2, DiscSubtitle: "DiscSubtitle2"}, {DiscNumber: 1, DiscSubtitle: "DiscSubtitle"}} + }) + It("sets the correct Discs", func() { + album := mfs.ToAlbum() + Expect(album.Discs).To(Equal(Discs{1: "DiscSubtitle", 2: "DiscSubtitle2"})) + }) + }) + }) + + Context("Genres", func() { + When("we have only one Genre", func() { + BeforeEach(func() { + mfs = MediaFiles{{Genres: Genres{{ID: "g1", Name: "Rock"}}}} + }) + It("sets the correct Genre", func() { + album := mfs.ToAlbum() + Expect(album.Genre).To(Equal("Rock")) + Expect(album.Genres).To(ConsistOf(Genre{ID: "g1", Name: "Rock"})) + }) + }) + When("we have multiple Genres", func() { + BeforeEach(func() { + mfs = MediaFiles{{Genres: Genres{{ID: "g1", Name: "Rock"}, {ID: "g2", Name: "Punk"}, {ID: "g3", Name: "Alternative"}}}} + }) + It("sets the correct Genre", func() { + album := mfs.ToAlbum() + Expect(album.Genre).To(Equal("Rock")) + Expect(album.Genres).To(Equal(Genres{{ID: "g1", Name: "Rock"}, {ID: "g2", Name: "Punk"}, {ID: "g3", Name: "Alternative"}})) + }) + }) + When("we have one predominant Genre", func() { + var album Album + BeforeEach(func() { + mfs = MediaFiles{{Genres: Genres{{ID: "g2", Name: "Punk"}, {ID: "g1", Name: "Rock"}, {ID: "g2", Name: "Punk"}}}} + album = mfs.ToAlbum() + }) + It("sets the correct Genre", func() { + Expect(album.Genre).To(Equal("Punk")) + }) + It("removes duplications from Genres", func() { + Expect(album.Genres).To(Equal(Genres{{ID: "g1", Name: "Rock"}, {ID: "g2", Name: "Punk"}})) + }) + }) + }) + Context("Comments", func() { + When("we have only one Comment", func() { + BeforeEach(func() { + mfs = MediaFiles{{Comment: "comment1"}} + }) + It("sets the correct Comment", func() { + album := mfs.ToAlbum() + Expect(album.Comment).To(Equal("comment1")) + }) + }) + When("we have multiple equal comments", func() { + BeforeEach(func() { + mfs = MediaFiles{{Comment: "comment1"}, {Comment: "comment1"}, {Comment: "comment1"}} + }) + It("sets the correct Comment", func() { + album := mfs.ToAlbum() + Expect(album.Comment).To(Equal("comment1")) + }) + }) + When("we have different comments", func() { + BeforeEach(func() { + mfs = MediaFiles{{Comment: "comment1"}, {Comment: "not the same"}, {Comment: "comment1"}} + }) + It("sets the correct Genre", func() { + album := mfs.ToAlbum() + Expect(album.Comment).To(BeEmpty()) + }) + }) + }) + Context("AllArtistIds", func() { + BeforeEach(func() { + mfs = MediaFiles{ + {AlbumArtistID: "22", ArtistID: "11"}, + {AlbumArtistID: "22", ArtistID: "33"}, + {AlbumArtistID: "22", ArtistID: "11"}, + } + }) + It("removes duplications", func() { + album := mfs.ToAlbum() + Expect(album.AllArtistIDs).To(Equal("11 22 33")) + }) + }) + Context("FullText", func() { + BeforeEach(func() { + mfs = MediaFiles{ + { + Album: "Album1", AlbumArtist: "AlbumArtist1", Artist: "Artist1", DiscSubtitle: "DiscSubtitle1", + SortAlbumName: "SortAlbumName1", SortAlbumArtistName: "SortAlbumArtistName1", SortArtistName: "SortArtistName1", + }, + { + Album: "Album1", AlbumArtist: "AlbumArtist1", Artist: "Artist2", DiscSubtitle: "DiscSubtitle2", + SortAlbumName: "SortAlbumName1", SortAlbumArtistName: "SortAlbumArtistName1", SortArtistName: "SortArtistName2", + }, + } + }) + It("fills the fullText attribute correctly", func() { + album := mfs.ToAlbum() + Expect(album.FullText).To(Equal(" album1 albumartist1 artist1 artist2 discsubtitle1 discsubtitle2 sortalbumartistname1 sortalbumname1 sortartistname1 sortartistname2")) + }) + }) + Context("MbzAlbumID", func() { + When("we have only one MbzAlbumID", func() { + BeforeEach(func() { + mfs = MediaFiles{{MbzAlbumID: "id1"}} + }) + It("sets the correct MbzAlbumID", func() { + album := mfs.ToAlbum() + Expect(album.MbzAlbumID).To(Equal("id1")) + }) + }) + When("we have multiple MbzAlbumID", func() { + BeforeEach(func() { + mfs = MediaFiles{{MbzAlbumID: "id1"}, {MbzAlbumID: "id2"}, {MbzAlbumID: "id1"}} + }) + It("sets the correct MbzAlbumID", func() { + album := mfs.ToAlbum() + Expect(album.MbzAlbumID).To(Equal("id1")) + }) + }) + }) + }) + }) + + Describe("Dirs", func() { + var mfs MediaFiles + + When("there are no media files", func() { + BeforeEach(func() { + mfs = MediaFiles{} + }) + It("returns an empty list", func() { + Expect(mfs.Dirs()).To(BeEmpty()) + }) + }) + + When("there is one media file", func() { + BeforeEach(func() { + mfs = MediaFiles{ + {Path: "/music/artist/album/song.mp3"}, + } + }) + It("returns the directory of the media file", func() { + Expect(mfs.Dirs()).To(Equal([]string{filepath.Clean("/music/artist/album")})) + }) + }) + + When("there are multiple media files in the same directory", func() { + BeforeEach(func() { + mfs = MediaFiles{ + {Path: "/music/artist/album/song1.mp3"}, + {Path: "/music/artist/album/song2.mp3"}, + } + }) + It("returns a single directory", func() { + Expect(mfs.Dirs()).To(Equal([]string{filepath.Clean("/music/artist/album")})) + }) + }) + + When("there are multiple media files in different directories", func() { + BeforeEach(func() { + mfs = MediaFiles{ + {Path: "/music/artist2/album/song2.mp3"}, + {Path: "/music/artist1/album/song1.mp3"}, + } + }) + It("returns all directories", func() { + Expect(mfs.Dirs()).To(Equal([]string{filepath.Clean("/music/artist1/album"), filepath.Clean("/music/artist2/album")})) + }) + }) + + When("there are media files with empty paths", func() { + BeforeEach(func() { + mfs = MediaFiles{ + {Path: ""}, + {Path: "/music/artist/album/song.mp3"}, + } + }) + It("ignores the empty paths", func() { + Expect(mfs.Dirs()).To(Equal([]string{".", filepath.Clean("/music/artist/album")})) }) }) }) diff --git a/persistence/sql_base_repository.go b/persistence/sql_base_repository.go index 5f7e5fcf7..b25a42ff0 100644 --- a/persistence/sql_base_repository.go +++ b/persistence/sql_base_repository.go @@ -140,11 +140,12 @@ func (r sqlRepository) buildSortOrder(sort, order string) string { reverseOrder = "desc" } - var newSort []string parts := strings.FieldsFunc(sort, splitFunc(',')) + newSort := make([]string, 0, len(parts)) for _, p := range parts { f := strings.FieldsFunc(p, splitFunc(' ')) - newField := []string{f[0]} + newField := make([]string, 1, len(f)) + newField[0] = f[0] if len(f) == 1 { newField = append(newField, order) } else { diff --git a/scanner/mapping.go b/scanner/mapping.go index 79195157d..9db464eb3 100644 --- a/scanner/mapping.go +++ b/scanner/mapping.go @@ -143,7 +143,7 @@ func (s MediaFileMapper) albumArtistID(md metadata.Tags) string { func (s MediaFileMapper) mapGenres(genres []string) (string, model.Genres) { var result model.Genres unique := map[string]struct{}{} - var all []string + all := make([]string, 0, len(genres)*2) for i := range genres { gs := strings.FieldsFunc(genres[i], func(r rune) bool { return strings.ContainsRune(conf.Server.Scanner.GenreSeparators, r) diff --git a/scanner/metadata/metadata.go b/scanner/metadata/metadata.go index 768add042..4bcbab0ce 100644 --- a/scanner/metadata/metadata.go +++ b/scanner/metadata/metadata.go @@ -84,7 +84,7 @@ func NewTag(filePath string, fileInfo os.FileInfo, tags ParsedTags) Tags { func removeDuplicatesAndEmpty(values []string) []string { encountered := map[string]struct{}{} empty := true - var result []string + result := make([]string, 0, len(values)) for _, v := range values { if _, ok := encountered[v]; ok { continue @@ -300,7 +300,7 @@ func (t Tags) getFirstTagValue(tagNames ...string) string { } func (t Tags) getAllTagValues(tagNames ...string) []string { - var values []string + values := make([]string, 0, len(tagNames)*2) for _, tag := range tagNames { if v, ok := t.Tags[tag]; ok { values = append(values, v...) @@ -311,7 +311,8 @@ func (t Tags) getAllTagValues(tagNames ...string) []string { func (t Tags) getSortTag(originalTag string, tagNames ...string) string { formats := []string{"sort%s", "sort_%s", "sort-%s", "%ssort", "%s_sort", "%s-sort"} - all := []string{originalTag} + all := make([]string, 1, len(tagNames)*len(formats)+1) + all[0] = originalTag for _, tag := range tagNames { for _, format := range formats { name := fmt.Sprintf(format, tag) diff --git a/scanner/tag_scanner.go b/scanner/tag_scanner.go index ab315c778..ec1177eeb 100644 --- a/scanner/tag_scanner.go +++ b/scanner/tag_scanner.go @@ -294,7 +294,7 @@ func (s *TagScanner) processChangedDir(ctx context.Context, refresher *refresher // If track from folder is newer than the one in DB, select for update/insert in DB log.Trace(ctx, "Processing changed folder", "dir", dir, "tracksInDB", len(currentTracks), "tracksInFolder", len(files)) - var filesToUpdate []string + filesToUpdate := make([]string, 0, len(files)) for filePath, entry := range files { c, inDB := currentTracks[filePath] if !inDB || fullScan { diff --git a/scanner/walk_dir_tree.go b/scanner/walk_dir_tree.go index f348f7c5d..fa4c2d24c 100644 --- a/scanner/walk_dir_tree.go +++ b/scanner/walk_dir_tree.go @@ -64,7 +64,6 @@ func walkFolder(ctx context.Context, rootPath string, currentFolder string, resu } func loadDir(ctx context.Context, dirPath string) ([]string, *dirStats, error) { - var children []string stats := &dirStats{} dirInfo, err := os.Stat(dirPath) @@ -77,11 +76,13 @@ func loadDir(ctx context.Context, dirPath string) ([]string, *dirStats, error) { dir, err := os.Open(dirPath) if err != nil { log.Error(ctx, "Error in Opening directory", "path", dirPath, err) - return children, stats, err + return nil, stats, err } defer dir.Close() - for _, entry := range fullReadDir(ctx, dir) { + entries := fullReadDir(ctx, dir) + children := make([]string, 0, len(entries)) + for _, entry := range entries { isDir, err := isDirOrSymlinkToDir(dirPath, entry) // Skip invalid symlinks if err != nil { diff --git a/utils/cache/simple_cache.go b/utils/cache/simple_cache.go index db95a8de5..595a26637 100644 --- a/utils/cache/simple_cache.go +++ b/utils/cache/simple_cache.go @@ -100,7 +100,7 @@ func (c *simpleCache[K, V]) evictExpired() { } func (c *simpleCache[K, V]) Keys() []K { - var res []K + res := make([]K, 0, c.data.Len()) c.data.Range(func(item *ttlcache.Item[K, V]) bool { if !item.IsExpired() { res = append(res, item.Key()) @@ -111,7 +111,7 @@ func (c *simpleCache[K, V]) Keys() []K { } func (c *simpleCache[K, V]) Values() []V { - var res []V + res := make([]V, 0, c.data.Len()) c.data.Range(func(item *ttlcache.Item[K, V]) bool { if !item.IsExpired() { res = append(res, item.Value())