using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web.Hosting; using Nancy; using Nancy.ModelBinding; using Sony.Filtr.AdminAPI.Config; using Sony.Filtr.AdminAPI.Utility; using Sony.Filtr.AdminAPI.ViewModels; using Sony.Filtr.Applications; using Sony.Filtr.Buzz; using Sony.Filtr.Contracts.Definitions; using Sony.Filtr.Contracts.Entities; using Sony.Filtr.Contracts.Entities.Buzz; using Sony.Filtr.Core.EditorialPlaylists; using Sony.Filtr.DistributedCaching; using Sony.Filtr.Playlists.Spotify; using Sony.Filtr.Playlists.Spotify.Model.History; using Sony.Filtr.SpotifyWebAPI; using Sony.Filtr.Utility; using Sony.Filtr.Utility.Extensions; using Sony.Filtr.SpotifyAnalytics; using TaskExtensions = Sony.Filtr.Utility.Extensions.TaskExtensions; using Sony.Filtr.Utility.Logging; using Sony.Filtr.AdminAPI.Extensions; using Sony.Filtr.DspWebAPI; using Sony.Filtr.DspWebAPI.DspModels; using System.Diagnostics; namespace Sony.Filtr.AdminAPI.Modules.SpotifySpecific { public class SpotifyPlaylistModule : BaseModule { private readonly SpotifyPlaylistManager _spotifyPlaylistManager; private readonly SpotifyWebApi _spotifyWebApi; private readonly BuzzAccountManager _buzzAccountManager; private readonly DistributedCacheHandlerReserve _distributedCacheHandler; private readonly BuzzOwnerViewModelHelper _buzzOwnerViewModelHelper; private readonly EditorialPlaylistManager _editorialPlaylistManager; private readonly SpotifyPlaylistHistoricTrackListManager _historicTrackListManager; private readonly IDspWebApi _dspWebApi; public SpotifyPlaylistModule(SpotifyPlaylistManager spotifyPlaylistManager, ApplicationInstanceManager applicationInstanceManager, SpotifyWebApi spotifyWebApi, BuzzAccountManager buzzAccountManager, DistributedCacheHandlerReserve distributedCacheHandler, BuzzOwnerViewModelHelper buzzOwnerViewModelHelper, EditorialPlaylistManager editorialPlaylistManager, SpotifyAnalyticsManager spotifyAnalyticsManager, SpotifyPlaylistHistoricTrackListManager historicTrackListManager, ILogger logger, IDspWebApi dspWebApi) : base(applicationInstanceManager, logger) { _spotifyPlaylistManager = spotifyPlaylistManager; _spotifyWebApi = spotifyWebApi; _buzzAccountManager = buzzAccountManager; _distributedCacheHandler = distributedCacheHandler; _buzzOwnerViewModelHelper = buzzOwnerViewModelHelper; _editorialPlaylistManager = editorialPlaylistManager; _historicTrackListManager = historicTrackListManager; _dspWebApi = dspWebApi; Get["GetMarketPlaylists", "{countryCode}/marketPlaylists/", true] = async (parameters, cx) => { if (CurrentApplication == null) return HttpStatusCode.BadRequest; var market = CurrentApplication.SpotifyRegionCode; var buzzCategoryId = Maybe.ToEnumOrDefault((string)Request.Query.buzzCategoryId) ?? StaticBuzzCategory.SonyMusic; var limit = Maybe.ToIntOrFallback((string)Request.Query.limit, 1000); var offset = Maybe.ToIntOrFallback((string)Request.Query.offset, 0); var playlistViewModels = await GetMarketPlaylistData(market, buzzCategoryId); playlistViewModels = SortByParameters(playlistViewModels, Request).ToList(); var responseList = playlistViewModels.Paginate(limit, offset); return Response.AsJson(responseList); }; Get["GetMarketPlaylistOverview", "/marketPlaylists/Overview", true] = async (parameters, cx) => { var limit = Maybe.ToIntOrFallback((string)Request.Query.limit, 100); var offset = Maybe.ToIntOrFallback((string)Request.Query.offset, 0); var lastDate = Maybe.ToDateTimeOrFallback((string)Request.Query.lastDate, DateTime.UtcNow.AddDays(-3)); var marketFiltrPlaylistStreamsTask = spotifyAnalyticsManager.GetMarketPlaylistStreamsAsync(SpotifyAnalyticsAccount.Sony, null, lastDate.AddDays(-56), lastDate, (int)StaticBuzzCategory.SonyMusic); var marketPlaylistsOverviewTask = spotifyPlaylistManager.GetPlaylistCountryOverviewAsync(StaticBuzzCategory.SonyMusic); var applications = applicationInstanceManager.GetApplications(); var streamSummaries = spotifyAnalyticsManager.GetStreamSummary(SpotifyAnalyticsAccount.Sony, lastDate.AddDays(-56), lastDate); var streamSummaryDic = streamSummaries.GroupBy(s => s.Market).ToDictionary(k => k.Key, v => v); var marketFiltrPlaylistStreamsDic = (await marketFiltrPlaylistStreamsTask).GroupBy(s => s.Market).ToDictionary(k => k.Key, v => v); var marketPlaylistsOverviewDic = (await marketPlaylistsOverviewTask).Where(p => p.CountryCode != null).ToDictionary(k => k.CountryCode, v => v); List viewModels = new List(); foreach (var market in applications.GroupBy(a => a.SpotifyRegionCode)) { var countryCode = market.Key; var marketName = market.First().Name; var viewModel = new MarketPlaylistOverviewViewModel() { MarketCode = countryCode, MarketName = marketName, }; var playlistOverview = marketPlaylistsOverviewDic.GetValueOrDefault(countryCode); if (playlistOverview != null) { viewModel.PlaylistSubscribers = playlistOverview.PlaylistFollowers; viewModel.PublicPlaylistSubscribers = playlistOverview.PublicPlaylistFollowers; viewModel.PlaylistCount = playlistOverview.Playlists; viewModel.PublicPlaylistCount = playlistOverview.PublicPlaylists; } var currentMarketFiltrPlaylistStreams = marketFiltrPlaylistStreamsDic.GetValueOrDefault(countryCode); if (currentMarketFiltrPlaylistStreams != null) { var playlistStreams28Days = currentMarketFiltrPlaylistStreams.Where(p => p.Date >= lastDate.AddDays(-28)).ToList(); var playlistStreams56Days = currentMarketFiltrPlaylistStreams.Where(p => p.Date >= lastDate.AddDays(-56)).ToList(); viewModel.MarketOwnedSonyPlaylistUsers28Days = playlistStreams28Days.Sum(s => (long)s.LocalListListeners); viewModel.MarketOwnedSonyPlaylistUsers56Days = playlistStreams56Days.Sum(s => (long)s.LocalListListeners); viewModel.MarketOwnedSonyPlaylistStreams28Days = playlistStreams28Days.Sum(s => (long)s.LocalListStreams); viewModel.MarketOwnedSonyPlaylistStreams56Days = playlistStreams56Days.Sum(s => (long)s.LocalListStreams); viewModel.SonyPlaylistUsers28Days = playlistStreams28Days.Sum(s => (long)s.TotalListListeners); viewModel.SonyPlaylistUsers56Days = playlistStreams56Days.Sum(s => (long)s.TotalListListeners); viewModel.SonyPlaylistStreams28Days = playlistStreams28Days.Sum(s => (long)s.TotalListStreams); viewModel.SonyPlaylistStreams56Days = playlistStreams56Days.Sum(s => (long)s.TotalListStreams); } var marketStreamSummaries = streamSummaryDic.GetValueOrDefault(countryCode); if (marketStreamSummaries != null) { var marketStreamSummaries28Days = marketStreamSummaries.Where(p => p.Date >= lastDate.AddDays(-28)).ToList(); var marketStreamSummaries56Days = marketStreamSummaries.Where(p => p.Date >= lastDate.AddDays(-56)).ToList(); viewModel.Streams28Days = marketStreamSummaries28Days.Sum(t => (long)t.TotalStreams); viewModel.Streams56Days = marketStreamSummaries56Days.Sum(t => (long)t.TotalStreams); viewModel.Users28Days = marketStreamSummaries28Days.Sum(t => (long)t.UniqueUsers); viewModel.Users56Days = marketStreamSummaries56Days.Sum(t => (long)t.UniqueUsers); viewModel.PlaylistStreams28Days = marketStreamSummaries28Days.Sum(t => (long)t.SourcePlaylist); viewModel.PlaylistStreams56Days = marketStreamSummaries56Days.Sum(t => (long)t.SourcePlaylist); viewModel.PlaylistUsers28Days = marketStreamSummaries28Days.Sum(t => t.UniqueUsersSourcePlaylist); viewModel.PlaylistUsers56Days = marketStreamSummaries56Days.Sum(t => t.UniqueUsersSourcePlaylist); } viewModels.Add(viewModel); } viewModels = viewModels.OrderBy(p => p.MarketCode).Where(p => p.Streams56Days > 0).ToList(); var paginatedList = viewModels.Paginate(limit, offset); return Response.AsJson(paginatedList); }; Get["GetSpotifyPlaylistsByUser", "/spotifyPlaylistsByUser/", true] = async (parameters, cx) => { var market = GetSpecifiedMarket(); long total = 0; var userUri = (string)Request.Query.userUri; if (string.IsNullOrWhiteSpace(userUri)) return HttpStatusCode.BadRequest; var username = new SpotifyLink(userUri).ExtractSpotifyUserName(); if (string.IsNullOrWhiteSpace(username)) return HttpStatusCode.BadRequest; var limit = Maybe.ToIntOrFallback((string)Request.Query.limit, 1000); var offset = Maybe.ToIntOrFallback((string)Request.Query.offset, 0); var tasks = new List(); var cacheKey = $"SpotifyPlaylistsByUser-1.8:{userUri}:{market}";//:{offset}:{limit}"; var cacheKeyCount = cacheKey + ":Count"; var playlistViewModels = distributedCacheHandler.Get(cacheKey) as List; var cachedCount = distributedCacheHandler.Get(cacheKeyCount); if (cachedCount != null) { total = Convert.ToInt64(cachedCount); } else { tasks.Add(Task.Run(async () => { var count = await spotifyPlaylistManager.CountGeneralAmountOfPlaylistsForUser(username); total = count; Task.Run(() => distributedCacheHandler.Put(cacheKeyCount, count, DateTime.Now.AddMinutes(5))); })); } if (playlistViewModels == null) { tasks.Add(Task.Run(async () => { IEnumerable buzzUsers = Enumerable.Empty(); IEnumerable buzzCategories = Enumerable.Empty(); IEnumerable playlists = Enumerable.Empty(); var tasksInternal = new List { Task.Run(async () => { playlists = await spotifyPlaylistManager.GetPlaylistsForUserAsync(username, market, offset: null, limit: null); }), Task.Run(async () => { buzzUsers = await buzzAccountManager.GetBuzzUsersAsync(musicServiceId: (int)MusicService.Spotify); buzzCategories = buzzAccountManager.GetBuzzCategories(); }) }; await TaskExtensions.EnsureAllSuccessAsync(tasksInternal); playlistViewModels = BuildViewModelWithOwner(playlists, buzzUsers, buzzCategories).OrderByDescending(x => x.LocalUsers28Days).ThenBy(x => x.Subscribers28Days).ToList(); Task.Run(() => distributedCacheHandler.Put(cacheKey, playlistViewModels, DateTime.Now.AddHours(12))); })); } await TaskExtensions.EnsureAllSuccessAsync(tasks); playlistViewModels = playlistViewModels.Skip(offset).Take(limit).ToList(); return Response.AsJson(playlistViewModels.ExtendForPagination(limit, offset, total)); }; Get["GetPlaylistsWithTrack", "/spotifyPlaylistsWithTrack/", true] = async (parameters, cx) => { Response result = await LogActionWrapper.Execute( async () => await GetPlaylistsWithTrack(spotifyPlaylistManager, Request), Logger, "/spotifyPlaylistsWithTrack/ -> GetPlaylistsWithTrack", ExtractRequestData(Request)); return result; }; Get["GetMultipleSpotifyPlaylist", "/spotifyPlaylists/", true] = async (parameters, cx) => { var playlistIds = Request.GetMultiplePlaylistIds(throwIfMissing: false); if (!playlistIds.Any()) { var playlistBody = this.Bind(); playlistIds = playlistBody.PlaylistIds; } if (!playlistIds.Any()) { throw new NancyStatusException(HttpStatusCode.BadRequest, "No playlists specified"); } var market = (string)Request.Query.market; var playlists = await spotifyPlaylistManager.GetPlaylistsAsync(playlistIds, market); var playlistViewModels = BuildPlaylistTrackViewModelWithOwner(playlists); return Response.AsJson(playlistViewModels); }; Get["GetPersonalizedPlaylists", "/spotifyPlaylists/personalized"] = parameters => { var personalizedPlaylists = _spotifyPlaylistManager.GetPersonalizedPlaylists(); var limit = Maybe.ToIntOrFallback((string)Request.Query.limit, 500); var offset = Maybe.ToIntOrFallback((string)Request.Query.offset, 0); return Response.AsJson(personalizedPlaylists.Paginate(limit, offset)); }; } private async Task GetPlaylistsWithTrack(SpotifyPlaylistManager spotifyPlaylistManager, Request request) { //mostly only 1 isrc string[] isrcs = await GetIsrcFromRequestAsync(request); if (isrcs.Length == 0) { return new Response { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = "isrc is required." }; } var limit = Maybe.ToIntOrFallback((string)Request.Query.limit, 1000); var offset = Maybe.ToIntOrFallback((string)Request.Query.offset, 0); DateTime date = Maybe.ToDateTimeOrFallback((string)Request.Query.date, DateTime.Now.AddDays(-2).Date); string market = GetSpecifiedMarket(); string cacheKey = $"GetPlaylistsWithTrack:{typeof(SpotifyPlaylistWithTrackData)}:{String.Join("_", isrcs)}:{market}"; SpecialListCacheItem playlistsCached = this._distributedCacheHandler.GetSpecialList(cacheKey); TimeSpan cacheTime = TimeSpan.FromMinutes(15); if (playlistsCached.ShouldReloadData(cacheTime)) { PaginatedContent playlistsAll = await GetPlaylistsWithStreamsAsync(isrcs, market, date); this._distributedCacheHandler.PutSpecial(cacheKey, playlistsAll.Items, DateTime.Now.Add(cacheTime)); playlistsCached = new SpecialListCacheItem() { Items = playlistsAll.Items }; } PaginatedContent playlists = new PaginatedContent() { Items = playlistsCached.Items.Skip(offset).Take(limit).ToList(), Pagination = new Pagination() { Limit = limit, Offset = offset, Total = playlistsCached.Items.Count } }; Dictionary> historicTrackPosition = await GetTrackPositionChangesAsync(playlists.Items.Select(p => p.Playlist.PlaylistId).ToList(), isrcs); var paginatedContent = new PaginatedContent() { Items = BuildPlaylistTrackViewModelWithOwner(playlists.Items, historicTrackPosition), Pagination = playlists.Pagination }; return Response.AsJson(paginatedContent); } private async Task> GetPlaylistsWithStreamsAsync(string[] isrcs, string market, DateTime date) { var stream1DayTasks = isrcs.Select(isrc => _dspWebApi.GetSpotifyPlaylistsSummaryAsync(isrc, date, date, market)).ToList(); var stream7DayTasks = isrcs.Select(isrc => _dspWebApi.GetSpotifyPlaylistsSummaryAsync(isrc, date.AddDays(-6), date, market)).ToList(); var playlistsAllTask = _spotifyPlaylistManager.GetPlaylistsWithTrackAsync(isrcs, market); var tasksToAwait = new List(); tasksToAwait.Add(playlistsAllTask); tasksToAwait.AddRange(stream1DayTasks); tasksToAwait.AddRange(stream7DayTasks); await Task.WhenAll(tasksToAwait.ToArray()); var playlistsAll = playlistsAllTask.Result; InitStreams(playlistsAll.Items, stream1DayTasks.Select(t => t.Result).ToList(), stream7DayTasks.Select(t => t.Result).ToList()); return playlistsAll; } private void InitStreams(List playlists, List oneDayStreams, List sevenDayStreams) { var oneDayDic = oneDayStreams.SelectMany(s => s.Items).ToDictionaryWithListValues(p => p.PlaylistId); var sevenDayDic = sevenDayStreams.SelectMany(s => s.Items).ToDictionaryWithListValues(p => p.PlaylistId); foreach (var item in playlists) { item.Playlist.Streams = new TrackStreamsInPlaylists() { Global1Day = oneDayDic.ContainsKey(item.Playlist.PlaylistId) ? oneDayDic[item.Playlist.PlaylistId].Sum(streams => streams.GlobalStreams) : 0, Global7Days = sevenDayDic.ContainsKey(item.Playlist.PlaylistId) ? sevenDayDic[item.Playlist.PlaylistId].Sum(streams => streams.GlobalStreams) : 0, Local1Day = oneDayDic.ContainsKey(item.Playlist.PlaylistId) ? oneDayDic[item.Playlist.PlaylistId].Sum(streams => streams.LocalStreams) : 0, Local7Days = sevenDayDic.ContainsKey(item.Playlist.PlaylistId) ? sevenDayDic[item.Playlist.PlaylistId].Sum(streams => streams.LocalStreams) : 0, }; } } private async Task> GetMarketPlaylistData(string market, StaticBuzzCategory buzzCategoryId) { var cacheKey = "GetMarketPlaylistData:" + typeof(SpotifyPlaylistWithOwnerViewModel) + ":" + market + ":" + buzzCategoryId; var playlistMarketData = _distributedCacheHandler.GetSpecialList(cacheKey); var cacheTimeSpan = TimeSpan.FromHours(1); var shouldUseExistingData = playlistMarketData?.UtcCacheTime > DateTime.UtcNow.Add(-cacheTimeSpan); if (playlistMarketData == null || !shouldUseExistingData) { var playlistViewModels = await GetMarketPlaylistDataInternalAsync(market, buzzCategoryId); _distributedCacheHandler.PutSpecial(cacheKey, playlistViewModels, groupCount: 500); return playlistViewModels; } else { var staleTime = TimeSpan.FromMinutes(10); var needToUpdateData = playlistMarketData.UtcCacheTime < DateTime.UtcNow.Add(-staleTime); if (needToUpdateData) { HostingEnvironment.QueueBackgroundWorkItem(async token => { try { //TODO: Check if we are already running update in background. var playlistViewModels = await GetMarketPlaylistDataInternalAsync(market, buzzCategoryId, runOnAspNetThreadPool: true); _distributedCacheHandler.PutSpecial(cacheKey, playlistViewModels, groupCount: 500); } catch (Exception ex) { ErrorLogging.ErrorLoggingManager.Instance.LogError(ex); } }); } return playlistMarketData.Items; } } private async Task> GetMarketPlaylistDataInternalAsync(string market, StaticBuzzCategory buzzCategoryId, bool runOnAspNetThreadPool = false) { var tasks = new List(); var playlists = new List(); var buzzUsers = Enumerable.Empty(); var buzzCategories = Enumerable.Empty(); var customPlaylists = Enumerable.Empty(); tasks.Add(Task.Run(async () => { playlists.AddRange(await _spotifyPlaylistManager.GetPlaylistsForMarketAsync(market, (int)buzzCategoryId)); })); tasks.Add(Task.Run(async () => { buzzUsers = await _buzzAccountManager.GetBuzzUsersAsync(buzzCategoryId: (int)buzzCategoryId, musicServiceId: (int)MusicService.Spotify); })); tasks.Add(Task.Run(() => { buzzCategories = _buzzAccountManager.GetBuzzCategories(); })); tasks.Add(Task.Run(async () => { customPlaylists = await _spotifyPlaylistManager.GetCustomPlaylistsForMarketAsync(market); })); if (runOnAspNetThreadPool) await TaskExtensions.EnsureAllSuccessAsync(tasks); else await TaskExtensions.EnsureAllSuccessAsync(tasks, TaskScheduler.FromCurrentSynchronizationContext()); playlists.AddRange(customPlaylists); var playlistViewModels = BuildViewModelWithOwner(playlists, buzzUsers, buzzCategories); return playlistViewModels; } public class MultiplePlaylistIdPostBody { public List PlaylistIds { get; set; } } private async Task>> GetTrackPositionChangesAsync(List playlistIds, IEnumerable isrcs) { return playlistIds.Any() ? await _historicTrackListManager.GetTrackPositionChangesByTrackAsync(isrcs, playlistIds, DateTime.Today.AddDays(-14)) : new Dictionary>(); } private async Task GetIsrcFromRequestAsync(Request request) { var ISRCs = (string)request.Query.isrc; if (!string.IsNullOrWhiteSpace(ISRCs)) { return ISRCs.Split(','); } var trackId = (string)request.Query.trackId; return new string[] { await GetIsrcFromTrackId(trackId) }; } private async Task GetIsrcFromTrackId(string trackId) { if (string.IsNullOrWhiteSpace(trackId)) return null; var isrcMapping = (await _spotifyPlaylistManager.GetIsrcFromTrackIdAsync(new List() { trackId })).FirstOrDefault(); var isrc = isrcMapping.Value; if (!string.IsNullOrWhiteSpace(isrc)) return isrc; var spotifyTrack = await _spotifyWebApi.GetAllTracksAsync(new List { trackId }); return spotifyTrack?.FirstOrDefault()?.external_ids?.isrc; } private string GetSpecifiedMarket() { var market = (string)Request.Query.market; if (market == "global" || market == "_gl") // Use NULL as market if global return null; return market; } private List BuildPlaylistTrackViewModelWithOwner(List playlists) { var models = new List(); foreach (var playlist in playlists) { var model = new SpotifyPlaylistWithStreamsAndFollowersItemViewModel() { Name = playlist.Playlist.Name, SpotifyLink = playlist.Playlist.PlaylistUri, Subscribers = playlist.Playlist.Followers, CountryCode = playlist.Playlist.CountryCode, ImageFileName = playlist.Playlist.Image, LocalUsers56Days = playlist.Playlist.Users56Days, LocalUsers28Days = playlist.Playlist.Users28Days, LocalUsers14Days = playlist.Playlist.Users14Days, LocalUsers7Days = playlist.Playlist.Users7Days, LocalUsersLatest = playlist.Playlist.UsersLatest, LocalStreams56Days = playlist.Playlist.Streams56Days, LocalStreams28Days = playlist.Playlist.Streams28Days, LocalStreams14Days = playlist.Playlist.Streams14Days, LocalStreams7Days = playlist.Playlist.Streams7Days, LocalStreamsLatest = playlist.Playlist.StreamsLatest, LocalStreamsPerUser56Days = playlist.Playlist.StreamsPerUser56Days, LocalStreamsPerUser28Days = playlist.Playlist.StreamsPerUser28Days, LocalStreamsPerUser14Days = playlist.Playlist.StreamsPerUser14Days, LocalStreamsPerUser7Days = playlist.Playlist.StreamsPerUser7Days, LocalStreamsPerUserLatest = playlist.Playlist.StreamsPerUserLatest, // Global stream data GlobalStreams56Days = playlist.Playlist.Streams56Days, GlobalStreams28Days = playlist.Playlist.Streams28Days, GlobalStreams14Days = playlist.Playlist.Streams14Days, GlobalStreams7Days = playlist.Playlist.Streams7Days, GlobalStreamsLatest = playlist.Playlist.StreamsLatest, GlobalUsers56Days = playlist.Playlist.Users56Days, GlobalUsers28Days = playlist.Playlist.Users28Days, GlobalUsers14Days = playlist.Playlist.Users14Days, GlobalUsers7Days = playlist.Playlist.Users7Days, GlobalUsersLatest = playlist.Playlist.UsersLatest, // Market stream data MarketStreams56Days = playlist.MarketPlaylistStreams?.MarketStreams56Days, MarketStreams28Days = playlist.MarketPlaylistStreams?.MarketStreams28Days, MarketStreams14Days = playlist.MarketPlaylistStreams?.MarketStreams14Days, MarketStreams7Days = playlist.MarketPlaylistStreams?.MarketStreams7Days, MarketStreamsLatest = playlist.MarketPlaylistStreams?.MarketStreamsLatest, MarketUsers56Days = playlist.MarketPlaylistStreams?.MarketUsers56Days, MarketUsers28Days = playlist.MarketPlaylistStreams?.MarketUsers28Days, MarketUsers14Days = playlist.MarketPlaylistStreams?.MarketUsers14Days, MarketUsers7Days = playlist.MarketPlaylistStreams?.MarketUsers7Days, MarketUsersLatest = playlist.MarketPlaylistStreams?.MarketUsersLatest, Subscribers1Day = playlist.PlaylistFollowerSummary?.Followers1dayAgo, Subscribers7Days = playlist.PlaylistFollowerSummary?.Followers7daysAgo, Subscribers14Days = playlist.PlaylistFollowerSummary?.Followers14daysAgo, Subscribers28Days = playlist.PlaylistFollowerSummary?.Followers28daysAgo, Subscribers56Days = playlist.PlaylistFollowerSummary?.Followers56daysAgo, StreamDaysInPeriod7Day = playlist.PlaylistStreamSummary.StreamsDays7days, StreamDaysInPeriod14Day = playlist.PlaylistStreamSummary.StreamsDays14days, StreamDaysInPeriod28Day = playlist.PlaylistStreamSummary.StreamsDays28days, StreamDaysInPeriod56Day = playlist.PlaylistStreamSummary.StreamsDays56days, TrackCount = playlist.Playlist.TrackCount, Public = playlist.Playlist.Public, TracksLastAdded = playlist.Playlist.TrackLatestAdded, Owner = BuildBuzzOwnerViewModel(playlist.BuzzUser), }; models.Add(model); } return models; } private List BuildPlaylistTrackViewModelWithOwner( List playlists, Dictionary> trackPositionChange) { var models = new List(); foreach (var playlist in playlists) { string playlistId = playlist.Playlist.PlaylistId; var model = new SpotifyPlaylistWithTrackItemViewModel() { Name = playlist.Playlist.Name, SpotifyLink = playlist.Playlist.PlaylistUri, Subscribers = playlist.Playlist.Followers, CountryCode = playlist.Playlist.CountryCode, ImageFileName = playlist.Playlist.Image, LocalUsers56Days = playlist.Playlist.Users56Days, LocalUsers28Days = playlist.Playlist.Users28Days, LocalUsers14Days = playlist.Playlist.Users14Days, LocalUsers7Days = playlist.Playlist.Users7Days, LocalUsersLatest = playlist.Playlist.UsersLatest, LocalStreams56Days = playlist.Playlist.Streams56Days, LocalStreams28Days = playlist.Playlist.Streams28Days, LocalStreams14Days = playlist.Playlist.Streams14Days, LocalStreams7Days = playlist.Playlist.Streams7Days, LocalStreamsLatest = playlist.Playlist.StreamsLatest, LocalStreamsPerUser56Days = playlist.Playlist.StreamsPerUser56Days, LocalStreamsPerUser28Days = playlist.Playlist.StreamsPerUser28Days, LocalStreamsPerUser14Days = playlist.Playlist.StreamsPerUser14Days, LocalStreamsPerUser7Days = playlist.Playlist.StreamsPerUser7Days, LocalStreamsPerUserLatest = playlist.Playlist.StreamsPerUserLatest, // Global stream data GlobalStreams56Days = playlist.Playlist.Streams56Days, GlobalStreams28Days = playlist.Playlist.Streams28Days, GlobalStreams14Days = playlist.Playlist.Streams14Days, GlobalStreams7Days = playlist.Playlist.Streams7Days, GlobalStreamsLatest = playlist.Playlist.StreamsLatest, GlobalUsers56Days = playlist.Playlist.Users56Days, GlobalUsers28Days = playlist.Playlist.Users28Days, GlobalUsers14Days = playlist.Playlist.Users14Days, GlobalUsers7Days = playlist.Playlist.Users7Days, GlobalUsersLatest = playlist.Playlist.UsersLatest, // Market stream data MarketStreams56Days = playlist.MarketPlaylistStreams?.MarketStreams56Days, MarketStreams28Days = playlist.MarketPlaylistStreams?.MarketStreams28Days, MarketStreams14Days = playlist.MarketPlaylistStreams?.MarketStreams14Days, MarketStreams7Days = playlist.MarketPlaylistStreams?.MarketStreams7Days, MarketStreamsLatest = playlist.MarketPlaylistStreams?.MarketStreamsLatest, MarketUsers56Days = playlist.MarketPlaylistStreams?.MarketUsers56Days, MarketUsers28Days = playlist.MarketPlaylistStreams?.MarketUsers28Days, MarketUsers14Days = playlist.MarketPlaylistStreams?.MarketUsers14Days, MarketUsers7Days = playlist.MarketPlaylistStreams?.MarketUsers7Days, MarketUsersLatest = playlist.MarketPlaylistStreams?.MarketUsersLatest, Subscribers1Day = playlist.PlaylistFollowerSummary?.Followers1dayAgo, Subscribers7Days = playlist.PlaylistFollowerSummary?.Followers7daysAgo, Subscribers14Days = playlist.PlaylistFollowerSummary?.Followers14daysAgo, Subscribers28Days = playlist.PlaylistFollowerSummary?.Followers28daysAgo, Subscribers56Days = playlist.PlaylistFollowerSummary?.Followers56daysAgo, StreamDaysInPeriod7Day = playlist.PlaylistStreamSummary?.StreamsDays7days ?? 0, StreamDaysInPeriod14Day = playlist.PlaylistStreamSummary?.StreamsDays14days ?? 0, StreamDaysInPeriod28Day = playlist.PlaylistStreamSummary?.StreamsDays28days ?? 0, StreamDaysInPeriod56Day = playlist.PlaylistStreamSummary?.StreamsDays56days ?? 0, TrackCount = playlist.Playlist.TrackCount, Public = playlist.Playlist.Public, TracksLastAdded = playlist.Playlist.TrackLatestAdded, Owner = BuildBuzzOwnerViewModel(playlist.BuzzUser), Position = playlist.TracklistItem.Position,// + 1, //Expose as 1-indexed EarliestAdded = playlist.TracklistItem.EarliestAdded, Added = playlist.TracklistItem.Added, ISRC = playlist.TracklistItem.ISRC, Streams = new TrackStreamsInPlaylists() { Global1Day = playlist.Playlist.Streams.Global1Day, Global7Days = playlist.Playlist.Streams.Global7Days, Local1Day = playlist.Playlist.Streams.Local1Day, Local7Days = playlist.Playlist.Streams.Local7Days } }; Tuple trend = trackPositionChange .GetValueOrDefault(playlistId) .CalculateTrend(playlist.TracklistItem.Position); if (trend != null) { model.PositionChange = trend.Item1; model.PositionChangeDate = trend.Item2; } model.IsEntry = trackPositionChange.GetValueOrDefault(playlistId).IsEntry(playlist.TracklistItem.Position, DateTime.Now); models.Add(model); } return models; } private static BuzzOwnerViewModel BuildBuzzOwnerViewModel(SpotifyPlaylistBuzzItem playlistBuzzUser) { if (playlistBuzzUser.BuzzUsername != null) { return new BuzzOwnerViewModel() { AccountId = playlistBuzzUser.BuzzUsername, AccountName = playlistBuzzUser.BuzzDisplayName ?? playlistBuzzUser.BuzzUsername, CategoryId = playlistBuzzUser.BuzzCategoryId, CategoryName = playlistBuzzUser.BuzzCategoryName, MarketCode = playlistBuzzUser.BuzzCountryCode }; } return null; } private IEnumerable SortByParameters(IEnumerable playlistViewModels, Request request) { var orderByDirection = Maybe.ToEnumOrDefault((string)request.Query.sortOrder) ?? OrderByDirection.Asc; IEnumerable sortedPlaylists = playlistViewModels.OrderByDescending(p => p.LocalStreams28Days); SpotifyPlaylistSortOrder sortOrder; const bool ignoreCase = true; if (Enum.TryParse((string)request.Query.sortby, ignoreCase, out sortOrder)) { switch (sortOrder) { case SpotifyPlaylistSortOrder.LocalStreams28Days: sortedPlaylists = sortedPlaylists.OrderBy(p => p.LocalStreams28Days, orderByDirection); break; case SpotifyPlaylistSortOrder.LocalStreams7Days: sortedPlaylists = sortedPlaylists.OrderBy(p => p.LocalStreams7Days, orderByDirection); break; case SpotifyPlaylistSortOrder.LocalStreamsLatest: sortedPlaylists = sortedPlaylists.OrderBy(p => p.LocalStreamsLatest, orderByDirection); break; case SpotifyPlaylistSortOrder.LocalUsers28Days: sortedPlaylists = sortedPlaylists.OrderBy(p => p.LocalUsers28Days, orderByDirection); break; case SpotifyPlaylistSortOrder.LocalUsers7Days: sortedPlaylists = sortedPlaylists.OrderBy(p => p.LocalUsers7Days, orderByDirection); break; case SpotifyPlaylistSortOrder.LocalUsersLatest: sortedPlaylists = sortedPlaylists.OrderBy(p => p.LocalUsersLatest, orderByDirection); break; case SpotifyPlaylistSortOrder.TracksLastAdded: sortedPlaylists = sortedPlaylists.OrderBy(p => p.TracksLastAdded, orderByDirection); break; case SpotifyPlaylistSortOrder.Subscribers: sortedPlaylists = sortedPlaylists.OrderBy(p => p.Subscribers, orderByDirection); break; } } return sortedPlaylists; } private List BuildViewModelWithOwner( IEnumerable playlists, IEnumerable buzzUsers, IEnumerable buzzCategories) { var models = new List(); var buzzUsersDic = buzzUsers.ToDictionary(k => k.Username, v => v, StringComparer.InvariantCultureIgnoreCase); var buzzCategoryDic = buzzCategories.ToDictionary(k => k.ID, v => v.Name); foreach (var playlist in playlists) { var buzzUser = buzzUsersDic.GetValueOrDefault(playlist.Playlist.User); var model = new SpotifyPlaylistWithOwnerViewModel() { CountryCode = playlist.Playlist.CountryCode, ImageFileName = playlist.Playlist.Image, Name = playlist.Playlist.Name, SpotifyLink = playlist.Playlist.PlaylistUri, Subscribers = playlist.Playlist.Followers, TrackCount = playlist.Playlist.TrackCount, Public = playlist.Playlist.Public, TracksLastAdded = playlist.Playlist.TrackLatestAdded, Owner = _buzzOwnerViewModelHelper.BuildBuzzOwner(buzzUser, buzzCategoryDic), Is_Personalized = playlist.IsPersonalized }; SetStreamInfo(model, playlist.StreamSummary); SetFollowerInfo(model, playlist.FollowerSummary); models.Add(model); } return models; } private void SetFollowerInfo(SpotifyPlaylistWithOwnerViewModel model, PlaylistFollowerSummary playlistFollowerSummary) { if (playlistFollowerSummary == null) return; model.Subscribers1Day = playlistFollowerSummary.Followers1dayAgo; model.Subscribers7Days = playlistFollowerSummary.Followers7daysAgo; model.Subscribers14Days = playlistFollowerSummary.Followers14daysAgo; model.Subscribers28Days = playlistFollowerSummary.Followers28daysAgo; model.Subscribers56Days = playlistFollowerSummary.Followers56daysAgo; } private void SetStreamInfo(SpotifyPlaylistWithOwnerViewModel model, PlaylistStreamSummary playlistStreamSummary) { if (playlistStreamSummary == null) return; model.LocalUsers56Days = playlistStreamSummary.Users56Days; model.LocalUsers28Days = playlistStreamSummary.Users28Days; model.LocalUsers14Days = playlistStreamSummary.Users14Days; model.LocalUsers7Days = playlistStreamSummary.Users7Days; model.LocalUsersLatest = playlistStreamSummary.UsersLatest; model.LocalStreams56Days = playlistStreamSummary.Streams56Days; model.LocalStreams28Days = playlistStreamSummary.Streams28Days; model.LocalStreams14Days = playlistStreamSummary.Streams14Days; model.LocalStreams7Days = playlistStreamSummary.Streams7Days; model.LocalStreamsLatest = playlistStreamSummary.StreamsLatest; model.LocalStreamsPerUser56Days = Round(playlistStreamSummary.StreamsPerUser56Days); model.LocalStreamsPerUser28Days = Round(playlistStreamSummary.StreamsPerUser28Days); model.LocalStreamsPerUser14Days = Round(playlistStreamSummary.StreamsPerUser14Days); model.LocalStreamsPerUser7Days = Round(playlistStreamSummary.StreamsPerUser7Days); model.LocalStreamsPerUserLatest = Round(playlistStreamSummary.StreamsPerUserLatest); model.LocalAverageStreams56Days = Round(playlistStreamSummary.AverageStreams56Days); model.LocalAverageStreams28Days = Round(playlistStreamSummary.AverageStreams28Days); model.LocalAverageStreams14Days = Round(playlistStreamSummary.AverageStreams14Days); model.LocalAverageStreams7Days = Round(playlistStreamSummary.AverageStreams7Days); model.LocalAverageUsers56Days = Round(playlistStreamSummary.AverageListeners56Days); model.LocalAverageUsers28Days = Round(playlistStreamSummary.AverageListeners28Days); model.LocalAverageUsers14Days = Round(playlistStreamSummary.AverageListeners14Days); model.LocalAverageUsers7Days = Round(playlistStreamSummary.AverageListeners7Days); model.StreamDaysInPeriod56Days = playlistStreamSummary.StreamsDays56days; model.StreamDaysInPeriod28Days = playlistStreamSummary.StreamsDays28days; model.StreamDaysInPeriod14Days = playlistStreamSummary.StreamsDays14days; model.StreamDaysInPeriod7Days = playlistStreamSummary.StreamsDays7days; } private double Round(double value) { return Math.Round(value, 2); } private double? Round(double? value) { if (!value.HasValue) return null; return Round(value.Value); } private List BuildViewModelWithOwner( IEnumerable playlists, IEnumerable buzzUsers, IEnumerable buzzCategories) { var models = new List(); var buzzUsersDic = buzzUsers.ToDictionary(k => k.Username, v => v, StringComparer.InvariantCultureIgnoreCase); var buzzCategoryDic = buzzCategories.ToDictionary(k => k.ID, v => v.Name); foreach (var playlist in playlists) { var buzzUser = buzzUsersDic.GetValueOrDefault(playlist.User); var model = new SpotifyPlaylistWithOwnerViewModel() { CountryCode = playlist.CountryCode, //BuzzCategoryId = playlist.BuzzCategoryId, //Description = playlist.Description, ImageFileName = playlist.Image, LocalUsers56Days = playlist.Users56Days, LocalUsers28Days = playlist.Users28Days, LocalUsers14Days = playlist.Users14Days, LocalUsers7Days = playlist.Users7Days, LocalUsersLatest = playlist.UsersLatest, Name = playlist.Name, SpotifyLink = playlist.PlaylistUri, Subscribers = playlist.Followers, LocalStreams56Days = playlist.Streams56Days, LocalStreams28Days = playlist.Streams28Days, LocalStreams14Days = playlist.Streams14Days, LocalStreams7Days = playlist.Streams7Days, LocalStreamsLatest = playlist.StreamsLatest, LocalStreamsPerUser56Days = playlist.StreamsPerUser56Days, LocalStreamsPerUser28Days = playlist.StreamsPerUser28Days, LocalStreamsPerUser14Days = playlist.StreamsPerUser14Days, LocalStreamsPerUser7Days = playlist.StreamsPerUser7Days, LocalStreamsPerUserLatest = playlist.StreamsPerUserLatest, Subscribers1Day = playlist.Followers1dayAgo, Subscribers7Days = playlist.Followers7daysAgo, Subscribers14Days = playlist.Followers14daysAgo, Subscribers28Days = playlist.Followers28daysAgo, Subscribers56Days = playlist.Followers56daysAgo, StreamDaysInPeriod56Days = playlist.StreamDaysInPeriod56Days, StreamDaysInPeriod28Days = playlist.StreamDaysInPeriod28Days, StreamDaysInPeriod14Days = playlist.StreamDaysInPeriod14Days, StreamDaysInPeriod7Days = playlist.StreamDaysInPeriod7Days, TrackCount = playlist.TrackCount, Public = playlist.Public, TracksLastAdded = playlist.TrackLatestAdded, //TracksLastAdded = attributes.GetValueOrDefault(playlist.PlaylistUri)?.Changed?.ToString("O"), Owner = _buzzOwnerViewModelHelper.BuildBuzzOwner(buzzUser, buzzCategoryDic) }; models.Add(model); } return models; } } public class MarketPlaylistOverviewViewModel { public string MarketCode { get; set; } public string MarketName { get; set; } public long? PlaylistCount { get; set; } public long? PublicPlaylistCount { get; set; } public long? PlaylistSubscribers { get; set; } public long? PublicPlaylistSubscribers { get; set; } public long? MarketOwnedSonyPlaylistUsers28Days { get; set; } public long? MarketOwnedSonyPlaylistUsers56Days { get; set; } public long? MarketOwnedSonyPlaylistStreams28Days { get; set; } public long? MarketOwnedSonyPlaylistStreams56Days { get; set; } public long? SonyPlaylistUsers28Days { get; set; } public long? SonyPlaylistUsers56Days { get; set; } public long? SonyPlaylistStreams28Days { get; set; } public long? SonyPlaylistStreams56Days { get; set; } public long? Streams28Days { get; set; } public long? Streams56Days { get; set; } public long? PlaylistStreams28Days { get; set; } public long? PlaylistStreams56Days { get; set; } public long? Users28Days { get; set; } public long? Users56Days { get; set; } public long? PlaylistUsers28Days { get; set; } public long? PlaylistUsers56Days { get; set; } } public enum SpotifyPlaylistSortOrder { LocalStreams28Days, LocalStreams7Days, LocalStreamsLatest, LocalUsers28Days, LocalUsers7Days, LocalUsersLatest, TracksLastAdded, Subscribers } }