fix(windows): „Disc wird gelesen" blieb waehrend des ganzen Rips stehen
Ampel / ampel (push) Successful in 1m13s

Commander: „der ‚Disk wird gelesen' panel braucht eeeeewig. Der Rip ist
bereits bei 30% und er liest immernoch."

Genau so war es — und die Erkennung kam auch nie zum Ende.

## Warum

`_auto_prescan` hatte GENAU EINE Absicherung: nicht zweimal gleichzeitig
(`_laeuft`). Ob auf dem Laufwerk gerade ein Rip laeuft, hat es nie gefragt.

Waehrend eines Rips haelt `makemkvcon` das Laufwerk. Ein zweites
`makemkvcon info` daneben wartet, bis es seine Zeitgrenze erreicht (gemessen:
eine Laufwerks-Abfrage braucht dann 14 s statt 5, der `info`-Aufruf laeuft in
seine 120 s). Solange steht die Marke `_laeuft` — und damit das Panel, das
ich heute frueh genau dafuer gebaut habe.

Es gibt keinen Grund, waehrend eines Rips zu scannen: Das Laufwerk ist
belegt, und **welche Disc drin ist, wissen wir bereits** — der Job laeuft ja
auf ihr.

## Zwei Stellen, weil es zwei Wege hinein gibt

1. `_auto_prescan` bricht ab, wenn auf dem Geraet ein Job laeuft. Das
   verhindert jeden Scan, der NACH dem Rip-Start angestossen wird.
2. Der Job-Start raeumt eine haengende Marke weg. Ein Scan, der KURZ VORHER
   begann, haelt sie sonst bis zu seinem Ende fest. Verloren geht dabei
   nichts: Unter `_laeuft` steht nur der Platzhalter, und der laufende Scan
   traegt sein Ergebnis spaeter selbst nach.

