feat(library): replace/upgrade an owned album on demand
Add Request.force (migration add_request_force): the pipeline's intake now skips the "already in library" dedupe for a forced job, so an owned album is re-downloaded. The import step still keeps the new copy only when it's higher quality, so a forced upgrade can never downgrade what's on disk. - Worker: _is_force_job → dedupe bypassed when Request.force. - POST /api/library/[id]/upgrade finds the matching MonitoredRelease and enqueues a force request (guards against stacking on an in-flight job). - Library route exposes monitoredReleaseId; the album modal shows a "Replace / upgrade" button when a release exists. Worker + web tests added (pipeline force re-acquire; upgrade route). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ type Album = {
|
||||
year: string | null;
|
||||
primaryType: string | null;
|
||||
artistMbid: string | null;
|
||||
monitoredReleaseId: string | null;
|
||||
trackNames: string[];
|
||||
};
|
||||
|
||||
@@ -115,6 +116,18 @@ export function LibraryClient() {
|
||||
refresh();
|
||||
}, []);
|
||||
|
||||
async function upgrade() {
|
||||
if (!open) return;
|
||||
const res = await fetch(`/api/library/${open.id}/upgrade`, { method: "POST" }).catch(() => null);
|
||||
if (res && (res.status === 202 || res.ok)) {
|
||||
const body = await res.json().catch(() => null);
|
||||
toast(body?.enqueued === false ? `Already searching for ${open.album}` : `Re-acquiring ${open.album} — keeps it only if better`);
|
||||
show(null);
|
||||
} else {
|
||||
toast((res && (await res.json().catch(() => null))?.error) || "Couldn't start the upgrade");
|
||||
}
|
||||
}
|
||||
|
||||
async function checkIntegrity() {
|
||||
setChecking(true);
|
||||
try {
|
||||
@@ -349,6 +362,11 @@ export function LibraryClient() {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{open.monitoredReleaseId ? (
|
||||
<button className="btn sm ghost" onClick={upgrade}>
|
||||
Replace / upgrade
|
||||
</button>
|
||||
) : null}
|
||||
<button className="btn sm ghost" onClick={startEdit}>
|
||||
Edit metadata
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user