1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2024-12-23 02:05:27 +02:00

Add reason enum to decision engine rejections

This commit is contained in:
Mark McDowall 2024-11-11 10:03:18 -08:00
parent 160151c6e0
commit 8c67a3bdee
80 changed files with 634 additions and 458 deletions

View File

@ -22,38 +22,38 @@ public class DownloadDecisionMakerFixture : CoreTest<DownloadDecisionMaker>
private List<ReleaseInfo> _reports;
private RemoteEpisode _remoteEpisode;
private Mock<IDecisionEngineSpecification> _pass1;
private Mock<IDecisionEngineSpecification> _pass2;
private Mock<IDecisionEngineSpecification> _pass3;
private Mock<IDownloadDecisionEngineSpecification> _pass1;
private Mock<IDownloadDecisionEngineSpecification> _pass2;
private Mock<IDownloadDecisionEngineSpecification> _pass3;
private Mock<IDecisionEngineSpecification> _fail1;
private Mock<IDecisionEngineSpecification> _fail2;
private Mock<IDecisionEngineSpecification> _fail3;
private Mock<IDownloadDecisionEngineSpecification> _fail1;
private Mock<IDownloadDecisionEngineSpecification> _fail2;
private Mock<IDownloadDecisionEngineSpecification> _fail3;
private Mock<IDecisionEngineSpecification> _failDelayed1;
private Mock<IDownloadDecisionEngineSpecification> _failDelayed1;
[SetUp]
public void Setup()
{
_pass1 = new Mock<IDecisionEngineSpecification>();
_pass2 = new Mock<IDecisionEngineSpecification>();
_pass3 = new Mock<IDecisionEngineSpecification>();
_pass1 = new Mock<IDownloadDecisionEngineSpecification>();
_pass2 = new Mock<IDownloadDecisionEngineSpecification>();
_pass3 = new Mock<IDownloadDecisionEngineSpecification>();
_fail1 = new Mock<IDecisionEngineSpecification>();
_fail2 = new Mock<IDecisionEngineSpecification>();
_fail3 = new Mock<IDecisionEngineSpecification>();
_fail1 = new Mock<IDownloadDecisionEngineSpecification>();
_fail2 = new Mock<IDownloadDecisionEngineSpecification>();
_fail3 = new Mock<IDownloadDecisionEngineSpecification>();
_failDelayed1 = new Mock<IDecisionEngineSpecification>();
_failDelayed1 = new Mock<IDownloadDecisionEngineSpecification>();
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(Decision.Accept);
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(Decision.Accept);
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(Decision.Accept);
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(DownloadSpecDecision.Accept);
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(DownloadSpecDecision.Accept);
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(DownloadSpecDecision.Accept);
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(Decision.Reject("fail1"));
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(Decision.Reject("fail2"));
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(Decision.Reject("fail3"));
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail1"));
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail2"));
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.Unknown, "fail3"));
_failDelayed1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(Decision.Reject("failDelayed1"));
_failDelayed1.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>(), null)).Returns(DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumAgeDelay, "failDelayed1"));
_failDelayed1.SetupGet(c => c.Priority).Returns(SpecificationPriority.Disk);
_reports = new List<ReleaseInfo> { new ReleaseInfo { Title = "The.Office.S03E115.DVDRip.XviD-OSiTV" } };
@ -68,9 +68,9 @@ public void Setup()
.Returns(_remoteEpisode);
}
private void GivenSpecifications(params Mock<IDecisionEngineSpecification>[] mocks)
private void GivenSpecifications(params Mock<IDownloadDecisionEngineSpecification>[] mocks)
{
Mocker.SetConstant<IEnumerable<IDecisionEngineSpecification>>(mocks.Select(c => c.Object));
Mocker.SetConstant<IEnumerable<IDownloadDecisionEngineSpecification>>(mocks.Select(c => c.Object));
}
[Test]
@ -273,7 +273,7 @@ public void should_only_include_reports_for_requested_episodes()
Episodes = episodes.Where(v => v.SceneEpisodeNumber == p.EpisodeNumbers.First()).ToList()
});
Mocker.SetConstant<IEnumerable<IDecisionEngineSpecification>>(new List<IDecisionEngineSpecification>
Mocker.SetConstant<IEnumerable<IDownloadDecisionEngineSpecification>>(new List<IDownloadDecisionEngineSpecification>
{
Mocker.Resolve<NzbDrone.Core.DecisionEngine.Specifications.Search.EpisodeRequestedSpecification>()
});
@ -345,7 +345,7 @@ public void should_return_unknown_series_rejection_if_series_title_is_an_alias_f
var result = Subject.GetRssDecision(_reports);
result.Should().HaveCount(1);
result.First().Rejections.First().Reason.Should().Contain("12345");
result.First().Rejections.First().Message.Should().Contain("12345");
}
}
}

View File

@ -4,7 +4,6 @@
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.History;
@ -122,11 +121,11 @@ public void should_not_mark_as_imported_if_all_files_were_rejected()
{
new ImportResult(
new ImportDecision(
new LocalEpisode { Path = @"C:\TestPath\Droned.S01E01.mkv", Episodes = { _episode1 } }, new Rejection("Rejected!")), "Test Failure"),
new LocalEpisode { Path = @"C:\TestPath\Droned.S01E01.mkv", Episodes = { _episode1 } }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure"),
new ImportResult(
new ImportDecision(
new LocalEpisode { Path = @"C:\TestPath\Droned.S01E02.mkv", Episodes = { _episode2 } }, new Rejection("Rejected!")), "Test Failure")
new LocalEpisode { Path = @"C:\TestPath\Droned.S01E02.mkv", Episodes = { _episode2 } }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure")
});
Subject.Import(_trackedDownload);
@ -146,11 +145,11 @@ public void should_not_mark_as_imported_if_no_episodes_were_parsed()
{
new ImportResult(
new ImportDecision(
new LocalEpisode { Path = @"C:\TestPath\Droned.S01E01.mkv", Episodes = { _episode1 } }, new Rejection("Rejected!")), "Test Failure"),
new LocalEpisode { Path = @"C:\TestPath\Droned.S01E01.mkv", Episodes = { _episode1 } }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure"),
new ImportResult(
new ImportDecision(
new LocalEpisode { Path = @"C:\TestPath\Droned.S01E02.mkv", Episodes = { _episode2 } }, new Rejection("Rejected!")), "Test Failure")
new LocalEpisode { Path = @"C:\TestPath\Droned.S01E02.mkv", Episodes = { _episode2 } }, new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")), "Test Failure")
});
_trackedDownload.RemoteEpisode.Episodes.Clear();

View File

@ -186,8 +186,8 @@ public async Task should_not_add_to_downloaded_list_when_download_fails()
public void should_return_an_empty_list_when_none_are_approved()
{
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(null, new Rejection("Failure!")));
decisions.Add(new DownloadDecision(null, new Rejection("Failure!")));
decisions.Add(new DownloadDecision(null, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!")));
decisions.Add(new DownloadDecision(null, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!")));
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
}
@ -199,7 +199,7 @@ public async Task should_not_grab_if_pending()
var remoteEpisode = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p));
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
decisions.Add(new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteEpisode>(), null), Times.Never());
@ -213,7 +213,7 @@ public async Task should_not_add_to_pending_if_episode_was_grabbed()
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode));
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
decisions.Add(new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Never());
@ -226,8 +226,8 @@ public async Task should_add_to_pending_even_if_already_added_to_pending()
var remoteEpisode = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p));
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
decisions.Add(new DownloadDecision(remoteEpisode, new Rejection("Failure!", RejectionType.Temporary)));
decisions.Add(new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
decisions.Add(new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Unknown, "Failure!", RejectionType.Temporary)));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IPendingReleaseService>().Verify(v => v.AddMany(It.IsAny<List<Tuple<DownloadDecision, PendingReleaseReason>>>()), Times.Once());

View File

@ -63,7 +63,7 @@ public void Setup()
_remoteEpisode.ParsedEpisodeInfo = _parsedEpisodeInfo;
_remoteEpisode.Release = _release;
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary));
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
_heldReleases = new List<PendingRelease>();

View File

@ -64,7 +64,7 @@ public void Setup()
_remoteEpisode.ParsedEpisodeInfo = _parsedEpisodeInfo;
_remoteEpisode.Release = _release;
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary));
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
_heldReleases = new List<PendingRelease>();

View File

@ -63,7 +63,7 @@ public void Setup()
_remoteEpisode.ParsedEpisodeInfo = _parsedEpisodeInfo;
_remoteEpisode.Release = _release;
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new Rejection("Temp Rejected", RejectionType.Temporary));
_temporarilyRejected = new DownloadDecision(_remoteEpisode, new DownloadRejection(DownloadRejectionReason.MinimumAgeDelay, "Temp Rejected", RejectionType.Temporary));
Mocker.GetMock<IPendingReleaseRepository>()
.Setup(s => s.All())

View File

@ -7,7 +7,6 @@
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles;
@ -47,9 +46,9 @@ public void Setup()
var episodes = Builder<Episode>.CreateListOfSize(5)
.Build();
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new Rejection("Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
_rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), new ImportRejection(ImportRejectionReason.Unknown, "Rejected!")));
_rejectedDecisions.ForEach(r => r.LocalEpisode.FileEpisodeInfo = new ParsedEpisodeInfo());
foreach (var episode in episodes)

View File

@ -4,7 +4,6 @@
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
@ -46,13 +45,13 @@ public void Setup()
_fail2 = new Mock<IImportDecisionEngineSpecification>();
_fail3 = new Mock<IImportDecisionEngineSpecification>();
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Accept());
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
_pass3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Accept());
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail1"));
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail2"));
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(Decision.Reject("_fail3"));
_fail1.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail1"));
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail2"));
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<LocalEpisode>(), It.IsAny<DownloadClientItem>())).Returns(ImportSpecDecision.Reject(ImportRejectionReason.Unknown, "_fail3"));
_series = Builder<Series>.CreateNew()
.With(e => e.Path = @"C:\Test\Series".AsOsAgnostic())

View File

@ -1,32 +0,0 @@
namespace NzbDrone.Core.DecisionEngine
{
public class Decision
{
public bool Accepted { get; private set; }
public string Reason { get; private set; }
private static readonly Decision AcceptDecision = new Decision { Accepted = true };
private Decision()
{
}
public static Decision Accept()
{
return AcceptDecision;
}
public static Decision Reject(string reason, params object[] args)
{
return Reject(string.Format(reason, args));
}
public static Decision Reject(string reason)
{
return new Decision
{
Accepted = false,
Reason = reason
};
}
}
}

View File

