"""分片上传会话 ORM:支撑断点续传。对齐 server 的 UploadSession 形态(SQLite 版)。""" from __future__ import annotations import json from datetime import datetime, timezone from typing import Any from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, TypeDecorator from sqlalchemy.orm import Mapped, mapped_column from ..database import Base def _now() -> datetime: return datetime.now(timezone.utc) class _IntList(TypeDecorator): """把 list[int] 存成 JSON 字符串。SQLite 没有 ARRAY 类型。""" impl = Text cache_ok = True def process_bind_param(self, value: Any, dialect) -> str | None: return json.dumps(value) if value is not None else None def process_result_value(self, value: Any, dialect) -> list[int]: return json.loads(value) if value else [] class UploadSession(Base): __tablename__ = "upload_session" upload_id: Mapped[str] = mapped_column(String(64), primary_key=True) filename: Mapped[str] = mapped_column(String(512), nullable=False) size_bytes: Mapped[int] = mapped_column(Integer, nullable=False) chunk_size: Mapped[int] = mapped_column(Integer, nullable=False) total_chunks: Mapped[int] = mapped_column(Integer, nullable=False) uploaded_chunks: Mapped[list[int]] = mapped_column(_IntList, default=list) # pending → completed(complete 成功)| abandoned(reaper 清理) status: Mapped[str] = mapped_column(String(32), nullable=False, default="pending") # 拼接完成后的视频相对 upload_dir 路径 final_path: Mapped[str | None] = mapped_column(String(1024), nullable=True) # complete 后关联的 Task.id(直接引用,避免反向查找 source_path) task_id: Mapped[int | None] = mapped_column(ForeignKey("task.id"), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=_now) updated_at: Mapped[datetime] = mapped_column(DateTime, default=_now, onupdate=_now)