cleanup comments, update translations, bump to 0.4.1
This commit is contained in:
parent
9edcc9841b
commit
7d8ab87eee
28 changed files with 412 additions and 1114 deletions
61
src/wines.js
61
src/wines.js
|
|
@ -103,12 +103,8 @@ async function refreshWines() {
|
|||
}
|
||||
const prev = readJson(REGISTRY, []).map(wineAbs)
|
||||
const prevById = new Map(prev.map(w => [w.id, w]))
|
||||
// A build whose path changed since last scan was moved (or the launcher
|
||||
// itself was) - rewire its absolute symlinks to the current location. A
|
||||
// build that cannot be executed (no exec bit on proton/wine - exactly what
|
||||
// a sync from a PLWL that predates permission transfer leaves behind) is
|
||||
// healed here instead: two access() calls per build, and only a broken build
|
||||
// pays for the walk. Without it such a build stays dead weight forever.
|
||||
// heal moved builds and builds missing their exec bit; only a broken build
|
||||
// pays for the walk
|
||||
for (const w of found) {
|
||||
const old = prevById.get(w.id)
|
||||
const moved = !old || old.path !== w.path
|
||||
|
|
@ -119,8 +115,7 @@ async function refreshWines() {
|
|||
}
|
||||
}
|
||||
const disabled = new Set(prev.filter(w => w.disabled).map(w => w.id))
|
||||
// a display name the user gave the build has to survive every rescan, just
|
||||
// like the disabled flag (both live in the registry, not in the folder)
|
||||
// display name and disabled flag live in the registry and survive rescans
|
||||
const renamed = new Map(prev.filter(w => w.displayName).map(w => [w.id, w.displayName]))
|
||||
for (const w of found) {
|
||||
if (disabled.has(w.id)) w.disabled = true
|
||||
|
|
@ -131,8 +126,7 @@ async function refreshWines() {
|
|||
return found
|
||||
}
|
||||
|
||||
// Stored relative to FILES (portable), resolved to absolute for use - same
|
||||
// scheme as the game registry. `id`, `name`, `label`, `version`, `type` pass through.
|
||||
// Stored FILES-relative (portable); `id`/`name`/`label`/`version`/`type` pass through.
|
||||
function wineAbs(w) {
|
||||
return applyDisplayName({ ...w, path: resolvePortablePath(w.path), wineBin: resolvePortablePath(w.wineBin) })
|
||||
}
|
||||
|
|
@ -149,25 +143,20 @@ function getWine(id) {
|
|||
return getWines().find(w => w.id === id) || null
|
||||
}
|
||||
|
||||
// What the user typed wins over the folder-derived label everywhere the
|
||||
// build is shown (library config, settings, Open with popup). Every registry
|
||||
// read goes through here, so a stale stored label can never leak out.
|
||||
// The user's label wins over the folder-derived one, everywhere.
|
||||
function applyDisplayName(w) {
|
||||
if (w && w.displayName) w.label = w.displayName
|
||||
return w
|
||||
}
|
||||
|
||||
// A build keeps its folder and id (apps point at the id, and prefix symlinks
|
||||
// point inside the folder), so "renaming" only changes the shown label.
|
||||
// "Renaming" only changes the shown label; folder and id stay.
|
||||
function setWineDisplayName(id, name) {
|
||||
const clean = normalizeLabel(name)
|
||||
if (!clean) return { ok: false, error: 'invalidName' }
|
||||
return writeWineLabel(id, clean)
|
||||
}
|
||||
|
||||
// Clears a custom label back to the folder-derived one: what the LAN sync
|
||||
// calls when the sender never renamed the build. The rename dialog never
|
||||
// clears - an empty name there is an error (setWineDisplayName above).
|
||||
// Clears a custom label back to the folder-derived one (used by LAN sync).
|
||||
function clearWineDisplayName(id) {
|
||||
return writeWineLabel(id, '')
|
||||
}
|
||||
|
|
@ -203,9 +192,7 @@ function getDefaultWine() {
|
|||
return require('./library').loadSettings().defaultWine || 'system'
|
||||
}
|
||||
|
||||
// Resolve the wine build a run should use: 'default'/'system'/unknown all
|
||||
// fall back to System Wine. Shared by the games service and the ad-hoc
|
||||
// "open with" runner.
|
||||
// 'default'/'system'/unknown all fall back to System Wine.
|
||||
function resolveWine(id) {
|
||||
const system = { id: 'system', type: 'system', label: 'System Wine' }
|
||||
const wanted = id === 'default' ? getDefaultWine() : id
|
||||
|
|
@ -238,22 +225,15 @@ async function deleteWine(id, onProgress) {
|
|||
}
|
||||
|
||||
// --- symlink healing -------------------------------------------------------
|
||||
// Wine/Proton builds contain symlinks (e.g. inside the bundled default prefix)
|
||||
// that point to absolute paths from wherever the build lived before (zip
|
||||
// layout, previous install dir). After the build or the whole launcher is
|
||||
// moved those links dangle and Proton crashes during prefix setup
|
||||
// (try_copy -> FileNotFoundError). The rewiring itself lives in symlinks.js
|
||||
// because prefixes, synced apps and game imports need exactly the same thing.
|
||||
// Builds contain absolute symlinks that dangle after a move and crash Proton
|
||||
// during prefix setup; the rewiring lives in symlinks.js.
|
||||
async function fixBuildSymlinks(root) {
|
||||
return rewireSymlinks(root, [DIRS.FILES])
|
||||
}
|
||||
|
||||
// Everything a freshly arrived build needs before it can run: relative links
|
||||
// and executable binaries. Returns what was changed, for the callers' reports.
|
||||
// Make a freshly arrived build runnable: relative links, exec bits.
|
||||
async function healBuild(root) {
|
||||
// a build folder that IS a symlink (kept on another disk) is left alone:
|
||||
// the walk would follow the link and edit its target, which is not
|
||||
// necessarily part of this launcher's trees
|
||||
// a symlinked build folder is left alone: its target may be outside
|
||||
let st = null
|
||||
try {
|
||||
st = fs.lstatSync(root)
|
||||
|
|
@ -275,16 +255,11 @@ function isExec(p) {
|
|||
}
|
||||
}
|
||||
|
||||
// Where a wine/proton build keeps the programs wine itself spawns (wine,
|
||||
// wineserver, wineboot, ...) and the loaders next to the shared libraries.
|
||||
// Where a build keeps the programs wine spawns (wine, wineserver, ...).
|
||||
const AUDIT_DIRS = ['files/bin', 'bin', 'dist/bin', 'files/lib/wine/x86_64-unix', 'files/lib/wine/i386-unix']
|
||||
|
||||
// Is this build missing a permission it needs to run? A build synced from a
|
||||
// PLWL that predates permission transfer has no executable bit anywhere, and
|
||||
// that stays invisible until the user presses Play (spawn fails with EACCES).
|
||||
// Auditing is a handful of directory listings - only a build that FAILS it
|
||||
// pays for the full walk, and the audit says nothing about a healthy build on
|
||||
// every later start.
|
||||
// Cheap audit for a build missing its exec bits (invisible until Play fails
|
||||
// with EACCES); only a failing build pays for the full walk.
|
||||
function buildAuditFailed(root, wineBin) {
|
||||
if (!root) return false
|
||||
if (wineBin && !isExec(wineBin)) return true
|
||||
|
|
@ -319,8 +294,7 @@ async function importWineFolder(srcDir, onProgress) {
|
|||
if (build) copySrc = path.join(srcDir, build.name)
|
||||
}
|
||||
const dst = dedupeDir(WINES_DIR, path.basename(copySrc))
|
||||
// a wine/proton build is big enough that the copy needs a real bar: count it
|
||||
// first (staging), then report bytes as they land while it copies
|
||||
// count first (staging), then report bytes as they land
|
||||
const emit = (phase, detail) => {
|
||||
if (onProgress) onProgress({ phase, label: path.basename(dst), ...detail })
|
||||
}
|
||||
|
|
@ -336,8 +310,7 @@ async function importWineFolder(srcDir, onProgress) {
|
|||
return { ok: true, path: toPortablePath(dst), symlinksFixed: healed.links, executablesFixed: healed.executables }
|
||||
}
|
||||
|
||||
// The pseudo entry for the distro's wine, offered next to the imported
|
||||
// builds; version probe is best-effort (a PATH without wine still lists it).
|
||||
// Pseudo entry for the distro's wine; version probe is best-effort.
|
||||
function systemWine() {
|
||||
let version = ''
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue