23c9125ad8
Backend CI / Lint (push) Waiting to run
Backend CI / Tests (push) Waiting to run
Squashes 26 incremental fork commits (Apr–May 2026) onto upstream main as a single
diff for cleaner cross-fork comparison. Original history preserved on the
pre-squash-backup tag locally.
Feature additions
─────────────────
• Inline single-track download via yt-dlp-worker proxy
New routes: POST /api/v1/track-download/search (source: youtube | spotify),
POST /api/v1/track-download, GET /api/v1/track-download/{id}. Frontend
TrackDownloadButton in album track list AND popular-songs row, with a per-button
source picker. Per-user rate limits live in the worker's SQLite store. On
completion the backend fires Lidarr RefreshArtist + Plex library refresh +
cache invalidation, and the popular-songs list auto-refreshes.
• Per-instance library pinning via MUSICSEERR_LIBRARY env
Backend stamps the library label server-side (music / music-personal /
music-shared); clients cannot override. Drives an instance-segregated
deployment of three musicseerr containers sharing one source tree.
• Lidarr-request flow (single-track requests via Lidarr indexers)
New routes: POST /api/v1/lidarr-request, GET /api/v1/lidarr-request/status.
Per-album asyncio.Lock keyed on album_mbid so rapid-clicks on the same album
serialize correctly. Cross-release track matcher with foreignTrackId →
foreignRecordingId → position+disc → exact-title → substring fallback chain,
evaluated per release (recording UUIDs frequently differ between album,
single, and deluxe edition releases of the same song). Flips
artist.monitored = True on request so Lidarr's WantedAlbums query reaches
the track. Full Lidarr-chain gate (artist AND album AND track) for the
status endpoint to avoid false-positive REQUESTED display. Persistent UI
state so button icons survive refresh and cross-album navigation.
• Privacy: show_now_playing toggle in Settings → Home
Default off. Plex /status/sessions returns active audio sessions across the
whole server with no library-section filter, so a shared instance leaks
every household member's listening activity. The merged store still emits
the user's local MusicSeerr playback bar; only server-derived sessions
(Plex / Jellyfin / Navidrome) are gated.
• Per-button visibility prefs for the track-row action cluster
Settings → Preferences → Download Options / Playback Buttons. Per-context
(popular_songs / album_page) force-off flags layered on top of the existing
source-availability gate.
• UX: wrap action cluster on mobile, hide LidarrRequestButton in tight
layouts, cross-album status-leak fix in AlbumTrackList ($effect keyed on
album.musicbrainz_id to rebuild lookup; map keyed by
"{albumMbid}:{position}:{disc}").
Test coverage
─────────────
Backend pytest: full suite green (2031/2031 as of squash). New: schema-default
tests for HomeSettings, lidarr_request_service cross-release matcher
regression test, singleton-registry expected-count bump to 59. Frontend
vitest: SettingsHome.svelte.spec covers new toggle, nowPlayingSessions
.svelte.spec covers the privacy gate (no fetch when off; fetches when on).
684 lines
24 KiB
Python
684 lines
24 KiB
Python
"""Tier 4 - Business-logic service providers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
|
|
from infrastructure.cache.cache_keys import (
|
|
lidarr_raw_albums_key,
|
|
lidarr_requested_mbids_key,
|
|
HOME_RESPONSE_PREFIX,
|
|
ALBUM_INFO_PREFIX,
|
|
ARTIST_INFO_PREFIX,
|
|
LIDARR_PREFIX,
|
|
LIDARR_ALBUM_DETAILS_PREFIX,
|
|
)
|
|
from infrastructure.persistence.request_history import RequestHistoryRecord
|
|
|
|
from ._registry import singleton
|
|
from .cache_providers import (
|
|
get_cache,
|
|
get_disk_cache,
|
|
get_library_db,
|
|
get_genre_index,
|
|
get_youtube_store,
|
|
get_mbid_store,
|
|
get_sync_state_store,
|
|
get_preferences_service,
|
|
get_cache_status_service,
|
|
)
|
|
from .repo_providers import (
|
|
get_lidarr_repository,
|
|
get_musicbrainz_repository,
|
|
get_wikidata_repository,
|
|
get_listenbrainz_repository,
|
|
get_jellyfin_repository,
|
|
get_navidrome_repository,
|
|
get_plex_repository,
|
|
get_coverart_repository,
|
|
get_youtube_repo,
|
|
get_audiodb_image_service,
|
|
get_audiodb_browse_queue,
|
|
get_lastfm_repository,
|
|
get_playlist_repository,
|
|
get_request_history_store,
|
|
get_github_repository,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@singleton
|
|
def get_search_service() -> "SearchService":
|
|
from services.search_service import SearchService
|
|
|
|
mb_repo = get_musicbrainz_repository()
|
|
lidarr_repo = get_lidarr_repository()
|
|
coverart_repo = get_coverart_repository()
|
|
preferences_service = get_preferences_service()
|
|
audiodb_image_service = get_audiodb_image_service()
|
|
browse_queue = get_audiodb_browse_queue()
|
|
return SearchService(mb_repo, lidarr_repo, coverart_repo, preferences_service, audiodb_image_service, browse_queue)
|
|
|
|
|
|
@singleton
|
|
def get_artist_service() -> "ArtistService":
|
|
from services.artist_service import ArtistService
|
|
|
|
mb_repo = get_musicbrainz_repository()
|
|
lidarr_repo = get_lidarr_repository()
|
|
wikidata_repo = get_wikidata_repository()
|
|
preferences_service = get_preferences_service()
|
|
memory_cache = get_cache()
|
|
disk_cache = get_disk_cache()
|
|
audiodb_image_service = get_audiodb_image_service()
|
|
browse_queue = get_audiodb_browse_queue()
|
|
library_db = get_library_db()
|
|
return ArtistService(mb_repo, lidarr_repo, wikidata_repo, preferences_service, memory_cache, disk_cache, audiodb_image_service, browse_queue, library_db)
|
|
|
|
|
|
@singleton
|
|
def get_album_service() -> "AlbumService":
|
|
from services.album_service import AlbumService
|
|
|
|
lidarr_repo = get_lidarr_repository()
|
|
mb_repo = get_musicbrainz_repository()
|
|
library_db = get_library_db()
|
|
memory_cache = get_cache()
|
|
disk_cache = get_disk_cache()
|
|
preferences_service = get_preferences_service()
|
|
audiodb_image_service = get_audiodb_image_service()
|
|
browse_queue = get_audiodb_browse_queue()
|
|
return AlbumService(lidarr_repo, mb_repo, library_db, memory_cache, disk_cache, preferences_service, audiodb_image_service, browse_queue)
|
|
|
|
|
|
def make_on_queue_import(memory_cache, disk_cache, library_db):
|
|
"""Create the on_queue_import closure used by the request queue."""
|
|
|
|
async def on_queue_import(record: RequestHistoryRecord) -> None:
|
|
"""Invalidate caches when the queue worker detects an already-imported album."""
|
|
invalidations = [
|
|
memory_cache.delete(lidarr_raw_albums_key()),
|
|
memory_cache.clear_prefix(f"{LIDARR_PREFIX}library:"),
|
|
memory_cache.delete(lidarr_requested_mbids_key()),
|
|
memory_cache.delete(f"{ALBUM_INFO_PREFIX}{record.musicbrainz_id}"),
|
|
memory_cache.delete(f"{LIDARR_ALBUM_DETAILS_PREFIX}{record.musicbrainz_id}"),
|
|
]
|
|
if record.artist_mbid:
|
|
invalidations.append(
|
|
memory_cache.delete(f"{ARTIST_INFO_PREFIX}{record.artist_mbid}")
|
|
)
|
|
invalidations.append(
|
|
disk_cache.delete_artist(record.artist_mbid)
|
|
)
|
|
await asyncio.gather(*invalidations, return_exceptions=True)
|
|
try:
|
|
await library_db.upsert_album({
|
|
"mbid": record.musicbrainz_id,
|
|
"artist_mbid": record.artist_mbid or "",
|
|
"artist_name": record.artist_name or "",
|
|
"title": record.album_title or "",
|
|
"year": record.year,
|
|
"cover_url": record.cover_url or "",
|
|
"monitored": True,
|
|
})
|
|
except Exception as ex: # noqa: BLE001
|
|
logger.warning("Queue import: failed to upsert album %s: %s", record.musicbrainz_id[:8], ex)
|
|
|
|
return on_queue_import
|
|
|
|
|
|
def make_processor(lidarr_repo, memory_cache, disk_cache, cover_repo, request_history):
|
|
"""Create the processor closure used by the request queue."""
|
|
|
|
async def processor(album_mbid: str) -> dict:
|
|
result = await lidarr_repo.add_album(album_mbid)
|
|
|
|
payload = result.get("payload", {})
|
|
if payload and isinstance(payload, dict):
|
|
is_monitored = payload.get("monitored", False)
|
|
|
|
if not is_monitored:
|
|
is_monitored = bool(result.get("monitored"))
|
|
|
|
if is_monitored:
|
|
try:
|
|
await disk_cache.promote_album_to_persistent(album_mbid)
|
|
await cover_repo.promote_cover_to_persistent(album_mbid, identifier_type="album")
|
|
|
|
artist_data = payload.get("artist", {})
|
|
if artist_data:
|
|
artist_mbid = artist_data.get("foreignArtistId") or artist_data.get("mbId")
|
|
if artist_mbid:
|
|
await disk_cache.promote_artist_to_persistent(artist_mbid)
|
|
await cover_repo.promote_cover_to_persistent(artist_mbid, identifier_type="artist")
|
|
|
|
except Exception as e: # noqa: BLE001
|
|
logger.error(f"Failed to promote cache entries for album {album_mbid[:8]}...: {e}")
|
|
else:
|
|
logger.warning(f"Album {album_mbid[:8]}... added but not monitored - skipping cache promotion")
|
|
|
|
try:
|
|
record = await request_history.async_get_record(album_mbid)
|
|
if record and record.monitor_artist and record.artist_mbid:
|
|
monitor_new = "all" if record.auto_download_artist else "none"
|
|
for attempt in range(2):
|
|
try:
|
|
await lidarr_repo.update_artist_monitoring(
|
|
record.artist_mbid, monitored=True, monitor_new_items=monitor_new,
|
|
)
|
|
await memory_cache.delete(f"{ARTIST_INFO_PREFIX}{record.artist_mbid}")
|
|
await disk_cache.delete_artist(record.artist_mbid)
|
|
break
|
|
except Exception: # noqa: BLE001
|
|
if attempt == 0:
|
|
await asyncio.sleep(2)
|
|
else:
|
|
raise
|
|
except Exception as e: # noqa: BLE001
|
|
logger.warning("Failed to apply deferred artist monitoring for %s: %s", album_mbid[:8], e)
|
|
|
|
return result
|
|
|
|
return processor
|
|
|
|
|
|
@singleton
|
|
def get_request_queue() -> "RequestQueue":
|
|
from infrastructure.queue.request_queue import RequestQueue
|
|
from infrastructure.queue.queue_store import QueueStore
|
|
from core.config import get_settings
|
|
settings = get_settings()
|
|
|
|
lidarr_repo = get_lidarr_repository()
|
|
disk_cache = get_disk_cache()
|
|
cover_repo = get_coverart_repository()
|
|
memory_cache = get_cache()
|
|
library_db = get_library_db()
|
|
|
|
on_queue_import = make_on_queue_import(memory_cache, disk_cache, library_db)
|
|
|
|
store = QueueStore(db_path=settings.queue_db_path)
|
|
request_history = get_request_history_store()
|
|
|
|
processor = make_processor(lidarr_repo, memory_cache, disk_cache, cover_repo, request_history)
|
|
|
|
concurrency = 2
|
|
try:
|
|
from services.preferences_service import PreferencesService
|
|
prefs = PreferencesService(settings)
|
|
advanced = prefs.get_advanced_settings()
|
|
concurrency = advanced.request_concurrency
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
|
|
return RequestQueue(
|
|
processor, store=store, request_history=request_history,
|
|
concurrency=concurrency, on_import_callback=on_queue_import,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_request_service() -> "RequestService":
|
|
from services.request_service import RequestService
|
|
|
|
lidarr_repo = get_lidarr_repository()
|
|
request_queue = get_request_queue()
|
|
request_history = get_request_history_store()
|
|
return RequestService(lidarr_repo, request_queue, request_history)
|
|
|
|
|
|
@singleton
|
|
def get_requests_page_service() -> "RequestsPageService":
|
|
from services.requests_page_service import RequestsPageService
|
|
|
|
lidarr_repo = get_lidarr_repository()
|
|
request_history = get_request_history_store()
|
|
memory_cache = get_cache()
|
|
disk_cache = get_disk_cache()
|
|
library_db = get_library_db()
|
|
|
|
async def on_import(record: RequestHistoryRecord) -> None:
|
|
invalidations = [
|
|
memory_cache.delete(lidarr_raw_albums_key()),
|
|
memory_cache.clear_prefix(f"{LIDARR_PREFIX}library:"),
|
|
memory_cache.delete(lidarr_requested_mbids_key()),
|
|
memory_cache.clear_prefix(HOME_RESPONSE_PREFIX),
|
|
memory_cache.delete(f"{ALBUM_INFO_PREFIX}{record.musicbrainz_id}"),
|
|
memory_cache.delete(f"{LIDARR_ALBUM_DETAILS_PREFIX}{record.musicbrainz_id}"),
|
|
]
|
|
if record.artist_mbid:
|
|
invalidations.append(
|
|
memory_cache.delete(f"{ARTIST_INFO_PREFIX}{record.artist_mbid}")
|
|
)
|
|
await asyncio.gather(*invalidations, return_exceptions=True)
|
|
if record.artist_mbid:
|
|
await asyncio.gather(
|
|
disk_cache.delete_album(record.musicbrainz_id),
|
|
disk_cache.delete_artist(record.artist_mbid),
|
|
return_exceptions=True,
|
|
)
|
|
else:
|
|
try:
|
|
await disk_cache.delete_album(record.musicbrainz_id)
|
|
except OSError as exc:
|
|
logger.warning(
|
|
"Failed to delete disk cache album %s during import invalidation: %s",
|
|
record.musicbrainz_id,
|
|
exc,
|
|
)
|
|
try:
|
|
await library_db.upsert_album({
|
|
"mbid": record.musicbrainz_id,
|
|
"artist_mbid": record.artist_mbid or "",
|
|
"artist_name": record.artist_name or "",
|
|
"title": record.album_title or "",
|
|
"year": record.year,
|
|
"cover_url": record.cover_url or "",
|
|
"monitored": True,
|
|
})
|
|
except Exception as ex: # noqa: BLE001
|
|
logger.warning("Failed to upsert album into library cache: %s", ex)
|
|
|
|
request_queue = get_request_queue()
|
|
library_service = get_library_service()
|
|
|
|
async def merged_library_mbids() -> set[str]:
|
|
return set(await library_service.get_library_mbids())
|
|
|
|
return RequestsPageService(
|
|
lidarr_repo=lidarr_repo,
|
|
request_history=request_history,
|
|
library_mbids_fn=merged_library_mbids,
|
|
on_import_callback=on_import,
|
|
request_queue=request_queue,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_playlist_service() -> "PlaylistService":
|
|
from services.playlist_service import PlaylistService
|
|
from core.config import get_settings
|
|
|
|
settings = get_settings()
|
|
playlist_repo = get_playlist_repository()
|
|
return PlaylistService(
|
|
repo=playlist_repo,
|
|
cache_dir=settings.cache_dir,
|
|
cache=get_cache(),
|
|
genre_index=get_genre_index(),
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_library_service() -> "LibraryService":
|
|
from services.library_service import LibraryService
|
|
|
|
lidarr_repo = get_lidarr_repository()
|
|
library_db = get_library_db()
|
|
cover_repo = get_coverart_repository()
|
|
preferences_service = get_preferences_service()
|
|
memory_cache = get_cache()
|
|
disk_cache = get_disk_cache()
|
|
artist_discovery_service = get_artist_discovery_service()
|
|
audiodb_image_service = get_audiodb_image_service()
|
|
local_files_service = get_local_files_service()
|
|
jellyfin_library_service = get_jellyfin_library_service()
|
|
navidrome_library_service = get_navidrome_library_service()
|
|
sync_state_store = get_sync_state_store()
|
|
genre_index = get_genre_index()
|
|
return LibraryService(
|
|
lidarr_repo, library_db, cover_repo, preferences_service,
|
|
memory_cache, disk_cache,
|
|
artist_discovery_service=artist_discovery_service,
|
|
audiodb_image_service=audiodb_image_service,
|
|
local_files_service=local_files_service,
|
|
jellyfin_library_service=jellyfin_library_service,
|
|
navidrome_library_service=navidrome_library_service,
|
|
sync_state_store=sync_state_store,
|
|
genre_index=genre_index,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_status_service() -> "StatusService":
|
|
from services.status_service import StatusService
|
|
|
|
lidarr_repo = get_lidarr_repository()
|
|
return StatusService(lidarr_repo)
|
|
|
|
|
|
@singleton
|
|
def get_home_service() -> "HomeService":
|
|
from services.home_service import HomeService
|
|
from core.config import get_settings
|
|
|
|
settings = get_settings()
|
|
listenbrainz_repo = get_listenbrainz_repository()
|
|
jellyfin_repo = get_jellyfin_repository()
|
|
lidarr_repo = get_lidarr_repository()
|
|
musicbrainz_repo = get_musicbrainz_repository()
|
|
preferences_service = get_preferences_service()
|
|
memory_cache = get_cache()
|
|
lastfm_repo = get_lastfm_repository()
|
|
audiodb_image_service = get_audiodb_image_service()
|
|
return HomeService(
|
|
listenbrainz_repo=listenbrainz_repo,
|
|
jellyfin_repo=jellyfin_repo,
|
|
lidarr_repo=lidarr_repo,
|
|
musicbrainz_repo=musicbrainz_repo,
|
|
preferences_service=preferences_service,
|
|
memory_cache=memory_cache,
|
|
lastfm_repo=lastfm_repo,
|
|
audiodb_image_service=audiodb_image_service,
|
|
cache_dir=settings.cache_dir,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_genre_cover_prewarm_service() -> "GenreCoverPrewarmService":
|
|
from services.genre_cover_prewarm_service import GenreCoverPrewarmService
|
|
|
|
cover_repo = get_coverart_repository()
|
|
return GenreCoverPrewarmService(cover_repo=cover_repo)
|
|
|
|
|
|
@singleton
|
|
def get_home_charts_service() -> "HomeChartsService":
|
|
from services.home_charts_service import HomeChartsService
|
|
|
|
listenbrainz_repo = get_listenbrainz_repository()
|
|
lidarr_repo = get_lidarr_repository()
|
|
musicbrainz_repo = get_musicbrainz_repository()
|
|
genre_index = get_genre_index()
|
|
lastfm_repo = get_lastfm_repository()
|
|
preferences_service = get_preferences_service()
|
|
prewarm_service = get_genre_cover_prewarm_service()
|
|
return HomeChartsService(
|
|
listenbrainz_repo=listenbrainz_repo,
|
|
lidarr_repo=lidarr_repo,
|
|
musicbrainz_repo=musicbrainz_repo,
|
|
genre_index=genre_index,
|
|
lastfm_repo=lastfm_repo,
|
|
preferences_service=preferences_service,
|
|
prewarm_service=prewarm_service,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_settings_service() -> "SettingsService":
|
|
from services.settings_service import SettingsService
|
|
|
|
preferences_service = get_preferences_service()
|
|
cache = get_cache()
|
|
return SettingsService(preferences_service, cache)
|
|
|
|
|
|
@singleton
|
|
def get_artist_discovery_service() -> "ArtistDiscoveryService":
|
|
from services.artist_discovery_service import ArtistDiscoveryService
|
|
|
|
listenbrainz_repo = get_listenbrainz_repository()
|
|
musicbrainz_repo = get_musicbrainz_repository()
|
|
library_db = get_library_db()
|
|
lidarr_repo = get_lidarr_repository()
|
|
lastfm_repo = get_lastfm_repository()
|
|
preferences_service = get_preferences_service()
|
|
memory_cache = get_cache()
|
|
return ArtistDiscoveryService(
|
|
listenbrainz_repo=listenbrainz_repo,
|
|
musicbrainz_repo=musicbrainz_repo,
|
|
library_db=library_db,
|
|
lidarr_repo=lidarr_repo,
|
|
memory_cache=memory_cache,
|
|
lastfm_repo=lastfm_repo,
|
|
preferences_service=preferences_service,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_artist_enrichment_service() -> "ArtistEnrichmentService":
|
|
from services.artist_enrichment_service import ArtistEnrichmentService
|
|
|
|
lastfm_repo = get_lastfm_repository()
|
|
preferences_service = get_preferences_service()
|
|
return ArtistEnrichmentService(
|
|
lastfm_repo=lastfm_repo,
|
|
preferences_service=preferences_service,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_album_enrichment_service() -> "AlbumEnrichmentService":
|
|
from services.album_enrichment_service import AlbumEnrichmentService
|
|
|
|
lastfm_repo = get_lastfm_repository()
|
|
preferences_service = get_preferences_service()
|
|
return AlbumEnrichmentService(
|
|
lastfm_repo=lastfm_repo,
|
|
preferences_service=preferences_service,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_album_discovery_service() -> "AlbumDiscoveryService":
|
|
from services.album_discovery_service import AlbumDiscoveryService
|
|
|
|
listenbrainz_repo = get_listenbrainz_repository()
|
|
musicbrainz_repo = get_musicbrainz_repository()
|
|
library_db = get_library_db()
|
|
lidarr_repo = get_lidarr_repository()
|
|
return AlbumDiscoveryService(
|
|
listenbrainz_repo=listenbrainz_repo,
|
|
musicbrainz_repo=musicbrainz_repo,
|
|
library_db=library_db,
|
|
lidarr_repo=lidarr_repo,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_search_enrichment_service() -> "SearchEnrichmentService":
|
|
from services.search_enrichment_service import SearchEnrichmentService
|
|
|
|
mb_repo = get_musicbrainz_repository()
|
|
lb_repo = get_listenbrainz_repository()
|
|
preferences_service = get_preferences_service()
|
|
lastfm_repo = get_lastfm_repository()
|
|
return SearchEnrichmentService(mb_repo, lb_repo, preferences_service, lastfm_repo)
|
|
|
|
|
|
@singleton
|
|
def get_youtube_service() -> "YouTubeService":
|
|
from services.youtube_service import YouTubeService
|
|
|
|
youtube_repo = get_youtube_repo()
|
|
youtube_store = get_youtube_store()
|
|
return YouTubeService(youtube_repo=youtube_repo, youtube_store=youtube_store)
|
|
|
|
|
|
@singleton
|
|
def get_lastfm_auth_service() -> "LastFmAuthService":
|
|
from services.lastfm_auth_service import LastFmAuthService
|
|
|
|
lastfm_repo = get_lastfm_repository()
|
|
return LastFmAuthService(lastfm_repo=lastfm_repo)
|
|
|
|
|
|
@singleton
|
|
def get_scrobble_service() -> "ScrobbleService":
|
|
from services.scrobble_service import ScrobbleService
|
|
|
|
lastfm_repo = get_lastfm_repository()
|
|
listenbrainz_repo = get_listenbrainz_repository()
|
|
preferences_service = get_preferences_service()
|
|
return ScrobbleService(lastfm_repo, listenbrainz_repo, preferences_service)
|
|
|
|
|
|
@singleton
|
|
def get_discover_service() -> "DiscoverService":
|
|
from services.discover_service import DiscoverService
|
|
from services.discover.radio_service import DiscoverRadioService
|
|
from services.discover.mbid_resolution_service import MbidResolutionService
|
|
from services.discover.integration_helpers import IntegrationHelpers
|
|
from services.home_transformers import HomeDataTransformers
|
|
|
|
listenbrainz_repo = get_listenbrainz_repository()
|
|
jellyfin_repo = get_jellyfin_repository()
|
|
lidarr_repo = get_lidarr_repository()
|
|
musicbrainz_repo = get_musicbrainz_repository()
|
|
preferences_service = get_preferences_service()
|
|
memory_cache = get_cache()
|
|
library_db = get_library_db()
|
|
mbid_store = get_mbid_store()
|
|
wikidata_repo = get_wikidata_repository()
|
|
lastfm_repo = get_lastfm_repository()
|
|
audiodb_image_service = get_audiodb_image_service()
|
|
genre_index = get_genre_index()
|
|
|
|
radio_mbid_svc = MbidResolutionService(
|
|
musicbrainz_repo=musicbrainz_repo,
|
|
lidarr_repo=lidarr_repo,
|
|
listenbrainz_repo=listenbrainz_repo,
|
|
library_db=library_db,
|
|
mbid_store=mbid_store,
|
|
)
|
|
radio_integration = IntegrationHelpers(preferences_service)
|
|
radio_service = DiscoverRadioService(
|
|
lb_repo=listenbrainz_repo,
|
|
mb_repo=musicbrainz_repo,
|
|
mbid_svc=radio_mbid_svc,
|
|
artist_discovery=get_artist_discovery_service(),
|
|
album_discovery=get_album_discovery_service(),
|
|
genre_index=genre_index,
|
|
integration=radio_integration,
|
|
transformers=HomeDataTransformers(jellyfin_repo),
|
|
)
|
|
|
|
return DiscoverService(
|
|
listenbrainz_repo=listenbrainz_repo,
|
|
jellyfin_repo=jellyfin_repo,
|
|
lidarr_repo=lidarr_repo,
|
|
musicbrainz_repo=musicbrainz_repo,
|
|
preferences_service=preferences_service,
|
|
memory_cache=memory_cache,
|
|
library_db=library_db,
|
|
mbid_store=mbid_store,
|
|
wikidata_repo=wikidata_repo,
|
|
lastfm_repo=lastfm_repo,
|
|
audiodb_image_service=audiodb_image_service,
|
|
genre_index=genre_index,
|
|
radio_service=radio_service,
|
|
playlist_service=get_playlist_service(),
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_discover_queue_manager() -> "DiscoverQueueManager":
|
|
from services.discover_queue_manager import DiscoverQueueManager
|
|
|
|
discover_service = get_discover_service()
|
|
preferences_service = get_preferences_service()
|
|
cover_repo = get_coverart_repository()
|
|
return DiscoverQueueManager(discover_service, preferences_service, cover_repo=cover_repo)
|
|
|
|
|
|
@singleton
|
|
def get_jellyfin_playback_service() -> "JellyfinPlaybackService":
|
|
from services.jellyfin_playback_service import JellyfinPlaybackService
|
|
|
|
jellyfin_repo = get_jellyfin_repository()
|
|
cache = get_cache()
|
|
return JellyfinPlaybackService(jellyfin_repo, cache)
|
|
|
|
|
|
@singleton
|
|
def get_local_files_service() -> "LocalFilesService":
|
|
from services.local_files_service import LocalFilesService
|
|
|
|
lidarr_repo = get_lidarr_repository()
|
|
preferences_service = get_preferences_service()
|
|
cache = get_cache()
|
|
return LocalFilesService(lidarr_repo, preferences_service, cache)
|
|
|
|
|
|
@singleton
|
|
def get_jellyfin_library_service() -> "JellyfinLibraryService":
|
|
from services.jellyfin_library_service import JellyfinLibraryService
|
|
|
|
jellyfin_repo = get_jellyfin_repository()
|
|
preferences_service = get_preferences_service()
|
|
return JellyfinLibraryService(jellyfin_repo, preferences_service)
|
|
|
|
|
|
@singleton
|
|
def get_navidrome_library_service() -> "NavidromeLibraryService":
|
|
from services.navidrome_library_service import NavidromeLibraryService
|
|
|
|
navidrome_repo = get_navidrome_repository()
|
|
preferences_service = get_preferences_service()
|
|
library_db = get_library_db()
|
|
mbid_store = get_mbid_store()
|
|
return NavidromeLibraryService(navidrome_repo, preferences_service, library_db, mbid_store)
|
|
|
|
|
|
@singleton
|
|
def get_navidrome_playback_service() -> "NavidromePlaybackService":
|
|
from services.navidrome_playback_service import NavidromePlaybackService
|
|
|
|
navidrome_repo = get_navidrome_repository()
|
|
cache = get_cache()
|
|
return NavidromePlaybackService(navidrome_repo, cache)
|
|
|
|
|
|
@singleton
|
|
def get_plex_library_service() -> "PlexLibraryService":
|
|
from services.plex_library_service import PlexLibraryService
|
|
|
|
plex_repo = get_plex_repository()
|
|
preferences_service = get_preferences_service()
|
|
library_db = get_library_db()
|
|
mbid_store = get_mbid_store()
|
|
return PlexLibraryService(plex_repo, preferences_service, library_db, mbid_store)
|
|
|
|
|
|
@singleton
|
|
def get_plex_playback_service() -> "PlexPlaybackService":
|
|
from services.plex_playback_service import PlexPlaybackService
|
|
|
|
plex_repo = get_plex_repository()
|
|
cache = get_cache()
|
|
return PlexPlaybackService(plex_repo, cache)
|
|
|
|
|
|
@singleton
|
|
def get_version_service() -> "VersionService":
|
|
from services.version_service import VersionService
|
|
|
|
github_repo = get_github_repository()
|
|
return VersionService(github_repo)
|
|
|
|
|
|
@singleton
|
|
def get_track_download_service() -> "TrackDownloadService":
|
|
from core.config import get_settings
|
|
from services.track_download_service import TrackDownloadService
|
|
|
|
settings = get_settings()
|
|
return TrackDownloadService(
|
|
worker_url=settings.yt_dlp_worker_url,
|
|
library=settings.musicseerr_library,
|
|
lidarr_repository=get_lidarr_repository(),
|
|
memory_cache=get_cache(),
|
|
plex_url=settings.plex_url,
|
|
plex_token=settings.plex_token,
|
|
plex_section_id=settings.plex_section_id,
|
|
)
|
|
|
|
|
|
@singleton
|
|
def get_lidarr_request_service() -> "LidarrRequestService":
|
|
from services.lidarr_request_service import LidarrRequestService
|
|
|
|
return LidarrRequestService(lidarr_repository=get_lidarr_repository())
|