Drop Artist/Album/Title depth assumptions from layout checks

Matching has always been by relative path, so any folder layout works as
long as both libraries use the same one. Remove the depth and loose-root-file
warnings, which flagged valid layouts, and reword the UI and README to say
"same folder layout on both sides" with Artist/Album/Title.ext only as an
example. The "elsewhere" fallback is now described as same top-level folder,
different subfolder. Add a test covering flat and deeply nested layouts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-22 16:48:55 -07:00
1 parent f49274360b
commit 75e0516434
9 files changed
+52 -33

No files matched your search

+4 -14
View File
@@ -20,7 +20,6 @@ import (
type walkStats struct {
files int
symlinks int
offDepth int // files not at Artist/Album/Title depth
}
// walk lists audio files beneath root. Hidden entries, playlink staging files,
@@ -67,9 +66,6 @@ func walk(ctx context.Context, l *libs, root fsops.Root, out map[string]fsops.In
}
out[rel] = in
ws.files++
if strings.Count(rel, string(filepath.Separator)) != 2 {
ws.offDepth++
}
return nil
})
return ws, err
@@ -107,7 +103,7 @@ type hostIndex struct {
files map[string]fsops.Info
lower map[string]string // lower(rel) -> rel
byDir map[string][]string // lower(dir) -> rels
byTitle map[string][]string // lower(artist)|normTitle -> rels
byTitle map[string][]string // lower(top folder)|normTitle -> rels
}
func buildIndex(files map[string]fsops.Info) *hostIndex {
@@ -167,8 +163,8 @@ func (ix *hostIndex) find(relSlave string) (string, string) {
if len(fuzzyHits) == 1 {
return fuzzyHits[0], store.KindFuzzy
}
if artist := firstComponent(relSlave); artist != "" && norm != "" {
if hits := ix.byTitle[strings.ToLower(artist)+"|"+norm]; len(hits) == 1 {
if top := firstComponent(relSlave); top != "" && norm != "" {
if hits := ix.byTitle[strings.ToLower(top)+"|"+norm]; len(hits) == 1 {
return hits[0], store.KindElsewhere
}
}
@@ -310,14 +306,8 @@ func (e *Engine) scan(ctx context.Context, j *Job, pairID int64, trigger string)
if ss.files > 0 {
if n := kinds[store.KindElsewhere] + kinds[store.KindNone]; n*4 > ss.files {
warns = append(warns, fmt.Sprintf("%d of %d slave files have no counterpart at the same path in the host library. "+
"Check that both libraries use the same Artist/Album/Title.ext layout.", n, ss.files))
"Check that both libraries use the same folder layout.", n, ss.files))
}
if ss.offDepth > 0 {
warns = append(warns, fmt.Sprintf("%d slave files are not at Artist/Album/Title.ext depth.", ss.offDepth))
}
}
if hs.offDepth > 0 {
warns = append(warns, fmt.Sprintf("%d host files are not at Artist/Album/Title.ext depth.", hs.offDepth))
}
if hs.symlinks+ss.symlinks > 0 {
warns = append(warns, fmt.Sprintf("Ignored %d symlinks (playlink never follows or replaces symlinks).", hs.symlinks+ss.symlinks))