fix(auswurf): CDROMEJECT meldete Erfolg und tat nichts - erst entriegeln
Ampel / ampel (push) Successful in 29s

Commander-Meldung: "Den Button gibt es in den Settings, aber es passiert nicht,
das Laufwerk geht nicht auf." Am laufenden System nachgestellt, mit der Disc, die
gerade drin lag:

  wirf_disc_aus("/dev/sr0")      -> True
  CDROM_DRIVE_STATUS danach      -> 4 (Disc drin)

Das ioctl wird also ANGENOMMEN und tut nichts. Ursache: MakeMKV verriegelt
waehrend des Rips die Laufwerkstuer (CDROM_LOCKDOOR 1) und entriegelt sie nicht
wieder. Ein verriegeltes Laufwerk quittiert den Auswurf trotzdem mit Erfolg.
Gegenprobe an derselben Disc:

  CDROM_LOCKDOOR 0 + CDROMEJECT  -> Status 2 (SCHUBLADE OFFEN)

Genau das macht das Werkzeug `eject` immer: erst entriegeln, dann auswerfen.

Zweite Haelfte des Fixes, und die wichtigere: Das Ergebnis wird GEPRUEFT statt
geglaubt. Bisher gab wirf_disc_aus True zurueck, sobald das ioctl nicht geworfen
hatte - und ins Log kam "Disc ausgeworfen", waehrend die Schublade zu blieb.
Deshalb ist der Fehler in v3.14 durchgerutscht: Dort wurde richtig festgestellt,
dass die Einstellung von niemandem gelesen wurde, und danach WURDE sie gelesen -
ausgeworfen wurde weiterhin nicht. Jetzt wird das Laufwerk gefragt (bis zu 5 s,
die Schublade braucht ein bis zwei), und "kein Datentraeger" zaehlt mit, weil ein
Slot-Laufwerk keine Schublade hat.

Dieselbe Luecke steckte im Auswurf-Knopf der API (devices.eject) - dort mit
Klartext-Fehler, wenn die Disc drin bleibt.

