(jobRef, { ok = true, error = '', state = '' } = {})
| 935 | } |
| 936 | |
| 937 | export function finishTransferProgress(jobRef, { ok = true, error = '', state = '' } = {}) { |
| 938 | const job = resolveJob(jobRef); |
| 939 | if (!job) return; |
| 940 | |
| 941 | const now = Date.now(); |
| 942 | const totalBytes = Number.isFinite(job.bytesTotal) && job.bytesTotal > 0 |
| 943 | ? job.bytesTotal |
| 944 | : Number(job.totalBytes || 0); |
| 945 | |
| 946 | if (ok && !job.indeterminate && totalBytes > 0) { |
| 947 | const elapsedSec = Math.max(0.5, (now - job.startedAt) / 1000); |
| 948 | const bytesForRate = totalBytes; |
| 949 | const actualBps = bytesForRate / elapsedSec; |
| 950 | const clamped = clamp(actualBps, MIN_SPEED_BPS, MAX_SPEED_BPS); |
| 951 | const blended = Math.round(job.estimateBps * 0.7 + clamped * 0.3); |
| 952 | storeSpeed(blended); |
| 953 | } |
| 954 | |
| 955 | if (ok) { |
| 956 | let currentPct = Number.isFinite(job.pct) ? clamp(job.pct, 0, 100) : null; |
| 957 | if (!Number.isFinite(currentPct)) { |
| 958 | if (Number.isFinite(job.bytesDone) && totalBytes > 0) { |
| 959 | currentPct = clamp(Math.round((job.bytesDone / totalBytes) * 100), 0, 100); |
| 960 | } else if (Number.isFinite(job.filesDone) && Number.isFinite(job.filesTotal) && job.filesTotal > 0) { |
| 961 | currentPct = clamp(Math.round((job.filesDone / job.filesTotal) * 100), 0, 100); |
| 962 | } |
| 963 | } |
| 964 | |
| 965 | const visiblePct = Number.isFinite(job.renderedPct) ? clamp(job.renderedPct, 0, 100) : null; |
| 966 | const fromPct = Number.isFinite(visiblePct) ? visiblePct : currentPct; |
| 967 | const shouldSmoothFinish = job.mode === 'actual' && Number.isFinite(fromPct) && fromPct >= 0 && fromPct < 98; |
| 968 | if (shouldSmoothFinish) { |
| 969 | job.state = 'finishing'; |
| 970 | job.error = ''; |
| 971 | job.finalizeStartAt = now; |
| 972 | job.finalizeDurationMs = 650; |
| 973 | job.finalizeFromPct = clamp(Number(fromPct), 0, 99); |
| 974 | job.endedAt = now + job.finalizeDurationMs; |
| 975 | job.removeAt = job.endedAt + FINAL_VISIBLE_MS; |
| 976 | ensureTicking(); |
| 977 | renderAll(); |
| 978 | return; |
| 979 | } |
| 980 | } |
| 981 | |
| 982 | const requestedState = String(state || '').toLowerCase(); |
| 983 | const finalState = requestedState || (ok ? 'done' : 'error'); |
| 984 | job.state = finalState; |
| 985 | job.error = ok ? '' : String(error || ''); |
| 986 | job.endedAt = now; |
| 987 | job.removeAt = job.endedAt + FINAL_VISIBLE_MS; |
| 988 | job.cancelable = false; |
| 989 | job.onCancel = null; |
| 990 | job.cancelInFlight = false; |
| 991 | |
| 992 | ensureTicking(); |
| 993 | renderAll(); |
| 994 | } |
no test coverage detected