fix(windows): der Deinstallierer liess 148 Dateien liegen und meldete Erfolg
Ampel / ampel (push) Successful in 54s

Beim ersten echten Deinstallieren auf dem Rechner des Commanders gemessen:

    Uninstall-Eintrag   weg
    Autostart           weg
    Verknuepfungen      weg
    Dateien             149 NOCH DA  (davon 148 in fenster\EBWebView\)

Gemeldet wurde: "Rippy wurde entfernt."

Zwei Ursachen, beide behoben:

## 1. WebView2 ueberlebt Rippy

WebView2 startet eigene Prozesse (msedgewebview2.exe: Renderer, GPU, Netz,
Crashpad). Stirbt Rippy, laufen sie als Waisen weiter und halten den
Zwischenspeicher offen -- acht davon liefen noch, als ich nachsah. shutil.rmtree
scheiterte an jeder gesperrten Datei.

fenster.helfer_beenden() beendet sie jetzt VOR dem Loeschen. Gefiltert wird
ueber den --user-data-dir in der Befehlszeile: msedgewebview2.exe benutzen
auch andere Programme, und die alle zu beenden waere ein Uebergriff -- der
Nutzer verloere die Fenster fremder Anwendungen. Ein Test haelt fest, dass
erst gefiltert und dann beendet wird.

## 2. Der Fehler wurde verschluckt

`except OSError: pass` in der Loeschschleife. Ein "Rippy wurde entfernt" ueber
einem halb geleerten Ordner ist eine Falschaussage -- genau die Sorte stiller
Fehlschlag, vor der AGENTS.md warnt. Jetzt wird gesammelt, was nicht ging, und
mit dem Grund genannt.

Dazu im Aufraeum-Skript: `rmdir /s /q` statt `rmdir`. Ein blosses rmdir
scheitert an JEDER verbliebenen Datei und laesst den ganzen Ordner stehen --
selbst wenn spaeter nur noch eine Sperrdatei uebrig ist.

