< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.TV.TVSeriesManager
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/TV/TVSeriesManager.cs
Line coverage
4%
Covered lines: 4
Uncovered lines: 78
Coverable lines: 82
Total lines: 280
Line coverage: 4.8%
Branch coverage
0%
Covered branches: 0
Total branches: 34
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetNextUp(...)0%110100%
GetNextUp(...)0%7280%
GetNextUpEpisodes(...)0%620%
GetUniqueSeriesKey(...)100%210%
GetNextUp(...)0%7280%
GetResult(...)0%4260%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/TV/TVSeriesManager.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Linq;
 6using Jellyfin.Data;
 7using Jellyfin.Data.Enums;
 8using Jellyfin.Database.Implementations.Entities;
 9using Jellyfin.Database.Implementations.Enums;
 10using Jellyfin.Extensions;
 11using MediaBrowser.Controller.Configuration;
 12using MediaBrowser.Controller.Dto;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Library;
 15using MediaBrowser.Controller.TV;
 16using MediaBrowser.Model.Querying;
 17using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 18using Series = MediaBrowser.Controller.Entities.TV.Series;
 19
 20namespace Emby.Server.Implementations.TV
 21{
 22    public class TVSeriesManager : ITVSeriesManager
 23    {
 24        private readonly IUserDataManager _userDataManager;
 25        private readonly ILibraryManager _libraryManager;
 26        private readonly IServerConfigurationManager _configurationManager;
 27
 28        public TVSeriesManager(IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationMan
 29        {
 2130            _userDataManager = userDataManager;
 2131            _libraryManager = libraryManager;
 2132            _configurationManager = configurationManager;
 2133        }
 34
 35        public QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options)
 36        {
 037            var user = query.User;
 38
 039            string? presentationUniqueKey = null;
 040            if (!query.SeriesId.IsNullOrEmpty())
 41            {
 042                if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series)
 43                {
 044                    presentationUniqueKey = GetUniqueSeriesKey(series);
 45                }
 46            }
 47
 048            if (!string.IsNullOrEmpty(presentationUniqueKey))
 49            {
 050                return GetResult(GetNextUpEpisodes(query, user, new[] { presentationUniqueKey }, options), query);
 51            }
 52
 53            BaseItem[] parents;
 54
 055            if (query.ParentId.HasValue)
 56            {
 057                var parent = _libraryManager.GetItemById(query.ParentId.Value);
 58
 059                if (parent is not null)
 60                {
 061                    parents = new[] { parent };
 62                }
 63                else
 64                {
 065                    parents = Array.Empty<BaseItem>();
 66                }
 67            }
 68            else
 69            {
 070                parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
 071                   .Where(i => i is Folder)
 072                   .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes).Contains(i.Id))
 073                   .ToArray();
 74            }
 75
 076            return GetNextUp(query, parents, options);
 77        }
 78
 79        public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options)
 80        {
 081            var user = request.User;
 82
 083            string? presentationUniqueKey = null;
 084            int? limit = null;
 085            if (!request.SeriesId.IsNullOrEmpty())
 86            {
 087                if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
 88                {
 089                    presentationUniqueKey = GetUniqueSeriesKey(series);
 090                    limit = 1;
 91                }
 92            }
 93
 094            if (!string.IsNullOrEmpty(presentationUniqueKey))
 95            {
 096                return GetResult(GetNextUpEpisodes(request, user, [presentationUniqueKey], options), request);
 97            }
 98
 099            if (limit.HasValue)
 100            {
 0101                limit = limit.Value + 10;
 102            }
 103
 0104            var nextUpSeriesKeys = _libraryManager.GetNextUpSeriesKeys(new InternalItemsQuery(user) { Limit = limit }, p
 105
 0106            var episodes = GetNextUpEpisodes(request, user, nextUpSeriesKeys, options);
 107
 0108            return GetResult(episodes, request);
 109        }
 110
 111        private IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys,
 112        {
 0113            var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, request.EnableResumable, false));
 114
 0115            if (request.EnableRewatching)
 116            {
 0117                allNextUp = allNextUp
 0118                    .Concat(seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false, true)))
 0119                    .OrderByDescending(i => i.LastWatchedDate);
 120            }
 121
 0122            return allNextUp
 0123                .Select(i => i.GetEpisodeFunction())
 0124                .Where(i => i is not null)!;
 125        }
 126
 127        private static string GetUniqueSeriesKey(Series series)
 128        {
 0129            return series.GetPresentationUniqueKey();
 130        }
 131
 132        /// <summary>
 133        /// Gets the next up.
 134        /// </summary>
 135        /// <returns>Task{Episode}.</returns>
 136        private (DateTime LastWatchedDate, Func<Episode?> GetEpisodeFunction) GetNextUp(string seriesKey, User user, Dto
 137        {
 0138            var lastQuery = new InternalItemsQuery(user)
 0139            {
 0140                AncestorWithPresentationUniqueKey = null,
 0141                SeriesPresentationUniqueKey = seriesKey,
 0142                IncludeItemTypes = [BaseItemKind.Episode],
 0143                IsPlayed = true,
 0144                Limit = 1,
 0145                ParentIndexNumberNotEquals = 0,
 0146                DtoOptions = new DtoOptions
 0147                {
 0148                    Fields = [ItemFields.SortName],
 0149                    EnableImages = false
 0150                }
 0151            };
 152
 153            // If including played results, sort first by date played and then by season and episode numbers
 0154            lastQuery.OrderBy = includePlayed
 0155                ? new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descen
 0156                : new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Desce
 157
 0158            var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault();
 159
 160            Episode? GetEpisode()
 161            {
 162                var nextQuery = new InternalItemsQuery(user)
 163                {
 164                    AncestorWithPresentationUniqueKey = null,
 165                    SeriesPresentationUniqueKey = seriesKey,
 166                    IncludeItemTypes = [BaseItemKind.Episode],
 167                    OrderBy = [(ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.A
 168                    Limit = 1,
 169                    IsPlayed = includePlayed,
 170                    IsVirtualItem = false,
 171                    ParentIndexNumberNotEquals = 0,
 172                    DtoOptions = dtoOptions
 173                };
 174
 175                // Locate the next up episode based on the last watched episode's season and episode number
 176                var lastWatchedParentIndexNumber = lastWatchedEpisode?.ParentIndexNumber;
 177                var lastWatchedIndexNumber = lastWatchedEpisode?.IndexNumberEnd ?? lastWatchedEpisode?.IndexNumber;
 178                if (lastWatchedParentIndexNumber.HasValue && lastWatchedIndexNumber.HasValue)
 179                {
 180                    nextQuery.MinParentAndIndexNumber = (lastWatchedParentIndexNumber.Value, lastWatchedIndexNumber.Valu
 181                }
 182
 183                var nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault();
 184
 185                if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
 186                {
 187                    var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
 188                    {
 189                        AncestorWithPresentationUniqueKey = null,
 190                        SeriesPresentationUniqueKey = seriesKey,
 191                        ParentIndexNumber = 0,
 192                        IncludeItemTypes = [BaseItemKind.Episode],
 193                        IsPlayed = includePlayed,
 194                        IsVirtualItem = false,
 195                        DtoOptions = dtoOptions
 196                    })
 197                    .Cast<Episode>()
 198                    .Where(episode => episode.AirsBeforeSeasonNumber is not null || episode.AirsAfterSeasonNumber is not
 199                    .ToList();
 200
 201                    if (lastWatchedEpisode is not null)
 202                    {
 203                        // Last watched episode is added, because there could be specials that aired before the last wat
 204                        consideredEpisodes.Add(lastWatchedEpisode);
 205                    }
 206
 207                    if (nextEpisode is not null)
 208                    {
 209                        consideredEpisodes.Add(nextEpisode);
 210                    }
 211
 212                    var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, [(ItemSortBy.AiredEpis
 213                        .Cast<Episode>();
 214                    if (lastWatchedEpisode is not null)
 215                    {
 216                        sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => !episode.Id.Equals(last
 217                    }
 218
 219                    nextEpisode = sortedConsideredEpisodes.FirstOrDefault();
 220                }
 221
 222                if (nextEpisode is not null && !includeResumable)
 223                {
 224                    var userData = _userDataManager.GetUserData(user, nextEpisode);
 225
 226                    if (userData?.PlaybackPositionTicks > 0)
 227                    {
 228                        return null;
 229                    }
 230                }
 231
 232                return nextEpisode;
 233            }
 234
 0235            if (lastWatchedEpisode is not null)
 236            {
 0237                var userData = _userDataManager.GetUserData(user, lastWatchedEpisode);
 238
 0239                if (userData is null)
 240                {
 0241                    return (DateTime.MinValue, GetEpisode);
 242                }
 243
 0244                var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1);
 245
 0246                return (lastWatchedDate, GetEpisode);
 247            }
 248
 249            // Return the first episode
 0250            return (DateTime.MinValue, GetEpisode);
 251        }
 252
 253        private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)
 254        {
 0255            int totalCount = 0;
 256
 0257            if (query.EnableTotalRecordCount)
 258            {
 0259                var list = items.ToList();
 0260                totalCount = list.Count;
 0261                items = list;
 262            }
 263
 0264            if (query.StartIndex.HasValue)
 265            {
 0266                items = items.Skip(query.StartIndex.Value);
 267            }
 268
 0269            if (query.Limit.HasValue)
 270            {
 0271                items = items.Take(query.Limit.Value);
 272            }
 273
 0274            return new QueryResult<BaseItem>(
 0275                query.StartIndex,
 0276                totalCount,
 0277                items.ToArray());
 278        }
 279    }
 280}