fix: a slot emptied of another song's file drops that file's recording MBID, so missing-pair folding cannot fold it into the copy kept (M498)
release / govulncheck (push) Successful in 24s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 5m1s
release / android (push) Successful in 5m2s
release / Build signed APK (releases and dev) (push) Successful in 5m11s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 24s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 5m1s
release / android (push) Successful in 5m2s
release / Build signed APK (releases and dev) (push) Successful in 5m11s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
On 2026-10-20 twelve of the 24 slots the resolver emptied were folded by MBID into the kept copy, so their albums were never wanted back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,21 @@ func (q *Queries) ClearStaleSuspectSourceTracks(ctx context.Context, pattern str
|
|||||||
return result.RowsAffected(), nil
|
return result.RowsAffected(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearTrackMbids = `-- name: ClearTrackMbids :exec
|
||||||
|
UPDATE tracks
|
||||||
|
SET mbid = NULL
|
||||||
|
WHERE id = ANY($1::uuid[])
|
||||||
|
`
|
||||||
|
|
||||||
|
// Forgets the recording MBID of rows whose file was another song's (M498's
|
||||||
|
// wrong-slot removal). The MBID came from that file, so it names the song kept
|
||||||
|
// elsewhere on the album, and missing-pair folding would fold the emptied slot
|
||||||
|
// into it: twelve slots went that way on 2026-10-20 and were never wanted back.
|
||||||
|
func (q *Queries) ClearTrackMbids(ctx context.Context, ids []pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, clearTrackMbids, ids)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const clearTracksMissing = `-- name: ClearTracksMissing :execrows
|
const clearTracksMissing = `-- name: ClearTracksMissing :execrows
|
||||||
UPDATE tracks
|
UPDATE tracks
|
||||||
SET missing_since = NULL
|
SET missing_since = NULL
|
||||||
|
|||||||
@@ -216,6 +216,15 @@ UPDATE tracks
|
|||||||
WHERE id = ANY(sqlc.arg(ids)::uuid[])
|
WHERE id = ANY(sqlc.arg(ids)::uuid[])
|
||||||
AND missing_since IS NULL;
|
AND missing_since IS NULL;
|
||||||
|
|
||||||
|
-- name: ClearTrackMbids :exec
|
||||||
|
-- Forgets the recording MBID of rows whose file was another song's (M498's
|
||||||
|
-- wrong-slot removal). The MBID came from that file, so it names the song kept
|
||||||
|
-- elsewhere on the album, and missing-pair folding would fold the emptied slot
|
||||||
|
-- into it: twelve slots went that way on 2026-10-20 and were never wanted back.
|
||||||
|
UPDATE tracks
|
||||||
|
SET mbid = NULL
|
||||||
|
WHERE id = ANY(sqlc.arg(ids)::uuid[]);
|
||||||
|
|
||||||
-- name: ClearTracksMissing :execrows
|
-- name: ClearTracksMissing :execrows
|
||||||
-- Clears the mark on rows whose file is back. Runs independently of the mtime
|
-- Clears the mark on rows whose file is back. Runs independently of the mtime
|
||||||
-- skip check, so a file that reappears unchanged is un-marked even though the
|
-- skip check, so a file that reappears unchanged is un-marked even though the
|
||||||
|
|||||||
@@ -381,10 +381,20 @@ func seedTrackList(t *testing.T, pool *pgxpool.Pool, album dbq.Album, songs map[
|
|||||||
func TestResolveDuplicates_RemovesACopyFromAnotherSongsSlot_Integration(t *testing.T) {
|
func TestResolveDuplicates_RemovesACopyFromAnotherSongsSlot_Integration(t *testing.T) {
|
||||||
f := newOneAlbumFixture(t, "acoustic", "Void", "Reach")
|
f := newOneAlbumFixture(t, "acoustic", "Void", "Reach")
|
||||||
seedTrackList(t, f.pool, f.album, map[string]int{"Void": 220500, "Reach": 281000})
|
seedTrackList(t, f.pool, f.album, map[string]int{"Void": 220500, "Reach": 281000})
|
||||||
|
// The wrong file is Void's recording, so it carries Void's MBID.
|
||||||
|
if _, err := f.pool.Exec(context.Background(),
|
||||||
|
`UPDATE tracks SET mbid = 'rec-void' WHERE id = ANY($1)`, []pgtype.UUID{f.a.ID, f.b.ID}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
res, err := ResolveDuplicates(context.Background(), f.pool, nil, "", true)
|
res, err := ResolveDuplicates(context.Background(), f.pool, nil, "", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// Twelve slots were lost this way on 2026-10-20: folded by that MBID into
|
||||||
|
// the copy kept, and so never wanted back.
|
||||||
|
if pairs, err := PairMissingTracks(context.Background(), f.pool, nil); err != nil || pairs.Folded() != 0 {
|
||||||
|
t.Fatalf("missing-pair pass = %+v, %v; want the emptied slot left alone", pairs, err)
|
||||||
|
}
|
||||||
if res.WrongFiles != 1 || !exists(f.aPath) || exists(f.bPath) {
|
if res.WrongFiles != 1 || !exists(f.aPath) || exists(f.bPath) {
|
||||||
t.Fatalf("result %+v, void exists %v, reach exists %v; want only Reach's copy removed",
|
t.Fatalf("result %+v, void exists %v, reach exists %v; want only Reach's copy removed",
|
||||||
res, exists(f.aPath), exists(f.bPath))
|
res, exists(f.aPath), exists(f.bPath))
|
||||||
|
|||||||
@@ -225,7 +225,9 @@ func neverRead(ctx context.Context, q *dbq.Queries, rg string) bool {
|
|||||||
|
|
||||||
// removeWrongSlots deletes each loser's file, then marks its track missing in
|
// removeWrongSlots deletes each loser's file, then marks its track missing in
|
||||||
// the same transaction that closes the group. The row is kept, with its likes
|
// the same transaction that closes the group. The row is kept, with its likes
|
||||||
// and plays: it is the slot, and a good copy fetched again takes it over. A
|
// and plays: it is the slot, and a good copy fetched again takes it over. Its
|
||||||
|
// recording MBID is dropped, being the wrong file's, so missing-pair folding
|
||||||
|
// does not fold the slot into the copy that was kept. A
|
||||||
// file that cannot be removed stops the group; the ones already gone are still
|
// file that cannot be removed stops the group; the ones already gone are still
|
||||||
// marked, and the group waits for the next pass.
|
// marked, and the group waits for the next pass.
|
||||||
func removeWrongSlots(
|
func removeWrongSlots(
|
||||||
@@ -253,6 +255,9 @@ func removeWrongSlots(
|
|||||||
if _, err := tq.MarkTracksMissing(ctx, gone); err != nil {
|
if _, err := tq.MarkTracksMissing(ctx, gone); err != nil {
|
||||||
return fmt.Errorf("mark missing: %w", err)
|
return fmt.Errorf("mark missing: %w", err)
|
||||||
}
|
}
|
||||||
|
if err := tq.ClearTrackMbids(ctx, gone); err != nil {
|
||||||
|
return fmt.Errorf("clear mbids: %w", err)
|
||||||
|
}
|
||||||
if err := logTrackChanges(ctx, tq, gone); err != nil {
|
if err := logTrackChanges(ctx, tq, gone); err != nil {
|
||||||
return fmt.Errorf("log changes: %w", err)
|
return fmt.Errorf("log changes: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user