< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Entities.TV.Series
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Entities/TV/Series.cs
Line coverage
1%
Covered lines: 3
Uncovered lines: 178
Coverable lines: 181
Total lines: 493
Line coverage: 1.6%
Branch coverage
0%
Covered branches: 0
Total branches: 52
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

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/Entities/TV/Series.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Globalization;
 8using System.Linq;
 9using System.Text.Json.Serialization;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using Jellyfin.Data.Entities;
 13using Jellyfin.Data.Enums;
 14using MediaBrowser.Controller.Dto;
 15using MediaBrowser.Controller.Providers;
 16using MediaBrowser.Model.Entities;
 17using MediaBrowser.Model.Providers;
 18using MediaBrowser.Model.Querying;
 19using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
 20
 21namespace MediaBrowser.Controller.Entities.TV
 22{
 23    /// <summary>
 24    /// Class Series.
 25    /// </summary>
 26    public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>, IMetadataContainer
 27    {
 10528        public Series()
 29        {
 10530            AirDays = Array.Empty<DayOfWeek>();
 10531        }
 32
 33        public DayOfWeek[] AirDays { get; set; }
 34
 35        public string AirTime { get; set; }
 36
 37        [JsonIgnore]
 038        public override bool SupportsAddingToPlaylist => true;
 39
 40        [JsonIgnore]
 041        public override bool IsPreSorted => true;
 42
 43        [JsonIgnore]
 044        public override bool SupportsDateLastMediaAdded => true;
 45
 46        [JsonIgnore]
 047        public override bool SupportsInheritedParentImages => false;
 48
 49        [JsonIgnore]
 050        public override bool SupportsPeople => true;
 51
 52        /// <inheritdoc />
 53        [JsonIgnore]
 054        public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
 055            .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
 056            .ToArray();
 57
 58        /// <summary>
 59        /// Gets or sets the display order.
 60        /// </summary>
 61        /// <remarks>
 62        /// Valid options are airdate, dvd or absolute.
 63        /// </remarks>
 64        public string DisplayOrder { get; set; }
 65
 66        /// <summary>
 67        /// Gets or sets the status.
 68        /// </summary>
 69        /// <value>The status.</value>
 70        public SeriesStatus? Status { get; set; }
 71
 72        public override double GetDefaultPrimaryImageAspectRatio()
 73        {
 074            double value = 2;
 075            value /= 3;
 76
 077            return value;
 78        }
 79
 80        public override string CreatePresentationUniqueKey()
 81        {
 082            if (LibraryManager.GetLibraryOptions(this).EnableAutomaticSeriesGrouping)
 83            {
 084                var userdatakeys = GetUserDataKeys();
 85
 086                if (userdatakeys.Count > 1)
 87                {
 088                    return AddLibrariesToPresentationUniqueKey(userdatakeys[0]);
 89                }
 90            }
 91
 092            return base.CreatePresentationUniqueKey();
 93        }
 94
 95        private string AddLibrariesToPresentationUniqueKey(string key)
 96        {
 097            var lang = GetPreferredMetadataLanguage();
 098            if (!string.IsNullOrEmpty(lang))
 99            {
 0100                key += "-" + lang;
 101            }
 102
 0103            var folders = LibraryManager.GetCollectionFolders(this)
 0104                .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
 0105                .ToArray();
 106
 0107            if (folders.Length == 0)
 108            {
 0109                return key;
 110            }
 111
 0112            return key + "-" + string.Join('-', folders);
 113        }
 114
 115        private static string GetUniqueSeriesKey(BaseItem series)
 116        {
 0117            return series.GetPresentationUniqueKey();
 118        }
 119
 120        public override int GetChildCount(User user)
 121        {
 0122            var seriesKey = GetUniqueSeriesKey(this);
 123
 0124            var result = LibraryManager.GetCount(new InternalItemsQuery(user)
 0125            {
 0126                AncestorWithPresentationUniqueKey = null,
 0127                SeriesPresentationUniqueKey = seriesKey,
 0128                IncludeItemTypes = new[] { BaseItemKind.Season },
 0129                IsVirtualItem = false,
 0130                Limit = 0,
 0131                DtoOptions = new DtoOptions(false)
 0132                {
 0133                    EnableImages = false
 0134                }
 0135            });
 136
 0137            return result;
 138        }
 139
 140        public override int GetRecursiveChildCount(User user)
 141        {
 0142            var seriesKey = GetUniqueSeriesKey(this);
 143
 0144            var query = new InternalItemsQuery(user)
 0145            {
 0146                AncestorWithPresentationUniqueKey = null,
 0147                SeriesPresentationUniqueKey = seriesKey,
 0148                DtoOptions = new DtoOptions(false)
 0149                {
 0150                    EnableImages = false
 0151                }
 0152            };
 153
 0154            if (query.IncludeItemTypes.Length == 0)
 155            {
 0156                query.IncludeItemTypes = new[] { BaseItemKind.Episode };
 157            }
 158
 0159            query.IsVirtualItem = false;
 0160            query.Limit = 0;
 0161            var totalRecordCount = LibraryManager.GetCount(query);
 162
 0163            return totalRecordCount;
 164        }
 165
 166        /// <summary>
 167        /// Gets the user data key.
 168        /// </summary>
 169        /// <returns>System.String.</returns>
 170        public override List<string> GetUserDataKeys()
 171        {
 0172            var list = base.GetUserDataKeys();
 173
 0174            if (this.TryGetProviderId(MetadataProvider.Imdb, out var key))
 175            {
 0176                list.Insert(0, key);
 177            }
 178
 0179            if (this.TryGetProviderId(MetadataProvider.Tvdb, out key))
 180            {
 0181                list.Insert(0, key);
 182            }
 183
 0184            if (this.TryGetProviderId(MetadataProvider.Custom, out key))
 185            {
 0186                list.Insert(0, key);
 187            }
 188
 0189            return list;
 190        }
 191
 192        public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
 193        {
 0194            return GetSeasons(user, new DtoOptions(true));
 195        }
 196
 197        public List<BaseItem> GetSeasons(User user, DtoOptions options)
 198        {
 0199            var query = new InternalItemsQuery(user)
 0200            {
 0201                DtoOptions = options
 0202            };
 203
 0204            SetSeasonQueryOptions(query, user);
 205
 0206            return LibraryManager.GetItemList(query);
 207        }
 208
 209        private void SetSeasonQueryOptions(InternalItemsQuery query, User user)
 210        {
 0211            var seriesKey = GetUniqueSeriesKey(this);
 212
 0213            query.AncestorWithPresentationUniqueKey = null;
 0214            query.SeriesPresentationUniqueKey = seriesKey;
 0215            query.IncludeItemTypes = new[] { BaseItemKind.Season };
 0216            query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
 217
 0218            if (user is not null && !user.DisplayMissingEpisodes)
 219            {
 0220                query.IsMissing = false;
 221            }
 0222        }
 223
 224        protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
 225        {
 0226            var user = query.User;
 227
 0228            if (query.Recursive)
 229            {
 0230                var seriesKey = GetUniqueSeriesKey(this);
 231
 0232                query.AncestorWithPresentationUniqueKey = null;
 0233                query.SeriesPresentationUniqueKey = seriesKey;
 0234                if (query.OrderBy.Count == 0)
 235                {
 0236                    query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
 237                }
 238
 0239                if (query.IncludeItemTypes.Length == 0)
 240                {
 0241                    query.IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season };
 242                }
 243
 0244                query.IsVirtualItem = false;
 0245                return LibraryManager.GetItemsResult(query);
 246            }
 247
 0248            SetSeasonQueryOptions(query, user);
 249
 0250            return LibraryManager.GetItemsResult(query);
 251        }
 252
 253        public IEnumerable<BaseItem> GetEpisodes(User user, DtoOptions options, bool shouldIncludeMissingEpisodes)
 254        {
 0255            var seriesKey = GetUniqueSeriesKey(this);
 256
 0257            var query = new InternalItemsQuery(user)
 0258            {
 0259                AncestorWithPresentationUniqueKey = null,
 0260                SeriesPresentationUniqueKey = seriesKey,
 0261                IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season },
 0262                OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
 0263                DtoOptions = options,
 0264            };
 265
 0266            if (!shouldIncludeMissingEpisodes)
 267            {
 0268                query.IsMissing = false;
 269            }
 270
 0271            var allItems = LibraryManager.GetItemList(query);
 272
 0273            var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
 274
 0275            var allEpisodes = allItems.OfType<Season>()
 0276                .SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes, options, shouldIncludeMissingEpisodes))
 0277                .Reverse();
 278
 279            // Specials could appear twice based on above - once in season 0, once in the aired season
 280            // This depends on settings for that series
 281            // When this happens, remove the duplicate from season 0
 282
 0283            return allEpisodes.DistinctBy(i => i.Id).Reverse();
 284        }
 285
 286        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, Cancella
 287        {
 288            // Refresh bottom up, seasons and episodes first, then the series
 289            var items = GetRecursiveChildren();
 290
 291            var totalItems = items.Count;
 292            var numComplete = 0;
 293
 294            // Refresh seasons
 295            foreach (var item in items)
 296            {
 297                if (item is not Season)
 298                {
 299                    continue;
 300                }
 301
 302                cancellationToken.ThrowIfCancellationRequested();
 303
 304                if (refreshOptions.RefreshItem(item))
 305                {
 306                    await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 307                }
 308
 309                numComplete++;
 310                double percent = numComplete;
 311                percent /= totalItems;
 312                progress.Report(percent * 100);
 313            }
 314
 315            // Refresh episodes and other children
 316            foreach (var item in items)
 317            {
 318                if (item is Season)
 319                {
 320                    continue;
 321                }
 322
 323                cancellationToken.ThrowIfCancellationRequested();
 324
 325                bool skipItem = item is Episode episode
 326                    && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh
 327                    && !refreshOptions.ReplaceAllMetadata
 328                    && episode.IsMissingEpisode
 329                    && episode.LocationType == LocationType.Virtual
 330                    && episode.PremiereDate.HasValue
 331                    && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30;
 332
 333                if (!skipItem)
 334                {
 335                    if (refreshOptions.RefreshItem(item))
 336                    {
 337                        await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 338                    }
 339                }
 340
 341                numComplete++;
 342                double percent = numComplete;
 343                percent /= totalItems;
 344                progress.Report(percent * 100);
 345            }
 346
 347            refreshOptions = new MetadataRefreshOptions(refreshOptions);
 348            await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
 349        }
 350
 351        public List<BaseItem> GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options, bool shouldIncludeMi
 352        {
 0353            var queryFromSeries = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons;
 354
 355            // add optimization when this setting is not enabled
 0356            var seriesKey = queryFromSeries ?
 0357                GetUniqueSeriesKey(this) :
 0358                GetUniqueSeriesKey(parentSeason);
 359
 0360            var query = new InternalItemsQuery(user)
 0361            {
 0362                AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
 0363                SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
 0364                IncludeItemTypes = new[] { BaseItemKind.Episode },
 0365                OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
 0366                DtoOptions = options
 0367            };
 368
 0369            if (!shouldIncludeMissingEpisodes)
 370            {
 0371                query.IsMissing = false;
 372            }
 373
 0374            var allItems = LibraryManager.GetItemList(query);
 375
 0376            return GetSeasonEpisodes(parentSeason, user, allItems, options, shouldIncludeMissingEpisodes);
 377        }
 378
 379        public List<BaseItem> GetSeasonEpisodes(Season parentSeason, User user, IEnumerable<BaseItem> allSeriesEpisodes,
 380        {
 0381            if (allSeriesEpisodes is null)
 382            {
 0383                return GetSeasonEpisodes(parentSeason, user, options, shouldIncludeMissingEpisodes);
 384            }
 385
 0386            var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.Di
 387
 0388            var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
 389
 0390            return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending).ToList();
 391        }
 392
 393        /// <summary>
 394        /// Filters the episodes by season.
 395        /// </summary>
 396        /// <param name="episodes">The episodes.</param>
 397        /// <param name="parentSeason">The season.</param>
 398        /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
 399        /// <returns>The set of episodes.</returns>
 400        public static IEnumerable<BaseItem> FilterEpisodesBySeason(IEnumerable<BaseItem> episodes, Season parentSeason, 
 401        {
 0402            var seasonNumber = parentSeason.IndexNumber;
 0403            var seasonPresentationKey = GetUniqueSeriesKey(parentSeason);
 404
 0405            var supportSpecialsInSeason = includeSpecials && seasonNumber.HasValue && seasonNumber.Value != 0;
 406
 0407            return episodes.Where(episode =>
 0408            {
 0409                var episodeItem = (Episode)episode;
 0410
 0411                var currentSeasonNumber = supportSpecialsInSeason ? episodeItem.AiredSeasonNumber : episode.ParentIndexN
 0412                if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.V
 0413                {
 0414                    return true;
 0415                }
 0416
 0417                if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType
 0418                {
 0419                    return true;
 0420                }
 0421
 0422                var season = episodeItem.Season;
 0423                return season is not null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComp
 0424            });
 425        }
 426
 427        /// <summary>
 428        /// Filters the episodes by season.
 429        /// </summary>
 430        /// <param name="episodes">The episodes.</param>
 431        /// <param name="seasonNumber">The season.</param>
 432        /// <param name="includeSpecials"><c>true</c> to include special, <c>false</c> to not.</param>
 433        /// <returns>The set of episodes.</returns>
 434        public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool 
 435        {
 0436            if (!includeSpecials || seasonNumber < 1)
 437            {
 0438                return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber);
 439            }
 440
 0441            return episodes.Where(i =>
 0442            {
 0443                var episode = i;
 0444
 0445                if (episode is not null)
 0446                {
 0447                    var currentSeasonNumber = episode.AiredSeasonNumber;
 0448
 0449                    return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
 0450                }
 0451
 0452                return false;
 0453            });
 454        }
 455
 456        protected override bool GetBlockUnratedValue(User user)
 457        {
 0458            return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series);
 459        }
 460
 461        public override UnratedItem GetBlockUnratedType()
 462        {
 0463            return UnratedItem.Series;
 464        }
 465
 466        public SeriesInfo GetLookupInfo()
 467        {
 0468            var info = GetItemLookupInfo<SeriesInfo>();
 469
 0470            return info;
 471        }
 472
 473        public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
 474        {
 0475            var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
 476
 0477            if (!ProductionYear.HasValue)
 478            {
 0479                var info = LibraryManager.ParseName(Name);
 480
 0481                var yearInName = info.Year;
 482
 0483                if (yearInName.HasValue)
 484                {
 0485                    ProductionYear = yearInName;
 0486                    hasChanges = true;
 487                }
 488            }
 489
 0490            return hasChanges;
 491        }
 492    }
 493}

