mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-18 20:57:05 +02:00
v0.7.5 : le bouton de mise a jour redemarre l'application (Windows)
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.
This commit is contained in:
1 parent
1f773151a6
commit
d72578ea55
8 files changed
+127
-13
No files matched your search
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const Version = "0.7.4"
|
||||
const Version = "0.7.5"
|
||||
|
||||
// Main est le vrai main() du binaire (cmd/ajean ne fait que l'appeler).
|
||||
func Main() {
|
||||
@@ -91,6 +91,10 @@ func Main() {
|
||||
mustExit(cmdUpdate(args))
|
||||
case "where", "paths":
|
||||
mustExit(cmdWhere(args))
|
||||
case restartArg:
|
||||
// Sous-commande interne, absente de l'aide : l'accompagnateur detache qui
|
||||
// attend la fermeture de l'app, migre, puis la relance.
|
||||
mustExit(cmdRestartAfterUpdate(args))
|
||||
case migrateHomeArg:
|
||||
// Sous-commande interne, volontairement absente de l'aide : c'est le
|
||||
// travail delegue au process elevé par elevateForHomeMigration().
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//go:build !windows
|
||||
|
||||
package ajean
|
||||
|
||||
// Pendant non-Windows de sys_restart_windows.go : sous Linux le redemarrage
|
||||
// passe par systemd (restartAfterUpdate), qui sait deja relancer proprement un
|
||||
// service. Il n'y a pas d'accompagnateur a lancer.
|
||||
|
||||
const restartArg = "restart-after-update"
|
||||
|
||||
func scheduleAppRestart() (bool, string) { return false, "" }
|
||||
|
||||
func cmdRestartAfterUpdate([]string) error { return nil }
|
||||
@@ -0,0 +1,93 @@
|
||||
//go:build windows
|
||||
|
||||
package ajean
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Redémarrage propre de l'application après une mise à jour, sous Windows.
|
||||
//
|
||||
// Un process ne peut pas se relancer lui-même : tant qu'il tourne, le nouveau
|
||||
// binaire ne peut pas prendre sa place sur le port, et le dossier de données
|
||||
// reste tenu. On délègue donc à un ACCOMPAGNATEUR détaché — une copie de nous
|
||||
// lancée avec une sous-commande interne — qui attend notre disparition, profite
|
||||
// de cette fenêtre pour terminer la migration, puis relance l'application.
|
||||
//
|
||||
// C'est cette fenêtre qui manquait. Une mise à jour en place laisse AJEAN
|
||||
// tourner, et donc le dossier tenu : le renommage échouait avec « Accès refusé »,
|
||||
// qui sous Windows ne distingue pas un verrou d'un manque de droits. D'où des
|
||||
// postes qui restaient indéfiniment sur l'ancien nom.
|
||||
|
||||
const restartArg = "restart-after-update"
|
||||
|
||||
// scheduleAppRestart lance l'accompagnateur et annonce que le redémarrage est
|
||||
// pris en charge. L'appelant doit ensuite rendre la main puis quitter, pour que
|
||||
// la réponse HTTP parte AVANT que le process ne disparaisse.
|
||||
func scheduleAppRestart() (bool, string) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
cmd := spawnDetached(exe, restartArg, strconv.Itoa(os.Getpid()))
|
||||
if err := cmd.Start(); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
// L'accompagnateur ne doit pas mourir avec nous : spawnDetached l'a déjà
|
||||
// sorti de notre groupe de processus, on se contente de l'oublier.
|
||||
_ = cmd.Process.Release()
|
||||
|
||||
go func() {
|
||||
time.Sleep(1500 * time.Millisecond) // laisser la réponse atteindre le navigateur
|
||||
os.Exit(0)
|
||||
}()
|
||||
return true, "AJEAN redémarre — la page se reconnectera toute seule dans quelques secondes."
|
||||
}
|
||||
|
||||
// cmdRestartAfterUpdate est exécuté par l'accompagnateur détaché.
|
||||
//
|
||||
// Il n'écrit rien à l'écran : personne ne le regarde, il n'a pas de console, et
|
||||
// son seul travail visible est de faire réapparaître l'application.
|
||||
func cmdRestartAfterUpdate(args []string) error {
|
||||
if len(args) > 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)
|
||||
}
|
||||
}
|
||||
@@ -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, ""
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user