using NLog; using Sony.Filtr.AppleMusic; using Sony.Filtr.AppleMusic.BestOfTheWeek; using Sony.Filtr.AppleMusic.BestOfTheWeek.Caching; using Sony.Filtr.AppleMusic.BestOfTheWeek.Entities; using Sony.Filtr.AppleMusic.Helpers; using Sony.Filtr.AppleMusic.Playlists; using Sony.Filtr.NewMusicFriday; using Sony.Filtr.Utility.Extensions; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Sony.Filtr.AppleMusic.BestOfTheWeek.Caching.Models; namespace Sony.Filtr.Tasks.Tasks.AppleMusic { public class ImportBestOfTheWeekTask : IScheduledTask { private readonly AppleMusicApi _appleMusicApi; private readonly AppleMusicPlaylistManager _appleMusicPlaylistManager; private readonly AppleMusicUpdateHelper _appleMusicUpdateHelper; private readonly BestOfTheWeekCacheProvider _cacheProvider; private readonly BestOfTheWeekFactory _botwFactory; private readonly BestOfTheWeekManager _botwManager; private readonly NewMusicFridayManager _nmfManager; private readonly Logger _logger; public ImportBestOfTheWeekTask(AppleMusicApi appleMusicApi, AppleMusicPlaylistManager appleMusicPlaylistManager, AppleMusicUpdateHelper appleMusicUpdateHelper, BestOfTheWeekCacheProvider cacheProvider, BestOfTheWeekFactory botwFactory, BestOfTheWeekManager botwManager, NewMusicFridayManager nmfManager) { _appleMusicApi = appleMusicApi; _appleMusicPlaylistManager = appleMusicPlaylistManager; _appleMusicUpdateHelper = appleMusicUpdateHelper; _cacheProvider = cacheProvider; _botwFactory = botwFactory; _botwManager = botwManager; _nmfManager = nmfManager; _logger = LogManager.GetLogger(nameof(ImportBestOfTheWeekTask)); } public async Task ExecuteAsync(Guid scheduledTaskLogId) { var botwPlaylists = await GetPlaylistWithAllStorefronts(); var botwPlaylistIndex = 0; var affectedFriday = _nmfManager.GetLogicalFridayForImport(DateTime.UtcNow); var precedingFriday = _nmfManager.GetPrecedingFriday(affectedFriday); await botwPlaylists.ForEachAsync(10, async botwPlaylist => { try { _logger.Info($"Getting playlist {Interlocked.Increment(ref botwPlaylistIndex)} of {botwPlaylists.Count}"); var fetchedPlaylist = await _appleMusicApi.GetPlaylistWithFullTracklistAsync(botwPlaylist.Storefront, botwPlaylist.PlaylistId); var fetchedTracks = fetchedPlaylist?.relationships?.tracks?.data; if (fetchedTracks != null && fetchedTracks.Any()) { var fetchedBotwTracks = fetchedTracks.ItemIndex().Select(x => new AppleMusicBestOfTheWeekPlaylistTrack() { Date = affectedFriday, PlaylistId = botwPlaylist.PlaylistId, PlaylistIndex = x.Index, SongId = x.Item.id, Storefront = botwPlaylist.Storefront }).ToList(); var precedingFridayTracksInDb = _botwFactory.GetPlaylistTracksForFriday(botwPlaylist, precedingFriday); var affectedFridayTracksInDb = _botwFactory.GetPlaylistTracksForFriday(botwPlaylist, affectedFriday); if (ShouldPlaylistUpdate(precedingFridayTracksInDb, affectedFridayTracksInDb, fetchedBotwTracks)) { _logger.Debug($"Updating { botwPlaylist.FullIdentifier } with { fetchedBotwTracks.Count } tracks"); await _appleMusicPlaylistManager.SetStorefrontSpecificDataAsync(botwPlaylist.Storefront, fetchedPlaylist.id, fetchedPlaylist.attributes.name, fetchedPlaylist.attributes.artwork?.url); await AddTracksDataToDatabase(fetchedBotwTracks, botwPlaylist); _logger.Debug($"Setting { fetchedBotwTracks.Count } tracks for playlist { botwPlaylist.FullIdentifier }"); await _botwFactory.SetPlaylistTracksForFriday(fetchedBotwTracks, botwPlaylist, affectedFriday, _logger); await CacheNewDataAsync(botwPlaylist, affectedFriday); } } else { _logger.Warn($"No available data for BotW import. Playlist: { botwPlaylist.FullIdentifier }"); } } catch (Exception ex) { _logger.Error(ex, $"Error while importing data for playlist { botwPlaylist?.FullIdentifier }"); } }); return null; } private async Task> GetPlaylistWithAllStorefronts() { var allStorefronts = await _appleMusicApi.GetAllStorefrontsAsync(); var trackedPlaylists = _botwFactory.GetPlaylists(); if (allStorefronts?.data != null) { var nonTrackedPlaylists = allStorefronts.data .Select(x => x.id).Except(trackedPlaylists.Select(x => x.Storefront)) .Select(x => new AppleMusicBestOfTheWeekPlaylist() { PlaylistId = BestOfTheWeekConstants.BestOfTheWeekPlaylistId, Storefront = x }).ToList(); if (nonTrackedPlaylists.Any()) await _botwFactory.AddPlaylists(nonTrackedPlaylists); return trackedPlaylists.Concat(nonTrackedPlaylists).OrderBy(x => x.Storefront).ToList(); } return trackedPlaylists; } private bool ShouldPlaylistUpdate(IList precedingFridayTracksInDb, IList affectedFridayTracksInDb, IList fetchedBotwTracks) { var isChangedFromPrecedingFriday = _botwManager.IsTrackListChanged(precedingFridayTracksInDb, fetchedBotwTracks); var isChangedFromAffectedFriday = _botwManager.IsTrackListChanged(affectedFridayTracksInDb, fetchedBotwTracks); if (isChangedFromPrecedingFriday && isChangedFromAffectedFriday) { //as Apple seems to often do minimal updates to the playlist, we also filter on that a percentage of tracks has to be new const float filterThresholdPercentage = 0.8f; var newTracksThreshold = fetchedBotwTracks.Count * filterThresholdPercentage; if (fetchedBotwTracks.Select(x => x.SongId).Except(precedingFridayTracksInDb.Select(x => x.SongId)).Count() > newTracksThreshold) { _logger.Debug($"Playlist { fetchedBotwTracks.FirstOrDefault()?.Storefront } is changed. Old count: {affectedFridayTracksInDb.Count} New count: {fetchedBotwTracks.Count}.\nOld tracks { string.Join("|", precedingFridayTracksInDb.Select(x => x.SongId)) }\n\nNew tracks { string.Join("|", fetchedBotwTracks.Select(x => x.SongId)) }"); return true; } } return false; } private async Task CacheNewDataAsync(AppleMusicBestOfTheWeekPlaylist playlist, DateTime date) { var freshTracksData = await _botwFactory.GetPlaylistTracksAsync(playlist, date); _logger.Debug($"Setting new cache data for { playlist.FullIdentifier} with { freshTracksData.Count } tracks"); _cacheProvider.SetPlaylistCollection(playlist, date, freshTracksData); } private async Task AddTracksDataToDatabase(IList botwTracks, AppleMusicBestOfTheWeekPlaylist playlist) { var tracks = await _appleMusicUpdateHelper.FetchAppleMusicSongsAsync(playlist.Storefront, botwTracks.Select(x => x.SongId)); await _appleMusicUpdateHelper.SaveSongsWithRelatedDataAsync(tracks, _logger); } } }