M464 loudness leveling (steps 4–8), APK distribution and security baseline adoptions #136

Merged
bvandeusen merged 12 commits from dev into main 2026-10-06 23:11:36 -04:00
5 changed files with 85 additions and 2 deletions
Showing only changes of commit 13a7a3629a - Show all commits
+4 -1
View File
@@ -552,6 +552,7 @@ SELECT a.id AS album_id,
SELECT file_path SELECT file_path
FROM tracks FROM tracks
WHERE album_id = a.id WHERE album_id = a.id
AND missing_since IS NULL
ORDER BY disc_number NULLS LAST, track_number NULLS LAST, id ORDER BY disc_number NULLS LAST, track_number NULLS LAST, id
LIMIT 1 LIMIT 1
) t ON true ) t ON true
@@ -569,7 +570,9 @@ type ListAlbumsMissingMbidWithTrackRow struct {
// One-shot MBID backfill: returns each album where mbid IS NULL alongside // One-shot MBID backfill: returns each album where mbid IS NULL alongside
// one of its tracks' file_path so the worker can re-read tags. LIMIT // one of its tracks' file_path so the worker can re-read tags. LIMIT
// supplied by caller for batching/progress purposes. // supplied by caller for batching/progress purposes. The track read is
// one still on disk; an album with none left is skipped until a scan
// finds its files again (#5139).
func (q *Queries) ListAlbumsMissingMbidWithTrack(ctx context.Context, limit int32) ([]ListAlbumsMissingMbidWithTrackRow, error) { func (q *Queries) ListAlbumsMissingMbidWithTrack(ctx context.Context, limit int32) ([]ListAlbumsMissingMbidWithTrackRow, error) {
rows, err := q.db.Query(ctx, listAlbumsMissingMbidWithTrack, limit) rows, err := q.db.Query(ctx, listAlbumsMissingMbidWithTrack, limit)
if err != nil { if err != nil {
+3
View File
@@ -668,6 +668,7 @@ const listTracksMissingMbidWithPath = `-- name: ListTracksMissingMbidWithPath :m
SELECT id, file_path SELECT id, file_path
FROM tracks FROM tracks
WHERE mbid IS NULL WHERE mbid IS NULL
AND missing_since IS NULL
ORDER BY id ORDER BY id
LIMIT $1 LIMIT $1
` `
@@ -679,6 +680,8 @@ type ListTracksMissingMbidWithPathRow struct {
// Track recording-MBID backfill: tracks with NULL mbid that still have // Track recording-MBID backfill: tracks with NULL mbid that still have
// a file to re-read. $1 caps the batch (mirrors the album backfill). // a file to re-read. $1 caps the batch (mirrors the album backfill).
// Missing tracks are skipped: there is no file, and opening it anyway
// logged a warning per track on every scan (#5139).
func (q *Queries) ListTracksMissingMbidWithPath(ctx context.Context, limit int32) ([]ListTracksMissingMbidWithPathRow, error) { func (q *Queries) ListTracksMissingMbidWithPath(ctx context.Context, limit int32) ([]ListTracksMissingMbidWithPathRow, error) {
rows, err := q.db.Query(ctx, listTracksMissingMbidWithPath, limit) rows, err := q.db.Query(ctx, listTracksMissingMbidWithPath, limit)
if err != nil { if err != nil {
+4 -1
View File
@@ -145,7 +145,9 @@ UPDATE albums
-- name: ListAlbumsMissingMbidWithTrack :many -- name: ListAlbumsMissingMbidWithTrack :many
-- One-shot MBID backfill: returns each album where mbid IS NULL alongside -- One-shot MBID backfill: returns each album where mbid IS NULL alongside
-- one of its tracks' file_path so the worker can re-read tags. LIMIT -- one of its tracks' file_path so the worker can re-read tags. LIMIT
-- supplied by caller for batching/progress purposes. -- supplied by caller for batching/progress purposes. The track read is
-- one still on disk; an album with none left is skipped until a scan
-- finds its files again (#5139).
SELECT a.id AS album_id, SELECT a.id AS album_id,
a.artist_id AS artist_id, a.artist_id AS artist_id,
a.title AS title, a.title AS title,
@@ -155,6 +157,7 @@ SELECT a.id AS album_id,
SELECT file_path SELECT file_path
FROM tracks FROM tracks
WHERE album_id = a.id WHERE album_id = a.id
AND missing_since IS NULL
ORDER BY disc_number NULLS LAST, track_number NULLS LAST, id ORDER BY disc_number NULLS LAST, track_number NULLS LAST, id
LIMIT 1 LIMIT 1
) t ON true ) t ON true
+3
View File
@@ -26,9 +26,12 @@ RETURNING *;
-- name: ListTracksMissingMbidWithPath :many -- name: ListTracksMissingMbidWithPath :many
-- Track recording-MBID backfill: tracks with NULL mbid that still have -- Track recording-MBID backfill: tracks with NULL mbid that still have
-- a file to re-read. $1 caps the batch (mirrors the album backfill). -- a file to re-read. $1 caps the batch (mirrors the album backfill).
-- Missing tracks are skipped: there is no file, and opening it anyway
-- logged a warning per track on every scan (#5139).
SELECT id, file_path SELECT id, file_path
FROM tracks FROM tracks
WHERE mbid IS NULL WHERE mbid IS NULL
AND missing_since IS NULL
ORDER BY id ORDER BY id
LIMIT $1; LIMIT $1;
+71
View File
@@ -0,0 +1,71 @@
package library
import (
"bytes"
"context"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
// The MBID backfills read tags from files, so a track whose file is gone has
// nothing to give them. Before #5139 they opened it anyway, and every scan
// logged an "open failed" warning per missing track.
func TestMBIDBackfill_SkipsMissingTracks_Integration(t *testing.T) {
pool := newPool(t)
ctx := context.Background()
q := dbq.New(pool)
dir := t.TempDir()
// The album's first track is missing; its second is on disk, untagged.
gone, album, artist := seedTrack(t, pool, filepath.Join(dir, "gone.mp3"))
present := filepath.Join(dir, "present.mp3")
if err := os.WriteFile(present, []byte("not really audio"), 0o600); err != nil {
t.Fatal(err)
}
two := int32(2)
if _, err := q.UpsertTrack(ctx, dbq.UpsertTrackParams{
Title: "Present", AlbumID: album.ID, ArtistID: artist.ID, TrackNumber: &two,
DurationMs: 1000, FilePath: present, FileSize: 100, FileFormat: "mp3",
}); err != nil {
t.Fatalf("track: %v", err)
}
if _, err := pool.Exec(ctx, "UPDATE tracks SET missing_since = now() WHERE id = $1", gone.ID); err != nil {
t.Fatalf("mark missing: %v", err)
}
tracks, err := q.ListTracksMissingMbidWithPath(ctx, 100)
if err != nil {
t.Fatal(err)
}
if len(tracks) != 1 || tracks[0].FilePath != present {
t.Errorf("track backfill lists %+v, want only %s", tracks, present)
}
albums, err := q.ListAlbumsMissingMbidWithTrack(ctx, 100)
if err != nil {
t.Fatal(err)
}
if len(albums) != 1 || albums[0].TrackFilePath != present {
t.Errorf("album backfill reads %+v, want the album via %s", albums, present)
}
// With every track gone, the album has no file to read and is skipped.
if _, err := pool.Exec(ctx, "UPDATE tracks SET missing_since = now() WHERE album_id = $1", album.ID); err != nil {
t.Fatalf("mark missing: %v", err)
}
var logs bytes.Buffer
logger := slog.New(slog.NewTextHandler(&logs, nil))
if res, err := BackfillTrackMBIDs(ctx, pool, logger, -1, nil); err != nil || res.Processed != 0 {
t.Errorf("track backfill processed %d (err %v), want 0", res.Processed, err)
}
if res, err := BackfillMBIDs(ctx, pool, logger, -1, nil); err != nil || res.Processed != 0 {
t.Errorf("album backfill processed %d (err %v), want 0", res.Processed, err)
}
if strings.Contains(logs.String(), "open failed") {
t.Errorf("a backfill opened a missing file:\n%s", logs.String())
}
}