fix syncing unselected apps from prefix
This commit is contained in:
parent
aee4d3d1f2
commit
02d296b7f1
5 changed files with 58 additions and 9 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "PLWL",
|
"name": "PLWL",
|
||||||
"productName": "PLWL",
|
"productName": "PLWL",
|
||||||
"version": "0.5.4",
|
"version": "0.6.0",
|
||||||
"description": "Portable Linux Wine Launcher",
|
"description": "Portable Linux Wine Launcher",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,10 @@ function keptBy(rel, keep) {
|
||||||
|
|
||||||
// One entry and, for a folder, everything in it. `full` means nothing inside
|
// One entry and, for a folder, everything in it. `full` means nothing inside
|
||||||
// survives, so the whole subtree can be shown (and deleted) as one row.
|
// survives, so the whole subtree can be shown (and deleted) as one row.
|
||||||
async function planEntry(abs, rel, keep) {
|
// `protect` is a subtree that is not ours to plan at all, even when its parent
|
||||||
|
// is redundant: without it a wholesale-planned parent would swallow an app a
|
||||||
|
// selection left out.
|
||||||
|
async function planEntry(abs, rel, keep, protect) {
|
||||||
let st
|
let st
|
||||||
try {
|
try {
|
||||||
st = await fsp.lstat(abs)
|
st = await fsp.lstat(abs)
|
||||||
|
|
@ -51,11 +54,15 @@ async function planEntry(abs, rel, keep) {
|
||||||
let bytes = 0
|
let bytes = 0
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const childRel = `${rel}/${entry.name}`
|
const childRel = `${rel}/${entry.name}`
|
||||||
|
if (protect && protect.has(childRel)) {
|
||||||
|
survived++
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (keep.has(childRel)) {
|
if (keep.has(childRel)) {
|
||||||
survived++
|
survived++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const child = await planEntry(`${abs}/${entry.name}`, childRel, keep)
|
const child = await planEntry(`${abs}/${entry.name}`, childRel, keep, protect)
|
||||||
if (!child) {
|
if (!child) {
|
||||||
survived++
|
survived++
|
||||||
continue
|
continue
|
||||||
|
|
@ -64,6 +71,8 @@ async function planEntry(abs, rel, keep) {
|
||||||
files += child.files
|
files += child.files
|
||||||
bytes += child.bytes
|
bytes += child.bytes
|
||||||
}
|
}
|
||||||
|
// nothing inside goes: the folder itself stays, so there is no row to show
|
||||||
|
if (survived > 0 && !children.length) return null
|
||||||
return { path: rel, name: baseName(rel), type: 'dir', files, bytes, full: survived === 0, children }
|
return { path: rel, name: baseName(rel), type: 'dir', files, bytes, full: survived === 0, children }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +97,7 @@ async function walkDeletions(abs, rel, keep, protect) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const node = await planEntry(`${abs}/${entry.name}`, childRel, keep)
|
const node = await planEntry(`${abs}/${entry.name}`, childRel, keep, protect)
|
||||||
if (node) out.push(node)
|
if (node) out.push(node)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|
|
||||||
|
|
@ -848,6 +848,15 @@ async function handlePlan(state, session, url, req, res, hooks) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
skip.push(rel)
|
skip.push(rel)
|
||||||
|
// The same folder may be named differently on this side (a unified
|
||||||
|
// `pfx` link is not followed by the walk): protect every spelling the
|
||||||
|
// local tree really uses, or the folder would be planned for deletion.
|
||||||
|
const abs = filesPath(rel)
|
||||||
|
if (!abs) continue
|
||||||
|
try {
|
||||||
|
const real = toPortablePath(fs.realpathSync(abs))
|
||||||
|
if (real && real !== rel && ownedRoot(real) && owned.some(root => real === root || real.startsWith(root + '/'))) skip.push(real)
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,15 +208,24 @@ function createPusher(api) {
|
||||||
|
|
||||||
// A prefix app a selection leaves out stays on this side: its folder must not
|
// A prefix app a selection leaves out stays on this side: its folder must not
|
||||||
// travel with the prefix, and the receiver must not prune it as a leftover.
|
// travel with the prefix, and the receiver must not prune it as a leftover.
|
||||||
|
// Every unselected prefix app inside the unit is found by where its folder
|
||||||
|
// really is, not by the prefix name it was registered with. The path is named
|
||||||
|
// the way the prefix walk names it: a folder imported through the unified
|
||||||
|
// `pfx` link is "drive_c/..." in the manifest, and the stored spelling alone
|
||||||
|
// would never match - the folder would travel.
|
||||||
function prefixExcludes(name, sel) {
|
function prefixExcludes(name, sel) {
|
||||||
const rootRel = `wineprefixes/${name}`
|
const rootRel = `wineprefixes/${name}`
|
||||||
const out = []
|
const out = []
|
||||||
for (const game of library.loadGames()) {
|
for (const game of library.loadGames()) {
|
||||||
if (api.appCategory(game) !== 'apps-prefix' || game.prefix !== name) continue
|
if (api.appCategory(game) !== 'apps-prefix') continue
|
||||||
if (sel.has(`app:${game.id}`, game.id)) continue
|
if (sel.has(`app:${game.id}`, game.id)) continue
|
||||||
const rel = String(toPortablePath(library.appFolder(game)) || '')
|
const folder = library.appFolder(game)
|
||||||
if (rel === rootRel || !rel.startsWith(rootRel + '/')) continue
|
const inPrefix = prefixes.prefixRelFor(name, folder)
|
||||||
out.push(rel)
|
const candidates = [inPrefix ? `${rootRel}/${inPrefix}` : '', String(toPortablePath(folder) || '')]
|
||||||
|
for (const rel of candidates) {
|
||||||
|
if (rel === rootRel || !rel.startsWith(rootRel + '/')) continue
|
||||||
|
if (!out.includes(rel)) out.push(rel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,28 @@ function ensureUnifiedPrefix(name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The prefix-relative path the prefix's own tree walk gives this folder. The
|
||||||
|
// walk never follows the unified `pfx` link, so a folder stored through it
|
||||||
|
// ("pfx/drive_c/...") is really named "drive_c/..." there: resolve both sides
|
||||||
|
// to their real place, or an exclude/keep list would never match. Falls back
|
||||||
|
// to the plain lexical path when that is all there is (folder gone, or a link
|
||||||
|
// pointing out of the prefix).
|
||||||
|
function prefixRelFor(name, absPath) {
|
||||||
|
if (!name || !absPath) return ''
|
||||||
|
const root = prefixPath(name)
|
||||||
|
const abs = path.resolve(String(absPath))
|
||||||
|
const toRel = (base, target) => {
|
||||||
|
const rel = path.relative(base, target)
|
||||||
|
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return ''
|
||||||
|
return rel.split(path.sep).join('/')
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const real = toRel(fs.realpathSync(root), fs.realpathSync(abs))
|
||||||
|
if (real) return real
|
||||||
|
} catch {}
|
||||||
|
return toRel(root, abs)
|
||||||
|
}
|
||||||
|
|
||||||
// True when a path IS one of a prefix's own roots (folder, pfx/, drive_c):
|
// True when a path IS one of a prefix's own roots (folder, pfx/, drive_c):
|
||||||
// importing one would gut the prefix.
|
// importing one would gut the prefix.
|
||||||
function isPrefixRoot(p) {
|
function isPrefixRoot(p) {
|
||||||
|
|
@ -184,4 +206,4 @@ function setDefaultPrefix(name) {
|
||||||
return { ok: true }
|
return { ok: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { isPrefixRel, PREFIX_REL_RE, LINUX_PFX, listPrefixes, savePrefixes, createPrefix, deletePrefix, renamePrefix, setDefaultPrefix, prefixPath, prefixLinuxRoot, prefixExists, prefixDriveRoots, isPrefixRoot, healPrefix, ensureUnifiedPrefix, MAIN }
|
module.exports = { isPrefixRel, PREFIX_REL_RE, LINUX_PFX, listPrefixes, savePrefixes, createPrefix, deletePrefix, renamePrefix, setDefaultPrefix, prefixPath, prefixLinuxRoot, prefixExists, prefixDriveRoots, isPrefixRoot, prefixRelFor, healPrefix, ensureUnifiedPrefix, MAIN }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue