refactor(plex): address /code-review on the R12 S1 split
Cleanup only, no behaviour change (ruff clean + pytest 193): - imports hoisted above `router =` and grouped/sorted (isort) across the split files; ruff-format pass restores the 2-blank-line PEP8 spacing the generator collapsed to 1. - the shared on-disk cache root moves to a neutral app/plex/cache.py, so subtitles.py no longer imports it from imgcache.py (drops the cross-import). - the former plex.py module docstring's access-model note (read endpoints open to any authed user; admin endpoints test/sync) is restored in the package __init__.py.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
"""Plex admin: connection test, catalog sync, and owner watch-link config."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app import sysconfig
|
||||
from app.auth import current_user
|
||||
from app.db import get_db
|
||||
@@ -21,8 +23,11 @@ from app.utils import iso
|
||||
|
||||
router = APIRouter(prefix="/api/plex", tags=["plex"])
|
||||
|
||||
|
||||
@router.post("/test")
|
||||
def test_connection(_: User = Depends(admin_user), db: Session = Depends(get_db)) -> dict:
|
||||
def test_connection(
|
||||
_: User = Depends(admin_user), db: Session = Depends(get_db)
|
||||
) -> dict:
|
||||
"""Admin: verify the configured Plex server URL + token, returning the server name and the
|
||||
movie/show sections (for the config library-picker). Also probes ONE real media file through
|
||||
the local mount, so a missing bind-mount / wrong path-map is caught here rather than only
|
||||
@@ -41,14 +46,22 @@ def test_connection(_: User = Depends(admin_user), db: Session = Depends(get_db)
|
||||
"server_name": info.get("friendlyName") or info.get("title1") or "Plex",
|
||||
"version": info.get("version"),
|
||||
"sections": [
|
||||
{"key": str(s.get("key")), "title": s.get("title"), "type": s.get("type"), "count": s.get("count")}
|
||||
{
|
||||
"key": str(s.get("key")),
|
||||
"title": s.get("title"),
|
||||
"type": s.get("type"),
|
||||
"count": s.get("count"),
|
||||
}
|
||||
for s in sections
|
||||
if s.get("type") in ("movie", "show")
|
||||
],
|
||||
"media_check": media_check,
|
||||
}
|
||||
|
||||
def _sample_plex_path(db: Session, plex: PlexClient, sections: list[dict]) -> str | None:
|
||||
|
||||
def _sample_plex_path(
|
||||
db: Session, plex: PlexClient, sections: list[dict]
|
||||
) -> str | None:
|
||||
"""A real Plex-side media file path to validate the mount + path-map against. Prefers the
|
||||
already-mirrored catalog (cheap); before the first sync, pulls one movie leaf from an enabled
|
||||
section straight from Plex."""
|
||||
@@ -69,6 +82,7 @@ def _sample_plex_path(db: Session, plex: PlexClient, sections: list[dict]) -> st
|
||||
return fp
|
||||
return None
|
||||
|
||||
|
||||
def _media_check(db: Session, plex: PlexClient, sections: list[dict]) -> dict:
|
||||
"""Resolve a sample Plex file path to its local mount path and confirm it's readable, so the
|
||||
admin sees at Test time whether the physical media is actually reachable (bind-mount +
|
||||
@@ -80,12 +94,14 @@ def _media_check(db: Session, plex: PlexClient, sections: list[dict]) -> dict:
|
||||
ok = plex_paths.local_media_path(db, plex_path) is not None
|
||||
return {"checked": True, "ok": ok, "plex_path": plex_path, "local_path": local_path}
|
||||
|
||||
|
||||
@router.post("/sync")
|
||||
def trigger_sync(_: User = Depends(admin_user), db: Session = Depends(get_db)) -> dict:
|
||||
"""Admin: mirror the enabled Plex sections into the local catalog. Synchronous (can take a
|
||||
while on a large library); a background/scheduled sync also runs periodically."""
|
||||
return plex_sync.sync(db)
|
||||
|
||||
|
||||
def _link_dict(link: PlexLink | None) -> dict:
|
||||
return {
|
||||
"linked": link is not None,
|
||||
@@ -95,6 +111,7 @@ def _link_dict(link: PlexLink | None) -> dict:
|
||||
"last_watch_sync_at": iso(link.last_watch_sync_at) if link else None,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/watch/link")
|
||||
def watch_link_status(
|
||||
user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||
@@ -103,9 +120,11 @@ def watch_link_status(
|
||||
link = db.query(PlexLink).filter_by(user_id=user.id).first()
|
||||
return _link_dict(link)
|
||||
|
||||
|
||||
class WatchLinkIn(BaseModel):
|
||||
enabled: bool = False
|
||||
|
||||
|
||||
@router.post("/watch/link")
|
||||
def watch_link_set(
|
||||
body: WatchLinkIn,
|
||||
@@ -120,7 +139,9 @@ def watch_link_set(
|
||||
enabled = body.enabled
|
||||
link = db.query(PlexLink).filter_by(user_id=user.id).first()
|
||||
if link is None:
|
||||
link = PlexLink(user_id=user.id, uses_admin=True, linked_at=datetime.now(timezone.utc))
|
||||
link = PlexLink(
|
||||
user_id=user.id, uses_admin=True, linked_at=datetime.now(timezone.utc)
|
||||
)
|
||||
db.add(link)
|
||||
link.sync_enabled = enabled
|
||||
db.commit()
|
||||
@@ -130,6 +151,7 @@ def watch_link_set(
|
||||
result = plex_watch.import_owner_watch_state(db, link)
|
||||
return {**_link_dict(link), "import": result or None}
|
||||
|
||||
|
||||
@router.post("/watch/import")
|
||||
def watch_import_now(
|
||||
user: User = Depends(admin_user), db: Session = Depends(get_db)
|
||||
|
||||
Reference in New Issue
Block a user