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

-67
View File
@@ -403,70 +403,3 @@ func CopyReplace(src, dst string, mode fs.FileMode) error {
func MkdirAllLike(dir string) error {
return os.MkdirAll(dir, 0o775)
}
// CopyNew copies src to dst, which must not exist. The copy is fsynced and
// verified byte for byte. Used for backups that must not share an inode with
// the original (the original is about to be modified in place).
func CopyNew(src, dst string, mode fs.FileMode) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode.Perm())
if err != nil {
return err
}
fail := func(e error) error { out.Close(); _ = os.Remove(dst); return e }
if _, err := io.Copy(out, in); err != nil {
return fail(err)
}
if err := out.Sync(); err != nil {
return fail(err)
}
if err := out.Close(); err != nil {
_ = os.Remove(dst)
return err
}
if same, err := SameContent(src, dst); err != nil || !same {
_ = os.Remove(dst)
if err == nil {
err = fmt.Errorf("%w: backup differs from the original", ErrVerifyFailed)
}
return err
}
return SyncDir(filepath.Dir(dst))
}
// OverwriteInPlace replaces the contents of dst with the bytes of src while
// keeping dst's inode, so every hardlink of dst sees the new contents. This is
// not atomic: callers keep a backup (CopyNew) to restore from on failure.
func OverwriteInPlace(dst, src string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_WRONLY|os.O_TRUNC, 0)
if err != nil {
return err
}
if _, err := io.Copy(out, in); err != nil {
out.Close()
return err
}
if err := out.Sync(); err != nil {
out.Close()
return err
}
if err := out.Close(); err != nil {
return err
}
if same, err := SameContent(src, dst); err != nil || !same {
if err == nil {
err = fmt.Errorf("%w: file does not match what was written", ErrVerifyFailed)
}
return err
}
return nil
}