Jikan/MAL-Quelle + Job-Titel + Abbrechen-Knopf + Dark-Hover + Ordner-Verwaltung
Ampel / ampel (push) Failing after 38s
Ampel / ampel (push) Failing after 38s
Commander-Befunde (Screenshots 23.07. abends):
- Jikan (MyAnimeList, kostenlos OHNE Key) als Anime-Quelle vor OMDb —
waehlt per Titel-Aehnlichkeit (SequenceMatcher, Schwelle 0.55) statt
blind Treffer 1; damit trifft "Evangelion 2.22" den exakten Film
- Job-Titel: POST /jobs uebernimmt den erkannten Disc-Titel aus dem
DISC_CACHE — Schluss mit "Unbekannt" in der Queue
- Kooperativer Job-Abbruch: POST /jobs/{id}/cancel setzt canceling,
der Worker prueft das Flag bei jedem Fortschritts-Update und killt
den Encoder-Prozess sauber (RipAbbruch); UI-Knopf "Abbrechen" fuer
laufende, Status-Badge "Wird abgebrochen"
- Dark-Mode: Job-Zeilen-Hover war hart hell (hover:bg-slate-50)
- Speicherziele: lokaler Ordner-Browser mit "Ordner anlegen"
(GET /browse + POST /browse/mkdir, nur unter /app/media)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+48
-1
@@ -262,8 +262,15 @@ async def create_job(request: JobCreateRequest):
|
||||
raise HTTPException(status_code=404, detail=f"Laufwerk {device_path} nicht gefunden")
|
||||
ziel = _validiere_ziel(request.target_dir)
|
||||
|
||||
# Titel aus der Disc-Erkennung übernehmen — sonst steht im Job "Unbekannt"
|
||||
titel = request.title
|
||||
if not titel:
|
||||
disc = DISC_CACHE.get(device_path)
|
||||
if disc and not disc.get("_laeuft"):
|
||||
titel = disc.get("title")
|
||||
|
||||
job_id = str(uuid.uuid4())
|
||||
await asyncio.to_thread(db.insert_job, job_id, device_path, None, request.title, ziel)
|
||||
await asyncio.to_thread(db.insert_job, job_id, device_path, None, titel, ziel)
|
||||
await asyncio.to_thread(
|
||||
db.add_log, "info", "api",
|
||||
f"Job {job_id} angelegt für {device_path}" + (f" → {ziel}" if ziel else ""),
|
||||
@@ -350,6 +357,24 @@ async def retry_transcode(job_id: str):
|
||||
return {"id": job_id, "status": "transcoding"}
|
||||
|
||||
|
||||
@app.post("/jobs/{job_id}/cancel")
|
||||
async def cancel_job(job_id: str):
|
||||
"""Bittet den Worker, den Job abzubrechen (kooperativ über die DB).
|
||||
|
||||
Der Worker prüft das Flag bei jedem Fortschritts-Update und beendet den
|
||||
Encoder-Prozess sauber — kein Celery-Task-ID-Tracking nötig.
|
||||
"""
|
||||
job = await asyncio.to_thread(db.get_job, job_id)
|
||||
if not job:
|
||||
raise HTTPException(status_code=404, detail="Job nicht gefunden")
|
||||
if job["status"] in ("completed", "failed"):
|
||||
raise HTTPException(status_code=409, detail="Job ist bereits beendet")
|
||||
|
||||
await asyncio.to_thread(db.update_job, job_id, status="canceling")
|
||||
await asyncio.to_thread(db.add_log, "warning", "api", f"Job {job_id}: Abbruch angefordert")
|
||||
return {"id": job_id, "status": "canceling"}
|
||||
|
||||
|
||||
@app.get("/capabilities")
|
||||
async def capabilities():
|
||||
"""Welche Encoder sind auf welchen Workern WIRKLICH verfügbar?
|
||||
@@ -453,6 +478,28 @@ async def browse(path: str = MEDIA_ROOT):
|
||||
return {"path": normalisiert, "parent": eltern, "dirs": ordner}
|
||||
|
||||
|
||||
class MkdirRequest(BaseModel):
|
||||
path: str
|
||||
name: str
|
||||
|
||||
|
||||
@app.post("/browse/mkdir", status_code=201)
|
||||
async def browse_mkdir(request: MkdirRequest):
|
||||
"""Neuen Ordner unter /app/media anlegen (Speicherziele-Verwaltung)."""
|
||||
basis = os.path.normpath(request.path)
|
||||
if not basis.startswith(MEDIA_ROOT):
|
||||
raise HTTPException(status_code=422, detail=f"Nur Pfade unter {MEDIA_ROOT}")
|
||||
name = request.name.strip()
|
||||
if not name or "/" in name or "\\" in name or name.startswith("."):
|
||||
raise HTTPException(status_code=422, detail="Ungültiger Ordnername")
|
||||
ziel = os.path.join(basis, name)
|
||||
try:
|
||||
await asyncio.to_thread(os.makedirs, ziel, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Anlegen fehlgeschlagen: {e}")
|
||||
return {"path": ziel}
|
||||
|
||||
|
||||
@app.get("/setup")
|
||||
async def setup_status():
|
||||
"""First-Run-Erkennung: wurde der Einrichtungs-Assistent abgeschlossen?"""
|
||||
|
||||
Reference in New Issue
Block a user