102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from infrastructure.queue.priority_queue import RequestPriority
|
|
|
|
if TYPE_CHECKING:
|
|
from repositories.coverart_repository import CoverArtRepository
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_PREWARM_INTER_ITEM_DELAY = 2.0
|
|
_MAX_CONCURRENT_PREWARMS = 3
|
|
_MAX_MBIDS_PER_RUN = 100
|
|
|
|
|
|
class GenreCoverPrewarmService:
|
|
def __init__(self, cover_repo: CoverArtRepository) -> None:
|
|
self._cover_repo = cover_repo
|
|
self._active_genres: dict[str, asyncio.Task[None]] = {}
|
|
self._global_semaphore = asyncio.Semaphore(_MAX_CONCURRENT_PREWARMS)
|
|
|
|
def schedule_prewarm(
|
|
self,
|
|
genre_name: str,
|
|
artist_mbids: list[str],
|
|
album_mbids: list[str],
|
|
) -> None:
|
|
existing = self._active_genres.get(genre_name)
|
|
if existing is not None and not existing.done():
|
|
logger.debug("Pre-warm already in progress for genre '%s', skipping", genre_name)
|
|
return
|
|
|
|
task = asyncio.create_task(
|
|
self._prewarm(genre_name, artist_mbids, album_mbids),
|
|
name=f"genre-prewarm-{genre_name}",
|
|
)
|
|
self._active_genres[genre_name] = task
|
|
from core.task_registry import TaskRegistry
|
|
try:
|
|
TaskRegistry.get_instance().register(f"genre-prewarm-{genre_name}", task)
|
|
except RuntimeError:
|
|
pass
|
|
task.add_done_callback(
|
|
lambda _t, _g=genre_name, _ref=task: (
|
|
self._active_genres.pop(_g, None)
|
|
if self._active_genres.get(_g) is _ref
|
|
else None
|
|
)
|
|
)
|
|
|
|
async def shutdown(self) -> None:
|
|
tasks = list(self._active_genres.values())
|
|
if not tasks:
|
|
return
|
|
for t in tasks:
|
|
t.cancel()
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
self._active_genres.clear()
|
|
logger.info("GenreCoverPrewarmService shutdown complete (%d tasks cancelled)", len(tasks))
|
|
|
|
async def _prewarm(
|
|
self,
|
|
genre_name: str,
|
|
artist_mbids: list[str],
|
|
album_mbids: list[str],
|
|
) -> None:
|
|
async with self._global_semaphore:
|
|
all_artist = artist_mbids[:_MAX_MBIDS_PER_RUN]
|
|
remaining = _MAX_MBIDS_PER_RUN - len(all_artist)
|
|
all_album = album_mbids[:remaining] if remaining > 0 else []
|
|
total = len(all_artist) + len(all_album)
|
|
warmed = 0
|
|
try:
|
|
for i, mbid in enumerate(all_artist):
|
|
try:
|
|
await self._cover_repo.get_artist_image(
|
|
mbid, size=250, priority=RequestPriority.BACKGROUND_SYNC
|
|
)
|
|
warmed += 1
|
|
except Exception as exc: # noqa: BLE001
|
|
logger.debug("Pre-warm artist image failed for %s: %s", mbid[:8], exc)
|
|
if i < len(all_artist) - 1 or all_album:
|
|
await asyncio.sleep(_PREWARM_INTER_ITEM_DELAY)
|
|
|
|
for i, mbid in enumerate(all_album):
|
|
try:
|
|
await self._cover_repo.get_release_group_cover(
|
|
mbid, size="250", priority=RequestPriority.BACKGROUND_SYNC
|
|
)
|
|
warmed += 1
|
|
except Exception as exc: # noqa: BLE001
|
|
logger.debug("Pre-warm album cover failed for %s: %s", mbid[:8], exc)
|
|
if i < len(all_album) - 1:
|
|
await asyncio.sleep(_PREWARM_INTER_ITEM_DELAY)
|
|
|
|
logger.info("Pre-warmed %d/%d genre covers for '%s'", warmed, total, genre_name)
|
|
except Exception as exc: # noqa: BLE001
|
|
logger.error("Genre cover pre-warm failed for '%s': %s", genre_name, exc)
|