Drei Befunde des Commanders vom 28.08.2026, alle drei gemessen.
## 1. „Was ist mit dem Cover auf Windows Rippy?"
Es gab keins, weil es keinen Treffer gab. `_scan_video` verlangte woertliche
Gleichheit:
if movie.get("title", "").lower() == kandidat.lower():
An seiner Disc gemessen:
'Evangelion 2.22' 1 Treffer Evangelion: 2.0 You Can (Not) Advance
'Evangelion' 20 Treffer irgendein Evangelion
Der EINZIGE Treffer auf den vollen Disc-Titel war der richtige Film, mit
Poster — und wurde verworfen. Jetzt zaehlt die SPEZIFITAET der Anfrage: Wer
auf den vollen Disc-Titel hoechstens drei Treffer bekommt, hat gefragt wie
jemand, der weiss was er sucht. Ergebnis an derselben Disc:
Evangelion: 2.0 You Can (Not) Advance / 2009 / 80 % / Poster + dt. Text
## 2. „Warum heisst das hier noch container platte? … Waere es moeglich das
## Arbeitsverzeichnis zu aendern? momentan geht das nicht."
Beide Haelften gehen auf EINE Zeile zurueck: `MEDIA_ROOT = "/app/media"` war
zugleich Vorgabe UND Pfadgrenze. Auf Windows gibt es den Ordner nicht:
* `/storage-targets` fing den OSError und gab still [] zurueck — die Auswahl
hatte genau einen Eintrag. Das ist „momentan geht das nicht".
* Dessen Text war fest verdrahtet „(Container-Platte)".
* `/browse` antwortete auf jeden Pfad mit 422.
Die Grenze faellt nicht weg, sie wird betriebsabhaengig: Container und
Kopflos-Betrieb bedienen ein Netz, die native App den Menschen davor.
Gemessen auf seinem PC: 7 Laufwerke zur Auswahl, X/Y/Z mit je 2,2 TB frei.
## 3. „Failed to remove temporary directory: …_MEI0000b0882"
GEMESSEN: 20 zurueckgelassene _MEI-Ordner mit 1,1 GB. Ursache: `Popen` ohne
`env=` reicht PyInstallers Auspack-Zeiger an die Kinder weiter (--dienst und
--oeffnen). Beide laufen dann im Ordner des Elternprozesses, der sich zuerst
beendet und ihn loeschen will. Waere das TEILWEISE geglueckt, haetten Dienst
und Fenster mitten im Betrieb ihre Dateien verloren.
Nebenbefund: test_setup_fenster scheiterte in jeder Umgebung ohne pywebview.
794 Tests gruen, ruff sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
3.6 KiB
Python
83 lines
3.6 KiB
Python
"""Welche Pfade darf die API anfassen — und wo ist „oben"?
|
|
|
|
## Warum diese Grenze verschiebbar sein muss (Befund 28.08.2026)
|
|
|
|
`MEDIA_ROOT = "/app/media"` war zugleich Vorgabe UND Pfadgrenze. Im Container
|
|
ist beides richtig. Auf dem Windows-PC des Commanders war beides falsch, und
|
|
die Folge war eine Oberflaeche, die stillschweigend nichts konnte:
|
|
|
|
* `/storage-targets` lieferte `[]` (der Ordner existiert dort nicht),
|
|
* `/browse` antwortete auf JEDEN Pfad mit 422,
|
|
* im Auswahlfeld fuer das Arbeitsverzeichnis stand genau ein Eintrag.
|
|
|
|
Sein Befund dazu: „Wäre es möglich das Arbeitsverzeichnis zu ändern?
|
|
momentan geht das nicht."
|
|
|
|
Die Grenze faellt aber NICHT einfach weg. Im Container haengt die API im
|
|
Netz — eine Weboberflaeche, die jeden Pfad des Wirts ausliefern kann, ist ein
|
|
Loch. Sie gilt nur dort nicht, wo Rippy den Menschen bedient, der vor dem
|
|
Rechner sitzt. Diese Tests halten beide Haelften fest.
|
|
"""
|
|
|
|
import main
|
|
|
|
|
|
# ── Die Grenze gilt, wo zugehoert wird ──────────────────────────────────
|
|
def test_im_container_gilt_die_wurzel_weiter():
|
|
"""Der Docker-Weg darf sich durch die Reparatur NICHT lockern."""
|
|
assert main.pfad_erlaubt("/app/media/movies", "/app/media", frei=False)
|
|
assert not main.pfad_erlaubt("/etc/passwd", "/app/media", frei=False)
|
|
# Der Klassiker: beginnt mit der Wurzel, liegt aber ausserhalb.
|
|
assert not main.pfad_erlaubt("/app/media-boese/x", "/app/media", frei=False)
|
|
|
|
|
|
def test_nativ_ist_auch_eine_freigabe_erlaubt():
|
|
"""Sein Ziel ist `\\\\192.168.179.62\\rippy\\movies` — das liegt unter gar
|
|
keiner lokalen Wurzel. Mit der alten Regel war es unerreichbar."""
|
|
assert main.pfad_erlaubt(r"\\192.168.179.62\rippy\movies", "/app/media", frei=True)
|
|
assert main.pfad_erlaubt(r"D:\Rippy-Arbeit", "/app/media", frei=True)
|
|
|
|
|
|
def test_ein_leerer_pfad_ist_nie_erlaubt():
|
|
"""Sonst wuerde aus einem vergessenen Feld ein Zugriff auf `/`."""
|
|
assert not main.pfad_erlaubt("", "/app/media", frei=True)
|
|
assert not main.pfad_erlaubt("", "/app/media", frei=False)
|
|
|
|
|
|
# ── Wo ist oben ─────────────────────────────────────────────────────────
|
|
def test_an_der_wurzel_ist_schluss():
|
|
"""Im Container. `None` heisst: kein Knopf „nach oben"."""
|
|
assert main.eltern_von("/app/media", "/app/media", frei=False) is None
|
|
assert main.eltern_von("/app/media/movies", "/app/media", frei=False) == "/app/media"
|
|
|
|
|
|
def test_ueber_der_laufwerkswurzel_steht_die_laufwerksliste():
|
|
"""Zwei Fallen auf einmal.
|
|
|
|
`os.path.dirname("C:\\\\")` ist wieder `"C:\\\\"` — ein Knopf „nach oben",
|
|
der auf denselben Ordner zeigt, sieht aus wie ein Fehler. Und ueber der
|
|
Laufwerkswurzel steht nicht *nichts*, sondern die Liste der Laufwerke:
|
|
Sonst kaeme man von `D:\\` nie zu `C:\\`, und genau dort ist Platz fuer
|
|
100 GB Rohdaten.
|
|
"""
|
|
import ntpath
|
|
import os
|
|
|
|
echt = os.path.dirname
|
|
os.path.dirname = ntpath.dirname # Windows-Pfade auch auf Linux
|
|
try:
|
|
assert main.eltern_von("C:\\", "D:\\Rippy", frei=True) == ""
|
|
assert main.eltern_von("C:\\Users\\Tobi", "D:\\Rippy", frei=True) == "C:\\Users"
|
|
finally:
|
|
os.path.dirname = echt
|
|
|
|
|
|
def test_die_wurzel_kommt_aus_dem_betrieb_und_ist_nie_leer():
|
|
"""Eine leere Wurzel wuerde jede Pruefung durchwinken."""
|
|
assert main.medien_wurzel()
|
|
|
|
|
|
def test_laufwerke_ohne_windows_sind_eine_leere_liste():
|
|
"""Auf dem Linux-Runner der Ampel darf das kein Fehler sein."""
|
|
assert isinstance(main.betrieb_laufwerke(), list)
|