diff --git a/docker/api/main.py b/docker/api/main.py index 83deec0..1a8b20b 100644 --- a/docker/api/main.py +++ b/docker/api/main.py @@ -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", diff --git a/docker/api/test_api_smoke.py b/docker/api/test_api_smoke.py index 1b2b167..3462e10 100644 --- a/docker/api/test_api_smoke.py +++ b/docker/api/test_api_smoke.py @@ -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"