diff --git a/application/src/electron/main.ts b/application/src/electron/main.ts index 307cda5c..4be685e2 100644 --- a/application/src/electron/main.ts +++ b/application/src/electron/main.ts @@ -197,7 +197,10 @@ async function handleLaunchHooks( if (mainWindow) { registerMainHandlers(mainWindow); await startAddonRuntime(); - await runStartupTasks(mainWindow); + const startupResult = await runStartupTasks(mainWindow); + if (startupResult.shutdownPending) { + return; + } // Load the main app with game ID and hook flags const baseUrl = isDev() @@ -232,7 +235,10 @@ async function launchGameById(gameId: number, wrapperCommand?: string | null) { registerMainHandlers(mainWindow); await startAddonRuntime(); // Run startup tasks first - await runStartupTasks(mainWindow); + const startupResult = await runStartupTasks(mainWindow); + if (startupResult.shutdownPending) { + return; + } // Load the main app with the game ID in the query params // The Svelte frontend will detect this and show the GameLaunchOverlay @@ -592,8 +598,14 @@ function createWindow(options: { gameLaunchMode?: boolean } = {}) { async function startAppFlow(win: BrowserWindow) { // Run startup tasks; splash updates go to the main window + let shutdownPending = false; if (win && !win.isDestroyed()) { - await runStartupTasks(win); + const startupResult = await runStartupTasks(win); + shutdownPending = startupResult.shutdownPending; + } + + if (shutdownPending) { + return; } // Load the main app into the same window (replaces splash) diff --git a/application/src/electron/startup-runner.ts b/application/src/electron/startup-runner.ts index dfb31c32..c5645b54 100644 --- a/application/src/electron/startup-runner.ts +++ b/application/src/electron/startup-runner.ts @@ -1,7 +1,10 @@ import { app, BrowserWindow } from 'electron'; import { join } from 'path'; import type { UpdaterCallbacks } from '@/electron/updater.js'; -import { createDefaultSystemUpdateManager } from '@/electron/system-updater.js'; +import { + createDefaultSystemUpdateManager, + type SystemUpdateResult, +} from '@/electron/system-updater.js'; import { restoreBackup, removeCachedAppUpdates, @@ -91,6 +94,15 @@ export function closeSplashWindow() { } } +export type StartupTasksResult = { + /** When true, an installer update is shutting the app down; do not load the main UI. */ + shutdownPending: boolean; +}; + +function isShutdownPendingFromUpdates(results: SystemUpdateResult[]): boolean { + return results.some((result) => result.updated === true); +} + /** * Runs all pre-launch startup tasks with splash screen feedback. * This includes restoring backups, running migrations, checking for updates, etc. @@ -103,7 +115,8 @@ export function closeSplashWindow() { */ export async function runStartupTasks( mainWindow?: BrowserWindow | null -): Promise { +): Promise { + let shutdownPending = false; try { if (mainWindow && !mainWindow.isDestroyed()) { splashTargetWindow = mainWindow; @@ -165,15 +178,24 @@ export async function runStartupTasks( updateSplashProgress(current, total, speed); }, }; - await createDefaultSystemUpdateManager().updateOnlineSystem( - updaterCallbacks - ); + const updateResults = + await createDefaultSystemUpdateManager().updateOnlineSystem( + updaterCallbacks + ); - // Final status before main window loads - updateSplashStatus('Starting application...'); + shutdownPending = isShutdownPendingFromUpdates(updateResults); + + // Final status before main window loads (skip when installer update will exit) + if (!shutdownPending) { + updateSplashStatus('Starting application...'); + } } finally { - splashTargetWindow = null; - // Ensure splash window is closed if it was created - closeSplashWindow(); + if (!shutdownPending) { + splashTargetWindow = null; + // Ensure splash window is closed if it was created + closeSplashWindow(); + } } + + return { shutdownPending }; } diff --git a/application/src/electron/startup.ts b/application/src/electron/startup.ts index d53f444b..826bdb09 100644 --- a/application/src/electron/startup.ts +++ b/application/src/electron/startup.ts @@ -446,6 +446,20 @@ export async function restoreBackup( return { needsAddonReinstall: false }; } + if (process.platform === 'linux') { + console.log('[backup] Skipping setup backup restore on Linux.'); + try { + rmSync(backupDir, { recursive: true, force: true }); + console.log('[backup] Removed stale Linux update backup.'); + } catch (deleteError: any) { + console.warn( + '[backup] Could not delete stale Linux update backup:', + deleteError.message + ); + } + return { needsAddonReinstall: false }; + } + const flagPath = join(backupDir, 'needs-addon-reinstall.flag'); needsAddonReinstall = existsSync(flagPath); @@ -469,7 +483,7 @@ export async function restoreBackup( return { needsAddonReinstall }; } - // Check for addon reinstall flag (works for both Windows and Linux) + // Check for addon reinstall flag. if (needsAddonReinstall) { needsAddonReinstall = true; console.log('[backup] Addon reinstall flag found'); diff --git a/application/src/electron/updater.ts b/application/src/electron/updater.ts index fc093d42..d6a53e6e 100644 --- a/application/src/electron/updater.ts +++ b/application/src/electron/updater.ts @@ -23,8 +23,8 @@ import { spawn, exec } from 'child_process'; import { createHash } from 'crypto'; import * as zlib from 'zlib'; import * as path from 'path'; -import { __dirname as persistentDataDir } from '@/electron/manager/manager.paths.js'; import { getEffectiveOnlineState } from '@/electron/lib/online.js'; +import { __dirname } from '@/electron/manager/manager.paths.js'; function isDev() { return !app.isPackaged; @@ -52,10 +52,6 @@ if (process.platform === 'linux') { __dirname = './'; } -function getBackupSourceRoot() { - return process.platform === 'linux' ? persistentDataDir : __dirname; -} - /** * Counts the total number of files to backup, excluding specified directories. */ @@ -151,7 +147,7 @@ async function backupFilesAsync( updateStatus: (text: string, subtext?: string) => void, updateProgress: (current: number, total: number, speed: string) => void ): Promise<{ success: boolean; needsAddonReinstall: boolean }> { - const sourceRoot = getBackupSourceRoot(); + const sourceRoot = __dirname; // First, count total files to backup let totalFiles = 0; @@ -591,6 +587,20 @@ async function backupStateForSetupReplacement( updateProgress: (current: number, total: number, speed: string) => void ) { const tempFolder = join(app.getPath('temp'), 'ogi-update-backup'); + + if (process.platform === 'linux') { + console.log('[updater] Skipping setup backup on Linux.'); + try { + rmSync(tempFolder, { recursive: true, force: true }); + } catch (cleanupError: any) { + console.warn( + '[updater] Failed to clean stale Linux update backup:', + cleanupError.message + ); + } + return; + } + updateStatus('Backing up Files', 'Calculating...'); try { @@ -868,8 +878,6 @@ export function checkIfInstallerUpdateAvailable( updateProgress, }); console.log(`[updater] Setup downloaded successfully.`); - console.log(`[updater] Backing up files in update.`); - await backupStateForSetupReplacement(updateStatus, updateProgress); updateStatus('Starting Setup');