From c62820dc3ef2491bd301b903ab292b88ec6b1ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathis=20Dr=C3=B6ge?= Date: Tue, 26 Mar 2024 23:19:35 +0100 Subject: [PATCH] [Feat] Improve the "Move Game" experience a bit (#3648) --- public/locales/en/gamepage.json | 2 +- src/backend/utils.ts | 133 +++++++++++------- .../Game/GamePage/components/GameStatus.tsx | 11 +- 3 files changed, 85 insertions(+), 61 deletions(-) diff --git a/public/locales/en/gamepage.json b/public/locales/en/gamepage.json index 859cfb6051..e0687aeb56 100644 --- a/public/locales/en/gamepage.json +++ b/public/locales/en/gamepage.json @@ -238,7 +238,7 @@ "installing": "Installing", "launching": "Launching", "moving": "Moving Installation, please wait", - "moving-files": "Moving file '{{file}}': {{percent}} ", + "moving-files": "Moving file '{{file}}': {{percent}}%", "notinstalled": "This game is not installed", "notSupported": "Not supported", "notSupportedGame": "Not Supported", diff --git a/src/backend/utils.ts b/src/backend/utils.ts index b5d930736c..3de38e704c 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -996,41 +996,33 @@ export async function moveOnWindows( newInstallPath = join(newInstallPath, basename(install_path)) let currentFile = '' - let currentPercent = '' // move using robocopy and show progress of the current file being copied const { code, stderr } = await spawnAsync( 'robocopy', - [install_path, newInstallPath, '/MOVE', '/MIR'], + [install_path, newInstallPath, '/MOVE', '/MIR', '/NJH', '/NJS', '/NDL'], { stdio: 'pipe' }, (data) => { - data = data.replaceAll(/\s/g, ' ') - - const match = data.split(' ').filter(Boolean) - // current percentage - const percent = match.filter((m) => m.includes('%'))[0] - // current file - const file = match[match.length - 1] - if (percent) { - currentPercent = percent - } - - if (file && file.includes('.') && !file.includes('%')) { - currentPercent = '0%' - currentFile = file - } - - if (match) { - sendFrontendMessage(`progressUpdate-${gameInfo.app_name}`, { - appName: gameInfo.app_name, - runner: gameInfo.runner, - status: 'moving', - progress: { - percent: currentPercent, - file: currentFile - } - }) - } + let percent = 0 + const percentMatch = data.match(/(\d+)(?:\.\d+)?%/)?.[1] + if (percentMatch) percent = Number(percentMatch) + + const filenameMatch = data.match(/([\w.:\\]+)$/)?.[1] + if (filenameMatch) currentFile = filenameMatch + + sendFrontendMessage(`progressUpdate-${gameInfo.app_name}`, { + appName: gameInfo.app_name, + runner: gameInfo.runner, + status: 'moving', + progress: { + percent, + file: currentFile, + // FIXME: Robocopy does not report bytes moved / an ETA, so we have to + // leave these blank for now + bytes: '', + eta: '' + } + }) } ) if (code !== 0) { @@ -1058,7 +1050,6 @@ export async function moveOnUnix( const destination = join(newInstallPath, basename(install_path)) let currentFile = '' - let currentPercent = '' let rsyncExists = false try { @@ -1070,38 +1061,72 @@ export async function moveOnUnix( if (rsyncExists) { const origin = install_path + '/' logInfo( - `moving command: rsync -az --progress ${origin} ${destination} `, + `moving command: rsync --archive --compress --no-human-readable --remove-source-files --info=name,progress ${origin} ${destination} `, LogPrefix.Backend ) const { code, stderr } = await spawnAsync( 'rsync', - ['-az', '--progress', origin, destination], + [ + '--archive', + '--compress', + '--no-human-readable', + '--remove-source-files', + '--info=name,progress', + origin, + destination + ], { stdio: 'pipe' }, (data) => { - const split = - data - .split('\n') - .find((d) => d.includes('/') && !d.includes('%')) - ?.split('/') || [] - const file = split.at(-1) || '' - - if (file) { - currentFile = file + let percent = 0 + let eta = '' + let bytes = '0' + + // Multiple output lines might be buffered into a single `data`, so + // we have to iterate over every line (we can't just look at the last + // one since that might skip new files) + for (const outLine of data.trim().split('\n')) { + // Rsync outputs either the file currently being transferred or a + // progress report. To know which one of those `outLine` is, we check + // if it includes a %, :, and starts with a space + // If all of these aren't the case, it's *most likely* a filename + // FIXME: This is pretty hacky, but I don't see an obvious way to + // "divide" the two output types other than that + const isFilenameOutput = + !outLine.includes('%') && + !outLine.includes(':') && + !outLine.startsWith(' ') + + if (isFilenameOutput) { + // If we have a filename output, set `lastFile` and reset all + // other metrics. Either there'll be a progress update in the next + // line of `data`, or we've just started copying and thus start at 0 + currentFile = outLine + percent = 0 + eta = '' + bytes = '0' + } else { + // If we got the progress update, try to read out the bytes, ETA and + // percent + const bytesMatch = outLine.match(/^\s+(\d+)/)?.[1] + const etaMatch = outLine.match(/(\d+:\d{2}:\d{2})/)?.[1] + const percentMatch = outLine.match(/(\d+)%/)?.[1] + if (bytesMatch) bytes = getFileSize(Number(bytesMatch)) + if (etaMatch) eta = etaMatch + if (percentMatch) percent = Number(percentMatch) + } } - const percent = data.match(/(\d+)%/) - if (percent) { - currentPercent = percent[0] - sendFrontendMessage(`progressUpdate-${gameInfo.app_name}`, { - appName: gameInfo.app_name, - runner: gameInfo.runner, - status: 'moving', - progress: { - percent: currentPercent, - file: currentFile - } - }) - } + sendFrontendMessage(`progressUpdate-${gameInfo.app_name}`, { + appName: gameInfo.app_name, + runner: gameInfo.runner, + status: 'moving', + progress: { + percent, + eta, + bytes, + file: currentFile + } + }) } ) if (code !== 1) { diff --git a/src/frontend/screens/Game/GamePage/components/GameStatus.tsx b/src/frontend/screens/Game/GamePage/components/GameStatus.tsx index 03e1643413..be06f1cfd7 100644 --- a/src/frontend/screens/Game/GamePage/components/GameStatus.tsx +++ b/src/frontend/screens/Game/GamePage/components/GameStatus.tsx @@ -56,13 +56,12 @@ const GameStatus = ({ gameInfo, progress, handleUpdate, hasUpdate }: Props) => { } if (is.moving) { - if (file && percent) { - return `${t( + if (file && percent !== undefined) { + return t( 'status.moving-files', - `Moving file '{{file}}': {{percent}} `, - { file, percent } - )} - ` + `Moving file '{{file}}': {{percent}}%`, + { file, percent: percent.toFixed(0) } + ) } return `${t('status.moving', 'Moving Installation, please wait')} ...`