Ampel lokal: 612 gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-28 13:15:58 +02:00
co-authored by Claude Opus 5
parent ce4e7e6b8a
commit a5e64f8e56
4 changed files with 144 additions and 5 deletions
+62
View File
@@ -159,6 +159,68 @@ def speicherort() -> str:
return ordner
def helfer_skript(speicherort: str) -> str:
"""Das PowerShell-Skript, das NUR unsere WebView2-Helfer beendet.
Reine Funktion, damit der Filter prüfbar ist, ohne etwas zu beenden.
## Warum das nötig ist (gemessen 28.08.2026)
Beim Deinstallieren blieben **148 Dateien** liegen, alle unter
`fenster\\EBWebView\\`. Der Grund: WebView2 startet eigene Prozesse
(`msedgewebview2.exe` — Renderer, GPU, Netz, Crashpad). Stirbt Rippy,
laufen sie als Waisen weiter und halten den Zwischenspeicher offen.
`shutil.rmtree` scheiterte, der Fehler wurde verschluckt, und der
Deinstallierer meldete trotzdem „Rippy wurde entfernt."
## Warum über den Speicherort gefiltert wird
`msedgewebview2.exe` benutzen auch andere Programme. Sie alle zu beenden,
wäre ein Übergriff — der Nutzer verlöre die Fenster fremder Anwendungen.
Jeder Helfer trägt seinen `--user-data-dir` in der Befehlszeile; nur wer
auf UNSEREN Ordner zeigt, gehört uns.
"""
ort = (speicherort or "").replace("'", "''")
return (
"Get-CimInstance Win32_Process -Filter \"Name='msedgewebview2.exe'\" "
"| Where-Object { $_.CommandLine -like '*%s*' } "
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force "
"-ErrorAction SilentlyContinue }" % ort
)
def helfer_beenden(speicherort: str = None) -> bool:
"""Beendet die WebView2-Helfer, die zu UNSEREM Speicherort gehören.
True, wenn der Versuch durchlief. Wirft nicht — ein Deinstallieren darf
daran nicht scheitern.
"""
if os.name != "nt":
return False
import subprocess
ort = speicherort or speicherort_lesen()
if not ort:
return False
from rippy.platform.winlauf import OHNE_FENSTER
try:
ergebnis = subprocess.run(
["powershell", "-NoProfile", "-NonInteractive", "-Command",
helfer_skript(ort)],
capture_output=True, text=True, timeout=60,
creationflags=OHNE_FENSTER)
except (OSError, subprocess.SubprocessError):
return False
return ergebnis.returncode == 0
def speicherort_lesen() -> str:
"""Der Speicherort OHNE ihn anzulegen — fürs Aufräumen."""
basis = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~")
return os.path.join(basis, "Rippy", "fenster")
def oeffnen(adresse: str, titel: str = TITEL, breite: int = BREITE,
hoehe: int = HOEHE) -> None:
"""Öffnet das Fenster und kehrt erst zurück, wenn es geschlossen wird.
+42
View File
@@ -125,6 +125,48 @@ def test_erfolgreiches_fenster_oeffnet_keinen_browser(monkeypatch):
assert gerufen == []
# ── Die WebView2-Helfer ─────────────────────────────────────────────────
def test_helfer_werden_ueber_den_speicherort_gefiltert():
"""DER Befund vom 28.08.2026: Nach dem Deinstallieren blieben 148 Dateien
liegen, alle unter `fenster\\EBWebView\\`. WebView2 startet eigene
Prozesse (Renderer, GPU, Netz, Crashpad); stirbt Rippy, laufen sie als
Waisen weiter und halten den Zwischenspeicher offen."""
s = fenster.helfer_skript(r"C:\Users\X\AppData\Local\Rippy\fenster")
assert "msedgewebview2.exe" in s
assert r"C:\Users\X\AppData\Local\Rippy\fenster" in s
assert "Stop-Process" in s
def test_der_filter_trifft_NUR_unsere_helfer():
"""`msedgewebview2.exe` benutzen auch andere Programme. Sie alle zu
beenden waere ein Uebergriff — der Nutzer verloere die Fenster fremder
Anwendungen. Ohne `Where-Object` auf den Speicherort passiert genau das."""
s = fenster.helfer_skript(r"C:\Rippy\fenster")
assert "Where-Object" in s, "ohne Filter wuerden FREMDE Fenster sterben"
assert s.index("Where-Object") < s.index("Stop-Process"), \
"erst filtern, dann beenden"
def test_apostroph_im_pfad_bricht_das_skript_nicht():
"""In PowerShell wird ein Apostroph durch VERDOPPELN maskiert. Ohne das
bricht ein Pfad wie C:\\Users\\O'Brien\\… das Skript mitten entzwei."""
s = fenster.helfer_skript(r"C:\Users\O'Brien\Rippy\fenster")
assert "O''Brien" in s
assert s.count("'") % 2 == 0
def test_ohne_windows_werden_keine_helfer_beendet():
if os.name == "nt":
pytest.skip("prueft das Verhalten auf Nicht-Windows")
assert fenster.helfer_beenden("/tmp/x") is False
def test_leerer_speicherort_beendet_nichts(monkeypatch):
"""Ein leerer Filter wuerde auf ALLE msedgewebview2-Prozesse passen."""
monkeypatch.setattr(fenster, "speicherort_lesen", lambda: "")
assert fenster.helfer_beenden("") is False
# ── Der Speicherort ─────────────────────────────────────────────────────
def test_speicherort_liegt_bei_rippy(monkeypatch, tmp_path):
"""Im TEMP waere er bei jedem Aufraeumen weg — und beim Deinstallieren
+10 -1
View File
@@ -55,7 +55,16 @@ def test_skript_loescht_sich_selbst():
def test_ordner_ohne_abschliessenden_trenner():
"""rmdir will den Ordner OHNE Schluss-Backslash — mit scheitert es."""
s = windows_app.aufraeum_skript(r"C:\R\Rippy.exe", "C:" + BACKSLASH + "R" + BACKSLASH)
assert 'rmdir "' + r"C:\R" + '"' in s
assert 'rmdir /s /q "' + r"C:\R" + '"' in s
def test_skript_raeumt_den_ordner_MIT_inhalt():
"""DER Befund vom 28.08.2026: Nach dem Deinstallieren blieben 148 Dateien
liegen (WebView2-Zwischenspeicher, dessen Prozesse Rippy ueberlebt
hatten). Ein blosses `rmdir` scheitert an JEDER verbliebenen Datei und
laesst den ganzen Ordner stehen."""
s = windows_app.aufraeum_skript(r"C:\R\Rippy.exe", r"C:\R")
assert "rmdir /s /q" in s, "ohne /s /q bleibt jeder nicht leere Ordner stehen"
# ── Installation (echt, in einem Testordner) ────────────────────────────
+30 -4
View File
@@ -269,25 +269,47 @@ def deinstallieren(still: bool = False) -> None:
reg.autostart_entfernen()
reg.austragen()
# ⚠️ ZUERST die WebView2-Helfer beenden (Befund 28.08.2026).
#
# Beim ersten echten Deinstallieren blieben 148 Dateien liegen, alle
# unter `fenster\EBWebView\`. WebView2 startet eigene Prozesse
# (msedgewebview2.exe: Renderer, GPU, Netz, Crashpad); stirbt Rippy,
# laufen sie als Waisen weiter und halten den Zwischenspeicher offen.
# Danach ging `shutil.rmtree` nicht mehr — und der Fehler wurde
# verschluckt, waehrend „Rippy wurde entfernt" gemeldet wurde.
from rippy import fenster as fenster_modul
fenster_modul.helfer_beenden(os.path.join(ordner, "fenster"))
laeuft_von_hier = os.path.abspath(_eigene_datei()).lower().startswith(
os.path.abspath(ordner).lower())
# Die eigene Datei kann sich nicht selbst loeschen, solange sie laeuft.
# Also: alles andere jetzt, die EXE per Aufraeum-Befehl danach.
geblieben = []
for name in os.listdir(ordner) if os.path.isdir(ordner) else []:
pfad = os.path.join(ordner, name)
if laeuft_von_hier and os.path.abspath(pfad).lower() == os.path.abspath(_eigene_datei()).lower():
continue
try:
shutil.rmtree(pfad) if os.path.isdir(pfad) else os.remove(pfad)
except OSError:
pass
except OSError as e:
# NICHT verschlucken. Ein „Rippy wurde entfernt" ueber einem
# halb geleerten Ordner ist eine Falschaussage.
geblieben.append("%s (%s)" % (name, e.strerror or e))
if laeuft_von_hier:
selbst_loeschen(_eigene_datei(), ordner)
if not still:
melden("Rippy wurde entfernt.")
if geblieben:
melden("Rippy wurde entfernt — diese Reste waren gesperrt und "
"werden gleich nachgeholt:")
for eintrag in geblieben:
melden(" " + eintrag)
else:
melden("Rippy wurde entfernt.")
AUFRAEUM_SKRIPT = """@echo off
@@ -299,7 +321,11 @@ for /l %%n in (1,1,15) do (
)
goto ende
:weg
rmdir "{ordner}" >nul 2>&1
rem /s /q, nicht nur rmdir: Ein blosses rmdir scheitert an JEDER
rem verbliebenen Datei. Am 28.08.2026 blieben 148 Dateien im
rem WebView2-Zwischenspeicher liegen, weil deren Prozesse Rippy ueberlebt
rem hatten -- der Ordner blieb dann samt Inhalt stehen.
rmdir /s /q "{ordner}" >nul 2>&1
:ende
del /q "%~f0" >nul 2>&1
"""