From b087db1b4dede4e34c0c140e4c945bc4ef875656 Mon Sep 17 00:00:00 2001 From: olcxja Date: Thu, 1 Oct 2026 20:57:45 +0200 Subject: [PATCH] sync fixes: - file counter - popup text - default settings not syncing --- files/translations/en-us.json | 1 + files/translations/pl.json | 1 + renderer/app.js | 23 ++++++---- src/lansyncpush.js | 82 ++++++++++++++++++++++------------- 4 files changed, 69 insertions(+), 38 deletions(-) diff --git a/files/translations/en-us.json b/files/translations/en-us.json index d4f6f11..4f17846 100644 --- a/files/translations/en-us.json +++ b/files/translations/en-us.json @@ -167,6 +167,7 @@ "lansync.defaults": "Default settings (default Wine and prefix)", "lansync.force": "Force sync all files (skip the hash check and overwrite everything)", "lansync.mirrorWarning": "This device is the source of truth: on the other device, files and items that do not exist here will be deleted.", + "lansync.incomingMirror": "The other device is the source of truth: files and items on this device that do not exist there will be deleted.", "lansync.start": "Start sync", "lansync.progress": "Syncing to {0}…", "lansync.phase.scan": "Preparing {0}", diff --git a/files/translations/pl.json b/files/translations/pl.json index 610db9d..ba62d36 100644 --- a/files/translations/pl.json +++ b/files/translations/pl.json @@ -167,6 +167,7 @@ "lansync.defaults": "Ustawienia domyślne (domyślne Wine i prefiks)", "lansync.force": "Wymuś synchronizację wszystkich plików (pomiń sprawdzanie hashy i nadpisz wszystko)", "lansync.mirrorWarning": "To urządzenie jest źródłem prawdy: na drugim urządzeniu pliki i elementy, których tu nie ma, zostaną usunięte.", + "lansync.incomingMirror": "Drugie urządzenie jest źródłem prawdy: pliki i elementy na tym urządzeniu, których tam nie ma, zostaną usunięte.", "lansync.start": "Rozpocznij synchronizację", "lansync.progress": "Synchronizowanie na {0}…", "lansync.phase.scan": "Przygotowywanie {0}", diff --git a/renderer/app.js b/renderer/app.js index 40ffdd3..cbeee9e 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -1337,13 +1337,13 @@ async function runLanSync(device, code, categories, force) { state.lanPushing = true window.plwl.onLanSyncProgress(data => { if (!active) return - // Every line keeps its value while the sync moves between phases: hashing - // reports its own file counter, copying its own, and the bar follows the - // bytes the whole way through + // Every line keeps its value while the sync moves between phases: the + // file counter is CUMULATIVE across units (it never resets to 0/N with + // the next app) and phases that move no files simply do not touch it - + // only the byte bar follows the phase being worked on phase.textContent = t('lansync.phase.' + data.phase, data.label || '').trim() renderProgressBar(bar, ...barValues(data)) if (data.total > 0) count.textContent = t('lansync.progressFiles', data.done, data.total) - else count.textContent = '' stats.textContent = lanStatsLine(data.items, data.itemsDone, data.totals) bytes.textContent = lanBytesLine(data.bytes, data.totalBytes) }) @@ -1427,7 +1427,9 @@ function onLanIncoming(info) { modal.querySelector('#ls-incoming-title').textContent = t('lansync.incomingTitle', name) modal.querySelector('#ls-incoming-text').textContent = t('lansync.incomingText', name) modal.querySelector('#ls-incoming-what').textContent = parts.length ? parts.join(' · ') : t('lansync.incomingNothing') - modal.querySelector('#ls-incoming-warning').textContent = t('lansync.mirrorWarning') + // the SENDER is the source of truth: the warning is phrased from this + // (receiving) side's point of view, not the send modal's mirrorWarning + modal.querySelector('#ls-incoming-warning').textContent = t('lansync.incomingMirror') modal.querySelector('#ls-incoming-decline').textContent = t('lansync.decline') modal.querySelector('#ls-incoming-accept').textContent = t('lansync.accept') const answer = accept => { @@ -1563,19 +1565,24 @@ function renderReceiving(info) { } // Data received from another device changed the local files: reload every -// list so the views show the merged library instead of stale state. +// list so the views show the merged library instead of stale state. Settings +// too - a sync with "Default settings" rewrote the default wine/prefix on +// disk, and the settings view would keep showing the values from before. async function reloadLocalData() { - const [wines, prefixes, games] = await Promise.all([ + const [wines, prefixes, games, settings] = await Promise.all([ window.plwl.winesList(), window.plwl.prefixesList(), - window.plwl.gamesList() + window.plwl.gamesList(), + window.plwl.settingsGet() ]) state.wines = wines state.prefixes = prefixes state.games = games + state.settings = settings if (state.currentView === 'library') renderLibrary() else if (state.currentView === 'wines') renderWines() else if (state.currentView === 'prefixes') renderPrefixes() + else if (state.currentView === 'settings') renderSettings() } function wireLanSync() { diff --git a/src/lansyncpush.js b/src/lansyncpush.js index 4afd7e9..7f5b7d8 100644 --- a/src/lansyncpush.js +++ b/src/lansyncpush.js @@ -297,31 +297,45 @@ function createPusher(api) { // show "1.2 GB / 3.4 GB" instead of only what has already arrived let plannedBytes = 0 let itemsSeen = 0 + // files uploaded/announced in PREVIOUS units: the popup's file counter is + // cumulative across the whole sync (a per-unit counter resets with every + // next app, which reads like progress going backwards) + let allDone = 0 + let allTotal = 0 // the bar tracks the PHASE being worked on (its own bytes), never a // leftover from the phase before: hashing starts from an empty bar let barBytes = 0 let barTotal = 0 - const snapshot = (phase, unit, done, total) => ({ - stage: phase, - phase, - kind: (unit && unit.kind) || '', - id: (unit && unit.id) || '', - key: (unit && unit.key) || '', - unit: (unit && unit.label) || '', - label: (unit && unit.label) || '', - name: (unit && unit.label) || '', - done, - total, - bytes: summary.bytes, - totalBytes: plannedBytes, - units: summary.units, - unitsDone: itemsSeen, - items: { ...summary.items }, - itemsDone: { ...finished }, - totals: { ...totals }, - barBytes, - barTotalBytes: barTotal - }) + const snapshot = (phase, unit, done, total) => { + const data = { + stage: phase, + phase, + kind: (unit && unit.kind) || '', + id: (unit && unit.id) || '', + key: (unit && unit.key) || '', + unit: (unit && unit.label) || '', + label: (unit && unit.label) || '', + name: (unit && unit.label) || '', + bytes: summary.bytes, + totalBytes: plannedBytes, + units: summary.units, + unitsDone: itemsSeen, + items: { ...summary.items }, + itemsDone: { ...finished }, + totals: { ...totals }, + barBytes, + barTotalBytes: barTotal + } + // only phases that actually move the file counter carry one (the copy + // phase passes absolute, cumulative values); the rest omit it, so the + // popup freezes the last count instead of jumping backwards while this + // unit is still being scanned or compared + if (Number.isFinite(done) && Number.isFinite(total)) { + data.done = done + data.total = total + } + return data + } // hashing and copying walk thousands of files: the state is snapshotted on // every file but forwarded a few times per second (phase changes always // go through, so nothing on screen ever jumps backwards) @@ -342,13 +356,15 @@ function createPusher(api) { itemsSeen += 1 barBytes = 0 barTotal = 0 - emit('scan', unit, 0, 0, true) + emit('scan', unit, null, null, true) const entries = await buildManifest(roots, !!force, (done, total, hashedBytes, totalBytes) => { barBytes = hashedBytes barTotal = totalBytes - emit('hash', unit, done, total) + // the file counter is not touched here: until the plan comes back + // nobody knows how many of this unit's files will actually move + emit('hash', unit, null, null) }) - emit('plan', unit, 0, 0, true) + emit('plan', unit, null, null, true) const plan = await planUnit(device, token, { ...unit, roots }, entries, !!force, { totals }) if (!plan.data || !plan.data.ok) throw new Error((plan.data && plan.data.error) || `http-${plan.status}`) const upload = new Set(plan.data.upload || []) @@ -365,6 +381,7 @@ 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 @@ -372,7 +389,7 @@ function createPusher(api) { if (pending.length) { barBytes = 0 barTotal = pending.reduce((sum, entry) => sum + (entry.size || 0), 0) - emit('copy', unit, 0, pending.length, true) + emit('copy', unit, allDone, allTotal + pending.length, true) } let failed = null let done = 0 @@ -387,12 +404,17 @@ function createPusher(api) { summary.uploaded++ summary.bytes += entry.size || 0 barBytes += entry.size || 0 - emit('copy', unit, done, pending.length) + emit('copy', unit, allDone + done, allTotal + pending.length) } if (typeof api.mapLimit === 'function') await api.mapLimit(pending, UPLOAD_CONCURRENCY, send) else for (const entry of pending) await send(entry) if (failed) throw failed - emit('copy', unit, done, pending.length, true) + emit('copy', unit, allDone + done, allTotal + pending.length, true) + // this unit's files count towards the running totals only once they + // are really on the wire - the counter holds there while the receiver + // prunes and the next unit is scanned/compared + allDone += pending.length + allTotal += pending.length const commit = await httpJson(device, 'POST', `/plwl/unit/commit?token=${encodeURIComponent(token)}&unit=${encodeURIComponent(unit.key)}`, { json: {} }) if (commit.data && commit.data.deleted) { summary.deleted += commit.data.deleted @@ -402,9 +424,9 @@ function createPusher(api) { // a finished unit leaves a finished bar behind, even one that sent none barTotal = barTotal || 1 barBytes = barTotal - emit('commit', unit, 0, 0, true) + emit('commit', unit, null, null, true) } - emit('registry', null, 0, 0, true) + emit('registry', null, null, null, true) const applied = await httpJson(device, 'POST', `/plwl/apply?token=${encodeURIComponent(token)}`, { json: buildRegistryPayload([...cats]), timeout: 60000 }) if (!applied.data || !applied.data.ok) throw new Error((applied.data && applied.data.error) || `http-${applied.status}`) for (const key of ['apps', 'wines', 'prefixes']) { @@ -412,7 +434,7 @@ function createPusher(api) { if (deleted) summary[key].deleted += deleted } if (applied.data.deleted) summary.deleted += applied.data.deleted - emit('done', null, summary.uploaded, summary.uploaded, true) + emit('done', null, allDone, allTotal, true) await end() } catch (err) { await end()