diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 0e0b05156d..c54a3fa4a3 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -30,6 +30,7 @@ namespace MediaBrowser.Providers.TV private readonly IFileSystem _fileSystem; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private const double UnairedEpisodeThresholdDays = 2; public MissingEpisodeProvider(ILogger logger, IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, IFileSystem fileSystem) { @@ -54,10 +55,13 @@ namespace MediaBrowser.Providers.TV .Select(i => { DateTime.TryParse(i.FirstAired, out var firstAired); - return new ValueTuple( - i.AiredSeason.GetValueOrDefault(-1), i.AiredEpisodeNumber.GetValueOrDefault(-1), firstAired); + var seasonNumber = i.AiredSeason.GetValueOrDefault(-1); + var episodeNumber = i.AiredEpisodeNumber.GetValueOrDefault(-1); + return (SeasonNumber: seasonNumber, EpisodeNumber: episodeNumber, firstAired: firstAired); }) - .Where(i => i.Item1 != -1 && i.Item2 != -1) + .Where(i => i.SeasonNumber != -1 && i.EpisodeNumber != -1) + .OrderBy(i => i.SeasonNumber) + .ThenBy(i => i.EpisodeNumber) .ToList(); var allRecursiveChildren = series.GetRecursiveChildren(); @@ -118,37 +122,25 @@ namespace MediaBrowser.Providers.TV }); } - private const double UnairedEpisodeThresholdDays = 2; - - private async Task AddMissingEpisodes( Series series, - IList allItems, + IEnumerable allItems, bool addMissingEpisodes, - List> episodeLookup, + IReadOnlyCollection<(int SeasonNumber, int Episodenumber, DateTime FirstAired)> episodeLookup, CancellationToken cancellationToken) { var existingEpisodes = allItems.OfType() .ToList(); - var seasonCounts = (from e in episodeLookup - group e by e.Item1 into g - select g) - .ToDictionary(g => g.Key, g => g.Count()); + var seasonCounts = episodeLookup.GroupBy(e => e.SeasonNumber).ToDictionary(g => g.Key, g => g.Count()); var hasChanges = false; foreach (var tuple in episodeLookup) { - if (tuple.Item1 <= 0) + if (tuple.SeasonNumber <= 0 || tuple.Episodenumber <= 0) { - // Ignore season zeros - continue; - } - - if (tuple.Item2 <= 0) - { - // Ignore episode zeros + // Ignore episode/season zeros continue; } @@ -159,15 +151,15 @@ namespace MediaBrowser.Providers.TV continue; } - var airDate = tuple.Item3; + var airDate = tuple.FirstAired; var now = DateTime.UtcNow.AddDays(0 - UnairedEpisodeThresholdDays); if (airDate < now && addMissingEpisodes || airDate > now) { // tvdb has a lot of nearly blank episodes - _logger.LogInformation("Creating virtual missing/unaired episode {0} {1}x{2}", series.Name, tuple.Item1, tuple.Item2); - await AddEpisode(series, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Creating virtual missing/unaired episode {0} {1}x{2}", series.Name, tuple.SeasonNumber, tuple.Episodenumber); + await AddEpisode(series, tuple.SeasonNumber, tuple.Episodenumber, cancellationToken).ConfigureAwait(false); hasChanges = true; } @@ -180,58 +172,57 @@ namespace MediaBrowser.Providers.TV /// Removes the virtual entry after a corresponding physical version has been added /// private bool RemoveObsoleteOrMissingEpisodes( - IList allRecursiveChildren, - IEnumerable> episodeLookup, + IEnumerable allRecursiveChildren, + IEnumerable<(int SeasonNumber, int EpisodeNumber, DateTime FirstAired)> episodeLookup, bool allowMissingEpisodes) { - var existingEpisodes = allRecursiveChildren.OfType() - .ToList(); - - var physicalEpisodes = existingEpisodes - .Where(i => i.LocationType != LocationType.Virtual) - .ToList(); + var existingEpisodes = allRecursiveChildren.OfType(); - var virtualEpisodes = existingEpisodes - .Where(i => i.LocationType == LocationType.Virtual) - .ToList(); + var physicalEpisodes = new List(); + var virtualEpisodes = new List(); + foreach (var episode in existingEpisodes) + { + if (episode.LocationType == LocationType.Virtual) + { + virtualEpisodes.Add(episode); + } + else + { + physicalEpisodes.Add(episode); + } + } var episodesToRemove = virtualEpisodes .Where(i => { - if (i.IndexNumber.HasValue && i.ParentIndexNumber.HasValue) + if (!i.IndexNumber.HasValue || !i.ParentIndexNumber.HasValue) { - var seasonNumber = i.ParentIndexNumber.Value; - var episodeNumber = i.IndexNumber.Value; - - // If there's a physical episode with the same season and episode number, delete it - if (physicalEpisodes.Any(p => - p.ParentIndexNumber.HasValue && (p.ParentIndexNumber.Value) == seasonNumber && - p.ContainsEpisodeNumber(episodeNumber))) - { - return true; - } + return true; + } - // If the episode no longer exists in the remote lookup, delete it - if (!episodeLookup.Any(e => e.Item1 == seasonNumber && e.Item2 == episodeNumber)) - { - return true; - } + var seasonNumber = i.ParentIndexNumber.Value; + var episodeNumber = i.IndexNumber.Value; - if (!allowMissingEpisodes && i.IsMissingEpisode) - { - // If it's missing, but not unaired, remove it - if (!i.PremiereDate.HasValue || i.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) < DateTime.Now.Date) - { - return true; - } - } + // If there's a physical episode with the same season and episode number, delete it + if (physicalEpisodes.Any(p => + p.ParentIndexNumber.HasValue && p.ParentIndexNumber.Value == seasonNumber && + p.ContainsEpisodeNumber(episodeNumber))) + { + return true; + } - return false; + // If the episode no longer exists in the remote lookup, delete it + if (!episodeLookup.Any(e => e.SeasonNumber == seasonNumber && e.EpisodeNumber == episodeNumber)) + { + return true; } - return true; - }) - .ToList(); + // If it's missing, but not unaired, remove it + return !allowMissingEpisodes && i.IsMissingEpisode && + (!i.PremiereDate.HasValue || + i.PremiereDate.Value.ToLocalTime().Date.AddDays(UnairedEpisodeThresholdDays) < + DateTime.Now.Date); + }); var hasChanges = false; @@ -240,7 +231,6 @@ namespace MediaBrowser.Providers.TV _libraryManager.DeleteItem(episodeToRemove, new DeleteOptions { DeleteFileLocation = true - }, false); hasChanges = true; @@ -256,17 +246,23 @@ namespace MediaBrowser.Providers.TV /// The episode lookup. /// Task{System.Boolean}. private bool RemoveObsoleteOrMissingSeasons(IList allRecursiveChildren, - IEnumerable<(int, int, DateTime)> episodeLookup) + IEnumerable<(int SeasonNumber, int EpisodeNumber, DateTime FirstAired)> episodeLookup) { var existingSeasons = allRecursiveChildren.OfType().ToList(); - var physicalSeasons = existingSeasons - .Where(i => i.LocationType != LocationType.Virtual) - .ToList(); - - var virtualSeasons = existingSeasons - .Where(i => i.LocationType == LocationType.Virtual) - .ToList(); + var physicalSeasons = new List(); + var virtualSeasons = new List(); + foreach (var season in existingSeasons) + { + if (season.LocationType == LocationType.Virtual) + { + virtualSeasons.Add(season); + } + else + { + physicalSeasons.Add(season); + } + } var allEpisodes = allRecursiveChildren.OfType().ToList(); @@ -278,13 +274,13 @@ namespace MediaBrowser.Providers.TV var seasonNumber = i.IndexNumber.Value; // If there's a physical season with the same number, delete it - if (physicalSeasons.Any(p => p.IndexNumber.HasValue && (p.IndexNumber.Value) == seasonNumber && string.Equals(p.Series.PresentationUniqueKey, i.Series.PresentationUniqueKey, StringComparison.Ordinal))) + if (physicalSeasons.Any(p => p.IndexNumber.HasValue && p.IndexNumber.Value == seasonNumber && string.Equals(p.Series.PresentationUniqueKey, i.Series.PresentationUniqueKey, StringComparison.Ordinal))) { return true; } // If the season no longer exists in the remote lookup, delete it, but only if an existing episode doesn't require it - if (episodeLookup.All(e => e.Item1 != seasonNumber)) + if (episodeLookup.All(e => e.SeasonNumber != seasonNumber)) { if (allEpisodes.All(s => s.ParentIndexNumber != seasonNumber || s.IsInSeasonFolder)) { @@ -298,8 +294,7 @@ namespace MediaBrowser.Providers.TV // Season does not have a number // Remove if there are no episodes directly in series without a season number return allEpisodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder); - }) - .ToList(); + }); var hasChanges = false; @@ -343,14 +338,12 @@ namespace MediaBrowser.Providers.TV Name = name, IndexNumber = episodeNumber, ParentIndexNumber = seasonNumber, - Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)), + Id = _libraryManager.GetNewItemId(series.Id + seasonNumber.ToString(_usCulture) + name, typeof(Episode)), IsVirtualItem = true, SeasonId = season?.Id ?? Guid.Empty, SeriesId = series.Id }; - episode.SetParent(season); - season.AddChild(episode, cancellationToken); await episode.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); @@ -363,21 +356,21 @@ namespace MediaBrowser.Providers.TV /// /// The tuple. /// Episode. - private Episode GetExistingEpisode(IList existingEpisodes, Dictionary seasonCounts, ValueTuple tuple) + private Episode GetExistingEpisode(IList existingEpisodes, Dictionary seasonCounts, (int SeasonNumber, int EpisodeNumber, DateTime FirstAired) episodeTuple) { - var s = tuple.Item1; - var e = tuple.Item2; + var seasonNumber = episodeTuple.SeasonNumber; + var episodeNumber = episodeTuple.EpisodeNumber; while (true) { - var episode = GetExistingEpisode(existingEpisodes, s, e); + var episode = GetExistingEpisode(existingEpisodes, seasonNumber, episodeNumber); if (episode != null) return episode; - s--; + seasonNumber--; - if (seasonCounts.ContainsKey(s)) - e += seasonCounts[s]; + if (seasonCounts.ContainsKey(seasonNumber)) + episodeNumber += seasonCounts[seasonNumber]; else break; }