fix(maintenance): sweep artist_metadata rows nothing references #252
@@ -775,6 +775,7 @@ func (yj *YellowJacketApp) startJanitor() {
|
||||
}
|
||||
|
||||
yj.janitor.Register(maintenance.ExpiredHTTPCacheJob(yj.database))
|
||||
yj.janitor.Register(maintenance.StaleArtistMetadataJob(yj.database))
|
||||
yj.janitor.Register(maintenance.OrphanedCoverFilesJob(
|
||||
yj.database, coversDir, library.CoverArtFileSet,
|
||||
))
|
||||
|
||||
@@ -666,3 +666,71 @@ func TestExpiredHTTPCacheJob_TrimsToBudget(t *testing.T) {
|
||||
t.Errorf("kept %q, want the longest-lived row", kept)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStaleArtistMetadataJob pins the sweep's two keep rules: an owned
|
||||
// artist's metadata survives, a browsed artist's survives while it still
|
||||
// holds cached artwork, and everything else goes (#248).
|
||||
func TestStaleArtistMetadataJob(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := database.NewTestDB(t)
|
||||
|
||||
const (
|
||||
ownedMBID = "11111111-1111-1111-1111-111111111111"
|
||||
browsedMBID = "22222222-2222-2222-2222-222222222222"
|
||||
staleMBID = "33333333-3333-3333-3333-333333333333"
|
||||
)
|
||||
|
||||
// The owned artist is in the library - which means a *file* says
|
||||
// so. An artists row on its own is not ownership.
|
||||
database.InsertTestTrack(t, db, database.TestTrack{
|
||||
FilePath: "/music/owned.mp3",
|
||||
Artist: "Owned",
|
||||
ArtistMBID: ownedMBID,
|
||||
})
|
||||
|
||||
for _, mbid := range []string{ownedMBID, browsedMBID, staleMBID} {
|
||||
if _, err := db.ExecContext(
|
||||
`INSERT INTO artist_metadata (mbid, source, data, fetched_at)
|
||||
VALUES (?, 'wikidata-p18', x'00', CURRENT_TIMESTAMP)`,
|
||||
mbid,
|
||||
); err != nil {
|
||||
t.Fatalf("seed artist_metadata for %s: %v", mbid, err)
|
||||
}
|
||||
}
|
||||
|
||||
// The browsed artist holds cached artwork, so its metadata is still
|
||||
// referenced and must survive.
|
||||
if _, err := db.ExecContext(
|
||||
`INSERT INTO artist_images
|
||||
(artist_mbid, source, source_url, file_path)
|
||||
VALUES (?, 'test', 'http://x', '/art/primary.jpg')`,
|
||||
browsedMBID,
|
||||
); err != nil {
|
||||
t.Fatalf("seed artist_images: %v", err)
|
||||
}
|
||||
|
||||
if _, err := StaleArtistMetadataJob(db).Run(context.Background()); err != nil {
|
||||
t.Fatalf("run job: %v", err)
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
mbid string
|
||||
want int
|
||||
}{
|
||||
{ownedMBID, 1},
|
||||
{browsedMBID, 1},
|
||||
{staleMBID, 0},
|
||||
} {
|
||||
var n int
|
||||
if err := db.QueryRowWriter(
|
||||
"SELECT COUNT(*) FROM artist_metadata WHERE mbid = ?", tc.mbid,
|
||||
).Scan(&n); err != nil {
|
||||
t.Fatalf("count %s: %v", tc.mbid, err)
|
||||
}
|
||||
|
||||
if n != tc.want {
|
||||
t.Errorf("artist_metadata rows for %s = %d, want %d", tc.mbid, n, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,3 +628,37 @@ func dirSize(dir string) (bytes, files int64) {
|
||||
|
||||
return bytes, files
|
||||
}
|
||||
|
||||
// StaleArtistMetadataJob evicts long-lived artist metadata (bios, wiki
|
||||
// leads, relationships) for artists the user no longer has any reason
|
||||
// to keep around: not owned and holding no cached artwork.
|
||||
//
|
||||
// artist_metadata has no TTL by design — entity data changes rarely and
|
||||
// re-fetching spends someone else's rate limit — so without a sweep it
|
||||
// grows for the life of the install. This is the "swept when the
|
||||
// artist is no longer referenced" contract the datamap always declared
|
||||
// for it and nothing ever performed (#248).
|
||||
func StaleArtistMetadataJob(db *database.DB) Job {
|
||||
return Job{
|
||||
Name: "artist-metadata-sweep",
|
||||
MinInterval: dailyInterval,
|
||||
Run: func(_ context.Context) (Result, error) {
|
||||
res, err := db.ExecContext(
|
||||
`DELETE FROM artist_metadata
|
||||
WHERE mbid NOT IN (` + ownedArtistMBIDs + `)
|
||||
AND mbid NOT IN (
|
||||
SELECT artist_mbid FROM artist_images
|
||||
)`,
|
||||
)
|
||||
if err != nil {
|
||||
return Result{}, fmt.Errorf(
|
||||
"delete stale artist_metadata rows: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
rows, _ := res.RowsAffected()
|
||||
|
||||
return Result{RowsDeleted: rows}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user