diff --git a/app/downloads/DownloadRow.jsx b/app/downloads/DownloadRow.jsx index d3dfaa0..9da9fcb 100644 --- a/app/downloads/DownloadRow.jsx +++ b/app/downloads/DownloadRow.jsx @@ -104,7 +104,8 @@ import { Text, View } from '@ps5-react/core'; id: job2.id, label: stateLabel(shown), moving: BUSY.includes(state) && state !== "pausing", - waiting: WAITING.includes(shown), + // An extraction past its size estimate has no total left to fill: the label shimmers instead. + waiting: WAITING.includes(shown) || (shown === "extracting" && !job2.total), complete, detail: shown === "finishing" ? t2("downloads.writingToDisk") : null } diff --git a/app/state/download-queue.js b/app/state/download-queue.js index 5054dac..a6cbc33 100644 --- a/app/state/download-queue.js +++ b/app/state/download-queue.js @@ -93,7 +93,7 @@ import { VIKING_PAGE_PREFIX, VIKING_PREFIX, describeVikingCapture, linkPageMetad delete job2.captureFile; delete job2.fallbacks; if (job2.state === "installing") return { ...job2, state: "installer-required" }; - if (job2.state === "extracting") job2.written = job2.total; + if (job2.state === "extracting") job2.written = job2.total ?? job2.written; if (job2.state === "removing") { job2.state = job2.removingFrom ?? "failed"; job2.error = REMOVAL_CUT; @@ -664,6 +664,10 @@ import { VIKING_PAGE_PREFIX, VIKING_PREFIX, describeVikingCapture, linkPageMetad const options = extraction(job2, files); const downloaded = { written: job2.written, total: job2.total }; let estimate = files.reduce((sum, path) => sum + (fs.stat(path)?.size || 0), 0) || null; + // The archive's own size stands in for what it unpacks to until the engine reads that from its headers. + // Games unpack to far more (issue #54: a 107.9 GB set to over 160 GB): past the guess the row counts + // what is written, with no total, instead of holding at 99%. + let guessed = true; job2.state = "extracting"; job2.written = 0; job2.total = estimate; @@ -672,6 +676,7 @@ import { VIKING_PAGE_PREFIX, VIKING_PREFIX, describeVikingCapture, linkPageMetad let sample = { at: Date.now(), bytes: 0 }; const report = (bytes) => { const now = Date.now(); + if (guessed && estimate && bytes > estimate * 0.99) job2.total = estimate = null; job2.written = estimate ? Math.min(bytes, estimate * 0.99) : bytes; if (now - sample.at >= 1e3) { const speed = (bytes - sample.bytes) * 1e3 / (now - sample.at); @@ -681,7 +686,11 @@ import { VIKING_PAGE_PREFIX, VIKING_PREFIX, describeVikingCapture, linkPageMetad emit2(); }; const follow = (progress) => { - if (progress.total && progress.total !== estimate) job2.total = estimate = progress.total; + // A total at the extraction's cap is the space free, not the files' size: they are bigger. + if (progress.total && progress.total !== estimate && progress.total < options.maxBytes) { + job2.total = estimate = progress.total; + guessed = false; + } if (progress.phase !== "finishing") { report(progress.written || 0); return;