feat(library): store each album's MusicBrainz release-group id (M483 #5242)
release / govulncheck (push) Successful in 22s
release / web (push) Successful in 1m14s
release / go (push) Successful in 1m33s
release / integration (push) Successful in 4m36s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / android (push) Canceled after 5m24s
release / Build signed APK (releases and dev) (push) Canceled after 5m29s

albums.mbid is the release id (Picard's musicbrainz_albumid, one edition).
Lidarr names albums by release group, so re-acquisition and request
completion need that id too (#5241).

Migration 0070 adds albums.release_group_mbid (nullable, non-unique index:
several releases share a group). The scanner reads musicbrainz_releasegroupid
through extractReleaseGroupMBID, writes it on insert and heals it onto
existing rows when NULL. tagReadVersion goes to 3 so the next scan fills
it for the library already indexed, bound by tag reads (no ffprobe, no
decode).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 11:33:26 -04:00
co-authored by Claude Opus 5.5
parent bf6364b709
commit 5c19a916ba
13 changed files with 250 additions and 35 deletions
+77
View File
@@ -308,3 +308,80 @@ func TestScanner_AdoptsMovedFile_Integration(t *testing.T) {
t.Errorf("tracks = %d, want 8 — a rename must not add a row", total)
}
}
// TestScanner_ReleaseGroupMBID_Integration is #5241's scan half: a new album
// takes its release-group id from the tag, and an album indexed before this
// read existed gets it on the next scan, through the tagReadVersion bump,
// without its file changing.
func TestScanner_ReleaseGroupMBID_Integration(t *testing.T) {
if testing.Short() {
t.Skip("skipping scanner integration in -short mode")
}
dsn := os.Getenv("MINSTREL_TEST_DATABASE_URL")
if dsn == "" {
t.Skip("MINSTREL_TEST_DATABASE_URL not set")
}
ctx := context.Background()
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
if err := db.Migrate(dsn, logger); err != nil {
t.Fatalf("migrate: %v", err)
}
pool, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Fatalf("pool: %v", err)
}
t.Cleanup(pool.Close)
if _, err := pool.Exec(ctx, "TRUNCATE tracks, albums, artists RESTART IDENTITY CASCADE"); err != nil {
t.Fatalf("truncate: %v", err)
}
const (
groupA = "aaaaaaaa-1111-2222-3333-444444444444"
groupB = "bbbbbbbb-1111-2222-3333-444444444444"
)
root := t.TempDir()
writeTestMP3(t, filepath.Join(root, "rg/A/01.mp3"), map[string]string{
"TIT2": "One", "TPE1": "Artist RG", "TALB": "Album A",
"TXXX": "MusicBrainz Release Group Id\x00" + groupA,
})
pathB := filepath.Join(root, "rg/B/01.mp3")
writeTestMP3(t, pathB, map[string]string{
"TIT2": "Two", "TPE1": "Artist RG", "TALB": "Album B",
"TXXX": "MusicBrainz Release Group Id\x00" + groupB,
})
scanner := New(pool, logger, []string{root}, nil)
if _, err := scanner.Scan(ctx, nil); err != nil {
t.Fatalf("first scan: %v", err)
}
groupOf := func(title string) *string {
t.Helper()
var rg *string
if err := pool.QueryRow(ctx, "SELECT release_group_mbid FROM albums WHERE title = $1", title).Scan(&rg); err != nil {
t.Fatalf("album %q: %v", title, err)
}
return rg
}
if rg := groupOf("Album A"); rg == nil || *rg != groupA {
t.Fatalf("new album release group = %v, want %s", rg, groupA)
}
// Album B as a library indexed under tag-read version 2 left it: no
// release group, file untouched since.
if _, err := pool.Exec(ctx, "UPDATE albums SET release_group_mbid = NULL WHERE title = 'Album B'"); err != nil {
t.Fatal(err)
}
if _, err := pool.Exec(ctx, "UPDATE tracks SET tag_read_version = 2 WHERE file_path = $1", pathB); err != nil {
t.Fatal(err)
}
if _, err := scanner.Scan(ctx, nil); err != nil {
t.Fatalf("second scan: %v", err)
}
if rg := groupOf("Album B"); rg == nil || *rg != groupB {
t.Errorf("existing album release group after rescan = %v, want %s", rg, groupB)
}
if rg := groupOf("Album A"); rg == nil || *rg != groupA {
t.Errorf("album A release group changed to %v", rg)
}
}