diff --git a/packaging/playtube.iss b/packaging/playtube.iss index 0cb92d4..efbde28 100644 --- a/packaging/playtube.iss +++ b/packaging/playtube.iss @@ -98,27 +98,61 @@ begin Result := ExpandConstant('{param:RELAUNCH|0}') = '1'; 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 ResultCode: Integer; begin - { /T beendet auch die Kindprozesse (QtWebEngine); der Hilfsprozess wird zusaetzlich - einzeln beendet, falls er als Waise ueberlebt hat. } - Exec(ExpandConstant('{sys}\taskkill.exe'), '/F /T /IM {#AppExeName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + { WICHTIG: bewusst OHNE "/T" (Prozessbaum mitbeenden). Der Updater startet Setup aus + Playtube.exe heraus - Setup ist also ein Kindprozess. Solange Playtube noch beendet + 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); + + { 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. } Sleep(800); end; function PrepareToInstall(var NeedsRestart: Boolean): String; begin - StopRunningPlaytube(); + WaitForOldInstance(); + StopLeftoverProcesses(); Result := ''; end; function InitializeUninstall(): Boolean; begin - StopRunningPlaytube(); + StopLeftoverProcesses(); Result := True; end; diff --git a/playtube/__init__.py b/playtube/__init__.py index 22db04d..b15b95c 100644 --- a/playtube/__init__.py +++ b/playtube/__init__.py @@ -1,4 +1,4 @@ """Playtube - ein eigenstaendiger YouTube- & YouTube-Music-Player mit Discord Rich Presence.""" __app_name__ = "Playtube" -__version__ = "2.3.2" +__version__ = "2.4.0" diff --git a/playtube/mainwindow.py b/playtube/mainwindow.py index 778d281..d66958a 100644 --- a/playtube/mainwindow.py +++ b/playtube/mainwindow.py @@ -2,6 +2,7 @@ from __future__ import annotations import os import sys +import threading from pathlib import Path from typing import Any @@ -28,6 +29,15 @@ from .updater import UpdateChecker, UpdateInstaller 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): def __init__(self, config: dict[str, Any]): super().__init__() @@ -359,6 +369,10 @@ class MainWindow(QMainWindow): "Installation abgeschlossen. Playtube 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() else: QMessageBox.information( diff --git a/playtube/updater.py b/playtube/updater.py index d8f740c..c8bc627 100644 --- a/playtube/updater.py +++ b/playtube/updater.py @@ -466,7 +466,12 @@ rm -rf "{staging}" packaging/playtube.iss) und startet Playtube dank /RELAUNCH=1 danach neu. Die laufende App beendet sich direkt nach diesem Aufruf (siehe MainWindow), muss hier also nicht auf Setup warten. Setup wird abgekoppelt gestartet, damit es das - Beenden dieses Prozesses ueberlebt.""" + Beenden dieses Prozesses ueberlebt. + + /WAITPID=: 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( [ str(setup_path), @@ -475,6 +480,8 @@ rm -rf "{staging}" "/NORESTART", "/NOCANCEL", "/RELAUNCH=1", + f"/WAITPID={os.getpid()}", + "/LOG", ], creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP, close_fds=True, @@ -497,7 +504,9 @@ $form.Refresh() # Ueber Start-Process (statt direktem Aufruf) gestartet und per Polling statt -Wait # abgewartet, damit die Fensternachrichtenschleife per DoEvents() weiterlaeuft - # 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 while (-not $roboProc.HasExited) {{ Start-Sleep -Milliseconds 200