New: Don't block imports when release was matched by ID if they were grabbed via interactive search

Closes #5043
This commit is contained in:
Mark McDowall 2022-12-20 18:28:33 -08:00 committed by Mark McDowall
parent 599ad86657
commit bc2942c28d
9 changed files with 115 additions and 81 deletions

View File

@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
results.Should().NotBeEmpty(); results.Should().NotBeEmpty();
Mocker.GetMock<IMakeDownloadDecision>() Mocker.GetMock<IMakeDownloadDecision>()
.Verify(v => v.GetRssDecision(It.Is<List<ReleaseInfo>>(d => d.Count == 0)), Times.Never()); .Verify(v => v.GetRssDecision(It.Is<List<ReleaseInfo>>(d => d.Count == 0), It.IsAny<bool>()), Times.Never());
} }
[Test] [Test]

View File

@ -17,7 +17,7 @@ namespace NzbDrone.Core.DecisionEngine
{ {
public interface IMakeDownloadDecision public interface IMakeDownloadDecision
{ {
List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports); List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports, bool pushedRelease = false);
List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase); List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase);
} }
@ -45,17 +45,17 @@ namespace NzbDrone.Core.DecisionEngine
_logger = logger; _logger = logger;
} }
public List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports) public List<DownloadDecision> GetRssDecision(List<ReleaseInfo> reports, bool pushedRelease = false)
{ {
return GetDecisions(reports).ToList(); return GetDecisions(reports).ToList();
} }
public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase) public List<DownloadDecision> GetSearchDecision(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteriaBase)
{ {
return GetDecisions(reports, searchCriteriaBase).ToList(); return GetDecisions(reports, false, searchCriteriaBase).ToList();
} }
private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, SearchCriteriaBase searchCriteria = null) private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, bool pushedRelease = false, SearchCriteriaBase searchCriteria = null)
{ {
if (reports.Any()) if (reports.Any())
{ {
@ -156,6 +156,26 @@ namespace NzbDrone.Core.DecisionEngine
if (decision != null) 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()) if (decision.Rejections.Any())
{ {
_logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections)); _logger.Debug("Release rejected for the following reasons: {0}", string.Join(", ", decision.Rejections));

View File

@ -1,73 +1,71 @@
using System.Linq; using NLog;
using NLog; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Extensions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.DecisionEngine.Specifications
{
namespace NzbDrone.Core.DecisionEngine.Specifications.Search public class SceneMappingSpecification : IDecisionEngineSpecification
{ {
public class SceneMappingSpecification : IDecisionEngineSpecification private readonly Logger _logger;
{
private readonly Logger _logger; public SceneMappingSpecification(Logger logger)
{
public SceneMappingSpecification(Logger logger) _logger = logger;
{ }
_logger = logger;
} public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Temporary; // Temporary till there's a mapping
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Temporary; // Temporary till there's a mapping public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria) if (remoteEpisode.SceneMapping == null)
{ {
if (remoteEpisode.SceneMapping == null) _logger.Debug("No applicable scene mapping, skipping.");
{ return Decision.Accept();
_logger.Debug("No applicable scene mapping, skipping."); }
return Decision.Accept();
} if (remoteEpisode.SceneMapping.SceneOrigin.IsNullOrWhiteSpace())
{
if (remoteEpisode.SceneMapping.SceneOrigin.IsNullOrWhiteSpace()) _logger.Debug("No explicit scene origin in scene mapping.");
{ return Decision.Accept();
_logger.Debug("No explicit scene origin in scene mapping."); }
return Decision.Accept();
} var split = remoteEpisode.SceneMapping.SceneOrigin.Split(':');
var split = remoteEpisode.SceneMapping.SceneOrigin.Split(':'); var isInteractive = searchCriteria != null && searchCriteria.InteractiveSearch;
var isInteractive = searchCriteria != null && searchCriteria.InteractiveSearch; if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace())
{
if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace()) _logger.Debug("SceneMapping has origin {0} with comment '{1}'.", remoteEpisode.SceneMapping.SceneOrigin, remoteEpisode.SceneMapping.Comment);
{ }
_logger.Debug("SceneMapping has origin {0} with comment '{1}'.", remoteEpisode.SceneMapping.SceneOrigin, remoteEpisode.SceneMapping.Comment); else
} {
else _logger.Debug("SceneMapping has origin {0}.", remoteEpisode.SceneMapping.SceneOrigin);
{ }
_logger.Debug("SceneMapping has origin {0}.", remoteEpisode.SceneMapping.SceneOrigin);
} if (split[0] == "mixed")
{
if (split[0] == "mixed") _logger.Debug("SceneMapping origin is explicitly mixed, this means these were released with multiple unidentifiable numbering schemes.");
{
_logger.Debug("SceneMapping origin is explicitly mixed, this means these were released with multiple unidentifiable numbering schemes."); if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace())
{
if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace()) return Decision.Reject("{0} has ambiguous numbering");
{ }
return Decision.Reject("{0} has ambiguous numbering"); else
} {
else return Decision.Reject("Ambiguous numbering");
{ }
return Decision.Reject("Ambiguous numbering"); }
}
} if (split[0] == "unknown")
{
if (split[0] == "unknown") var type = split.Length >= 2 ? split[1] : "scene";
{
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);
}
_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();
}
return Decision.Accept(); }
} }
}
}

