fix(windows): Drueberinstallieren, Linux-Reste im Windows-Betrieb, Ordner-Waehler
Ampel / ampel (push) Successful in 1m27s
Ampel / ampel (push) Successful in 1m27s
Vier Fragen des Commanders vom 29.08.2026, drei davon mit Codefolge.
## 1. „Was passiert wenn man die Setup.exe einfach drueber installiert?"
Bis hierher: nicht zuverlaessig. Rippy startet mit Windows, laeuft also fast
immer — dann ist `Rippy.exe` gesperrt, `shutil.copy2` warf PermissionError,
und die neue Fassung landete als `Rippy.exe.neu` daneben. Dazu die Meldung
„wird beim naechsten Start uebernommen".
**Diese Zusage hat niemand eingeloest.** `.neu` kam im ganzen Projekt genau
einmal vor: an der Stelle, die es schrieb. Wer drueberinstallierte, behielt
still die alte Fassung, und das Setup meldete Erfolg.
Jetzt wird der laufende Rippy vorher beendet (`dienst_beenden` gibt es seit
der Deinstallation und wartet auch die zwei Sekunden ab, die Windows fuer die
Dateihandles braucht). Eine von einer aelteren Setup-Fassung liegengelassene
`.neu` wird dabei uebernommen. Bleibt die Datei DANN noch gesperrt, gibt es
einen klaren Fehler statt einer Zusage — Rippy im Infobereich beenden und das
Setup erneut starten.
Der Tausch laeuft bewusst im SETUP und nicht beim Dienststart: Windows sperrt
eine laufende .exe, und `Rippy.exe` waere genau die zu ersetzende Datei.
## 3. Linux-Reste im Windows-Betrieb (Docker/Headless unveraendert)
**`caps.py`: `os.path.isdir("/app")`.** Damit hielt sich der eigenstaendige
Windows-Rippy fuer einen FREMDEN Worker — und das UI warnte vor fehlender
Pfad-Uebersetzung auf einer Maschine ohne Container und ohne Freigabe.
„Extern" heisst jetzt, was es meint: Rippy laeuft woanders als dieser Worker.
**`caps.py`: `shutil.which("makemkvcon")` + `os.path.ismount(daten_dir)`.**
Beide unter Windows immer falsch (Programme liegen nicht im PATH, ein
normaler Ordner ist kein Mount). Die Schluessel-Auskunft blieb dauerhaft
„unbekannt", obwohl MakeMKV samt Datenverzeichnis da war. Der Mount-Test
bleibt fuer den Container, wo er einen Zweck hat.
**`rohdaten.py`: `/app/temp/raw` und `/app/media` fest.** Dieses Modul findet
die Rohdaten eines Jobs wieder — fuer den Wiederholen-Dialog und fuer
„Rohdaten mitloeschen". Unter Windows fand es NIE etwas: Der Dialog meldete
„keine Rohdaten", das Aufraeumen loeschte nichts, und die Bruchstuecke eines
abgebrochenen Rips blieben liegen (bei 4K-UHD bis 100 GB).
Sieben Tests wurden dabei rot, und zwar zu Recht: Sie pruefen Container-Regeln,
liefen aber unter Windows. Die Wurzeln sind jetzt einspritzbar — beide
Betriebsfaelle auf jedem Rechner pruefbar statt vom laufenden abhaengig.
## 4. „Der Durchsuchen button fehlt. Wie es der Installer auch macht"
Neu: `OrdnerWaehler` — Pfadfeld plus „Durchsuchen …", benutzt fuer Ablage und
Arbeitsverzeichnis. Es waere der DRITTE fest eingebaute Ordner-Browser
geworden (RipTargetModal, StorageMounts); dieser hier ist wiederverwendbar.
`/browse` weiss seit dem 28.08. selbst, in welchem Betrieb es laeuft.
⚠️ Beim Einbau fiel der Import unter den Tisch. `vite` pruefte das NICHT — das
Buendel blieb byte-gleich gross, und zur Laufzeit waere es der naechste leere
Bildschirm gewesen. Aufgefallen nur, weil die erwartete Anzahl Ersetzungen
nicht stimmte. Im Browser gegengeprueft: alle sieben Laufwerke, Navigation in
D:\, keine Konsolenfehler.
843 Tests gruen, ruff sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
21b1757aa4
commit
b2acddbdfa
@@ -513,3 +513,48 @@ def test_laufender_dienst_wird_nicht_noch_einmal_gestartet(monkeypatch):
|
||||
monkeypatch.setattr(windows_app, "starte_hintergrund",
|
||||
lambda p: pytest.fail("es laeuft doch schon einer"))
|
||||
assert windows_app.bereitstellen(7788) is True
|
||||
|
||||
|
||||
# ── Drueberinstallieren (Befund 29.08.2026) ─────────────────────────────
|
||||
#
|
||||
# Commander: „Was passiert wenn man die Setup.exe einfach drüber installiert?
|
||||
# Geht das, ist es dann die aktuellste version?"
|
||||
#
|
||||
# Bis hierher: nicht zuverlaessig. Rippy startet mit Windows, laeuft also fast
|
||||
# immer — dann ist `Rippy.exe` gesperrt, und die neue Fassung landete als
|
||||
# `Rippy.exe.neu` daneben, mit der Meldung „wird beim naechsten Start
|
||||
# uebernommen". **Diese Zusage hat niemand eingeloest**: `.neu` kam im ganzen
|
||||
# Projekt genau einmal vor, naemlich an der Stelle, die es schrieb.
|
||||
|
||||
|
||||
def test_eine_liegengebliebene_fassung_wird_uebernommen(tmp_path):
|
||||
from rippy import windows_app
|
||||
|
||||
(tmp_path / "Rippy.exe").write_bytes(b"alt")
|
||||
(tmp_path / "Rippy.exe.neu").write_bytes(b"neu")
|
||||
|
||||
assert windows_app.ausstehende_fassung_uebernehmen(str(tmp_path))
|
||||
assert (tmp_path / "Rippy.exe").read_bytes() == b"neu"
|
||||
assert not (tmp_path / "Rippy.exe.neu").exists()
|
||||
|
||||
|
||||
def test_ohne_liegengebliebene_fassung_passiert_nichts(tmp_path):
|
||||
from rippy import windows_app
|
||||
|
||||
(tmp_path / "Rippy.exe").write_bytes(b"alt")
|
||||
assert windows_app.ausstehende_fassung_uebernehmen(str(tmp_path)) == ""
|
||||
assert (tmp_path / "Rippy.exe").read_bytes() == b"alt"
|
||||
|
||||
|
||||
def test_ein_fehlschlag_beim_tausch_wirft_nicht(tmp_path):
|
||||
"""Rippy laeuft dann mit der alten Fassung weiter — besser als gar nicht."""
|
||||
from rippy import windows_app
|
||||
|
||||
(tmp_path / "Rippy.exe").write_bytes(b"alt")
|
||||
(tmp_path / "Rippy.exe.neu").write_bytes(b"neu")
|
||||
|
||||
def gesperrt(a, b):
|
||||
raise OSError(32, "in Benutzung")
|
||||
|
||||
assert windows_app.ausstehende_fassung_uebernehmen(str(tmp_path), gesperrt) == ""
|
||||
assert (tmp_path / "Rippy.exe").read_bytes() == b"alt"
|
||||
|
||||
@@ -127,17 +127,44 @@ def installieren(ziel: str = None, autostart: bool = True,
|
||||
quelle = _eigene_datei()
|
||||
programm = os.path.join(ziel, "Rippy.exe")
|
||||
|
||||
# Sich selbst kopieren. Läuft gerade eine ältere Fassung, ist die Datei
|
||||
# gesperrt — dann daneben legen und beim nächsten Start tauschen.
|
||||
# ⚠️ ERST den laufenden Rippy beenden (Befund 29.08.2026).
|
||||
#
|
||||
# Commander: „Was passiert wenn man die Setup.exe einfach drüber
|
||||
# installiert? Geht das, ist es dann die aktuellste version?"
|
||||
#
|
||||
# Bis hierher: NEIN, nicht zuverlässig. Rippy startet mit Windows, läuft
|
||||
# also fast immer — dann ist `Rippy.exe` gesperrt, `shutil.copy2` warf
|
||||
# `PermissionError`, und die neue Fassung landete als `Rippy.exe.neu`
|
||||
# daneben. Dazu die Meldung „wird beim nächsten Start übernommen".
|
||||
#
|
||||
# **Diese Zusage hat niemand eingelöst.** `Rippy.exe.neu` kam im ganzen
|
||||
# Projekt genau einmal vor: hier. Wer drüberinstallierte, behielt still
|
||||
# die alte Fassung — und das Setup meldete Erfolg.
|
||||
#
|
||||
# Jetzt wird der laufende Rippy vorher beendet. `dienst_beenden` gibt es
|
||||
# seit der Deinstallation; es wartet auch die zwei Sekunden ab, die
|
||||
# Windows braucht, um die Dateihandles freizugeben.
|
||||
if os.path.isfile(programm) and \
|
||||
os.path.abspath(quelle).lower() != os.path.abspath(programm).lower():
|
||||
if dienst_beenden(ziel):
|
||||
melden(" Laufender Rippy beendet — die Dateien sind jetzt frei.")
|
||||
# Und aufräumen, was eine ÄLTERE Setup-Fassung liegen ließ.
|
||||
ausstehende_fassung_uebernehmen(ziel)
|
||||
|
||||
# Sich selbst kopieren.
|
||||
if os.path.abspath(quelle).lower() != os.path.abspath(programm).lower():
|
||||
try:
|
||||
shutil.copy2(quelle, programm)
|
||||
except PermissionError:
|
||||
ersatz = programm + ".neu"
|
||||
shutil.copy2(quelle, ersatz)
|
||||
melden("Rippy läuft gerade — die neue Fassung wird beim nächsten "
|
||||
"Start übernommen.")
|
||||
programm = ersatz
|
||||
except PermissionError as e:
|
||||
# Ehrlich scheitern statt eine Zusage zu geben, die niemand
|
||||
# einlöst. Hier lag bis zum 29.08.2026 der `.neu`-Rückfall — mit
|
||||
# der Meldung „wird beim nächsten Start übernommen" und ohne
|
||||
# jeden Code, der das getan hätte.
|
||||
raise RuntimeError(
|
||||
"Rippy.exe liess sich nicht ersetzen: %s\n"
|
||||
"Rippy laeuft noch. Bitte im Infobereich rechts unten "
|
||||
"beenden (Rechtsklick auf das Rippy-Symbol) und das Setup "
|
||||
"erneut starten." % e) from e
|
||||
|
||||
icon = _icon_pfad()
|
||||
if icon and os.path.isfile(icon):
|
||||
@@ -587,6 +614,46 @@ class Dienst:
|
||||
return False
|
||||
|
||||
|
||||
def ausstehende_fassung_uebernehmen(ordner: str = None, ersetzen=None) -> str:
|
||||
"""Eine beim Update danebengelegte `Rippy.exe.neu` jetzt übernehmen.
|
||||
|
||||
Gibt den übernommenen Pfad zurück, sonst "".
|
||||
|
||||
## Die uneingelöste Zusage (Befund 29.08.2026)
|
||||
|
||||
Beim Drüberinstallieren schrieb `installieren()` bei gesperrter Datei eine
|
||||
`Rippy.exe.neu` daneben und meldete: „wird beim nächsten Start
|
||||
übernommen." **Diese Zeile war das einzige Vorkommen von `.neu` im ganzen
|
||||
Projekt** — es gab niemanden, der sie übernommen hätte. Wer über eine
|
||||
laufende Installation installierte, behielt still die alte Fassung, und
|
||||
das Setup sagte „fertig".
|
||||
|
||||
## Warum das hier läuft und NICHT beim Dienststart
|
||||
|
||||
Der naheliegende Ort wäre der Start von Rippy selbst. Er funktioniert
|
||||
nicht: Windows sperrt eine laufende `.exe`, und `Rippy.exe` ist genau die
|
||||
Datei, die ersetzt werden müsste. `os.replace` scheitert dort immer.
|
||||
|
||||
Das Setup ist ein ANDERES Programm (`RippySetup.exe`) und hat den
|
||||
laufenden Rippy vorher beendet — hier ist die Datei frei. Aufgeräumt wird
|
||||
damit auch, was eine ältere Setup-Fassung liegen gelassen hat.
|
||||
"""
|
||||
ordner = ordner or os.path.dirname(_eigene_datei())
|
||||
ziel = os.path.join(ordner, "Rippy.exe")
|
||||
neu = ziel + ".neu"
|
||||
if not os.path.isfile(neu):
|
||||
return ""
|
||||
try:
|
||||
(ersetzen or os.replace)(neu, ziel)
|
||||
except OSError as e:
|
||||
# Nicht werfen: Rippy laeuft mit der alten Fassung weiter, das ist
|
||||
# besser als gar nicht zu starten. Gesagt wird es trotzdem.
|
||||
melden(" Neue Fassung konnte nicht uebernommen werden (%s)." % e)
|
||||
return ""
|
||||
melden(" Neue Fassung uebernommen: %s" % ziel)
|
||||
return ziel
|
||||
|
||||
|
||||
def _reste_wegraeumen() -> None:
|
||||
"""Zurueckgelassene Auspack-Ordner entfernen. Darf nie stoeren."""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user