Loosen approximate matching and link to the album version's folders

Matching:
- Prefer the studio album over singles, EPs and compilations when a song
  exists on several releases, including when the master also has the
  single at the slave's exact folder.
- Ignore release labels (Single Version, Remastered, Explicit, Deluxe)
  inside brackets or a trailing " - ..." part; "Clean" stays a version.
- Treat live/acoustic/remix/... album folders as different recordings;
  the strict top-folder fallback now respects this too.
- Add a last-resort library-wide search that only accepts candidates whose
  artist tag agrees with the slave file.

Linking:
- Cross-folder matches now mirror the master's path in the slave (e.g. a
  single moves into the album folder) and prune the emptied folder; a
  "keep in current folder" option preserves the old behaviour. Undo
  recreates and prunes folders accordingly.
- Operations record the match kind and reason, so undo (and promotes that
  rename the master file) restore the correct pairing instead of leaving
  the file unmatched until the next scan.

UI: rewrite the approximate matching description with Avenged Sevenfold
examples and clear safeguards, and show where linking will place a file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-22 17:25:21 -07:00
1 parent 381b804742
commit 5f895cab6a
12 files changed
+717 -128

No files matched your search

+9 -6
View File
@@ -246,7 +246,8 @@ type Match struct {
SlaveProbe json.RawMessage `json:"slave_probe,omitempty"`
Verdict json.RawMessage `json:"verdict,omitempty"`
Note string `json:"note"`
Reason string `json:"reason"` // why an approximate match was chosen
Reason string `json:"reason"` // why an approximate match was chosen
LinkTarget string `json:"link_target"` // where linking puts the slave file (filled by the API)
Ignored bool `json:"ignored"`
UpdatedAt int64 `json:"updated_at"`
}
@@ -500,6 +501,8 @@ type Operation struct {
RelMaster string `json:"rel_master"`
RelSlave string `json:"rel_slave"`
RelNew string `json:"rel_new"`
MatchKind string `json:"match_kind"`
MatchReason string `json:"match_reason"`
QuarantinePath string `json:"quarantine_path"`
QuarantineSide string `json:"quarantine_side"`
TmpPath string `json:"-"`
@@ -532,13 +535,13 @@ const (
)
const opCols = `id, pair_id, action, rel_master, rel_slave, rel_new, quarantine_path, quarantine_side, tmp_path, bytes,
dry_run, trigger, status, error, created_at, finished_at, undone_at, purged_at`
dry_run, trigger, status, error, created_at, finished_at, undone_at, purged_at, match_kind, match_reason`
func scanOp(r scanner) (Operation, error) {
var o Operation
var fin, und, pur sql.NullInt64
err := r.Scan(&o.ID, &o.PairID, &o.Action, &o.RelMaster, &o.RelSlave, &o.RelNew, &o.QuarantinePath, &o.QuarantineSide,
&o.TmpPath, &o.Bytes, &o.DryRun, &o.Trigger, &o.Status, &o.Error, &o.CreatedAt, &fin, &und, &pur)
&o.TmpPath, &o.Bytes, &o.DryRun, &o.Trigger, &o.Status, &o.Error, &o.CreatedAt, &fin, &und, &pur, &o.MatchKind, &o.MatchReason)
if fin.Valid {
o.FinishedAt = &fin.Int64
}
@@ -554,10 +557,10 @@ func scanOp(r scanner) (Operation, error) {
func (s *Store) CreateOp(ctx context.Context, o *Operation) error {
o.CreatedAt = now()
res, err := s.DB.ExecContext(ctx, `INSERT INTO operations (pair_id, action, rel_master, rel_slave, rel_new, quarantine_path,
quarantine_side, tmp_path, bytes, dry_run, trigger, status, error, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
quarantine_side, tmp_path, bytes, dry_run, trigger, status, error, created_at, match_kind, match_reason)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
o.PairID, o.Action, o.RelMaster, o.RelSlave, o.RelNew, o.QuarantinePath, o.QuarantineSide, o.TmpPath, o.Bytes,
o.DryRun, o.Trigger, o.Status, o.Error, o.CreatedAt)
o.DryRun, o.Trigger, o.Status, o.Error, o.CreatedAt, o.MatchKind, o.MatchReason)
if err != nil {
return err
}
+5
View File
@@ -109,6 +109,11 @@ var migrations = []string{
`ALTER TABLE pairs ADD COLUMN approx_matching INTEGER NOT NULL DEFAULT 0;
ALTER TABLE pairs ADD COLUMN approx_threshold INTEGER NOT NULL DEFAULT 85;
ALTER TABLE matches ADD COLUMN reason TEXT NOT NULL DEFAULT '';`,
// 4: remember how an operation's match was found, so undo can restore
// matches that are not at the same path (other format, approximate, ...).
`ALTER TABLE operations ADD COLUMN match_kind TEXT NOT NULL DEFAULT '';
ALTER TABLE operations ADD COLUMN match_reason TEXT NOT NULL DEFAULT '';`,
}
// Open opens (and migrates) the database in dataDir.