Compare commits
2 Commits
425d3a4ddf
...
c0cbc34911
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0cbc34911 | ||
|
|
dfc1c6d496 |
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -317,11 +317,11 @@ export async function resolveArchivedImageUrls<T>(value: T): Promise<T> {
|
||||
|
||||
// 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`));
|
||||
|
||||
@ -164,3 +164,16 @@ test("single-image route preserves authentication and returns a small CDN respon
|
||||
expect(response.status).toBe(200); expect(response.headers.get("Cache-Control")).toBe("no-store");
|
||||
expect((await response.json()).delivery).toBe("cdn"); expect(lookups).toBe(1);
|
||||
});
|
||||
test("batch downloads use the durable prepared PNG even when the source cache is cold", async () => {
|
||||
const original = `https://provider.example/${crypto.randomUUID()}.jpg`;
|
||||
const prepared = `https://cdn.example/${crypto.randomUUID()}.png`;
|
||||
const bytes = await sharp({ create: { width: 12, height: 20, channels: 4, background: "#abcdef80" } }).png().toBuffer();
|
||||
const fetched: string[] = [];
|
||||
const module = serverLoader({
|
||||
"@/lib/storage/image-archive-db": { findImageArchiveDownloadTask: async () => ({ targetUrl: original, metadata: { downloadAsset: { status: "ready", url: prepared, contentType: "image/png" } } }) },
|
||||
fetch: async (url: string) => { fetched.push(url); expect(url).toBe(prepared); return new Response(bytes, { headers: { "Content-Type": "image/png" } }); },
|
||||
}, { POSTGRES_URL: "mock-only" })("lib/canvas/photoshop-safe-download.ts");
|
||||
const result = await module.fetchCanvasDownloadAsset({ file: { url: original, name: "作品" }, index: 0, requestOrigin: "https://app.example", cookie: "", retryCount: 1 });
|
||||
expect(result.ok).toBe(true); expect(result.bytes.equals(bytes)).toBe(true);
|
||||
expect(result.filename).toBe("001-作品.png"); expect(fetched).toEqual([prepared]);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user