Add the step-by-step fixer and library types

Each pair is a Music (Artist/Album/01 Title) or Soundtracks (Album/01 Title)
library. The new Fixer tab takes the master, then the slave, through four
reviewed steps: folder structure, album tags (optional MusicBrainz lookup;
the slave reuses the fixed master's tags), folder names and file names,
then links the two libraries. Every fix is an undoable operation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-30 09:10:48 -07:00
1 parent b11d0806c1
commit 428935dc58
19 files changed
+3503 -23

No files matched your search

+18
View File
@@ -635,6 +635,17 @@ func (e *Engine) Undo(ctx context.Context, opID int64) error {
e.log.Info("folder cover undone", "op", o.ID)
return nil
}
if o.Action == store.ActMoveDir {
if err := undoMoveDir(l, o); err != nil {
return err
}
if err := e.st.MarkUndone(ctx, o.ID); err != nil {
return err
}
e.log.Info("folder move undone", "op", o.ID, "folder", o.RelNew)
_, _ = e.StartScan(o.PairID, "undo")
return nil
}
if o.Action == store.ActMove {
if err := undoMove(l, o); err != nil {
return err
@@ -879,6 +890,13 @@ func (e *Engine) Recover(ctx context.Context) {
return
}
for _, o := range ops {
if o.Action == store.ActMoveDir {
if l, err := e.loadPair(ctx, o.PairID); err == nil {
recoverMoveDir(l, o, e.log)
}
_ = e.st.FinishOp(ctx, o.ID, store.OpInterrupted, "interrupted by shutdown; rescan to verify state")
continue
}
if o.Action == store.ActMove {
if l, err := e.loadPair(ctx, o.PairID); err == nil {
recoverMove(l, o)