diff --git a/components/admin/structured-dashboard.tsx b/components/admin/structured-dashboard.tsx index b9bb63f..9380234 100644 --- a/components/admin/structured-dashboard.tsx +++ b/components/admin/structured-dashboard.tsx @@ -175,7 +175,7 @@ export function StructuredDashboard({
- {syncJob.message} + {syncJob.message} {syncJob.total ? `${syncJob.completed}/${syncJob.total}` : "กำลังเริ่ม…"}
diff --git a/lib/catalog/sync.ts b/lib/catalog/sync.ts index de4cfc7..61fd784 100644 --- a/lib/catalog/sync.ts +++ b/lib/catalog/sync.ts @@ -115,7 +115,7 @@ interface SyncedCharacter { type SyncControl = { signal?: AbortSignal; checkCancelled: () => Promise; report: (phase: string, completed: number, total: number, message: string) => Promise }; -async function retryTimeout(label: string, task: () => Promise, signal?: AbortSignal): Promise { +async function retryTransient(label: string, task: () => Promise, signal?: AbortSignal): Promise { for (let attempt = 1; ; attempt += 1) { signal?.throwIfAborted(); try { @@ -123,7 +123,7 @@ async function retryTimeout(label: string, task: () => Promise, signal?: A } catch (cause) { signal?.throwIfAborted(); const message = cause instanceof Error ? cause.message : String(cause); - if (attempt >= 3 || !/timed out|timeout/i.test(message)) { + if (attempt >= 3 || !/timed out|timeout|unexpected error has occurred/i.test(message)) { throw new Error(`${label}: ${message}`, { cause }); } await new Promise((resolve) => setTimeout(resolve, attempt * 1_000)); @@ -132,7 +132,7 @@ async function retryTimeout(label: string, task: () => Promise, signal?: A } async function getJson(url: string, signal?: AbortSignal): Promise { - return retryTimeout(`Lunaris request ${url}`, async () => { + return retryTransient(`Lunaris request ${url}`, async () => { const response = await fetch(url, { signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000), cache: "no-store", @@ -144,7 +144,7 @@ async function getJson(url: string, signal?: AbortSignal): Promise { } async function getOptionalJson(url: string, signal?: AbortSignal): Promise { - return retryTimeout(`Lunaris request ${url}`, async () => { + return retryTransient(`Lunaris request ${url}`, async () => { const response = await fetch(url, { signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000), cache: "no-store", @@ -229,12 +229,15 @@ async function uploadAssets( let completed = 0; let lastProgressAt = 0; const missing = new Set(); - async function reportProgress() { + async function reportProgress(currentAsset?: string) { if (!control) return; const now = Date.now(); if (completed < assets.length && now - lastProgressAt < PROGRESS_INTERVAL_MS) return; lastProgressAt = now; - await control.report("assets", completed, assets.length, `ดาวน์โหลดรูปภาพ ${completed}/${assets.length}`); + const message = currentAsset + ? `กำลังดาวน์โหลดรูปภาพ ${Math.min(completed + 1, assets.length)}/${assets.length}\n${currentAsset.split("/").at(-1)}` + : `ดาวน์โหลดรูปภาพ ${completed}/${assets.length}`; + await control.report("assets", completed, assets.length, message); } async function worker() { while (true) { @@ -242,7 +245,8 @@ async function uploadAssets( if (index >= assets.length) return; await control?.checkCancelled(); const asset = assets[index]; - await retryTimeout(`Asset ${asset.target}`, async () => { + await reportProgress(asset.target); + await retryTransient(`Asset ${asset.target}`, async () => { if (!(await storage.file(asset.target).exists())) { if (asset.body) { await storage.write(asset.target, asset.body, {