View File

@ -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.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."); trackedDownload.Warn("Found matching series via grab history, but release was matched to series by ID. Automatic import is not possible.");
return; return;

View File

@ -1,4 +1,4 @@
using System; using System;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -21,5 +21,6 @@ namespace NzbDrone.Core.Download.Pending
public class PendingReleaseAdditionalInfo public class PendingReleaseAdditionalInfo
{ {
public SeriesMatchType SeriesMatchType { get; set; } public SeriesMatchType SeriesMatchType { get; set; }
public ReleaseSourceType ReleaseSource { get; set; }
} }
} }

View File

@ -313,6 +313,7 @@ namespace NzbDrone.Core.Download.Pending
{ {
Series = series, Series = series,
SeriesMatchType = release.AdditionalInfo?.SeriesMatchType ?? SeriesMatchType.Unknown, SeriesMatchType = release.AdditionalInfo?.SeriesMatchType ?? SeriesMatchType.Unknown,
ReleaseSource = release.AdditionalInfo?.ReleaseSource ?? ReleaseSourceType.Unknown,
ParsedEpisodeInfo = release.ParsedEpisodeInfo, ParsedEpisodeInfo = release.ParsedEpisodeInfo,
Release = release.Release Release = release.Release
}; };

View File

@ -11,6 +11,7 @@ namespace NzbDrone.Core.History
{ {
public const string DOWNLOAD_CLIENT = "downloadClient"; public const string DOWNLOAD_CLIENT = "downloadClient";
public const string SERIES_MATCH_TYPE = "seriesMatchType"; public const string SERIES_MATCH_TYPE = "seriesMatchType";
public const string RELEASE_SOURCE = "releaseSource";
public EpisodeHistory() public EpisodeHistory()
{ {

View File

@ -168,6 +168,7 @@ namespace NzbDrone.Core.History
history.Data.Add("Protocol", ((int)message.Episode.Release.DownloadProtocol).ToString()); history.Data.Add("Protocol", ((int)message.Episode.Release.DownloadProtocol).ToString());
history.Data.Add("CustomFormatScore", message.Episode.CustomFormatScore.ToString()); history.Data.Add("CustomFormatScore", message.Episode.CustomFormatScore.ToString());
history.Data.Add("SeriesMatchType", message.Episode.SeriesMatchType.ToString()); history.Data.Add("SeriesMatchType", message.Episode.SeriesMatchType.ToString());
history.Data.Add("ReleaseSource", message.Episode.ReleaseSource.ToString());
if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace()) if (!message.Episode.ParsedEpisodeInfo.ReleaseHash.IsNullOrWhiteSpace())
{ {

View File

@ -15,7 +15,6 @@ namespace NzbDrone.Core.Parser.Model
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
public SceneMapping SceneMapping { get; set; } public SceneMapping SceneMapping { get; set; }
public int MappedSeasonNumber { get; set; } public int MappedSeasonNumber { get; set; }
public Series Series { get; set; } public Series Series { get; set; }
public List<Episode> Episodes { get; set; } public List<Episode> Episodes { get; set; }
public bool EpisodeRequested { get; set; } public bool EpisodeRequested { get; set; }
@ -25,6 +24,7 @@ namespace NzbDrone.Core.Parser.Model
public int CustomFormatScore { get; set; } public int CustomFormatScore { get; set; }
public SeriesMatchType SeriesMatchType { get; set; } public SeriesMatchType SeriesMatchType { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public ReleaseSourceType ReleaseSource { get; set; }
public RemoteEpisode() public RemoteEpisode()
{ {
@ -43,4 +43,14 @@ namespace NzbDrone.Core.Parser.Model
return Release.Title; return Release.Title;
} }
} }
public enum ReleaseSourceType
{
Unknown = 0,
Rss = 1,
Search = 2,
UserInvokedSearch = 3,
InteractiveSearch = 4,
ReleasePush = 5
}
} }