fix(windows): der Deinstallierer beendet jetzt auch den laufenden Dienst
Ampel / ampel (push) Failing after 54s

Beim Deinstallieren im laufenden Betrieb blieben liegen:

    rippy.db, rippy.db-shm, rippy.db-wal
    (Der Prozess kann nicht auf die Datei zugreifen, da sie von einem
     anderen Prozess verwendet wird)

Der Dienst lief weiter und hielt die Datenbank offen. Damit waere der Fehler
von vorhin durch eine andere Tuer zurueckgekommen: Die naechste Installation
haette wieder den alten Zustand samt setup.done geerbt, und der
Ersteinrichtungs-Assistent waere wieder verschwunden.

Aufgefallen ist es nur, weil der Deinstallierer seit heute NENNT, was er
nicht wegbekommen hat. Vorher stand dort ein `except OSError: pass` und die
Meldung "Rippy wurde entfernt".

Gefiltert wird ueber den Installationsordner, nicht ueber den Namen:
`Rippy.exe` heisst auch die Datei, die den Auftrag gerade ausfuehrt. Die
eigene Prozesskennung und die des PyInstaller-Starters sind ausgenommen.

Nachgewiesen am laufenden Prozess: beendet, Datenbankdateien danach
loeschbar. In der Entwicklungsumgebung schlaegt der Pfadvergleich uebrigens
immer fehl -- die Werkzeuge laufen in einem App-Container, der
%LOCALAPPDATA% umleitet, waehrend die Registry den echten Pfad meldet. Das
steht jetzt als Hinweis im Docstring, damit es niemand ein zweites Mal sucht.

