fix(windows): „Disc wird gelesen" blieb waehrend des ganzen Rips stehen
Ampel / ampel (push) Successful in 1m13s
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:
co-authored by
Claude Opus 5
parent
548742e771
commit
1a529f4e75
+27
-1
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user