feat(reader): a Takeout video the catalog learns later is marked watched then; the import's status
A watched video the catalog did not know at import time waits in `takeout_items`. The Shorts job now settles those rows right after its probe (the only place a video's Short-ness becomes known), every user's, a commit each: `reader.takeout.apply_waiting`, its count in the job's result. `GET /api/takeout` is what the settings page reads: the user's last import run (`jobruns.last_run`) and the kept videos by kind and outcome, "waiting" for one not known yet.
This commit is contained in:
@@ -422,3 +422,30 @@ def takeout_outcomes(db: Session, user: User) -> dict[tuple[str, str | None], in
|
||||
.group_by(TakeoutItem.kind, TakeoutItem.outcome)
|
||||
).all()
|
||||
return {(kind, outcome): n for kind, outcome, n in rows}
|
||||
|
||||
|
||||
def takeout_summary(db: Session, user: User) -> dict[str, dict[str, int]]:
|
||||
"""The user's kept Takeout videos by kind and outcome, for their settings page: `{"watched":
|
||||
{"watched": n, "short": n, "waiting": n, …}, "watch_later": {…}}` — the import's outcome words,
|
||||
"waiting" for a video not known yet."""
|
||||
out: dict[str, dict[str, int]] = {kind.value: {} for kind in TakeoutKind}
|
||||
for (kind, outcome), n in takeout_outcomes(db, user).items():
|
||||
out[kind][outcome or "waiting"] = n
|
||||
return out
|
||||
|
||||
|
||||
def takeout_users_ready(db: Session) -> list[int]:
|
||||
"""Every user with a waiting "watched" row whose video is now known and probed for Shorts: the
|
||||
rows a later run of the Shorts probe made ready."""
|
||||
return list(
|
||||
db.execute(
|
||||
select(TakeoutItem.user_id)
|
||||
.join(Video, Video.id == TakeoutItem.video_id)
|
||||
.where(
|
||||
TakeoutItem.kind == TakeoutKind.WATCHED,
|
||||
TakeoutItem.outcome.is_(None),
|
||||
Video.shorts_probed,
|
||||
)
|
||||
.distinct()
|
||||
).scalars()
|
||||
)
|
||||
|
||||
@@ -25,6 +25,8 @@ from contextlib import contextmanager
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app import quota
|
||||
from app.bodylimit import TAKEOUT_MAX_BYTES
|
||||
from app.db import SessionLocal
|
||||
@@ -174,6 +176,11 @@ def _one_import(user_id: int) -> Iterator[None]:
|
||||
yield
|
||||
|
||||
|
||||
def run_target(user_id: int) -> str:
|
||||
"""The import run's target, `job_runs.target`: one user's import."""
|
||||
return f"user:{user_id}"
|
||||
|
||||
|
||||
def run_import(user_id: int) -> dict:
|
||||
"""The import job: apply what the user's kept Takeout rows can say now, as one recorded run.
|
||||
The watched part commits before the Watch later part asks YouTube for the videos the catalog
|
||||
@@ -198,3 +205,20 @@ def run_import(user_id: int) -> dict:
|
||||
if outcomes.get((TakeoutKind.WATCH_LATER, None)):
|
||||
result["watch_later_waiting"] = outcomes[(TakeoutKind.WATCH_LATER, None)]
|
||||
return result
|
||||
|
||||
|
||||
def apply_waiting(db: Session) -> dict:
|
||||
"""Mark watched the waiting Takeout rows whose video the catalog has come to know and the Shorts
|
||||
probe has cleared since the import — every user's, one commit each. Run after each Shorts probe
|
||||
(the scheduler's `shorts` job), the only place a video's Short-ness becomes known. Returns its
|
||||
counts, or an empty dict when nothing was waiting on a probed video."""
|
||||
users = queries.takeout_users_ready(db)
|
||||
marked = 0
|
||||
for user_id in users:
|
||||
user = db.get(User, user_id)
|
||||
if user is None:
|
||||
continue
|
||||
with _one_import(user_id):
|
||||
marked += service.apply_takeout_watched(db, user)["watched"]
|
||||
db.commit()
|
||||
return {"takeout_watched": marked} if users else {}
|
||||
|
||||
Reference in New Issue
Block a user