diff --git a/build/installer.nsh b/build/installer.nsh index 1ac6f25..98ca94e 100644 --- a/build/installer.nsh +++ b/build/installer.nsh @@ -4,3 +4,42 @@ IfFileExists "$INSTDIR\resources\app.asar.unpacked\vendor\redist\vc_redist.x64.exe" 0 +2 ExecWait '"$INSTDIR\resources\app.asar.unpacked\vendor\redist\vc_redist.x64.exe" /install /quiet /norestart' $0 !macroend + +; --------------------------------------------------------------------------------------------------- +; Replaces electron-builder's "is the app running?" check (customCheckAppRunning). Runs at the start of +; installing, BEFORE the old version is removed and before any file is copied. +; +; The stock check only looks for "Bible App.exe". But the app also starts programs from its own folder +; - the local AI server (llama-server.exe), speech (whisper), the upscaler - and if the app is closed +; hard, those are left running. They hold their files open, so copying the new version fails and the +; installer says "Bible App cannot be closed. Please close it manually and click Retry" even though +; the app is plainly closed, and Retry can never succeed. So: +; 1. ask the app to close (it then stops the AI server itself), and give it a moment; +; 2. force-close anything named Bible App.exe that is still there; +; 3. force-close the app's own helper programs by name (plain taskkill, nothing to go wrong); +; 4. as a backstop, force-close EVERY program running from this install folder, whatever its name. +; Step 4 matches by folder, via WMI (works from this 32-bit installer on 64-bit Windows, where a +; 32-bit process cannot read a 64-bit process's path). The folder travels in an environment variable +; so a quote or space in it can never break the command, and it refuses to act on a bare drive. +; No question is asked first: installing this version replaces the app, which is what was asked for. +; --------------------------------------------------------------------------------------------------- +!macro customCheckAppRunning + DetailPrint `Closing "${PRODUCT_NAME}"...` + nsExec::Exec `"$SYSDIR\cmd.exe" /c taskkill /im "${APP_EXECUTABLE_FILENAME}" /fi "USERNAME eq %USERNAME%"` + Pop $0 + Sleep 2500 + nsExec::Exec `"$SYSDIR\cmd.exe" /c taskkill /f /t /im "${APP_EXECUTABLE_FILENAME}" /fi "USERNAME eq %USERNAME%"` + Pop $0 + nsExec::Exec `"$SYSDIR\cmd.exe" /c taskkill /f /t /im llama-server.exe /im whisper-cli.exe /im realesrgan-ncnn-vulkan.exe /fi "USERNAME eq %USERNAME%"` + Pop $0 + System::Call 'Kernel32::SetEnvironmentVariable(t "BIBLEAPP_INSTDIR", t "$INSTDIR")' + StrCpy $1 "$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" + ${If} ${FileExists} "$WINDIR\Sysnative\WindowsPowerShell\v1.0\powershell.exe" + StrCpy $1 "$WINDIR\Sysnative\WindowsPowerShell\v1.0\powershell.exe" + ${EndIf} + StrCpy $2 `"$1" -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "$$r = $$env:BIBLEAPP_INSTDIR.TrimEnd('\'); if ($$r.Split('\').Count -lt 2) { exit }; $$d = $$r + '\'; Get-CimInstance Win32_Process | Where-Object { $$_.ExecutablePath -and $$_.ExecutablePath.StartsWith($$d, [StringComparison]::OrdinalIgnoreCase) } | ForEach-Object { Stop-Process -Id $$_.ProcessId -Force -ErrorAction SilentlyContinue }"` + DetailPrint `Closing anything else still running from "${PRODUCT_NAME}"'s folder...` + nsExec::Exec $2 + Pop $0 + Sleep 1000 +!macroend diff --git a/main.cjs b/main.cjs index 2b05063..14fb970 100644 --- a/main.cjs +++ b/main.cjs @@ -355,6 +355,9 @@ async function createWindow () { quit: () => { try { app.quit(); } catch (_) {} }, runInstaller: (installerPath) => { // Launch the installer detached, then quit so it can replace the running app. + // Stop the local AI server FIRST: it runs from the install folder, and one left running holds + // its files open, so the installer cannot copy the new version over them ("cannot be closed"). + try { stopLlamaServer(); } catch (_) {} try { const { spawn } = require('child_process'); const child = spawn(installerPath, [], { detached: true, stdio: 'ignore', windowsHide: false });