diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs new file mode 100644 index 000000000..840b88af5 --- /dev/null +++ b/MediaBrowser.Api/TvShowsService.cs @@ -0,0 +1,247 @@ +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api +{ + /// + /// Class GetNextUpEpisodes + /// + [Route("/Shows/NextUp", "GET")] + [Api(("Gets a list of currently installed plugins"))] + public class GetNextUpEpisodes : IReturn + { + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } + + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: AudioInfo, Budget, Chapters, DateCreated, DisplayMediaType, DisplayPreferences, EndDate, Genres, HomePageUrl, ItemCounts, IndexOptions, Locations, MediaStreams, Overview, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SeriesInfo, SortName, Studios, Taglines, TrailerUrls, UserData", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } + + /// + /// Gets the item fields. + /// + /// IEnumerable{ItemFields}. + public IEnumerable GetItemFields() + { + var val = Fields; + + if (string.IsNullOrEmpty(val)) + { + return new ItemFields[] { }; + } + + return val.Split(',').Select(v => (ItemFields)Enum.Parse(typeof(ItemFields), v, true)); + } + } + + /// + /// Class TvShowsService + /// + public class TvShowsService : BaseApiService + { + /// + /// The _user manager + /// + private readonly IUserManager _userManager; + + /// + /// The _user data repository + /// + private readonly IUserDataRepository _userDataRepository; + /// + /// The _library manager + /// + private readonly ILibraryManager _libraryManager; + + /// + /// Initializes a new instance of the class. + /// + /// The user manager. + /// The user data repository. + /// The library manager. + public TvShowsService(IUserManager userManager, IUserDataRepository userDataRepository, ILibraryManager libraryManager) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetNextUpEpisodes request) + { + var result = GetNextUpEpisodes(request).Result; + + return ToOptimizedResult(result); + } + + /// + /// Gets the next up episodes. + /// + /// The request. + /// Task{ItemsResult}. + private async Task GetNextUpEpisodes(GetNextUpEpisodes request) + { + var user = _userManager.GetUserById(request.UserId); + + var tasks = user.RootFolder + .GetRecursiveChildren(user) + .OfType() + .AsParallel() + .Select(i => GetNextUp(i, user)); + + var itemsArray = await Task.WhenAll(tasks).ConfigureAwait(false); + + itemsArray = itemsArray + .Where(i => i.Item1 != null) + .OrderByDescending(i => + { + var seriesUserData = + _userDataRepository.GetUserData(user.Id, i.Item1.Series.GetUserDataKey()).Result; + + if (seriesUserData.IsFavorite) + { + return 2; + } + + if (seriesUserData.Likes.HasValue) + { + return seriesUserData.Likes.Value ? 1 : -1; + } + + return 0; + }) + .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) + .ToArray(); + + var pagedItems = ApplyPaging(request, itemsArray.Select(i => i.Item1)); + + var fields = request.GetItemFields().ToList(); + + var returnItems = await GetItemDtos(pagedItems, user, fields).ConfigureAwait(false); + + return new ItemsResult + { + TotalRecordCount = itemsArray.Length, + Items = returnItems + }; + } + + /// + /// Gets the next up. + /// + /// The series. + /// The user. + /// Task{Episode}. + private async Task> GetNextUp(Series series, User user) + { + var allEpisodes = series.GetRecursiveChildren(user) + .OfType() + .OrderByDescending(i => i.PremiereDate) + .ToList(); + + Episode lastWatched = null; + var lastWatchedDate = DateTime.MinValue; + Episode nextUp = null; + + // Go back starting with the most recent episodes + foreach (var episode in allEpisodes) + { + var userData = await _userDataRepository.GetUserData(user.Id, episode.GetUserDataKey()).ConfigureAwait(false); + + if (userData.Played) + { + if (lastWatched != null || nextUp == null) + { + break; + } + + lastWatched = episode; + lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue; + } + else + { + nextUp = episode; + } + } + + if (lastWatched != null) + { + return new Tuple(nextUp, lastWatchedDate); + } + + return new Tuple(null, lastWatchedDate); + } + + /// + /// Gets the item dtos. + /// + /// The paged items. + /// The user. + /// The fields. + /// Task. + private Task GetItemDtos(IEnumerable pagedItems, User user, List fields) + { + var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository); + + return Task.WhenAll(pagedItems.Select(i => dtoBuilder.GetBaseItemDto(i, user, fields))); + } + + /// + /// Applies the paging. + /// + /// The request. + /// The items. + /// IEnumerable{BaseItem}. + private IEnumerable ApplyPaging(GetNextUpEpisodes request, IEnumerable items) + { + // Start at + if (request.StartIndex.HasValue) + { + items = items.Skip(request.StartIndex.Value); + } + + // Return limit + if (request.Limit.HasValue) + { + items = items.Take(request.Limit.Value); + } + + return items; + } + } +}