using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.ServiceModel.Web; using System.Threading.Tasks; using MoreLinq; using Sony.Filtr.Applications; using Sony.Filtr.API.ViewModels; using Sony.Filtr.API.ViewModels.v3; using Sony.Filtr.ContentManagement; using Sony.Filtr.ContentManagement.Data; using Sony.Filtr.Contracts.Definitions; using Sony.Filtr.Contracts.Entities; using Sony.Filtr.Contracts.Entities.Genre; using Sony.Filtr.Core.Genres; using Sony.Filtr.Core.LastModified; using Sony.Filtr.Core.Settings; using Sony.Filtr.Core.TopLists; using Sony.Filtr.DistributedCaching; using Sony.Filtr.Globalization.v3; using Sony.Filtr.PlaylistGeneration; using Sony.Filtr.PlaylistGeneration.Autocomplete; using Sony.Filtr.Search; using Sony.Filtr.Utility.Extensions; namespace Sony.Filtr.API.Service { public class BaseService3 { private readonly ApplicationInstanceManager _applicationInstanceManager; private readonly LastModifiedManager _lastModifiedManager; private readonly PlaylistGenerationManager _playlistGenerationManager; private readonly TrackSuggestionManager2 _trackSuggestionManager2; private readonly AutocompleteManager _autocompleteManager; private readonly DistributedCacheHandler _distributedCacheHandler; private readonly TopListManager _topListManager; private readonly ContentManager _contentManager; private readonly SearchManager _searchManager; private readonly GenreManager _genreManager; private readonly SettingsManager _settingsManager; private readonly MarketGlobalizationHandler _marketGlobalizationHandler; protected readonly CultureInfo FallbackLanguage = new CultureInfo("en-GB"); protected readonly DateTime CodeLastModified = new DateTime(2016, 03, 30, 13, 00, 00, DateTimeKind.Utc); public BaseService3(ApplicationInstanceManager applicationInstanceManager, MarketGlobalizationHandler marketGlobalizationHandler, LastModifiedManager lastModifiedManager, PlaylistGenerationManager playlistGenerationManager, TrackSuggestionManager2 trackSuggestionManager2, AutocompleteManager autocompleteManager, DistributedCacheHandler distributedCacheHandler, TopListManager topListManager, ContentManager contentManager, SearchManager searchManager, GenreManager genreManager, SettingsManager settingsManager) { _applicationInstanceManager = applicationInstanceManager; _lastModifiedManager = lastModifiedManager; _playlistGenerationManager = playlistGenerationManager; _trackSuggestionManager2 = trackSuggestionManager2; _autocompleteManager = autocompleteManager; _distributedCacheHandler = distributedCacheHandler; _topListManager = topListManager; _contentManager = contentManager; _searchManager = searchManager; _genreManager = genreManager; _settingsManager = settingsManager; _marketGlobalizationHandler = marketGlobalizationHandler; } public async Task GetSearchAutocompleteLastModifiedAsync(Application app) { var artistAutocompleteLastModified = await _autocompleteManager.GetLastModidifedDateAsync(app); var genresLastModified = await _lastModifiedManager.GetLastModifyDateAsync(app, EntityType.Genres); var globalGenresLastModified = await _lastModifiedManager.GetLastModifyDateAsync(null, EntityType.Genres); return new DateTime[] { artistAutocompleteLastModified, genresLastModified, globalGenresLastModified, }.Max(); } public async Task> SearchAutocompleteAsync(Application app, string searchString) { const int maxAutocompleteResults = 15; var searchArtistsTask = _autocompleteManager.SearchAutocompleteArtistsAsync(searchString, count: maxAutocompleteResults); var tags = GetGlobalAndLocalGenres(app).Where(g => g.Name.StartsWith(searchString, StringComparison.CurrentCultureIgnoreCase)); var genreCategories = _genreManager.GetGenreCategories().ToDictionary(g => g.ID, g => g.Name); List tagViewModels = new List(); tagViewModels.AddRange(tags.Select(g => new TagViewModel(g, genreCategories))); var artists = await searchArtistsTask; tagViewModels.AddRange(artists.Select(a => new TagViewModel(a))); return tagViewModels.OrderByDescending(t => t.InternalScore).ThenBy(a=> a.Label).Take(maxAutocompleteResults).ToList(); } protected List GetGlobalAndLocalGenres(Application application) { return _genreManager.GetGenres(application).Union(_genreManager.GetGenres(_applicationInstanceManager.GetGlobalPushApplication())).DistinctBy(t => t.Name).ToList(); } protected Genre GetGlobalAndLocalGenre(Application app, string tag) { var genre = _genreManager.GetGenre(app, tag); if (genre == null) genre = _genreManager.GetGenre(_applicationInstanceManager.GetGlobalPushApplication(), tag); return genre; } public async Task GetGlobalBootstrapLastModifiedAsync(string countryCode) { var applicationDate = await _lastModifiedManager.GetLastModifyDateAsync(null, EntityType.Applications); var codeDate = CodeLastModified; var lastModified = new[] { applicationDate, codeDate }.Max(); return lastModified; } public GlobalBootstrapViewModel GetGlobalBootstrap(string countryCode) { var app = _applicationInstanceManager.GetRegularApplicationByCountrySpecialFallback(countryCode); var fallbackApp = _applicationInstanceManager.GetFallbackApplication(); var bootstrapViewModels = new GlobalBootstrapViewModel(); List marketViewModels = new List { MarketViewModel.CreateMarketViewModel(app), MarketViewModel.CreateMarketViewModel(fallbackApp) }; marketViewModels = marketViewModels.DistinctBy(m => m.ID).OrderBy(b=> b.IsFallback).ToList(); bootstrapViewModels.Markets = marketViewModels; bootstrapViewModels.BaseUrls = new BaseUrlsViewModel() { CDNBaseUrl = ApiConfig.CDNBaseUrl, EditorialImageBaseUrl = ApiConfig.EditorialImageBaseUrl, ProxyBaseUrl = ApiConfig.ProxyBaseUrl, }; return bootstrapViewModels; } public BootstrapViewModel GetBootstrapNew(string countryCode) { var applicationsInCountry = _applicationInstanceManager.GetRegularApplicationByCountrySpecialFallback(countryCode); //TODO: Only include applications which are active for service? //TODO: Which languages to use? //var languageViewModels = GetOrderedLanguageViewModels(app); var bootstrap = new BootstrapViewModel { //Languages = languageViewModels, Markets = new List() { applicationsInCountry.ID }, BaseUrls = new BaseUrlsViewModel() { CDNBaseUrl = ApiConfig.CDNBaseUrl, EditorialImageBaseUrl = ApiConfig.EditorialImageBaseUrl, ProxyBaseUrl = ApiConfig.ProxyBaseUrl, }, DefaultFilteringValues = GetDefaultFilteringValues(), }; return bootstrap; } protected MyJsonDictionary GetDefaultFilteringValues() { MyJsonDictionary defaultFilteringValues = new MyJsonDictionary(); foreach (var defaultFilteringKey in _defaultFilteringKeys) { var value = _settingsManager.GetDoubleSetting(defaultFilteringKey, _applicationInstanceManager.GetGlobalPushApplication()); defaultFilteringValues.Add(defaultFilteringKey, value); } return defaultFilteringValues; } private readonly List _defaultFilteringKeys = new List() { "minTempo", "maxTempo", "minDanceability", "maxDanceability", "minEnergy", "maxEnergy", "minHotness", "maxHotness", "minDuration", "maxDuration", }; public List GetMarketGlobalizations(Application app, string context) { var translationsByKey = _marketGlobalizationHandler.GetTranslations(app.ID, context); var translationList = translationsByKey.Select( kvp => new MarketGlobalizationViewModel() { Context = kvp.Key.Context, Key = kvp.Key.Key, ApiConsumerId = kvp.Key.ApiConsumerId, Value = kvp.Value != null ? kvp.Value.Value : kvp.Key.DefaultValue }); return translationList.OrderBy(t => t.Context).ToList(); } public async Task> GetTracks(Application app, string countryCode, string tag, string artist, ServiceType serviceType) { var allTagCategories = _genreManager.GetGenreCategories(); var tagCategories = allTagCategories.ToDictionary(g => g.ID, g => g.Name); //var genreCategory = allTagCategories.FirstOrDefault(t => t.Name.Equals("genre", StringComparison.CurrentCultureIgnoreCase)); //TODO: filter so we only include genres-tags. if (!string.IsNullOrWhiteSpace(artist)) { var artistArtist = await _playlistGenerationManager.GetArtistAsync(artist); if (artistArtist == null) { throw new WebFaultException(HttpStatusCode.NotFound); } var tracks = await _trackSuggestionManager2.GetSuggestedTracksAsync(app, countryCode, artistArtist, serviceType); var artists = tracks.Select(t => t.Artist).DistinctBy(a => a.Name).ToList(); var artistsTags = await _playlistGenerationManager.GetArtistTagsAsync(app, artists); var artistTagViewModel = artistsTags.ToDictionary(k => k.Key, v => v.Value.Select(t => new TagViewModel(t, tagCategories))); //var artistMoods = artists.AsParallel().ToDictionary(k => k.Name, v => PlaylistGenerationManager.Instance.GetMood(v).Result); List viewModels = new List(); foreach (var track in tracks) { IEnumerable artistTags; if (!artistTagViewModel.TryGetValue(track.Artist.Name, out artistTags)) { artistTags = new List(); } var viewModel = TrackViewModel.BuildTrackViewModel(track, artistTags); viewModels.Add(viewModel); } return viewModels; } else if (!string.IsNullOrWhiteSpace(tag)) { var genre = GetGlobalAndLocalGenre(app, tag); if (genre == null) { throw new WebFaultException(HttpStatusCode.NotFound); } var tracks = await _trackSuggestionManager2.GetSuggestedTracksForGenreAsync(genre, countryCode, serviceType); var artists = tracks.Select(t => t.Artist).DistinctBy(a => a.Name).ToList(); var artistsTags = await _playlistGenerationManager.GetArtistTagsAsync(app, artists); var artistTagViewModel = artistsTags.ToDictionary(k => k.Key, v => v.Value.Select(t => new TagViewModel(t, tagCategories))); //var artistMoods = tracks.Select(t => t.Artist).DistinctBy(a => a.Name).ToDictionary(k => k.Name, v => PlaylistGenerationManager.Instance.GetMood(v).Result); List viewModels = new List(); foreach (var track in tracks) { IEnumerable artistTags; if (!artistTagViewModel.TryGetValue(track.Artist.Name, out artistTags)) { artistTags = new List(); } var viewModel = TrackViewModel.BuildTrackViewModel(track, artistTags); viewModels.Add(viewModel); } return viewModels; } return new List(); } public async Task> GetTagsAsync(Application app, string[] artists, string[] tags) { const int lagomSimilarArtists = 30; var tagResult = new ConcurrentBag(); var genreCategories = _genreManager.GetGenreCategories().ToDictionary(g => g.ID, g => g.Name); if (artists != null) { var artistList = artists.Select(a => _playlistGenerationManager.GetArtistAsync(a).Result).Where(a => a != null).ToList(); if (artistList.Any()) { var artistTags = _playlistGenerationManager.GetArtistTagsAsync(app, artistList).Result; //TODO: Just flatten? Sum? Average? var flatTags = artistTags.SelectMany(t => t.Value).GroupBy(a => a.Item).Select(t => new RankedItem(t.Key, t.Sum(v => v.Weight))).ToList(); //var flatTags = artistTags.SelectMany(t => t.Value).Where(t => t.Weight > 0); foreach (var tag in flatTags) { tagResult.Add(new TagViewModel(tag, genreCategories)); } ConcurrentBag similarArtists = new ConcurrentBag(); await artistList.ForEachAsync(5, async artist => { var artistSimilarArtists = await _playlistGenerationManager.GetSimilarArtistsAsync(artist, lagomSimilarArtists); artistSimilarArtists.ForEach(a => similarArtists.Add(a)); }); var groupedSimilarArtists = similarArtists.GroupBy(a => a.Artist).Where(IsAcceptableArtistRecommendation).Select(t => new SimilarArtistMatch(t.Key, t.Sum(v => v.Match))); foreach (var artist in groupedSimilarArtists) { tagResult.Add(new TagViewModel(artist)); } } } else { ////We return all tags. //var localTags = GetGlobalAndLocalGenres(app); //foreach (var tag in localTags) //{ // tagResult.Add(new TagViewModel(tag, genreCategories)); //} if (tags != null) { foreach (var tag in tags) { var relatedTags = (await _playlistGenerationManager.GetRelatedTagsAsync(app, tag)).ToList(); foreach (var relatedTag in relatedTags) { tagResult.Add(new TagViewModel(relatedTag, genreCategories)); } } } } return tagResult.DistinctBy(t => t.ID).OrderByDescending(t=> t.Match).ToList(); } public async Task GetPopularArtistsLastModifiedAsync(Application application) { return await _lastModifiedManager.GetLastModifyDateAsync(null, EntityType.TrackTopList); } public async Task> GetPopularArtistsAsync(Application application) { var topList = await _topListManager.GetToplistAsync(TrackReleaseType.Both, null, TopListType.TopPlaylistsAdded7Days, null); return topList.Select(t => t.ArtistName).Distinct().ToList(); } public async Task GetPlaylistLastModifiedDateAsync(Application app) { var globalMarket = _applicationInstanceManager.GetGlobalPushApplication(); var fallbackMarket = _applicationInstanceManager.GetFallbackApplication(); var globalPlaylistLastModified = _lastModifiedManager.GetLastModifyDateAsync(globalMarket, EntityType.Playlists); var localPlaylistLastModified = _lastModifiedManager.GetLastModifyDateAsync(app, EntityType.Playlists); var globalGenreLastModified = _lastModifiedManager.GetLastModifyDateAsync(globalMarket, EntityType.Genres); var localGenreLastModified = _lastModifiedManager.GetLastModifyDateAsync(app, EntityType.Genres); var statsLastModified = _lastModifiedManager.GetLastModifyDateAsync(null, EntityType.PlaylistStats); var localPlaylistCategoriesLastModified = _lastModifiedManager.GetLastModifyDateAsync(app, EntityType.PlaylistCategories); var marketLastModified = _lastModifiedManager.GetLastModifyDateAsync(app, EntityType.Applications); var codeLastModified = CodeLastModified; DateTime fallbackMarketLastModified = DateTime.MinValue; if (app != null && app.IncludeOtherPlaylists) { fallbackMarketLastModified = await _lastModifiedManager.GetLastModifyDateAsync(fallbackMarket, EntityType.Playlists); } await Task.WhenAll(globalPlaylistLastModified, localPlaylistLastModified, globalGenreLastModified, localGenreLastModified, statsLastModified, localPlaylistCategoriesLastModified, marketLastModified); var viewModelGeneration = GetPlaylistViewModelGenerationLastModified(app); var lastModified = new[] { await globalPlaylistLastModified, await localPlaylistLastModified, await globalGenreLastModified, await localGenreLastModified, await statsLastModified, await localPlaylistCategoriesLastModified, codeLastModified, fallbackMarketLastModified, await marketLastModified, viewModelGeneration }.Max(); return lastModified; } protected void SetPlaylistViewModelGenerationLastModified(Application app, DateTime generateDate) { _distributedCacheHandler.Put("PlaylistViewModels-1.0:" + app.ID, generateDate); } protected DateTime GetPlaylistViewModelGenerationLastModified(Application app) { var appPart = (app != null) ? app.ID.ToString() : "null"; var lastGeneration = _distributedCacheHandler.Get("PlaylistViewModels-1.0:" + appPart) as DateTime?; if (lastGeneration == null) return DateTime.MinValue; return lastGeneration.Value; } private bool IsAcceptableArtistRecommendation(IGrouping a) { var artistName = a.Key.Name; if (artistName.Contains(" feat ") || artistName.Contains(" feat.")) return false; return true; } public async Task GetCarouselImageLastModifiedAsync(Application application) { var carouselImageLastModified = await _lastModifiedManager.GetLastModifyDateAsync(application, EntityType.CarouselImage); return new[] {CodeLastModified, carouselImageLastModified}.Max(); } public async Task GetMarketGlobalizationLastModifiedAsync(Application application) { var marketLastModified = _lastModifiedManager.GetLastModifyDateAsync(application, EntityType.MarketGlobalization); var globalLastModified = _lastModifiedManager.GetLastModifyDateAsync(null, EntityType.MarketGlobalization); return new[] {await marketLastModified, await globalLastModified}.Max(); } public async Task GetPlaylistCategoriesLastModifiedDateAsync(Application app) { var categoriesLastModified = _lastModifiedManager.GetLastModifyDateAsync(app, EntityType.PlaylistCategories); var categoriIconLastModified = _lastModifiedManager.GetLastModifyDateAsync(null, EntityType.PlaylistCategoryIcons); var lastModified = new[] { CodeLastModified, await categoriesLastModified, await categoriIconLastModified }.Max(); return lastModified; } public ContentPage GetPage(Application application, ServiceType serviceType, string pageKey) { return _contentManager.GetPage(application, serviceType, pageKey); } public List GetPages(Application application, ServiceType serviceType) { return _contentManager.GetPages(application, serviceType); } public async Task GetPromotedTracksLastModifiedAsync(Application app) { return await _lastModifiedManager.GetLastModifyDateAsync(app, EntityType.PromotedTracks); } } }