fix(lidarr): complete a request only when the album actually came back (#5263)
release / govulncheck (push) Failing after 33s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m44s
release / integration (push) Successful in 4m37s
release / android (push) Successful in 5m29s
release / Build signed APK (releases and dev) (push) Successful in 5m34s
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

Re-acquisition targets albums with ANY track missing, and completion only
asked for a track on disk, so the tracks that never left completed every
re-acquisition request the moment Lidarr accepted the add (52 on the deploy,
each ~150ms after its add).

An album or track request now completes when an album named by its release
or group has a track on disk AND either a track arrived after the request
(a new album, or Lidarr fetching another release into its own row) or no
track that was missing at the request is still missing. added_at is the
arrival clock; updated_at moves on every tag re-read.

Migration 0071 reopens completed album/track requests whose matched album
fails that test, as approved with the match cleared; the Lidarr add stays
confirmed, so nothing is re-sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 14:55:32 -04:00
co-authored by Claude Opus 5.5
parent e509d7d5a9
commit 4e3ce4065c
4 changed files with 227 additions and 8 deletions
+24 -8
View File
@@ -258,16 +258,32 @@ func (r *Reconciler) repointAtReleaseGroup(ctx context.Context, q *dbq.Queries,
return row, true
}
// albumForRequest finds the library album a request's album id names, by
// release id or release group, and only once it has a track back on disk. A
// re-acquisition request names an album whose row never went away; matching
// the row alone would complete the request before Lidarr delivered anything.
// albumForRequest finds the library album a request's album id ($1) names, by
// release id or release group, once the request ($2 = requested_at) has been
// answered: the album has a track on disk AND either
//
// - a track arrived after the request (a new album, or Lidarr fetching
// another release of the group into its own row), or
// - no track that was already missing when the request was made is still
// missing (the lost files came back in place or were adopted at a new path).
//
// "Has a track on disk" alone is not enough (#5263): re-acquisition targets
// albums with ANY track missing, so the tracks that never left satisfied it
// the moment Lidarr accepted the add. added_at is the arrival clock because
// nothing rewrites it; updated_at moves on every tag re-read.
// An exact release match is preferred over another release of the group.
const albumForRequest = `
SELECT a.id
FROM albums a
WHERE (a.mbid = $1 OR a.release_group_mbid = $1)
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL)
AND (
EXISTS (SELECT 1 FROM tracks t
WHERE t.album_id = a.id AND t.missing_since IS NULL AND t.added_at > $2)
OR NOT EXISTS (SELECT 1 FROM tracks t
WHERE t.album_id = a.id AND t.missing_since IS NOT NULL
AND t.missing_since < $2)
)
ORDER BY (a.mbid = $1) DESC, a.id
LIMIT 1`
@@ -301,7 +317,7 @@ func (r *Reconciler) reconcileAlbum(ctx context.Context, q *dbq.Queries, row dbq
return nil
}
var albumID pgtype.UUID
err := r.pool.QueryRow(ctx, albumForRequest, *row.LidarrAlbumMbid).Scan(&albumID)
err := r.pool.QueryRow(ctx, albumForRequest, *row.LidarrAlbumMbid, row.RequestedAt).Scan(&albumID)
if err != nil {
if isNoRows(err) {
return nil
@@ -327,7 +343,7 @@ func (r *Reconciler) reconcileTrack(ctx context.Context, q *dbq.Queries, row dbq
}
// Track-kind requests match via their parent album's MBID, not track.mbid.
var albumID pgtype.UUID
err := r.pool.QueryRow(ctx, albumForRequest, *row.LidarrAlbumMbid).Scan(&albumID)
err := r.pool.QueryRow(ctx, albumForRequest, *row.LidarrAlbumMbid, row.RequestedAt).Scan(&albumID)
if err != nil {
if isNoRows(err) {
return nil
@@ -335,10 +351,10 @@ func (r *Reconciler) reconcileTrack(ctx context.Context, q *dbq.Queries, row dbq
return err
}
// Load any track from that album to set matched_track_id.
// Load a track from that album to set matched_track_id, newest arrival first.
var trackID pgtype.UUID
err = r.pool.QueryRow(ctx,
"SELECT id FROM tracks WHERE album_id = $1 AND missing_since IS NULL ORDER BY id LIMIT 1",
"SELECT id FROM tracks WHERE album_id = $1 AND missing_since IS NULL ORDER BY added_at DESC, id LIMIT 1",
albumID,
).Scan(&trackID)
if err != nil {