using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.Linq; namespace MediaBrowser.Server.Implementations.TV { public class TVSeriesManager : ITVSeriesManager { private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager) { _userManager = userManager; _userDataManager = userDataManager; _libraryManager = libraryManager; } public QueryResult GetNextUp(NextUpQuery request) { var user = _userManager.GetUserById(request.UserId); if (user == null) { throw new ArgumentException("User not found"); } var parentIds = string.IsNullOrEmpty(request.ParentId) ? new string[] { } : new[] { request.ParentId }; string presentationUniqueKey = null; int? limit = null; if (!string.IsNullOrWhiteSpace(request.SeriesId)) { var series = _libraryManager.GetItemById(request.SeriesId); if (series != null) { presentationUniqueKey = series.PresentationUniqueKey; limit = 1; } } var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Series).Name }, SortOrder = SortOrder.Ascending, PresentationUniqueKey = presentationUniqueKey, Limit = limit }, parentIds).Cast(); // Avoid implicitly captured closure var episodes = GetNextUpEpisodes(request, user, items); return GetResult(episodes, null, request); } public QueryResult GetNextUp(NextUpQuery request, IEnumerable parentsFolders) { var user = _userManager.GetUserById(request.UserId); if (user == null) { throw new ArgumentException("User not found"); } string presentationUniqueKey = null; int? limit = null; if (!string.IsNullOrWhiteSpace(request.SeriesId)) { var series = _libraryManager.GetItemById(request.SeriesId); if (series != null) { presentationUniqueKey = series.PresentationUniqueKey; limit = 1; } } var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Series).Name }, SortOrder = SortOrder.Ascending, PresentationUniqueKey = presentationUniqueKey, Limit = limit }, parentsFolders.Select(i => i.Id.ToString("N"))).Cast(); // Avoid implicitly captured closure var episodes = GetNextUpEpisodes(request, user, items); return GetResult(episodes, null, request); } public IEnumerable GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable series) { // Avoid implicitly captured closure var currentUser = user; return series .AsParallel() .Select(i => GetNextUp(i, currentUser)) // Include if an episode was found, and either the series is not unwatched or the specific series was requested .Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId))) .OrderByDescending(i => i.Item2) .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) .Select(i => i.Item1); } /// /// Gets the next up. /// /// The series. /// The user. /// Task{Episode}. private Tuple GetNextUp(Series series, User user) { // Get them in display order, then reverse var allEpisodes = series.GetEpisodes(user, false, false) .Where(i => !i.ParentIndexNumber.HasValue || i.ParentIndexNumber.Value != 0) .Reverse() .ToList(); Episode lastWatched = null; var lastWatchedDate = DateTime.MinValue; Episode nextUp = null; var unplayedEpisodes = new List(); // Go back starting with the most recent episodes foreach (var episode in allEpisodes) { var userData = _userDataManager.GetUserData(user, episode); if (userData.Played) { if (lastWatched != null || nextUp == null) { break; } lastWatched = episode; lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue; } else { unplayedEpisodes.Add(episode); nextUp = episode; } } if (lastWatched != null) { return new Tuple(nextUp, lastWatchedDate, false); } Episode firstEpisode = null; // Find the first unplayed episode. Start from the back of the list since they're in reverse order for (var i = unplayedEpisodes.Count - 1; i >= 0; i--) { var unplayedEpisode = unplayedEpisodes[i]; firstEpisode = unplayedEpisode; break; } // Return the first episode return new Tuple(firstEpisode, DateTime.MinValue, true); } private QueryResult GetResult(IEnumerable items, int? totalRecordLimit, NextUpQuery query) { var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray(); var totalCount = itemsArray.Length; if (query.Limit.HasValue) { itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray(); } else if (query.StartIndex.HasValue) { itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray(); } return new QueryResult { TotalRecordCount = totalCount, Items = itemsArray }; } } }