From 29bf25cc20a71e96488a638261b62eb0e46b191d Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sat, 8 Aug 2026 22:35:37 +0100 Subject: [PATCH] Refactor Windows installer update process for improved file handling - Introduced new macros `tryRenameProbe` and `waitForInstallDirWritable` to enhance the update process by ensuring files are accessible before replacement. - Updated the `swapUpdateStaging` macro to include checks for file locks and implement a retry mechanism for directory removal, improving reliability during updates. - Enhanced detail logging for better visibility during the update process. --- packaging/windows/installer.nsh | 98 ++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/packaging/windows/installer.nsh b/packaging/windows/installer.nsh index 3dd4900..5dc1415 100644 --- a/packaging/windows/installer.nsh +++ b/packaging/windows/installer.nsh @@ -121,24 +121,94 @@ Var FinalInstDir ${EndIf} !macroend -!macro swapUpdateStaging - !insertmacro progressPhase "Applying update" - !insertmacro progressStatus "Replacing Farm Control with the new version..." - DetailPrint "Replacing Farm Control with staged update..." +; Returns 0 in $R9 when $R8 can be renamed (file unlocked / path free). +; _ID must be unique per insert so labels do not collide. +!macro tryRenameProbe _ID + StrCpy $R9 1 + ${IfNot} ${FileExists} "$R8" + StrCpy $R9 0 + Goto try_rename_probe_done_${_ID} + ${EndIf} + + ClearErrors + Rename "$R8" "$R8.updating" + ${If} ${Errors} + Goto try_rename_probe_done_${_ID} + ${EndIf} + + ; Leave the .updating name — the whole tree is about to be deleted. + StrCpy $R9 0 + try_rename_probe_done_${_ID}: +!macroend + +; Wait until the live install directory is released by the quitting app. +!macro waitForInstallDirWritable + !insertmacro progressStatus "Waiting for Farm Control to release install files..." + DetailPrint "Waiting for install directory to become writable..." StrCpy $R7 0 - swap_delete_retry: - RMDir /r "$FinalInstDir" - ${If} ${FileExists} "$FinalInstDir" - IntOp $R7 $R7 + 1 - ${If} $R7 < 20 - Sleep 500 - Goto swap_delete_retry - ${EndIf} - !insertmacro progressFailure "Could not remove the previous Farm Control installation." - Abort + wait_install_writable: + ; Probe the main binaries — rename fails while the process still holds them. + StrCpy $R8 "$FinalInstDir\bin\FarmControl.exe" + !insertmacro tryRenameProbe farmcontrol + ${If} $R9 != 0 + Goto wait_install_writable_retry ${EndIf} + StrCpy $R8 "$FinalInstDir\bin\launcher.exe" + !insertmacro tryRenameProbe launcher + ${If} $R9 != 0 + Goto wait_install_writable_retry + ${EndIf} + + ; Also probe creating a temp file in the install root. + ClearErrors + FileOpen $R8 "$FinalInstDir\.farmcontrol-write-probe" w + ${If} $R8 == "" + Goto wait_install_writable_retry + ${EndIf} + FileClose $R8 + Delete "$FinalInstDir\.farmcontrol-write-probe" + Goto wait_install_writable_done + + wait_install_writable_retry: + IntOp $R7 $R7 + 1 + ${If} $R7 < 60 + Sleep 500 + Goto wait_install_writable + ${EndIf} + !insertmacro progressFailure "Could not access the previous Farm Control installation." + Abort + + wait_install_writable_done: +!macroend + +!macro swapUpdateStaging + !insertmacro progressPhase "Applying update" + + ${If} ${FileExists} "$FinalInstDir" + !insertmacro waitForInstallDirWritable + + !insertmacro progressStatus "Replacing Farm Control with the new version..." + DetailPrint "Replacing Farm Control with staged update..." + + StrCpy $R7 0 + swap_delete_retry: + RMDir /r "$FinalInstDir" + ${If} ${FileExists} "$FinalInstDir" + IntOp $R7 $R7 + 1 + ${If} $R7 < 20 + Sleep 500 + Goto swap_delete_retry + ${EndIf} + !insertmacro progressFailure "Could not remove the previous Farm Control installation." + Abort + ${EndIf} + ${EndIf} + + !insertmacro progressStatus "Activating new Farm Control installation..." + DetailPrint "Activating staged update..." + ClearErrors Rename "$INSTDIR" "$FinalInstDir" ${If} ${Errors}