From bc2942c28d3c48e9eea61a68baf032ba181aef1e Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 20 Dec 2022 18:28:33 -0800 Subject: [PATCH] New: Don't block imports when release was matched by ID if they were grabbed via interactive search Closes #5043 --- .../PendingReleaseServiceFixture.cs | 2 +- .../DecisionEngine/DownloadDecisionMaker.cs | 28 +++- .../{Search => }/SceneMappingSpecification.cs | 144 +++++++++--------- .../Download/CompletedDownloadService.cs | 4 +- .../Download/Pending/PendingRelease.cs | 3 +- .../Download/Pending/PendingReleaseService.cs | 1 + src/NzbDrone.Core/History/EpisodeHistory.cs | 1 + src/NzbDrone.Core/History/HistoryService.cs | 1 + .../Parser/Model/RemoteEpisode.cs | 12 +- 9 files changed, 115 insertions(+), 81 deletions(-) rename src/NzbDrone.Core/DecisionEngine/Specifications/{Search => }/SceneMappingSpecification.cs (92%) diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/PendingReleaseServiceFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/PendingReleaseServiceFixture.cs index 5933db2e4..1b559c46e 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/PendingReleaseServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/PendingReleaseServiceFixture.cs @@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests results.Should().NotBeEmpty(); Mocker.GetMock() - .Verify(v => v.GetRssDecision(It.Is>(d => d.Count == 0)), Times.Never()); + .Verify(v => v.GetRssDecision(It.Is>(d => d.Count == 0), It.IsAny()), Times.Never()); } [Test] diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index 1a1e7fc49..78c4a6c65 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Core.DecisionEngine { public interface IMakeDownloadDecision { - List GetRssDecision(List reports); + List GetRssDecision(List reports, bool pushedRelease = false); List GetSearchDecision(List reports, SearchCriteriaBase searchCriteriaBase); } @@ -45,17 +45,17 @@ namespace NzbDrone.Core.DecisionEngine _logger = logger; } - public List GetRssDecision(List reports) + public List GetRssDecision(List reports, bool pushedRelease = false) { return GetDecisions(reports).ToList(); } public List GetSearchDecision(List reports, SearchCriteriaBase searchCriteriaBase) { - return GetDecisions(reports, searchCriteriaBase).ToList(); + return GetDecisions(reports, false, searchCriteriaBase).ToList(); } - private IEnumerable GetDecisions(List reports, SearchCriteriaBase searchCriteria = null) + private IEnumerable GetDecisions(List reports, bool pushedRelease = false, SearchCriteriaBase searchCriteria = null) { if (reports.Any()) { @@ -156,6 +156,26 @@ namespace NzbDrone.Core.DecisionEngine if (decision != null) { + var source = pushedRelease ? ReleaseSourceType.ReleasePush : ReleaseSourceType.Rss; + + if (searchCriteria != null) + { + if (searchCriteria.InteractiveSearch) + { + source = ReleaseSourceType.InteractiveSearch; + } + else if (searchCriteria.UserInvokedSearch) + { + source = ReleaseSourceType.UserInvokedSearch; + } + else + { + source = ReleaseSourceType.Search; + } + } + + decision.RemoteEpisode.ReleaseSource = source; + if (decision.Rejections.Any()) { _logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections)); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SceneMappingSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/SceneMappingSpecification.cs similarity index 92% rename from src/NzbDrone.Core/DecisionEngine/Specifications/Search/SceneMappingSpecification.cs rename to src/NzbDrone.Core/DecisionEngine/Specifications/SceneMappingSpecification.cs index 3fd5f3868..04bb2c712 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SceneMappingSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/SceneMappingSpecification.cs @@ -1,73 +1,71 @@ -using System.Linq; -using NLog; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.DataAugmentation.Scene; -using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.DecisionEngine.Specifications.Search -{ - public class SceneMappingSpecification : IDecisionEngineSpecification - { - private readonly Logger _logger; - - public SceneMappingSpecification(Logger logger) - { - _logger = logger; - } - - public SpecificationPriority Priority => SpecificationPriority.Default; - public RejectionType Type => RejectionType.Temporary; // Temporary till there's a mapping - - public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) - { - if (remoteEpisode.SceneMapping == null) - { - _logger.Debug("No applicable scene mapping, skipping."); - return Decision.Accept(); - } - - if (remoteEpisode.SceneMapping.SceneOrigin.IsNullOrWhiteSpace()) - { - _logger.Debug("No explicit scene origin in scene mapping."); - return Decision.Accept(); - } - - var split = remoteEpisode.SceneMapping.SceneOrigin.Split(':'); - - var isInteractive = searchCriteria != null && searchCriteria.InteractiveSearch; - - if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace()) - { - _logger.Debug("SceneMapping has origin {0} with comment '{1}'.", remoteEpisode.SceneMapping.SceneOrigin, remoteEpisode.SceneMapping.Comment); - } - else - { - _logger.Debug("SceneMapping has origin {0}.", remoteEpisode.SceneMapping.SceneOrigin); - } - - if (split[0] == "mixed") - { - _logger.Debug("SceneMapping origin is explicitly mixed, this means these were released with multiple unidentifiable numbering schemes."); - - if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace()) - { - return Decision.Reject("{0} has ambiguous numbering"); - } - else - { - return Decision.Reject("Ambiguous numbering"); - } - } - - if (split[0] == "unknown") - { - var type = split.Length >= 2 ? split[1] : "scene"; - - _logger.Debug("SceneMapping origin is explicitly unknown, unsure what numbering scheme it uses but '{0}' will be assumed. Provide full release title to Sonarr/TheXEM team.", type); - } - - return Decision.Accept(); - } - } -} +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class SceneMappingSpecification : IDecisionEngineSpecification + { + private readonly Logger _logger; + + public SceneMappingSpecification(Logger logger) + { + _logger = logger; + } + + public SpecificationPriority Priority => SpecificationPriority.Default; + public RejectionType Type => RejectionType.Temporary; // Temporary till there's a mapping + + public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) + { + if (remoteEpisode.SceneMapping == null) + { + _logger.Debug("No applicable scene mapping, skipping."); + return Decision.Accept(); + } + + if (remoteEpisode.SceneMapping.SceneOrigin.IsNullOrWhiteSpace()) + { + _logger.Debug("No explicit scene origin in scene mapping."); + return Decision.Accept(); + } + + var split = remoteEpisode.SceneMapping.SceneOrigin.Split(':'); + + var isInteractive = searchCriteria != null && searchCriteria.InteractiveSearch; + + if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace()) + { + _logger.Debug("SceneMapping has origin {0} with comment '{1}'.", remoteEpisode.SceneMapping.SceneOrigin, remoteEpisode.SceneMapping.Comment); + } + else + { + _logger.Debug("SceneMapping has origin {0}.", remoteEpisode.SceneMapping.SceneOrigin); + } + + if (split[0] == "mixed") + { + _logger.Debug("SceneMapping origin is explicitly mixed, this means these were released with multiple unidentifiable numbering schemes."); + + if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace()) + { + return Decision.Reject("{0} has ambiguous numbering"); + } + else + { + return Decision.Reject("Ambiguous numbering"); + } + } + + if (split[0] == "unknown") + { + var type = split.Length >= 2 ? split[1] : "scene"; + + _logger.Debug("SceneMapping origin is explicitly unknown, unsure what numbering scheme it uses but '{0}' will be assumed. Provide full release title to Sonarr/TheXEM team.", type); + } + + return Decision.Accept(); + } + } +} diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index c512114dd..caf4d0a08 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -99,8 +99,10 @@ namespace NzbDrone.Core.Download } Enum.TryParse(historyItem.Data.GetValueOrDefault(EpisodeHistory.SERIES_MATCH_TYPE, SeriesMatchType.Unknown.ToString()), out SeriesMatchType seriesMatchType); + Enum.TryParse(historyItem.Data.GetValueOrDefault(EpisodeHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource); - if (seriesMatchType == SeriesMatchType.Id) + // Show a warning if the release was matched by ID and the source is not interactive search + if (seriesMatchType == SeriesMatchType.Id && releaseSource != ReleaseSourceType.InteractiveSearch) { trackedDownload.Warn("Found matching series via grab history, but release was matched to series by ID. Automatic import is not possible."); return; diff --git a/src/NzbDrone.Core/Download/Pending/PendingRelease.cs b/src/NzbDrone.Core/Download/Pending/PendingRelease.cs index 16cc87874..77180c497 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingRelease.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingRelease.cs @@ -1,4 +1,4 @@ -using System; +using System; using NzbDrone.Core.Datastore; using NzbDrone.Core.Parser.Model; @@ -21,5 +21,6 @@ namespace NzbDrone.Core.Download.Pending public class PendingReleaseAdditionalInfo { public SeriesMatchType SeriesMatchType { get; set; } + public ReleaseSourceType ReleaseSource { get; set; } } } diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index 263a117bc..b859e91ff 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -313,6 +313,7 @@ namespace NzbDrone.Core.Download.Pending { Series = series, SeriesMatchType = release.AdditionalInfo?.SeriesMatchType ?? SeriesMatchType.Unknown, + ReleaseSource = release.AdditionalInfo?.ReleaseSource ?? ReleaseSourceType.Unknown, ParsedEpisodeInfo = release.ParsedEpisodeInfo, Release = release.Release }; diff --git a/src/NzbDrone.Core/History/EpisodeHistory.cs b/src/NzbDrone.Core/History/EpisodeHistory.cs index 3b3635f70..52c329ab9 100644 --- a/src/NzbDrone.Core/History/EpisodeHistory.cs +++ b/src/NzbDrone.Core/History/EpisodeHistory.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.History { public const string DOWNLOAD_CLIENT = "downloadClient"; public const string SERIES_MATCH_TYPE = "seriesMatchType"; + public const string RELEASE_SOURCE = "releaseSource"; public EpisodeHistory() { diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index 948f7c8ae..31cc01c27 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -168,6 +168,7 @@ namespace NzbDrone.Core.History history.Data.Add("Protocol", ((int)message.Episode.Release.DownloadProtocol).ToString()); history.Data.Add("CustomFormatScore", message.Episode.CustomFormatScore.ToString()); history.Data.Add("SeriesMatchType", message.Episode.SeriesMatchType.ToString()); + history.Data.Add("ReleaseSource", message.Episode.ReleaseSource.ToString()); if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs b/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs index 15f3c1fca..62e788ae7 100644 --- a/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs @@ -15,7 +15,6 @@ namespace NzbDrone.Core.Parser.Model public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } public SceneMapping SceneMapping { get; set; } public int MappedSeasonNumber { get; set; } - public Series Series { get; set; } public List Episodes { get; set; } public bool EpisodeRequested { get; set; } @@ -25,6 +24,7 @@ namespace NzbDrone.Core.Parser.Model public int CustomFormatScore { get; set; } public SeriesMatchType SeriesMatchType { get; set; } public List Languages { get; set; } + public ReleaseSourceType ReleaseSource { get; set; } public RemoteEpisode() { @@ -43,4 +43,14 @@ namespace NzbDrone.Core.Parser.Model return Release.Title; } } + + public enum ReleaseSourceType + { + Unknown = 0, + Rss = 1, + Search = 2, + UserInvokedSearch = 3, + InteractiveSearch = 4, + ReleasePush = 5 + } }