Der Auswurf sitzt uebrigens schon an der richtigen Stelle: nach dem Rip, VOR dem
Einreihen der Kompression. Und das Laufwerk ist waehrend `transcoding` frei -
has_active_job blockiert nur bei pending/running, der Worker laeuft mit 4 Slots.
Die zweite Disc parallel war also nur am nicht aufgehenden Laufwerk gescheitert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-26 14:03:10 +02:00
parent 2587fe63af
commit be04a9772e
3 changed files with 210 additions and 17 deletions
+91 -16
View File
@@ -25,34 +25,109 @@ class RipAbbruch(Exception):
Nutzer den Job abgebrochen hat (Status 'canceling' in der DB)."""
# include/uapi/linux/cdrom.h — dasselbe ioctl wie in api/devices.py
# include/uapi/linux/cdrom.h — dieselben ioctls wie in api/devices.py
CDROMEJECT = 0x5309
CDROM_LOCKDOOR = 0x5329 # 1 = Tür verriegeln, 0 = entriegeln
CDROM_DRIVE_STATUS = 0x5326
CDROMCLOSETRAY = 0x5319
# Antworten von CDROM_DRIVE_STATUS (cdrom.h)
CDS_NO_DISC = 1
CDS_TRAY_OPEN = 2
CDS_DRIVE_NOT_READY = 3
CDS_DISC_OK = 4
# Wie lange auf die Schublade gewartet wird. Ein Laufwerk braucht dafür ein
# bis zwei Sekunden; fünf sind reichlich und blockieren nichts Wichtiges.
AUSWURF_WARTEN_SEKUNDEN = 5
def wirf_disc_aus(device_path: str) -> bool:
"""Wirft die Disc aus; gibt zurück, ob es geklappt hat. Wirft NIE.
def _auswurf_geglueckt(status: int) -> bool:
"""Ist die Disc nach dem Auswurf wirklich draußen? (pure Funktion)
Befund 25.07.2026: Die Einstellung „Automatischer Auswurf" (Standard: ein,
Beschreibung „Disc nach erfolgreichem Ripping automatisch auswerfen") wurde
von niemandem gelesen — bei DVD/Blu-ray warf Rippy nie aus.
Sowohl „Schublade offen" als auch „kein Datenträger" zählen: Ein
Slot-Laufwerk hat keine Schublade und meldet nach dem Auswerfen CDS_NO_DISC.
"""
return status in (CDS_TRAY_OPEN, CDS_NO_DISC)
def wirf_disc_aus(device_path: str, ioctl_fn=None, oeffnen=None,
schliessen=None, warten=None) -> bool:
"""Wirft die Disc aus und PRÜFT, ob sie draußen ist. Wirft NIE.
## Warum `CDROMEJECT` allein nicht genügt (Befund 26.07.2026, gemessen)
Der Commander meldete: „Der Button gibt es in den Settings, aber es passiert
nicht, das Laufwerk geht nicht auf." Am laufenden System nachgestellt:
wirf_disc_aus("/dev/sr0") → True
CDROM_DRIVE_STATUS danach → 4 (Disc drin)
Das ioctl wird also **angenommen und tut nichts**. Ursache: MakeMKV
verriegelt während des Rips die Laufwerkstür (`CDROM_LOCKDOOR 1`) und
entriegelt sie nicht wieder. Ein verriegeltes Laufwerk quittiert den Auswurf
trotzdem mit Erfolg. Deshalb macht das Werkzeug `eject` immer beides:
erst entriegeln, dann auswerfen. Gegenprobe an derselben Disc:
CDROM_LOCKDOOR 0 + CDROMEJECT → Status 2 (SCHUBLADE OFFEN)
## Und deshalb wird das Ergebnis geprüft, nicht geglaubt
Genau diese Sorte Fehler ist zweimal durchgerutscht: In v3.14 stand hier
„Auswurf tat nichts, jetzt entscheidet die Einstellung" — die Einstellung
wurde danach wirklich gelesen, nur ausgeworfen wurde weiterhin nicht, und im
Log stand „Disc ausgeworfen". Ein Rückgabewert eines ioctls beweist nichts;
gefragt wird jetzt das Laufwerk.
Bewusst hier und nicht in detection.py: das Modul ist ein byteweiser
Zwilling der API-Kopie. `fcntl` gibt es nur unter Linux — der native
Windows-Worker lädt ripping.py ebenfalls, rippt dort aber nie.
Die drei Parameter sind nur zum Testen einspritzbar (kein echtes Laufwerk).
"""
try:
from fcntl import ioctl
except ImportError: # Windows
return False
try:
fd = os.open(device_path, os.O_RDONLY | os.O_NONBLOCK)
if ioctl_fn is None:
try:
ioctl(fd, CDROMEJECT, 0)
finally:
os.close(fd)
return True
from fcntl import ioctl
except ImportError: # Windows — dieser Worker rippt nie
return False
ioctl_fn = ioctl
if warten is None:
import time
warten = time.sleep
oeffnen = oeffnen or (lambda p: os.open(p, os.O_RDONLY | os.O_NONBLOCK))
schliessen = schliessen or os.close
try:
fd = oeffnen(device_path)
except OSError:
return False
try:
# Entriegeln ist der entscheidende Schritt. Scheitert er, wird der
# Auswurf trotzdem versucht — bei einem nicht verriegelten Laufwerk
# (oder einem, das das ioctl nicht kennt) klappt er ohnehin.
try:
ioctl_fn(fd, CDROM_LOCKDOOR, 0)
except OSError:
pass
try:
ioctl_fn(fd, CDROMEJECT, 0)
except OSError:
return False
# Nachsehen statt hoffen: Die Schublade braucht ein bis zwei Sekunden.
for _ in range(AUSWURF_WARTEN_SEKUNDEN):
try:
if _auswurf_geglueckt(ioctl_fn(fd, CDROM_DRIVE_STATUS, 0)):
return True
except OSError:
return False
warten(1)
return False
finally:
try:
schliessen(fd)
except OSError:
pass
def check_makemkv_installed() -> bool: