diff --git a/.gitignore b/.gitignore index 9891804..7d20209 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist/ build/ __pycache__/ *.spec +*.exe.old +_update.bat diff --git a/GitUpdateChecker.exe b/GitUpdateChecker.exe new file mode 100644 index 0000000..d14ccad Binary files /dev/null and b/GitUpdateChecker.exe differ diff --git a/git_updater.py b/git_updater.py index 3fcf0c4..053d32d 100644 --- a/git_updater.py +++ b/git_updater.py @@ -141,25 +141,80 @@ def check_self_update(): def do_self_update(): - """Pull les mises à jour du programme lui-même. Retourne (ok, message).""" + """ + Met à jour le programme lui-même. + Sur Windows, un .exe en cours d'exécution ne peut pas être écrasé. + Stratégie : renommer l'exe actuel en .old, puis git pull le nouveau. + Retourne (ok, message). + """ exe_dir = str(get_exe_dir()) + is_frozen = getattr(sys, "frozen", False) code, branch, _ = run_git(["rev-parse", "--abbrev-ref", "HEAD"], cwd=exe_dir) if code != 0: return False, "Impossible de lire la branche" - # Restaurer les fichiers locaux modifiés d'abord - run_git(["checkout", "--", "."], cwd=exe_dir) + # Si on tourne en .exe, renommer l'exe actuel pour libérer le fichier + exe_old_path = None + if is_frozen: + exe_path = Path(sys.executable) + exe_old_path = exe_path.with_suffix(".exe.old") + try: + # Supprimer un ancien .old s'il existe + if exe_old_path.exists(): + exe_old_path.unlink() + # Windows permet de renommer un exe en cours d'exécution + exe_path.rename(exe_old_path) + log.info(f"Auto-update: exe renomme {exe_path.name} -> {exe_old_path.name}") + except OSError as e: + log.error(f"Auto-update: impossible de renommer l'exe: {e}") + return False, f"Impossible de renommer l'exe: {e}" + + # Restaurer les fichiers locaux modifiés puis pull + run_git(["checkout", "--", "."], cwd=exe_dir) + code, _, err = run_git(["pull", "origin", branch], cwd=exe_dir) - code, out, err = run_git(["pull", "origin", branch], cwd=exe_dir) if code == 0: - log.info(f"Auto-update: pull OK") - return True, "Mise a jour du programme reussie !\nRedemarre le programme pour appliquer." + log.info("Auto-update: pull OK") + return True, "Mise a jour reussie !\nLe programme va redemarrer." else: + # En cas d'échec, restaurer l'ancien exe + if is_frozen and exe_old_path and exe_old_path.exists(): + try: + exe_old_path.rename(Path(sys.executable)) + log.info("Auto-update: exe restaure apres echec") + except OSError: + pass log.error(f"Auto-update: pull echoue: {err}") return False, f"Erreur pull: {err}" +def relaunch_program(): + """Relance le programme (nouvel exe) et quitte le processus actuel.""" + if getattr(sys, "frozen", False): + exe_path = str(Path(sys.executable)) + log.info(f"Auto-update: relance de {exe_path}") + # Lancer un batch qui attend 1s puis lance le nouvel exe et supprime l'ancien .old + bat_path = str(get_exe_dir() / "_update.bat") + bat_content = ( + f'@echo off\n' + f'timeout /t 1 /nobreak >nul\n' + f'start "" "{exe_path}"\n' + f'del "{exe_path}.old" 2>nul\n' + f'del "%~f0"\n' + ) + with open(bat_path, "w") as f: + f.write(bat_content) + subprocess.Popen( + ["cmd", "/c", bat_path], + creationflags=subprocess.CREATE_NO_WINDOW, + ) + else: + # Mode script : relancer python + log.info("Auto-update: relance du script") + subprocess.Popen([sys.executable, __file__]) + + # ── Configuration ──────────────────────────────────────────────────────────── def get_config_path(): @@ -472,7 +527,8 @@ class App(tk.Tk): """Callback après auto-update.""" if ok: messagebox.showinfo("Auto-update", msg) - log.info("Auto-update appliquee, fermeture") + log.info("Auto-update appliquee, relance...") + relaunch_program() self.destroy() return else: