fix(worker,api): Platten-Schutz griff nicht, Fortschritt log, Auswurf tat nichts
Vier Funde aus der Durchsicht, alle auf der VM gemessen.
1. DER PLATTEN-SCHUTZ AUS c065967 WAR WIRKUNGSLOS
_original_aufheben() entschied per os.stat().st_dev, ob umgehaengt oder
kopiert werden muss. Im Worker-Container gemessen - beides gleichzeitig wahr:
st_dev /app/temp = 2050
st_dev /app/media = 2050 → identisch
os.rename(...) → EXDEV, "Invalid cross-device link"
Der Kernel vergleicht bei rename() den MOUNT, nicht das Geraet. /app/temp
(Docker-Volume) und /app/media (Bind-Mount) sind zwei Mounts DERSELBEN
ext4-Partition. Die Pruefung sah "gleiches Dateisystem", uebersprang die
Platzpruefung, und shutil.move kopierte doch - 75 GB bei 37 GB frei. Der
Schutz haette genau den Schaden zugelassen, gegen den er gebaut wurde.
Jetzt wird os.rename VERSUCHT statt vorhergesagt: klappt es, ist es
umgehaengt und fertig; kommt EXDEV, steht die Kopie fest und ERST DANN wird
der Platz geprueft. Das ist keine Vermutung mehr, sondern die Antwort des
Kernels. Vier Tests in test_original_aufheben.py, darunter genau der Fall,
der die Platte fuellte. Die zwei alten Tests in test_medien.py sind dorthin
gewandert - sie taeuschten per gefaelschtem os.stat "verschiedene
Dateisysteme" vor, also genau die Annahme, an der der Schutz scheiterte.
2. DIE FORTSCHRITTSANZEIGE ZEIGTE DEN SCAN, NICHT DEN ENCODE
get_progress_from_line matchte jede Zahl vor einem Prozentzeichen. HandBrake
gibt Prozente aber in drei Phasen aus (Formatstrings aus dem Binary gelesen):
Scanning title %d of %d, preview %d, %.2f %% → laeuft VOR dem
Encode bis 100 %
Encoding: task %d of %d, %.2f %% (%.2f fps, avg → der echte Wert
Encoding: task %d of %d, Searching for start time, ... → Vorlauf
Dazu warf `if progress > 0` im Aufrufer jeden Wert unter 1,00 % weg. Live
beobachtet: Anzeige stand auf 99 %, der Encode bei 1,06 %; sie fiel erst auf
1, als der Encode die 1-%-Marke ueberschritt. Jetzt wird nur die
Encoding-Zeile gelesen, `task N of M` mitgerechnet (sonst springt die
Anzeige bei Zwei-Pass-Presets mitten in der Datei zurueck), und -1 heisst
"keine Angabe" - dasselbe Muster wie bei get_progress_from_prgv.
3. "AUTOMATISCHER AUSWURF" WURDE VON NIEMANDEM GELESEN
Die Einstellung (Standard: ein, "Disc nach erfolgreichem Ripping automatisch
auswerfen") kam in keiner Zeile Backend-Code vor. DVD/Blu-ray warfen deshalb
NIE aus, Audio-CDs IMMER, weil abcde `-x` fest verdrahtet bekam. Jetzt
entscheidet die Einstellung beides: wirf_disc_aus() per CDROMEJECT-ioctl
(fcntl-guarded, der native Windows-Worker laedt das Modul auch) und `-x` nur
noch, wenn gewuenscht.
4. PFAD-PRUEFUNG FIEL AUF PRAEFIX-NAMEN HEREIN
Elf Stellen prueften mit nacktem startswith(MEDIA_ROOT). "/app/media-boese/x"
beginnt mit "/app/media", liegt aber ausserhalb - betroffen waren auch
/browse und /browse/mkdir, wo der Pfad vom Nutzer kommt. Neuer
Zwillings-Helfer unter_wurzel() in api/main.py und worker/tasks.py, alle elf
Stellen umgestellt, Tests in beiden.
Nebenbefund: _zielbasis() benutzte os.path.normpath - unter Windows werden
daraus Backslashes, die MEDIA_ROOT-Pruefung greift nicht mehr, und das
gewaehlte Ziel faellt still auf den Standard zurueck. Genau die Falle, die
_arbeitsverzeichnis() drei Zeilen weiter dokumentiert und mit posixpath
vermeidet. Live war es nie (nur aus rip_disc, das auf Windows verriegelt
ist), jetzt konsistent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,10 +43,43 @@ def test_handbrake_cmd_arbeitet_auf_datei_nicht_geraet():
|
||||
|
||||
|
||||
def test_handbrake_progress_parsing():
|
||||
# Testfund 22.07.: echtes HandBrake schreibt „45.50 %" MIT Leerzeichen
|
||||
# Testfund 22.07.: echtes HandBrake schreibt 45.50 % MIT Leerzeichen
|
||||
assert get_progress_from_line("Encoding: task 1 of 1, 45.50 %") == 45
|
||||
assert get_progress_from_line("Encoding: task 1 of 1, 100.00 %") == 100
|
||||
assert get_progress_from_line("kein Fortschritt hier") == 0
|
||||
# Echte Zeile mit fps-Anhang, wie sie im Binary steht
|
||||
assert get_progress_from_line(
|
||||
"Encoding: task 1 of 1, 12.34 % (5.67 fps, avg 4.32 fps, ETA 00h12m34s)"
|
||||
) == 12
|
||||
# Ein echtes 0 % ist eine ANGABE, keine Leermeldung
|
||||
assert get_progress_from_line("Encoding: task 1 of 1, 0.00 %") == 0
|
||||
|
||||
|
||||
def test_handbrake_progress_ignoriert_scan_durchlauf():
|
||||
"""Befund 25.07.2026 (Akira-UHD, live gemessen): HandBrake läuft VOR dem
|
||||
Encodieren einen Scan-Durchlauf, der ebenfalls Prozente ausgibt und dabei
|
||||
bis 100 % steigt. Die alte Regex nahm jede Zahl vor einem Prozentzeichen
|
||||
und meldete deshalb 99 %, während der Encode bei 1 % stand.
|
||||
|
||||
-1 heißt „keine Encode-Fortschrittszeile" — dasselbe Muster wie bei
|
||||
get_progress_from_prgv. Fremde Zeilen dürfen NIE als 0 % gelten.
|
||||
"""
|
||||
assert get_progress_from_line("Scanning title 1 of 1, preview 3, 30.00 %") == -1
|
||||
assert get_progress_from_line("Scanning title 1 of 1, preview 10, 100.00 %") == -1
|
||||
# Vorlauf-Phase: Prozente beziehen sich auf die Suche, nicht auf den Encode
|
||||
assert get_progress_from_line(
|
||||
"Encoding: task 1 of 1, Searching for start time, 42.00 %"
|
||||
) == -1
|
||||
assert get_progress_from_line("kein Fortschritt hier") == -1
|
||||
assert get_progress_from_line("Muxing: this may take awhile...") == -1
|
||||
assert get_progress_from_line("") == -1
|
||||
|
||||
|
||||
def test_handbrake_progress_rechnet_zwei_durchlaeufe_zusammen():
|
||||
"""Presets mit zwei Durchläufen zählen die Prozente je Durchlauf neu.
|
||||
Ohne Verrechnung sprang die Anzeige mitten in der Datei zurück auf 0."""
|
||||
assert get_progress_from_line("Encoding: task 1 of 2, 50.00 %") == 25
|
||||
assert get_progress_from_line("Encoding: task 2 of 2, 0.00 %") == 50
|
||||
assert get_progress_from_line("Encoding: task 2 of 2, 100.00 %") == 100
|
||||
|
||||
|
||||
def test_prgv_parsing():
|
||||
@@ -129,6 +162,20 @@ def test_abcde_cmd_hat_genau_ein_ausgabeformat():
|
||||
assert "-N" in cmd # nicht-interaktiv, sonst hängt der Worker
|
||||
|
||||
|
||||
def test_abcde_auswurf_folgt_der_einstellung():
|
||||
"""Befund 25.07.2026: `-x` (Auswurf) stand fest verdrahtet drin. Eine
|
||||
Audio-CD warf damit IMMER aus, eine DVD/Blu-ray NIE — und die Einstellung
|
||||
„Automatischer Auswurf" regelte keines von beidem, weil sie nirgends
|
||||
gelesen wurde."""
|
||||
assert "-x" in build_abcde_cmd("/dev/sr0", "/tmp/c.conf", auswerfen=True)
|
||||
assert "-x" not in build_abcde_cmd("/dev/sr0", "/tmp/c.conf", auswerfen=False)
|
||||
# Standard bleibt „auswerfen" — so war das Verhalten bisher
|
||||
assert "-x" in build_abcde_cmd("/dev/sr0", "/tmp/c.conf")
|
||||
# Die Config darf durch das weggefallene -x nicht verrutschen
|
||||
ohne = build_abcde_cmd("/dev/sr0", "/tmp/c.conf", auswerfen=False)
|
||||
assert ohne[ohne.index("-c") + 1] == "/tmp/c.conf"
|
||||
|
||||
|
||||
def test_abcde_config_enthaelt_zielverzeichnis():
|
||||
pfad = write_abcde_config("/app/media/cd/test123")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user