"""Супервизор: владеет набором RecorderTask по всем включённым камерам.""" from __future__ import annotations import logging from ..config import Camera, Config from ..models import CameraStatus from ..services import go2rtc from .recorder_task import RecorderTask log = logging.getLogger("nvr.supervisor") class Supervisor: def __init__(self, config: Config, on_status=None): self.config = config self.on_status = on_status self.tasks: dict[str, RecorderTask] = {} def _input_url(self, cam: Camera) -> str: # запись основного потока идёт через рестрим go2rtc (одна сессия к камере) return go2rtc.restream_main_url(self.config, cam.id) def _new_task(self, cam: Camera) -> RecorderTask: # хранилище камеры: её storage_id, иначе активное (по умолчанию). Несуществующий # id (удалённое хранилище) → тоже откат на активное. store = self.config.storage_by_id(cam.storage_id) or self.config.active_storage() return RecorderTask(cam, store, self.config.storage.segment_seconds, on_status=self.on_status, input_url=self._input_url(cam)) def start_all(self) -> None: for cam in self.config.cameras: task = self._new_task(cam) self.tasks[cam.id] = task if cam.enabled and cam.record: task.start() log.info("recorder started: %s", cam.id) else: log.info("recorder skipped (enabled=%s record=%s): %s", cam.enabled, cam.record, cam.id) async def stop_all(self) -> None: for task in self.tasks.values(): await task.stop() def statuses(self) -> list[CameraStatus]: return [t.status for t in self.tasks.values()] def status(self, cam_id: str) -> CameraStatus | None: task = self.tasks.get(cam_id) return task.status if task else None def log_tail(self, cam_id: str) -> list[str]: task = self.tasks.get(cam_id) return task.log_tail if task else [] async def restart(self, cam_id: str) -> bool: task = self.tasks.get(cam_id) if not task: return False await task.stop() task.start() return True def add_camera(self, cam: Camera) -> None: """Создаёт задачу записи для новой камеры и запускает (если включена).""" task = self._new_task(cam) self.tasks[cam.id] = task if cam.enabled and cam.record: task.start() log.info("recorder added: %s", cam.id) async def remove_camera(self, cam_id: str) -> None: task = self.tasks.pop(cam_id, None) if task: await task.stop() log.info("recorder removed: %s", cam_id) async def replace_camera(self, cam: Camera) -> None: await self.remove_camera(cam.id) self.add_camera(cam) async def restart_all(self) -> None: """Пересоздаёт все задачи (например, после смены настроек хранилища).""" for cam in list(self.config.cameras): await self.replace_camera(cam)