From acc901455bc7cdf031b8429e542a62a907f09555 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 27 Nov 2017 18:31:08 -0800 Subject: [PATCH] New: Separate automatic and interactive searches Closes #253 --- src/NzbDrone.Api/Indexers/IndexerModule.cs | 9 +-- src/NzbDrone.Api/Indexers/IndexerResource.cs | 4 +- src/NzbDrone.Api/Indexers/ReleaseModule.cs | 6 +- .../121_update_animetosho_urlFixture.cs | 7 +- .../Checks/IndexerSearchCheckFixture.cs | 49 +++++++++++-- .../NzbSearchServiceFixture.cs | 28 ++++---- .../SeriesSearchServiceFixture.cs | 10 +-- ...rate_automatic_and_interactive_searches.cs | 19 ++++++ .../Migration/121_update_animetosho_url.cs | 14 +++- .../HealthCheck/Checks/IndexerSearchCheck.cs | 17 +++-- .../Definitions/SearchCriteriaBase.cs | 1 + .../IndexerSearch/EpisodeSearchService.cs | 8 +-- .../IndexerSearch/NzbSearchService.cs | 68 ++++++++++--------- .../IndexerSearch/SeasonSearchService.cs | 4 +- .../IndexerSearch/SeriesSearchService.cs | 4 +- src/NzbDrone.Core/Indexers/IndexerBase.cs | 5 +- .../Indexers/IndexerDefinition.cs | 7 +- src/NzbDrone.Core/Indexers/IndexerFactory.cs | 21 ++++-- src/NzbDrone.Core/Indexers/Newznab/Newznab.cs | 5 +- src/NzbDrone.Core/Indexers/Torznab/Torznab.cs | 5 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + src/Sonarr.Api.V3/Indexers/IndexerResource.cs | 13 ++-- src/Sonarr.Api.V3/Indexers/ReleaseModule.cs | 2 +- 23 files changed, 204 insertions(+), 103 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/119_separate_automatic_and_interactive_searches.cs diff --git a/src/NzbDrone.Api/Indexers/IndexerModule.cs b/src/NzbDrone.Api/Indexers/IndexerModule.cs index c66fa7db6..02daa7708 100644 --- a/src/NzbDrone.Api/Indexers/IndexerModule.cs +++ b/src/NzbDrone.Api/Indexers/IndexerModule.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers; namespace NzbDrone.Api.Indexers { @@ -14,7 +14,7 @@ namespace NzbDrone.Api.Indexers base.MapToResource(resource, definition); resource.EnableRss = definition.EnableRss; - resource.EnableSearch = definition.EnableSearch; + resource.EnableSearch = definition.EnableAutomaticSearch || definition.EnableInteractiveSearch; resource.SupportsRss = definition.SupportsRss; resource.SupportsSearch = definition.SupportsSearch; resource.Protocol = definition.Protocol; @@ -25,7 +25,8 @@ namespace NzbDrone.Api.Indexers base.MapToModel(definition, resource); definition.EnableRss = resource.EnableRss; - definition.EnableSearch = resource.EnableSearch; + definition.EnableAutomaticSearch = resource.EnableSearch; + definition.EnableInteractiveSearch = resource.EnableSearch; } protected override void Validate(IndexerDefinition definition, bool includeWarnings) @@ -34,4 +35,4 @@ namespace NzbDrone.Api.Indexers base.Validate(definition, includeWarnings); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Api/Indexers/IndexerResource.cs b/src/NzbDrone.Api/Indexers/IndexerResource.cs index 26bb27cb9..befdeb1fd 100644 --- a/src/NzbDrone.Api/Indexers/IndexerResource.cs +++ b/src/NzbDrone.Api/Indexers/IndexerResource.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers; namespace NzbDrone.Api.Indexers { @@ -10,4 +10,4 @@ namespace NzbDrone.Api.Indexers public bool SupportsSearch { get; set; } public DownloadProtocol Protocol { get; set; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Api/Indexers/ReleaseModule.cs b/src/NzbDrone.Api/Indexers/ReleaseModule.cs index 9793800b5..c5e7c9db2 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseModule.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using FluentValidation; using Nancy; @@ -89,7 +89,7 @@ namespace NzbDrone.Api.Indexers { try { - var decisions = _nzbSearchService.EpisodeSearch(episodeId, true); + var decisions = _nzbSearchService.EpisodeSearch(episodeId, true, true); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); return MapDecisions(prioritizedDecisions); @@ -123,4 +123,4 @@ namespace NzbDrone.Api.Indexers return string.Concat(resource.IndexerId, "_", resource.Guid); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/121_update_animetosho_urlFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/121_update_animetosho_urlFixture.cs index 33bdddddd..ac2cf94e3 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/121_update_animetosho_urlFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/121_update_animetosho_urlFixture.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using FluentAssertions; using NUnit.Framework; using NzbDrone.Common.Serializer; @@ -28,11 +28,12 @@ namespace NzbDrone.Core.Test.Datastore.Migration ApiPath = "/feed/nabapi" }.ToJson(), - ConfigContract = impl + "Settings" + ConfigContract = impl + "Settings", + EnableInteractiveSearch = false }); }); - var items = db.Query("SELECT * FROM Indexers"); + var items = db.Query("SELECT * FROM Indexers"); items.Should().HaveCount(1); items.First().Settings.ToObject().BaseUrl.Should().Be(baseUrl.Replace("animetosho", "feed.animetosho")); diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/IndexerSearchCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/IndexerSearchCheckFixture.cs index 8cbc28b9d..988d5d8e9 100644 --- a/src/NzbDrone.Core.Test/HealthCheck/Checks/IndexerSearchCheckFixture.cs +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/IndexerSearchCheckFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Moq; using NUnit.Framework; using NzbDrone.Core.HealthCheck.Checks; @@ -20,7 +20,11 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks .Returns(new List()); Mocker.GetMock() - .Setup(s => s.SearchEnabled(It.IsAny())) + .Setup(s => s.AutomaticSearchEnabled(It.IsAny())) + .Returns(new List()); + + Mocker.GetMock() + .Setup(s => s.InteractiveSearchEnabled(It.IsAny())) .Returns(new List()); } @@ -35,17 +39,28 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks .Returns(new List { _indexerMock.Object }); } - private void GivenSearchEnabled() + private void GivenAutomaticSearchEnabled() { Mocker.GetMock() - .Setup(s => s.SearchEnabled(It.IsAny())) + .Setup(s => s.AutomaticSearchEnabled(It.IsAny())) + .Returns(new List { _indexerMock.Object }); + } + + private void GivenInteractiveSearchEnabled() + { + Mocker.GetMock() + .Setup(s => s.InteractiveSearchEnabled(It.IsAny())) .Returns(new List { _indexerMock.Object }); } private void GivenSearchFiltered() { Mocker.GetMock() - .Setup(s => s.SearchEnabled(false)) + .Setup(s => s.AutomaticSearchEnabled(false)) + .Returns(new List { _indexerMock.Object }); + + Mocker.GetMock() + .Setup(s => s.InteractiveSearchEnabled(false)) .Returns(new List { _indexerMock.Object }); } @@ -64,14 +79,33 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks } [Test] - public void should_return_ok_when_search_is_enabled() + public void should_return_ok_when_automatic_and__search_is_enabled() { GivenIndexer(false, true); - GivenSearchEnabled(); + GivenAutomaticSearchEnabled(); + GivenInteractiveSearchEnabled(); Subject.Check().ShouldBeOk(); } + [Test] + public void should_return_warning_when_only_automatic_search_is_enabled() + { + GivenIndexer(false, true); + GivenAutomaticSearchEnabled(); + + Subject.Check().ShouldBeWarning(); + } + + [Test] + public void should_return_warning_when_only_interactive_search_is_enabled() + { + GivenIndexer(false, true); + GivenInteractiveSearchEnabled(); + + Subject.Check().ShouldBeWarning(); + } + [Test] public void should_return_warning_if_search_is_supported_but_disabled() { @@ -80,6 +114,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks Subject.Check().ShouldBeWarning(); } + [Test] public void should_return_filter_warning_if_search_is_enabled_but_filtered() { diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs index e9aa1cf13..736c00cbf 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests _mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true); Mocker.GetMock() - .Setup(s => s.SearchEnabled(true)) + .Setup(s => s.AutomaticSearchEnabled(true)) .Returns(new List { _mockIndexer.Object }); Mocker.GetMock() @@ -136,7 +136,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.EpisodeSearch(_xemEpisodes.First(), true); + Subject.EpisodeSearch(_xemEpisodes.First(), true, false); var criteria = allCriteria.OfType().ToList(); @@ -152,7 +152,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 1, false, true); + Subject.SeasonSearch(_xemSeries.Id, 1, false, true, false); var criteria = allCriteria.OfType().ToList(); @@ -167,7 +167,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 2, false, true); + Subject.SeasonSearch(_xemSeries.Id, 2, false, true, false); var criteria = allCriteria.OfType().ToList(); @@ -183,7 +183,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 4, false, true); + Subject.SeasonSearch(_xemSeries.Id, 4, false, true, false); var criteria1 = allCriteria.OfType().ToList(); var criteria2 = allCriteria.OfType().ToList(); @@ -203,7 +203,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 7, false, true); + Subject.SeasonSearch(_xemSeries.Id, 7, false, true, false); var criteria = allCriteria.OfType().ToList(); @@ -221,7 +221,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var seasonNumber = 1; var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true, true); + Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true, true, false); var criteria = allCriteria.OfType().ToList(); @@ -239,7 +239,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var seasonNumber = 1; var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, seasonNumber, false, true); + Subject.SeasonSearch(_xemSeries.Id, seasonNumber, false, true, false); var criteria = allCriteria.OfType().ToList(); @@ -256,7 +256,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var seasonNumber = 1; var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true, true); + Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true, true, false); var criteria = allCriteria.OfType().ToList(); @@ -274,7 +274,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 1, false, true); + Subject.SeasonSearch(_xemSeries.Id, 1, false, true, false); var criteria = allCriteria.OfType().ToList(); @@ -293,7 +293,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 1, false, true); + Subject.SeasonSearch(_xemSeries.Id, 1, false, true, false); var criteria1 = allCriteria.OfType().ToList(); var criteria2 = allCriteria.OfType().ToList(); @@ -316,7 +316,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 1, false, true); + Subject.SeasonSearch(_xemSeries.Id, 1, false, true, false); var criteria1 = allCriteria.OfType().ToList(); var criteria2 = allCriteria.OfType().ToList(); @@ -332,7 +332,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 7, false, true); + Subject.SeasonSearch(_xemSeries.Id, 7, false, true, false); Mocker.GetMock() .Verify(v => v.GetSceneNames(_xemSeries.Id, It.Is>(l => l.Contains(7)), It.Is>(l => l.Contains(7))), Times.Once()); diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs index 906a9f071..8056f7a2e 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/SeriesSearchServiceFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Moq; @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests .Returns(_series); Mocker.GetMock() - .Setup(s => s.SeasonSearch(_series.Id, It.IsAny(), false, true)) + .Setup(s => s.SeasonSearch(_series.Id, It.IsAny(), false, true, false)) .Returns(new List()); Mocker.GetMock() @@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests Subject.Execute(new SeriesSearchCommand { SeriesId = _series.Id, Trigger = CommandTrigger.Manual }); Mocker.GetMock() - .Verify(v => v.SeasonSearch(_series.Id, It.IsAny(), false, true), Times.Exactly(_series.Seasons.Count(s => s.Monitored))); + .Verify(v => v.SeasonSearch(_series.Id, It.IsAny(), false, true, false), Times.Exactly(_series.Seasons.Count(s => s.Monitored))); } [Test] @@ -68,9 +68,9 @@ namespace NzbDrone.Core.Test.IndexerSearchTests }; Mocker.GetMock() - .Setup(s => s.SeasonSearch(_series.Id, It.IsAny(), false, true)) + .Setup(s => s.SeasonSearch(_series.Id, It.IsAny(), false, true, false)) .Returns(new List()) - .Callback((seriesId, seasonNumber, missingOnly, userInvokedSearch) => seasonOrder.Add(seasonNumber)); + .Callback((seriesId, seasonNumber, missingOnly, userInvokedSearch, interactiveSearch) => seasonOrder.Add(seasonNumber)); Subject.Execute(new SeriesSearchCommand { SeriesId = _series.Id, Trigger = CommandTrigger.Manual }); diff --git a/src/NzbDrone.Core/Datastore/Migration/119_separate_automatic_and_interactive_searches.cs b/src/NzbDrone.Core/Datastore/Migration/119_separate_automatic_and_interactive_searches.cs new file mode 100644 index 000000000..eb22cde57 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/119_separate_automatic_and_interactive_searches.cs @@ -0,0 +1,19 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(119)] + public class separate_automatic_and_interactive_searches : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Rename.Column("EnableSearch").OnTable("Indexers").To("EnableAutomaticSearch"); + Alter.Table("Indexers").AddColumn("EnableInteractiveSearch").AsBoolean().Nullable(); + + Execute.Sql("UPDATE Indexers SET EnableInteractiveSearch = EnableAutomaticSearch"); + + Alter.Table("Indexers").AlterColumn("EnableInteractiveSearch").AsBoolean().NotNullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/121_update_animetosho_url.cs b/src/NzbDrone.Core/Datastore/Migration/121_update_animetosho_url.cs index 1a91ded75..da74b46ab 100644 --- a/src/NzbDrone.Core/Datastore/Migration/121_update_animetosho_url.cs +++ b/src/NzbDrone.Core/Datastore/Migration/121_update_animetosho_url.cs @@ -1,4 +1,4 @@ -using FluentMigrator; +using FluentMigrator; using Newtonsoft.Json.Linq; using NzbDrone.Core.Datastore.Migration.Framework; @@ -13,6 +13,18 @@ namespace NzbDrone.Core.Datastore.Migration } } + public class IndexerDefinition121 + { + public int Id { get; set; } + public string Name { get; set; } + public JObject Settings { get; set; } + public string Implementation { get; set; } + public string ConfigContract { get; set; } + public bool EnableRss { get; set; } + public bool EnableAutomaticSearch { get; set; } + public bool EnableInteractiveSearch { get; set; } + } + public class NewznabSettings121 { public string BaseUrl { get; set; } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/IndexerSearchCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/IndexerSearchCheck.cs index a4c33506c..c0e02ceed 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/IndexerSearchCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/IndexerSearchCheck.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.Indexers; using NzbDrone.Core.ThingiProvider.Events; @@ -19,14 +19,21 @@ namespace NzbDrone.Core.HealthCheck.Checks public override HealthCheck Check() { - var enabled = _indexerFactory.SearchEnabled(false); + var automaticSearchEnabled = _indexerFactory.AutomaticSearchEnabled(false); - if (enabled.Empty()) + if (automaticSearchEnabled.Empty()) { - return new HealthCheck(GetType(), HealthCheckResult.Warning, "No indexers available with Search enabled, Sonarr will not provide any search results"); + return new HealthCheck(GetType(), HealthCheckResult.Warning, "No indexers available with Automatic Search enabled, Sonarr will not provide any automatic search results"); } - var active = _indexerFactory.SearchEnabled(true); + var interactiveSearchEnabled = _indexerFactory.InteractiveSearchEnabled(false); + + if (interactiveSearchEnabled.Empty()) + { + return new HealthCheck(GetType(), HealthCheckResult.Warning, "No indexers available with Interactive Search enabled, Sonarr will not provide any interactive search results"); + } + + var active = _indexerFactory.AutomaticSearchEnabled(true); if (active.Empty()) { diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs index 066a4bd7e..837dcc4b2 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 Episodes { get; set; } public virtual bool MonitoredEpisodesOnly { get; set; } public virtual bool UserInvokedSearch { get; set; } + public virtual bool InteractiveSearch { get; set; } public List QueryTitles => SceneTitles.Select(GetQueryTitle).Distinct().ToList(); diff --git a/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs b/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs index 6fc423a15..b63b05572 100644 --- a/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -55,7 +55,7 @@ namespace NzbDrone.Core.IndexerSearch { try { - decisions = _nzbSearchService.SeasonSearch(series.Key, season.Key, true, userInvokedSearch); + decisions = _nzbSearchService.SeasonSearch(series.Key, season.Key, true, userInvokedSearch, false); } catch (Exception ex) { @@ -68,7 +68,7 @@ namespace NzbDrone.Core.IndexerSearch { try { - decisions = _nzbSearchService.EpisodeSearch(season.First(), userInvokedSearch); + decisions = _nzbSearchService.EpisodeSearch(season.First(), userInvokedSearch, false); } catch (Exception ex) { @@ -90,7 +90,7 @@ namespace NzbDrone.Core.IndexerSearch { foreach (var episodeId in message.EpisodeIds) { - var decisions = _nzbSearchService.EpisodeSearch(episodeId, message.Trigger == CommandTrigger.Manual); + var decisions = _nzbSearchService.EpisodeSearch(episodeId, message.Trigger == CommandTrigger.Manual, false); var processed = _processDownloadDecisions.ProcessDecisions(decisions); _logger.ProgressInfo("Episode search completed. {0} reports downloaded.", processed.Grabbed.Count); diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index d589dd9bf..6b6f79e2e 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -18,9 +18,9 @@ namespace NzbDrone.Core.IndexerSearch { public interface ISearchForNzb { - List EpisodeSearch(int episodeId, bool userInvokedSearch); - List EpisodeSearch(Episode episode, bool userInvokedSearch); - List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool userInvokedSearch); + List EpisodeSearch(int episodeId, bool userInvokedSearch, bool interactiveSearch); + List EpisodeSearch(Episode episode, bool userInvokedSearch, bool interactiveSearch); + List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool userInvokedSearch, bool interactiveSearch); } public class NzbSearchService : ISearchForNzb @@ -47,14 +47,14 @@ namespace NzbDrone.Core.IndexerSearch _logger = logger; } - public List EpisodeSearch(int episodeId, bool userInvokedSearch) + public List EpisodeSearch(int episodeId, bool userInvokedSearch, bool interactiveSearch) { var episode = _episodeService.GetEpisode(episodeId); - return EpisodeSearch(episode, userInvokedSearch); + return EpisodeSearch(episode, userInvokedSearch, interactiveSearch); } - public List EpisodeSearch(Episode episode, bool userInvokedSearch) + public List EpisodeSearch(Episode episode, bool userInvokedSearch, bool interactiveSearch) { var series = _seriesService.GetSeries(episode.SeriesId); @@ -65,23 +65,23 @@ namespace NzbDrone.Core.IndexerSearch throw new InvalidOperationException("Daily episode is missing AirDate. Try to refresh series info."); } - return SearchDaily(series, episode, userInvokedSearch); + return SearchDaily(series, episode, userInvokedSearch, interactiveSearch); } if (series.SeriesType == SeriesTypes.Anime) { - return SearchAnime(series, episode, userInvokedSearch); + return SearchAnime(series, episode, userInvokedSearch, interactiveSearch); } if (episode.SeasonNumber == 0) { // search for special episodes in season 0 - return SearchSpecial(series, new List { episode }, userInvokedSearch); + return SearchSpecial(series, new List { episode }, userInvokedSearch, interactiveSearch); } - return SearchSingle(series, episode, userInvokedSearch); + return SearchSingle(series, episode, userInvokedSearch, interactiveSearch); } - public List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool userInvokedSearch) + public List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool userInvokedSearch, bool interactiveSearch) { var series = _seriesService.GetSeries(seriesId); var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber); @@ -93,18 +93,18 @@ namespace NzbDrone.Core.IndexerSearch if (series.SeriesType == SeriesTypes.Anime) { - return SearchAnimeSeason(series, episodes, userInvokedSearch); + return SearchAnimeSeason(series, episodes, userInvokedSearch, interactiveSearch); } if (series.SeriesType == SeriesTypes.Daily) { - return SearchDailySeason(series, episodes, userInvokedSearch); + return SearchDailySeason(series, episodes, userInvokedSearch, interactiveSearch); } if (seasonNumber == 0) { // search for special episodes in season 0 - return SearchSpecial(series, episodes, userInvokedSearch); + return SearchSpecial(series, episodes, userInvokedSearch, interactiveSearch); } var downloadDecisions = new List(); @@ -125,7 +125,7 @@ namespace NzbDrone.Core.IndexerSearch if (sceneSeasonEpisodes.Count() == 1) { var episode = sceneSeasonEpisodes.First(); - var searchSpec = Get(series, sceneSeasonEpisodes.ToList(), userInvokedSearch); + var searchSpec = Get(series, sceneSeasonEpisodes.ToList(), userInvokedSearch, interactiveSearch); searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; searchSpec.MonitoredEpisodesOnly = true; @@ -144,7 +144,7 @@ namespace NzbDrone.Core.IndexerSearch } else { - var searchSpec = Get(series, sceneSeasonEpisodes.ToList(), userInvokedSearch); + var searchSpec = Get(series, sceneSeasonEpisodes.ToList(), userInvokedSearch, interactiveSearch); searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); @@ -154,7 +154,7 @@ namespace NzbDrone.Core.IndexerSearch } else { - var searchSpec = Get(series, episodes, userInvokedSearch); + var searchSpec = Get(series, episodes, userInvokedSearch, interactiveSearch); searchSpec.SeasonNumber = seasonNumber; var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); @@ -164,9 +164,9 @@ namespace NzbDrone.Core.IndexerSearch return downloadDecisions; } - private List SearchSingle(Series series, Episode episode, bool userInvokedSearch) + private List SearchSingle(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch) { - var searchSpec = Get(series, new List { episode }, userInvokedSearch); + var searchSpec = Get(series, new List { episode }, userInvokedSearch, interactiveSearch); if (series.UseSceneNumbering && episode.SceneSeasonNumber.HasValue && episode.SceneEpisodeNumber.HasValue) { @@ -182,18 +182,18 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchDaily(Series series, Episode episode, bool userInvokedSearch) + private List SearchDaily(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch) { var airDate = DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture); - var searchSpec = Get(series, new List { episode }, userInvokedSearch); + var searchSpec = Get(series, new List { episode }, userInvokedSearch, interactiveSearch); searchSpec.AirDate = airDate; return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchAnime(Series series, Episode episode, bool userInvokedSearch) + private List SearchAnime(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch) { - var searchSpec = Get(series, new List { episode }, userInvokedSearch); + var searchSpec = Get(series, new List { episode }, userInvokedSearch, interactiveSearch); if (episode.SceneAbsoluteEpisodeNumber.HasValue) { @@ -211,9 +211,9 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchSpecial(Series series, List episodes, bool userInvokedSearch) + private List SearchSpecial(Series series, List episodes, bool userInvokedSearch, bool interactiveSearch) { - var searchSpec = Get(series, episodes, userInvokedSearch); + var searchSpec = Get(series, episodes, userInvokedSearch, interactiveSearch); // 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))) @@ -222,19 +222,19 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchAnimeSeason(Series series, List episodes, bool userInvokedSearch) + private List SearchAnimeSeason(Series series, List episodes, bool userInvokedSearch, bool interactiveSearch) { var downloadDecisions = new List(); foreach (var episode in episodes.Where(e => e.Monitored)) { - downloadDecisions.AddRange(SearchAnime(series, episode, userInvokedSearch)); + downloadDecisions.AddRange(SearchAnime(series, episode, userInvokedSearch, interactiveSearch)); } return downloadDecisions; } - private List SearchDailySeason(Series series, List episodes, bool userInvokedSearch) + private List SearchDailySeason(Series series, List episodes, bool userInvokedSearch, bool interactiveSearch) { var downloadDecisions = new List(); foreach (var yearGroup in episodes.Where(v => v.Monitored && v.AirDate.IsNotNullOrWhiteSpace()) @@ -244,21 +244,21 @@ namespace NzbDrone.Core.IndexerSearch if (yearEpisodes.Count > 1) { - var searchSpec = Get(series, yearEpisodes, userInvokedSearch); + var searchSpec = Get(series, yearEpisodes, userInvokedSearch, interactiveSearch); searchSpec.Year = yearGroup.Key; downloadDecisions.AddRange(Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec)); } else { - downloadDecisions.AddRange(SearchDaily(series, yearEpisodes.First(), userInvokedSearch)); + downloadDecisions.AddRange(SearchDaily(series, yearEpisodes.First(), userInvokedSearch, interactiveSearch)); } } return downloadDecisions; } - private TSpec Get(Series series, List episodes, bool userInvokedSearch) where TSpec : SearchCriteriaBase, new() + private TSpec Get(Series series, List episodes, bool userInvokedSearch, bool interactiveSearch) where TSpec : SearchCriteriaBase, new() { var spec = new TSpec(); @@ -274,13 +274,17 @@ namespace NzbDrone.Core.IndexerSearch spec.Episodes = episodes; spec.UserInvokedSearch = userInvokedSearch; + spec.InteractiveSearch = interactiveSearch; return spec; } private List Dispatch(Func> searchAction, SearchCriteriaBase criteriaBase) { - var indexers = _indexerFactory.SearchEnabled(); + var indexers = criteriaBase.InteractiveSearch ? + _indexerFactory.InteractiveSearchEnabled() : + _indexerFactory.AutomaticSearchEnabled(); + var reports = new List(); _logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase); diff --git a/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs b/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs index 84c38c07d..c35f38ab0 100644 --- a/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Download; using NzbDrone.Core.Messaging.Commands; @@ -22,7 +22,7 @@ namespace NzbDrone.Core.IndexerSearch public void Execute(SeasonSearchCommand message) { - var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber, false, message.Trigger == CommandTrigger.Manual); + var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber, false, message.Trigger == CommandTrigger.Manual, false); 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 388dadfd8..3e25af42e 100644 --- a/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/SeriesSearchService.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using NLog; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Core.Download; @@ -39,7 +39,7 @@ namespace NzbDrone.Core.IndexerSearch continue; } - var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual); + var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual, false); downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count; } diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index 96638a419..4095eb28d 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; @@ -48,7 +48,8 @@ namespace NzbDrone.Core.Indexers { Name = GetType().Name, EnableRss = config.Validate().IsValid && SupportsRss, - EnableSearch = config.Validate().IsValid && SupportsSearch, + EnableAutomaticSearch = config.Validate().IsValid && SupportsSearch, + EnableInteractiveSearch = config.Validate().IsValid && SupportsSearch, Implementation = GetType().Name, Settings = config }; diff --git a/src/NzbDrone.Core/Indexers/IndexerDefinition.cs b/src/NzbDrone.Core/Indexers/IndexerDefinition.cs index 229d35948..019a61727 100644 --- a/src/NzbDrone.Core/Indexers/IndexerDefinition.cs +++ b/src/NzbDrone.Core/Indexers/IndexerDefinition.cs @@ -1,16 +1,17 @@ -using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers { public class IndexerDefinition : ProviderDefinition { public bool EnableRss { get; set; } - public bool EnableSearch { get; set; } + public bool EnableAutomaticSearch { get; set; } + public bool EnableInteractiveSearch { get; set; } public DownloadProtocol Protocol { get; set; } public bool SupportsRss { get; set; } public bool SupportsSearch { get; set; } - public override bool Enable => EnableRss || EnableSearch; + public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch; public IndexerStatus Status { get; set; } } diff --git a/src/NzbDrone.Core/Indexers/IndexerFactory.cs b/src/NzbDrone.Core/Indexers/IndexerFactory.cs index 0d918b980..902de54b9 100644 --- a/src/NzbDrone.Core/Indexers/IndexerFactory.cs +++ b/src/NzbDrone.Core/Indexers/IndexerFactory.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using FluentValidation.Results; using NLog; @@ -11,7 +11,8 @@ namespace NzbDrone.Core.Indexers public interface IIndexerFactory : IProviderFactory { List RssEnabled(bool filterBlockedIndexers = true); - List SearchEnabled(bool filterBlockedIndexers = true); + List AutomaticSearchEnabled(bool filterBlockedIndexers = true); + List InteractiveSearchEnabled(bool filterBlockedIndexers = true); } public class IndexerFactory : ProviderFactory, IIndexerFactory @@ -57,9 +58,21 @@ namespace NzbDrone.Core.Indexers return enabledIndexers.ToList(); } - public List SearchEnabled(bool filterBlockedIndexers = true) + public List AutomaticSearchEnabled(bool filterBlockedIndexers = true) { - var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableSearch); + var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableAutomaticSearch); + + if (filterBlockedIndexers) + { + return FilterBlockedIndexers(enabledIndexers).ToList(); + } + + return enabledIndexers.ToList(); + } + + public List InteractiveSearchEnabled(bool filterBlockedIndexers = true) + { + var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableInteractiveSearch); if (filterBlockedIndexers) { diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index 79c40b997..29a355d44 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; @@ -67,7 +67,8 @@ namespace NzbDrone.Core.Indexers.Newznab return new IndexerDefinition { EnableRss = false, - EnableSearch = false, + EnableAutomaticSearch = false, + EnableInteractiveSearch = false, Name = name, Implementation = GetType().Name, Settings = settings, diff --git a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs index a671b4093..0756ae1b8 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; @@ -56,7 +56,8 @@ namespace NzbDrone.Core.Indexers.Torznab return new IndexerDefinition { EnableRss = false, - EnableSearch = false, + EnableAutomaticSearch = false, + EnableInteractiveSearch = false, Name = name, Implementation = GetType().Name, Settings = settings, diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index da5c693e5..82e539d67 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -244,6 +244,7 @@ + diff --git a/src/Sonarr.Api.V3/Indexers/IndexerResource.cs b/src/Sonarr.Api.V3/Indexers/IndexerResource.cs index a505e9367..7ca24731f 100644 --- a/src/Sonarr.Api.V3/Indexers/IndexerResource.cs +++ b/src/Sonarr.Api.V3/Indexers/IndexerResource.cs @@ -1,11 +1,12 @@ -using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers; namespace Sonarr.Api.V3.Indexers { public class IndexerResource : ProviderResource { public bool EnableRss { get; set; } - public bool EnableSearch { get; set; } + public bool EnableAutomaticSearch { get; set; } + public bool EnableInteractiveSearch { get; set; } public bool SupportsRss { get; set; } public bool SupportsSearch { get; set; } public DownloadProtocol Protocol { get; set; } @@ -20,7 +21,8 @@ namespace Sonarr.Api.V3.Indexers var resource = base.ToResource(definition); resource.EnableRss = definition.EnableRss; - resource.EnableSearch = definition.EnableSearch; + resource.EnableAutomaticSearch = definition.EnableAutomaticSearch; + resource.EnableInteractiveSearch = definition.EnableInteractiveSearch; resource.SupportsRss = definition.SupportsRss; resource.SupportsSearch = definition.SupportsSearch; resource.Protocol = definition.Protocol; @@ -35,9 +37,10 @@ namespace Sonarr.Api.V3.Indexers var definition = base.ToModel(resource); definition.EnableRss = resource.EnableRss; - definition.EnableSearch = resource.EnableSearch; + definition.EnableAutomaticSearch = resource.EnableAutomaticSearch; + definition.EnableInteractiveSearch = resource.EnableInteractiveSearch; return definition; } } -} \ No newline at end of file +} diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs b/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs index 88875b8b9..2ff2da8ce 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs @@ -91,7 +91,7 @@ namespace Sonarr.Api.V3.Indexers { try { - var decisions = _nzbSearchService.EpisodeSearch(episodeId, true); + var decisions = _nzbSearchService.EpisodeSearch(episodeId, true, true); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); return MapDecisions(prioritizedDecisions);