4 Commits

Author SHA1 Message Date
02350cfca1 chore(release): bump version to 1.6.4
All checks were successful
Desktop CI / tests (push) Successful in 13s
Desktop Release / release (push) Successful in 1m51s
2026-02-15 19:44:05 +03:00
68fa841857 feat(update): stage 3 rollback on failed apply/start 2026-02-15 19:43:15 +03:00
bca9007463 chore(release): bump version to 1.6.3
All checks were successful
Desktop CI / tests (push) Successful in 12s
Desktop Release / release (push) Successful in 1m54s
2026-02-15 18:52:06 +03:00
52b1301982 fix(update): wait for app exit before file replacement
All checks were successful
Desktop CI / tests (push) Successful in 12s
Desktop Release / release (push) Successful in 13s
2026-02-15 18:51:00 +03:00
3 changed files with 34 additions and 9 deletions

View File

@@ -1 +1 @@
APP_VERSION = "1.6.2" APP_VERSION = "1.6.4"

37
main.py
View File

@@ -835,7 +835,7 @@ class VkChatManager(QMainWindow):
return candidate return candidate
return extracted_dir return extracted_dir
def _build_update_script(self, app_dir, source_dir, exe_name): def _build_update_script(self, app_dir, source_dir, exe_name, target_pid):
script_path = os.path.join(tempfile.gettempdir(), "anabasis_apply_update.cmd") script_path = os.path.join(tempfile.gettempdir(), "anabasis_apply_update.cmd")
script_lines = [ script_lines = [
"@echo off", "@echo off",
@@ -843,14 +843,35 @@ class VkChatManager(QMainWindow):
f"set APP_DIR={app_dir}", f"set APP_DIR={app_dir}",
f"set SRC_DIR={source_dir}", f"set SRC_DIR={source_dir}",
f"set EXE_NAME={exe_name}", f"set EXE_NAME={exe_name}",
"timeout /t 2 /nobreak >nul", f"set TARGET_PID={target_pid}",
"robocopy \"%SRC_DIR%\" \"%APP_DIR%\" /E /NFL /NDL /NJH /NJS /NP /R:3 /W:1 >nul", "set BACKUP_DIR=%TEMP%\\anabasis_backup_%RANDOM%%RANDOM%",
":wait_for_exit",
"tasklist /FI \"PID eq %TARGET_PID%\" | find \"%TARGET_PID%\" >nul",
"if %ERRORLEVEL% EQU 0 (",
" timeout /t 1 /nobreak >nul",
" goto :wait_for_exit",
")",
"timeout /t 1 /nobreak >nul",
"mkdir \"%BACKUP_DIR%\" >nul 2>&1",
"robocopy \"%APP_DIR%\" \"%BACKUP_DIR%\" /E /NFL /NDL /NJH /NJS /NP /R:6 /W:2 >nul",
"set RC=%ERRORLEVEL%", "set RC=%ERRORLEVEL%",
"if %RC% GEQ 8 goto :copy_error", "if %RC% GEQ 8 goto :backup_error",
"robocopy \"%SRC_DIR%\" \"%APP_DIR%\" /E /NFL /NDL /NJH /NJS /NP /R:12 /W:2 >nul",
"set RC=%ERRORLEVEL%",
"if %RC% GEQ 8 goto :rollback",
"start \"\" \"%APP_DIR%\\%EXE_NAME%\"", "start \"\" \"%APP_DIR%\\%EXE_NAME%\"",
"timeout /t 2 /nobreak >nul",
"tasklist /FI \"IMAGENAME eq %EXE_NAME%\" | find /I \"%EXE_NAME%\" >nul",
"if %ERRORLEVEL% NEQ 0 goto :rollback",
"rmdir /S /Q \"%BACKUP_DIR%\" >nul 2>&1",
"exit /b 0", "exit /b 0",
":copy_error", ":rollback",
"echo Auto-update failed with code %RC% > \"%APP_DIR%\\update_error.log\"", "robocopy \"%BACKUP_DIR%\" \"%APP_DIR%\" /E /NFL /NDL /NJH /NJS /NP /R:6 /W:2 >nul",
"start \"\" \"%APP_DIR%\\%EXE_NAME%\"",
"echo Auto-update failed. Rollback executed. > \"%APP_DIR%\\update_error.log\"",
"exit /b 2",
":backup_error",
"echo Auto-update failed during backup. Code %RC% > \"%APP_DIR%\\update_error.log\"",
"exit /b %RC%", "exit /b %RC%",
] ]
with open(script_path, "w", encoding="utf-8", newline="\r\n") as f: with open(script_path, "w", encoding="utf-8", newline="\r\n") as f:
@@ -892,7 +913,7 @@ class VkChatManager(QMainWindow):
app_exe = sys.executable app_exe = sys.executable
app_dir = os.path.dirname(app_exe) app_dir = os.path.dirname(app_exe)
exe_name = os.path.basename(app_exe) exe_name = os.path.basename(app_exe)
script_path = self._build_update_script(app_dir, source_dir, exe_name) script_path = self._build_update_script(app_dir, source_dir, exe_name, os.getpid())
creation_flags = 0 creation_flags = 0
if hasattr(subprocess, "CREATE_NEW_PROCESS_GROUP"): if hasattr(subprocess, "CREATE_NEW_PROCESS_GROUP"):
@@ -911,7 +932,7 @@ class VkChatManager(QMainWindow):
"Обновление запущено", "Обновление запущено",
"Обновление скачано. Приложение будет перезапущено.", "Обновление скачано. Приложение будет перезапущено.",
) )
QTimer.singleShot(150, QApplication.instance().quit) QApplication.instance().quit()
return True return True
except Exception as e: except Exception as e:
self._log_event("auto_update_failed", str(e), level="ERROR") self._log_event("auto_update_failed", str(e), level="ERROR")

View File

@@ -46,6 +46,10 @@ class AuthReloginSmokeTests(unittest.TestCase):
self.assertIn('message_box.addButton("Обновить сейчас", QMessageBox.AcceptRole)', self.source) self.assertIn('message_box.addButton("Обновить сейчас", QMessageBox.AcceptRole)', self.source)
self.assertIn("def _start_auto_update(self, download_url, latest_version, checksum_url=\"\", download_name=\"\"):", self.source) self.assertIn("def _start_auto_update(self, download_url, latest_version, checksum_url=\"\", download_name=\"\"):", self.source)
self.assertIn("def _verify_update_checksum(self, zip_path, checksum_url, download_name):", self.source) self.assertIn("def _verify_update_checksum(self, zip_path, checksum_url, download_name):", self.source)
self.assertIn("def _build_update_script(self, app_dir, source_dir, exe_name, target_pid):", self.source)
self.assertIn("set TARGET_PID=", self.source)
self.assertIn("set BACKUP_DIR=", self.source)
self.assertIn(":rollback", self.source)
if __name__ == "__main__": if __name__ == "__main__":