feat(16-03): WriteTrackTags pipeline with player safety, scan mutex, events, and app wiring

- TagWriter struct with WriteTrackTags entry point orchestrating full pipeline
- PlayerStopper interface + playerAdapter for import-cycle-free player safety
- PipelineLocker interface + pipelineMu on Library for scan/write mutual exclusion
- TrackMetadataChanged event constant + auto-generated TypeScript events
- TagWriter wired into app.go as Wails binding with SetContext in OnStartup
- 5 pipeline integration tests: player safety, scan mutex, orphan cleanup, genre relink, full DB sync
- Lint fixes: static error, else-if, wsl cuddle, golines
This commit is contained in:
2026-03-17 10:55:10 -04:00
parent 2966079625
commit 64322f9353
7 changed files with 707 additions and 6 deletions
+18
View File
@@ -110,6 +110,11 @@ type Library struct {
// scanHooks holds callbacks for post-scan processing
// (e.g. resolving phantom playlist tracks).
scanHooks ScanHooks
// pipelineMu provides mutual exclusion between the scan
// pipeline and the tag write pipeline. Acquired at the
// start of each pipeline, released at the end.
pipelineMu sync.Mutex
}
// SetRescanHooks provides optional hooks for cross-cutting
@@ -157,6 +162,15 @@ func NewLibrary(
return library, nil
}
// AcquirePipelineLock acquires the pipeline mutex for a tag write
// operation. The caller must call ReleasePipelineLock when done.
// If a scan is currently in progress, AcquirePipelineLock blocks
// until it completes (and vice versa).
func (l *Library) AcquirePipelineLock() { l.pipelineMu.Lock() }
// ReleasePipelineLock releases the pipeline mutex after a tag write.
func (l *Library) ReleasePipelineLock() { l.pipelineMu.Unlock() }
// SetContext sets the Wails runtime context and registers event handlers.
func (l *Library) SetContext(ctx context.Context) {
l.mu.Lock()
@@ -187,6 +201,10 @@ func (l *Library) scanInternal(
libraryName string,
libraryPath string,
) *ScanMetrics {
// Acquire pipeline lock for scan/write mutual exclusion.
l.pipelineMu.Lock()
defer l.pipelineMu.Unlock()
metrics := newScanMetrics()
metrics.LibraryID = libraryID
metrics.LibraryName = libraryName