fix(catalog) : show asset progress and retry transient failures
This commit is contained in:
@@ -175,7 +175,7 @@ export function StructuredDashboard({
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent className="space-y-2 pt-6">
|
<CardContent className="space-y-2 pt-6">
|
||||||
<div className="flex items-center justify-between gap-3 text-sm">
|
<div className="flex items-center justify-between gap-3 text-sm">
|
||||||
<span>{syncJob.message}</span>
|
<span className="whitespace-pre-line">{syncJob.message}</span>
|
||||||
<span className="text-muted-foreground">{syncJob.total ? `${syncJob.completed}/${syncJob.total}` : "กำลังเริ่ม…"}</span>
|
<span className="text-muted-foreground">{syncJob.total ? `${syncJob.completed}/${syncJob.total}` : "กำลังเริ่ม…"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2 overflow-hidden rounded-full bg-muted">
|
<div className="h-2 overflow-hidden rounded-full bg-muted">
|
||||||
|
|||||||
+11
-7
@@ -115,7 +115,7 @@ interface SyncedCharacter {
|
|||||||
|
|
||||||
type SyncControl = { signal?: AbortSignal; checkCancelled: () => Promise<void>; report: (phase: string, completed: number, total: number, message: string) => Promise<void> };
|
type SyncControl = { signal?: AbortSignal; checkCancelled: () => Promise<void>; report: (phase: string, completed: number, total: number, message: string) => Promise<void> };
|
||||||
|
|
||||||
async function retryTimeout<T>(label: string, task: () => Promise<T>, signal?: AbortSignal): Promise<T> {
|
async function retryTransient<T>(label: string, task: () => Promise<T>, signal?: AbortSignal): Promise<T> {
|
||||||
for (let attempt = 1; ; attempt += 1) {
|
for (let attempt = 1; ; attempt += 1) {
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
try {
|
try {
|
||||||
@@ -123,7 +123,7 @@ async function retryTimeout<T>(label: string, task: () => Promise<T>, signal?: A
|
|||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
const message = cause instanceof Error ? cause.message : String(cause);
|
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 });
|
throw new Error(`${label}: ${message}`, { cause });
|
||||||
}
|
}
|
||||||
await new Promise((resolve) => setTimeout(resolve, attempt * 1_000));
|
await new Promise((resolve) => setTimeout(resolve, attempt * 1_000));
|
||||||
@@ -132,7 +132,7 @@ async function retryTimeout<T>(label: string, task: () => Promise<T>, signal?: A
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getJson(url: string, signal?: AbortSignal): Promise<unknown> {
|
async function getJson(url: string, signal?: AbortSignal): Promise<unknown> {
|
||||||
return retryTimeout(`Lunaris request ${url}`, async () => {
|
return retryTransient(`Lunaris request ${url}`, async () => {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000),
|
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000),
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
@@ -144,7 +144,7 @@ async function getJson(url: string, signal?: AbortSignal): Promise<unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getOptionalJson(url: string, signal?: AbortSignal): Promise<unknown | null> {
|
async function getOptionalJson(url: string, signal?: AbortSignal): Promise<unknown | null> {
|
||||||
return retryTimeout(`Lunaris request ${url}`, async () => {
|
return retryTransient(`Lunaris request ${url}`, async () => {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000),
|
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000),
|
||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
@@ -229,12 +229,15 @@ async function uploadAssets(
|
|||||||
let completed = 0;
|
let completed = 0;
|
||||||
let lastProgressAt = 0;
|
let lastProgressAt = 0;
|
||||||
const missing = new Set<string>();
|
const missing = new Set<string>();
|
||||||
async function reportProgress() {
|
async function reportProgress(currentAsset?: string) {
|
||||||
if (!control) return;
|
if (!control) return;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (completed < assets.length && now - lastProgressAt < PROGRESS_INTERVAL_MS) return;
|
if (completed < assets.length && now - lastProgressAt < PROGRESS_INTERVAL_MS) return;
|
||||||
lastProgressAt = now;
|
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() {
|
async function worker() {
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -242,7 +245,8 @@ async function uploadAssets(
|
|||||||
if (index >= assets.length) return;
|
if (index >= assets.length) return;
|
||||||
await control?.checkCancelled();
|
await control?.checkCancelled();
|
||||||
const asset = assets[index];
|
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 (!(await storage.file(asset.target).exists())) {
|
||||||
if (asset.body) {
|
if (asset.body) {
|
||||||
await storage.write(asset.target, asset.body, {
|
await storage.write(asset.target, asset.body, {
|
||||||
|
|||||||
Reference in New Issue
Block a user