wine import auto-fix symlinks
This commit is contained in:
parent
e082fedf19
commit
2b7623227d
1 changed files with 96 additions and 3 deletions
99
src/wines.js
99
src/wines.js
|
|
@ -106,6 +106,17 @@ async function refreshWines() {
|
|||
found = []
|
||||
}
|
||||
const prev = readJson(REGISTRY, [])
|
||||
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.
|
||||
for (const w of found) {
|
||||
const old = prevById.get(w.id)
|
||||
if (!old || old.path !== w.path) {
|
||||
try {
|
||||
await fixBuildSymlinks(w.path)
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
const disabled = new Set(prev.filter(w => w.disabled).map(w => w.id))
|
||||
for (const w of found) if (disabled.has(w.id)) w.disabled = true
|
||||
const merged = [...found]
|
||||
|
|
@ -168,6 +179,84 @@ function dedupeDir(base, name) {
|
|||
return candidate
|
||||
}
|
||||
|
||||
// --- 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). Rewire every absolute symlink whose content
|
||||
// can be located inside the build, always storing the new target as a
|
||||
// *relative* path so the build stays relocatable forever.
|
||||
async function collectSymlinks(dir, out) {
|
||||
let entries
|
||||
try {
|
||||
entries = await fsp.readdir(dir, { withFileTypes: true })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
for (const e of entries) {
|
||||
const full = path.join(dir, e.name)
|
||||
if (e.isSymbolicLink()) {
|
||||
out.push(full)
|
||||
} else if (e.isDirectory() && e.name !== 'dosdevices') {
|
||||
// dosdevices/ holds prefix drive mappings (c:, z:, ...) which are
|
||||
// *supposed* to point outside the build - never touch them.
|
||||
await collectSymlinks(full, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rewireTarget(root, link, target) {
|
||||
if (!path.isAbsolute(target)) return null // already relocatable
|
||||
// Absolute link that lives inside the build: make it relative.
|
||||
if (target.startsWith(root + path.sep)) {
|
||||
return path.relative(path.dirname(link), target)
|
||||
}
|
||||
// Absolute link to an old location: find the longest suffix of the target
|
||||
// that exists inside the new build and point there instead.
|
||||
let i = target.indexOf(path.sep, 1)
|
||||
while (i !== -1) {
|
||||
const rel = target.slice(i + 1)
|
||||
if (rel) {
|
||||
const candidate = path.join(root, rel)
|
||||
if (!rel.split(path.sep).includes('..') && candidate.startsWith(root + path.sep) && exists(candidate)) {
|
||||
return path.relative(path.dirname(link), candidate)
|
||||
}
|
||||
}
|
||||
i = target.indexOf(path.sep, i + 1)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function fixBuildSymlinks(root) {
|
||||
const links = []
|
||||
await collectSymlinks(root, links)
|
||||
let fixed = 0
|
||||
// A link may only become rewirable once another link it chains through has
|
||||
// been fixed, so repeat until a full pass changes nothing.
|
||||
for (let pass = 0; pass < 5; pass++) {
|
||||
let changed = 0
|
||||
for (const link of links) {
|
||||
let target
|
||||
try {
|
||||
target = await fsp.readlink(link)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
const newTarget = rewireTarget(root, link, path.normalize(target))
|
||||
if (!newTarget || newTarget === target) continue
|
||||
try {
|
||||
await fsp.unlink(link)
|
||||
await fsp.symlink(newTarget, link)
|
||||
fixed++
|
||||
changed++
|
||||
} catch {}
|
||||
}
|
||||
if (!changed) break
|
||||
}
|
||||
return fixed
|
||||
}
|
||||
|
||||
async function importWineFolder(srcDir) {
|
||||
const type = detectType(srcDir)
|
||||
let copySrc = srcDir
|
||||
|
|
@ -184,9 +273,13 @@ async function importWineFolder(srcDir) {
|
|||
}
|
||||
}
|
||||
const dst = dedupeDir(WINES_DIR, path.basename(copySrc))
|
||||
await fsp.cp(copySrc, dst, { recursive: true })
|
||||
await fsp.cp(copySrc, dst, { recursive: true, verbatimSymlinks: true })
|
||||
let symlinksFixed = 0
|
||||
try {
|
||||
symlinksFixed = await fixBuildSymlinks(dst)
|
||||
} catch {}
|
||||
await refreshWines()
|
||||
return { ok: true, path: dst }
|
||||
return { ok: true, path: dst, symlinksFixed }
|
||||
}
|
||||
|
||||
module.exports = { refreshWines, getWines, getWine, setWineDisabled, getDefaultWine, setDefaultWine, deleteWine, importWineFolder, findWineBinary, detectType }
|
||||
module.exports = { refreshWines, getWines, getWine, setWineDisabled, getDefaultWine, setDefaultWine, deleteWine, importWineFolder, findWineBinary, detectType, fixBuildSymlinks }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue