From fd42ddec1b1df4be22f2858ce68f71266c88289e Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 12 May 2016 21:02:57 -0700 Subject: [PATCH] Fixed: Respect delays when searching after a failed DownloadRelease Closes #1292 --- src/NzbDrone.Api/Indexers/ReleaseModule.cs | 2 +- .../RssSync/DelaySpecificationFixture.cs | 15 ++++- .../NzbSearchServiceFixture.cs | 16 +++--- .../SeriesSearchServiceFixture.cs | 6 +- .../RssSync/DelaySpecification.cs | 9 +-- .../Definitions/SearchCriteriaBase.cs | 1 + .../IndexerSearch/EpisodeSearchService.cs | 10 ++-- .../IndexerSearch/NzbSearchService.cs | 55 ++++++++++--------- .../IndexerSearch/SeasonSearchService.cs | 2 +- .../IndexerSearch/SeriesSearchService.cs | 2 +- 10 files changed, 64 insertions(+), 54 deletions(-) diff --git a/src/NzbDrone.Api/Indexers/ReleaseModule.cs b/src/NzbDrone.Api/Indexers/ReleaseModule.cs index 9b87f7642..5729af932 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseModule.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseModule.cs @@ -89,7 +89,7 @@ namespace NzbDrone.Api.Indexers { try { - var decisions = _nzbSearchService.EpisodeSearch(episodeId); + var decisions = _nzbSearchService.EpisodeSearch(episodeId, true); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); return MapDecisions(prioritizedDecisions); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs index 8129043fe..2bbe1ae24 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs @@ -87,9 +87,20 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync } [Test] - public void should_be_true_when_search() + public void should_be_true_when_user_invoked_search() { - Subject.IsSatisfiedBy(new RemoteEpisode(), new SingleEpisodeSearchCriteria()).Accepted.Should().BeTrue(); + Subject.IsSatisfiedBy(new RemoteEpisode(), new SingleEpisodeSearchCriteria { UserInvokedSearch = true }).Accepted.Should().BeTrue(); + } + + [Test] + public void should_be_false_when_system_invoked_search_and_release_is_younger_than_delay() + { + _remoteEpisode.ParsedEpisodeInfo.Quality = new QualityModel(Quality.SDTV); + _remoteEpisode.Release.PublishDate = DateTime.UtcNow; + + _delayProfile.UsenetDelay = 720; + + Subject.IsSatisfiedBy(_remoteEpisode, new SingleEpisodeSearchCriteria()).Accepted.Should().BeFalse(); } [Test] diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs index 49a7f96b9..c74058569 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs @@ -127,7 +127,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.EpisodeSearch(_xemEpisodes.First()); + Subject.EpisodeSearch(_xemEpisodes.First(), true); var criteria = allCriteria.OfType().ToList(); @@ -143,7 +143,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 1, false); + Subject.SeasonSearch(_xemSeries.Id, 1, false, true); var criteria = allCriteria.OfType().ToList(); @@ -158,7 +158,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 2, false); + Subject.SeasonSearch(_xemSeries.Id, 2, false, true); var criteria = allCriteria.OfType().ToList(); @@ -174,7 +174,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 4, false); + Subject.SeasonSearch(_xemSeries.Id, 4, false, true); var criteria1 = allCriteria.OfType().ToList(); var criteria2 = allCriteria.OfType().ToList(); @@ -194,7 +194,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 7, false); + Subject.SeasonSearch(_xemSeries.Id, 7, false, true); var criteria = allCriteria.OfType().ToList(); @@ -212,7 +212,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var seasonNumber = 1; var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true); + Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true, true); var criteria = allCriteria.OfType().ToList(); @@ -230,7 +230,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var seasonNumber = 1; var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, seasonNumber, false); + Subject.SeasonSearch(_xemSeries.Id, seasonNumber, false, true); var criteria = allCriteria.OfType().ToList(); @@ -247,7 +247,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var seasonNumber = 1; var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true); + Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true, true); var criteria = allCriteria.OfType().ToList(); diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs index afd04c369..c3aa538c9 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests .Returns(_series); Mocker.GetMock() - .Setup(s => s.SeasonSearch(_series.Id, It.IsAny(), false)) + .Setup(s => s.SeasonSearch(_series.Id, It.IsAny(), false, true)) .Returns(new List()); Mocker.GetMock() @@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests Subject.Execute(new SeriesSearchCommand{ SeriesId = _series.Id }); Mocker.GetMock() - .Verify(v => v.SeasonSearch(_series.Id, It.IsAny(), false), Times.Exactly(_series.Seasons.Count(s => s.Monitored))); + .Verify(v => v.SeasonSearch(_series.Id, It.IsAny(), false, true), Times.Exactly(_series.Seasons.Count(s => s.Monitored))); } [Test] @@ -68,7 +68,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests }; Mocker.GetMock() - .Setup(s => s.SeasonSearch(_series.Id, It.IsAny(), false)) + .Setup(s => s.SeasonSearch(_series.Id, It.IsAny(), false, true)) .Returns(new List()) .Callback((seriesId, seasonNumber, missingOnly) => seasonOrder.Add(seasonNumber)); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index a824fa298..1f440b41e 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -30,12 +30,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { - //How do we want to handle drone being off and the automatic search being triggered? - //TODO: Add a flag to the search to state it is a "scheduled" search - - if (searchCriteria != null) + if (searchCriteria != null && searchCriteria.UserInvokedSearch) { - _logger.Debug("Ignore delay for searches"); + _logger.Debug("Ignoring delay for user invoked search"); return Decision.Accept(); } @@ -71,7 +68,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync } } - //If quality meets or exceeds the best allowed quality in the profile accept it immediately + // If quality meets or exceeds the best allowed quality in the profile accept it immediately var bestQualityInProfile = new QualityModel(profile.LastAllowedQuality()); var isBestInProfile = comparer.Compare(subject.ParsedEpisodeInfo.Quality, bestQualityInProfile) >= 0; diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs index fe1e18260..ea0048a11 100644 --- a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions public List SceneTitles { get; set; } public List Episodes { get; set; } public virtual bool MonitoredEpisodesOnly { get; set; } + public virtual bool UserInvokedSearch { get; set; } public List QueryTitles { diff --git a/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs b/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs index 577dc60e8..4ae7418a8 100644 --- a/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.IndexerSearch _logger = logger; } - private void SearchForMissingEpisodes(List episodes) + private void SearchForMissingEpisodes(List episodes, bool userInvokedSearch) { _logger.ProgressInfo("Performing missing search for {0} episodes", episodes.Count); var downloadedCount = 0; @@ -49,7 +49,7 @@ namespace NzbDrone.Core.IndexerSearch { try { - decisions = _nzbSearchService.SeasonSearch(series.Key, season.Key, true); + decisions = _nzbSearchService.SeasonSearch(series.Key, season.Key, true, userInvokedSearch); } catch (Exception ex) { @@ -63,7 +63,7 @@ namespace NzbDrone.Core.IndexerSearch { try { - decisions = _nzbSearchService.EpisodeSearch(season.First()); + decisions = _nzbSearchService.EpisodeSearch(season.First(), userInvokedSearch); } catch (Exception ex) { @@ -86,7 +86,7 @@ namespace NzbDrone.Core.IndexerSearch { foreach (var episodeId in message.EpisodeIds) { - var decisions = _nzbSearchService.EpisodeSearch(episodeId); + var decisions = _nzbSearchService.EpisodeSearch(episodeId, message.Trigger == CommandTrigger.Manual); var processed = _processDownloadDecisions.ProcessDecisions(decisions); _logger.ProgressInfo("Episode search completed. {0} reports downloaded.", processed.Grabbed.Count); @@ -125,7 +125,7 @@ namespace NzbDrone.Core.IndexerSearch var queue = _queueService.GetQueue().Select(q => q.Episode.Id); var missing = episodes.Where(e => !queue.Contains(e.Id)).ToList(); - SearchForMissingEpisodes(missing); + SearchForMissingEpisodes(missing, message.Trigger == CommandTrigger.Manual); } } } diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index 217bcdeb6..e517a5788 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -17,9 +17,9 @@ namespace NzbDrone.Core.IndexerSearch { public interface ISearchForNzb { - List EpisodeSearch(int episodeId); - List EpisodeSearch(Episode episode); - List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly); + List EpisodeSearch(int episodeId, bool userInvokedSearch); + List EpisodeSearch(Episode episode, bool userInvokedSearch); + List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool userInvokedSearch); } public class NzbSearchService : ISearchForNzb @@ -46,14 +46,14 @@ namespace NzbDrone.Core.IndexerSearch _logger = logger; } - public List EpisodeSearch(int episodeId) + public List EpisodeSearch(int episodeId, bool userInvokedSearch) { var episode = _episodeService.GetEpisode(episodeId); - return EpisodeSearch(episode); + return EpisodeSearch(episode, userInvokedSearch); } - public List EpisodeSearch(Episode episode) + public List EpisodeSearch(Episode episode, bool userInvokedSearch) { var series = _seriesService.GetSeries(episode.SeriesId); @@ -64,23 +64,23 @@ namespace NzbDrone.Core.IndexerSearch throw new InvalidOperationException("Daily episode is missing AirDate. Try to refresh series info."); } - return SearchDaily(series, episode); + return SearchDaily(series, episode, userInvokedSearch); } if (series.SeriesType == SeriesTypes.Anime) { - return SearchAnime(series, episode); + return SearchAnime(series, episode, userInvokedSearch); } if (episode.SeasonNumber == 0) { // search for special episodes in season 0 - return SearchSpecial(series, new List { episode }); + return SearchSpecial(series, new List { episode }, userInvokedSearch); } - return SearchSingle(series, episode); + return SearchSingle(series, episode, userInvokedSearch); } - public List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly) + public List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool userInvokedSearch) { var series = _seriesService.GetSeries(seriesId); var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber); @@ -92,13 +92,13 @@ namespace NzbDrone.Core.IndexerSearch if (series.SeriesType == SeriesTypes.Anime) { - return SearchAnimeSeason(series, episodes); + return SearchAnimeSeason(series, episodes, userInvokedSearch); } if (seasonNumber == 0) { // search for special episodes in season 0 - return SearchSpecial(series, episodes); + return SearchSpecial(series, episodes, userInvokedSearch); } var downloadDecisions = new List(); @@ -119,7 +119,7 @@ namespace NzbDrone.Core.IndexerSearch if (sceneSeasonEpisodes.Count() == 1) { var episode = sceneSeasonEpisodes.First(); - var searchSpec = Get(series, sceneSeasonEpisodes.ToList()); + var searchSpec = Get(series, sceneSeasonEpisodes.ToList(), userInvokedSearch); searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; searchSpec.MonitoredEpisodesOnly = true; @@ -138,7 +138,7 @@ namespace NzbDrone.Core.IndexerSearch } else { - var searchSpec = Get(series, sceneSeasonEpisodes.ToList()); + var searchSpec = Get(series, sceneSeasonEpisodes.ToList(), userInvokedSearch); searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); @@ -148,7 +148,7 @@ namespace NzbDrone.Core.IndexerSearch } else { - var searchSpec = Get(series, episodes); + var searchSpec = Get(series, episodes, userInvokedSearch); searchSpec.SeasonNumber = seasonNumber; var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); @@ -158,9 +158,9 @@ namespace NzbDrone.Core.IndexerSearch return downloadDecisions; } - private List SearchSingle(Series series, Episode episode) + private List SearchSingle(Series series, Episode episode, bool userInvokedSearch) { - var searchSpec = Get(series, new List{episode}); + var searchSpec = Get(series, new List{episode}, userInvokedSearch); if (series.UseSceneNumbering && episode.SceneSeasonNumber.HasValue && episode.SceneEpisodeNumber.HasValue) { @@ -176,18 +176,18 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchDaily(Series series, Episode episode) + private List SearchDaily(Series series, Episode episode, bool userInvokedSearch) { var airDate = DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture); - var searchSpec = Get(series, new List{ episode }); + var searchSpec = Get(series, new List{ episode }, userInvokedSearch); searchSpec.AirDate = airDate; return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchAnime(Series series, Episode episode) + private List SearchAnime(Series series, Episode episode, bool userInvokedSearch) { - var searchSpec = Get(series, new List { episode }); + var searchSpec = Get(series, new List { episode }, userInvokedSearch); if (episode.SceneAbsoluteEpisodeNumber.HasValue) { @@ -205,9 +205,9 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchSpecial(Series series, List episodes) + private List SearchSpecial(Series series, List episodes, bool userInvokedSearch) { - var searchSpec = Get(series, episodes); + var searchSpec = Get(series, episodes, userInvokedSearch); // build list of queries for each episode in the form: " " searchSpec.EpisodeQueryTitles = episodes.Where(e => !string.IsNullOrWhiteSpace(e.Title)) .SelectMany(e => searchSpec.QueryTitles.Select(title => title + " " + SearchCriteriaBase.GetQueryTitle(e.Title))) @@ -216,19 +216,19 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchAnimeSeason(Series series, List episodes) + private List SearchAnimeSeason(Series series, List episodes, bool userInvokedSearch) { var downloadDecisions = new List(); foreach (var episode in episodes.Where(e => e.Monitored)) { - downloadDecisions.AddRange(SearchAnime(series, episode)); + downloadDecisions.AddRange(SearchAnime(series, episode, userInvokedSearch)); } return downloadDecisions; } - private TSpec Get(Series series, List episodes) where TSpec : SearchCriteriaBase, new() + private TSpec Get(Series series, List episodes, bool userInvokedSearch) where TSpec : SearchCriteriaBase, new() { var spec = new TSpec(); @@ -243,6 +243,7 @@ namespace NzbDrone.Core.IndexerSearch spec.Episodes = episodes; spec.SceneTitles.Add(series.Title); + spec.UserInvokedSearch = userInvokedSearch; return spec; } diff --git a/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs b/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs index b8ec0f5cc..84c38c07d 100644 --- a/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Core.IndexerSearch public void Execute(SeasonSearchCommand message) { - var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber, false); + var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber, false, message.Trigger == CommandTrigger.Manual); var processed = _processDownloadDecisions.ProcessDecisions(decisions); _logger.ProgressInfo("Season search completed. {0} reports downloaded.", processed.Grabbed.Count); diff --git a/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs b/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs index fbaa3450f..388dadfd8 100644 --- a/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs @@ -39,7 +39,7 @@ namespace NzbDrone.Core.IndexerSearch continue; } - var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, season.SeasonNumber, false); + var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual); downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count; }