from __future__ import annotations from typing import TYPE_CHECKING, Iterable, Mapping, Sequence from src.api_client.atlas import VendorAPIClient from src.api_client.dsp.spotify import SpotifyApiClient from src.data_info import PlaylistInfo, TrackInfo from src.enums import ServiceType from src.logger import BoundLogger from ..errors import SpotifySynchronizerError from ..utils import compare_sequences from .base import BaseSynchronizer if TYPE_CHECKING: from src.service_account.models import ServiceAccount __all__ = ["SpotifySynchronizer"] class SpotifySynchronizer(BaseSynchronizer[SpotifyApiClient]): service_type = ServiceType.spotify vendor_specific_sync_error_cls = SpotifySynchronizerError def __init__(self, service_account: ServiceAccount, *, logger: BoundLogger): super().__init__(service_account, logger=logger) self._vendor_api = VendorAPIClient.get_client(logger=logger) def _get_tracks_from_dsp(self, tracks: Iterable[TrackInfo]) -> Mapping[TrackInfo, str]: # TODO: we support synchronization only from spotify # so for now we can cheat a bit and do not search for track here # and just use existing id return {track: track.id for track in tracks} def _get_target_track_ids(self, playlist_id: str) -> Iterable[str]: for track in self._vendor_api.get_spotify_playlist_tracks(playlist_id): yield track.id def _synchronize_tracks( self, playlist_id: str, source_track_ids: Sequence[str], target_track_ids: Sequence[str] ) -> tuple[int, int]: inserted = 0 deleted = 0 for action, t1, t2, s1, s2 in compare_sequences(source_track_ids, target_track_ids): if action == "insert": self.api_client.insert_tracks(playlist_id, source_track_ids[s1:s2], t1) inserted += s2 - s1 if action == "delete": self.api_client.delete_tracks(playlist_id, target_track_ids[t1:t2]) deleted += t2 - t1 if action == "replace": self.api_client.insert_tracks(playlist_id, source_track_ids[s1:s2], t1) self.api_client.delete_tracks(playlist_id, target_track_ids[t1:t2]) inserted += s2 - s1 deleted += t2 - t1 return inserted, deleted def get_playlist_info(self, playlist_id: str) -> PlaylistInfo: return self._vendor_api.get_spotify_playlist_info(playlist_id) def _update_playlist_info(self, playlist_id: str, old_info: PlaylistInfo, new_info: Mapping[str, str | None]): self.api_client.update_playlist_info(playlist_id, new_info) def check_target_playlist(self, service_account: ServiceAccount, playlist_id: str) -> bool: playlist_info = self._vendor_api.get_spotify_playlist_info(playlist_id, use_cache=True) return playlist_info.user_id == service_account.user_identifier