Skip to content

Restore as many files at a time as the drive does best, with progress - #55

Merged
Omar-VF merged 3 commits into
Liquid-co:dev-publicfrom
ChakraFusion:pr/parallel-restore
Oct 17, 2026
Merged

Omar-VF merged 3 commits into
Liquid-co:dev-publicfrom
ChakraFusion:pr/parallel-restore

Conversation

@ChakraFusion

Copy link
Copy Markdown

As offered in #21.

  • internal/iopar runs many small file operations at whatever concurrency suits the drive: 1 on an HDD, starting at 8 on a SATA SSD and 32 on NVMe, then adapting every 750 ms. Drive type comes from the seek penalty and bus type on Windows, and from rotational / nvme on Linux.
  • A restore creates every folder once, then writes the files through iopar. Restoring Project Zomboid (238,410 files) on a 4-thread PC: 2 min 29 s → 1 min 42 s.
  • The game header shows the restore phase, percentage and time left (restoring in the game payload).

Tests: iopar (settles near the drive's best, three runs since it's timed with sub-millisecond sleeps; HDD one at a time; every item once with monotonic progress; stops at the first error); frontend progress formatting.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 14, 2026

Copy link
Copy Markdown

@ChakraFusion is attempting to deploy a commit to the sivadaboi's projects Team on Vercel.

A member of the Team first needs to authorize it.

@ChakraFusion

Copy link
Copy Markdown
Author

Rebased onto today's dev-public. The conflict was in the game payload in internal/api/server.go: #24's saveDriveMissing and relocateSuggestion and this PR's restoring are all kept. Go tests, the 312 frontend tests and the frontend build pass locally.

@Omar-VF

Omar-VF commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Thanks, this looks good. #75 (checking a snapshot before clearing a save) touches the same restore code. Once it's merged, could you rebase onto dev-public? UnzipRoots becomes UnzipRootsProgress here, so please keep #75's CheckExtractable call at the start of it, before anything is cleared. Optional while you're there: an archive that lists the same file twice would have two writers on it at once in parallel extraction. #75's check could refuse duplicates too, if you'd like to add that.

ChakraFusion and others added 3 commits October 16, 2026 16:52
Putting a large save back - a quarter of a million files - wrote them one
after another, looking up each one's folder again, while the drive sat at
90% active time and 15 MB/s, and the window showed a spinner for many
minutes with nothing to say what it was doing.

- internal/iopar runs many small file operations as many at a time as
  suits the drive they are on: one at a time on a hard disk, starting at
  8 on a SATA SSD and 32 on NVMe (Windows: the volume's seek penalty and
  bus type; Linux: rotational and nvme). On an SSD the number then moves
  every 750 ms the way that finishes more - on while it helps, back when
  it hurts, and now and then the other way once settled, so a drive that
  slows as it goes is followed.
- A restore makes every folder once, then writes the files through it;
  checking the archive reads its entries through it too. On a 4-thread
  machine restoring Project Zomboid (238,410 files) the processor, not the
  SSD, is the limit: writing went from 2m29s to 1m42s, settling at 3-8 at
  once; machines with more cores go further by themselves.
- A restore says how far it is - rebuilding a compacted snapshot,
  checking it, writing the files - with percent and, once it can be told,
  the time left, in the game's header ("restoring" in the game payload).

The drive-adaptation test is timed with sub-millisecond sleeps, so it
gets three runs and one has to settle near the best; a broken adaptation
misses in all three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The phase names, the "{done} of {total}" line and the time left were English
strings in lib/restoreprogress.js. They are restoreProgress.* keys now, the
minutes a plural, and restoreProgressText takes the caller's $t, so the line
in the game header follows a change of language mid-restore. As asked on Liquid-co#73.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tore

Extraction now writes files in parallel, so an archive holding the same
file twice would have two writers on it at once, and what is left on disk
could be neither entry. CheckExtractable (Liquid-co#75) refuses it, before anything
is cleared, as it refuses any archive that cannot be laid out whole. Names
differing only in case count as the same file where the filesystem ignores
case (Windows, macOS), as the rest of the check already compares them.

Tests: the same file twice, in the main folder and in an extra location;
one name in two locations is still fine; names differing only in case.
All fail without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ChakraFusion

Copy link
Copy Markdown
Author

Rebased onto dev-public with #75 (fd5b68c). CheckExtractable stays at the start of UnzipRootsProgress, before anything is cleared, and Restore runs it right after VerifyArchiveProgress, as checkRestorable did.

I took the optional part too, as its own commit: CheckExtractable refuses an archive that lists a file twice, including names differing only in case on Windows and macOS (the same placeKey comparison). Tests in restore_safety_test.go fail without it.

@Omar-VF
Omar-VF merged commit 7b9d004 into Liquid-co:dev-public Oct 17, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants