Last Season and Recent Episodes

New: Added option to only monitor recent episodes
Fixed: Last Season always monitors the whole season
Closes #6175
This commit is contained in:
Mark McDowall 2023-11-18 14:35:03 -08:00 committed by Mark McDowall
parent ade40b72bc
commit 5d86329c18
7 changed files with 129 additions and 29 deletions

View File

@ -26,14 +26,24 @@ function SeriesMonitoringOptionsPopoverContent() {
data={translate('MonitorExistingEpisodesDescription')} data={translate('MonitorExistingEpisodesDescription')}
/> />
<DescriptionListItem
title={translate('MonitorRecentEpisodes')}
data={translate('MonitorRecentEpisodesDescription')}
/>
<DescriptionListItem
title={translate('MonitorPilotEpisode')}
data={translate('MonitorPilotEpisodeDescription')}
/>
<DescriptionListItem <DescriptionListItem
title={translate('MonitorFirstSeason')} title={translate('MonitorFirstSeason')}
data={translate('MonitorFirstSeasonDescription')} data={translate('MonitorFirstSeasonDescription')}
/> />
<DescriptionListItem <DescriptionListItem
title={translate('MonitorLatestSeason')} title={translate('MonitorLastSeason')}
data={translate('MonitorLatestSeasonDescription')} data={translate('MonitorLastSeasonDescription')}
/> />
<DescriptionListItem <DescriptionListItem

View File

@ -25,6 +25,12 @@ const monitorOptions = [
return translate('MonitorExistingEpisodes'); return translate('MonitorExistingEpisodes');
} }
}, },
{
key: 'recent',
get value() {
return translate('MonitorRecentEpisodes');
}
},
{ {
key: 'pilot', key: 'pilot',
get value() { get value() {
@ -38,9 +44,9 @@ const monitorOptions = [
} }
}, },
{ {
key: 'latestSeason', key: 'lastSeason',
get value() { get value() {
return translate('MonitorLatestSeason'); return translate('MonitorLastSeason');
} }
}, },
{ {

View File

@ -202,7 +202,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests
} }
[Test] [Test]
public void should_not_monitor_season_when_all_episodes_are_monitored_except_latest_season() public void should_not_monitor_season_when_all_episodes_are_monitored_except_last_season()
{ {
_series.Seasons = Builder<Season>.CreateListOfSize(2) _series.Seasons = Builder<Season>.CreateListOfSize(2)
.All() .All()
@ -226,7 +226,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests
var monitoringOptions = new MonitoringOptions var monitoringOptions = new MonitoringOptions
{ {
Monitor = MonitorTypes.LatestSeason Monitor = MonitorTypes.LastSeason
}; };
Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions);
@ -264,13 +264,47 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests
} }
[Test] [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<Season>.CreateListOfSize(2)
.All()
.With(n => n.Monitored = true)
.Build()
.ToList();
_episodes = Builder<Episode>.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)); _episodes.ForEach(e => e.AirDateUtc = DateTime.UtcNow.AddDays(-100));
var monitoringOptions = new MonitoringOptions var monitoringOptions = new MonitoringOptions
{ {
Monitor = MonitorTypes.LatestSeason Monitor = MonitorTypes.Recent
}; };
Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions);
@ -279,6 +313,43 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeMonitoredServiceTests
.Verify(v => v.UpdateEpisodes(It.Is<List<Episode>>(l => l.All(e => !e.Monitored)))); .Verify(v => v.UpdateEpisodes(It.Is<List<Episode>>(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<Season>.CreateListOfSize(1)
.All()
.With(n => n.Monitored = true)
.Build()
.ToList();
_episodes = Builder<Episode>.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<IEpisodeService>()
.Setup(s => s.GetEpisodeBySeries(It.IsAny<int>()))
.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] [Test]
public void should_monitor_latest_season_if_some_episodes_have_aired() 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 var monitoringOptions = new MonitoringOptions
{ {
Monitor = MonitorTypes.LatestSeason Monitor = MonitorTypes.LastSeason
}; };
Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions); Subject.SetEpisodeMonitoredStatus(_series, monitoringOptions);

View File

@ -984,8 +984,8 @@
"MonitorFirstSeasonDescription": "Monitor all episodes of the first season. All other seasons will be ignored", "MonitorFirstSeasonDescription": "Monitor all episodes of the first season. All other seasons will be ignored",
"MonitorFutureEpisodes": "Future Episodes", "MonitorFutureEpisodes": "Future Episodes",
"MonitorFutureEpisodesDescription": "Monitor episodes that have not aired yet", "MonitorFutureEpisodesDescription": "Monitor episodes that have not aired yet",
"MonitorLatestSeason": "Latest Season", "MonitorLastSeason": "Last Season",
"MonitorLatestSeasonDescription": "Monitor all episodes of the latest season that aired within the last 90 days and all future seasons", "MonitorLastSeasonDescription": "Monitor all episodes of the last season",
"MonitorMissingEpisodes": "Missing Episodes", "MonitorMissingEpisodes": "Missing Episodes",
"MonitorMissingEpisodesDescription": "Monitor episodes that do not have files or have not aired yet", "MonitorMissingEpisodesDescription": "Monitor episodes that do not have files or have not aired yet",
"MonitorNewSeasons": "Monitor New Seasons", "MonitorNewSeasons": "Monitor New Seasons",
@ -994,6 +994,9 @@
"MonitorNone": "None", "MonitorNone": "None",
"MonitorNoneDescription": "No episodes will be monitored", "MonitorNoneDescription": "No episodes will be monitored",
"MonitorPilotEpisode": "Pilot Episode", "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", "MonitorSelected": "Monitor Selected",
"MonitorSeries": "Monitor Series", "MonitorSeries": "Monitor Series",
"MonitorSpecials": "Monitor Specials", "MonitorSpecials": "Monitor Specials",

View File

@ -83,23 +83,27 @@ namespace NzbDrone.Core.Tv
break; break;
case MonitorTypes.LastSeason:
#pragma warning disable CS0612
case MonitorTypes.LatestSeason: case MonitorTypes.LatestSeason:
if (episodes.Where(e => e.SeasonNumber == lastSeason) #pragma warning restore CS0612
.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;
}
_logger.Debug("[{0}] Monitoring latest season episodes", series.Title); _logger.Debug("[{0}] Monitoring latest season episodes", series.Title);
ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && e.SeasonNumber == lastSeason); ToggleEpisodesMonitoredState(episodes, e => e.SeasonNumber > 0 && e.SeasonNumber == lastSeason);
break; 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: case MonitorTypes.MonitorSpecials:
_logger.Debug("[{0}] Monitoring special episodes", series.Title); _logger.Debug("[{0}] Monitoring special episodes", series.Title);
ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == 0), true); ToggleEpisodesMonitoredState(episodes.Where(e => e.SeasonNumber == 0), true);
@ -128,15 +132,14 @@ namespace NzbDrone.Core.Tv
{ {
var seasonNumber = season.SeasonNumber; var seasonNumber = season.SeasonNumber;
// Monitor the season when: // Monitor the last season when:
// - Not specials // - Not specials
// - The latest season // - The latest season
// - Not only supposed to monitor the first season // - Set to monitor all or future episodes
if (seasonNumber > 0 && if (seasonNumber > 0 &&
seasonNumber == lastSeason && seasonNumber == lastSeason &&
monitoringOptions.Monitor != MonitorTypes.FirstSeason && (monitoringOptions.Monitor == MonitorTypes.All ||
monitoringOptions.Monitor != MonitorTypes.Pilot && monitoringOptions.Monitor == MonitorTypes.Future))
monitoringOptions.Monitor != MonitorTypes.None)
{ {
season.Monitored = true; season.Monitored = true;
} }

View File

@ -1,3 +1,4 @@
using System;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
@ -17,8 +18,13 @@ namespace NzbDrone.Core.Tv
Missing, Missing,
Existing, Existing,
FirstSeason, FirstSeason,
LastSeason,
[Obsolete]
LatestSeason, LatestSeason,
Pilot, Pilot,
Recent,
MonitorSpecials, MonitorSpecials,
UnmonitorSpecials, UnmonitorSpecials,
None None

View File

@ -139,7 +139,6 @@ namespace NzbDrone.Core.Tv
{ {
var existingSeason = series.Seasons.FirstOrDefault(s => s.SeasonNumber == season.SeasonNumber); 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 (existingSeason == null)
{ {
if (season.SeasonNumber == 0) if (season.SeasonNumber == 0)
@ -149,8 +148,10 @@ namespace NzbDrone.Core.Tv
continue; continue;
} }
_logger.Debug("New season ({0}) for series: [{1}] {2}, setting monitored to {3}", season.SeasonNumber, series.TvdbId, series.Title, series.Monitored.ToString().ToLowerInvariant()); var monitorNewSeasons = series.MonitorNewItems == NewItemMonitorTypes.All;
season.Monitored = series.Monitored;
_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 else
{ {