New: Custom Formats

Co-Authored-By: ta264 <ta264@users.noreply.github.com>
This commit is contained in:
Qstick
2022-01-23 23:42:41 -06:00
parent 4a3062deae
commit dbb6ef7664
185 changed files with 6974 additions and 810 deletions
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.DecisionEngine
var comparers = new List<CompareDelegate>
{
CompareQuality,
ComparePreferredWordScore,
CompareCustomFormatScore,
CompareProtocol,
CompareIndexerPriority,
ComparePeersIfTorrent,
@@ -76,9 +76,9 @@ namespace NzbDrone.Core.DecisionEngine
CompareBy(x.RemoteBook, y.RemoteBook, remoteBook => remoteBook.ParsedBookInfo.Quality.Revision));
}
private int ComparePreferredWordScore(DownloadDecision x, DownloadDecision y)
private int CompareCustomFormatScore(DownloadDecision x, DownloadDecision y)
{
return CompareBy(x.RemoteBook, y.RemoteBook, remoteBook => remoteBook.PreferredWordScore);
return CompareBy(x.RemoteBook, y.RemoteBook, remoteBook => remoteBook.CustomFormatScore);
}
private int CompareProtocol(DownloadDecision x, DownloadDecision y)
@@ -5,6 +5,7 @@ using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download.Aggregation;
using NzbDrone.Core.IndexerSearch.Definitions;
@@ -23,17 +24,20 @@ namespace NzbDrone.Core.DecisionEngine
public class DownloadDecisionMaker : IMakeDownloadDecision
{
private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly IParsingService _parsingService;
private readonly IRemoteBookAggregationService _aggregationService;
private readonly Logger _logger;
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications,
IParsingService parsingService,
ICustomFormatCalculationService formatService,
IRemoteBookAggregationService aggregationService,
Logger logger)
{
_specifications = specifications;
_parsingService = parsingService;
_formatCalculator = formatService;
_aggregationService = aggregationService;
_logger = logger;
}
@@ -89,6 +93,9 @@ namespace NzbDrone.Core.DecisionEngine
if (parsedBookInfo != null && !parsedBookInfo.AuthorName.IsNullOrWhiteSpace())
{
var remoteBook = _parsingService.Map(parsedBookInfo, searchCriteria);
remoteBook.Release = report;
_aggregationService.Augment(remoteBook);
// try parsing again using the search criteria, in case it parsed but parsed incorrectly
if ((remoteBook.Author == null || remoteBook.Books.Empty()) && searchCriteria != null)
@@ -134,6 +141,10 @@ namespace NzbDrone.Core.DecisionEngine
else
{
_aggregationService.Augment(remoteBook);
remoteBook.CustomFormats = _formatCalculator.ParseCustomFormat(remoteBook, remoteBook.Release.Size);
remoteBook.CustomFormatScore = remoteBook?.Author?.QualityProfile?.Value.CalculateCustomFormatScore(remoteBook.CustomFormats) ?? 0;
remoteBook.DownloadAllowed = remoteBook.Books.Any();
decision = GetDecisionForReport(remoteBook, searchCriteria);
}
@@ -0,0 +1,25 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class CustomFormatAllowedbyProfileSpecification : IDecisionEngineSpecification
{
public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public virtual Decision IsSatisfiedBy(RemoteBook subject, SearchCriteriaBase searchCriteria)
{
var minScore = subject.Author.QualityProfile.Value.MinFormatScore;
var score = subject.CustomFormatScore;
if (score < minScore)
{
return Decision.Reject("Custom Formats {0} have score {1} below Author profile minimum {2}", subject.CustomFormats.ConcatToString(), score, minScore);
}
return Decision.Accept();
}
}
}
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
@@ -13,14 +14,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly Logger _logger;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly ICustomFormatCalculationService _formatService;
public CutoffSpecification(UpgradableSpecification upgradableSpecification,
IPreferredWordService preferredWordServiceCalculator,
ICustomFormatCalculationService formatService,
Logger logger)
{
_upgradableSpecification = upgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_formatService = formatService;
_logger = logger;
}
@@ -38,11 +39,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("Comparing file quality with report. Existing files contain {0}", currentQualities.ConcatToString());
var customFormats = _formatService.ParseCustomFormat(file);
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
currentQualities,
_preferredWordServiceCalculator.Calculate(subject.Author, file.GetSceneOrFileName(), subject.Release.IndexerId),
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore))
customFormats,
subject.ParsedBookInfo.Quality))
{
_logger.Debug("Cutoff already met by existing files, rejecting.");
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
@@ -14,17 +15,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
private readonly IQueueService _queueService;
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger;
public QueueSpecification(IQueueService queueService,
UpgradableSpecification upgradableSpecification,
IPreferredWordService preferredWordServiceCalculator,
Logger logger)
UpgradableSpecification upgradableSpecification,
ICustomFormatCalculationService formatService,
Logger logger)
{
_queueService = queueService;
_upgradableSpecification = upgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_formatService = formatService;
_logger = logger;
}
@@ -54,13 +55,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteBook.ParsedBookInfo.Quality);
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Author, queueItem.Title, subject.Release?.IndexerId ?? 0);
var queuedItemCustomFormats = _formatService.ParseCustomFormat(remoteBook, (long)queueItem.Size);
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
new List<QualityModel> { remoteBook.ParsedBookInfo.Quality },
queuedItemPreferredWordScore,
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore))
queuedItemCustomFormats,
subject.ParsedBookInfo.Quality))
{
return Decision.Reject("Release in queue already meets cutoff: {0}", remoteBook.ParsedBookInfo.Quality);
}
@@ -69,9 +69,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (!_upgradableSpecification.IsUpgradable(qualityProfile,
remoteBook.ParsedBookInfo.Quality,
queuedItemPreferredWordScore,
queuedItemCustomFormats,
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore))
subject.CustomFormats))
{
return Decision.Reject("Release in queue is of equal or higher preference: {0}", remoteBook.ParsedBookInfo.Quality);
}
@@ -80,7 +80,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile,
remoteBook.ParsedBookInfo.Quality,
subject.ParsedBookInfo.Quality))
queuedItemCustomFormats,
subject.ParsedBookInfo.Quality,
subject.CustomFormats))
{
return Decision.Reject("Another release is queued and the Quality profile does not allow upgrades");
}
@@ -32,12 +32,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
var title = subject.Release.Title;
var releaseProfiles = _releaseProfileService.EnabledForTags(subject.Author.Tags, subject.Release.IndexerId);
var required = releaseProfiles.Where(r => r.Required.IsNotNullOrWhiteSpace());
var ignored = releaseProfiles.Where(r => r.Ignored.IsNotNullOrWhiteSpace());
var required = releaseProfiles.Where(r => r.Required.Any());
var ignored = releaseProfiles.Where(r => r.Ignored.Any());
foreach (var r in required)
{
var requiredTerms = r.Required.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
var requiredTerms = r.Required;
var foundTerms = ContainsAny(requiredTerms, title);
if (foundTerms.Empty())
@@ -50,7 +50,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
foreach (var r in ignored)
{
var ignoredTerms = r.Ignored.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
var ignoredTerms = r.Ignored;
var foundTerms = ContainsAny(ignoredTerms, title);
if (foundTerms.Any())
@@ -5,7 +5,6 @@ using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
@@ -16,21 +15,18 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
private readonly IUpgradableSpecification _upgradableSpecification;
private readonly IDelayProfileService _delayProfileService;
private readonly IMediaFileService _mediaFileService;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger;
public DelaySpecification(IPendingReleaseService pendingReleaseService,
IUpgradableSpecification qualityUpgradableSpecification,
IDelayProfileService delayProfileService,
IMediaFileService mediaFileService,
IPreferredWordService preferredWordServiceCalculator,
Logger logger)
{
_pendingReleaseService = pendingReleaseService;
_upgradableSpecification = qualityUpgradableSpecification;
_delayProfileService = delayProfileService;
_mediaFileService = mediaFileService;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger;
}
@@ -80,13 +76,29 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
}
// If quality meets or exceeds the best allowed quality in the profile accept it immediately
var bestQualityInProfile = qualityProfile.LastAllowedQuality();
var isBestInProfile = qualityComparer.Compare(subject.ParsedBookInfo.Quality.Quality, bestQualityInProfile) >= 0;
if (isBestInProfile && isPreferredProtocol)
if (delayProfile.BypassIfHighestQuality)
{
var bestQualityInProfile = qualityProfile.LastAllowedQuality();
var isBestInProfile = qualityComparer.Compare(subject.ParsedBookInfo.Quality.Quality, bestQualityInProfile) >= 0;
if (isBestInProfile && isPreferredProtocol)
{
_logger.Debug("Quality is highest in profile for preferred protocol, will not delay");
return Decision.Accept();
}
}
// If quality meets or exceeds the best allowed quality in the profile accept it immediately
if (delayProfile.BypassIfAboveCustomFormatScore)
{
var score = subject.CustomFormatScore;
var minimum = delayProfile.MinimumCustomFormatScore;
if (score >= minimum && isPreferredProtocol)
{
_logger.Debug("Custom format score ({0}) meets minimum ({1}) for preferred protocol, will not delay", score, minimum);
return Decision.Accept();
}
}
var bookIds = subject.Books.Select(e => e.Id);
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.History;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
@@ -15,20 +16,20 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{
private readonly IHistoryService _historyService;
private readonly UpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly IConfigService _configService;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger;
public HistorySpecification(IHistoryService historyService,
UpgradableSpecification qualityUpgradableSpecification,
IConfigService configService,
IPreferredWordService preferredWordServiceCalculator,
Logger logger)
UpgradableSpecification upgradableSpecification,
ICustomFormatCalculationService formatService,
IConfigService configService,
Logger logger)
{
_historyService = historyService;
_upgradableSpecification = qualityUpgradableSpecification;
_upgradableSpecification = upgradableSpecification;
_formatService = formatService;
_configService = configService;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger;
}
@@ -60,23 +61,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
continue;
}
// The author will be the same as the one in history since it's the same book.
// Instead of fetching the author from the DB reuse the known author.
var preferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Author, mostRecent.SourceTitle, subject.Release?.IndexerId ?? 0);
var customFormats = _formatService.ParseCustomFormat(mostRecent, subject.Author);
// The series will be the same as the one in history since it's the same episode.
// Instead of fetching the series from the DB reuse the known series.
var cutoffUnmet = _upgradableSpecification.CutoffNotMet(
subject.Author.QualityProfile,
new List<QualityModel> { mostRecent.Quality },
preferredWordScore,
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore);
customFormats,
subject.ParsedBookInfo.Quality);
var upgradeable = _upgradableSpecification.IsUpgradable(
subject.Author.QualityProfile,
mostRecent.Quality,
preferredWordScore,
customFormats,
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore);
subject.CustomFormats);
if (!cutoffUnmet)
{
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities;
@@ -9,11 +11,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
public interface IUpgradableSpecification
{
bool IsUpgradable(QualityProfile profile, QualityModel currentQualities, int currentScore, QualityModel newQuality, int newScore);
bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, List<CustomFormat> currentCustomFormats, QualityModel newQuality, List<CustomFormat> newCustomFormats);
bool QualityCutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool CutoffNotMet(QualityProfile profile, List<QualityModel> currentQualities, int currentScore, QualityModel newQuality = null, int newScore = 0);
bool CutoffNotMet(QualityProfile profile, List<QualityModel> currentQualities, List<CustomFormat> currentFormats, QualityModel newQuality = null);
bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality);
bool IsUpgradeAllowed(QualityProfile qualityProfile, QualityModel currentQuality, QualityModel newQuality);
bool IsUpgradeAllowed(QualityProfile qualityProfile, QualityModel currentQuality, List<CustomFormat> currentCustomFormats, QualityModel newQuality, List<CustomFormat> newCustomFormats);
}
public class UpgradableSpecification : IUpgradableSpecification
@@ -61,14 +63,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return ProfileComparisonResult.Upgrade;
}
private bool IsPreferredWordUpgradable(int currentScore, int newScore)
{
_logger.Debug("Comparing preferred word score. Current: {0} New: {1}", currentScore, newScore);
return newScore > currentScore;
}
public bool IsUpgradable(QualityProfile qualityProfile, QualityModel currentQualities, int currentScore, QualityModel newQuality, int newScore)
public bool IsUpgradable(QualityProfile qualityProfile, QualityModel currentQualities, List<CustomFormat> currentCustomFormats, QualityModel newQuality, List<CustomFormat> newCustomFormats)
{
var qualityUpgrade = IsQualityUpgradable(qualityProfile, currentQualities, newQuality);
@@ -84,19 +79,26 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false;
}
if (!IsPreferredWordUpgradable(currentScore, newScore))
var currentFormatScore = qualityProfile.CalculateCustomFormatScore(currentCustomFormats);
var newFormatScore = qualityProfile.CalculateCustomFormatScore(newCustomFormats);
if (newFormatScore <= currentFormatScore)
{
_logger.Debug("Existing item has a better preferred word score, skipping");
_logger.Debug("New item's custom formats [{0}] do not improve on [{1}], skipping",
newCustomFormats.ConcatToString(),
currentCustomFormats.ConcatToString());
return false;
}
_logger.Debug("New item has a better preferred word score");
_logger.Debug("New item has a better custom format score");
return true;
}
public bool QualityCutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null)
{
var cutoffCompare = new QualityModelComparer(profile).Compare(currentQuality.Quality.Id, profile.Cutoff);
var cutoff = profile.UpgradeAllowed ? profile.Cutoff : profile.FirstAllowedQuality().Id;
var cutoffCompare = new QualityModelComparer(profile).Compare(currentQuality.Quality.Id, cutoff);
if (cutoffCompare < 0)
{
@@ -111,7 +113,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false;
}
public bool CutoffNotMet(QualityProfile profile, List<QualityModel> currentQualities, int currentScore, QualityModel newQuality = null, int newScore = 0)
private bool CustomFormatCutoffNotMet(QualityProfile profile, List<CustomFormat> currentFormats)
{
var score = profile.CalculateCustomFormatScore(currentFormats);
return score < profile.CutoffFormatScore;
}
public bool CutoffNotMet(QualityProfile profile, List<QualityModel> currentQualities, List<CustomFormat> currentFormats, QualityModel newQuality = null)
{
foreach (var quality in currentQualities)
{
@@ -121,7 +129,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
if (IsPreferredWordUpgradable(currentScore, newScore))
if (CustomFormatCutoffNotMet(profile, currentFormats))
{
return true;
}
@@ -145,16 +153,23 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return false;
}
public bool IsUpgradeAllowed(QualityProfile qualityProfile, QualityModel currentQuality, QualityModel newQuality)
public bool IsUpgradeAllowed(QualityProfile qualityProfile, QualityModel currentQuality, List<CustomFormat> currentCustomFormats, QualityModel newQuality, List<CustomFormat> newCustomFormats)
{
var isQualityUpgrade = IsQualityUpgradable(qualityProfile, currentQuality, newQuality);
var isCustomFormatUpgrade = qualityProfile.CalculateCustomFormatScore(newCustomFormats) > qualityProfile.CalculateCustomFormatScore(currentCustomFormats);
return CheckUpgradeAllowed(qualityProfile, isQualityUpgrade);
return CheckUpgradeAllowed(qualityProfile, isQualityUpgrade, isCustomFormatUpgrade);
}
private bool CheckUpgradeAllowed(QualityProfile qualityProfile, ProfileComparisonResult isQualityUpgrade)
private bool CheckUpgradeAllowed(QualityProfile qualityProfile, ProfileComparisonResult isQualityUpgrade, bool isCustomFormatUpgrade)
{
if (isQualityUpgrade == ProfileComparisonResult.Upgrade && !qualityProfile.UpgradeAllowed)
if ((isQualityUpgrade == ProfileComparisonResult.Upgrade || isCustomFormatUpgrade) && qualityProfile.UpgradeAllowed)
{
_logger.Debug("Quality profile allows upgrading");
return true;
}
if ((isQualityUpgrade == ProfileComparisonResult.Upgrade || isCustomFormatUpgrade) && !qualityProfile.UpgradeAllowed)
{
_logger.Debug("Quality profile does not allow upgrades, skipping");
return false;
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
@@ -11,12 +12,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public class UpgradeAllowedSpecification : IDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger;
public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecification,
Logger logger)
Logger logger,
ICustomFormatCalculationService formatService)
{
_upgradableSpecification = upgradableSpecification;
_formatService = formatService;
_logger = logger;
}
@@ -35,11 +39,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
continue;
}
var fileCustomFormats = _formatService.ParseCustomFormat(file, subject.Author);
_logger.Debug("Comparing file quality with report. Existing files contain {0}", file.Quality);
if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile,
file.Quality,
subject.ParsedBookInfo.Quality))
fileCustomFormats,
subject.ParsedBookInfo.Quality,
subject.CustomFormats))
{
_logger.Debug("Upgrading is not allowed by the quality profile");
@@ -1,25 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class UpgradeDiskSpecification : IDecisionEngineSpecification
{
private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger;
public UpgradeDiskSpecification(UpgradableSpecification qualityUpgradableSpecification,
IPreferredWordService preferredWordServiceCalculator,
ICacheManager cacheManager,
ICustomFormatCalculationService formatService,
Logger logger)
{
_upgradableSpecification = qualityUpgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_formatService = formatService;
_logger = logger;
}
@@ -35,13 +35,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return Decision.Accept();
}
var customFormats = _formatService.ParseCustomFormat(file);
if (!_upgradableSpecification.IsUpgradable(subject.Author.QualityProfile,
file.Quality,
_preferredWordServiceCalculator.Calculate(subject.Author, file.GetSceneOrFileName(), subject.Release?.IndexerId ?? 0),
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore))
file.Quality,
customFormats,
subject.ParsedBookInfo.Quality,
subject.CustomFormats))
{
return Decision.Reject("Existing files on disk is of equal or higher preference: {0}", file.Quality);
return Decision.Reject("Existing files on disk is of equal or higher preference: {0}", file.Quality.Quality.Name);
}
}