Go back to the hardlink deduplicator (1da0132), keeping backups

Restores the code of 1da0132 ("Add a setting to hide the quarantine from
Plex") and removes everything added after it: the tag editor, album art,
organizing/naming, the step-by-step tune-up and library types.

Kept:
- Database backups (Settings -> Backups): scheduled, manual, download,
  restore, and the optional backup before every action.
- The .env-driven docker-compose.yml for Arcane.

Databases that already ran the later migrations keep working: migrations
6-10 stay in the list (they only add columns) so numbering never goes
backwards, and Undo refuses History entries of removed features instead
of treating them as links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-30 10:43:29 -07:00
1 parent 7b180e26db
commit e0617047f7
44 files changed
+103 -10456

No files matched your search

+7 -62
View File
@@ -621,52 +621,17 @@ func (e *Engine) Undo(ctx context.Context, opID int64) error {
if o.PurgedAt != nil {
return errors.New("the quarantined original was already purged")
}
switch o.Action {
case store.ActLink, store.ActPromote, store.ActBuild, store.ActRemove, store.ActRename:
default:
// History can hold operations of features that were removed (tag
// edits, file moves); undoing them as a link would be wrong.
return fmt.Errorf("%q operations can no longer be undone from playlink; their originals stay in the quarantine folder", o.Action)
}
l, err := e.loadPair(ctx, o.PairID)
if err != nil {
return err
}
if o.Action == store.ActCover {
if err := e.undoCover(l, o); err != nil {
return err
}
if err := e.st.MarkUndone(ctx, o.ID); err != nil {
return err
}
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
}
if err := e.st.MarkUndone(ctx, o.ID); err != nil {
return err
}
e.log.Info("file move undone", "op", o.ID)
_, _ = e.StartScan(o.PairID, "undo")
return nil
}
if o.Action == store.ActTags {
if err := e.undoTags(ctx, l, o); err != nil {
return err
}
if err := e.st.MarkUndone(ctx, o.ID); err != nil {
return err
}
e.log.Info("tag edit undone", "op", o.ID)
return nil
}
if o.Action == store.ActRename {
if err := undoRename(l, o); err != nil {
return err
@@ -890,26 +855,6 @@ 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)
}
_ = e.st.FinishOp(ctx, o.ID, store.OpInterrupted, "interrupted by shutdown; rescan to verify state")
continue
}
if o.Action == store.ActTags {
msg := e.recoverTags(ctx, o)
_ = e.st.FinishOp(ctx, o.ID, store.OpInterrupted, msg)
e.log.Warn("recovered interrupted tag edit", "op", o.ID, "result", msg)
continue
}
if o.Action == store.ActRename {
if p, err := e.st.Pair(ctx, o.PairID); err == nil {
recoverRename(p, o, e.log)