Dockerized media library scanner for NAS drives. Identifies movies and series from filenames, fetches posters via the public IMDB suggestion API (no key required), and exposes a dark-themed web UI with multilingual search support. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
from fastapi import APIRouter, HTTPException, Request
|
|
|
|
from database import ScanHistory, get_session
|
|
from scheduler import (
|
|
get_active_scan_id,
|
|
is_scan_running,
|
|
trigger_full_scan,
|
|
trigger_incremental_scan,
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/scan", tags=["scan"])
|
|
|
|
|
|
@router.post("/full")
|
|
def start_full_scan(request: Request):
|
|
settings = request.app.state.settings
|
|
if is_scan_running():
|
|
raise HTTPException(status_code=409, detail="A scan is already running")
|
|
scan_id = trigger_full_scan(settings)
|
|
if scan_id is None:
|
|
raise HTTPException(status_code=409, detail="A scan is already running")
|
|
return {"scan_id": scan_id, "type": "full", "status": "running"}
|
|
|
|
|
|
@router.post("/incremental")
|
|
def start_incremental_scan(request: Request):
|
|
settings = request.app.state.settings
|
|
if is_scan_running():
|
|
raise HTTPException(status_code=409, detail="A scan is already running")
|
|
scan_id = trigger_incremental_scan(settings)
|
|
if scan_id is None:
|
|
raise HTTPException(status_code=409, detail="A scan is already running")
|
|
return {"scan_id": scan_id, "type": "incremental", "status": "running"}
|
|
|
|
|
|
@router.get("/status/{scan_id}")
|
|
def get_scan_status(scan_id: int):
|
|
session = get_session()
|
|
try:
|
|
record = session.query(ScanHistory).get(scan_id)
|
|
if record is None:
|
|
raise HTTPException(status_code=404, detail="Scan not found")
|
|
return {
|
|
"scan_id": record.id,
|
|
"type": record.scan_type,
|
|
"status": record.status,
|
|
"started_at": record.started_at.isoformat() if record.started_at else None,
|
|
"finished_at": record.finished_at.isoformat() if record.finished_at else None,
|
|
"files_found": record.files_found,
|
|
"files_added": record.files_added,
|
|
"files_removed": record.files_removed,
|
|
"files_unmatched": record.files_unmatched,
|
|
"error_message": record.error_message,
|
|
}
|
|
finally:
|
|
session.close()
|
|
|
|
|
|
@router.post("/fetch-alt-titles")
|
|
def fetch_alt_titles_now():
|
|
"""Trigger a Wikidata alt-title fetch in the background (no file scan)."""
|
|
def _run():
|
|
session = get_session()
|
|
try:
|
|
_fetch_alt_titles(session)
|
|
finally:
|
|
session.close()
|
|
threading.Thread(target=_run, daemon=True, name="alt-titles-fetch").start()
|
|
return {"status": "started"}
|
|
|
|
|
|
@router.get("/history")
|
|
def get_scan_history():
|
|
session = get_session()
|
|
try:
|
|
records = (
|
|
session.query(ScanHistory)
|
|
.order_by(ScanHistory.started_at.desc())
|
|
.limit(20)
|
|
.all()
|
|
)
|
|
return [
|
|
{
|
|
"scan_id": r.id,
|
|
"type": r.scan_type,
|
|
"status": r.status,
|
|
"started_at": r.started_at.isoformat() if r.started_at else None,
|
|
"finished_at": r.finished_at.isoformat() if r.finished_at else None,
|
|
"files_found": r.files_found,
|
|
"files_added": r.files_added,
|
|
"files_removed": r.files_removed,
|
|
"files_unmatched": r.files_unmatched,
|
|
"error_message": r.error_message,
|
|
}
|
|
for r in records
|
|
]
|
|
finally:
|
|
session.close()
|