164 lines
3.5 KiB
Python
164 lines
3.5 KiB
Python
"""Controller domain records shared by persistence and orchestration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
MEDIA_STATES = frozenset(
|
|
{"empty", "queued", "downloading", "ready", "error", "deleting"}
|
|
)
|
|
LIVE_JOB_STATES = frozenset({"reserved", "running"})
|
|
LIVE_GUARD_STATES = frozenset({"active", "review_required"})
|
|
UTILITY_EXPORT_MODES = frozenset({"mp4", "m4a", "mp3"})
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SlotRecord:
|
|
slot: int
|
|
generation: int
|
|
media_state: str
|
|
url: str | None
|
|
yt_id: str | None
|
|
title: str
|
|
item_count: int
|
|
published_item_count: int
|
|
created_at: float | None
|
|
ready_at: float | None
|
|
expires_at: float | None
|
|
last_error: str | None
|
|
updated_at: float
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DownloadReservation:
|
|
status: str
|
|
slot: int | None = None
|
|
generation: int | None = None
|
|
job_id: str | None = None
|
|
reused_slot: bool = False
|
|
oldest_expires_at: float | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DownloadJob:
|
|
job_id: str
|
|
slot: int
|
|
generation: int
|
|
url: str
|
|
status: str
|
|
submitter: str | None
|
|
note: str | None
|
|
requested_at: float
|
|
started_at: float | None
|
|
finished_at: float | None
|
|
error: str | None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ExportReservation:
|
|
status: str
|
|
job_id: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ExportJob:
|
|
job_id: str
|
|
mode: str
|
|
url: str
|
|
status: str
|
|
submitter: str | None
|
|
note: str | None
|
|
requested_at: float
|
|
started_at: float | None
|
|
finished_at: float | None
|
|
output_paths: tuple[str, ...]
|
|
error: str | None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DeleteToken:
|
|
slot: int
|
|
generation: int
|
|
reason: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SlotGuard:
|
|
guard_id: str
|
|
slot: int
|
|
kind: str
|
|
state: str
|
|
created_at: float
|
|
review_after: float | None
|
|
cleared_at: float | None
|
|
cleared_by: str | None
|
|
clear_reason: str | None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PlayerObservation:
|
|
availability: str
|
|
phase: str
|
|
active_slot: int | None
|
|
active_path: str | None
|
|
paused: bool | None
|
|
generation: int
|
|
reconciliation_state: str
|
|
reconciliation_due_at: float | None
|
|
reconciliation_id: str | None
|
|
last_status_at: float | None
|
|
last_telemetry_at: float | None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RouterObservation:
|
|
mode: str | None
|
|
freshness: str
|
|
observed_at: float | None
|
|
last_warning: str | None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AudioLeaseRecord:
|
|
lease_id: str
|
|
operation_id: str | None
|
|
playback_generation: int | None
|
|
state: str
|
|
previous_mode: str | None
|
|
target_mode: str
|
|
restoration_mode: str | None
|
|
restoration_source: str
|
|
controller_changed: bool
|
|
confirmation_deadline: float | None
|
|
created_at: float
|
|
acquired_at: float | None
|
|
released_at: float | None
|
|
warning: str | None
|
|
error: str | None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MigrationReport:
|
|
migrated: bool
|
|
source_path: str | None = None
|
|
source_sha256: str | None = None
|
|
backup_path: str | None = None
|
|
warnings: tuple[str, ...] = ()
|
|
|
|
|
|
class StoreError(RuntimeError):
|
|
"""Base persistence error."""
|
|
|
|
|
|
class MigrationError(StoreError):
|
|
"""Legacy migration was rejected or could not complete safely."""
|
|
|
|
|
|
class SlotProtectedError(StoreError):
|
|
"""A slot cannot be modified because live activity protects it."""
|
|
|
|
|
|
class StaleOperationError(StoreError):
|
|
"""An operation token no longer identifies the slot occupant."""
|