improvemets & fixes:
- added 'open with' handling to desktop shortcuts - move logs button to app configuration - fix sync progress disappearing after long hashing - fix sync double file counter - merge wine and proton prefix folders via symlink
This commit is contained in:
parent
8c6db38fab
commit
59ff0b1088
15 changed files with 520 additions and 113 deletions
|
|
@ -17,6 +17,12 @@ const path = require('path')
|
|||
// small files a game or prefix is mostly made of.
|
||||
const UPLOAD_CONCURRENCY = 4
|
||||
|
||||
// Keeps the receiving side's session and popup alive while this device hashes
|
||||
// a unit (minutes of complete silence from the network) - well under the
|
||||
// receiving popup's watchdog, and cheap enough to run through the transfer too.
|
||||
// Env override: tests cannot wait 20 s for a ping.
|
||||
const HEARTBEAT_MS = Number(process.env.PLWL_LANSYNC_HEARTBEAT) || 20000
|
||||
|
||||
function createPusher(api) {
|
||||
const {
|
||||
CATEGORIES,
|
||||
|
|
@ -131,9 +137,15 @@ function createPusher(api) {
|
|||
units.set(root, { key: `wine:${root.slice('wines/'.length)}`, kind: 'wine', id: root.slice('wines/'.length), label: label || root, roots: [root] })
|
||||
}
|
||||
for (const wine of wines.getWines()) {
|
||||
const rel = toPortablePath(wine.path)
|
||||
const parts = String(rel).split('/')
|
||||
add(parts.length === 2 && parts[0] === 'wines' ? rel : `wines/${path.basename(wine.path)}`, wine.label)
|
||||
const rel = String(toPortablePath(wine.path) || '')
|
||||
const parts = rel.split('/')
|
||||
// a build folder can sit one level down (zip-extracted layouts are
|
||||
// detected at any depth): the unit is always the TOP folder under
|
||||
// wines/ - that is the folder mirrored as a whole, and listDirs()
|
||||
// names it as well. Using the basename here would point the unit at a
|
||||
// folder that does not exist and inflate the "Wine versions" totals
|
||||
// with a phantom unit.
|
||||
add(parts[0] === 'wines' && parts[1] ? `wines/${parts[1]}` : `wines/${path.basename(wine.path)}`, wine.label)
|
||||
}
|
||||
for (const name of listDirs(DIRS.WINES)) add(`wines/${name}`)
|
||||
return [...units.values()]
|
||||
|
|
@ -178,14 +190,39 @@ function createPusher(api) {
|
|||
// the receiver prunes its own builds down to this list and rescans
|
||||
payload.wines = wineUnits().map(unit => ({ name: unit.id }))
|
||||
}
|
||||
if (cats.has('defaults')) {
|
||||
// "Default settings": which wine everything falls back to and where a new
|
||||
// import lands. Opt-in on purpose - a sync of apps must not silently
|
||||
// change the other machine's defaults.
|
||||
if (cats.has('settings')) {
|
||||
// "Sync settings": every registry-level value that describes how the
|
||||
// launcher is set up here - the defaults, the names/flags the user gave
|
||||
// wine builds, the prefix registry, and each app's configuration plus
|
||||
// its position in the library. The receiver only patches things it
|
||||
// already has; this category creates and deletes no files. Opt-in on
|
||||
// purpose - a sync of apps must not silently rewrite the other
|
||||
// machine's settings.
|
||||
const defs = prefixes.listPrefixes()
|
||||
const games = library.loadGames()
|
||||
payload.defaults = {
|
||||
defaultWine: (library.loadSettings().defaultWine) || 'system',
|
||||
defaultPrefix: prefixes.listPrefixes().default || ''
|
||||
defaultPrefix: defs.default || ''
|
||||
}
|
||||
payload.wineSettings = wines
|
||||
.getWines()
|
||||
.filter(w => w.type !== 'system')
|
||||
.map(w => ({ name: w.id, displayName: w.displayName || '', disabled: !!w.disabled }))
|
||||
payload.prefixSettings = {
|
||||
default: defs.default || '',
|
||||
prefixes: defs.prefixes.map(p => ({ name: p.name, created: p.created, locked: !!p.locked }))
|
||||
}
|
||||
payload.appSettings = games.map(g => ({
|
||||
id: g.id,
|
||||
name: g.name,
|
||||
wine: g.wine || 'default',
|
||||
prefix: g.prefix || '',
|
||||
args: g.args || '',
|
||||
env: g.env || {},
|
||||
dllOverrides: g.dllOverrides || [],
|
||||
disableUmu: !!g.disableUmu
|
||||
}))
|
||||
payload.appOrder = games.map(g => g.id)
|
||||
}
|
||||
if (cats.has('prefixes')) {
|
||||
const defs = prefixes.listPrefixes()
|
||||
|
|
@ -291,6 +328,14 @@ function createPusher(api) {
|
|||
// typing (a rejected code is never stored)
|
||||
rememberDevice(device.id || deviceId, code, device.name)
|
||||
|
||||
// Hashing a big unit happens here, before a single byte is sent: without a
|
||||
// sign of life the other side would see minutes of silence and its
|
||||
// "receiving" popup could close while this device is still working.
|
||||
const heartbeat = setInterval(() => {
|
||||
httpJson(device, 'POST', `/plwl/ping?token=${encodeURIComponent(token)}`, { json: {}, timeout: 10000 }).catch(() => {})
|
||||
}, HEARTBEAT_MS)
|
||||
if (heartbeat.unref) heartbeat.unref()
|
||||
|
||||
const summary = newSummary(units.length)
|
||||
const finished = { apps: 0, wines: 0, prefixes: 0 }
|
||||
// bytes the units planned so far will put on the wire: the popup can then
|
||||
|
|
@ -381,7 +426,6 @@ function createPusher(api) {
|
|||
pending.push(entry)
|
||||
plannedBytes += entry.size || 0
|
||||
}
|
||||
allTotal += pending.length
|
||||
// a few files in flight at once: with thousands of small ones the
|
||||
// round trip to the other machine costs more than the bytes do
|
||||
// the files that were already identical never move, so the bar counts
|
||||
|
|
@ -439,6 +483,8 @@ function createPusher(api) {
|
|||
} catch (err) {
|
||||
await end()
|
||||
return fail(String((err && err.message) || err), summary, events)
|
||||
} finally {
|
||||
clearInterval(heartbeat)
|
||||
}
|
||||
return syncResult(true, null, summary, events)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue