fix(persistence): also try a numeric colon prefix as a plain path

'1999: A Different Life/01.mp3' parsed as library 1999 plus a truncated path
and matched nothing. The prefix is ambiguous, so search both ways.

Also buffer the json branch of 'missing list', which wrote a syscall per row.
This commit is contained in:
Deluan 2026-09-12 10:22:05 -04:00
parent b57bf54b26
commit 9f216f624a
3 changed files with 16 additions and 6 deletions

View File

@ -1,6 +1,7 @@
package cmd
import (
"bufio"
"context"
"encoding/csv"
"encoding/json"
@ -84,18 +85,19 @@ func runMissingList(ctx context.Context) {
// writeMissingList streams the cursor so a library with many missing files doesn't get loaded into memory
func writeMissingList(w io.Writer, format string, mfs model.MediaFileCursor) error {
if format == "json" {
_, _ = io.WriteString(w, "[")
bw := bufio.NewWriter(w)
_, _ = io.WriteString(bw, "[")
sep := ""
for mf, err := range mfs {
if err != nil {
return err
}
j, _ := json.Marshal(displayMissingFile{ID: mf.ID, LibraryID: mf.LibraryID, Title: mf.Title, Album: mf.Album, Artist: mf.Artist, Path: mf.Path})
_, _ = fmt.Fprintf(w, "%s%s", sep, j)
_, _ = fmt.Fprintf(bw, "%s%s", sep, j)
sep = ","
}
_, err := io.WriteString(w, "]\n")
return err
_, _ = io.WriteString(bw, "]\n")
return bw.Flush()
}
cw := csv.NewWriter(w)

View File

@ -362,11 +362,11 @@ func (r *mediaFileRepository) FindByPaths(paths []string) (model.MediaFiles, err
var unqualified []string
for _, path := range paths {
// A colon only qualifies the path when what precedes it is a library id ("Bach: Goldberg/01.mp3" is a plain path)
// A numeric prefix is ambiguous: "1:foo.mp3" qualifies a library, but "1999: A Life/01.mp3"
// is a plain path. Search both ways rather than guessing.
if id, rest, ok := strings.Cut(path, ":"); ok {
if libraryID, err := strconv.Atoi(id); err == nil {
byLibrary[libraryID] = append(byLibrary[libraryID], rest)
continue
}
}
unqualified = append(unqualified, path)

View File

@ -993,6 +993,7 @@ var _ = Describe("MediaRepository", func() {
// French diacritic: è (U+00E8, can decompose to e + combining grave)
{ID: "findpath-4", LibraryID: 1, Path: "artist/Michèle/song.mp3", Title: "French"},
{ID: "findpath-5", LibraryID: 1, Path: "Bach: Goldberg Variations/01.mp3", Title: "Colon"},
{ID: "findpath-6", LibraryID: 1, Path: "1999: A Different Life/01.mp3", Title: "Numeric colon"},
}
for _, mf := range testFiles {
Expect(mr.Put(&mf)).To(Succeed())
@ -1012,6 +1013,13 @@ var _ = Describe("MediaRepository", func() {
Expect(results[0].ID).To(Equal("findpath-5"))
})
It("finds a plain path whose colon prefix looks like a library id", func() {
results, err := mr.FindByPaths([]string{"1999: A Different Life/01.mp3"})
Expect(err).ToNot(HaveOccurred())
Expect(results).To(HaveLen(1))
Expect(results[0].ID).To(Equal("findpath-6"))
})
It("splits only the first colon of a library-qualified path", func() {
results, err := mr.FindByPaths([]string{"1:Bach: Goldberg Variations/01.mp3"})
Expect(err).ToNot(HaveOccurred())