refactor: R12 S3 — invert scheduler->routes, publish cross-module privates
Layering cleanup, no behaviour change (ruff clean backend+scripts, pytest 193): - Invert the scheduler->routes dependency: reset_demo_state (+ its demo-seed helpers) move from routes/admin.py to a new service module app/demo.py. Both routes/admin.py and scheduler.py now import it from app.demo, so the scheduler no longer reaches into the routes layer — its lazy import (the routes<->scheduler cycle workaround) is gone, hoisted to a top-level import. - Publish the private symbols that were imported across module boundaries (the underscore convention was lying): feed._serialize -> serialize, feed._to_tsquery_str -> to_tsquery_str, videos._insert_stubs -> insert_stubs, videos._stub_from_playlist_item -> stub_from_playlist_item, worker._uploader_url -> uploader_url, plex._shared._item_or_404 -> item_or_404. Scoped renames — the 5 other route modules keep their own truly private _serialize helpers. - _release_asset moves from routes/downloads.py to app/downloads/service.py as the public release_asset (asset bookkeeping is a service concern); the routes call it via service.release_asset.
This commit is contained in:
@@ -12,7 +12,7 @@ from app.models import (
|
||||
log = logging.getLogger("siftlode.plex")
|
||||
|
||||
|
||||
def _item_or_404(db: Session, rating_key: str) -> PlexItem:
|
||||
def item_or_404(db: Session, rating_key: str) -> PlexItem:
|
||||
it = db.query(PlexItem).filter_by(rating_key=str(rating_key)).first()
|
||||
if it is None:
|
||||
raise HTTPException(status_code=404, detail="Unknown Plex item")
|
||||
|
||||
@@ -32,8 +32,8 @@ from app.plex import sync as plex_sync
|
||||
from app.plex.client import PlexClient, PlexError, PlexNotConfigured
|
||||
from app.routes._common import owned_or_404
|
||||
from app.routes.admin import admin_user
|
||||
from app.routes.feed import _to_tsquery_str
|
||||
from app.routes.plex._shared import _item_or_404
|
||||
from app.routes.feed import to_tsquery_str
|
||||
from app.routes.plex._shared import item_or_404
|
||||
|
||||
router = APIRouter(prefix="/api/plex", tags=["plex"])
|
||||
|
||||
@@ -258,7 +258,7 @@ def unified_library(
|
||||
want_movies = scope in ("movie", "both") and bool(movie_lib_ids)
|
||||
want_shows = scope in ("show", "both") and bool(show_lib_ids)
|
||||
|
||||
ts = _to_tsquery_str(q) if q else None
|
||||
ts = to_tsquery_str(q) if q else None
|
||||
tsq = func.to_tsquery(_TS_CONFIG, ts) if ts else None
|
||||
|
||||
selects = []
|
||||
@@ -1561,7 +1561,7 @@ def item_detail(
|
||||
) -> dict:
|
||||
"""Everything the player needs: metadata, cast, intro/credit markers (from Plex), episode
|
||||
prev/next, and the per-user resume position + watch status."""
|
||||
it = _item_or_404(db, rating_key)
|
||||
it = item_or_404(db, rating_key)
|
||||
st = db.query(PlexState).filter_by(user_id=user.id, item_id=it.id).first()
|
||||
|
||||
rich: dict = {}
|
||||
|
||||
@@ -33,7 +33,7 @@ from app.plex import people as plex_people
|
||||
from app.plex import stream as plex_stream
|
||||
from app.plex import watch_sync as plex_watch
|
||||
from app.plex.client import PlexClient, PlexError, PlexNotConfigured
|
||||
from app.routes.plex._shared import _item_or_404
|
||||
from app.routes.plex._shared import item_or_404
|
||||
|
||||
router = APIRouter(prefix="/api/plex", tags=["plex"])
|
||||
|
||||
@@ -105,7 +105,7 @@ def subtitle(
|
||||
return Response(subtitles.shift_vtt(hit, offset), media_type="text/vtt")
|
||||
try:
|
||||
with SessionLocal() as db:
|
||||
it = _item_or_404(db, rating_key)
|
||||
it = item_or_404(db, rating_key)
|
||||
with PlexClient(db) as plex:
|
||||
part = ((plex.metadata(it.rating_key) or {}).get("Media") or [{}])[
|
||||
0
|
||||
@@ -236,7 +236,7 @@ def item_progress(
|
||||
`final` (sent on the pause/pagehide/unmount flush, not the periodic 10s checkpoint) is what
|
||||
debounces the Plex resume push: we mirror the settled position to Plex only on a final flush, so
|
||||
a single watch doesn't spray `/:/timeline` calls at the server every 10 seconds."""
|
||||
it = _item_or_404(db, rating_key)
|
||||
it = item_or_404(db, rating_key)
|
||||
position = max(0, int(body.position_seconds))
|
||||
duration = int(body.duration_seconds)
|
||||
final = body.final
|
||||
@@ -307,7 +307,7 @@ def item_state(
|
||||
"""Set the per-user watch status (new|watched|hidden). watched→scrobble / →new→unscrobble push
|
||||
to a linked Plex account immediately; `hidden` is a Siftlode-only concept with no Plex analogue,
|
||||
so it never touches Plex."""
|
||||
it = _item_or_404(db, rating_key)
|
||||
it = item_or_404(db, rating_key)
|
||||
status = body.status
|
||||
if status not in ("new", "watched", "hidden"):
|
||||
raise HTTPException(status_code=400, detail="Invalid status")
|
||||
@@ -434,7 +434,7 @@ def stream_session(
|
||||
non-default audio track (`audio` = stream ordinal) forces the HLS path so the chosen audio can be
|
||||
muxed in. Subtitles are independent WebVTT tracks (GET /subtitle) — no session restart."""
|
||||
plex_stream.reap_idle() # opportunistic cleanup of idle sessions on each new playback
|
||||
it = _item_or_404(db, rating_key)
|
||||
it = item_or_404(db, rating_key)
|
||||
if it.playable == "transcode":
|
||||
raise HTTPException(
|
||||
status_code=501, detail="This format needs transcoding (a later phase)."
|
||||
@@ -471,7 +471,7 @@ def stream_file(
|
||||
"""Serve the raw local media file with HTTP range support. Used both for direct-play (inline)
|
||||
and for the player's "download original" button (`?download=1` → attachment). The download is
|
||||
the untouched physical file — no re-encode/repackage."""
|
||||
it = _item_or_404(db, rating_key)
|
||||
it = item_or_404(db, rating_key)
|
||||
src = plex_paths.local_media_path(db, it.file_path)
|
||||
if src is None:
|
||||
raise HTTPException(status_code=404, detail="Media file not found on disk")
|
||||
@@ -512,7 +512,7 @@ def stream_hls_file(
|
||||
if s is None:
|
||||
# No session yet (e.g. reaped): the ENTRY playlist may auto-start one at 0 — master.m3u8 for a
|
||||
# multi-audio item, index.m3u8 for a single-audio remux item.
|
||||
it = _item_or_404(db, rating_key)
|
||||
it = item_or_404(db, rating_key)
|
||||
if filename == "master.m3u8":
|
||||
s = plex_stream.start_session(db, it, 0.0, multi=True)
|
||||
elif filename == "index.m3u8" and it.playable == "remux":
|
||||
|
||||
Reference in New Issue
Block a user