このコミットが含まれているのは:
2026-10-31 04:08:05 +09:00
コミット 6a0f1ec819
23個のファイルの変更、188行の追加、57行の削除
+20 -8
ファイルの表示
@@ -32,8 +32,10 @@ const CountsExportProgress = (
const job = useRef<string | null> (null)
const creation = useRef<Promise<{ job: string }> | null> (null)
const downloaded = useRef (false)
const registration = useRef (false)
const attempt = useRef<string | null> (null)
const downloadDeadline = useRef (0)
const acknowledgementTimer = useRef<ReturnType<typeof setTimeout> | null> (null)
const releaseDownload = useRef<(() => void) | null> (null)
const [downloadError, setDownloadError] = useState<string | null> (null)
const pollRequest = useRef<AbortController | null> (null)
@@ -79,25 +81,33 @@ const CountsExportProgress = (
const response = await apiGet<ExportStatus> (
`/counts/export/jobs/${encodeURIComponent (token)}`, {
locale, signal: controller.signal })
if (!active) return
if (!active || terminal.current) return
const result = (cancelled.current && response.status === 'ready')
? await apiDelete<ExportStatus> (
`/counts/export/jobs/${encodeURIComponent (token)}`, {
locale, signal: controller.signal }) : response
if (!active) return
if (!active || terminal.current) return
lastResponse = Date.now ()
setState (result)
terminal.current = ['failed', 'cancelled'].includes (result.status)
if (result.status === 'ready' && result.token != null && !downloaded.current)
{
downloaded.current = true
registration.current = true
const receipt = await apiPost<ExportStatus> (
`/counts/export/jobs/${encodeURIComponent (token)}/download`, {}, {
locale, signal: controller.signal })
if (!active || cancelled.current) return
if (receipt.downloadAttempt == null) throw new Error ('Missing download attempt')
downloaded.current = true
attempt.current = receipt.downloadAttempt
downloadDeadline.current = Date.now () + 30_000
acknowledgementTimer.current = setTimeout (() => {
if (!active || terminal.current) return
terminal.current = true
pollRequest.current?.abort ()
releaseDownload.current?.()
setDownloadError ('timeout')
}, 30_000)
releaseDownload.current = startAPIDownload ('/counts/export', result.token, () => {
if (active)
{
@@ -132,13 +142,13 @@ const CountsExportProgress = (
}
catch (error)
{
if (active)
if (active && !terminal.current)
{
if ((isApiError (error) && error.response?.status === 404)
if (downloaded.current || registration.current || (isApiError (error) && error.response?.status === 404)
|| Date.now () - lastResponse > 30_000)
{
terminal.current = true
if (downloaded.current) setDownloadError ('unavailable')
setDownloadError ('unavailable')
setState (previous => ({ ...previous, status: 'failed' }))
return
}
@@ -160,13 +170,14 @@ const CountsExportProgress = (
locale, signal: controller.signal }).finally (() => clearTimeout (deadline))
}
creation.current.then (async result => {
if (!active) return
if (!active || terminal.current) return
job.current = result.job
if (cancelled.current) await handle.cancel ()
await poll (result.job)
}).catch (() => {
if (active)
{
setDownloadError ('failed')
terminal.current = true
setState (previous => ({ ...previous, status: 'failed' }))
}
@@ -176,8 +187,9 @@ const CountsExportProgress = (
if (timer != null) clearTimeout (timer)
pollRequest.current?.abort ()
releaseDownload.current?.()
if (acknowledgementTimer.current != null) clearTimeout (acknowledgementTimer.current)
}
}, [handle, locale, payload, controls])
}, [handle, locale])
useEffect (() => {
controls.setActions ([])
}, [controls])