Ampel lokal: 713 gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-28 14:50:50 +02:00
co-authored by Claude Opus 5
parent 8e65411189
commit 3d5f82b786
2 changed files with 112 additions and 0 deletions
+32
View File
@@ -233,6 +233,38 @@ def test_die_testsuite_ruehrt_den_ECHTEN_eintrag_nicht_an(tmp_path, monkeypatch)
)
# ── Den laufenden Dienst zuerst beenden ─────────────────────────────────
def test_beenden_filtert_ueber_den_ordner():
"""DER Befund vom 28.08.2026: Beim Deinstallieren im laufenden Betrieb
blieben rippy.db, rippy.db-shm und rippy.db-wal liegen — „von einem
anderen Prozess verwendet". Der Dienst lief weiter."""
s = windows_app.beenden_skript("C:" + BACKSLASH + "Rippy", [123, 456])
assert "Rippy.exe" in s
assert "C:" + BACKSLASH + "Rippy" + BACKSLASH + "*" in s
assert "Stop-Process" in s
def test_der_eigene_prozess_wird_ausgenommen():
"""`Rippy.exe` heisst auch die Datei, die den Auftrag gerade ausfuehrt —
die darf sich nicht selbst abschiessen."""
s = windows_app.beenden_skript("C:" + BACKSLASH + "R", [4711, 4712])
assert "4711" in s and "4712" in s
assert s.index("notin") < s.index("Stop-Process"), "erst filtern, dann beenden"
def test_ohne_eigene_pids_wird_nichts_wildes_erzeugt():
"""Eine leere Liste duerfte keine kaputte Bedingung ergeben."""
s = windows_app.beenden_skript("C:" + BACKSLASH + "R", [])
assert "@()" not in s, "leere Menge waere eine unbrauchbare Bedingung"
assert "@(0)" in s
def test_apostroph_im_ordner_bricht_das_skript_nicht():
s = windows_app.beenden_skript("C:" + BACKSLASH + "O'Brien", [1])
assert "O''Brien" in s
assert s.count("'") % 2 == 0
# ── Die Datenbank am richtigen Ort ──────────────────────────────────────
def test_datenbank_liegt_im_installationsordner():
"""DER Befund vom 28.08.2026: „Es fehlt der 1st run wizzard wenn man es
+80
View File
@@ -184,6 +184,79 @@ def installieren(ziel: str = None, autostart: bool = True,
return ziel
def beenden_skript(ordner: str, eigene_pids: list) -> str:
"""Das PowerShell-Skript, das die ANDEREN Rippy-Prozesse beendet.
Reine Funktion, damit der Filter prüfbar ist, ohne etwas zu beenden.
## Warum das nötig ist (gemessen 28.08.2026)
Beim Deinstallieren im laufenden Betrieb blieben liegen:
rippy.db, rippy.db-shm, rippy.db-wal
(von einem anderen Prozess verwendet)
Der Dienst lief weiter und hielt die Datenbank offen. Damit wäre der
Fehler von vorhin durch eine andere Tür zurückgekommen: Die nächste
Installation hätte wieder den alten Zustand geerbt.
## Warum über den ORDNER gefiltert wird
`Rippy.exe` heißt auch die Setup-Datei, die den Auftrag gerade ausführt —
und die darf sich nicht selbst abschießen. Beendet wird nur, was aus dem
Installationsordner läuft, und die eigenen Prozesskennungen sind
ausgenommen.
## Hinweis für spätere Sitzungen
In der Entwicklungsumgebung schlägt der Vergleich zuverlässig fehl, und
das ist KEIN Fehler im Code: Die Werkzeuge laufen dort in einem
App-Container, der Schreibzugriffe auf `%LOCALAPPDATA%` nach
\AppData\Local\Packages\<app>\LocalCache\Local\Rippy
umleitet. Der Registry-Eintrag `InstallLocation` zeigt dagegen auf den
ECHTEN Pfad — Prozess und Ordner passen dann nie zusammen. Auf einer
normalen Installation liegen beide am selben Ort. Die Mechanik wurde am
28.08.2026 mit dem tatsächlichen Ordner nachgewiesen: Prozess beendet,
Datenbankdateien danach löschbar.
"""
ort = (ordner or "").replace("'", "''").rstrip("\\/")
pids = ",".join(str(int(p)) for p in eigene_pids if p)
return (
"Get-CimInstance Win32_Process -Filter \"Name='Rippy.exe'\" "
"| Where-Object { $_.ExecutablePath -like '%s\\*' "
"-and $_.ProcessId -notin @(%s) } "
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force "
"-ErrorAction SilentlyContinue }" % (ort, pids or "0")
)
def dienst_beenden(ordner: str) -> bool:
"""Beendet die anderen Rippy-Prozesse aus diesem Ordner. Wirft nicht."""
if os.name != "nt" or not ordner:
return False
from rippy.platform.winlauf import OHNE_FENSTER
eigene = [os.getpid()]
try:
eigene.append(os.getppid()) # der PyInstaller-Starter
except OSError:
pass
try:
ergebnis = subprocess.run(
["powershell", "-NoProfile", "-NonInteractive", "-Command",
beenden_skript(ordner, eigene)],
capture_output=True, text=True, timeout=60,
creationflags=OHNE_FENSTER)
except (OSError, subprocess.SubprocessError):
return False
# Kurz Luft lassen: Windows gibt die Dateihandles nicht im selben
# Augenblick frei, in dem der Prozess endet.
time.sleep(2)
return ergebnis.returncode == 0
def alte_orte() -> list:
"""Ordner ausserhalb des Installationsordners, die Rippy einmal angelegt hat.
@@ -332,6 +405,13 @@ def deinstallieren(still: bool = False) -> None:
fenster_modul.helfer_beenden(os.path.join(ordner, "fenster"))
# ⚠️ Und den DIENST selbst (Befund 28.08.2026). Lief er weiter, blieben
# rippy.db, rippy.db-shm und rippy.db-wal liegen — „von einem anderen
# Prozess verwendet". Die naechste Installation haette damit wieder den
# alten Zustand geerbt, also genau den Fehler von vorhin durch eine
# andere Tuer.
dienst_beenden(ordner)
# Der ALTE Datenbank-Ort (bis 28.08.2026 %ProgramData%\Rippy) liegt
# ausserhalb des Installationsordners. Blieb er stehen, erbte die naechste
# Installation den alten Zustand — genau daran fehlte dem Commander der