mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
Merge remote-tracking branch 'origin/master' into fix/artwork-no-absent-retry
This commit is contained in:
commit
cc07a78a29
@ -25,8 +25,8 @@ func (s *playlists) parseM3U(ctx context.Context, pls *model.Playlist, folder *m
|
||||
return err
|
||||
}
|
||||
var mfs model.MediaFiles
|
||||
// Chunk size of 100 lines, as each line can generate up to 4 lookup candidates
|
||||
// (NFC/NFD × raw/lowercase), and SQLite has a max expression tree depth of 1000.
|
||||
// Chunked so a huge playlist is not held in memory at once. Each line yields up to
|
||||
// 4 lookup candidates (NFC/NFD × raw/lowercase), far below SQLite's 32766 variables.
|
||||
for lines := range slice.CollectChunks(slice.LinesFrom(reader), 100) {
|
||||
filteredLines := make([]string, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -355,7 +356,10 @@ func (r *mediaFileRepository) GetCursorWithArtwork(options ...model.QueryOptions
|
||||
// Library-qualified paths search within the specified library, while unqualified paths
|
||||
// search across all libraries for backward compatibility.
|
||||
func (r *mediaFileRepository) FindByPaths(paths []string) (model.MediaFiles, error) {
|
||||
query := Or{}
|
||||
// One IN list per library instead of one OR term per path: SQLite abandons the
|
||||
// path index at just two OR-ed equality terms and scans the whole table.
|
||||
byLibrary := map[int][]string{}
|
||||
var unqualified []string
|
||||
|
||||
for _, path := range paths {
|
||||
parts := strings.SplitN(path, ":", 2)
|
||||
@ -366,17 +370,24 @@ func (r *mediaFileRepository) FindByPaths(paths []string) (model.MediaFiles, err
|
||||
// Invalid format, skip
|
||||
continue
|
||||
}
|
||||
relativePath := parts[1]
|
||||
query = append(query, And{
|
||||
Eq{"path collate nocase": relativePath},
|
||||
Eq{"library_id": libraryID},
|
||||
})
|
||||
byLibrary[libraryID] = append(byLibrary[libraryID], parts[1])
|
||||
} else {
|
||||
// Unqualified path: search across all libraries
|
||||
query = append(query, Eq{"path collate nocase": path})
|
||||
unqualified = append(unqualified, path)
|
||||
}
|
||||
}
|
||||
|
||||
query := Or{}
|
||||
for _, libraryID := range slices.Sorted(maps.Keys(byLibrary)) {
|
||||
query = append(query, And{
|
||||
Eq{"path collate nocase": byLibrary[libraryID]},
|
||||
Eq{"library_id": libraryID},
|
||||
})
|
||||
}
|
||||
if len(unqualified) > 0 {
|
||||
query = append(query, Eq{"path collate nocase": unqualified})
|
||||
}
|
||||
|
||||
if len(query) == 0 {
|
||||
return model.MediaFiles{}, nil
|
||||
}
|
||||
|
||||
@ -1055,6 +1055,28 @@ var _ = Describe("MediaRepository", func() {
|
||||
Expect(results).To(HaveLen(1))
|
||||
Expect(results[0].ID).To(Equal("otherlib-track"))
|
||||
})
|
||||
|
||||
It("resolves paths from multiple libraries in a single call", func() {
|
||||
adminMr := NewMediaFileRepository(request.WithUser(GinkgoT().Context(), adminUser), GetDBXBuilder())
|
||||
results, err := adminMr.FindByPaths([]string{
|
||||
"1:artist/Album/track.mp3",
|
||||
fmt.Sprintf("%d:hidden/test.mp3", otherLib.ID),
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(results).To(HaveLen(2))
|
||||
Expect([]string{results[0].ID, results[1].ID}).To(ConsistOf("findpath-1", "otherlib-track"))
|
||||
})
|
||||
|
||||
It("keeps each path scoped to its own library when several are queried", func() {
|
||||
adminMr := NewMediaFileRepository(request.WithUser(GinkgoT().Context(), adminUser), GetDBXBuilder())
|
||||
// Each path exists, but under the other library's ID, so neither must match.
|
||||
results, err := adminMr.FindByPaths([]string{
|
||||
fmt.Sprintf("%d:artist/Album/track.mp3", otherLib.ID),
|
||||
"1:hidden/test.mp3",
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(results).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user