using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using MoreLinq; using Sony.Filtr.Contracts.Entities; using Sony.Filtr.Core.LastModified; using Sony.Filtr.DistributedCaching; using Sony.Filtr.Contracts.Abstractions; using Sony.Filtr.Contracts.Definitions; using Sony.Filtr.Contracts.Entities.Genre; using Sony.Filtr.Utility.Extensions; using StackExchange.Redis; namespace Sony.Filtr.Core.EditorialPlaylists { public class EditorialPlaylistManager { private readonly IEditorialPlaylistFactory _factory; private readonly DistributedCacheHandler _cacheHandler; private readonly LastModifiedManager _lastModifiedManager; private readonly IDatabase _redisDatabase; public EditorialPlaylistManager(IEditorialPlaylistFactory factory, DistributedCacheHandler cacheHandler, LastModifiedManager lastModifiedManager, IDatabase redisDatabase) { _factory = factory; _cacheHandler = cacheHandler; _lastModifiedManager = lastModifiedManager; _redisDatabase = redisDatabase; } #region EditorialPlaylist ImportUser public async Task SaveEditorialPlaylistAsync(EditorialPlaylist playlist) { if (playlist.ID == default(int)) playlist = _factory.AddEditorialPlaylist(playlist); else _factory.UpdateEditorialPlaylist(playlist); await ClearCacheAsync(playlist); await _lastModifiedManager.SetLastModifyDateAsync(playlist.ApplicationID, EntityType.Playlists); return playlist; } public async Task UpdateSpotifyFieldsAsync(EditorialPlaylist playlist, bool clearCache = true) { _factory.UpdatePlaylistImportFields(playlist); if (clearCache) { await ClearCacheAsync(playlist); } await _lastModifiedManager.SetLastModifyDateAsync(playlist.ApplicationID, EntityType.Playlists); return playlist; } public async Task SetEditorialPlaylistContentAsync(EditorialPlaylist playlist, IEnumerable genres) { if (genres != null) { await _factory.SetEditorialPlaylistGenresAsync(playlist, genres.ToList()); } await ClearCacheAsync(playlist); await _lastModifiedManager.SetLastModifyDateAsync(playlist.ApplicationID, EntityType.Playlists); return playlist; } public async Task DeleteEditorialPlaylistAsync(EditorialPlaylist playlist) { await _factory.DeleteEditorialPlaylistAsync(playlist); await _lastModifiedManager.SetLastModifyDateAsync(playlist.ApplicationID, EntityType.Playlists); //ARTEM: //await ClearCacheAsync(playlist); } public async Task GetEditorialPlaylistAsync(int editorialPlayListId) { var cacheKey = GetCacheKeySingleEditorialPlaylist(editorialPlayListId); var playlist = (EditorialPlaylist)_cacheHandler.Get(cacheKey); if (playlist != null) return playlist; playlist = await _factory.GetEditorialPlaylistAsync(editorialPlayListId); _cacheHandler.Put(cacheKey, playlist); return playlist; } public async Task> GetLightweightEditorialPlaylistsAsync(Application application = null, bool onlyActive = false) { return await GetEditorialPlaylistsAsync(application, onlyActive, lightWeight: true); } public async Task> GetEditorialPlaylistsAsync(Application application = null, bool onlyActive = false, bool lightWeight = false) { var cacheKey = GetCacheKeyEditorialPlaylists(application, onlyActive, lightWeight); var playlists = _cacheHandler.GetList(cacheKey); if (playlists != null) return playlists; playlists = await _factory.GetEditorialPlaylistsAsync(application, onlyActive, lightWeight); _cacheHandler.PutList(cacheKey, playlists.ToList()); return playlists; } public async Task> GetEditorialPlaylistsAsync(List playlistUris) { var playlists = await _factory.GetEditorialPlaylistsAsync(playlistUris); return playlists; } public async Task> GetEditorialPlaylistsByIdAsync(List playlistIds) { var playlists = await _factory.GetEditorialPlaylistsByIdAsync(playlistIds); return playlists; } public async Task GetEditorialPlaylistAsync(Application application, SpotifyLink spotifyLink) { var playlists = await GetEditorialPlaylistsAsync(application); return playlists.FirstOrDefault(p => p.SpotifyLink == spotifyLink); } public async Task ClearCacheAsync(EditorialPlaylist playlist) { _cacheHandler.Remove(GetCacheKeySingleEditorialPlaylist(playlist.ID)); ClearCache(playlist.ApplicationID); } public void ClearCache(int applicationId) { var playlistApp = new Application() { ID = applicationId }; var cacheKeys = new List(); var apps = new List() { null, playlistApp }; var onlyActiveStates = new[] { true, false }; var lightWeightStates = new[] { true, false }; foreach(var app in apps) { foreach(var onlyActive in onlyActiveStates) { foreach(var lightweight in lightWeightStates) { cacheKeys.Add(GetCacheKeyEditorialPlaylists(app, onlyActive: onlyActive, lightWeight: lightweight)); } } } Parallel.ForEach(cacheKeys, key => { _cacheHandler.Remove(key); }); } private string GetCacheKeySingleEditorialPlaylist(int editorialPlayListID) { return "EditorialPlaylist-1.2-" + typeof(EditorialPlaylist) + ":" + EditorialPlaylist.Version + ":" + editorialPlayListID; } private string GetCacheKeyEditorialPlaylists(Application application, bool onlyActive, bool lightWeight) { var cacheKey = "EditorialPlaylists-2.0-" + typeof(EditorialPlaylist) + ":" + EditorialPlaylist.Version + (application != null ? application.ID.ToString() : "null") + "-" + "-" + onlyActive + "-" + lightWeight; return cacheKey; } public async Task> GetEditorialPlaylistsAsync(string spotifyUsername, int applicationId) { return await _factory.GetEditorialPlaylistsAsync(spotifyUsername, applicationId); } #endregion public async Task>> GetArtistsAsync(List playlists) { var allPlaylistArtists = GetCachedArtistsForPlaylist(playlists); var notCachedPlaylists = playlists.Where(p => !allPlaylistArtists.ContainsKey(p.SpotifyLinkUri)).ToList(); if (notCachedPlaylists.Any()) { var playlistIds = notCachedPlaylists.Select(p => p.SpotifyLink.ExtractPlaylistID()).Distinct().ToList(); var nonCachedPlaylistArtists = await _factory.GetArtistsAsync(playlistIds); foreach (var nonCachedPlaylist in notCachedPlaylists) { var playlistId = nonCachedPlaylist.SpotifyLink.ExtractPlaylistID(); var playlistUri = nonCachedPlaylist.SpotifyLinkUri; var playlistArtists = new List(); if (nonCachedPlaylistArtists.ContainsKey(playlistId)) playlistArtists = nonCachedPlaylistArtists[playlistId]; var cacheKey = BuildArtistsCacheKey(nonCachedPlaylist); _cacheHandler.Put(cacheKey, playlistArtists, DateTime.Now.AddHours(4)); allPlaylistArtists.Add(playlistUri, playlistArtists); } } return allPlaylistArtists; } private Dictionary> GetCachedArtistsForPlaylist(List playlists) { ConcurrentDictionary> allPlaylistArtists = new ConcurrentDictionary>(); var cacheKeys = playlists.Select(BuildArtistsCacheKey); var results = _cacheHandler.Get(cacheKeys); foreach (var playlist in playlists) { var key = BuildArtistsCacheKey(playlist); if (results.ContainsKey(key)) { var playlistArtists = results[key] as List; if (playlistArtists != null) { allPlaylistArtists.TryAdd(playlist.SpotifyLinkUri, playlistArtists); } } } return allPlaylistArtists.ToDictionary(k => k.Key, v => v.Value); } private string BuildArtistsCacheKey(EditorialPlaylist playlist) { return "EP-Artists-2.0:" + playlist.SpotifyLink.Uri; } public async Task>> GetTagsAsync(IEnumerable playlists) { var playlistsToGet = playlists.DistinctBy(p=> p.SpotifyLink).ToList(); var allPlaylistTags = GetCachedTagsForPlaylist(playlistsToGet); var notCachedPlaylists = playlistsToGet.Where(p => !allPlaylistTags.ContainsKey(p.ID)).ToList(); if (notCachedPlaylists.Any()) { var nonCachedPlaylistTracks = await _factory.GetPlaylistGenresAsync(notCachedPlaylists); foreach (var nonCachedPlaylist in notCachedPlaylists) { var playlistTags = new List(); if (nonCachedPlaylistTracks.ContainsKey(nonCachedPlaylist.ID)) playlistTags = nonCachedPlaylistTracks[nonCachedPlaylist.ID]; var cacheKey = BuildTagsCacheKey(nonCachedPlaylist); _cacheHandler.PutList(cacheKey, playlistTags, DateTime.Now.AddDays(1)); allPlaylistTags.Add(nonCachedPlaylist.ID, playlistTags); } } return allPlaylistTags; } private Dictionary> GetCachedTagsForPlaylist(IEnumerable playlists) { ConcurrentDictionary> allPlaylistTags = new ConcurrentDictionary>(); Parallel.ForEach(playlists, playlist => { var cacheKey = BuildTagsCacheKey(playlist); var playlistTracks = _cacheHandler.GetList(cacheKey); if (playlistTracks != null) { allPlaylistTags.TryAdd(playlist.ID, playlistTracks); } }); return allPlaylistTags.ToDictionary(k => k.Key, v => v.Value); } private string BuildTagsCacheKey(EditorialPlaylist playlist) { return "EditorialPlaylistTags-1.1:" + playlist.SpotifyLink.Uri; } public async Task> GetPlaylistAttributesAsync(IEnumerable playlists) { var editorialPlaylists = playlists as IList ?? playlists.ToList(); var distinctUris = editorialPlaylists.Select(p => p.SpotifyLinkUri).Distinct(); var playlistAttributes = await GetPlaylistAttributesAsync(distinctUris); List extendedEditorialPlaylists = new List(); foreach (var playlist in editorialPlaylists) { EditorialPlaylistAttributes editorialPlaylistAttributes = null; playlistAttributes.TryGetValue(playlist.SpotifyLink.Uri, out editorialPlaylistAttributes); extendedEditorialPlaylists.Add(new ExtendedEditorialPlaylist() { EditorialPlaylist = playlist, Attributes = editorialPlaylistAttributes }); } return extendedEditorialPlaylists; } public async Task> GetPlaylistAttributesAsync(IEnumerable playlistsUris) { var distinctUris = playlistsUris.Select(p => p).Distinct(); ConcurrentDictionary allAttributes = new ConcurrentDictionary(); await distinctUris.ForEachAsync(50, async uri => { var attributes = await LoadAttributesAsync(uri); allAttributes.TryAdd(uri, attributes); }); return allAttributes.ToDictionary(k=> k.Key, v=> v.Value); } public async Task LoadAttributesAsync(string spotifyUri) { var attributes = new EditorialPlaylistAttributes(spotifyUri); var key = "PlaylistAttribute:" + attributes.SpotifyUri; //RedisConnection redisConnection = PersistentCachingManager.Instance.Connection; //var attributeHash = await redisConnection.Hashes.GetAll(0, key); var attributeHashes = await _redisDatabase.HashGetAllAsync(key); if (attributeHashes == null || !attributeHashes.Any()) return new EditorialPlaylistAttributes(spotifyUri); var attributeHash = attributeHashes.ToDictionary(k => k.Name, v => v.Value); if (attributeHash.ContainsKey("followers")) attributes.Followers = TryGetLong(attributeHash["followers"]); if (attributeHash.ContainsKey("lastFollowers")) attributes.LastFollowers = TryGetLong(attributeHash["lastFollowers"]); if (attributeHash.ContainsKey("followers7")) attributes.FollowerChange7 = TryGetLong(attributeHash["followers7"]); if (attributeHash.ContainsKey("followers15")) attributes.FollowerChange15 = TryGetLong(attributeHash["followers15"]); if (attributeHash.ContainsKey("followers30")) attributes.FollowerChange30 = TryGetLong(attributeHash["followers30"]); if (attributeHash.ContainsKey("trackCount")) attributes.TrackCount = TryGetLong(attributeHash["trackCount"]); if (attributeHash.ContainsKey("trackDuration")) attributes.TrackDuration = TryGetLong(attributeHash["trackDuration"]); if (attributeHash.ContainsKey("tracksAddedWeek")) attributes.TracksAddedThisWeek = TryGetLong(attributeHash["tracksAddedWeek"]); if (attributeHash.ContainsKey("tracksAddedToday")) attributes.TracksAddedToday = TryGetLong((attributeHash["tracksAddedToday"])); if (attributeHash.ContainsKey("changed")) attributes.Changed = TryGetDatetime(attributeHash["changed"]); if (attributeHash.ContainsKey("energy")) attributes.Energy = TryGetDouble(attributeHash["energy"]); if (attributeHash.ContainsKey("hotness")) attributes.Hotness = TryGetDouble(attributeHash["hotness"]); if (attributeHash.ContainsKey("tempo")) attributes.Tempo = TryGetDouble(attributeHash["tempo"]); if (attributeHash.ContainsKey("popularity")) attributes.Popularity = TryGetDouble(attributeHash["popularity"]); if (attributeHash.ContainsKey("sonyPercentage")) attributes.SonyPercentage = TryGetDouble(attributeHash["sonyPercentage"]); if (attributeHash.ContainsKey("countryCode")) attributes.CountryCode = attributeHash["countryCode"]; return attributes; } private string GetString(byte[] p) { return Encoding.UTF8.GetString(p); } private long? TryGetLong(string possibleLong) { long longValue; if (long.TryParse(possibleLong, out longValue)) { return longValue; } return null; } public async Task SaveAttributesAsync(EditorialPlaylistAttributes attributes) { //RedisConnection redisConnection = PersistentCachingManager.Instance.Connection; var key = "PlaylistAttribute:" + attributes.SpotifyUri; List hashValues = new List(); if (attributes.Followers != null) hashValues.Add(new HashEntry("followers", GetBytes(attributes.Followers.ToString()))); if (attributes.LastFollowers != null) hashValues.Add(new HashEntry("lastFollowers", GetBytes(attributes.LastFollowers.ToString()))); if (attributes.FollowerChange7 != null) hashValues.Add(new HashEntry("followers7", GetBytes(attributes.FollowerChange7.ToString()))); if (attributes.FollowerChange15 != null) hashValues.Add(new HashEntry("followers15", GetBytes(attributes.FollowerChange15.ToString()))); if (attributes.FollowerChange30 != null) hashValues.Add(new HashEntry("followers30", GetBytes(attributes.FollowerChange30.ToString()))); if (attributes.Changed != null) hashValues.Add(new HashEntry("changed", GetBytes(attributes.Changed.Value.ToString("O")))); if (attributes.TrackCount != null) hashValues.Add(new HashEntry("trackCount", GetBytes(attributes.TrackCount.ToString()))); if (attributes.TrackDuration != null) hashValues.Add(new HashEntry("trackDuration", GetBytes(attributes.TrackDuration.ToString()))); if (attributes.TracksAddedThisWeek != null) hashValues.Add(new HashEntry("tracksAddedWeek", GetBytes(attributes.TracksAddedThisWeek.ToString()))); if (attributes.TracksAddedToday != null) hashValues.Add(new HashEntry("tracksAddedToday", GetBytes(attributes.TracksAddedToday.ToString()))); if (attributes.Energy != null) hashValues.Add(new HashEntry("energy", GetBytes(attributes.Energy.ToString()))); if (attributes.Hotness != null) hashValues.Add(new HashEntry("hotness", GetBytes(attributes.Hotness.ToString()))); if (attributes.Tempo != null) hashValues.Add(new HashEntry("tempo", GetBytes(attributes.Tempo.ToString()))); if (attributes.Popularity != null) hashValues.Add(new HashEntry("popularity", GetBytes(attributes.Popularity.ToString()))); if (attributes.SonyPercentage != null) hashValues.Add(new HashEntry("sonyPercentage", GetBytes(attributes.SonyPercentage.ToString()))); if (attributes.CountryCode != null) hashValues.Add(new HashEntry("countryCode", GetBytes(attributes.CountryCode))); await _redisDatabase.HashSetAsync(key, hashValues.ToArray()); //await redisConnection.Hashes.Set(0, key, hashValues); } private byte[] GetBytes(string s) { return Encoding.Default.GetBytes(s); } private double? TryGetDouble(string dbl) { double outDouble; if (double.TryParse(dbl, out outDouble)) { return outDouble; } return null; } private DateTime? TryGetDatetime(string dateTimeString) { DateTime outDatetime; if (DateTime.TryParse(dateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out outDatetime)) { return outDatetime; } return null; } public async Task> GetUniqueSonySpotifyPlaylistsAsync() { return await _factory.GetUniqueSonySpotifyPlaylistsAsync(); } } }