fix(windows): Roh- und Zielordner landeten in einem Ordner namens „app"

Beim Nachstellen des leeren Bildschirms lief ein echter Test-Rip durch. Die
Rohdaten landeten in:

    F:\app\temp\raw\<job>\Evangelion 2.22_t00.mkv   (436 MB)

Also in einem Ordner namens `app` auf dem Laufwerk, von dem Rippy gerade lief.

## Zwei Container-Wurzeln im Worker

    RAW_DIR    = /app/temp/raw
    MEDIA_ROOT = /app/media

Unter Windows sind das keine Pfade, sondern Unfaelle. Schlimmer: Die Pruefung
`unter_wurzel(wahl, MEDIA_ROOT)` verwarf auch eine AUSDRUECKLICHE Wahl — ein
Arbeitsordner wie `D:\Roh` liegt nicht unter `/app/media`, also fiel er still
auf den Container-Standard zurueck.

**Damit kam der Arbeitsordner, den der Commander am 28.08.2026 ausdruecklich
bestellt hat, unter Windows nie an.** Der Dialog zeigte ihn, das Setzen ging,
und der Worker ignorierte ihn — ohne ein Wort. Dasselbe galt fuer das Ziel:
Eine UNC-Freigabe liegt unter gar keiner lokalen Wurzel, also waere die
fertige Datei in `X:\app\media\bluray` gelandet.

## Die Wurzeln kommen jetzt aus dem Betrieb

Im Container aendert sich NICHTS: dort ist `/app/media` die Wurzel und `frei`
falsch. Nativ zaehlt die Wahl des Nutzers — dort IST sein Laufwerk die Grenze.

Die drei bestehenden Tests wurden rot, und zwar zu Recht: Sie pruefen die
Container-Regel, liefen aber unter Windows, wo `frei` gilt. Die Wurzeln sind
deshalb einspritzbar — beide Betriebsfaelle sind jetzt auf jedem Rechner
pruefbar, statt vom laufenden abzuhaengen.

