Files
musicseerr/backend/services/request_service.py
T

175 lines
6.9 KiB
Python

import logging
from datetime import datetime, timezone
from repositories.protocols import LidarrRepositoryProtocol
from infrastructure.queue.request_queue import RequestQueue
from infrastructure.persistence.request_history import RequestHistoryStore
from api.v1.schemas.request import (
BatchCancelResponse,
BatchRequestResponse,
QueueStatusResponse,
RequestAcceptedResponse,
)
from core.exceptions import ExternalServiceError
logger = logging.getLogger(__name__)
class RequestService:
def __init__(
self,
lidarr_repo: LidarrRepositoryProtocol,
request_queue: RequestQueue,
request_history: RequestHistoryStore,
):
self._lidarr_repo = lidarr_repo
self._request_queue = request_queue
self._request_history = request_history
async def request_album(
self,
musicbrainz_id: str,
artist: str | None = None,
album: str | None = None,
year: int | None = None,
artist_mbid: str | None = None,
monitor_artist: bool = False,
auto_download_artist: bool = False,
) -> RequestAcceptedResponse:
if not self._lidarr_repo.is_configured():
raise ExternalServiceError("Lidarr isn't configured. Add an API key in Settings before requesting albums.")
try:
# Don't overwrite an active record (pending/downloading) — just re-check the queue.
existing = await self._request_history.async_get_record(musicbrainz_id)
if existing and existing.status in ("pending", "downloading"):
# Merge monitoring flags if the user updated their choice on re-request
if monitor_artist and not existing.monitor_artist:
await self._request_history.async_update_monitoring_flags(
musicbrainz_id, monitor_artist=True, auto_download_artist=auto_download_artist,
)
enqueued = await self._request_queue.enqueue(musicbrainz_id)
return RequestAcceptedResponse(
success=True,
message="Request already in progress",
musicbrainz_id=musicbrainz_id,
status=existing.status,
)
await self._request_history.async_record_request(
musicbrainz_id=musicbrainz_id,
artist_name=artist or "Unknown",
album_title=album or "Unknown",
year=year,
artist_mbid=artist_mbid,
monitor_artist=monitor_artist,
auto_download_artist=auto_download_artist,
)
except Exception as e: # noqa: BLE001
logger.error("Failed to record request history for %s: %s", musicbrainz_id, e)
raise ExternalServiceError(f"Failed to record request: {e}")
try:
enqueued = await self._request_queue.enqueue(musicbrainz_id)
if not enqueued:
return RequestAcceptedResponse(
success=True,
message="Request already in queue",
musicbrainz_id=musicbrainz_id,
status="pending",
)
except Exception as e: # noqa: BLE001
logger.error("Failed to enqueue album %s: %s", musicbrainz_id, e)
try:
from datetime import datetime, timezone
await self._request_history.async_update_status(
musicbrainz_id, "failed",
completed_at=datetime.now(timezone.utc).isoformat(),
)
except Exception: # noqa: BLE001
pass
raise ExternalServiceError(f"Failed to enqueue request: {e}")
return RequestAcceptedResponse(
success=True,
message="Request accepted",
musicbrainz_id=musicbrainz_id,
status="pending",
)
async def request_batch(
self,
items: list[dict],
monitor_artist: bool = False,
auto_download_artist: bool = False,
) -> BatchRequestResponse:
"""Request multiple albums at once. Returns counts of requested, skipped, and overflow."""
if not self._lidarr_repo.is_configured():
raise ExternalServiceError("Lidarr isn't configured. Add an API key in Settings before requesting albums.")
try:
active = await self._request_history.async_get_active_mbids()
new_items = [
item for item in items
if item["musicbrainz_id"].lower() not in active
]
skipped = len(items) - len(new_items)
if not new_items:
return BatchRequestResponse(
success=True,
message="All albums already requested",
requested=0,
skipped=skipped,
)
await self._request_history.async_bulk_record_requests(
new_items,
monitor_artist=monitor_artist,
auto_download_artist=auto_download_artist,
)
mbids = [item["musicbrainz_id"] for item in new_items]
enqueued, overflow = await self._request_queue.enqueue_many(mbids)
return BatchRequestResponse(
success=True,
message=f"Batch request accepted: {enqueued} enqueued",
requested=enqueued + overflow,
skipped=skipped,
overflow=overflow,
)
except ExternalServiceError:
raise
except Exception as e: # noqa: BLE001
logger.error("Batch request failed: %s", e)
raise ExternalServiceError(f"Batch request failed: {e}")
async def cancel_batch(self, musicbrainz_ids: list[str]) -> BatchCancelResponse:
"""Cancel multiple requests. Uses RequestQueue.cancel() for each."""
cancelled = 0
failed = 0
for mbid in musicbrainz_ids:
try:
await self._request_queue.cancel(mbid)
now_iso = datetime.now(timezone.utc).isoformat()
await self._request_history.async_update_status(
mbid, "cancelled", completed_at=now_iso,
)
cancelled += 1
except Exception: # noqa: BLE001
failed += 1
return BatchCancelResponse(
success=cancelled > 0,
cancelled=cancelled,
failed=failed,
message=f"Cancelled {cancelled} requests" + (f", {failed} failed" if failed else ""),
)
def get_queue_status(self) -> QueueStatusResponse:
status = self._request_queue.get_status()
return QueueStatusResponse(
queue_size=status["queue_size"],
processing=status["processing"],
active_workers=status.get("active_workers", 0),
max_workers=status.get("max_workers", 1),
)