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 : SynchronizerBase { 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"); } protected override async Task CopyToPlaylistInternalAsync(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}"); } await SetDeezerTrackIds(genericPlaylist, serviceAccount); SyncCounts counts = await UpdatePlaylistTracks(deezerPlaylist, genericPlaylist, serviceAccount); if (playlistSynchronization.TitleCopyMode != FieldCopyMode.NoUpdate && playlistSynchronization.DescriptionCopyMode != FieldCopyMode.NoUpdate) { await UpdatePlaylistInfo(genericPlaylist, deezerPlaylist, playlistSynchronization, serviceAccount); } return PlaylistSynchronizationResult.FromSuccess(counts, genericPlaylist.Tracks.Count); } 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 = _synchronizationTrackManager.GetSynchronizedTracks(isrcs, ServiceType.Deezer); localDeezerIds = FilterOldMappings(genericPlaylist, localDeezerIds); 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.Count, 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()); allIsrcTrackIdMapping.TryAdd(isrc, deezerTrackResponse.id); } }); } return allIsrcTrackIdMapping; } private readonly List _bigStaticPlaylistUsers = new List() { "europa.kinderprogramm", "die_drei_fragezeichen" }; private List FilterOldMappings(GenericPlaylist genericPlaylist, List localDeezerIds) { if (_bigStaticPlaylistUsers.Contains(genericPlaylist.User, StringComparer.InvariantCultureIgnoreCase)) { // These account playlists are large and special so we don't have the same need for up to date mappings here. } else { localDeezerIds = localDeezerIds.Where(t => t.MatchDate > DateTime.UtcNow.AddDays(-7)).ToList(); //We ignore old mappings since these can sometimes change } return localDeezerIds; } private async Task UpdatePlaylistTracks(PlaylistResponse existingDeezerPlaylist, GenericPlaylist newGenericPlaylist, ServiceAccount serviceAccount) { var newPlaylistTrackIds = newGenericPlaylist.Tracks.Select(p => p.DeezerId).Distinct().FilterNull().ToList(); var existingTrackIds = existingDeezerPlaylist.tracks.data.Select(p => p.id).ToList(); 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))); var playlistId = existingDeezerPlaylist.id; 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, playlistId, trackIdBatch); } } } if (trackIdsToAdd.Any()) { _logger.Debug("Adding {0} tracks to target playlist", trackIdsToAdd.Count()); if (!_SafeMode) { foreach(var trackIdBatch in trackIdsToAdd.Batch(100)) { await _deezerApi.AddTracksAsync(serviceAccount.AccessToken, playlistId, trackIdBatch); } } } _logger.Debug("Setting order for {0} tracks", newPlaylistTrackIds.Count); if (!_SafeMode) { await _deezerApi.OrderPlaylistTracksAsync(serviceAccount.AccessToken, playlistId, newPlaylistTrackIds); } return new SyncCounts(trackIdsToAdd.Count, 0, trackIdsToRemove.Count); } } }