ai-chatbot/lib/storage/image-download-preparation.ts

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;
}