fix: Deinstaller, Verwaltungsfenster, Dashboard-Widerspruch, Mount-Tempo
Ampel / ampel (push) Successful in 30s

Vier Meldungen des Commanders, alle nachgemessen.

1. DEINSTALLER LIEF NICHT MEHR - reproduziert mit echtem PowerShell:

     Der Typ [System.Windows.Forms.MessageBox] wurde nicht gefunden.

   `Add-Type -AssemblyName System.Windows.Forms` stand EINE ZEILE ZU SPAET, die
   MessageBox wurde davor benutzt. Der Deinstaller starb also in seiner ersten
   Arbeitszeile, jedes Mal. Zwei weitere Maengel gleich mit:
   Kodierung war ASCII trotz Umlauten, und `Remove-Item -Recurse -Force
   $PSScriptRoot` loescht den Ordner, in dem das laufende Skript liegt - das
   klappt auf Windows nicht zuverlaessig (venv-DLLs sind geladen). Jetzt raeumt
   ein losgeloestes cmd nach, sobald PowerShell weg ist. Der erzeugte Deinstaller
   ist gegengeprueft: parst, BOM da, Umlaute intakt.

2. VERWALTUNGSFENSTER (Doppelklick aufs Tray). Zeigt Status, Aufgaben und Log,
   plus Knoepfe fuer Rippy, Log-in-Rippy und Deinstallieren - Deinstallieren geht
   damit auch aus dem Tray-Menue. Eigener PROZESS statt Fenster im Tray, weil
   pystray und tkinter beide den Haupt-Thread wollen; tkinter statt WinForms,
   weil es bei jeder Windows-Python-Installation dabei ist. Headless gerendert
   und angesehen. Alles Fachliche kommt von Rippy (/capabilities, /jobs), damit
   dort nicht eine zweite, abweichende Wahrheit steht.

3. DASHBOARD-WIDERSPRUCH. Oben stand "Akira im Laufwerk erkannt", die
   Server-Status-Karte gleichzeitig "Bereit - keine Disc in Arbeit / Disc
   einlegen". Zwei Aussagen, ein Blick. Die Karte fragt jetzt /devices und sagt
   "Disc erkannt - wartet auf Rippen starten" samt Titel.

4. MOUNT-TEMPO. Der Commander: "150 Sekunden? Das ist verrueckt langsam." Recht
   hat er. Gemessen ging die Zeit fast komplett in einen FEHLVERSUCH: `umount -l`
   ist lazy, der Abbau passiert spaeter, und wer direkt danach mountet, riskiert
   dass der Abbau hinter dem neuen Mount landet. Der erste Reparaturversuch
   scheiterte dadurch regelmaessig - und der zweite kostete 30 s mount-Timeout
   plus Pruefungen. Jetzt werden 1,5 s auf den Abbau gewartet, bevor neu
   gemountet wird; die Wache prueft erstmals nach 3 s statt 10 s und benutzt zum
   ERKENNEN die einfache schnelle Probe (die Doppelprobe steckt dort, wo der
   Wettlauf lauert: direkt nach dem Mount).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-26 14:52:53 +02:00
parent 4c2331563c
commit 2a90538473
9 changed files with 619 additions and 28 deletions
+41
View File
@@ -281,3 +281,44 @@ def test_wirklich_erreichbar_spart_das_warten_wenn_schon_die_erste_probe_faellt(
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: False)
assert mounts.wirklich_erreichbar("rippy", warten=gewartet.append) is False
assert gewartet == [] # nicht drei Sekunden fuer nichts
def test_stale_loesen_wartet_den_lazy_abbau_ab(monkeypatch):
"""Der Grund fuer die 150 Sekunden (Befund 26.07.2026): `umount -l` ist lazy,
der Abbau passiert spaeter. Wer direkt danach mountet, riskiert, dass der
Abbau HINTER dem neuen Mount landet - der erste Reparaturversuch scheiterte
dadurch regelmaessig, und der zweite kostete 30 s Timeout."""
import time
import types
import mounts
gewartet = []
monkeypatch.setattr(time, "sleep", gewartet.append)
aufrufe = []
def fake_run(cmd, **kwargs):
aufrufe.append(cmd)
rc = 0 if len(aufrufe) <= 2 else 1
return types.SimpleNamespace(returncode=rc, stdout=b"", stderr=b"")
monkeypatch.setattr(mounts.subprocess, "run", fake_run)
assert mounts._stale_mounts_loesen("/app/media/x") == 2
assert gewartet == [1.5]
def test_stale_loesen_wartet_nicht_wenn_nichts_zu_loesen_war(monkeypatch):
"""War kein Mount da, gibt es auch keinen Abbau abzuwarten - dann darf die
Reparatur nicht kuenstlich gebremst werden."""
import time
import types
import mounts
gewartet = []
monkeypatch.setattr(time, "sleep", gewartet.append)
monkeypatch.setattr(
mounts.subprocess, "run",
lambda cmd, **k: types.SimpleNamespace(returncode=1, stdout=b"", stderr=b""))
assert mounts._stale_mounts_loesen("/app/media/x") == 0
assert gewartet == []