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

-37
View File
@@ -13,12 +13,9 @@ import (
"sync"
"time"
"github.com/cdnutter/playlink/internal/art"
"github.com/cdnutter/playlink/internal/fsops"
"github.com/cdnutter/playlink/internal/meta"
"github.com/cdnutter/playlink/internal/probe"
"github.com/cdnutter/playlink/internal/store"
"github.com/cdnutter/playlink/internal/tags"
)
// DefaultExtensions are the audio file types considered by default.
@@ -44,20 +41,7 @@ type Engine struct {
// the same files.
mu sync.Mutex
tagTools tags.Tools
beforeAction func(ctx context.Context, reason string) error
tagCache tagCache
artClient *art.Client
artStore *art.Store
meta *meta.Client
fixMu sync.Mutex
fixPlans map[int64]*FixPlan // latest fixer check per pair
fixSeq int64
plansMu sync.Mutex
plans map[int64]*organizePlan // latest "rename now" preview per pair
jobsMu sync.Mutex
jobs map[int64]*Job
@@ -84,7 +68,6 @@ type Job struct {
Error string `json:"error"`
Results map[string]int `json:"results"`
Skipped []string `json:"skipped"`
Notes []string `json:"notes"`
StartedAt int64 `json:"started_at"`
FinishedAt int64 `json:"finished_at,omitempty"`
@@ -106,15 +89,6 @@ func (j *Job) tick() { j.update(func(j *Job) { j.Done++ }) }
func (j *Job) count(key string) { j.update(func(j *Job) { j.Results[key]++ }) }
// note records an informational line (e.g. which cover was chosen).
func (j *Job) note(msg string) {
j.update(func(j *Job) {
if len(j.Notes) < 300 {
j.Notes = append(j.Notes, msg)
}
})
}
func (j *Job) skip(msg string) {
j.update(func(j *Job) {
j.Results["skipped"]++
@@ -138,7 +112,6 @@ func (e *Engine) Job(pairID int64) *Job {
cp.Results[k] = v
}
cp.Skipped = append([]string{}, j.Skipped...) // never nil: the UI expects an array
cp.Notes = append([]string{}, j.Notes...)
return &cp
}
@@ -316,16 +289,6 @@ func (e *Engine) NormalizePair(p *store.Pair) ([]store.Warning, error) {
if p.RetentionDays < 0 || p.ScanIntervalMinutes < 0 || p.SettleMinutes < 0 {
return nil, errors.New("intervals must not be negative")
}
switch p.LibraryType {
case "":
p.LibraryType = LibraryMusic
case LibraryMusic, LibrarySoundtracks:
default:
return nil, errors.New("library type must be music or soundtracks")
}
if err := normalizeOrganize(p); err != nil {
return nil, err
}
if p.ApproxThreshold == 0 {
p.ApproxThreshold = 85
}