< 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
3%
Covered lines: 4
Uncovered lines: 109
Coverable lines: 113
Total lines: 314
Line coverage: 3.5%
Branch coverage
0%
Covered branches: 0
Total branches: 32
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%
GetUniqueSeriesKey(...)100%210%
GetNextUp(...)0%4260%
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.Entities;
 7using Jellyfin.Data.Enums;
 8using Jellyfin.Extensions;
 9using MediaBrowser.Controller.Configuration;
 10using MediaBrowser.Controller.Dto;
 11using MediaBrowser.Controller.Entities;
 12using MediaBrowser.Controller.Library;
 13using MediaBrowser.Controller.TV;
 14using MediaBrowser.Model.Querying;
 15using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 16using Series = MediaBrowser.Controller.Entities.TV.Series;
 17
 18namespace Emby.Server.Implementations.TV
 19{
 20    public class TVSeriesManager : ITVSeriesManager
 21    {
 22        private readonly IUserDataManager _userDataManager;
 23        private readonly ILibraryManager _libraryManager;
 24        private readonly IServerConfigurationManager _configurationManager;
 25
 26        public TVSeriesManager(IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationMan
 27        {
 2228            _userDataManager = userDataManager;
 2229            _libraryManager = libraryManager;
 2230            _configurationManager = configurationManager;
 2231        }
 32
 33        public QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options)
 34        {
 035            var user = query.User;
 36
 037            string? presentationUniqueKey = null;
 038            if (!query.SeriesId.IsNullOrEmpty())
 39            {
 040                if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series)
 41                {
 042                    presentationUniqueKey = GetUniqueSeriesKey(series);
 43                }
 44            }
 45
 046            if (!string.IsNullOrEmpty(presentationUniqueKey))
 47            {
 048                return GetResult(GetNextUpEpisodes(query, user, new[] { presentationUniqueKey }, options), query);
 49            }
 50
 51            BaseItem[] parents;
 52
 053            if (query.ParentId.HasValue)
 54            {
 055                var parent = _libraryManager.GetItemById(query.ParentId.Value);
 56
 057                if (parent is not null)
 58                {
 059                    parents = new[] { parent };
 60                }
 61                else
 62                {
 063                    parents = Array.Empty<BaseItem>();
 64                }
 65            }
 66            else
 67            {
 068                parents = _libraryManager.GetUserRootFolder().GetChildren(user, true)
 069                   .Where(i => i is Folder)
 070                   .Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes).Contains(i.Id))
 071                   .ToArray();
 72            }
 73
 074            return GetNextUp(query, parents, options);
 75        }
 76
 77        public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options)
 78        {
 079            var user = request.User;
 80
 081            string? presentationUniqueKey = null;
 082            int? limit = null;
 083            if (!request.SeriesId.IsNullOrEmpty())
 84            {
 085                if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
 86                {
 087                    presentationUniqueKey = GetUniqueSeriesKey(series);
 088                    limit = 1;
 89                }
 90            }
 91
 092            if (!string.IsNullOrEmpty(presentationUniqueKey))
 93            {
 094                return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, options), request);
 95            }
 96
 097            if (limit.HasValue)
 98            {
 099                limit = limit.Value + 10;
 100            }
 101
 0102            var items = _libraryManager
 0103                .GetItemList(
 0104                    new InternalItemsQuery(user)
 0105                    {
 0106                        IncludeItemTypes = new[] { BaseItemKind.Episode },
 0107                        OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
 0108                        SeriesPresentationUniqueKey = presentationUniqueKey,
 0109                        Limit = limit,
 0110                        DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableI
 0111                        GroupBySeriesPresentationUniqueKey = true
 0112                    },
 0113                    parentsFolders.ToList())
 0114                .Cast<Episode>()
 0115                .Where(episode => !string.IsNullOrEmpty(episode.SeriesPresentationUniqueKey))
 0116                .Select(GetUniqueSeriesKey)
 0117                .ToList();
 118
 119            // Avoid implicitly captured closure
 0120            var episodes = GetNextUpEpisodes(request, user, items, options);
 121
 0122            return GetResult(episodes, request);
 123        }
 124
 125        private IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys,
 126        {
 0127            var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, request.EnableResumable, false));
 128
 0129            if (request.EnableRewatching)
 130            {
 0131                allNextUp = allNextUp
 0132                    .Concat(seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false, true)))
 0133                    .OrderByDescending(i => i.LastWatchedDate);
 134            }
 135
 136            // If viewing all next up for all series, remove first episodes
 137            // But if that returns empty, keep those first episodes (avoid completely empty view)
 0138            var alwaysEnableFirstEpisode = !request.SeriesId.IsNullOrEmpty();
 0139            var anyFound = false;
 140
 0141            return allNextUp
 0142                .Where(i =>
 0143                {
 0144                    if (request.DisableFirstEpisode)
 0145                    {
 0146                        return i.LastWatchedDate != DateTime.MinValue;
 0147                    }
 0148
 0149                    if (alwaysEnableFirstEpisode || (i.LastWatchedDate != DateTime.MinValue && i.LastWatchedDate.Date >=
 0150                    {
 0151                        anyFound = true;
 0152                        return true;
 0153                    }
 0154
 0155                    return !anyFound && i.LastWatchedDate == DateTime.MinValue;
 0156                })
 0157                .Select(i => i.GetEpisodeFunction())
 0158                .Where(i => i is not null)!;
 159        }
 160
 161        private static string GetUniqueSeriesKey(Episode episode)
 162        {
 0163            return episode.SeriesPresentationUniqueKey;
 164        }
 165
 166        private static string GetUniqueSeriesKey(Series series)
 167        {
 0168            return series.GetPresentationUniqueKey();
 169        }
 170
 171        /// <summary>
 172        /// Gets the next up.
 173        /// </summary>
 174        /// <returns>Task{Episode}.</returns>
 175        private (DateTime LastWatchedDate, Func<Episode?> GetEpisodeFunction) GetNextUp(string seriesKey, User user, Dto
 176        {
 0177            var lastQuery = new InternalItemsQuery(user)
 0178            {
 0179                AncestorWithPresentationUniqueKey = null,
 0180                SeriesPresentationUniqueKey = seriesKey,
 0181                IncludeItemTypes = new[] { BaseItemKind.Episode },
 0182                IsPlayed = true,
 0183                Limit = 1,
 0184                ParentIndexNumberNotEquals = 0,
 0185                DtoOptions = new DtoOptions
 0186                {
 0187                    Fields = new[] { ItemFields.SortName },
 0188                    EnableImages = false
 0189                }
 0190            };
 191
 192            // If including played results, sort first by date played and then by season and episode numbers
 0193            lastQuery.OrderBy = includePlayed
 0194                ? new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descen
 0195                : new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Desce
 196
 0197            var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault();
 198
 199            Episode? GetEpisode()
 200            {
 201                var nextQuery = new InternalItemsQuery(user)
 202                {
 203                    AncestorWithPresentationUniqueKey = null,
 204                    SeriesPresentationUniqueKey = seriesKey,
 205                    IncludeItemTypes = new[] { BaseItemKind.Episode },
 206                    OrderBy = new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, Sort
 207                    Limit = 1,
 208                    IsPlayed = includePlayed,
 209                    IsVirtualItem = false,
 210                    ParentIndexNumberNotEquals = 0,
 211                    DtoOptions = dtoOptions
 212                };
 213
 214                // Locate the next up episode based on the last watched episode's season and episode number
 215                var lastWatchedParentIndexNumber = lastWatchedEpisode?.ParentIndexNumber;
 216                var lastWatchedIndexNumber = lastWatchedEpisode?.IndexNumberEnd ?? lastWatchedEpisode?.IndexNumber;
 217                if (lastWatchedParentIndexNumber.HasValue && lastWatchedIndexNumber.HasValue)
 218                {
 219                    nextQuery.MinParentAndIndexNumber = (lastWatchedParentIndexNumber.Value, lastWatchedIndexNumber.Valu
 220                }
 221
 222                var nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault();
 223
 224                if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
 225                {
 226                    var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
 227                    {
 228                        AncestorWithPresentationUniqueKey = null,
 229                        SeriesPresentationUniqueKey = seriesKey,
 230                        ParentIndexNumber = 0,
 231                        IncludeItemTypes = new[] { BaseItemKind.Episode },
 232                        IsPlayed = includePlayed,
 233                        IsVirtualItem = false,
 234                        DtoOptions = dtoOptions
 235                    })
 236                    .Cast<Episode>()
 237                    .Where(episode => episode.AirsBeforeSeasonNumber is not null || episode.AirsAfterSeasonNumber is not
 238                    .ToList();
 239
 240                    if (lastWatchedEpisode is not null)
 241                    {
 242                        // Last watched episode is added, because there could be specials that aired before the last wat
 243                        consideredEpisodes.Add(lastWatchedEpisode);
 244                    }
 245
 246                    if (nextEpisode is not null)
 247                    {
 248                        consideredEpisodes.Add(nextEpisode);
 249                    }
 250
 251                    var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.Ai
 252                        .Cast<Episode>();
 253                    if (lastWatchedEpisode is not null)
 254                    {
 255                        sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => !episode.Id.Equals(last
 256                    }
 257
 258                    nextEpisode = sortedConsideredEpisodes.FirstOrDefault();
 259                }
 260
 261                if (nextEpisode is not null && !includeResumable)
 262                {
 263                    var userData = _userDataManager.GetUserData(user, nextEpisode);
 264
 265                    if (userData.PlaybackPositionTicks > 0)
 266                    {
 267                        return null;
 268                    }
 269                }
 270
 271                return nextEpisode;
 272            }
 273
 0274            if (lastWatchedEpisode is not null)
 275            {
 0276                var userData = _userDataManager.GetUserData(user, lastWatchedEpisode);
 277
 0278                var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1);
 279
 0280                return (lastWatchedDate, GetEpisode);
 281            }
 282
 283            // Return the first episode
 0284            return (DateTime.MinValue, GetEpisode);
 285        }
 286
 287        private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)
 288        {
 0289            int totalCount = 0;
 290
 0291            if (query.EnableTotalRecordCount)
 292            {
 0293                var list = items.ToList();
 0294                totalCount = list.Count;
 0295                items = list;
 296            }
 297
 0298            if (query.StartIndex.HasValue)
 299            {
 0300                items = items.Skip(query.StartIndex.Value);
 301            }
 302
 0303            if (query.Limit.HasValue)
 304            {
 0305                items = items.Take(query.Limit.Value);
 306            }
 307
 0308            return new QueryResult<BaseItem>(
 0309                query.StartIndex,
 0310                totalCount,
 0311                items.ToArray());
 312        }
 313    }
 314}