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:
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
|
||||
"yellowjacket/backend/events"
|
||||
"yellowjacket/backend/jobs"
|
||||
)
|
||||
|
||||
// CancelScan cancels an in-progress scan. Returns immediately;
|
||||
@@ -27,31 +28,66 @@ func (l *Library) CancelScan() {
|
||||
// next pause checkpoint until ResumeScan is called.
|
||||
func (l *Library) PauseScan() {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
if !l.scanActive || l.scanPaused {
|
||||
l.mu.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
l.scanPaused = true
|
||||
l.scanPauseCh = make(chan struct{})
|
||||
pausedID := l.currentScanLibraryID
|
||||
reg := l.jobs
|
||||
l.mu.Unlock()
|
||||
|
||||
runtime.EventsEmit(l.ctx, events.LibraryScanPaused)
|
||||
|
||||
// Confirm the pause on the job — the registry moved it to "pausing"
|
||||
// when the request came in. Writing the durable pause record is a
|
||||
// side effect of reaching StatePaused, so a scan paused now comes
|
||||
// back paused after a restart. Done after releasing l.mu: this
|
||||
// writes to the database, and workers take l.mu on every pause
|
||||
// checkpoint.
|
||||
if reg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if h := reg.Get(scanJobID(pausedID)); h != nil {
|
||||
h.SetState(jobs.StatePaused)
|
||||
h.SetPhase("Paused")
|
||||
h.Logf(jobs.LevelInfo, "Scan paused")
|
||||
}
|
||||
}
|
||||
|
||||
// ResumeScan unblocks a paused scan.
|
||||
func (l *Library) ResumeScan() {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
if !l.scanPaused {
|
||||
l.mu.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
l.scanPaused = false
|
||||
close(l.scanPauseCh) // unblocks all waiting workers
|
||||
resumedID := l.currentScanLibraryID
|
||||
reg := l.jobs
|
||||
l.mu.Unlock()
|
||||
|
||||
runtime.EventsEmit(l.ctx, events.LibraryScanResumed)
|
||||
|
||||
// Clears the durable pause record as a side effect of leaving
|
||||
// StatePaused.
|
||||
if reg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if h := reg.Get(scanJobID(resumedID)); h != nil {
|
||||
h.SetState(jobs.StateRunning)
|
||||
h.Logf(jobs.LevelInfo, "Scan resumed")
|
||||
}
|
||||
}
|
||||
|
||||
// IsScanActive returns whether a scan is currently running.
|
||||
|
||||
Reference in New Issue
Block a user