import logging from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.core.auth_dependency import require_session from app.db import get_db from app.models.channel import Channel from app.models.download_job import DownloadJob from app.models.video import Video from app.services.download_jobs import ( DeleteDidNotRemoveFile, DeleteNotAllowed, MeTubeRejected, delete_local_copy, get_latest_job, request_download, ) from app.services.metube_client import MeTubeClient from app.services.video_presentation import channel_categories_map, serialize_video logger = logging.getLogger(__name__) router = APIRouter(dependencies=[Depends(require_session)]) def _get_video_or_404(db: Session, youtube_video_id: str) -> Video: video = db.query(Video).filter(Video.youtube_video_id == youtube_video_id).one_or_none() if video is None: raise HTTPException(status_code=404, detail="Video not found") return video def _serialize_job(job: DownloadJob) -> dict: return { "status": job.status, "progress_percent": job.progress_percent, "media_url": job.media_url if job.status == "completed" else None, "error_message": job.error_message, "requested_at": job.requested_at, "started_at": job.started_at, "completed_at": job.completed_at, } @router.get("/videos/{youtube_video_id}") def get_video(youtube_video_id: str, db: Session = Depends(get_db)) -> dict: video = _get_video_or_404(db, youtube_video_id) channel = db.get(Channel, video.channel_id) if channel is None: raise HTTPException(status_code=404, detail="Channel not found") categories = channel_categories_map(db, [channel.id]).get(channel.id, []) job = get_latest_job(db, video.id) return serialize_video(video, channel, categories, job) @router.post("/videos/{youtube_video_id}/download") def download_video(youtube_video_id: str, db: Session = Depends(get_db)) -> dict: video = _get_video_or_404(db, youtube_video_id) try: job = request_download(db, video) except MeTubeRejected as exc: raise HTTPException(status_code=502, detail=f"MeTube rejected the download: {exc}") except Exception: logger.exception("Failed to enqueue download for %s", youtube_video_id) raise HTTPException(status_code=502, detail="MeTube is unavailable") return _serialize_job(job) @router.delete("/videos/{youtube_video_id}/download") def delete_download(youtube_video_id: str, db: Session = Depends(get_db)) -> dict: video = _get_video_or_404(db, youtube_video_id) try: job = delete_local_copy(db, video) except DeleteNotAllowed as exc: raise HTTPException(status_code=400, detail=str(exc)) except DeleteDidNotRemoveFile: raise HTTPException( status_code=409, detail=( "MeTube убрал запись из своего списка, но файл остался на диске " "(на mediaVM выключена настройка DELETE_FILE_ON_TRASHCAN)" ), ) except Exception: logger.exception("Failed to delete local copy for %s", youtube_video_id) raise HTTPException(status_code=502, detail="MeTube is unavailable") return _serialize_job(job) @router.get("/videos/{youtube_video_id}/download-status") def download_status(youtube_video_id: str, db: Session = Depends(get_db)) -> dict: video = _get_video_or_404(db, youtube_video_id) job = get_latest_job(db, video.id) if job is None: return {"status": "not_downloaded", "progress_percent": None, "media_url": None, "error_message": None} return _serialize_job(job) @router.post("/videos/{youtube_video_id}/recheck-local") def recheck_local(youtube_video_id: str, db: Session = Depends(get_db)) -> dict: video = _get_video_or_404(db, youtube_video_id) job = get_latest_job(db, video.id) if job is None or job.status != "completed": raise HTTPException(status_code=400, detail="No completed download to recheck") if job.media_url and MeTubeClient().check_media(job.media_url): return _serialize_job(job) job.status = "unknown" job.media_url = None db.commit() return _serialize_job(job)