feat(jobs): surface background jobs with progress, logs and controls
Add a central job registry that library scans and search index builds report into, so background work is visible instead of buried in the settings page. - backend/jobs: registry with per-job ring-buffer logs, capability-driven controls, and one coalesced JobsChanged snapshot at 4Hz - pause survives restart via a job_state table; a paused scan is adopted back on launch and skipped by the soft scan - top-bar indicator, popover, details drawer and a Jobs page replacing the config page's scan UI; per-library start/stop retained - scan timing breakdown moves into the job log, Full rescan to the Jobs page; delete the orphaned library-manager component Also add cmd/indexbuild and cmd/indexexport so the explore index can be built once centrally rather than by every install, which today streams ~205GB from the ListenBrainz spark dump on first run. indexbuild picks build/refresh/rebuild from index state; the Gitea workflow runs it on push, weekly, or manually and publishes only when content changed. fresh-install no longer defaults YJ_HOME under /tmp: it is tmpfs on most distros, and the import needs ~6GB of real disk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,16 +26,19 @@ func (l *Library) ScanLibrary(id int64) error {
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
// Silent dedup: already scanning this library.
|
||||
if l.currentScanLibraryID == id {
|
||||
l.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Silent dedup: already queued.
|
||||
for _, entry := range l.scanQueue {
|
||||
if entry.libraryID == id {
|
||||
l.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -50,6 +53,7 @@ func (l *Library) ScanLibrary(id int64) error {
|
||||
l.scanActive = true
|
||||
l.currentScanLibraryID = entry.libraryID
|
||||
l.currentScanLibraryName = entry.libraryName
|
||||
l.mu.Unlock()
|
||||
|
||||
go l.startScan(entry)
|
||||
|
||||
@@ -58,13 +62,19 @@ func (l *Library) ScanLibrary(id int64) error {
|
||||
|
||||
// A scan is already running — queue this library.
|
||||
l.scanQueue = append(l.scanQueue, entry)
|
||||
queueLength := len(l.scanQueue)
|
||||
l.mu.Unlock()
|
||||
|
||||
runtime.EventsEmit(l.ctx, events.LibraryScanQueued, map[string]any{
|
||||
"libraryId": lib.ID,
|
||||
"libraryName": lib.Name,
|
||||
"queueLength": len(l.scanQueue),
|
||||
"queueLength": queueLength,
|
||||
})
|
||||
|
||||
// Registering the queued job takes l.mu again, so it must happen
|
||||
// after the unlock above.
|
||||
l.registerQueuedScanJob(entry)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -127,6 +137,20 @@ func (l *Library) SoftScanAllLibraries() error {
|
||||
}
|
||||
|
||||
for _, lib := range libs {
|
||||
// A scan the user paused stays paused across restarts — the
|
||||
// soft scan must not quietly start it again behind their back.
|
||||
// RestorePausedScans has already surfaced it in the jobs panel
|
||||
// with a resume button.
|
||||
if l.isScanPausedPersistently(lib.ID) {
|
||||
l.logger.Info(
|
||||
"soft scan: library scan is paused, skipping",
|
||||
"libraryID", lib.ID,
|
||||
"libraryName", lib.Name,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
dbCount, countErr := l.db.Queries.CountAudioFilesByLibrary(
|
||||
l.ctx, lib.ID,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user