837 Tests gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-29 14:15:17 +02:00
co-authored by Claude Opus 5
parent 27c9d9a4bb
commit 423374e65a
2 changed files with 142 additions and 27 deletions
+70 -15
View File
@@ -157,7 +157,7 @@ def unter_wurzel(pfad: str, wurzel: str) -> bool:
return pfad == sauber or pfad.startswith(sauber + "/")
def _zielbasis(target_dir, disc_type: str) -> str:
def _zielbasis(target_dir, disc_type: str, wurzeln=None) -> str:
"""Ablagebasis: vom Nutzer gewähltes Ziel (validiert) oder Standard.
posixpath statt os.path — aus demselben Grund wie in _arbeitsverzeichnis:
@@ -167,16 +167,70 @@ def _zielbasis(target_dir, disc_type: str) -> str:
als der Test dafür erstmals unter Windows lief. Live war es nie: aufgerufen
wird nur aus rip_disc, und das ist auf Windows-Workern verriegelt.
"""
wurzel, _vorgabe, frei = wurzeln or _betriebs_wurzeln()
if target_dir:
normalisiert = posixpath.normpath(target_dir)
if unter_wurzel(normalisiert, MEDIA_ROOT):
normalisiert = _normalisiert(target_dir)
if frei or unter_wurzel(normalisiert, wurzel):
return normalisiert
return posixpath.join(RIP_OUTPUT_DIR, disc_type)
# Ohne Wahl: die Wurzel dieses Betriebs, nicht die des Containers. Sonst
# landete die fertige Datei unter Windows in `X:\app\media\bluray` —
# einem Ordner, den niemand gesucht hat (gemessen 29.08.2026).
from rippy import pfade
return pfade.verbinden(wurzel if frei else RIP_OUTPUT_DIR, disc_type)
def _arbeitsverzeichnis(einstellungen: dict, job_wahl: str = "") -> str:
def _betriebs_wurzeln() -> tuple:
"""`(medien_wurzel, arbeits_vorgabe, frei)` für DIESEN Betrieb.
## Warum das hier gebraucht wird (Befund 29.08.2026)
Beim Nachstellen des leeren Bildschirms lief ein echter Test-Rip durch —
und die Rohdaten landeten in **`F:\\app\\temp\\raw`**. Also in einem
Ordner namens `app` auf dem Laufwerk, von dem Rippy gerade lief.
`RAW_DIR` ist `/app/temp/raw` und `MEDIA_ROOT` ist `/app/media`; unter
Windows sind das keine Pfade, sondern Unfälle. Schlimmer noch: Die
Prüfung `unter_wurzel(wahl, MEDIA_ROOT)` verwarf **auch eine ausdrückliche
Wahl** — ein Arbeitsordner wie `D:\\Roh` liegt nicht unter `/app/media`,
also fiel er still auf den Container-Standard zurück.
Damit kam der Arbeitsordner, den der Commander am 28.08.2026 ausdrücklich
bestellt hat („kannst du noch einbauen das man den arbeitsordner … setzen
kann"), unter Windows nie an. Der Dialog zeigte ihn, das Setzen ging, und
der Worker ignorierte ihn — ohne ein Wort.
Im Container ändert sich nichts: Dort ist `/app/media` die Wurzel, und
`frei` ist falsch.
"""
from rippy import betrieb, config
try:
werte = config.laden()
except Exception: # noqa: BLE001
werte = {}
return (betrieb.medien_wurzel(werte) or MEDIA_ROOT,
betrieb.arbeits_vorgabe(werte) or RAW_DIR,
betrieb.frei_blaettern(werte))
def _normalisiert(wert: str) -> str:
"""Pfad säubern — nach seiner FORM, nicht nach dem laufenden Rechner.
`posixpath` für Container-Pfade (sonst macht Windows Backslashes daraus
und die Wurzelprüfung greift nicht mehr), `os.path` für echte
Windows-Pfade. Dieselbe Regel wie in `rippy.pfade`.
"""
from rippy import pfade
return os.path.normpath(wert) if pfade.ist_windows_pfad(wert) \
else posixpath.normpath(wert)
def _arbeitsverzeichnis(einstellungen: dict, job_wahl: str = "",
wurzeln=None) -> str:
"""Basis für Roh-Rips. Reihenfolge: Wahl DIESES Rips → UI-Setting
`workDir` → Container-Default /app/temp/raw.
`workDir` → Vorgabe dieses Betriebs (Container: /app/temp/raw).
Hintergrund (Commander 24.07.): Die VM-Platte (150 GB) reicht für BD-50,
aber eine 4K-UHD (bis 100 GB roh + Kompression daneben) sprengt sie —
@@ -187,17 +241,18 @@ def _arbeitsverzeichnis(einstellungen: dict, job_wahl: str = "") -> str:
Standard — und ist damit der Wert, der bei Vollautomatik-Rips greift, bei
denen niemand gefragt wird.
"""
# posixpath statt os.path: Das sind IMMER Container-Pfade (/app/media/...),
# auch wenn ein nativer Windows-Worker dieses Modul lädt — der übersetzt
# sie erst später mit pfad_lokal(). os.path.normpath macht unter Windows
# Backslashes daraus, und dann greift die MEDIA_ROOT-Prüfung nicht mehr.
wurzel, vorgabe, frei = wurzeln or _betriebs_wurzeln()
for kandidat in (job_wahl, einstellungen.get("workDir")):
wert = (kandidat or "").strip()
if wert:
normalisiert = posixpath.normpath(wert)
if unter_wurzel(normalisiert, MEDIA_ROOT):
return normalisiert
return RAW_DIR
if not wert:
continue
normalisiert = _normalisiert(wert)
# Nativ zählt die Wahl des Nutzers — dort IST sein Laufwerk die
# Grenze. Im Container bleibt die Wurzelprüfung: Ein Pfad ausserhalb
# von /app/media wäre dort ein Pfad ins Nichts.
if frei or unter_wurzel(normalisiert, wurzel):
return normalisiert
return vorgabe
def _frei_bytes(pfad: str) -> int: