From d72578ea550d7966bbac88bda77a4bd536f34907 Mon Sep 17 00:00:00 2001 From: nathaninline Date: Tue, 4 Aug 2026 17:31:06 +0200 Subject: [PATCH] v0.7.5 : le bouton de mise a jour redemarre l'application (Windows) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un process ne peut pas se relancer lui-meme : tant qu'il tourne, il occupe le port et tient son propre dossier de donnees. On delegue donc a un accompagnateur detache — une copie de nous lancee avec une sous-commande interne — qui attend notre disparition, migre pendant cette fenetre, puis relance l'application. C'est cette fenetre qui manquait depuis le debut. Une mise a jour en place laisse AJEAN tourner, donc le dossier tenu, donc le renommage echoue avec « Acces refuse » — que Windows renvoie aussi bien pour un verrou que pour un manque de droits. D'ou des postes bloques sur l'ancien nom malgre 0.7.1 a 0.7.4. Le retour terrain le confirme : la migration n'a abouti qu'en lancant l'exe telecharge, seul chemin qui arretait les instances avant de migrer. installedExePath() est relu APRES la migration avant de relancer : le binaire installe vit dans le dossier de donnees, il change donc de place avec lui, et relancer l'ancien chemin echouerait. Repli sur le binaire courant si la cible a disparu — mieux vaut relancer quelque chose que laisser l'utilisateur sans application. L'attente est bornee a 30 s. Un depassement n'interrompt pas la suite : la migration echouera peut-etre et sera retentee plus tard, alors que rester coince laisserait l'utilisateur sans rien. Verifie de bout en bout sur banc d'essai : l'accompagnateur attend la fermeture du process cible, se termine proprement, et l'application est bien relancee et ressert l'UI. --- RELEASE_NOTES.md | 14 ++-- cmd/ajean/resource_windows_amd64.syso | Bin 7544 -> 7544 bytes cmd/ajean/resource_windows_arm64.syso | Bin 7544 -> 7544 bytes cmd/ajean/versioninfo.json | 6 +- internal/ajean/run.go | 6 +- internal/ajean/sys_restart_other.go | 13 ++++ internal/ajean/sys_restart_windows.go | 93 ++++++++++++++++++++++++++ internal/ajean/sys_update.go | 8 ++- 8 files changed, 127 insertions(+), 13 deletions(-) create mode 100644 internal/ajean/sys_restart_other.go create mode 100644 internal/ajean/sys_restart_windows.go diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4f63fe3..6e6ab07 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,14 +1,12 @@ -## Le renommage du dossier se fait maintenant à la mise à jour +## Le bouton « mettre à jour » redémarre AJEAN tout seul -Les versions 0.7.1 à 0.7.3 n'y parvenaient pas sur les postes Windows sans droits administrateur : le code prévu pour demander l'autorisation n'était jamais atteint. Il ne se déclenchait qu'en relançant l'installation ou en retéléchargeant le programme — deux gestes que personne ne fait pour une mise à jour. +Sous Windows, il fallait jusqu'ici quitter puis relancer l'application à la main pour que la mise à jour prenne effet. C'est fait automatiquement : la page se reconnecte d'elle-même au bout de quelques secondes. -`ajean update`, et le bouton de mise à jour de l'interface, s'en chargent désormais. L'autorisation Windows est demandée à ce moment-là, une fois, et le dossier `C:\ProgramData\jean` devient `C:\ProgramData\ajean`. +Ce redémarrage règle aussi, au passage, le renommage du dossier de données. Tant qu'AJEAN tourne, il tient son propre dossier, et `C:\ProgramData\jean` ne peut pas devenir `C:\ProgramData\ajean` — c'est ce qui laissait certains postes indéfiniment sur l'ancien nom malgré les versions précédentes. Le redémarrage ouvre le court instant où plus rien ne le tient, et la migration s'y termine. -Le raccourci du menu Démarrer et la commande `ajean` sont repointés dans la foulée. Le programme installé vit à l'intérieur du dossier de données : sans cette étape, le déplacement aurait laissé un raccourci mort et une commande introuvable. +Le raccourci du menu Démarrer et la commande `ajean` suivent le déplacement, puisque le programme installé vit à l'intérieur de ce dossier. -Refuser l'autorisation reste sans conséquence : AJEAN continue sur son dossier actuel, et la question reviendra à la prochaine mise à jour. - -Si vous êtes déjà sur la dernière version et toujours sur l'ancien dossier, la migration se fera à la mise à jour suivante. +Si quelque chose empêche le renommage, il ne se passe rien : AJEAN redémarre sur son dossier actuel et réessaiera plus tard. Rien n'est jamais copié ni supprimé. ## Mise à jour @@ -16,6 +14,6 @@ Si vous êtes déjà sur la dernière version et toujours sur l'ancien dossier, ajean update ``` -Sous Windows, télécharger `ajean-windows-amd64.exe` depuis cette page et le lancer fait le même travail. +Ou le bouton de l'interface, qui se charge désormais de tout. Les binaires restent publiés sous leurs deux noms, `ajean-*` et `jean-*`, le temps que le parc bascule. diff --git a/cmd/ajean/resource_windows_amd64.syso b/cmd/ajean/resource_windows_amd64.syso index cb8da480d60a026ad060c123de7f64abc05d03d8..4bc2170c5fc010049bab1e0327bc6f2dc4cec441 100644 GIT binary patch delta 35 ocmexi^}}idmn0`E13Lo 0 { + if pid, err := strconv.Atoi(args[0]); err == nil { + waitForExit(pid, 30*time.Second) + } + } + // L'application vient de se fermer : plus rien ne tient le dossier de + // données, le renommage peut enfin aboutir. postUpdateMigrate ne fait rien + // s'il n'y a pas lieu, et repointe raccourci et PATH s'il a migré. + postUpdateMigrate() + + // installedExePath() est relu APRÈS la migration : le binaire a pu changer + // de dossier avec elle, et relancer l'ancien chemin échouerait. + target := installedExePath() + if _, err := os.Stat(target); err != nil { + // La cible a disparu : mieux vaut relancer ce qu'on a que rien du tout. + if self, e := os.Executable(); e == nil { + target = self + } + } + if !launch(target) { + return fmt.Errorf("relance de %s impossible", target) + } + return nil +} + +// waitForExit attend la fin du process pid, sans dépasser le délai imparti. +// Un dépassement n'est pas bloquant : on poursuit quand même, quitte à ce que +// la migration échoue et soit retentée plus tard. Rester coincé ici serait pire +// — l'utilisateur se retrouverait sans application du tout. +func waitForExit(pid int, timeout time.Duration) { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + if !pidAlive(pid) { + time.Sleep(500 * time.Millisecond) // laisser les handles se fermer + return + } + time.Sleep(200 * time.Millisecond) + } +} diff --git a/internal/ajean/sys_update.go b/internal/ajean/sys_update.go index 6a701bf..ab249a8 100644 --- a/internal/ajean/sys_update.go +++ b/internal/ajean/sys_update.go @@ -405,7 +405,6 @@ func handleUpdateApply(w http.ResponseWriter, r *http.Request) { sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()}) return } - postUpdateMigrate() restarting, msg := restartAfterUpdate() if !restarting { msg = restartHintText() @@ -423,6 +422,13 @@ func handleUpdateApply(w http.ResponseWriter, r *http.Request) { // et l'UI se reconnecte toute seule (boucle de reconnexion du flux). Renvoie // (déclenché, message). Non-serveur (poste client, Windows) → (false, ""). func restartAfterUpdate() (bool, string) { + // Windows : pas de superviseur pour nous relancer, on delegue a un + // accompagnateur detache (voir sys_restart_windows.go). C'est aussi ce qui + // cree la fenetre ou plus rien ne tient le dossier de donnees, donc ou la + // migration peut aboutir. + if runtime.GOOS == "windows" { + return scheduleAppRestart() + } if runtime.GOOS != "linux" || !linkServiceActive() { return false, "" }