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>
128 lines
2.7 KiB
Go
128 lines
2.7 KiB
Go
package library
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
|
|
"yellowjacket/backend/events"
|
|
"yellowjacket/backend/jobs"
|
|
)
|
|
|
|
// CancelScan cancels an in-progress scan. Returns immediately;
|
|
// scan goroutines stop at their next checkpoint.
|
|
//
|
|
// Deprecated: Use CancelCurrentScan or CancelAllScans for
|
|
// queue-aware cancellation.
|
|
func (l *Library) CancelScan() {
|
|
l.mu.Lock()
|
|
cancel := l.scanCancel
|
|
l.mu.Unlock()
|
|
|
|
if cancel != nil {
|
|
cancel()
|
|
}
|
|
}
|
|
|
|
// PauseScan pauses an in-progress scan. Workers block at their
|
|
// next pause checkpoint until ResumeScan is called.
|
|
func (l *Library) PauseScan() {
|
|
l.mu.Lock()
|
|
|
|
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()
|
|
|
|
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.
|
|
func (l *Library) IsScanActive() bool {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
return l.scanActive
|
|
}
|
|
|
|
// IsScanPaused returns whether the scan is currently paused.
|
|
func (l *Library) IsScanPaused() bool {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
return l.scanPaused
|
|
}
|
|
|
|
// waitIfPaused blocks the calling goroutine if the scan is paused.
|
|
// Returns ctx.Err() if the context is cancelled while waiting.
|
|
func (l *Library) waitIfPaused(ctx context.Context) error {
|
|
l.mu.Lock()
|
|
ch := l.scanPauseCh
|
|
paused := l.scanPaused
|
|
l.mu.Unlock()
|
|
|
|
if !paused || ch == nil {
|
|
return nil
|
|
}
|
|
|
|
select {
|
|
case <-ch: // closed = unpaused
|
|
return nil
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|