Dashboard-Disc-Karte: Auto-Pre-Scan beim Einlegen — man SIEHT was drinliegt
Ampel / ampel (push) Successful in 41s

Commander-Befund: Disc wurde erkannt, aber das UI verriet nicht WAS.
- Watcher stoesst beim Einlegen (und beim Start mit bereits eingelegter
  Disc) automatisch den Pre-Scan an, Ergebnis landet im DISC_CACHE und
  im Log ("Disc erkannt: Titel (Jahr) [typ, Confidence]")
- GET /devices liefert das disc-Feld mit Titel/Jahr/Typ/Confidence/Poster
- UI: prominente Karte "Im Laufwerk erkannt" mit Poster (TMDB relativ
  oder OMDb absolut), Beschreibung und Rippen-Start (Ziel-Dialog)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-23 19:55:18 +02:00
parent dfef585ec8
commit e350523f7e
2 changed files with 103 additions and 2 deletions
+38 -2
View File
@@ -63,8 +63,34 @@ async def startup_event():
asyncio.create_task(disc_watcher())
# Auto-Pre-Scan-Ergebnisse je Laufwerk: das Dashboard zeigt damit sofort,
# WAS im Laufwerk liegt (Titel/Jahr/Poster), ohne dass jemand klicken muss.
DISC_CACHE: Dict[str, Dict] = {}
async def _auto_prescan(pfad: str):
"""Identifiziert die eingelegte Disc im Hintergrund und cached das Ergebnis."""
if DISC_CACHE.get(pfad, {}).get("_laeuft"):
return
DISC_CACHE[pfad] = {"_laeuft": True, "title": "Wird erkannt…"}
try:
prescan = PreScan()
ergebnis = await asyncio.to_thread(prescan.scan, pfad)
DISC_CACHE[pfad] = ergebnis.to_dict()
db.add_log(
"info", "watcher",
f"Disc erkannt: {ergebnis.title}"
+ (f" ({ergebnis.year})" if ergebnis.year else "")
+ f" [{ergebnis.disc_type}, Confidence {ergebnis.confidence:.0%}] auf {pfad}",
)
except Exception as e:
DISC_CACHE.pop(pfad, None)
print(f"Auto-Pre-Scan {pfad}: {e}")
async def disc_watcher():
"""Disc-Wache: pollt die Laufwerke und protokolliert Einwurf/Auswurf.
"""Disc-Wache: pollt die Laufwerke, protokolliert Einwurf/Auswurf und
stößt beim Einlegen automatisch den Pre-Scan an (Dashboard-Disc-Karte).
Ersetzt den nie gebauten udev-Daemon aus dem KONZEPT: udev funktioniert im
Container nicht sinnvoll (kein udevd) — ein 3-Sekunden-Poll per ioctl ist
@@ -79,11 +105,17 @@ async def disc_watcher():
except OSError:
continue
vorher = bekannt.get(pfad)
if vorher is not None and status != vorher:
if vorher is None:
# Erststart: liegt schon eine Disc drin, direkt erkennen
if status == CDS_DISC_OK:
asyncio.create_task(_auto_prescan(pfad))
elif status != vorher:
if status == CDS_DISC_OK:
db.add_log("info", "watcher", f"Disc eingelegt: {pfad}")
asyncio.create_task(_auto_prescan(pfad))
elif status in (CDS_NO_DISC, CDS_TRAY_OPEN) and vorher == CDS_DISC_OK:
db.add_log("info", "watcher", f"Disc entfernt: {pfad}")
DISC_CACHE.pop(pfad, None)
bekannt[pfad] = status
except Exception as e:
print(f"Disc-Wache: {e}")
@@ -147,6 +179,7 @@ class Device(BaseModel):
status: str
model: Optional[str] = None
serial: Optional[str] = None
disc: Optional[Dict] = None # Auto-Pre-Scan-Ergebnis (Titel/Jahr/Poster)
def _job_row_to_model(zeile: dict) -> Job:
@@ -323,6 +356,9 @@ async def get_devices():
geraete = []
for pfad in device_discovery.list_optical_devices():
info = await asyncio.to_thread(device_discovery.device_info, pfad)
disc = DISC_CACHE.get(pfad)
if disc and not disc.get("_laeuft"):
info["disc"] = disc
geraete.append(Device(**info))
return geraete