diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index edec79135..05876a0fe 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -231,6 +231,7 @@ + diff --git a/src/NzbDrone.Core.Test/TvTests/ShouldRefreshSeriesFixture.cs b/src/NzbDrone.Core.Test/TvTests/ShouldRefreshSeriesFixture.cs new file mode 100644 index 000000000..14d5f6d68 --- /dev/null +++ b/src/NzbDrone.Core.Test/TvTests/ShouldRefreshSeriesFixture.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.TvTests +{ + [TestFixture] + public class ShouldRefreshSeriesFixture : TestBase + { + private Series _series; + + [SetUp] + public void Setup() + { + _series = Builder.CreateNew() + .Build(); + } + + private void GivenSeriesIsEnded() + { + _series.Status = SeriesStatusType.Ended; + } + + private void GivenSeriesLastRefreshedRecently() + { + _series.LastInfoSync = DateTime.UtcNow.AddDays(-1); + } + + [Test] + public void should_return_true_if_series_is_continuing() + { + _series.Status = SeriesStatusType.Continuing; + + Subject.ShouldRefresh(_series).Should().BeTrue(); + } + + [Test] + public void should_return_true_if_series_last_refreshed_more_than_30_days_ago() + { + GivenSeriesIsEnded(); + _series.LastInfoSync = DateTime.UtcNow.AddDays(-100); + + Subject.ShouldRefresh(_series).Should().BeTrue(); + } + + [Test] + public void should_should_return_true_if_episode_aired_in_last_30_days() + { + Mocker.GetMock() + .Setup(s => s.GetEpisodeBySeries(_series.Id)) + .Returns(Builder.CreateListOfSize(2) + .TheFirst(1) + .With(e => e.AirDateUtc = DateTime.Today.AddDays(-7)) + .TheLast(1) + .With(e => e.AirDateUtc = DateTime.Today.AddDays(-100)) + .Build() + .ToList()); + + Subject.ShouldRefresh(_series).Should().BeTrue(); + } + + [Test] + public void should_should_return_false_when_recently_refreshed_ended_show_has_not_aired_for_30_days() + { + Mocker.GetMock() + .Setup(s => s.GetEpisodeBySeries(_series.Id)) + .Returns(Builder.CreateListOfSize(2) + .All() + .With(e => e.AirDateUtc = DateTime.Today.AddDays(-100)) + .Build() + .ToList()); + + Subject.ShouldRefresh(_series).Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RescanSeriesCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RescanSeriesCommand.cs index 81ee0951f..4ff2450b0 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/RescanSeriesCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/RescanSeriesCommand.cs @@ -13,5 +13,14 @@ namespace NzbDrone.Core.MediaFiles.Commands return true; } } + + public RescanSeriesCommand() + { + } + + public RescanSeriesCommand(int seriesId) + { + SeriesId = seriesId; + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 3bf7c995c..ca9b25f0d 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -670,6 +670,7 @@ + diff --git a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs index b9c9a56cf..b7b455048 100644 --- a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -5,6 +5,7 @@ using System.Linq; using NLog; using NzbDrone.Core.DataAugmentation.DailySeries; using NzbDrone.Core.Instrumentation.Extensions; +using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.MetadataSource; @@ -21,15 +22,26 @@ namespace NzbDrone.Core.Tv private readonly IRefreshEpisodeService _refreshEpisodeService; private readonly IEventAggregator _eventAggregator; private readonly IDailySeriesService _dailySeriesService; + private readonly ICommandExecutor _commandExecutor; + private readonly ICheckIfSeriesShouldBeRefreshed _checkIfSeriesShouldBeRefreshed; private readonly Logger _logger; - public RefreshSeriesService(IProvideSeriesInfo seriesInfo, ISeriesService seriesService, IRefreshEpisodeService refreshEpisodeService, IEventAggregator eventAggregator, IDailySeriesService dailySeriesService, Logger logger) + public RefreshSeriesService(IProvideSeriesInfo seriesInfo, + ISeriesService seriesService, + IRefreshEpisodeService refreshEpisodeService, + IEventAggregator eventAggregator, + IDailySeriesService dailySeriesService, + ICommandExecutor commandExecutor, + ICheckIfSeriesShouldBeRefreshed checkIfSeriesShouldBeRefreshed, + Logger logger) { _seriesInfo = seriesInfo; _seriesService = seriesService; _refreshEpisodeService = refreshEpisodeService; _eventAggregator = eventAggregator; _dailySeriesService = dailySeriesService; + _commandExecutor = commandExecutor; + _checkIfSeriesShouldBeRefreshed = checkIfSeriesShouldBeRefreshed; _logger = logger; } @@ -116,13 +128,29 @@ namespace NzbDrone.Core.Tv foreach (var series in allSeries) { - try + if (_checkIfSeriesShouldBeRefreshed.ShouldRefresh(series)) { - RefreshSeriesInfo(series); + try + { + RefreshSeriesInfo(series); + } + catch (Exception e) + { + _logger.ErrorException("Couldn't refresh info for {0}".Inject(series), e); + } } - catch (Exception e) + + else { - _logger.ErrorException("Couldn't refresh info for {0}".Inject(series), e); + try + { + _logger.Info("Skipping refresh of series: {0}", series.Title); + _commandExecutor.PublishCommand(new RescanSeriesCommand(series.Id)); + } + catch (Exception e) + { + _logger.ErrorException("Couldn't rescan series {0}".Inject(series), e); + } } } } diff --git a/src/NzbDrone.Core/Tv/ShouldRefreshSeries.cs b/src/NzbDrone.Core/Tv/ShouldRefreshSeries.cs new file mode 100644 index 000000000..a052935b4 --- /dev/null +++ b/src/NzbDrone.Core/Tv/ShouldRefreshSeries.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using NLog; + +namespace NzbDrone.Core.Tv +{ + public interface ICheckIfSeriesShouldBeRefreshed + { + bool ShouldRefresh(Series series); + } + + public class ShouldRefreshSeries : ICheckIfSeriesShouldBeRefreshed + { + private readonly IEpisodeService _episodeService; + private readonly Logger _logger; + + public ShouldRefreshSeries(IEpisodeService episodeService, Logger logger) + { + _episodeService = episodeService; + _logger = logger; + } + + public bool ShouldRefresh(Series series) + { + if (series.Status == SeriesStatusType.Continuing) + { + _logger.Trace("Series {0} is continuing, should refresh.", series.Title); + return true; + } + + if (series.LastInfoSync < DateTime.UtcNow.AddDays(-30)) + { + _logger.Trace("Series {0} last updated more than 30 days ago, should refresh.", series.Title); + return true; + } + + var lastEpisode = _episodeService.GetEpisodeBySeries(series.Id).OrderByDescending(e => e.AirDateUtc).FirstOrDefault(); + + if (lastEpisode != null && lastEpisode.AirDateUtc > DateTime.UtcNow.AddDays(-30)) + { + _logger.Trace("Last episode in {0} aired less than 30 days ago, should refresh.", series.Title); + return true; + } + + _logger.Trace("Series {0} should not be refreshed.", series.Title); + return false; + } + } +}