from __future__ import annotations import asyncio import logging from yuxi.channel.message.media_store import MediaStore logger = logging.getLogger(__name__) class MediaCleaner: def __init__(self, media_store: MediaStore, interval_seconds: int): self._media_store = media_store self._interval = interval_seconds self._task: asyncio.Task | None = None self._stop_event = asyncio.Event() @property def is_running(self) -> bool: return self._task is not None and not self._task.done() async def start(self) -> None: if self.is_running: return self._stop_event.clear() self._task = asyncio.create_task(self._loop(), name="media-cleaner") logger.info( "MediaCleaner started (interval=%ds, ttl=%ds)", self._interval, self._media_store.ttl_seconds, ) async def stop(self) -> None: if not self.is_running: return self._stop_event.set() try: await asyncio.wait_for(self._task, timeout=10) except TimeoutError: logger.warning("MediaCleaner stop timed out, cancelling task") self._task.cancel() except asyncio.CancelledError: pass self._task = None logger.info("MediaCleaner stopped") async def cleanup_once(self) -> int: return await self._media_store.cleanup_expired() async def _loop(self) -> None: while not self._stop_event.is_set(): try: await asyncio.wait_for(self._stop_event.wait(), timeout=self._interval) break except TimeoutError: pass try: deleted = await self._media_store.cleanup_expired() if deleted: logger.info("MediaCleaner cycle: deleted %d expired files", deleted) except Exception: logger.exception("MediaCleaner cycle error")