Replace the single sequential transfer with a pool of N worker threads
that pull pieces of one manifest in parallel, lifting the per-connection
~7 MB/s ceiling. One job runs at a time; the rest queue. The connection
count is configurable (1-16, default 4) and applies on the next start.
Resume is tracked per piece in a sidecar bitmap that survives a reboot,
and a one-time migration recognises a partial written by the old
sequential build (a piece-aligned contiguous prefix on disk) and marks
those pieces done so an in-progress download is not restarted from zero.
Pause keeps the partial; Cancel deletes it. Both, plus Resume, are
available at any point in a download's life.
Concurrency review fixes folded in:
- a job is published as the active (claimable) job only after its
manifest/state/fd are attached, so a half-built job can no longer be
settled to "done" before any bytes are fetched
- cancel/pause during the admit I/O window only flag the job; admit_next
is the sole finalizer, closing a use-after-free and a lost-pause race
- resuming a paused job frees the stale per-job buffers and zeroes the
committed counters before re-seeding, fixing a leak and a double-count
- the background version.xml thread is joined on shutdown before the
title list is freed
- verify_downloads is snapshotted under its own lock before the pool lock
- the web UI keeps Resume/Cancel after a failed transfer and bounds the
local "downloading" bridge flag so a card cannot wedge
Split the single morphing button into a green/amber play-pause (Update →
Pause → Resume) and a red stop (Cancel). Pause aborts the download but
keeps the partial (resumable); Cancel aborts and deletes. Backend gets a
separate pause flag distinct from cancel.
Resume now continues WITHIN a part: the partially-written piece is fetched
from its last byte via an HTTP byte range (with a safe fall back to
re-fetching the whole piece if the CDN ignores the range), instead of
re-downloading the whole part. A title is resumable as soon as any bytes
are on disk.
Version bumped to 0.0.3 (no release tagged).
Replace the single-page layout with a view-based one (Games / Settings /
Logs via the left nav) and fold all download UI into the game tile — the
separate download queue is gone.
Per tile while downloading: an in-tile progress bar with auto-scaled size
(B/KB/MB/GB/TB), live transfer speed and ETA from the poll deltas, and a
green "Downloading" marker. One fixed-width action button that no longer
reflows with its label: a blue Update/Download/Install that morphs into an
amber Cancel while the download runs, plus a ghost Delete for a finished
package.
Settings and Logs moved to their own pages. Mobile-first responsive layout
(rail collapses to a top bar, icon-only nav, single-column tiles, >=44px
touch targets, 16px inputs). Real free space (statvfs) is shown auto-scaled
in the rail and status strip.
New "Home-screen shortcut" toggle (default on, persisted as home_shortcut
in config.json). The actual PS5 tile install is not wired yet: it needs a
prebuilt deeplinkUri stub PKG installed via sceAppInstUtil.
Fixes from an adversarial review pass:
- Reconcile in-flight downloads from /api/downloads onto the cards, so
progress + Cancel appear after a reload or a download started elsewhere,
and stop the per-poll full-grid rebuild.
- Clear the downloaded flag on install and once the server reports the
title up to date, so a patched title no longer shows Install/Delete
forever.
- Zero a stale speed/ETA if the byte counter goes backwards.
- a11y: nav buttons keep an accessible name when the label is hidden on
small screens; visible focus ring on the search box; filter group is
role=group with aria-pressed; drop the noisy grid-level aria-live; fold
the transient "checking" state into a visible filter bucket.
Each Sony manifest piece carries a SHA-256 (hashValue). When the new
"Verify downloaded pieces" setting is on, every piece is hashed while it
streams to disk (OpenSSL EVP, already linked) and compared against the
manifest value; a mismatch aborts the download, deletes the partial, and
reports piece_verify_failed instead of handing a corrupt 60 GB package to
the installer.
Off by default: TLS already protects the bytes in transit and the PS5
installer verifies the whole packageDigest before applying, so this is a
fail-fast belt-and-suspenders check. It is also unverified on hardware
yet, so it stays opt-in (persisted in config.json) until confirmed
on-device; the hex compare is case-insensitive since Sony mixes cases.
Standalone PS5 payload with an embedded web UI on :12880, no etaHEN.
- Scans installed titles and classifies the source (genuine install,
ShadowMountPlus mount, preinstall, unknown) from the on-disk layout.
- Reads name, installed version and the Sony version.xml URL from the PS5
app database (vendored SQLite).
- Resolves version.xml past nanoDNS via a raw DNS query, TLS pinned to the
SCEI DNAS root.
- Filters patches to the newest one compatible with the current firmware.
- Downloads the patch and installs it through AppInstUtil (sysmodule loaded
at runtime), gated to genuine installs with a package title-id match guard.
- Action-based UI filters and an on-screen startup notification with the URL.