Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e745acf88a |
@@ -775,6 +775,7 @@ func (yj *YellowJacketApp) startJanitor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
yj.janitor.Register(maintenance.ExpiredHTTPCacheJob(yj.database))
|
yj.janitor.Register(maintenance.ExpiredHTTPCacheJob(yj.database))
|
||||||
|
yj.janitor.Register(maintenance.StaleArtistMetadataJob(yj.database))
|
||||||
yj.janitor.Register(maintenance.OrphanedCoverFilesJob(
|
yj.janitor.Register(maintenance.OrphanedCoverFilesJob(
|
||||||
yj.database, coversDir, library.CoverArtFileSet,
|
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)
|
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
|
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