mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-18 21:07:44 +03:00
refactor: reduce GC pressure by pre-allocating slices
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
3982ba7258
commit
d229ff39e5
@ -84,7 +84,7 @@ type Albums []Album
|
|||||||
// It assumes all albums have the same AlbumArtist, or else results are unpredictable.
|
// It assumes all albums have the same AlbumArtist, or else results are unpredictable.
|
||||||
func (als Albums) ToAlbumArtist() Artist {
|
func (als Albums) ToAlbumArtist() Artist {
|
||||||
a := Artist{AlbumCount: len(als)}
|
a := Artist{AlbumCount: len(als)}
|
||||||
var mbzArtistIds []string
|
mbzArtistIds := make([]string, 0, len(als))
|
||||||
for _, al := range als {
|
for _, al := range als {
|
||||||
a.ID = al.AlbumArtistID
|
a.ID = al.AlbumArtistID
|
||||||
a.Name = al.AlbumArtist
|
a.Name = al.AlbumArtist
|
||||||
|
@ -39,11 +39,11 @@ func ToLyrics(language, text string) (*Lyrics, error) {
|
|||||||
text = str.SanitizeText(text)
|
text = str.SanitizeText(text)
|
||||||
|
|
||||||
lines := strings.Split(text, "\n")
|
lines := strings.Split(text, "\n")
|
||||||
|
structuredLines := make([]Line, 0, len(lines)*2)
|
||||||
|
|
||||||
artist := ""
|
artist := ""
|
||||||
title := ""
|
title := ""
|
||||||
var offset *int64 = nil
|
var offset *int64 = nil
|
||||||
structuredLines := []Line{}
|
|
||||||
|
|
||||||
synced := syncRegex.MatchString(text)
|
synced := syncRegex.MatchString(text)
|
||||||
priorLine := ""
|
priorLine := ""
|
||||||
@ -105,7 +105,7 @@ func ToLyrics(language, text string) (*Lyrics, error) {
|
|||||||
Value: strings.TrimSpace(priorLine),
|
Value: strings.TrimSpace(priorLine),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
timestamps = []int64{}
|
timestamps = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
end := 0
|
end := 0
|
||||||
|
@ -108,11 +108,9 @@ type MediaFiles []MediaFile
|
|||||||
|
|
||||||
// Dirs returns a deduped list of all directories from the MediaFiles' paths
|
// Dirs returns a deduped list of all directories from the MediaFiles' paths
|
||||||
func (mfs MediaFiles) Dirs() []string {
|
func (mfs MediaFiles) Dirs() []string {
|
||||||
var dirs []string
|
dirs := slice.Map(mfs, func(m MediaFile) string {
|
||||||
for _, mf := range mfs {
|
return filepath.Dir(m.Path)
|
||||||
dir, _ := filepath.Split(mf.Path)
|
})
|
||||||
dirs = append(dirs, filepath.Clean(dir))
|
|
||||||
}
|
|
||||||
slices.Sort(dirs)
|
slices.Sort(dirs)
|
||||||
return slices.Compact(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.
|
// It assumes all mediafiles have the same Album, or else results are unpredictable.
|
||||||
func (mfs MediaFiles) ToAlbum() Album {
|
func (mfs MediaFiles) ToAlbum() Album {
|
||||||
a := Album{SongCount: len(mfs)}
|
a := Album{SongCount: len(mfs)}
|
||||||
var fullText []string
|
fullText := make([]string, 0, len(mfs))
|
||||||
var albumArtistIds []string
|
albumArtistIds := make([]string, 0, len(mfs))
|
||||||
var songArtistIds []string
|
songArtistIds := make([]string, 0, len(mfs))
|
||||||
var mbzAlbumIds []string
|
mbzAlbumIds := make([]string, 0, len(mfs))
|
||||||
var comments []string
|
comments := make([]string, 0, len(mfs))
|
||||||
var years []int
|
years := make([]int, 0, len(mfs))
|
||||||
var dates []string
|
dates := make([]string, 0, len(mfs))
|
||||||
var originalYears []int
|
originalYears := make([]int, 0, len(mfs))
|
||||||
var originalDates []string
|
originalDates := make([]string, 0, len(mfs))
|
||||||
var releaseDates []string
|
releaseDates := make([]string, 0, len(mfs))
|
||||||
for _, m := range mfs {
|
for _, m := range mfs {
|
||||||
// We assume these attributes are all the same for all songs on an album
|
// We assume these attributes are all the same for all songs on an album
|
||||||
a.ID = m.AlbumID
|
a.ID = m.AlbumID
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package model_test
|
package model_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/conf"
|
"github.com/navidrome/navidrome/conf"
|
||||||
@ -13,7 +14,7 @@ import (
|
|||||||
|
|
||||||
var _ = Describe("MediaFiles", func() {
|
var _ = Describe("MediaFiles", func() {
|
||||||
var mfs MediaFiles
|
var mfs MediaFiles
|
||||||
|
Describe("ToAlbum", func() {
|
||||||
Context("Simple attributes", func() {
|
Context("Simple attributes", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
mfs = MediaFiles{
|
mfs = MediaFiles{
|
||||||
@ -270,6 +271,67 @@ var _ = Describe("MediaFiles", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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")}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
var _ = Describe("MediaFile", func() {
|
var _ = Describe("MediaFile", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
DeferCleanup(configtest.SetupConfig())
|
DeferCleanup(configtest.SetupConfig())
|
||||||
|
@ -140,11 +140,12 @@ func (r sqlRepository) buildSortOrder(sort, order string) string {
|
|||||||
reverseOrder = "desc"
|
reverseOrder = "desc"
|
||||||
}
|
}
|
||||||
|
|
||||||
var newSort []string
|
|
||||||
parts := strings.FieldsFunc(sort, splitFunc(','))
|
parts := strings.FieldsFunc(sort, splitFunc(','))
|
||||||
|
newSort := make([]string, 0, len(parts))
|
||||||
for _, p := range parts {
|
for _, p := range parts {
|
||||||
f := strings.FieldsFunc(p, splitFunc(' '))
|
f := strings.FieldsFunc(p, splitFunc(' '))
|
||||||
newField := []string{f[0]}
|
newField := make([]string, 1, len(f))
|
||||||
|
newField[0] = f[0]
|
||||||
if len(f) == 1 {
|
if len(f) == 1 {
|
||||||
newField = append(newField, order)
|
newField = append(newField, order)
|
||||||
} else {
|
} else {
|
||||||
|
@ -143,7 +143,7 @@ func (s MediaFileMapper) albumArtistID(md metadata.Tags) string {
|
|||||||
func (s MediaFileMapper) mapGenres(genres []string) (string, model.Genres) {
|
func (s MediaFileMapper) mapGenres(genres []string) (string, model.Genres) {
|
||||||
var result model.Genres
|
var result model.Genres
|
||||||
unique := map[string]struct{}{}
|
unique := map[string]struct{}{}
|
||||||
var all []string
|
all := make([]string, 0, len(genres)*2)
|
||||||
for i := range genres {
|
for i := range genres {
|
||||||
gs := strings.FieldsFunc(genres[i], func(r rune) bool {
|
gs := strings.FieldsFunc(genres[i], func(r rune) bool {
|
||||||
return strings.ContainsRune(conf.Server.Scanner.GenreSeparators, r)
|
return strings.ContainsRune(conf.Server.Scanner.GenreSeparators, r)
|
||||||
|
@ -84,7 +84,7 @@ func NewTag(filePath string, fileInfo os.FileInfo, tags ParsedTags) Tags {
|
|||||||
func removeDuplicatesAndEmpty(values []string) []string {
|
func removeDuplicatesAndEmpty(values []string) []string {
|
||||||
encountered := map[string]struct{}{}
|
encountered := map[string]struct{}{}
|
||||||
empty := true
|
empty := true
|
||||||
var result []string
|
result := make([]string, 0, len(values))
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
if _, ok := encountered[v]; ok {
|
if _, ok := encountered[v]; ok {
|
||||||
continue
|
continue
|
||||||
@ -300,7 +300,7 @@ func (t Tags) getFirstTagValue(tagNames ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t Tags) getAllTagValues(tagNames ...string) []string {
|
func (t Tags) getAllTagValues(tagNames ...string) []string {
|
||||||
var values []string
|
values := make([]string, 0, len(tagNames)*2)
|
||||||
for _, tag := range tagNames {
|
for _, tag := range tagNames {
|
||||||
if v, ok := t.Tags[tag]; ok {
|
if v, ok := t.Tags[tag]; ok {
|
||||||
values = append(values, v...)
|
values = append(values, v...)
|
||||||
@ -311,7 +311,8 @@ func (t Tags) getAllTagValues(tagNames ...string) []string {
|
|||||||
|
|
||||||
func (t Tags) getSortTag(originalTag string, 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"}
|
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 _, tag := range tagNames {
|
||||||
for _, format := range formats {
|
for _, format := range formats {
|
||||||
name := fmt.Sprintf(format, tag)
|
name := fmt.Sprintf(format, tag)
|
||||||
|
@ -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
|
// 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))
|
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 {
|
for filePath, entry := range files {
|
||||||
c, inDB := currentTracks[filePath]
|
c, inDB := currentTracks[filePath]
|
||||||
if !inDB || fullScan {
|
if !inDB || fullScan {
|
||||||
|
@ -64,7 +64,6 @@ func walkFolder(ctx context.Context, rootPath string, currentFolder string, resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadDir(ctx context.Context, dirPath string) ([]string, *dirStats, error) {
|
func loadDir(ctx context.Context, dirPath string) ([]string, *dirStats, error) {
|
||||||
var children []string
|
|
||||||
stats := &dirStats{}
|
stats := &dirStats{}
|
||||||
|
|
||||||
dirInfo, err := os.Stat(dirPath)
|
dirInfo, err := os.Stat(dirPath)
|
||||||
@ -77,11 +76,13 @@ func loadDir(ctx context.Context, dirPath string) ([]string, *dirStats, error) {
|
|||||||
dir, err := os.Open(dirPath)
|
dir, err := os.Open(dirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Error in Opening directory", "path", dirPath, err)
|
log.Error(ctx, "Error in Opening directory", "path", dirPath, err)
|
||||||
return children, stats, err
|
return nil, stats, err
|
||||||
}
|
}
|
||||||
defer dir.Close()
|
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)
|
isDir, err := isDirOrSymlinkToDir(dirPath, entry)
|
||||||
// Skip invalid symlinks
|
// Skip invalid symlinks
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
4
utils/cache/simple_cache.go
vendored
4
utils/cache/simple_cache.go
vendored
@ -100,7 +100,7 @@ func (c *simpleCache[K, V]) evictExpired() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *simpleCache[K, V]) Keys() []K {
|
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 {
|
c.data.Range(func(item *ttlcache.Item[K, V]) bool {
|
||||||
if !item.IsExpired() {
|
if !item.IsExpired() {
|
||||||
res = append(res, item.Key())
|
res = append(res, item.Key())
|
||||||
@ -111,7 +111,7 @@ func (c *simpleCache[K, V]) Keys() []K {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *simpleCache[K, V]) Values() []V {
|
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 {
|
c.data.Range(func(item *ttlcache.Item[K, V]) bool {
|
||||||
if !item.IsExpired() {
|
if !item.IsExpired() {
|
||||||
res = append(res, item.Value())
|
res = append(res, item.Value())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user