From cd02373e554df232d88063f41b8aee391f3e7667 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 2 May 2016 01:32:04 -0400 Subject: [PATCH] support pooling series and seasons --- .../Entities/InternalItemsQuery.cs | 1 + .../Entities/TV/Episode.cs | 33 ++++---- MediaBrowser.Controller/Entities/TV/Season.cs | 26 +++++- MediaBrowser.Controller/Entities/TV/Series.cs | 63 ++++++++++---- .../Persistence/SqliteItemRepository.cs | 16 +++- .../Sorting/AiredEpisodeOrderComparer.cs | 10 +-- .../TV/TVSeriesManager.cs | 82 +++++++++++-------- 7 files changed, 153 insertions(+), 78 deletions(-) diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index f3b4f40534..b148251ff1 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -46,6 +46,7 @@ namespace MediaBrowser.Controller.Entities public string NameLessThan { get; set; } public string NameContains { get; set; } + public string PresentationUniqueKey { get; set; } public string Path { get; set; } public string Person { get; set; } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 3ad6170a53..605b838cd7 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -58,25 +58,7 @@ namespace MediaBrowser.Controller.Entities.TV { get { - return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? PhysicalSeasonNumber; - } - } - - [IgnoreDataMember] - public int? PhysicalSeasonNumber - { - get - { - var value = ParentIndexNumber; - - if (value.HasValue) - { - return value; - } - - var season = Season; - - return season != null ? season.IndexNumber : null; + return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? ParentIndexNumber; } } @@ -316,6 +298,19 @@ namespace MediaBrowser.Controller.Entities.TV Logger.ErrorException("Error in FillMissingEpisodeNumbersFromPath. Episode: {0}", ex, Path ?? Name ?? Id.ToString()); } + if (!ParentIndexNumber.HasValue) + { + var season = Season; + if (season != null) + { + if (season.ParentIndexNumber.HasValue) + { + ParentIndexNumber = season.ParentIndexNumber; + hasChanges = true; + } + } + } + return hasChanges; } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index ebd22c6c4e..1f443071c7 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -92,6 +92,24 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override string PresentationUniqueKey + { + get + { + if (IndexNumber.HasValue) + { + var series = Series; + if (series != null) + { + return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000"); + } + } + + return base.PresentationUniqueKey; + } + } + /// /// Creates the name of the sort. /// @@ -169,16 +187,16 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetEpisodes(User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) { - var episodes = GetRecursiveChildren(user) - .OfType(); - var series = Series; if (IndexNumber.HasValue && series != null) { - return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes); + return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes); } + var episodes = GetRecursiveChildren(user) + .OfType(); + if (series != null && series.ContainsEpisodesWithoutSeasonFolders) { var seasonNumber = IndexNumber; diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index c062b5fbad..abca6a6431 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public override string PresentationUniqueKey { - get { return GetUserDataKey(); } + get { return GetUserDataKeys().First(); } } /// @@ -191,8 +191,28 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired) { - var seasons = base.GetChildren(user, true) - .OfType(); + var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user) + { + PresentationUniqueKey = PresentationUniqueKey, + IncludeItemTypes = new[] { typeof(Series).Name } + }); + + IEnumerable seasons; + + if (seriesIds.Count > 1) + { + seasons = LibraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(), + IncludeItemTypes = new[] { typeof(Season).Name }, + SortBy = new[] { ItemSortBy.SortName } + + }).OfType(); + } + else + { + seasons = base.GetChildren(user, true).OfType(); + } if (!includeMissingSeasons && !includeVirtualUnaired) { @@ -210,9 +230,7 @@ namespace MediaBrowser.Controller.Entities.TV } } - return LibraryManager - .Sort(seasons, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending) - .Cast(); + return seasons; } public IEnumerable GetEpisodes(User user) @@ -321,18 +339,31 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes) { - return GetEpisodes(user, seasonNumber, includeMissingEpisodes, includeVirtualUnairedEpisodes, - new List()); - } + var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user) + { + PresentationUniqueKey = PresentationUniqueKey, + IncludeItemTypes = new[] { typeof(Series).Name } + }); - internal IEnumerable GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable additionalEpisodes) - { - var episodes = GetRecursiveChildren(user, i => i is Episode) - .Cast(); + IEnumerable episodes; - episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons); + if (seriesIds.Count > 1) + { + episodes = LibraryManager.GetItemList(new InternalItemsQuery(user) + { + AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(), + IncludeItemTypes = new[] { typeof(Episode).Name }, + SortBy = new[] { ItemSortBy.SortName } - episodes = episodes.Concat(additionalEpisodes).Distinct(); + }).OfType(); + } + else + { + episodes = GetRecursiveChildren(user, i => i is Episode) + .Cast(); + } + + episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons); if (!includeMissingEpisodes) { @@ -360,7 +391,7 @@ namespace MediaBrowser.Controller.Entities.TV { if (!includeSpecials || seasonNumber < 1) { - return episodes.Where(i => (i.PhysicalSeasonNumber ?? -1) == seasonNumber); + return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber); } return episodes.Where(i => diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 87b3bffe45..6959de8e6b 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -82,7 +82,7 @@ namespace MediaBrowser.Server.Implementations.Persistence private IDbCommand _updateInheritedRatingCommand; private IDbCommand _updateInheritedTagsCommand; - public const int LatestSchemaVersion = 66; + public const int LatestSchemaVersion = 68; /// /// Initializes a new instance of the class. @@ -1936,6 +1936,12 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.Parameters.Add(cmd, "@Path", DbType.String).Value = query.Path; } + if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) + { + whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey"); + cmd.Parameters.Add(cmd, "@PresentationUniqueKey", DbType.String).Value = query.PresentationUniqueKey; + } + if (query.MinCommunityRating.HasValue) { whereClauses.Add("CommunityRating>=@MinCommunityRating"); @@ -2323,6 +2329,11 @@ namespace MediaBrowser.Server.Implementations.Persistence private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query) { + if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) + { + return false; + } + if (query.IncludeItemTypes.Length == 0) { return true; @@ -2333,7 +2344,8 @@ namespace MediaBrowser.Server.Implementations.Persistence typeof(Video).Name , typeof(Movie).Name , typeof(MusicVideo).Name , - typeof(Series).Name }; + typeof(Series).Name , + typeof(Season).Name }; if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase))) { diff --git a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 70cf805cf1..91abbe34c9 100644 --- a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -49,8 +49,8 @@ namespace MediaBrowser.Server.Implementations.Sorting private int Compare(Episode x, Episode y) { - var isXSpecial = (x.PhysicalSeasonNumber ?? -1) == 0; - var isYSpecial = (y.PhysicalSeasonNumber ?? -1) == 0; + var isXSpecial = (x.ParentIndexNumber ?? -1) == 0; + var isYSpecial = (y.ParentIndexNumber ?? -1) == 0; if (isXSpecial && isYSpecial) { @@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.Sorting { // http://thetvdb.com/wiki/index.php?title=Special_Episodes - var xSeason = x.PhysicalSeasonNumber ?? -1; + var xSeason = x.ParentIndexNumber ?? -1; var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1; if (xSeason != ySeason) @@ -142,8 +142,8 @@ namespace MediaBrowser.Server.Implementations.Sorting private int CompareEpisodes(Episode x, Episode y) { - var xValue = (x.PhysicalSeasonNumber ?? -1) * 1000 + (x.IndexNumber ?? -1); - var yValue = (y.PhysicalSeasonNumber ?? -1) * 1000 + (y.IndexNumber ?? -1); + var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1); + var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1); return xValue.CompareTo(yValue); } diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs index dc880d84b5..ec91dc1b71 100644 --- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs +++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs @@ -36,10 +36,25 @@ namespace MediaBrowser.Server.Implementations.TV ? 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 + SortOrder = SortOrder.Ascending, + PresentationUniqueKey = presentationUniqueKey, + Limit = limit }, parentIds).Cast(); @@ -58,10 +73,25 @@ namespace MediaBrowser.Server.Implementations.TV 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 + SortOrder = SortOrder.Ascending, + PresentationUniqueKey = presentationUniqueKey, + Limit = limit }, parentsFolders.Select(i => i.Id.ToString("N"))).Cast(); @@ -76,30 +106,30 @@ namespace MediaBrowser.Server.Implementations.TV // Avoid implicitly captured closure var currentUser = user; - return FilterSeries(request, series) + 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 => - { - var episode = i.Item1; + //.OrderByDescending(i => + //{ + // var episode = i.Item1; - var seriesUserData = _userDataManager.GetUserData(user, episode.Series); + // var seriesUserData = _userDataManager.GetUserData(user, episode.Series); - if (seriesUserData.IsFavorite) - { - return 2; - } + // if (seriesUserData.IsFavorite) + // { + // return 2; + // } - if (seriesUserData.Likes.HasValue) - { - return seriesUserData.Likes.Value ? 1 : -1; - } + // if (seriesUserData.Likes.HasValue) + // { + // return seriesUserData.Likes.Value ? 1 : -1; + // } - return 0; - }) - .ThenByDescending(i => i.Item2) + // return 0; + //}) + .OrderByDescending(i => i.Item2) .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) .Select(i => i.Item1); } @@ -142,7 +172,7 @@ namespace MediaBrowser.Server.Implementations.TV } else { - if (!episode.IsVirtualUnaired && (!episode.IsMissingEpisode || includeMissing)) + if (!episode.IsVirtualUnaired && (includeMissing || !episode.IsMissingEpisode)) { nextUp = episode; } @@ -154,24 +184,12 @@ namespace MediaBrowser.Server.Implementations.TV return new Tuple(nextUp, lastWatchedDate, false); } - var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (!i.IsMissingEpisode || includeMissing) && !i.IsPlayed(user)); + var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (includeMissing || !i.IsMissingEpisode) && !i.IsPlayed(user)); // Return the first episode return new Tuple(firstEpisode, DateTime.MinValue, true); } - private IEnumerable FilterSeries(NextUpQuery request, IEnumerable items) - { - if (!string.IsNullOrWhiteSpace(request.SeriesId)) - { - var id = new Guid(request.SeriesId); - - items = items.Where(i => i.Id == id); - } - - return items; - } - private QueryResult GetResult(IEnumerable items, int? totalRecordLimit, NextUpQuery query) { var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();