diff --git a/lib/canvas/photoshop-safe-download.ts b/lib/canvas/photoshop-safe-download.ts index 9bcebfbeb..f7871b05f 100644 --- a/lib/canvas/photoshop-safe-download.ts +++ b/lib/canvas/photoshop-safe-download.ts @@ -303,6 +303,20 @@ export async function fetchCanvasDownloadAsset(params: { params.requestOrigin ); + if (process.env.POSTGRES_URL) { + // ZIPs and binary fallback also reuse the durable prepared object after the + // local byte cache expires, instead of converting the original JPEG again. + const { findImageArchiveDownloadTask } = await import("@/lib/storage/image-archive-db"); + const task = await findImageArchiveDownloadTask(candidateUrls).catch(() => null); + const asset = (task?.metadata as { downloadAsset?: { status?: string; url?: string; contentType?: string } } | null)?.downloadAsset; + if (asset?.status === "ready" && asset.url && asset.contentType === "image/png") { + const readyUrl = resolveHttpUrl(asset.url, params.requestOrigin); + if (readyUrl && !candidateUrls.includes(readyUrl)) candidateUrls.unshift(readyUrl); + } else if (task?.targetUrl && !candidateUrls.includes(task.targetUrl)) { + candidateUrls.unshift(task.targetUrl); + } + } + for (const candidateUrl of candidateUrls) { for (let attempt = 1; attempt <= retryCount; attempt += 1) { try { diff --git a/lib/storage/canvas-cdn-download.ts b/lib/storage/canvas-cdn-download.ts index 54ff7f2f4..f2f744f1d 100644 --- a/lib/storage/canvas-cdn-download.ts +++ b/lib/storage/canvas-cdn-download.ts @@ -8,7 +8,7 @@ export async function resolveCanvasCdnDownload(urls: string[], filename: string) const source = extractImageProxySourceUrl(url) || url; return [source, inferOriginalImageUrl(source) || source]; }))); - const task = await findImageArchiveDownloadTask(sources); + const task = await findImageArchiveDownloadTask(sources, true); const asset = (task?.metadata as { downloadAsset?: { status?: string; url?: string; contentType?: string; bytes?: number } } | null)?.downloadAsset; if (asset?.status !== "ready" || !asset.url || asset.contentType !== "image/png") return { task, direct: null }; const config = await getDefaultStorageConfig(); diff --git a/lib/storage/image-archive-db.ts b/lib/storage/image-archive-db.ts index f86231133..6c34a9c30 100644 --- a/lib/storage/image-archive-db.ts +++ b/lib/storage/image-archive-db.ts @@ -317,11 +317,11 @@ export async function resolveArchivedImageUrls(value: T): Promise { // Download assets use an independent JSONB lease; original archiving/replacement // can finish even if PNG preparation or thumbnail storage is temporarily unavailable. -export async function findImageArchiveDownloadTask(urls: string[]) { +export async function findImageArchiveDownloadTask(urls: string[], prepareOnDemand = false) { const [task] = await db.select().from(imageArchiveTask).where(or( inArray(imageArchiveTask.sourceUrl, urls), inArray(imageArchiveTask.targetUrl, urls), )).limit(1); - if (task?.targetUrl && !(task.metadata as { downloadAsset?: unknown } | null)?.downloadAsset) { + if (prepareOnDemand && task?.targetUrl && !(task.metadata as { downloadAsset?: unknown } | null)?.downloadAsset) { await db.update(imageArchiveTask).set({ metadata: sql`coalesce(${imageArchiveTask.metadata}, '{}'::jsonb) || jsonb_build_object('downloadAsset', jsonb_build_object('status', 'pending'))`, }).where(and(eq(imageArchiveTask.id, task.id), sql`${imageArchiveTask.metadata}->'downloadAsset' is null`));