Stop warning about slave-only content in layout checks

A singles library naturally has artists and songs the albums library
lacks, so "only N of M top-level folders exist in the master" and "N of M
slave files have no counterpart" were false alarms.

Layout warnings now only flag real layout problems:
- Folders that are the same but named differently (case, punctuation,
  accents, "&"/"and", leading/trailing "The"), at the top level and one
  level down inside shared folders, with examples.
- Songs found in both libraries that mostly live in a different folder in
  the master (e.g. Artist/Song vs Artist/Album/Song). Slave-only songs are
  ignored, as are singles/EPs/compilations matched to their album version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-22 17:55:02 -07:00
1 parent e8c534a3ae
commit 0ae4e85bd2
4 files changed
+269 -37

No files matched your search

+13 -30
View File
@@ -298,43 +298,26 @@ func (e *Engine) NormalizePair(p *store.Pair) ([]string, error) {
return layoutWarnings(l), nil
}
// layoutWarnings does a cheap top-level comparison of the two libraries.
// Any folder layout works; what matters is that both libraries use the same
// one, so matching files sit at the same relative path.
// layoutWarnings is the quick check run when a pair is saved and at the
// start of each scan. Content that exists in only one library is normal and
// never warned about; only folders that are the same but named differently are.
func layoutWarnings(l *libs) []string {
var warns []string
list := func(root string) (dirs map[string]bool, files int) {
dirs = map[string]bool{}
ents, _ := os.ReadDir(root)
empty := true
if ents, _ := os.ReadDir(l.slave.Path); len(ents) > 0 {
for _, en := range ents {
if strings.HasPrefix(en.Name(), ".") {
continue
}
if en.IsDir() {
dirs[strings.ToLower(en.Name())] = true
} else if l.hasExt(en.Name()) {
files++
if !strings.HasPrefix(en.Name(), ".") && (en.IsDir() || l.hasExt(en.Name())) {
empty = false
break
}
}
return
}
md, _ := list(l.master.Path)
sd, sf := list(l.slave.Path)
if len(sd) == 0 && sf == 0 {
warns = append(warns, "The slave library is empty. Use Build to pick songs from the master library to hardlink into it.")
return warns
if empty {
return []string{"The slave library is empty. Use Build to pick songs from the master library to hardlink into it."}
}
shared := 0
for d := range sd {
if md[d] {
shared++
}
if w := renamedWarning(renamedFolders(l)); w != "" {
return []string{w}
}
if len(sd) > 0 && shared*2 < len(sd) {
warns = append(warns, fmt.Sprintf("Only %d of %d top-level folders in the slave library also exist in the master library. "+
"Both libraries should use the same folder layout, or most files will not match.", shared, len(sd)))
}
return warns
return nil
}
// ---- helpers ----