a LOT sync fixes...

This commit is contained in:
olcxja 2026-10-01 17:12:53 +02:00
commit 806e173e43
14 changed files with 1073 additions and 240 deletions

View file

@ -17,6 +17,8 @@ src/ main process (Node/Electron backend)
library.js apps registry (apps.json) + settings + playtime files
wines.js wine/proton build scan, registry, portable symlink healing
prefixes.js prefix registry (files/wineprefixes)
symlinks.js relative symlink rewiring + executable-bit restoration
(shared by imports, prefixes and LAN sync)
importer.js app import (copy/move/leave/prefix)
openwith.js "Open with PLWL": ad-hoc runs, no library entry
shortcuts.js .desktop shortcut generation
@ -70,9 +72,44 @@ handlers in src/ipc/. Keep new APIs in that pattern.
states share the module, so nothing may be kept in its module scope. Both
sides report their own progress (the receiver through the onReceive hook),
so each machine can show the transfer.
- Sync progress is bytes plus per-category counters (items = announced units,
itemsDone = finished ones, totals = everything the sender picked), never raw
file counts: the popup says "Apps: 2/5". The bar follows the CURRENT PHASE's
bytes (barBytes/barTotalBytes, not the overall bytes/totalBytes) so a phase
starts from an empty bar instead of inheriting a full one - otherwise hashing
looks finished the moment it starts. Hashing and copying walk tens of
thousands of files, so every per-file update goes through progressGate
(~10/s, phase changes forced) - never send one IPC message per file.
- The launcher's own defaults are opt-in (the "defaults" sync category):
payload.defaults carries the default wine and prefix, and the receiver only
applies values that exist there (an unknown wine id or prefix is skipped).
- LAN sync remembers file hashes in files/data/lansync-hashes.json, keyed by
path with size + mtime (machine-local, never synced): equal size and mtime
means equal bytes, so a repeat sync only stat's the tree. Hashing and the
receiving side's comparison both run HASH_CONCURRENCY files at a time.
- A modal showing a running operation (a sync) opens locked:
showModal(html, { locked: true }) ignores the backdrop and Escape, because
the user cannot cancel the operation by dismissing it.
- Symlinks inside everything PLWL owns (a wine/proton build, a prefix, an
imported app) are stored RELATIVE whenever the target can be expressed
locally: a Proton prefix is full of links into its build, and an absolute one
points at the machine that wrote it. symlinks.js owns this (rewireSymlinks,
plus restoreExecutableBits for the executables a content copy drops) and
every writer calls it: importWineFolder/healBuild, importer after copy/move,
healPrefix, and the sync both on the wire (portableLinkTarget sends the
relative target) and after a unit lands (healUnitRoots). dosdevices drive
mappings and targets that leave files/ (a mount, a system path, the user's
home) stay absolute - they are machine-specific by design.
- Wine and Proton create prefix links with ABSOLUTE local targets whenever they
update a prefix (font/dll stubs, Steam appcache), so on-disk links are not
always relative after a run: the sync payload relativizes them anyway, and
refreshWines()/healPrefix() repair what arrived from an older PLWL. A build
whose binaries lost their executable bit is detected by a cheap audit in
refreshWines() and healed once, because spawn() on a non-executable `proton`
fails with EACCES and the game would look like it started while nothing ran.
- File permissions travel with a sync (manifest entry `mode`): the receiving
side applies them after writing the bytes and repairs a file that has the
same hash but the wrong mode without re-uploading it.
- Tests are plain assert-by-print scripts (see test/): set PLWL_BUILD_DIR to
a temp dir, check(), exit 1 on failure. Add one when fixing a bug.
@ -83,5 +120,13 @@ node --check <changed js files> # syntax
node test/import.test.js # ...and the other test files
node test/lansync.test.js # LAN sync contract (identity, pairing, mirror)
node test/lansync-receiving.test.js # receive-side progress events (the popup)
node test/lansync-settings.test.js # playtime, app settings, wine/prefix + defaults
node test/lansync-links.test.js # wire: exec bits + relative links in a sync
node test/lansync-pending-links.test.js # links to files created later, startup heal
node test/bench-sync.js # timing: phases of a cold and a cached sync
npm start # click through the flow you touched
# Repairing / testing a real instance (test/ scripts are gitignored helpers):
PLWL_BUILD_DIR=/path/to/instance node test/heal-instance.js # links + exec bits
PLWL_BUILD_DIR=/path/to/instance node test/launch-instance.js <id> # really start a game
```