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
+31 -9
View File
@@ -67,18 +67,16 @@ type Watcher struct {
scanner *Scanner
enricher *coverart.Enricher
logger *slog.Logger
roots []string
debounce time.Duration
}
func NewWatcher(
scanner *Scanner, enricher *coverart.Enricher, logger *slog.Logger, roots []string,
) *Watcher {
// NewWatcher watches the scanner's folders, and moves to the new ones when
// they change in Settings (#6129).
func NewWatcher(scanner *Scanner, enricher *coverart.Enricher, logger *slog.Logger) *Watcher {
return &Watcher{
scanner: scanner,
enricher: enricher,
logger: logger,
roots: roots,
debounce: watcherDebounce,
}
}
@@ -87,8 +85,32 @@ func NewWatcher(
// fsnotify watcher cannot be created at all; per-path watch failures and
// fsnotify errors are logged and tolerated.
func (w *Watcher) Run(ctx context.Context) error {
if len(w.roots) == 0 {
w.logger.Info("library watcher: no scan paths configured, watcher disabled")
for {
if err := w.watch(ctx, w.scanner.Roots()); err != nil {
return err
}
if ctx.Err() != nil {
return nil
}
}
}
// watch watches roots until ctx is cancelled or the folders change.
func (w *Watcher) watch(ctx context.Context, roots []string) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
select {
case <-w.scanner.RootsChanged():
w.logger.Info("library watcher: folders changed, restarting")
cancel()
case <-ctx.Done():
}
}()
if len(roots) == 0 {
w.logger.Info("library watcher: no scan paths configured, watcher idle")
<-ctx.Done()
return nil
}
fsw, err := fsnotify.NewWatcher()
@@ -98,10 +120,10 @@ func (w *Watcher) Run(ctx context.Context) error {
}
defer func() { _ = fsw.Close() }()
for _, root := range w.roots {
for _, root := range roots {
w.addRecursive(fsw, root)
}
w.logger.Info("library watcher: started", "roots", len(w.roots))
w.logger.Info("library watcher: started", "roots", len(roots))
batches := make(chan []string, batchQueueDepth)
go w.processLoop(ctx, batches)