From 5d86329c182343de3103a5d6cf941553da0f8246 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 18 Nov 2023 14:35:03 -0800 Subject: [PATCH] Last Season and Recent Episodes New: Added option to only monitor recent episodes Fixed: Last Season always monitors the whole season Closes #6175 --- .../SeriesMonitoringOptionsPopoverContent.js | 14 +++- .../src/Utilities/Series/monitorOptions.js | 10 ++- .../SetEpisodeMontitoredFixture.cs | 81 +++++++++++++++++-- src/NzbDrone.Core/Localization/Core/en.json | 7 +- .../Tv/EpisodeMonitoredService.cs | 33 ++++---- src/NzbDrone.Core/Tv/MonitoringOptions.cs | 6 ++ src/NzbDrone.Core/Tv/RefreshSeriesService.cs | 7 +- 7 files changed, 129 insertions(+), 29 deletions(-) diff --git a/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js b/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js index c1fa934d5..fab8ec3bb 100644 --- a/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js +++ b/frontend/src/AddSeries/SeriesMonitoringOptionsPopoverContent.js @@ -26,14 +26,24 @@ function SeriesMonitoringOptionsPopoverContent() { data={translate('MonitorExistingEpisodesDescription')} /> + + + + .CreateListOfSize(2) .All() @@ -226,7 +226,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests var monitoringOptions = new MonitoringOptions { - Monitor = MonitorTypes.LatestSeason + Monitor = MonitorTypes.LastSeason }; Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); @@ -264,13 +264,47 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests } [Test] - public void should_not_monitor_latest_season_if_all_episodes_aired_more_than_90_days_ago() + public void should_monitor_last_season_if_all_episodes_aired_more_than_90_days_ago() + { + _series.Seasons = Builder.CreateListOfSize(2) + .All() + .With(n => n.Monitored = true) + .Build() + .ToList(); + + _episodes = Builder.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeFileId = 0) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-200)) + .TheLast(2) + .With(e => e.SeasonNumber = 2) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-100)) + .Build() + .ToList(); + + var monitoringOptions = new MonitoringOptions + { + Monitor = MonitorTypes.LastSeason + }; + + Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); + + VerifySeasonMonitored(n => n.SeasonNumber == 2); + VerifyMonitored(n => n.SeasonNumber == 2); + + VerifySeasonNotMonitored(n => n.SeasonNumber == 1); + VerifyNotMonitored(n => n.SeasonNumber == 1); + } + + [Test] + public void should_not_monitor_any_recent_episodes_if_all_episodes_aired_more_than_90_days_ago() { _episodes.ForEach(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-100)); var monitoringOptions = new MonitoringOptions { - Monitor = MonitorTypes.LatestSeason + Monitor = MonitorTypes.Recent }; Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); @@ -279,6 +313,43 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests .Verify(v => v.UpdateEpisodes(It.Is>(l => l.All(e => !e.Monitored)))); } + [Test] + public void should_monitor_any_recent_and_future_episodes_if_all_episodes_aired_within_90_days() + { + _series.Seasons = Builder.CreateListOfSize(1) + .All() + .With(n => n.Monitored = true) + .Build() + .ToList(); + + _episodes = Builder.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeFileId = 0) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-200)) + .TheLast(3) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-5)) + .TheLast(1) + .With(e => e.AirDateUtc = DateTime.UtcNow.AddDays(30)) + .Build() + .ToList(); + + Mocker.GetMock() + .Setup(s => s.GetEpisodeBySeries(It.IsAny())) + .Returns(_episodes); + + var monitoringOptions = new MonitoringOptions + { + Monitor = MonitorTypes.Recent + }; + + Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); + + VerifySeasonMonitored(n => n.SeasonNumber == 1); + VerifyNotMonitored(n => n.AirDateUtc.HasValue && n.AirDateUtc.Value.Before(DateTime.UtcNow.AddDays(-90))); + VerifyMonitored(n => n.AirDateUtc.HasValue && n.AirDateUtc.Value.After(DateTime.UtcNow.AddDays(-90))); + } + [Test] public void should_monitor_latest_season_if_some_episodes_have_aired() { @@ -302,7 +373,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests var monitoringOptions = new MonitoringOptions { - Monitor = MonitorTypes.LatestSeason + Monitor = MonitorTypes.LastSeason }; Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 5623983fd..b61b045c3 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -984,8 +984,8 @@ "MonitorFirstSeasonDescription": "Monitor all episodes of the first season. All other seasons will be ignored", "MonitorFutureEpisodes": "Future Episodes", "MonitorFutureEpisodesDescription": "Monitor episodes that have not aired yet", - "MonitorLatestSeason": "Latest Season", - "MonitorLatestSeasonDescription": "Monitor all episodes of the latest season that aired within the last 90 days and all future seasons", + "MonitorLastSeason": "Last Season", + "MonitorLastSeasonDescription": "Monitor all episodes of the last season", "MonitorMissingEpisodes": "Missing Episodes", "MonitorMissingEpisodesDescription": "Monitor episodes that do not have files or have not aired yet", "MonitorNewSeasons": "Monitor New Seasons", @@ -994,6 +994,9 @@ "MonitorNone": "None", "MonitorNoneDescription": "No episodes will be monitored", "MonitorPilotEpisode": "Pilot Episode", + "MonitorPilotEpisodeDescription": "Only monitor the first episode of the first season", + "MonitorRecentEpisodes": "Recent Episodes", + "MonitorRecentEpisodesDescription": "Monitor episodes aired within the last 90 days and future episodes", "MonitorSelected": "Monitor Selected", "MonitorSeries": "Monitor Series", "MonitorSpecials": "Monitor Specials", diff --git a/src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs b/src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs index c0351457a..41cdbcfc2 100644 --- a/src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeMonitoredService.cs @@ -83,23 +83,27 @@ namespace NzbDrone.Core.Tv break; + case MonitorTypes.LastSeason: + #pragma warning disable CS0612 case MonitorTypes.LatestSeason: - if (episodes.Where(e => e.SeasonNumber == lastSeason) - .All(e => e.AirDateUtc.HasValue && - e.AirDateUtc.Value.Before(DateTime.UtcNow) && - !e.AirDateUtc.Value.InLastDays(90))) - { - _logger.Debug("[{0}] Unmonitoring all episodes because latest season aired more than 90 days ago", series.Title); - ToggleEpisodesMonitoredState(episodes, e => false); - break; - } - + #pragma warning restore CS0612 _logger.Debug("[{0}] Monitoring latest season episodes", series.Title); ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && e.SeasonNumber == lastSeason); break; + case MonitorTypes.Recent: + _logger.Debug("[{0}] Monitoring recent and future episodes", series.Title); + + ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && + (!e.AirDateUtc.HasValue || ( + e.AirDateUtc.Value.Before(DateTime.UtcNow) && + e.AirDateUtc.Value.InLastDays(90)) + || e.AirDateUtc.Value.After(DateTime.UtcNow))); + + break; + case MonitorTypes.MonitorSpecials: _logger.Debug("[{0}] Monitoring special episodes", series.Title); ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == 0), true); @@ -128,15 +132,14 @@ namespace NzbDrone.Core.Tv { var seasonNumber = season.SeasonNumber; - // Monitor the season when: + // Monitor the last season when: // - Not specials // - The latest season - // - Not only supposed to monitor the first season + // - Set to monitor all or future episodes if (seasonNumber > 0 && seasonNumber == lastSeason && - monitoringOptions.Monitor != MonitorTypes.FirstSeason && - monitoringOptions.Monitor != MonitorTypes.Pilot && - monitoringOptions.Monitor != MonitorTypes.None) + (monitoringOptions.Monitor == MonitorTypes.All || + monitoringOptions.Monitor == MonitorTypes.Future)) { season.Monitored = true; } diff --git a/src/NzbDrone.Core/Tv/MonitoringOptions.cs b/src/NzbDrone.Core/Tv/MonitoringOptions.cs index 0fb4baeed..b49a5ee6a 100644 --- a/src/NzbDrone.Core/Tv/MonitoringOptions.cs +++ b/src/NzbDrone.Core/Tv/MonitoringOptions.cs @@ -1,3 +1,4 @@ +using System; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Tv @@ -17,8 +18,13 @@ namespace NzbDrone.Core.Tv Missing, Existing, FirstSeason, + LastSeason, + + [Obsolete] LatestSeason, + Pilot, + Recent, MonitorSpecials, UnmonitorSpecials, None diff --git a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs index 3cd0ab6b3..41a3b5cae 100644 --- a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -139,7 +139,6 @@ namespace NzbDrone.Core.Tv { var existingSeason = series.Seasons.FirstOrDefault(s => s.SeasonNumber == season.SeasonNumber); - // Todo: Should this should use the previous season's monitored state? if (existingSeason == null) { if (season.SeasonNumber == 0) @@ -149,8 +148,10 @@ namespace NzbDrone.Core.Tv continue; } - _logger.Debug("New season ({0}) for series: [{1}] {2}, setting monitored to {3}", season.SeasonNumber, series.TvdbId, series.Title, series.Monitored.ToString().ToLowerInvariant()); - season.Monitored = series.Monitored; + var monitorNewSeasons = series.MonitorNewItems == NewItemMonitorTypes.All; + + _logger.Debug("New season ({0}) for series: [{1}] {2}, setting monitored to {3}", season.SeasonNumber, series.TvdbId, series.Title, monitorNewSeasons.ToString().ToLowerInvariant()); + season.Monitored = monitorNewSeasons; } else {