880 Tests gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-29 15:54:48 +02:00
co-authored by Claude Opus 5
parent 548742e771
commit 1a529f4e75
2 changed files with 103 additions and 1 deletions
+27 -1
View File
@@ -186,9 +186,27 @@ def _duplikat_suchen(fingerprint: str):
async def _auto_prescan(pfad: str):
"""Identifiziert die eingelegte Disc im Hintergrund und cached das Ergebnis."""
"""Identifiziert die eingelegte Disc im Hintergrund und cached das Ergebnis.
## Warum ein laufender Job hier alles stoppt (Befund 29.08.2026)
> „der Disk wird gelesen' panel braucht eeeeewig. Der Rip ist bereits bei
> 30% und er liest immernoch."
Genau so war es — und die Erkennung kam auch nie zum Ende. Waehrend eines
Rips haelt `makemkvcon` das Laufwerk; ein zweites `makemkvcon info`
daneben wartet, bis es seine Zeitgrenze erreicht (gemessen: eine
Laufwerks-Abfrage braucht dann 14 s statt 5). Die Marke `_laeuft` blieb
solange stehen, also stand auch das Panel.
Es gibt keinen Grund, waehrend eines Rips zu scannen: Das Laufwerk ist
belegt, und **welche Disc drin ist, wissen wir bereits** — der Job laeuft
ja auf ihr.
"""
if DISC_CACHE.get(pfad, {}).get("_laeuft"):
return
if await asyncio.to_thread(db.has_active_job, pfad):
return
DISC_CACHE[pfad] = {"_laeuft": True, "title": "Wird erkannt…"}
try:
prescan = PreScan()
@@ -1254,6 +1272,14 @@ async def create_job(request: JobCreateRequest):
meta_json = json.dumps(meta_dict) if meta_dict else None
job_id = str(uuid.uuid4())
# Eine noch laufende Disc-Erkennung ist ab jetzt gegenstandslos: Das
# Laufwerk gehoert dem Rip, und WELCHE Disc drin ist, wissen wir. Ohne
# das Wegraeumen stuende „Disc wird gelesen" bis zum Ende des Rips
# (Commander 29.08.2026: „Der Rip ist bereits bei 30% und er liest
# immernoch"). Der laufende Scan traegt sein Ergebnis spaeter ohnehin
# selbst nach.
if (DISC_CACHE.get(device_path) or {}).get("_laeuft"):
DISC_CACHE.pop(device_path, None)
await asyncio.to_thread(db.insert_job, job_id, device_path, None, titel, ziel, meta_json)
await asyncio.to_thread(
db.add_log, "info", "api",
+76
View File
@@ -758,3 +758,79 @@ def test_die_jobliste_traegt_die_restzeit_felder():
for feld in ("eta_sekunden", "eta_text", "can_retry", "retry_art"):
assert feld in main.Job.model_fields, feld
# ── Kein Vor-Scan waehrend eines Rips (Befund 29.08.2026) ───────────────
#
# Commander: „der Disk wird gelesen' panel braucht eeeeewig. Der Rip ist
# bereits bei 30% und er liest immernoch."
#
# Waehrend eines Rips haelt makemkvcon das Laufwerk. Ein zweites
# `makemkvcon info` daneben wartet bis zu seiner Zeitgrenze — und die Marke
# `_laeuft` blieb solange stehen, also stand auch das Panel. Es gibt keinen
# Grund, dann zu scannen: Das Laufwerk ist belegt, und WELCHE Disc drin ist,
# wissen wir — der Job laeuft ja auf ihr.
def test_kein_vorscan_solange_ein_job_auf_dem_laufwerk_laeuft(monkeypatch):
import asyncio
import main
gescannt = []
class NieBenutzt:
def scan(self, pfad):
gescannt.append(pfad)
raise AssertionError("waehrend eines Rips darf nicht gescannt werden")
monkeypatch.setattr(main, "PreScan", NieBenutzt)
monkeypatch.setattr(main.db, "has_active_job", lambda p: True)
main.DISC_CACHE.pop(r"\.\G:", None)
asyncio.run(main._auto_prescan(r"\.\G:"))
assert gescannt == []
assert r"\.\G:" not in main.DISC_CACHE, "auch keine Marke setzen"
def test_ohne_laufenden_job_wird_normal_gescannt(monkeypatch):
import asyncio
import main
class Ergebnis:
title, year, disc_type, confidence, fingerprint = "Akira", 1988, "Blu-ray", 0.9, "x"
def to_dict(self):
return {"title": "Akira"}
class Scanner:
def scan(self, pfad):
return Ergebnis()
monkeypatch.setattr(main, "PreScan", Scanner)
monkeypatch.setattr(main.db, "has_active_job", lambda p: False)
monkeypatch.setattr(main.db, "add_log", lambda *a, **k: None)
monkeypatch.setattr(main, "_duplikat_suchen", lambda f: None)
monkeypatch.setattr(main, "_auto_rip_wenn_aktiviert",
lambda p: asyncio.sleep(0))
main.DISC_CACHE.pop(r"\.\G:", None)
asyncio.run(main._auto_prescan(r"\.\G:"))
assert main.DISC_CACHE[r"\.\G:"]["title"] == "Akira"
main.DISC_CACHE.pop(r"\.\G:", None)
def test_der_job_start_raeumt_eine_haengende_erkennung_weg():
"""Eine Marke, die KURZ VOR dem Rip gesetzt wurde, bliebe sonst bis zum
Ende stehen — der Scan davor haelt sie ja fest."""
import inspect
import main
quelle = inspect.getsource(main.create_job) if hasattr(main, "create_job") else ""
if not quelle:
# Der Endpunkt heisst anders — dann ueber das Modul suchen.
quelle = inspect.getsource(main)
assert 'DISC_CACHE.pop(device_path, None)' in quelle, \
"beim Job-Start muss eine haengende Erkennungs-Marke weg"