cleanup comments, update translations, bump to 0.4.1

This commit is contained in:
olcxja 2026-10-05 15:42:44 +02:00
commit 7d8ab87eee
28 changed files with 412 additions and 1114 deletions

View file

@ -23,11 +23,7 @@ function dedupeDir(base, name) {
return path.join(base, `${name} (${n})`)
}
// Progress is cheap to produce and expensive to render: hashing or copying a
// tree can emit tens of thousands of events, and forwarding every single one
// over IPC makes the window stutter instead of showing progress. Events are
// rate limited to ~10/s; `force` lets a caller push the value that matters
// (the end of a unit) through immediately.
// Events are rate limited to ~10/s; `force` pushes the final value through.
const PROGRESS_MIN_MS = 100
function progressGate(emit) {
@ -60,9 +56,7 @@ function progressGate(emit) {
return push
}
// Run `fn` over `items` with at most `limit` calls in flight, results in
// order. Walking a tree is dominated by stat/readlink/open syscalls: a few in
// flight keep the disk and cpu busy without exhausting file descriptors.
// Run `fn` over `items` with at most `limit` in flight, results in order.
async function mapLimit(items, limit, fn) {
const out = new Array(items.length)
let next = 0
@ -76,15 +70,12 @@ async function mapLimit(items, limit, fn) {
return out
}
// What a tree copy will have to move. Symlinks have no size of their own
// (they are recreated, not read), so they only count for the file total.
// `skipDir` is a single directory name to leave out (a game folder's
// redistributables) - walking the whole tree first is fast next to the copy.
// What a tree copy will move. Symlinks are recreated, not read. `skipDir`
// leaves out one directory name (redistributables).
async function treeSize(root) {
let files = 0
let bytes = 0
// a symlink (or a plain file) as the root is the whole tree: walking it
// would measure whatever it points at, not what is about to be removed
// a symlinked root is the whole tree; never measure through it
let rootStat = null
try {
rootStat = await fsp.lstat(root)
@ -117,11 +108,8 @@ async function treeSize(root) {
return { files, bytes }
}
// Copy a directory tree the way an imported game/wine build has to arrive:
// symlinks recreated as they are (copyFile would silently drop them) and file
// permissions carried over (copyFile writes 0644, killing scripts and any
// binary the game executes itself). Reports cumulative { done, bytes } after
// every file through `onProgress`, so long copies can drive a progress bar.
// Copy a tree with symlinks recreated and permissions carried (copyFile
// drops both); reports cumulative { done, bytes } through onProgress.
async function copyTree(src, dst, onProgress) {
let done = 0
let bytes = 0
@ -173,10 +161,8 @@ async function copyTree(src, dst, onProgress) {
return { done, bytes }
}
// Delete a tree deepest-first so a long removal (a multi-GB prefix) can drive
// a progress bar instead of freezing the window on one rmSync. Symlinks are
// unlinked, never followed. Reports cumulative { files, bytes, dirs } through
// `onProgress`; a missing tree is simply done.
// Delete deepest-first with progress through onProgress; symlinks are
// unlinked, never followed.
async function removeTree(root, onProgress) {
let files = 0
let dirs = 0
@ -185,9 +171,7 @@ async function removeTree(root, onProgress) {
const tick = () => {
if (gate) gate({ files, dirs, bytes }, false)
}
// A symlink (or a file) as the root is removed as itself, never walked
// through: the root of an import/prefix can be a link to another disk, and
// deleting through it would take the target's files instead of the link.
// a symlinked root is removed as itself, never walked through
let rootStat = null
try {
rootStat = await fsp.lstat(root)