111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from .. import config
|
|
from ..db import BookRequest, get_session
|
|
from ..fsnames import decode_path, readable
|
|
from ..importer import matcher, mover, scanner
|
|
|
|
router = APIRouter(prefix="/api/import", tags=["import"])
|
|
|
|
|
|
def _orphaned(session: Session) -> list[BookRequest]:
|
|
"""Imported requests whose files are gone from the library - moved away or
|
|
deleted by hand. They are effectively missing again, so the scan should
|
|
suggest them just like an open request."""
|
|
imported = session.scalars(
|
|
select(BookRequest).where(BookRequest.status == "imported")
|
|
).all()
|
|
return [r for r in imported
|
|
if not r.imported_path or not Path(r.imported_path).exists()]
|
|
|
|
|
|
@router.get("/scan")
|
|
def scan(split_dirs: bool = False, session: Session = Depends(get_session)):
|
|
"""Scan the download dir and suggest matches against open requests."""
|
|
items = scanner.scan(Path(config.DOWNLOAD_DIR), split_dirs=split_dirs)
|
|
missing = session.scalars(
|
|
select(BookRequest).where(BookRequest.status == "missing")
|
|
).all()
|
|
orphaned = _orphaned(session)
|
|
return {
|
|
"download_dir": str(config.DOWNLOAD_DIR),
|
|
"items": matcher.best_matches(items, missing + orphaned),
|
|
# the UI offers these like open requests instead of "append parts"
|
|
"orphaned_request_ids": [r.id for r in orphaned],
|
|
}
|
|
|
|
|
|
class ImportItem(BaseModel):
|
|
path: str
|
|
is_dir: bool
|
|
files: list[str]
|
|
request_id: int
|
|
# add to an already imported audiobook instead of importing a new one
|
|
append: bool = False
|
|
|
|
|
|
class ImportIn(BaseModel):
|
|
items: list[ImportItem]
|
|
|
|
|
|
@router.post("")
|
|
def do_import(data: ImportIn, session: Session = Depends(get_session)):
|
|
results = []
|
|
download_root = Path(config.DOWNLOAD_DIR).resolve()
|
|
for item in data.items:
|
|
# the client hands back whatever the scan produced, base64 included
|
|
item.path = decode_path(item.path)
|
|
item.files = [decode_path(f) for f in item.files]
|
|
shown = readable(item.path)
|
|
req = session.get(BookRequest, item.request_id)
|
|
if not req:
|
|
results.append({"path": shown, "ok": False,
|
|
"error": f"Anfrage #{item.request_id} existiert nicht mehr"})
|
|
continue
|
|
path_gone = not req.imported_path or not Path(req.imported_path).exists()
|
|
if req.status == "imported" and path_gone:
|
|
# the library copy is gone; treat this as a fresh import
|
|
pass
|
|
elif item.append and req.status == "imported":
|
|
try:
|
|
dest = mover.append_to_import(item.files, req, req.library)
|
|
except Exception as exc:
|
|
results.append({"path": shown, "ok": False, "error": str(exc)})
|
|
continue
|
|
req.imported_path = dest
|
|
session.commit()
|
|
results.append({"path": shown, "ok": True, "dest": dest, "appended": True})
|
|
continue
|
|
elif req.status != "missing":
|
|
# usually several entries pointing at the same request: the first one
|
|
# imported and flipped it, the rest land here
|
|
results.append({"path": shown, "ok": False, "error": (
|
|
f"Anfrage „{req.title}“ (#{req.id}) ist bereits importiert"
|
|
f"{' nach ' + req.imported_path if req.imported_path else ''}"
|
|
" — mehrere Einträge auf dieselbe Anfrage? Dann vorher zusammenfassen."
|
|
)})
|
|
continue
|
|
# every source path must stay inside the download dir - merged items
|
|
# carry files from several folders, so check them all
|
|
paths = [Path(p).resolve() for p in [item.path, *item.files]]
|
|
if any(download_root not in p.parents and p != download_root for p in paths):
|
|
results.append({"path": shown, "ok": False, "error": "path outside download dir"})
|
|
continue
|
|
try:
|
|
dest = mover.import_item(item.path, item.files, item.is_dir, req, req.library)
|
|
except Exception as exc:
|
|
results.append({"path": shown, "ok": False, "error": str(exc)})
|
|
continue
|
|
req.status = "imported"
|
|
req.imported_path = dest
|
|
session.commit()
|
|
results.append({"path": shown, "ok": True, "dest": dest})
|
|
if not results:
|
|
raise HTTPException(400, "nothing to import")
|
|
return {"results": results}
|