using MoreLinq; using NLog; using Sony.Filtr.Contracts.Definitions; using Sony.Filtr.Contracts.Entities; using Sony.Filtr.DeezerAPI; using Sony.Filtr.DeezerAPI.Model; using Sony.Filtr.PlaylistSynchronization.Data; using Sony.Filtr.Utility.Extensions; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Sony.Filtr.PlaylistSynchronization.Synchronizer { public class DeezerSynchronizer : ISynchronizer { private readonly DeezerApi _deezerApi; private readonly SynchronizationTrackManager _synchronizationTrackManager; private readonly Logger _logger; private const bool _SafeMode = false; public DeezerSynchronizer(DeezerApi deezerApi, SynchronizationTrackManager synchronizationTrackManager) { _deezerApi = deezerApi; _synchronizationTrackManager = synchronizationTrackManager; _logger = LogManager.GetLogger("PlaylistSynchronization"); } public async Task<(PlaylistSynchronizationResult, string, string)> CopyToPlaylistAsync(GenericPlaylist genericPlaylist, Data.PlaylistSynchronization playlistSynchronization, ServiceAccount serviceAccount) { var playlistId = long.Parse(playlistSynchronization.ToPlaylistId); _logger.Debug("DeezerSynchronizer for {0} to Deezer playlist: {1}", genericPlaylist.Name, playlistSynchronization.ToPlaylistId); var deezerPlaylist = await _deezerApi.GetPlaylistAsync(playlistId, serviceAccount.AccessToken); if (!(deezerPlaylist?.id > 0)) { return (PlaylistSynchronizationResult.FromError(SyncError.NoTargetPlaylist, $"Could not find playlist with id {playlistSynchronization.ToPlaylistId}"), null, null); } await SetDeezerTrackIds(genericPlaylist, serviceAccount); SyncCounts counts = await UpdatePlaylistTracks(deezerPlaylist.id, genericPlaylist, serviceAccount); if (playlistSynchronization.TitleCopyMode != FieldCopyMode.NoUpdate && playlistSynchronization.DescriptionCopyMode != FieldCopyMode.NoUpdate) { await UpdatePlaylistInfo(genericPlaylist, deezerPlaylist, playlistSynchronization, serviceAccount); } return (PlaylistSynchronizationResult.FromSuccess(counts, genericPlaylist.Tracks.Count), deezerPlaylist.title, deezerPlaylist.picture); } private async Task UpdatePlaylistInfo(GenericPlaylist genericPlaylist, PlaylistResponse deezerPlaylist, Data.PlaylistSynchronization playlistSynchronization, ServiceAccount serviceAccount) { var title = deezerPlaylist.title; var description = deezerPlaylist.description; var playlistId = deezerPlaylist.id; if (playlistSynchronization.TitleCopyMode != FieldCopyMode.NoUpdate) { if (playlistSynchronization.TitleCopyMode == FieldCopyMode.UseSetting) { title = playlistSynchronization.Title; } else if (playlistSynchronization.TitleCopyMode == FieldCopyMode.CopySource) { title = genericPlaylist.Name; } } if (playlistSynchronization.DescriptionCopyMode != FieldCopyMode.NoUpdate) { if (playlistSynchronization.DescriptionCopyMode == FieldCopyMode.UseSetting) { description = playlistSynchronization.Description; } else if (playlistSynchronization.DescriptionCopyMode == FieldCopyMode.CopySource) { description = genericPlaylist.Description; } } _logger.Debug($"Setting title to {title} and description to {description} for playlist with id {playlistId}"); if (!_SafeMode) { await _deezerApi.SetPlaylistInfoAsync(serviceAccount.AccessToken, playlistId, title, description, deezerPlaylist.@public, deezerPlaylist.collaborative); } } private async Task SetDeezerTrackIds(GenericPlaylist genericPlaylist, ServiceAccount serviceAccount) { var trackIsrcs = genericPlaylist.Tracks.Select(t => t.ISRC).Where(p => !string.IsNullOrWhiteSpace(p)).ToList(); var deezerTrackIdDic = await GetDeezerTrackMappingAsync(genericPlaylist, trackIsrcs, serviceAccount); foreach (var track in genericPlaylist.Tracks) { var isrc = track.ISRC; if (!string.IsNullOrEmpty(isrc) && deezerTrackIdDic.ContainsKey(isrc)) { track.DeezerId = deezerTrackIdDic[isrc]; } } } private async Task> GetDeezerTrackMappingAsync(GenericPlaylist genericPlaylist, List isrcs, ServiceAccount serviceAccount) { _logger.Debug("Checking local mappings"); var localDeezerIds = await _synchronizationTrackManager.GetSynchronizedTracksByIsrcAsync(isrcs, ServiceType.Deezer, DateTime.Now.AddDays(-7)); var isrcTrackIdMapping = localDeezerIds.DistinctBy(p => p.ISRC).ToDictionary(k => k.ISRC, v => long.Parse(v.TrackId), StringComparer.InvariantCultureIgnoreCase); var allIsrcTrackIdMapping = new ConcurrentDictionary(isrcTrackIdMapping, StringComparer.InvariantCultureIgnoreCase); _logger.Debug("Found local track mappings for {0} of {1} tracks", localDeezerIds.Length, isrcs.Count); var tracksToLookup = isrcs.Except(isrcTrackIdMapping.Keys).ToList(); if (tracksToLookup.Any()) { _logger.Debug("Doing ISRC lookup on deezer for {0} tracks", tracksToLookup.Count()); await tracksToLookup.ForEachAsync(10, async isrc => { var deezerTrackResponse = await _deezerApi.GetTrackByISRCAsync(serviceAccount.AccessToken, isrc); if (deezerTrackResponse != null) { _logger.Debug("Saving local mapping for {0}", isrc); await _synchronizationTrackManager.SaveSynchronizationTrackAsync(isrc, ServiceType.Deezer, deezerTrackResponse.id.ToString(), TrackSource.Deezer); allIsrcTrackIdMapping.TryAdd(isrc, deezerTrackResponse.id); } }); } return allIsrcTrackIdMapping; } private readonly List _bigStaticPlaylistUsers = new List() { "europa.kinderprogramm", "die_drei_fragezeichen" }; private async Task UpdatePlaylistTracks(long deezerPlaylistId, GenericPlaylist newGenericPlaylist, ServiceAccount serviceAccount) { var newPlaylistTrackIds = newGenericPlaylist.Tracks.Select(p => p.DeezerId).Distinct().FilterNull().ToList(); var existingTrackIds = (await this._deezerApi.GetAllPlaylistTracksAsync(deezerPlaylistId)).Select(i => Int64.Parse(i.id)).ToArray(); var trackIdsToRemove = existingTrackIds.Except(newPlaylistTrackIds).ToList(); _logger.Debug("Found {0} tracks to remove in target playlist: {1}", trackIdsToRemove.Count(), string.Join(",", trackIdsToRemove.Select(t => t))); var trackIdsToAdd = newPlaylistTrackIds.Except(existingTrackIds).ToList(); _logger.Debug("Found {0} tracks to add in target playlist: {1}", trackIdsToAdd.Count(), string.Join(",", trackIdsToAdd.Select(t => t))); if (trackIdsToRemove.Any()) { _logger.Debug("Deleting {0} tracks in target playlist", trackIdsToRemove.Count()); if (!_SafeMode) { foreach (var trackIdBatch in trackIdsToRemove.Batch(100)) { await _deezerApi.DeletePlaylistTracksAsync(serviceAccount.AccessToken, deezerPlaylistId, trackIdBatch); } } } var spotifyMap = newPlaylistTrackIds.GroupBy(t => t).ToDictionary(t => t.Key, t => t.Count()); var deezerMap = existingTrackIds.GroupBy(t => t).ToDictionary(t => t.Key, t => t.Count()); if (trackIdsToAdd.Any()) { _logger.Debug("Adding {0} tracks to target playlist", trackIdsToAdd.Count()); if (!_SafeMode) { foreach (var trackIdBatch in trackIdsToAdd.Batch(100)) { try { await _deezerApi.AddTracksAsync(serviceAccount.AccessToken, deezerPlaylistId, trackIdBatch); } catch (Exception ex) { _logger.Error(ex, $"Could not add tracks batch"); } } } } _logger.Debug("Setting order for {0} tracks", newPlaylistTrackIds.Count); bool reordered = false; if (!_SafeMode) { await _deezerApi.OrderPlaylistTracksAsync(serviceAccount.AccessToken, deezerPlaylistId, newPlaylistTrackIds); reordered = true; } return new SyncCounts(trackIdsToAdd.Count, reordered, trackIdsToRemove.Count); } } }