Methods/Properties

.ctor()
get_SupportsAddingToPlaylist()
get_IsPreSorted()
get_SupportsDateLastMediaAdded()
get_SupportsInheritedParentImages()
get_SupportsPeople()
get_LocalTrailers()
GetDefaultPrimaryImageAspectRatio()
CreatePresentationUniqueKey()
AddLibrariesToPresentationUniqueKey(System.String)
GetUniqueSeriesKey(MediaBrowser.Controller.Entities.BaseItem)
GetChildCount(Jellyfin.Data.Entities.User)
GetRecursiveChildCount(Jellyfin.Data.Entities.User)
GetUserDataKeys()
GetChildren(Jellyfin.Data.Entities.User,System.Boolean,MediaBrowser.Controller.Entities.InternalItemsQuery)
GetSeasons(Jellyfin.Data.Entities.User,MediaBrowser.Controller.Dto.DtoOptions)
SetSeasonQueryOptions(MediaBrowser.Controller.Entities.InternalItemsQuery,Jellyfin.Data.Entities.User)
GetItemsInternal(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetEpisodes(Jellyfin.Data.Entities.User,MediaBrowser.Controller.Dto.DtoOptions,System.Boolean)
GetSeasonEpisodes(MediaBrowser.Controller.Entities.TV.Season,Jellyfin.Data.Entities.User,MediaBrowser.Controller.Dto.DtoOptions,System.Boolean)
GetSeasonEpisodes(MediaBrowser.Controller.Entities.TV.Season,Jellyfin.Data.Entities.User,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>,MediaBrowser.Controller.Dto.DtoOptions,System.Boolean)
FilterEpisodesBySeason(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.BaseItem>,MediaBrowser.Controller.Entities.TV.Season,System.Boolean)
FilterEpisodesBySeason(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Entities.TV.Episode>,System.Int32,System.Boolean)
GetBlockUnratedValue(Jellyfin.Data.Entities.User)
GetBlockUnratedType()
GetLookupInfo()
BeforeMetadataRefresh(System.Boolean)