39 lines
2.1 KiB
TypeScript
39 lines
2.1 KiB
TypeScript
import "server-only";
|
|
import { randomUUID } from "node:crypto";
|
|
import { fetchImageBytes, cacheImageBytes } from "./image-byte-cache";
|
|
import { normalizeCanvasDownloadAsset } from "@/lib/canvas/photoshop-safe-download";
|
|
import { claimImageDownloadTask, saveImageDownloadAsset, getImageDownloadTasks } from "./image-archive-db";
|
|
import { uploadFile } from "./service";
|
|
|
|
export async function prepareImageArchiveDownload(id: string) {
|
|
const task = await claimImageDownloadTask(id);
|
|
if (!task?.targetUrl) return;
|
|
try {
|
|
const source = await fetchImageBytes(task.targetUrl);
|
|
const asset = await normalizeCanvasDownloadAsset({
|
|
item: { url: task.targetUrl }, index: 0, bytes: source.buffer,
|
|
contentType: source.contentType, includeSequencePrefix: false,
|
|
});
|
|
if (!asset.ok) throw new Error(asset.error);
|
|
// An already compatible PNG shares the original object; other formats get
|
|
// one prepared PNG, without changing the original dimensions or transparency.
|
|
const url = source.buffer.equals(asset.bytes) && asset.contentType === "image/png"
|
|
? task.targetUrl
|
|
: (await uploadFile(`canvas-download-${randomUUID()}.${asset.filename.split(".").pop() || "png"}`, Uint8Array.from(asset.bytes).buffer)).url;
|
|
await saveImageDownloadAsset(id, { status: "ready", url, bytes: asset.bytes.length,
|
|
contentType: asset.contentType, preparedAt: new Date().toISOString() });
|
|
await cacheImageBytes(`source::${url}`, { buffer: asset.bytes, contentType: asset.contentType });
|
|
} catch {
|
|
const attempts = Number((task.metadata as { downloadAsset?: { attempts?: number } } | null)?.downloadAsset?.attempts || 1);
|
|
await saveImageDownloadAsset(id, { status: attempts >= 12 ? "abandoned" : "failed", attempts, retryAt: new Date(Date.now() + 10 * 60_000).toISOString() });
|
|
}
|
|
}
|
|
export async function preparePendingImageDownloads() {
|
|
const tasks = await getImageDownloadTasks();
|
|
let cursor = 0;
|
|
await Promise.all(Array.from({ length: Math.min(2, tasks.length) }, async () => {
|
|
while (cursor < tasks.length) await prepareImageArchiveDownload(tasks[cursor++].id);
|
|
}));
|
|
return tasks.length;
|
|
}
|