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)); } } [Route("/Shows/{Id}/Similar", "GET")] [Api(Description = "Finds tv shows similar to a given one.")] public class GetSimilarShows : BaseGetSimilarItems { } /// /// 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; private readonly IItemRepository _itemRepo; /// /// 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, IItemRepository itemRepo) { _userManager = userManager; _userDataRepository = userDataRepository; _libraryManager = libraryManager; _itemRepo = itemRepo; } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetSimilarShows request) { var result = SimilarItemsHelper.GetSimilarItems(_userManager, _itemRepo, _libraryManager, _userDataRepository, Logger, request, item => item is Series, SimilarItemsHelper.GetSimiliarityScore); return ToOptimizedResult(result); } /// /// 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 itemsArray = user.RootFolder .GetRecursiveChildren(user) .OfType() .AsParallel() .Select(i => GetNextUp(i, user)) .ToArray(); itemsArray = itemsArray .Where(i => i.Item1 != null) .OrderByDescending(i => { var seriesUserData = _userDataRepository.GetUserData(user.Id, i.Item1.Series.GetUserDataKey()); 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 Tuple GetNextUp(Series series, User user) { var allEpisodes = series.GetRecursiveChildren(user) .OfType() .OrderByDescending(i => i.PremiereDate ?? DateTime.MinValue) .ThenByDescending(i => i.IndexNumber ?? 0) .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 = _userDataRepository.GetUserData(user.Id, episode.GetUserDataKey()); 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, _itemRepo); return Task.WhenAll(pagedItems.Select(i => dtoBuilder.GetBaseItemDto(i, fields, user))); } /// /// 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; } } }