Update-Fix: Installer beendet sich nicht mehr selbst, Neustart in die neue Version
Ursache: Der Installer beendete laufende Playtube-Prozesse per "taskkill /F /T". Der Updater startet ihn aus Playtube.exe heraus, er ist also ein Kindprozess - solange Playtube noch beendet wurde, schoss "/T" den Installer selbst mit ab, bevor er etwas installiert hatte (kein Kopieren, kein Neustart, nur der Download blieb im Temp-Ordner). - playtube.iss: taskkill ohne /T; Setup wartet per /WAITPID=<PID> (max. 20 s) darauf, dass sich die alte App selbst beendet; uebrig gebliebene QtWebEngine-Prozesse aus dem Installationsordner werden gezielt nach Pfad beendet - updater: uebergibt /WAITPID und /LOG (Setup-Protokoll nach %TEMP%); robocopy im ZIP-Fallback mit /R:3 /W:2, damit gesperrte Dateien das Update nicht endlos haengen - mainwindow: hartes Beenden nach 8 s, falls Qt/QtWebEngine beim Beenden haengt (sonst wartet der Update-Helfer ewig und die alte Version bleibt laufen)
This commit is contained in:
+40
-6
@@ -98,27 +98,61 @@ begin
|
|||||||
Result := ExpandConstant('{param:RELAUNCH|0}') = '1';
|
Result := ExpandConstant('{param:RELAUNCH|0}') = '1';
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure StopRunningPlaytube();
|
function PowerShellPath(): String;
|
||||||
|
begin
|
||||||
|
Result := ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure WaitForOldInstance();
|
||||||
|
var
|
||||||
|
OldPid, ResultCode: Integer;
|
||||||
|
begin
|
||||||
|
{ Playtubes Updater startet Setup aus der laufenden App heraus und uebergibt deren
|
||||||
|
Prozess-ID (/WAITPID=...). Setup wartet, bis sich diese Instanz selbst beendet hat,
|
||||||
|
statt sie hart abzuschiessen (max. 20 s). Ohne Angabe (Setup per Doppelklick) passiert
|
||||||
|
hier nichts. }
|
||||||
|
OldPid := StrToIntDef(ExpandConstant('{param:WAITPID|0}'), 0);
|
||||||
|
if OldPid > 0 then
|
||||||
|
Exec(PowerShellPath(),
|
||||||
|
'-NoProfile -NonInteractive -WindowStyle Hidden -Command "Wait-Process -Id ' + IntToStr(OldPid) + ' -Timeout 20 -ErrorAction SilentlyContinue"',
|
||||||
|
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure StopLeftoverProcesses();
|
||||||
var
|
var
|
||||||
ResultCode: Integer;
|
ResultCode: Integer;
|
||||||
begin
|
begin
|
||||||
{ /T beendet auch die Kindprozesse (QtWebEngine); der Hilfsprozess wird zusaetzlich
|
{ WICHTIG: bewusst OHNE "/T" (Prozessbaum mitbeenden). Der Updater startet Setup aus
|
||||||
einzeln beendet, falls er als Waise ueberlebt hat. }
|
Playtube.exe heraus - Setup ist also ein Kindprozess. Solange Playtube noch beendet
|
||||||
Exec(ExpandConstant('{sys}\taskkill.exe'), '/F /T /IM {#AppExeName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
wird, wuerde "/T" den Installer selbst mit abschiessen, bevor er etwas installiert hat
|
||||||
|
(so ging frueher ein Update still verloren: kein Kopieren, kein Neustart). }
|
||||||
|
Exec(ExpandConstant('{sys}\taskkill.exe'), '/F /IM {#AppExeName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||||
Exec(ExpandConstant('{sys}\taskkill.exe'), '/F /IM {#AppHelperName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
Exec(ExpandConstant('{sys}\taskkill.exe'), '/F /IM {#AppHelperName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||||
|
|
||||||
|
{ QtWebEngine-Kindprozesse, die nach dem Ende von Playtube noch aus dem
|
||||||
|
Installationsordner laufen, wuerden Dateien sperren - gezielt nach Pfad beenden
|
||||||
|
(nicht per Namen: QtWebEngineProcess.exe heisst auch bei anderen Qt-Programmen). }
|
||||||
|
if DirExists(ExpandConstant('{app}')) then
|
||||||
|
Exec(PowerShellPath(),
|
||||||
|
'-NoProfile -NonInteractive -WindowStyle Hidden -Command "$d = ''' + ExpandConstant('{app}') + '\''; ' +
|
||||||
|
'Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -and $_.ExecutablePath.StartsWith($d, [StringComparison]::OrdinalIgnoreCase) } | ' +
|
||||||
|
'ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"',
|
||||||
|
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||||
|
|
||||||
{ Dateisperren freigeben lassen, bevor kopiert wird. }
|
{ Dateisperren freigeben lassen, bevor kopiert wird. }
|
||||||
Sleep(800);
|
Sleep(800);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||||
begin
|
begin
|
||||||
StopRunningPlaytube();
|
WaitForOldInstance();
|
||||||
|
StopLeftoverProcesses();
|
||||||
Result := '';
|
Result := '';
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function InitializeUninstall(): Boolean;
|
function InitializeUninstall(): Boolean;
|
||||||
begin
|
begin
|
||||||
StopRunningPlaytube();
|
StopLeftoverProcesses();
|
||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -28,6 +29,15 @@ from .updater import UpdateChecker, UpdateInstaller
|
|||||||
ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets"
|
ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets"
|
||||||
|
|
||||||
|
|
||||||
|
def _force_exit_after(seconds: float) -> None:
|
||||||
|
"""Beendet den Prozess nach `seconds` hart (os._exit), falls das normale Beenden bis
|
||||||
|
dahin noch nicht durch ist. Der Timer laeuft als Daemon-Thread und stoert das
|
||||||
|
normale, schnelle Beenden nicht."""
|
||||||
|
timer = threading.Timer(seconds, os._exit, args=(0,))
|
||||||
|
timer.daemon = True
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
def __init__(self, config: dict[str, Any]):
|
def __init__(self, config: dict[str, Any]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -359,6 +369,10 @@ class MainWindow(QMainWindow):
|
|||||||
"Installation abgeschlossen. Playtube wird neu gestartet …"
|
"Installation abgeschlossen. Playtube wird neu gestartet …"
|
||||||
)
|
)
|
||||||
self._tray.setToolTip(f"{APP_NAME} – wird neu gestartet …")
|
self._tray.setToolTip(f"{APP_NAME} – wird neu gestartet …")
|
||||||
|
# Haengt sich Qt/QtWebEngine beim Beenden auf, wuerde der Update-Helfer (bzw.
|
||||||
|
# Setup, siehe /WAITPID) ewig auf das Ende dieses Prozesses warten - und die
|
||||||
|
# alte Version bliebe einfach weiterlaufen. Nach 8 s hart beenden.
|
||||||
|
_force_exit_after(8.0)
|
||||||
self._quit()
|
self._quit()
|
||||||
else:
|
else:
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
|
|||||||
+11
-2
@@ -466,7 +466,12 @@ rm -rf "{staging}"
|
|||||||
packaging/playtube.iss) und startet Playtube dank /RELAUNCH=1 danach neu. Die
|
packaging/playtube.iss) und startet Playtube dank /RELAUNCH=1 danach neu. Die
|
||||||
laufende App beendet sich direkt nach diesem Aufruf (siehe MainWindow), muss
|
laufende App beendet sich direkt nach diesem Aufruf (siehe MainWindow), muss
|
||||||
hier also nicht auf Setup warten. Setup wird abgekoppelt gestartet, damit es das
|
hier also nicht auf Setup warten. Setup wird abgekoppelt gestartet, damit es das
|
||||||
Beenden dieses Prozesses ueberlebt."""
|
Beenden dieses Prozesses ueberlebt.
|
||||||
|
|
||||||
|
/WAITPID=<eigene PID>: Setup wartet, bis sich diese App wirklich beendet hat,
|
||||||
|
bevor es Dateien tauscht. /LOG (ohne Pfad): Setup schreibt ein Protokoll nach
|
||||||
|
%TEMP% ("Setup Log ....txt") - so laesst sich ein fehlgeschlagenes Update
|
||||||
|
nachtraeglich nachvollziehen, obwohl die Installation selbst still laeuft."""
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
[
|
[
|
||||||
str(setup_path),
|
str(setup_path),
|
||||||
@@ -475,6 +480,8 @@ rm -rf "{staging}"
|
|||||||
"/NORESTART",
|
"/NORESTART",
|
||||||
"/NOCANCEL",
|
"/NOCANCEL",
|
||||||
"/RELAUNCH=1",
|
"/RELAUNCH=1",
|
||||||
|
f"/WAITPID={os.getpid()}",
|
||||||
|
"/LOG",
|
||||||
],
|
],
|
||||||
creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP,
|
creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP,
|
||||||
close_fds=True,
|
close_fds=True,
|
||||||
@@ -497,7 +504,9 @@ $form.Refresh()
|
|||||||
# Ueber Start-Process (statt direktem Aufruf) gestartet und per Polling statt -Wait
|
# Ueber Start-Process (statt direktem Aufruf) gestartet und per Polling statt -Wait
|
||||||
# abgewartet, damit die Fensternachrichtenschleife per DoEvents() weiterlaeuft -
|
# abgewartet, damit die Fensternachrichtenschleife per DoEvents() weiterlaeuft -
|
||||||
# sonst wuerde Windows das Fenster waehrend robocopy als "Keine Rueckmeldung" anzeigen.
|
# sonst wuerde Windows das Fenster waehrend robocopy als "Keine Rueckmeldung" anzeigen.
|
||||||
$roboArgs = @("{source_dir}", "{install_dir}", "/MIR", "/NFL", "/NDL", "/NJH", "/NJS", "/NC", "/NS", "/NP")
|
# /R:3 /W:2 begrenzt die Wiederholungen bei gesperrten Dateien (Standard: praktisch endlos,
|
||||||
|
# das Update haengt dann sichtbar "fuer immer" im Fortschrittsfenster).
|
||||||
|
$roboArgs = @("{source_dir}", "{install_dir}", "/MIR", "/R:3", "/W:2", "/NFL", "/NDL", "/NJH", "/NJS", "/NC", "/NS", "/NP")
|
||||||
$roboProc = Start-Process -FilePath "robocopy" -ArgumentList $roboArgs -WindowStyle Hidden -PassThru
|
$roboProc = Start-Process -FilePath "robocopy" -ArgumentList $roboArgs -WindowStyle Hidden -PassThru
|
||||||
while (-not $roboProc.HasExited) {{
|
while (-not $roboProc.HasExited) {{
|
||||||
Start-Sleep -Milliseconds 200
|
Start-Sleep -Milliseconds 200
|
||||||
|
|||||||
Reference in New Issue
Block a user