feat: the library's folders are set in Admin › Library, and changing a folder moves its tracks with it, keeping their plays, likes and playlists (#6129)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m20s
release / go (push) Failing after 1m37s
release / integration (push) Failing after 4m56s
release / android (push) Successful in 5m1s
release / Build signed APK (releases and dev) (push) Successful in 5m13s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 20:37:03 -04:00
co-authored by Claude Opus 5.5
parent 453a977e48
commit edcb3a57ef
25 changed files with 1102 additions and 57 deletions
+10 -8
View File
@@ -63,9 +63,10 @@ type Importer struct {
Scanner Scanner
Catalogue Catalogue
Printer Printer
// Roots are the library's folders (the scan paths). New music is filed
// under the one the settings name, or the first.
Roots []string
// Roots are the library's folders (the scan paths), read on every use
// because they change in Settings (#6129). New music is filed under the
// one the settings name, or the first.
Roots func() []string
// DataDir is where cached art lives, for tidying an artist an upgrade
// leaves with nothing (#5524). Empty skips it.
DataDir string
@@ -79,7 +80,7 @@ type Importer struct {
// New returns an Importer. printer may be nil: the check then rests on
// lengths and tags.
func New(pool *pgxpool.Pool, scanner Scanner, cat Catalogue, printer Printer, roots []string, logger *slog.Logger) *Importer {
func New(pool *pgxpool.Pool, scanner Scanner, cat Catalogue, printer Printer, roots func() []string, logger *slog.Logger) *Importer {
if logger == nil {
logger = slog.Default()
}
@@ -549,14 +550,15 @@ func (im *Importer) editions(ctx context.Context, group string) ([]Edition, erro
// root is the library folder new music is filed into.
func (im *Importer) root(ctx context.Context, q *dbq.Queries) (string, error) {
if len(im.Roots) == 0 {
return "", errors.New("there is no library folder to file new music into (MINSTREL_LIBRARY_SCAN_PATHS)")
roots := im.Roots()
if len(roots) == 0 {
return "", errors.New("there is no library folder to file new music into; add one in Settings → Library")
}
s, err := q.GetAcquisitionSettings(ctx)
if err == nil && s.ImportRoot != "" && slices.Contains(im.Roots, s.ImportRoot) {
if err == nil && s.ImportRoot != "" && slices.Contains(roots, s.ImportRoot) {
return s.ImportRoot, nil
}
return im.Roots[0], nil
return roots[0], nil
}
// held reads which of the planned tracks the library already has: by
+1 -1
View File
@@ -130,7 +130,7 @@ func newStack(t *testing.T, printer Printer) *stack {
}
s := &stack{pool: pool, q: q, client: &fakeClient{content: content}, scanner: &fakeScanner{},
library: t.TempDir(), want: w, grab: g}
s.im = New(pool, s.scanner, fakeCatalogue{}, printer, []string{s.library}, slog.New(slog.NewTextHandler(io.Discard, nil)))
s.im = New(pool, s.scanner, fakeCatalogue{}, printer, func() []string { return []string{s.library} }, slog.New(slog.NewTextHandler(io.Discard, nil)))
s.im.ClientFor = func(context.Context, string) (downloader.Driver, error) { return s.client, nil }
return s
}