@ -7,7 +7,7 @@ namespace NzbDrone.Core.DecisionEngine
public class DownloadDecision
{
public RemoteEpisode RemoteEpisode { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public IEnumerable<DownloadRejection> Rejections { get; private set; }
public bool Approved => !Rejections.Any();
@ -27,7 +27,7 @@ public bool Rejected
}
}
public DownloadDecision(RemoteEpisode episode, params Rejection[] rejections)
public DownloadDecision(RemoteEpisode episode, params DownloadRejection[] rejections)
{
RemoteEpisode = episode;
Rejections = rejections.ToList();

View File

@ -23,14 +23,14 @@ public interface IMakeDownloadDecision
public class DownloadDecisionMaker : IMakeDownloadDecision
{
private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
private readonly IEnumerable<IDownloadDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly IRemoteEpisodeAggregationService _aggregationService;
private readonly ISceneMappingService _sceneMappingService;
private readonly Logger _logger;
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications,
public DownloadDecisionMaker(IEnumerable<IDownloadDecisionEngineSpecification> specifications,
IParsingService parsingService,
ICustomFormatCalculationService formatService,
IRemoteEpisodeAggregationService aggregationService,
@ -95,19 +95,20 @@ private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, bo
if (remoteEpisode.Series == null)
{
var reason = "Unknown Series";
var matchingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle, parsedEpisodeInfo.SeasonNumber);
if (matchingTvdbId.HasValue)
{
reason = $"{parsedEpisodeInfo.SeriesTitle} matches an alias for series with TVDB ID: {matchingTvdbId}";
decision = new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.MatchesAnotherSeries, $"{parsedEpisodeInfo.SeriesTitle} matches an alias for series with TVDB ID: {matchingTvdbId}"));
}
else
{
decision = new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.UnknownSeries, "Unknown Series"));
}
decision = new DownloadDecision(remoteEpisode, new Rejection(reason));
}
else if (remoteEpisode.Episodes.Empty())
{
decision = new DownloadDecision(remoteEpisode, new Rejection("Unable to identify correct episode(s) using release name and scene mappings"));
decision = new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.UnknownEpisode, "Unable to identify correct episode(s) using release name and scene mappings"));
}
else
{
@ -141,7 +142,7 @@ private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, bo
Languages = parsedEpisodeInfo.Languages
};
decision = new DownloadDecision(remoteEpisode, new Rejection("Unable to parse release"));
decision = new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.UnableToParse, "Unable to parse release"));
}
}
}
@ -150,7 +151,7 @@ private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, bo
_logger.Error(e, "Couldn't process release.");
var remoteEpisode = new RemoteEpisode { Release = report };
decision = new DownloadDecision(remoteEpisode, new Rejection("Unexpected error processing release"));
decision = new DownloadDecision(remoteEpisode, new DownloadRejection(DownloadRejectionReason.Error, "Unexpected error processing release"));
}
reportNumber++;
@ -193,7 +194,7 @@ private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, bo
private DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria = null)
{
var reasons = Array.Empty<Rejection>();
var reasons = Array.Empty<DownloadRejection>();
foreach (var specifications in _specifications.GroupBy(v => v.Priority).OrderBy(v => v.Key))
{
@ -210,7 +211,7 @@ private DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, Searc
return new DownloadDecision(remoteEpisode, reasons.ToArray());
}
private Rejection EvaluateSpec(IDecisionEngineSpecification spec, RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteriaBase = null)
private DownloadRejection EvaluateSpec(IDownloadDecisionEngineSpecification spec, RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteriaBase = null)
{
try
{
@ -218,7 +219,7 @@ private Rejection EvaluateSpec(IDecisionEngineSpecification spec, RemoteEpisode
if (!result.Accepted)
{
return new Rejection(result.Reason, spec.Type);
return new DownloadRejection(result.Reason, result.Message, spec.Type);
}
}
catch (Exception e)
@ -226,7 +227,7 @@ private Rejection EvaluateSpec(IDecisionEngineSpecification spec, RemoteEpisode
e.Data.Add("report", remoteEpisode.Release.ToJson());
e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.Error(e, "Couldn't evaluate decision on {0}", remoteEpisode.Release.Title);
return new Rejection($"{spec.GetType().Name}: {e.Message}");
return new DownloadRejection(DownloadRejectionReason.DecisionError, $"{spec.GetType().Name}: {e.Message}");
}
return null;

View File

@ -0,0 +1,9 @@
namespace NzbDrone.Core.DecisionEngine;
public class DownloadRejection : Rejection<DownloadRejectionReason>
{
public DownloadRejection(DownloadRejectionReason reason, string message, RejectionType type = RejectionType.Permanent)
: base(reason, message, type)
{
}
}

View File

@ -0,0 +1,75 @@
namespace NzbDrone.Core.DecisionEngine;
public enum DownloadRejectionReason
{
Unknown,
UnknownSeries,
UnknownEpisode,
MatchesAnotherSeries,
UnableToParse,
Error,
DecisionError,
MinimumAgeDelay,
SeriesNotMonitored,
EpisodeNotMonitored,
HistoryRecentCutoffMet,
HistoryCdhDisabledCutoffMet,
HistoryHigherPreference,
HistoryHigherRevision,
HistoryCutoffMet,
HistoryCustomFormatCutoffMet,
HistoryCustomFormatScore,
HistoryCustomFormatScoreIncrement,
NoMatchingTag,
PropersDisabled,
ProperForOldFile,
WrongEpisode,
WrongSeason,
WrongSeries,
FullSeason,
UnknownRuntime,
BelowMinimumSize,
AboveMaximumSize,
AlreadyImportedSameHash,
AlreadyImportedSameName,
UnknownReleaseGroup,
ReleaseGroupDoesNotMatch,
IndexerDisabled,
Blocklisted,
CustomFormatMinimumScore,
MinimumFreeSpace,
FullSeasonNotAired,
MaximumSizeExceeded,
MinimumAge,
MaximumAge,
MultiSeason,
Sample,
ProtocolDisabled,
QualityNotWanted,
QualityUpgradesDisabled,
QueueHigherPreference,
QueueHigherRevision,
QueueCutoffMet,
QueueCustomFormatCutoffMet,
QueueCustomFormatScore,
QueueCustomFormatScoreIncrement,
QueueNoUpgrades,
QueuePropersDisabled,
Raw,
MustContainMissing,
MustNotContainPresent,
RepackDisabled,
RepackUnknownReleaseGroup,
RepackReleaseGroupDoesNotMatch,
ExistingFileHasMoreEpisodes,
AmbiguousNumbering,
NotSeasonPack,
SplitEpisode,
MinimumSeeders,
DiskHigherPreference,
DiskHigherRevision,
DiskCutoffMet,
DiskCustomFormatCutoffMet,
DiskCustomFormatScore,
DiskCustomFormatScoreIncrement,
}

View File

@ -0,0 +1,34 @@
namespace NzbDrone.Core.DecisionEngine
{
public class DownloadSpecDecision
{
public bool Accepted { get; private set; }
public DownloadRejectionReason Reason { get; set; }
public string Message { get; private set; }
private static readonly DownloadSpecDecision AcceptDownloadSpecDecision = new () { Accepted = true };
private DownloadSpecDecision()
{
}
public static DownloadSpecDecision Accept()
{
return AcceptDownloadSpecDecision;
}
public static DownloadSpecDecision Reject(DownloadRejectionReason reason, string message, params object[] args)
{
return Reject(reason, string.Format(message, args));
}
public static DownloadSpecDecision Reject(DownloadRejectionReason reason, string message)
{
return new DownloadSpecDecision
{
Accepted = false,
Reason = reason,
Message = message
};
}
}
}

View File

@ -1,19 +1,21 @@
namespace NzbDrone.Core.DecisionEngine
{
public class Rejection
public class Rejection<TRejectionReason>
{
public string Reason { get; set; }
public TRejectionReason Reason { get; set; }
public string Message { get; set; }
public RejectionType Type { get; set; }
public Rejection(string reason, RejectionType type = RejectionType.Permanent)
public Rejection(TRejectionReason reason, string message, RejectionType type = RejectionType.Permanent)
{
Reason = reason;
Message = message;
Type = type;
}
public override string ToString()
{
return string.Format("[{0}] {1}", Type, Reason);
return string.Format("[{0}] {1}", Type, Message);
}
}
}

View File

@ -8,7 +8,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class AcceptableSizeSpecification : IDecisionEngineSpecification
public class AcceptableSizeSpecification : IDownloadDecisionEngineSpecification
{
private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly IEpisodeService _episodeService;
@ -24,7 +24,7 @@ public AcceptableSizeSpecification(IQualityDefinitionService qualityDefinitionSe
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
_logger.Debug("Beginning size check for: {0}", subject);
@ -33,13 +33,13 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (subject.ParsedEpisodeInfo.Special)
{
_logger.Debug("Special release found, skipping size check.");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (subject.Release.Size == 0)
{
_logger.Debug("Release has unknown size, skipping size check");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var seriesRuntime = subject.Series.Runtime;
@ -75,7 +75,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (runtime == 0)
{
_logger.Debug("Runtime of all episodes is 0, unable to validate size until it is available, rejecting");
return Decision.Reject("Runtime of all episodes is 0, unable to validate size until it is available");
return DownloadSpecDecision.Reject(DownloadRejectionReason.UnknownRuntime, "Runtime of all episodes is 0, unable to validate size until it is available");
}
var qualityDefinition = _qualityDefinitionService.Get(quality);
@ -93,7 +93,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
var runtimeMessage = subject.Episodes.Count == 1 ? $"{runtime}min" : $"{subject.Episodes.Count}x {runtime}min";
_logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2} bytes for {3}), rejecting.", subject, subject.Release.Size, minSize, runtimeMessage);
return Decision.Reject("{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage);
return DownloadSpecDecision.Reject(DownloadRejectionReason.BelowMinimumSize, "{0} is smaller than minimum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix(), runtimeMessage);
}
}
@ -114,12 +114,12 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
var runtimeMessage = subject.Episodes.Count == 1 ? $"{runtime}min" : $"{subject.Episodes.Count}x {runtime}min";
_logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2} for {3}), rejecting", subject, subject.Release.Size, maxSize, runtimeMessage);
return Decision.Reject("{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), runtimeMessage);
return DownloadSpecDecision.Reject(DownloadRejectionReason.AboveMaximumSize, "{0} is larger than maximum allowed {1} (for {2})", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix(), runtimeMessage);
}
}
_logger.Debug("Item: {0}, meets size constraints", subject);
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -9,7 +9,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class AlreadyImportedSpecification : IDecisionEngineSpecification
public class AlreadyImportedSpecification : IDownloadDecisionEngineSpecification
{
private readonly IHistoryService _historyService;
private readonly IConfigService _configService;
@ -27,14 +27,14 @@ public AlreadyImportedSpecification(IHistoryService historyService,
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
if (!cdhEnabled)
{
_logger.Debug("Skipping already imported check because CDH is disabled");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
_logger.Debug("Performing already imported check on report");
@ -80,7 +80,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (torrentInfo?.InfoHash != null && torrentInfo.InfoHash.ToUpper() == lastGrabbed.DownloadId)
{
_logger.Debug("Has same torrent hash as a grabbed and imported release");
return Decision.Reject("Has same torrent hash as a grabbed and imported release");
return DownloadSpecDecision.Reject(DownloadRejectionReason.AlreadyImportedSameHash, "Has same torrent hash as a grabbed and imported release");
}
}
@ -90,11 +90,11 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (release.Title.Equals(lastGrabbed.SourceTitle, StringComparison.InvariantCultureIgnoreCase))
{
_logger.Debug("Has same release name as a grabbed and imported release");
return Decision.Reject("Has same release name as a grabbed and imported release");
return DownloadSpecDecision.Reject(DownloadRejectionReason.AlreadyImportedSameName, "Has same release name as a grabbed and imported release");
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -9,7 +9,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class AnimeVersionUpgradeSpecification : IDecisionEngineSpecification
public class AnimeVersionUpgradeSpecification : IDownloadDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IConfigService _configService;
@ -25,13 +25,13 @@ public AnimeVersionUpgradeSpecification(UpgradableSpecification upgradableSpecif
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var releaseGroup = subject.ParsedEpisodeInfo.ReleaseGroup;
if (subject.Series.SeriesType != SeriesTypes.Anime)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
@ -39,7 +39,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
{
_logger.Debug("Version upgrades are not preferred, skipping check");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
@ -54,24 +54,24 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (file.ReleaseGroup.IsNullOrWhiteSpace())
{
_logger.Debug("Unable to compare release group, existing file's release group is unknown");
return Decision.Reject("Existing release group is unknown");
return DownloadSpecDecision.Reject(DownloadRejectionReason.UnknownReleaseGroup, "Existing release group is unknown");
}
if (releaseGroup.IsNullOrWhiteSpace())
{
_logger.Debug("Unable to compare release group, release's release group is unknown");
return Decision.Reject("Release group is unknown");
return DownloadSpecDecision.Reject(DownloadRejectionReason.UnknownReleaseGroup, "Release group is unknown");
}
if (file.ReleaseGroup != releaseGroup)
{
_logger.Debug("Existing Release group is: {0} - release's release group is: {1}", file.ReleaseGroup, releaseGroup);
return Decision.Reject("{0} does not match existing release group {1}", releaseGroup, file.ReleaseGroup);
return DownloadSpecDecision.Reject(DownloadRejectionReason.ReleaseGroupDoesNotMatch, "{0} does not match existing release group {1}", releaseGroup, file.ReleaseGroup);
}
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -9,7 +9,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class BlockedIndexerSpecification : IDecisionEngineSpecification
public class BlockedIndexerSpecification : IDownloadDecisionEngineSpecification
{
private readonly IIndexerStatusService _indexerStatusService;
private readonly Logger _logger;
@ -27,15 +27,15 @@ public BlockedIndexerSpecification(IIndexerStatusService indexerStatusService, I
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Temporary;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var status = _blockedIndexerCache.Find(subject.Release.IndexerId.ToString());
if (status != null)
{
return Decision.Reject($"Indexer {subject.Release.Indexer} is blocked till {status.DisabledTill} due to failures, cannot grab release.");
return DownloadSpecDecision.Reject(DownloadRejectionReason.IndexerDisabled, $"Indexer {subject.Release.Indexer} is blocked till {status.DisabledTill} due to failures, cannot grab release.");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
private IDictionary<string, IndexerStatus> FetchBlockedIndexer()

View File

@ -5,7 +5,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class BlocklistSpecification : IDecisionEngineSpecification
public class BlocklistSpecification : IDownloadDecisionEngineSpecification
{
private readonly IBlocklistService _blocklistService;
private readonly Logger _logger;
@ -19,15 +19,15 @@ public BlocklistSpecification(IBlocklistService blocklistService, Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (_blocklistService.Blocklisted(subject.Series.Id, subject.Release))
{
_logger.Debug("{0} is blocklisted, rejecting.", subject.Release.Title);
return Decision.Reject("Release is blocklisted");
return DownloadSpecDecision.Reject(DownloadRejectionReason.Blocklisted, "Release is blocklisted");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -4,22 +4,22 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class CustomFormatAllowedbyProfileSpecification : IDecisionEngineSpecification
public class CustomFormatAllowedbyProfileSpecification : IDownloadDecisionEngineSpecification
{
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var minScore = subject.Series.QualityProfile.Value.MinFormatScore;
var score = subject.CustomFormatScore;
if (score < minScore)
{
return Decision.Reject("Custom Formats {0} have score {1} below Series profile minimum {2}", subject.CustomFormats.ConcatToString(), score, minScore);
return DownloadSpecDecision.Reject(DownloadRejectionReason.CustomFormatMinimumScore, "Custom Formats {0} have score {1} below Series profile minimum {2}", subject.CustomFormats.ConcatToString(), score, minScore);
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -8,7 +8,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class FreeSpaceSpecification : IDecisionEngineSpecification
public class FreeSpaceSpecification : IDownloadDecisionEngineSpecification
{
private readonly IConfigService _configService;
private readonly IDiskProvider _diskProvider;
@ -24,12 +24,12 @@ public FreeSpaceSpecification(IConfigService configService, IDiskProvider diskPr
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (_configService.SkipFreeSpaceCheckWhenImporting)
{
_logger.Debug("Skipping free space check");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var size = subject.Release.Size;
@ -49,7 +49,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
{
_logger.Debug("Unable to get available space for {0}. Skipping", path);
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var minimumSpace = _configService.MinimumFreeSpaceWhenImporting.Megabytes();
@ -60,7 +60,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
var message = "Importing after download will exceed available disk space";
_logger.Debug(message);
return Decision.Reject(message);
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumFreeSpace, message);
}
if (remainingSpace < minimumSpace)
@ -68,10 +68,10 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
var message = $"Not enough free space ({minimumSpace.SizeSuffix()}) to import after download: {remainingSpace.SizeSuffix()}. (Settings: Media Management: Minimum Free Space)";
_logger.Debug(message);
return Decision.Reject(message);
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumFreeSpace, message);
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -7,7 +7,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class FullSeasonSpecification : IDecisionEngineSpecification
public class FullSeasonSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -19,7 +19,7 @@ public FullSeasonSpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.ParsedEpisodeInfo.FullSeason)
{
@ -28,11 +28,11 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (subject.Episodes.Any(e => !e.AirDateUtc.HasValue || e.AirDateUtc.Value.After(DateTime.UtcNow.AddHours(24))))
{
_logger.Debug("Full season release {0} rejected. All episodes haven't aired yet.", subject.Release.Title);
return Decision.Reject("Full season release rejected. All episodes haven't aired yet.");
return DownloadSpecDecision.Reject(DownloadRejectionReason.FullSeasonNotAired, "Full season release rejected. All episodes haven't aired yet.");
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -3,12 +3,12 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public interface IDecisionEngineSpecification
public interface IDownloadDecisionEngineSpecification
{
RejectionType Type { get; }
SpecificationPriority Priority { get; }
Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria);
DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria);
}
}

View File

@ -6,7 +6,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class MaximumSizeSpecification : IDecisionEngineSpecification
public class MaximumSizeSpecification : IDownloadDecisionEngineSpecification
{
private readonly IConfigService _configService;
private readonly Logger _logger;
@ -20,7 +20,7 @@ public MaximumSizeSpecification(IConfigService configService, Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var size = subject.Release.Size;
var maximumSize = _configService.MaximumSize.Megabytes();
@ -28,13 +28,13 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (maximumSize == 0)
{
_logger.Debug("Maximum size is not set.");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (size == 0)
{
_logger.Debug("Release has unknown size, skipping size check.");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
_logger.Debug("Checking if release meets maximum size requirements. {0}", size.SizeSuffix());
@ -44,10 +44,10 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
var message = $"{size.SizeSuffix()} is too big, maximum size is {maximumSize.SizeSuffix()} (Settings->Indexers->Maximum Size)";
_logger.Debug(message);
return Decision.Reject(message);
return DownloadSpecDecision.Reject(DownloadRejectionReason.MaximumSizeExceeded, message);
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -6,7 +6,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class MinimumAgeSpecification : IDecisionEngineSpecification
public class MinimumAgeSpecification : IDownloadDecisionEngineSpecification
{
private readonly IConfigService _configService;
private readonly Logger _logger;
@ -20,12 +20,12 @@ public MinimumAgeSpecification(IConfigService configService, Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Temporary;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
{
_logger.Debug("Not checking minimum age requirement for non-usenet report");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var age = subject.Release.AgeMinutes;
@ -35,7 +35,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (minimumAge == 0)
{
_logger.Debug("Minimum age is not set.");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
_logger.Debug("Checking if report meets minimum age requirements. {0}", ageRounded);
@ -43,12 +43,12 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (age < minimumAge)
{
_logger.Debug("Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
return Decision.Reject("Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumAge, "Only {0} minutes old, minimum age is {1} minutes", ageRounded, minimumAge);
}
_logger.Debug("Release is {0} minutes old, greater than minimum age of {1} minutes", ageRounded, minimumAge);
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -4,7 +4,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class MultiSeasonSpecification : IDecisionEngineSpecification
public class MultiSeasonSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -16,15 +16,15 @@ public MultiSeasonSpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.ParsedEpisodeInfo.IsMultiSeason)
{
_logger.Debug("Multi-season release {0} rejected. Not supported", subject.Release.Title);
return Decision.Reject("Multi-season releases are not supported");
return DownloadSpecDecision.Reject(DownloadRejectionReason.MultiSeason, "Multi-season releases are not supported");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -5,7 +5,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class NotSampleSpecification : IDecisionEngineSpecification
public class NotSampleSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -17,15 +17,15 @@ public NotSampleSpecification(Logger logger)
_logger = logger;
}
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes())
{
_logger.Debug("Sample release, rejecting.");
return Decision.Reject("Sample");
return DownloadSpecDecision.Reject(DownloadRejectionReason.Sample, "Sample");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -6,7 +6,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class ProtocolSpecification : IDecisionEngineSpecification
public class ProtocolSpecification : IDownloadDecisionEngineSpecification
{
private readonly IDelayProfileService _delayProfileService;
private readonly Logger _logger;
@ -21,23 +21,23 @@ public ProtocolSpecification(IDelayProfileService delayProfileService,
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var delayProfile = _delayProfileService.BestForTags(subject.Series.Tags);
if (subject.Release.DownloadProtocol == DownloadProtocol.Usenet && !delayProfile.EnableUsenet)
{
_logger.Debug("[{0}] Usenet is not enabled for this series", subject.Release.Title);
return Decision.Reject("Usenet is not enabled for this series");
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProtocolDisabled, "Usenet is not enabled for this series");
}
if (subject.Release.DownloadProtocol == DownloadProtocol.Torrent && !delayProfile.EnableTorrent)
{
_logger.Debug("[{0}] Torrent is not enabled for this series", subject.Release.Title);
return Decision.Reject("Torrent is not enabled for this series");
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProtocolDisabled, "Torrent is not enabled for this series");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -4,7 +4,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class QualityAllowedByProfileSpecification : IDecisionEngineSpecification
public class QualityAllowedByProfileSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -16,7 +16,7 @@ public QualityAllowedByProfileSpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
_logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedEpisodeInfo.Quality);
@ -27,10 +27,10 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (!qualityOrGroup.Allowed)
{
_logger.Debug("Quality {0} rejected by Series' quality profile", subject.ParsedEpisodeInfo.Quality);
return Decision.Reject("{0} is not wanted in profile", subject.ParsedEpisodeInfo.Quality.Quality);
return DownloadSpecDecision.Reject(DownloadRejectionReason.QualityNotWanted, "{0} is not wanted in profile", subject.ParsedEpisodeInfo.Quality.Quality);
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -10,7 +10,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class QueueSpecification : IDecisionEngineSpecification
public class QueueSpecification : IDownloadDecisionEngineSpecification
{
private readonly IQueueService _queueService;
private readonly UpgradableSpecification _upgradableSpecification;
@ -34,7 +34,7 @@ public QueueSpecification(IQueueService queueService,
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var queue = _queueService.GetQueue();
var matchingEpisode = queue.Where(q => q.RemoteEpisode?.Series != null &&
@ -65,7 +65,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
queuedItemCustomFormats,
subject.ParsedEpisodeInfo.Quality))
{
return Decision.Reject("Release in queue already meets cutoff: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCutoffMet, "Release in queue already meets cutoff: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
}
_logger.Debug("Checking if release is higher quality than queued release. Queued: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
@ -79,22 +79,22 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
switch (upgradeableRejectReason)
{
case UpgradeableRejectReason.BetterQuality:
return Decision.Reject("Release in queue is of equal or higher preference: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueHigherPreference, "Release in queue is of equal or higher preference: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
case UpgradeableRejectReason.BetterRevision:
return Decision.Reject("Release in queue is of equal or higher revision: {0}", remoteEpisode.ParsedEpisodeInfo.Quality.Revision);
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueHigherRevision, "Release in queue is of equal or higher revision: {0}", remoteEpisode.ParsedEpisodeInfo.Quality.Revision);
case UpgradeableRejectReason.QualityCutoff:
return Decision.Reject("Release in queue meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCutoffMet, "Release in queue meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
case UpgradeableRejectReason.CustomFormatCutoff:
return Decision.Reject("Release in queue meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatCutoffMet, "Release in queue meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
case UpgradeableRejectReason.CustomFormatScore:
return Decision.Reject("Release in queue has an equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(queuedItemCustomFormats));
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatScore, "Release in queue has an equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(queuedItemCustomFormats));
case UpgradeableRejectReason.MinCustomFormatScore:
return Decision.Reject("Release in queue has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueCustomFormatScoreIncrement, "Release in queue has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
}
_logger.Debug("Checking if profiles allow upgrading. Queued: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
@ -105,7 +105,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
subject.ParsedEpisodeInfo.Quality,
subject.CustomFormats))
{
return Decision.Reject("Another release is queued and the Quality profile does not allow upgrades");
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueueNoUpgrades, "Another release is queued and the Quality profile does not allow upgrades");
}
if (_upgradableSpecification.IsRevisionUpgrade(remoteEpisode.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality))
@ -113,12 +113,12 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
{
_logger.Debug("Auto downloading of propers is disabled");
return Decision.Reject("Proper downloading is disabled");
return DownloadSpecDecision.Reject(DownloadRejectionReason.QueuePropersDisabled, "Proper downloading is disabled");
}
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -7,7 +7,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class RawDiskSpecification : IDecisionEngineSpecification
public class RawDiskSpecification : IDownloadDecisionEngineSpecification
{
private static readonly Regex[] DiscRegex = new[]
{
@ -29,11 +29,11 @@ public RawDiskSpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.Release == null)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
foreach (var regex in DiscRegex)
@ -41,28 +41,28 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (regex.IsMatch(subject.Release.Title))
{
_logger.Debug("Release contains raw Bluray/DVD, rejecting.");
return Decision.Reject("Raw Bluray/DVD release");
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw Bluray/DVD release");
}
}
if (subject.Release.Container.IsNullOrWhiteSpace())
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (_dvdContainerTypes.Contains(subject.Release.Container.ToLower()))
{
_logger.Debug("Release contains raw DVD, rejecting.");
return Decision.Reject("Raw DVD release");
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw DVD release");
}
if (_blurayContainerTypes.Contains(subject.Release.Container.ToLower()))
{
_logger.Debug("Release contains raw Bluray, rejecting.");
return Decision.Reject("Raw Bluray release");
return DownloadSpecDecision.Reject(DownloadRejectionReason.Raw, "Raw Bluray release");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -8,7 +8,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
public class ReleaseRestrictionsSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
private readonly IReleaseProfileService _releaseProfileService;
@ -24,7 +24,7 @@ public ReleaseRestrictionsSpecification(ITermMatcherService termMatcherService,
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
_logger.Debug("Checking if release meets restrictions: {0}", subject);
@ -43,7 +43,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
{
var terms = string.Join(", ", requiredTerms);
_logger.Debug("[{0}] does not contain one of the required terms: {1}", title, terms);
return Decision.Reject("Does not contain one of the required terms: {0}", terms);
return DownloadSpecDecision.Reject(DownloadRejectionReason.MustContainMissing, "Does not contain one of the required terms: {0}", terms);
}
}
@ -56,12 +56,12 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
{
var terms = string.Join(", ", foundTerms);
_logger.Debug("[{0}] contains these ignored terms: {1}", title, terms);
return Decision.Reject("Contains these ignored terms: {0}", terms);
return DownloadSpecDecision.Reject(DownloadRejectionReason.MustNotContainPresent, "Contains these ignored terms: {0}", terms);
}
}
_logger.Debug("[{0}] No restrictions apply, allowing", subject);
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
private List<string> ContainsAny(List<string> terms, string title)

View File

@ -9,7 +9,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class RepackSpecification : IDecisionEngineSpecification
public class RepackSpecification : IDownloadDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IConfigService _configService;
@ -25,11 +25,11 @@ public RepackSpecification(UpgradableSpecification upgradableSpecification, ICon
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (!subject.ParsedEpisodeInfo.Quality.Revision.IsRepack)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
@ -37,7 +37,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
{
_logger.Debug("Repacks are not preferred, skipping check");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
@ -47,7 +47,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
{
_logger.Debug("Auto downloading of repacks is disabled");
return Decision.Reject("Repack downloading is disabled");
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackDisabled, "Repack downloading is disabled");
}
var releaseGroup = subject.ParsedEpisodeInfo.ReleaseGroup;
@ -55,12 +55,12 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
if (fileReleaseGroup.IsNullOrWhiteSpace())
{
return Decision.Reject("Unable to determine release group for the existing file");
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackUnknownReleaseGroup, "Unable to determine release group for the existing file");
}
if (releaseGroup.IsNullOrWhiteSpace())
{
return Decision.Reject("Unable to determine release group for this release");
return DownloadSpecDecision.Reject(DownloadRejectionReason.RepackUnknownReleaseGroup, "Unable to determine release group for this release");
}
if (!fileReleaseGroup.Equals(releaseGroup, StringComparison.InvariantCultureIgnoreCase))
@ -69,7 +69,8 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
"Release is a repack for a different release group. Release Group: {0}. File release group: {1}",
releaseGroup,
fileReleaseGroup);
return Decision.Reject(
return DownloadSpecDecision.Reject(
DownloadRejectionReason.RepackReleaseGroupDoesNotMatch,
"Release is a repack for a different release group. Release Group: {0}. File release group: {1}",
releaseGroup,
fileReleaseGroup);
@ -77,7 +78,7 @@ public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCr
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -5,7 +5,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class RetentionSpecification : IDecisionEngineSpecification
public class RetentionSpecification : IDownloadDecisionEngineSpecification
{
private readonly IConfigService _configService;
private readonly Logger _logger;
@ -19,12 +19,12 @@ public RetentionSpecification(IConfigService configService, Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
{
_logger.Debug("Not checking retention requirement for non-usenet report");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var age = subject.Release.Age;
@ -34,10 +34,10 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (retention > 0 && age > retention)
{
_logger.Debug("Report age: {0} rejected by user's retention limit", age);
return Decision.Reject("Older than configured retention");
return DownloadSpecDecision.Reject(DownloadRejectionReason.MaximumAge, "Older than configured retention");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -8,7 +8,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
public class DelaySpecification : IDecisionEngineSpecification
public class DelaySpecification : IDownloadDecisionEngineSpecification
{
private readonly IPendingReleaseService _pendingReleaseService;
private readonly IDelayProfileService _delayProfileService;
@ -26,12 +26,12 @@ public DelaySpecification(IPendingReleaseService pendingReleaseService,
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Temporary;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null && searchCriteria.UserInvokedSearch)
{
_logger.Debug("Ignoring delay for user invoked search");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var qualityProfile = subject.Series.QualityProfile.Value;
@ -42,7 +42,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (delay == 0)
{
_logger.Debug("QualityProfile does not require a waiting period before download for {0}.", subject.Release.DownloadProtocol);
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var qualityComparer = new QualityModelComparer(qualityProfile);
@ -58,7 +58,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (qualityCompare == 0 && newQuality?.Revision.CompareTo(currentQuality.Revision) > 0)
{
_logger.Debug("New quality is a better revision for existing quality, skipping delay");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}
@ -72,7 +72,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (isBestInProfile && isPreferredProtocol)
{
_logger.Debug("Quality is highest in profile for preferred protocol, will not delay");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
@ -85,7 +85,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (score >= minimum && isPreferredProtocol)
{
_logger.Debug("Custom format score ({0}) meets minimum ({1}) for preferred protocol, will not delay", score, minimum);
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
@ -95,16 +95,16 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (oldest != null && oldest.Release.AgeMinutes > delay)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (subject.Release.AgeMinutes < delay)
{
_logger.Debug("Waiting for better quality release, There is a {0} minute delay on {1}", delay, subject.Release.DownloadProtocol);
return Decision.Reject("Waiting for better quality release");
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumAgeDelay, "Waiting for better quality release");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -10,7 +10,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
public class DeletedEpisodeFileSpecification : IDecisionEngineSpecification
public class DeletedEpisodeFileSpecification : IDownloadDecisionEngineSpecification
{
private readonly IDiskProvider _diskProvider;
private readonly IConfigService _configService;
@ -26,17 +26,17 @@ public DeletedEpisodeFileSpecification(IDiskProvider diskProvider, IConfigServic
public SpecificationPriority Priority => SpecificationPriority.Disk;
public RejectionType Type => RejectionType.Temporary;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (!_configService.AutoUnmonitorPreviouslyDownloadedEpisodes)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (searchCriteria != null)
{
_logger.Debug("Skipping deleted episodefile check during search");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var missingEpisodeFiles = subject.Episodes
@ -54,10 +54,10 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
}
_logger.Debug("Files for this episode exist in the database but not on disk, will be unmonitored on next diskscan. skipping.");
return Decision.Reject("Series is not monitored");
return DownloadSpecDecision.Reject(DownloadRejectionReason.SeriesNotMonitored, "Series is not monitored");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
private bool IsEpisodeFileMissing(Series series, EpisodeFile episodeFile)

View File

@ -9,7 +9,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
public class HistorySpecification : IDecisionEngineSpecification
public class HistorySpecification : IDownloadDecisionEngineSpecification
{
private readonly IHistoryService _historyService;
private readonly UpgradableSpecification _upgradableSpecification;
@ -33,12 +33,12 @@ public HistorySpecification(IHistoryService historyService,
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null)
{
_logger.Debug("Skipping history check during search");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
@ -81,10 +81,10 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
{
if (recent)
{
return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryRecentCutoffMet, "Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
}
return Decision.Reject("CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality);
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCdhDisabledCutoffMet, "CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality);
}
var rejectionSubject = recent ? "Recent" : "CDH is disabled and";
@ -95,27 +95,27 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
continue;
case UpgradeableRejectReason.BetterQuality:
return Decision.Reject("{0} grab event in history is of equal or higher preference: {1}", rejectionSubject, mostRecent.Quality);
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryHigherPreference, "{0} grab event in history is of equal or higher preference: {1}", rejectionSubject, mostRecent.Quality);
case UpgradeableRejectReason.BetterRevision:
return Decision.Reject("{0} grab event in history is of equal or higher revision: {1}", rejectionSubject, mostRecent.Quality.Revision);
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryHigherRevision, "{0} grab event in history is of equal or higher revision: {1}", rejectionSubject, mostRecent.Quality.Revision);
case UpgradeableRejectReason.QualityCutoff:
return Decision.Reject("{0} grab event in history meets quality cutoff: {1}", rejectionSubject, qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCutoffMet, "{0} grab event in history meets quality cutoff: {1}", rejectionSubject, qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
case UpgradeableRejectReason.CustomFormatCutoff:
return Decision.Reject("{0} grab event in history meets Custom Format cutoff: {1}", rejectionSubject, qualityProfile.CutoffFormatScore);
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatCutoffMet, "{0} grab event in history meets Custom Format cutoff: {1}", rejectionSubject, qualityProfile.CutoffFormatScore);
case UpgradeableRejectReason.CustomFormatScore:
return Decision.Reject("{0} grab event in history has an equal or higher Custom Format score: {1}", rejectionSubject, qualityProfile.CalculateCustomFormatScore(customFormats));
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatScore, "{0} grab event in history has an equal or higher Custom Format score: {1}", rejectionSubject, qualityProfile.CalculateCustomFormatScore(customFormats));
case UpgradeableRejectReason.MinCustomFormatScore:
return Decision.Reject("{0} grab event in history has Custom Format score within Custom Format score increment: {1}", rejectionSubject, qualityProfile.MinUpgradeFormatScore);
return DownloadSpecDecision.Reject(DownloadRejectionReason.HistoryCustomFormatScoreIncrement, "{0} grab event in history has Custom Format score within Custom Format score increment: {1}", rejectionSubject, qualityProfile.MinUpgradeFormatScore);
}
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -8,7 +8,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
public class IndexerTagSpecification : IDecisionEngineSpecification
public class IndexerTagSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
private readonly IIndexerFactory _indexerFactory;
@ -22,11 +22,11 @@ public IndexerTagSpecification(Logger logger, IIndexerFactory indexerFactory)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.Release == null || subject.Series?.Tags == null || subject.Release.IndexerId == 0)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
IndexerDefinition indexer;
@ -37,7 +37,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
catch (ModelNotFoundException)
{
_logger.Debug("Indexer with id {0} does not exist, skipping indexer tags check", subject.Release.IndexerId);
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
// If indexer has tags, check that at least one of them is present on the series
@ -47,10 +47,10 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
{
_logger.Debug("Indexer {0} has tags. None of these are present on series {1}. Rejecting", subject.Release.Indexer, subject.Series);
return Decision.Reject("Series tags do not match any of the indexer tags");
return DownloadSpecDecision.Reject(DownloadRejectionReason.NoMatchingTag, "Series tags do not match any of the indexer tags");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -5,7 +5,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
public class MonitoredEpisodeSpecification : IDecisionEngineSpecification
public class MonitoredEpisodeSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -17,33 +17,33 @@ public MonitoredEpisodeSpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null)
{
if (!searchCriteria.MonitoredEpisodesOnly)
{
_logger.Debug("Skipping monitored check during search");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
if (!subject.Series.Monitored)
{
_logger.Debug("{0} is present in the DB but not tracked. Rejecting", subject.Series);
return Decision.Reject("Series is not monitored");
return DownloadSpecDecision.Reject(DownloadRejectionReason.SeriesNotMonitored, "Series is not monitored");
}
var monitoredCount = subject.Episodes.Count(episode => episode.Monitored);
if (monitoredCount == subject.Episodes.Count)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (subject.Episodes.Count == 1)
{
_logger.Debug("Episode is not monitored. Rejecting", monitoredCount, subject.Episodes.Count);
return Decision.Reject("Episode is not monitored");
return DownloadSpecDecision.Reject(DownloadRejectionReason.EpisodeNotMonitored, "Episode is not monitored");
}
if (monitoredCount == 0)
@ -55,7 +55,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
_logger.Debug("Only {0}/{1} episodes in the release are monitored. Rejecting", monitoredCount, subject.Episodes.Count);
}
return Decision.Reject("One or more episodes is not monitored");
return DownloadSpecDecision.Reject(DownloadRejectionReason.EpisodeNotMonitored, "One or more episodes is not monitored");
}
}
}

View File

@ -8,7 +8,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
public class ProperSpecification : IDecisionEngineSpecification
public class ProperSpecification : IDownloadDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IConfigService _configService;
@ -24,11 +24,11 @@ public ProperSpecification(UpgradableSpecification upgradableSpecification, ICon
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria != null)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
@ -36,7 +36,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)
{
_logger.Debug("Propers are not preferred, skipping check");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
@ -46,18 +46,18 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade)
{
_logger.Debug("Auto downloading of propers is disabled");
return Decision.Reject("Proper downloading is disabled");
return DownloadSpecDecision.Reject(DownloadRejectionReason.PropersDisabled, "Proper downloading is disabled");
}
if (file.DateAdded < DateTime.Today.AddDays(-7))
{
_logger.Debug("Proper for old file, rejecting: {0}", subject);
return Decision.Reject("Proper for old file");
return DownloadSpecDecision.Reject(DownloadRejectionReason.ProperForOldFile, "Proper for old file");
}
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -4,7 +4,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class SameEpisodesGrabSpecification : IDecisionEngineSpecification
public class SameEpisodesGrabSpecification : IDownloadDecisionEngineSpecification
{
private readonly SameEpisodesSpecification _sameEpisodesSpecification;
private readonly Logger _logger;
@ -18,15 +18,15 @@ public SameEpisodesGrabSpecification(SameEpisodesSpecification sameEpisodesSpeci
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (_sameEpisodesSpecification.IsSatisfiedBy(subject.Episodes))
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
_logger.Debug("Episode file on disk contains more episodes than this release contains");
return Decision.Reject("Episode file on disk contains more episodes than this release contains");
return DownloadSpecDecision.Reject(DownloadRejectionReason.ExistingFileHasMoreEpisodes, "Episode file on disk contains more episodes than this release contains");
}
}
}

View File

@ -5,7 +5,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class SceneMappingSpecification : IDecisionEngineSpecification
public class SceneMappingSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -17,18 +17,18 @@ public SceneMappingSpecification(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)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (remoteEpisode.SceneMapping == null)
{
_logger.Debug("No applicable scene mapping, skipping.");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (remoteEpisode.SceneMapping.SceneOrigin.IsNullOrWhiteSpace())
{
_logger.Debug("No explicit scene origin in scene mapping.");
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var split = remoteEpisode.SceneMapping.SceneOrigin.Split(':');
@ -50,11 +50,11 @@ public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase se
if (remoteEpisode.SceneMapping.Comment.IsNotNullOrWhiteSpace())
{
return Decision.Reject("{0} has ambiguous numbering");
return DownloadSpecDecision.Reject(DownloadRejectionReason.AmbiguousNumbering, "{0} has ambiguous numbering");
}
else
{
return Decision.Reject("Ambiguous numbering");
return DownloadSpecDecision.Reject(DownloadRejectionReason.AmbiguousNumbering, "Ambiguous numbering");
}
}
@ -65,7 +65,7 @@ public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase se
_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 DownloadSpecDecision.Accept();
}
}
}

View File

@ -5,7 +5,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
{
public class EpisodeRequestedSpecification : IDecisionEngineSpecification
public class EpisodeRequestedSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -17,11 +17,11 @@ public EpisodeRequestedSpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var criteriaEpisodes = searchCriteria.Episodes.Select(v => v.Id).ToList();
@ -37,20 +37,20 @@ public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase se
if (episodes.Count > 1)
{
return Decision.Reject($"Episode wasn't requested: {episodes.First().SeasonNumber}x{episodes.First().EpisodeNumber}-{episodes.Last().EpisodeNumber}");
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongEpisode, $"Episode wasn't requested: {episodes.First().SeasonNumber}x{episodes.First().EpisodeNumber}-{episodes.Last().EpisodeNumber}");
}
else
{
return Decision.Reject($"Episode wasn't requested: {episodes.First().SeasonNumber}x{episodes.First().EpisodeNumber}");
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongEpisode, $"Episode wasn't requested: {episodes.First().SeasonNumber}x{episodes.First().EpisodeNumber}");
}
}
else
{
return Decision.Reject("Episode wasn't requested");
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongEpisode, "Episode wasn't requested");
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -5,7 +5,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
{
public class SeasonMatchSpecification : IDecisionEngineSpecification
public class SeasonMatchSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
private readonly ISceneMappingService _sceneMappingService;
@ -19,26 +19,26 @@ public SeasonMatchSpecification(ISceneMappingService sceneMappingService, Logger
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var singleEpisodeSpec = searchCriteria as SeasonSearchCriteria;
if (singleEpisodeSpec == null)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
{
_logger.Debug("Season number does not match searched season number, skipping.");
return Decision.Reject("Wrong season");
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongSeason, "Wrong season");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -4,7 +4,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
{
public class SeriesSpecification : IDecisionEngineSpecification
public class SeriesSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -16,11 +16,11 @@ public SeriesSpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
_logger.Debug("Checking if series matches searched series");
@ -28,10 +28,10 @@ public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase se
if (remoteEpisode.Series.Id != searchCriteria.Series.Id)
{
_logger.Debug("Series {0} does not match {1}", remoteEpisode.Series, searchCriteria.Series);
return Decision.Reject("Wrong series");
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongSeries, "Wrong series");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -6,7 +6,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
{
public class SingleEpisodeSearchMatchSpecification : IDecisionEngineSpecification
public class SingleEpisodeSearchMatchSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
private readonly ISceneMappingService _sceneMappingService;
@ -20,11 +20,11 @@ public SingleEpisodeSearchMatchSpecification(ISceneMappingService sceneMappingSe
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var singleEpisodeSpec = searchCriteria as SingleEpisodeSearchCriteria;
@ -39,41 +39,41 @@ public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase se
return IsSatisfiedBy(remoteEpisode, animeEpisodeSpec);
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
private Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SingleEpisodeSearchCriteria singleEpisodeSpec)
private DownloadSpecDecision IsSatisfiedBy(RemoteEpisode remoteEpisode, SingleEpisodeSearchCriteria singleEpisodeSpec)
{
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
{
_logger.Debug("Season number does not match searched season number, skipping.");
return Decision.Reject("Wrong season");
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongSeason, "Wrong season");
}
if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Any())
{
_logger.Debug("Full season result during single episode search, skipping.");
return Decision.Reject("Full season pack");
return DownloadSpecDecision.Reject(DownloadRejectionReason.FullSeason, "Full season pack");
}
if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Contains(singleEpisodeSpec.EpisodeNumber))
{
_logger.Debug("Episode number does not match searched episode number, skipping.");
return Decision.Reject("Wrong episode");
return DownloadSpecDecision.Reject(DownloadRejectionReason.WrongEpisode, "Wrong episode");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
private Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, AnimeEpisodeSearchCriteria animeEpisodeSpec)
private DownloadSpecDecision IsSatisfiedBy(RemoteEpisode remoteEpisode, AnimeEpisodeSearchCriteria animeEpisodeSpec)
{
if (remoteEpisode.ParsedEpisodeInfo.FullSeason && !animeEpisodeSpec.IsSeasonSearch)
{
_logger.Debug("Full season result during single episode search, skipping.");
return Decision.Reject("Full season pack");
return DownloadSpecDecision.Reject(DownloadRejectionReason.FullSeason, "Full season pack");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -8,7 +8,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class SeasonPackOnlySpecification : IDecisionEngineSpecification
public class SeasonPackOnlySpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -20,11 +20,11 @@ public SeasonPackOnlySpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (searchCriteria == null || searchCriteria.Episodes.Count == 1)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
if (subject.Release.SeasonSearchMaximumSingleEpisodeAge > 0)
@ -37,12 +37,12 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
if (subset.Count > 0 && subset.Max(e => e.AirDateUtc).Value.Before(DateTime.UtcNow - TimeSpan.FromDays(subject.Release.SeasonSearchMaximumSingleEpisodeAge)))
{
_logger.Debug("Release {0}: last episode in this season aired more than {1} days ago, season pack required.", subject.Release.Title, subject.Release.SeasonSearchMaximumSingleEpisodeAge);
return Decision.Reject("Last episode in this season aired more than {0} days ago, season pack required.", subject.Release.SeasonSearchMaximumSingleEpisodeAge);
return DownloadSpecDecision.Reject(DownloadRejectionReason.NotSeasonPack, "Last episode in this season aired more than {0} days ago, season pack required.", subject.Release.SeasonSearchMaximumSingleEpisodeAge);
}
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -4,7 +4,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class SplitEpisodeSpecification : IDecisionEngineSpecification
public class SplitEpisodeSpecification : IDownloadDecisionEngineSpecification
{
private readonly Logger _logger;
@ -16,15 +16,15 @@ public SplitEpisodeSpecification(Logger logger)
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.ParsedEpisodeInfo.IsSplitEpisode)
{
_logger.Debug("Split episode release {0} rejected. Not supported", subject.Release.Title);
return Decision.Reject("Split episode releases are not supported");
return DownloadSpecDecision.Reject(DownloadRejectionReason.SplitEpisode, "Split episode releases are not supported");
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -6,7 +6,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class TorrentSeedingSpecification : IDecisionEngineSpecification
public class TorrentSeedingSpecification : IDownloadDecisionEngineSpecification
{
private readonly IIndexerFactory _indexerFactory;
private readonly Logger _logger;
@ -20,13 +20,13 @@ public TorrentSeedingSpecification(IIndexerFactory indexerFactory, Logger logger
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
public DownloadSpecDecision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
var torrentInfo = remoteEpisode.Release as TorrentInfo;
if (torrentInfo == null || torrentInfo.IndexerId == 0)
{
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
IndexerDefinition indexer;
@ -37,7 +37,7 @@ public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase se
catch (ModelNotFoundException)
{
_logger.Debug("Indexer with id {0} does not exist, skipping seeders check", torrentInfo.IndexerId);
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
@ -49,11 +49,11 @@ public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase se
if (torrentInfo.Seeders.HasValue && torrentInfo.Seeders.Value < minimumSeeders)
{
_logger.Debug("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
return Decision.Reject("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
return DownloadSpecDecision.Reject(DownloadRejectionReason.MinimumSeeders, "Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -6,7 +6,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class UpgradeAllowedSpecification : IDecisionEngineSpecification
public class UpgradeAllowedSpecification : IDownloadDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
@ -24,7 +24,7 @@ public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecificati
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var qualityProfile = subject.Series.QualityProfile.Value;
@ -48,11 +48,11 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
{
_logger.Debug("Upgrading is not allowed by the quality profile");
return Decision.Reject("Existing file and the Quality profile does not allow upgrades");
return DownloadSpecDecision.Reject(DownloadRejectionReason.QualityUpgradesDisabled, "Existing file and the Quality profile does not allow upgrades");
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -6,7 +6,7 @@
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class UpgradeDiskSpecification : IDecisionEngineSpecification
public class UpgradeDiskSpecification : IDownloadDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
@ -24,7 +24,7 @@ public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification,
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
public virtual DownloadSpecDecision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var qualityProfile = subject.Series.QualityProfile.Value;
@ -48,7 +48,7 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
var cutoff = qualityProfile.UpgradeAllowed ? qualityProfile.Cutoff : qualityProfile.FirststAllowedQuality().Id;
var qualityCutoff = qualityProfile.Items[qualityProfile.GetIndex(cutoff).Index];
return Decision.Reject("Existing file meets cutoff: {0}", qualityCutoff);
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCutoffMet, "Existing file meets cutoff: {0}", qualityCutoff);
}
var customFormats = _formatService.ParseCustomFormat(file);
@ -65,26 +65,26 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
continue;
case UpgradeableRejectReason.BetterQuality:
return Decision.Reject("Existing file on disk is of equal or higher preference: {0}", file.Quality);
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskHigherPreference, "Existing file on disk is of equal or higher preference: {0}", file.Quality);
case UpgradeableRejectReason.BetterRevision:
return Decision.Reject("Existing file on disk is of equal or higher revision: {0}", file.Quality.Revision);
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskHigherRevision, "Existing file on disk is of equal or higher revision: {0}", file.Quality.Revision);
case UpgradeableRejectReason.QualityCutoff:
return Decision.Reject("Existing file on disk meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCutoffMet, "Existing file on disk meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]);
case UpgradeableRejectReason.CustomFormatCutoff:
return Decision.Reject("Existing file on disk meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatCutoffMet, "Existing file on disk meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore);
case UpgradeableRejectReason.CustomFormatScore:
return Decision.Reject("Existing file on disk has a equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(customFormats));
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatScore, "Existing file on disk has a equal or higher Custom Format score: {0}", qualityProfile.CalculateCustomFormatScore(customFormats));
case UpgradeableRejectReason.MinCustomFormatScore:
return Decision.Reject("Existing file on disk has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
return DownloadSpecDecision.Reject(DownloadRejectionReason.DiskCustomFormatScoreIncrement, "Existing file on disk has Custom Format score within Custom Format score increment: {0}", qualityProfile.MinUpgradeFormatScore);
}
}
return Decision.Accept();
return DownloadSpecDecision.Accept();
}
}
}

View File

@ -6,7 +6,6 @@
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Parser;
@ -178,7 +177,7 @@ private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode
_logger.Warn("Unable to process folder that is mapped to an existing series");
return new List<ImportResult>
{
RejectionResult("Import path is mapped to a series folder")
RejectionResult(ImportRejectionReason.SeriesFolder, "Import path is mapped to a series folder")
};
}
@ -255,7 +254,7 @@ private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode,
return new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new ImportRejection(ImportRejectionReason.InvalidFilePath, "Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'")
};
}
@ -268,7 +267,7 @@ private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode,
return new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName },
new Rejection($"Invalid video file, unsupported extension: '{extension}'")),
new ImportRejection(ImportRejectionReason.UnsupportedExtension, $"Invalid video file, unsupported extension: '{extension}'")),
$"Invalid video file, unsupported extension: '{extension}'")
};
}
@ -300,19 +299,19 @@ private string GetCleanedUpFolderName(string folder)
private ImportResult FileIsLockedResult(string videoFile)
{
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new ImportRejection(ImportRejectionReason.FileLocked, "Locked file, try again later")), "Locked file, try again later");
}
private ImportResult UnknownSeriesResult(string message, string videoFile = null)
{
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Series")), message);
return new ImportResult(new ImportDecision(localEpisode, new ImportRejection(ImportRejectionReason.UnknownSeries, "Unknown Series")), message);
}
private ImportResult RejectionResult(string message)
private ImportResult RejectionResult(ImportRejectionReason reason, string message)
{
return new ImportResult(new ImportDecision(null, new Rejection(message)), message);
return new ImportResult(new ImportDecision(null, new ImportRejection(reason, message)), message);
}
private ImportResult CheckEmptyResultForIssue(string folder)
@ -321,12 +320,12 @@ private ImportResult CheckEmptyResultForIssue(string folder)
if (files.Any(file => FileExtensions.ExecutableExtensions.Contains(Path.GetExtension(file))))
{
return RejectionResult("Caution: Found executable file");
return RejectionResult(ImportRejectionReason.ExecutableFile, "Caution: Found executable file");
}
if (files.Any(file => FileExtensions.ArchiveExtensions.Contains(Path.GetExtension(file))))
{
return RejectionResult("Found archive file, might need to be extracted");
return RejectionResult(ImportRejectionReason.ArchiveFile, "Found archive file, might need to be extracted");
}
return null;

View File

@ -1,11 +1,10 @@
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
public interface IImportDecisionEngineSpecification
{
Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem);
ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem);
}
}

View File

@ -224,7 +224,7 @@ public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownloa
// Adding all the rejected decisions
importResults.AddRange(decisions.Where(c => !c.Approved)
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Message).ToArray())));
return importResults;
}

View File

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
@ -9,11 +8,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public class ImportDecision
{
public LocalEpisode LocalEpisode { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public IEnumerable<ImportRejection> Rejections { get; private set; }
public bool Approved => Rejections.Empty();
public ImportDecision(LocalEpisode localEpisode, params Rejection[] rejections)
public ImportDecision(LocalEpisode localEpisode, params ImportRejection[] rejections)
{
LocalEpisode = localEpisode;
Rejections = rejections.ToList();

View File

@ -5,7 +5,6 @@
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation;
@ -136,15 +135,15 @@ private ImportDecision GetDecision(LocalEpisode localEpisode, DownloadClientItem
{
if (IsPartialSeason(localEpisode))
{
decision = new ImportDecision(localEpisode, new Rejection("Partial season packs are not supported"));
decision = new ImportDecision(localEpisode, new ImportRejection(ImportRejectionReason.PartialSeason, "Partial season packs are not supported"));
}
else if (IsSeasonExtra(localEpisode))
{
decision = new ImportDecision(localEpisode, new Rejection("Extras are not supported"));
decision = new ImportDecision(localEpisode, new ImportRejection(ImportRejectionReason.SeasonExtra, "Extras are not supported"));
}
else
{
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
decision = new ImportDecision(localEpisode, new ImportRejection(ImportRejectionReason.InvalidSeasonOrEpisode, "Invalid season or episode"));
}
}
else
@ -167,13 +166,13 @@ private ImportDecision GetDecision(LocalEpisode localEpisode, DownloadClientItem
}
catch (AugmentingFailedException)
{
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file"));
decision = new ImportDecision(localEpisode, new ImportRejection(ImportRejectionReason.UnableToParse, "Unable to parse file"));
}
catch (Exception ex)
{
_logger.Error(ex, "Couldn't import file. {0}", localEpisode.Path);
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
decision = new ImportDecision(localEpisode, new ImportRejection(ImportRejectionReason.Error, "Unexpected error processing file"));
}
if (decision == null)
@ -192,7 +191,7 @@ private ImportDecision GetDecision(LocalEpisode localEpisode, DownloadClientItem
return decision;
}
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
private ImportRejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
try
{
@ -200,7 +199,7 @@ private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpi
if (!result.Accepted)
{
return new Rejection(result.Reason);
return new ImportRejection(result.Reason, result.Message);
}
}
catch (Exception e)
@ -208,7 +207,7 @@ private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpi
// e.Data.Add("report", remoteEpisode.Report.ToJson());
// e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.Error(e, "Couldn't evaluate decision on {0}", localEpisode.Path);
return new Rejection($"{spec.GetType().Name}: {e.Message}");
return new ImportRejection(ImportRejectionReason.DecisionError, $"{spec.GetType().Name}: {e.Message}");
}
return null;

View File

@ -0,0 +1,11 @@
using NzbDrone.Core.DecisionEngine;
namespace NzbDrone.Core.MediaFiles.EpisodeImport;
public class ImportRejection : Rejection<ImportRejectionReason>
{
public ImportRejection(ImportRejectionReason reason, string message, RejectionType type = RejectionType.Permanent)
: base(reason, message, type)
{
}
}

View File

@ -0,0 +1,38 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport;
public enum ImportRejectionReason
{
Unknown,
FileLocked,
UnknownSeries,
ExecutableFile,
ArchiveFile,
SeriesFolder,
InvalidFilePath,
UnsupportedExtension,
PartialSeason,
SeasonExtra,
InvalidSeasonOrEpisode,
UnableToParse,
Error,
DecisionError,
NoEpisodes,
MissingAbsoluteEpisodeNumber,
EpisodeAlreadyImported,
TitleMissing,
TitleTba,
MinimumFreeSpace,
FullSeason,
NoAudio,
EpisodeUnexpected,
EpisodeNotFoundInRelease,
Sample,
SampleIndeterminate,
Unpacking,
ExistingFileHasMoreEpisodes,
SplitEpisode,
UnverifiedSceneMapping,
NotQualityUpgrade,
NotRevisionUpgrade,
NotCustomFormatUpgrade
}

View File

@ -0,0 +1,34 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
public class ImportSpecDecision
{
public bool Accepted { get; private set; }
public ImportRejectionReason Reason { get; set; }
public string Message { get; private set; }
private static readonly ImportSpecDecision AcceptDecision = new () { Accepted = true };
private ImportSpecDecision()
{
}
public static ImportSpecDecision Accept()
{
return AcceptDecision;
}
public static ImportSpecDecision Reject(ImportRejectionReason reason, string message, params object[] args)
{
return Reject(reason, string.Format(message, args));
}
public static ImportSpecDecision Reject(ImportRejectionReason reason, string message)
{
return new ImportSpecDecision
{
Accepted = false,
Reason = reason,
Message = message
};
}
}
}

View File

@ -1,6 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
@ -27,7 +26,7 @@ public class ManualImportItem
public int CustomFormatScore { get; set; }
public int IndexerFlags { get; set; }
public ReleaseType ReleaseType { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
public IEnumerable<ImportRejection> Rejections { get; set; }
public ManualImportItem()
{

View File

@ -7,7 +7,6 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Languages;
@ -104,7 +103,7 @@ public List<ManualImportItem> GetMediaFiles(int seriesId, int? seasonNumber)
Quality = new QualityModel(Quality.Unknown),
Languages = new List<Language> { Language.Unknown },
Size = _diskProvider.GetFileSize(file),
Rejections = Enumerable.Empty<Rejection>()
Rejections = Enumerable.Empty<ImportRejection>()
}));
}
@ -226,7 +225,7 @@ public ManualImportItem ReprocessItem(string path, string downloadId, int series
ReleaseType = releaseType
};
return MapItem(new ImportDecision(localEpisode, new Rejection("Episodes not selected")), rootFolder, downloadId, null);
return MapItem(new ImportDecision(localEpisode, new ImportRejection(ImportRejectionReason.NoEpisodes, "Episodes not selected")), rootFolder, downloadId, null);
}
return ProcessFile(rootFolder, rootFolder, path, downloadId, series);
@ -338,7 +337,7 @@ private ManualImportItem ProcessFile(string rootFolder, string baseFolder, strin
localEpisode.Size = _diskProvider.GetFileSize(file);
return MapItem(new ImportDecision(localEpisode,
new Rejection("Unknown Series")),
new ImportRejection(ImportRejectionReason.UnknownSeries, "Unknown Series")),
rootFolder,
downloadId,
null);
@ -367,7 +366,7 @@ private ManualImportItem ProcessFile(string rootFolder, string baseFolder, strin
RelativePath = rootFolder.GetRelativePath(file),
Name = Path.GetFileNameWithoutExtension(file),
Size = _diskProvider.GetFileSize(file),
Rejections = new List<Rejection>()
Rejections = new List<ImportRejection>()
};
}
@ -471,7 +470,7 @@ private ManualImportItem MapItem(EpisodeFile episodeFile, Series series, string
item.IndexerFlags = (int)episodeFile.IndexerFlags;
item.ReleaseType = episodeFile.ReleaseType;
item.Size = _diskProvider.GetFileSize(item.Path);
item.Rejections = Enumerable.Empty<Rejection>();
item.Rejections = Enumerable.Empty<ImportRejection>();
item.EpisodeFileId = episodeFile.Id;
item.CustomFormats = _formatCalculator.ParseCustomFormat(episodeFile, series);

View File

@ -1,7 +1,6 @@
using System;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
@ -20,18 +19,18 @@ public AbsoluteEpisodeNumberSpecification(IBuildFileNames buildFileNames, Logger
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.Series.SeriesType != SeriesTypes.Anime)
{
_logger.Debug("Series type is not Anime, skipping check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
if (!_buildFileNames.RequiresAbsoluteEpisodeNumber())
{
_logger.Debug("File name format does not require absolute episode number, skipping check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
foreach (var episode in localEpisode.Episodes)
@ -49,11 +48,11 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
{
_logger.Debug("Episode does not have an absolute episode number and recently aired");
return Decision.Reject("Episode does not have an absolute episode number and recently aired");
return ImportSpecDecision.Reject(ImportRejectionReason.MissingAbsoluteEpisodeNumber, "Episode does not have an absolute episode number and recently aired");
}
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -22,12 +22,12 @@ public AlreadyImportedSpecification(IHistoryService historyService,
public SpecificationPriority Priority => SpecificationPriority.Database;
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (downloadClientItem == null)
{
_logger.Debug("No download client information is available, skipping");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
foreach (var episode in localEpisode.Episodes)
@ -64,17 +64,17 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (lastImported.Date.After(lastGrabbed.Date))
{
_logger.Debug("Episode file previously imported at {0}", lastImported.Date);
return Decision.Reject("Episode file already imported at {0}", lastImported.Date.ToLocalTime());
return ImportSpecDecision.Reject(ImportRejectionReason.EpisodeAlreadyImported, "Episode file already imported at {0}", lastImported.Date.ToLocalTime());
}
}
else
{
_logger.Debug("Episode file previously imported at {0}", lastImported.Date);
return Decision.Reject("Episode file already imported at {0}", lastImported.Date.ToLocalTime());
return ImportSpecDecision.Reject(ImportRejectionReason.EpisodeAlreadyImported, "Episode file already imported at {0}", lastImported.Date.ToLocalTime());
}
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -3,7 +3,6 @@
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
@ -29,12 +28,12 @@ public EpisodeTitleSpecification(IConfigService configService,
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.ExistingFile)
{
_logger.Debug("{0} is in series folder, skipping check", localEpisode.Path);
return Decision.Accept();
return ImportSpecDecision.Accept();
}
var episodeTitleRequired = _configService.EpisodeTitleRequired;
@ -42,13 +41,13 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (episodeTitleRequired == EpisodeTitleRequiredType.Never)
{
_logger.Debug("Episode titles are never required, skipping check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
if (!_buildFileNames.RequiresEpisodeTitle(localEpisode.Series, localEpisode.Episodes))
{
_logger.Debug("File name format does not require episode title, skipping check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
var episodes = localEpisode.Episodes;
@ -64,7 +63,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
e.AirDateUtc.Value == firstEpisode.AirDateUtc.Value) < 4)
{
_logger.Debug("Episode title only required for bulk season releases");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
foreach (var episode in episodes)
@ -82,18 +81,18 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
{
_logger.Debug("Episode does not have a title and recently aired");
return Decision.Reject("Episode does not have a title and recently aired");
return ImportSpecDecision.Reject(ImportRejectionReason.TitleMissing, "Episode does not have a title and recently aired");
}
if (title.Equals("TBA"))
{
_logger.Debug("Episode has a TBA title and recently aired");
return Decision.Reject("Episode has a TBA title and recently aired");
return ImportSpecDecision.Reject(ImportRejectionReason.TitleTba, "Episode has a TBA title and recently aired");
}
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -4,7 +4,6 @@
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
@ -23,12 +22,12 @@ public FreeSpaceSpecification(IDiskProvider diskProvider, IConfigService configS
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (_configService.SkipFreeSpaceCheckWhenImporting)
{
_logger.Debug("Skipping free space check when importing");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
try
@ -36,7 +35,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (localEpisode.ExistingFile)
{
_logger.Debug("Skipping free space check for existing episode");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
var path = Directory.GetParent(localEpisode.Series.Path);
@ -45,13 +44,13 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (!freeSpace.HasValue)
{
_logger.Debug("Free space check returned an invalid result for: {0}", path);
return Decision.Accept();
return ImportSpecDecision.Accept();
}
if (freeSpace < localEpisode.Size + _configService.MinimumFreeSpaceWhenImporting.Megabytes())
{
_logger.Warn("Not enough free space ({0}) to import: {1} ({2})", freeSpace, localEpisode, localEpisode.Size);
return Decision.Reject("Not enough free space");
return ImportSpecDecision.Reject(ImportRejectionReason.MinimumFreeSpace, "Not enough free space");
}
}
catch (DirectoryNotFoundException ex)
@ -63,7 +62,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
_logger.Error(ex, "Unable to check free disk space while importing. {0}", localEpisode.Path);
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -1,5 +1,4 @@
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
@ -14,20 +13,20 @@ public FullSeasonSpecification(Logger logger)
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.FileEpisodeInfo == null)
{
return Decision.Accept();
return ImportSpecDecision.Accept();
}
if (localEpisode.FileEpisodeInfo.FullSeason)
{
_logger.Debug("Single episode file detected as containing all episodes in the season due to no episode parsed from the file name.");
return Decision.Reject("Single episode file contains all episodes in seasons. Review file name or manually import");
return ImportSpecDecision.Reject(ImportRejectionReason.FullSeason, "Single episode file contains all episodes in seasons. Review file name or manually import");
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -1,5 +1,4 @@
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
@ -14,22 +13,22 @@ public HasAudioTrackSpecification(Logger logger)
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.MediaInfo == null)
{
_logger.Debug("Failed to get media info from the file, make sure ffprobe is available, skipping check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
if (localEpisode.MediaInfo.AudioStreamCount == 0)
{
_logger.Debug("No audio tracks found in file");
return Decision.Reject("No audio tracks detected");
return ImportSpecDecision.Reject(ImportRejectionReason.NoAudio, "No audio tracks detected");
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -2,7 +2,6 @@
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -21,11 +20,11 @@ public MatchesFolderSpecification(IParsingService parsingService, Logger logger)
_parsingService = parsingService;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.ExistingFile)
{
return Decision.Accept();
return ImportSpecDecision.Accept();
}
var fileInfo = localEpisode.FileEpisodeInfo;
@ -44,13 +43,13 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (folderInfo == null)
{
_logger.Debug("No folder ParsedEpisodeInfo, skipping check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
if (fileInfo == null)
{
_logger.Debug("No file ParsedEpisodeInfo, skipping check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
var folderEpisodes = _parsingService.GetEpisodes(folderInfo, localEpisode.Series, true);
@ -59,7 +58,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (folderEpisodes.Empty())
{
_logger.Debug("No episode numbers in folder ParsedEpisodeInfo, skipping check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
var unexpected = fileEpisodes.Where(e => folderEpisodes.All(o => o.Id != e.Id)).ToList();
@ -70,13 +69,13 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (unexpected.Count == 1)
{
return Decision.Reject("Episode {0} was unexpected considering the {1} folder name", FormatEpisode(unexpected), folderInfo.ReleaseTitle);
return ImportSpecDecision.Reject(ImportRejectionReason.EpisodeUnexpected, "Episode {0} was unexpected considering the {1} folder name", FormatEpisode(unexpected), folderInfo.ReleaseTitle);
}
return Decision.Reject("Episodes {0} were unexpected considering the {1} folder name", FormatEpisode(unexpected), folderInfo.ReleaseTitle);
return ImportSpecDecision.Reject(ImportRejectionReason.EpisodeUnexpected, "Episodes {0} were unexpected considering the {1} folder name", FormatEpisode(unexpected), folderInfo.ReleaseTitle);
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
private string FormatEpisode(List<Episode> episodes)

View File

@ -2,7 +2,6 @@
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
@ -18,18 +17,18 @@ public MatchesGrabSpecification(Logger logger)
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.ExistingFile)
{
return Decision.Accept();
return ImportSpecDecision.Accept();
}
var releaseInfo = localEpisode.Release;
if (releaseInfo == null || releaseInfo.EpisodeIds.Empty())
{
return Decision.Accept();
return ImportSpecDecision.Accept();
}
var unexpected = localEpisode.Episodes.Where(e => releaseInfo.EpisodeIds.All(o => o != e.Id)).ToList();
@ -40,13 +39,13 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (unexpected.Count == 1)
{
return Decision.Reject("Episode {0} was not found in the grabbed release: {1}", FormatEpisode(unexpected), releaseInfo.Title);
return ImportSpecDecision.Reject(ImportRejectionReason.EpisodeNotFoundInRelease, "Episode {0} was not found in the grabbed release: {1}", FormatEpisode(unexpected), releaseInfo.Title);
}
return Decision.Reject("Episodes {0} were not found in the grabbed release: {1}", FormatEpisode(unexpected), releaseInfo.Title);
return ImportSpecDecision.Reject(ImportRejectionReason.EpisodeNotFoundInRelease, "Episodes {0} were not found in the grabbed release: {1}", FormatEpisode(unexpected), releaseInfo.Title);
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
private string FormatEpisode(List<Episode> episodes)

View File

@ -1,5 +1,4 @@
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -18,12 +17,12 @@ public NotSampleSpecification(IDetectSample detectSample,
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.ExistingFile)
{
_logger.Debug("Existing file, skipping sample check");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
try
@ -32,11 +31,11 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (sample == DetectSampleResult.Sample)
{
return Decision.Reject("Sample");
return ImportSpecDecision.Reject(ImportRejectionReason.Sample, "Sample");
}
else if (sample == DetectSampleResult.Indeterminate)
{
return Decision.Reject("Unable to determine if file is a sample");
return ImportSpecDecision.Reject(ImportRejectionReason.SampleIndeterminate, "Unable to determine if file is a sample");
}
}
catch (InvalidSeasonException e)
@ -44,7 +43,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
_logger.Warn(e, "Invalid season detected during sample check");
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -4,7 +4,6 @@
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
@ -23,12 +22,12 @@ public NotUnpackingSpecification(IDiskProvider diskProvider, IConfigService conf
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.ExistingFile)
{
_logger.Debug("{0} is in series folder, skipping unpacking check", localEpisode.Path);
return Decision.Accept();
return ImportSpecDecision.Accept();
}
foreach (var workingFolder in _configService.DownloadClientWorkingFolders.Split('|'))
@ -41,13 +40,13 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (OsInfo.IsNotWindows)
{
_logger.Debug("{0} is still being unpacked", localEpisode.Path);
return Decision.Reject("File is still being unpacked");
return ImportSpecDecision.Reject(ImportRejectionReason.Unpacking, "File is still being unpacked");
}
if (_diskProvider.FileGetLastWrite(localEpisode.Path) > DateTime.UtcNow.AddMinutes(-5))
{
_logger.Debug("{0} appears to be unpacking still", localEpisode.Path);
return Decision.Reject("File is still being unpacked");
return ImportSpecDecision.Reject(ImportRejectionReason.Unpacking, "File is still being unpacked");
}
}
@ -55,7 +54,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
}
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -19,15 +19,15 @@ public SameEpisodesImportSpecification(SameEpisodesSpecification sameEpisodesSpe
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (_sameEpisodesSpecification.IsSatisfiedBy(localEpisode.Episodes))
{
return Decision.Accept();
return ImportSpecDecision.Accept();
}
_logger.Debug("Episode file on disk contains more episodes than this file contains");
return Decision.Reject("Episode file on disk contains more episodes than this file contains");
return ImportSpecDecision.Reject(ImportRejectionReason.ExistingFileHasMoreEpisodes, "Episode file on disk contains more episodes than this file contains");
}
}
}

View File

@ -1,5 +1,4 @@
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
@ -14,20 +13,20 @@ public SplitEpisodeSpecification(Logger logger)
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.FileEpisodeInfo == null)
{
return Decision.Accept();
return ImportSpecDecision.Accept();
}
if (localEpisode.FileEpisodeInfo.IsSplitEpisode)
{
_logger.Debug("Single episode split into multiple files");
return Decision.Reject("Single episode split into multiple files");
return ImportSpecDecision.Reject(ImportRejectionReason.SplitEpisode, "Single episode split into multiple files");
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -1,6 +1,5 @@
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
@ -14,21 +13,21 @@ public UnverifiedSceneNumberingSpecification(Logger logger)
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.ExistingFile)
{
_logger.Debug("Skipping scene numbering check for existing episode");
return Decision.Accept();
return ImportSpecDecision.Accept();
}
if (localEpisode.Episodes.Any(v => v.UnverifiedSceneNumbering))
{
_logger.Debug("This file uses unverified scene numbers, will not auto-import until numbering is confirmed on TheXEM. Skipping {0}", localEpisode.Path);
return Decision.Reject("This show has individual episode mappings on TheXEM but the mapping for this episode has not been confirmed yet by their administrators. TheXEM needs manual input.");
return ImportSpecDecision.Reject(ImportRejectionReason.UnverifiedSceneMapping, "This show has individual episode mappings on TheXEM but the mapping for this episode has not been confirmed yet by their administrators. TheXEM needs manual input.");
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -3,7 +3,6 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
@ -25,7 +24,7 @@ public UpgradeSpecification(IConfigService configService,
_logger = logger;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
public ImportSpecDecision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks;
var qualityProfile = localEpisode.Series.QualityProfile.Value;
@ -46,7 +45,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
if (qualityCompare < 0)
{
_logger.Debug("This file isn't a quality upgrade for all episodes. Existing quality: {0}. New Quality {1}. Skipping {2}", episodeFile.Quality.Quality, localEpisode.Quality.Quality, localEpisode.Path);
return Decision.Reject("Not an upgrade for existing episode file(s). Existing quality: {0}. New Quality {1}.", episodeFile.Quality.Quality, localEpisode.Quality.Quality);
return ImportSpecDecision.Reject(ImportRejectionReason.NotQualityUpgrade, "Not an upgrade for existing episode file(s). Existing quality: {0}. New Quality {1}.", episodeFile.Quality.Quality, localEpisode.Quality.Quality);
}
// Same quality, propers/repacks are preferred and it is not a revision update. Reject revision downgrade.
@ -56,7 +55,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
localEpisode.Quality.Revision.CompareTo(episodeFile.Quality.Revision) < 0)
{
_logger.Debug("This file isn't a quality revision upgrade for all episodes. Skipping {0}", localEpisode.Path);
return Decision.Reject("Not a quality revision upgrade for existing episode file(s)");
return ImportSpecDecision.Reject(ImportRejectionReason.NotRevisionUpgrade, "Not a quality revision upgrade for existing episode file(s)");
}
var currentFormats = _formatService.ParseCustomFormat(episodeFile);
@ -72,7 +71,8 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
currentFormats != null ? currentFormats.ConcatToString() : "",
currentFormatScore);
return Decision.Reject("Not a Custom Format upgrade for existing episode file(s). New: [{0}] ({1}) do not improve on Existing: [{2}] ({3})",
return ImportSpecDecision.Reject(ImportRejectionReason.NotCustomFormatUpgrade,
"Not a Custom Format upgrade for existing episode file(s). New: [{0}] ({1}) do not improve on Existing: [{2}] ({3})",
newFormats != null ? newFormats.ConcatToString() : "",
newFormatScore,
currentFormats != null ? currentFormats.ConcatToString() : "",
@ -86,7 +86,7 @@ public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem down
currentFormatScore);
}
return Decision.Accept();
return ImportSpecDecision.Accept();
}
}
}

View File

@ -138,7 +138,7 @@ public static ReleaseResource ToResource(this DownloadDecision model)
TvdbId = releaseInfo.TvdbId,
TvRageId = releaseInfo.TvRageId,
ImdbId = releaseInfo.ImdbId,
Rejections = model.Rejections.Select(r => r.Reason).ToList(),
Rejections = model.Rejections.Select(r => r.Message).ToList(),
PublishDate = releaseInfo.PublishDate,
CommentUrl = releaseInfo.CommentUrl,
DownloadUrl = releaseInfo.DownloadUrl,

View File

@ -45,7 +45,7 @@ public object ReprocessItems([FromBody] List<ManualImportReprocessResource> item
item.Episodes = processedItem.Episodes.ToResource();
item.ReleaseType = processedItem.ReleaseType;
item.IndexerFlags = processedItem.IndexerFlags;
item.Rejections = processedItem.Rejections;
item.Rejections = processedItem.Rejections.Select(r => r.ToResource());
item.CustomFormats = processedItem.CustomFormats.ToResource(false);
item.CustomFormatScore = processedItem.CustomFormatScore;

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
@ -24,6 +23,6 @@ public class ManualImportReprocessResource : RestResource
public int CustomFormatScore { get; set; }
public int IndexerFlags { get; set; }
public ReleaseType ReleaseType { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
public IEnumerable<ImportRejectionResource> Rejections { get; set; }
}
}

View File

@ -3,6 +3,7 @@
using NzbDrone.Common.Crypto;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
@ -33,7 +34,7 @@ public class ManualImportResource : RestResource
public int CustomFormatScore { get; set; }
public int IndexerFlags { get; set; }
public ReleaseType ReleaseType { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
public IEnumerable<ImportRejectionResource> Rejections { get; set; }
}
public static class ManualImportResourceMapper
@ -70,7 +71,7 @@ public static ManualImportResource ToResource(this ManualImportItem model)
DownloadId = model.DownloadId,
IndexerFlags = model.IndexerFlags,
ReleaseType = model.ReleaseType,
Rejections = model.Rejections
Rejections = model.Rejections.Select(r => r.ToResource())
};
}
@ -79,4 +80,27 @@ public static List<ManualImportResource> ToResource(this IEnumerable<ManualImpor
return models.Select(ToResource).ToList();
}
}
public class ImportRejectionResource
{
public string Reason { get; set; }
public RejectionType Type { get; set; }
}
public static class ImportRejectionResourceMapper
{
public static ImportRejectionResource ToResource(this ImportRejection rejection)
{
if (rejection == null)
{
return null;
}
return new ImportRejectionResource
{
Reason = rejection.Message,
Type = rejection.Type
};
}
}
}