mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-22 22:14:44 -04:00
Implement Release Parsing, Decision Engine, and Downloading (#35)
* Implement Parsing, Decision Engine, and Downloading
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
@@ -12,29 +11,21 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
public class AcceptableSizeSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IQualityDefinitionService _qualityDefinitionService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AcceptableSizeSpecification(IQualityDefinitionService qualityDefinitionService, IEpisodeService episodeService, Logger logger)
|
||||
public AcceptableSizeSpecification(IQualityDefinitionService qualityDefinitionService, Logger logger)
|
||||
{
|
||||
_qualityDefinitionService = qualityDefinitionService;
|
||||
_episodeService = episodeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Beginning size check for: {0}", subject);
|
||||
|
||||
var quality = subject.ParsedEpisodeInfo.Quality.Quality;
|
||||
|
||||
if (subject.ParsedEpisodeInfo.Special)
|
||||
{
|
||||
_logger.Debug("Special release found, skipping size check.");
|
||||
return Decision.Accept();
|
||||
}
|
||||
var quality = subject.ParsedAlbumInfo.Quality.Quality;
|
||||
|
||||
if (subject.Release.Size == 0)
|
||||
{
|
||||
@@ -43,20 +34,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
}
|
||||
|
||||
var qualityDefinition = _qualityDefinitionService.Get(quality);
|
||||
var albumsDuration = subject.Albums.Sum(album => album.Duration) / 60000;
|
||||
|
||||
if (qualityDefinition.MinSize.HasValue)
|
||||
{
|
||||
var minSize = qualityDefinition.MinSize.Value.Megabytes();
|
||||
|
||||
//Multiply maxSize by Series.Runtime
|
||||
minSize = minSize * subject.Series.Runtime * subject.Episodes.Count;
|
||||
//Multiply minSize by Album.Duration
|
||||
minSize = minSize * albumsDuration;
|
||||
|
||||
//If the parsed size is smaller than minSize we don't want it
|
||||
if (subject.Release.Size < minSize)
|
||||
{
|
||||
var runtimeMessage = subject.Episodes.Count == 1 ? $"{subject.Series.Runtime}min" : $"{subject.Episodes.Count}x {subject.Series.Runtime}min";
|
||||
var runtimeMessage = $"{albumsDuration}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);
|
||||
_logger.Debug("Item: {0}, Size: {1} is smaller than minimum allowed size ({2} bytes), rejecting.", subject, subject.Release.Size, minSize);
|
||||
return Decision.Reject("{0} is smaller than minimum allowed {1}", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix());
|
||||
}
|
||||
}
|
||||
if (!qualityDefinition.MaxSize.HasValue || qualityDefinition.MaxSize.Value == 0)
|
||||
@@ -67,40 +60,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
var maxSize = qualityDefinition.MaxSize.Value.Megabytes();
|
||||
|
||||
//Multiply maxSize by Series.Runtime
|
||||
maxSize = maxSize * subject.Series.Runtime * subject.Episodes.Count;
|
||||
|
||||
if (subject.Episodes.Count == 1)
|
||||
{
|
||||
Episode episode = subject.Episodes.First();
|
||||
List<Episode> seasonEpisodes;
|
||||
|
||||
var seasonSearchCriteria = searchCriteria as SeasonSearchCriteria;
|
||||
if (seasonSearchCriteria != null && !seasonSearchCriteria.Series.UseSceneNumbering && seasonSearchCriteria.Episodes.Any(v => v.Id == episode.Id))
|
||||
{
|
||||
seasonEpisodes = (searchCriteria as SeasonSearchCriteria).Episodes;
|
||||
}
|
||||
else
|
||||
{
|
||||
seasonEpisodes = _episodeService.GetEpisodesBySeason(episode.SeriesId, episode.SeasonNumber);
|
||||
}
|
||||
|
||||
//Ensure that this is either the first episode
|
||||
//or is the last episode in a season that has 10 or more episodes
|
||||
if (seasonEpisodes.First().Id == episode.Id || (seasonEpisodes.Count() >= 10 && seasonEpisodes.Last().Id == episode.Id))
|
||||
{
|
||||
_logger.Debug("Possible double episode, doubling allowed size.");
|
||||
maxSize = maxSize * 2;
|
||||
}
|
||||
}
|
||||
//Multiply maxSize by Album.Duration
|
||||
maxSize = maxSize * albumsDuration;
|
||||
|
||||
//If the parsed size is greater than maxSize we don't want it
|
||||
if (subject.Release.Size > maxSize)
|
||||
{
|
||||
var runtimeMessage = subject.Episodes.Count == 1 ? $"{subject.Series.Runtime}min" : $"{subject.Episodes.Count}x {subject.Series.Runtime}min";
|
||||
var runtimeMessage = $"{albumsDuration}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);
|
||||
_logger.Debug("Item: {0}, Size: {1} is greater than maximum allowed size ({2}), rejecting.", subject, subject.Release.Size, maxSize);
|
||||
return Decision.Reject("{0} is larger than maximum allowed {1}", subject.Release.Size.SizeSuffix(), maxSize.SizeSuffix());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class AnimeVersionUpgradeSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AnimeVersionUpgradeSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger)
|
||||
{
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var releaseGroup = subject.ParsedEpisodeInfo.ReleaseGroup;
|
||||
|
||||
if (subject.Series.SeriesType != SeriesTypes.Anime)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
|
||||
{
|
||||
if (_qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
if (releaseGroup.IsNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Debug("Unable to compare release group, release's release group is unknown");
|
||||
return Decision.Reject("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 Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Blacklisting;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -18,9 +18,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (_blacklistService.Blacklisted(subject.Series.Id, subject.Release))
|
||||
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (_blacklistService.Blacklisted(subject.Artist.Id, subject.Release))
|
||||
{
|
||||
_logger.Debug("{0} is blacklisted, rejecting.", subject.Release.Title);
|
||||
return Decision.Reject("Release is blacklisted");
|
||||
|
||||
@@ -1,39 +1,46 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class CutoffSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public CutoffSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger)
|
||||
public CutoffSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger, IMediaFileService mediaFileService)
|
||||
{
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_logger = logger;
|
||||
_mediaFileService = mediaFileService;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
_logger.Debug("File is no longer available, skipping this file.");
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality);
|
||||
|
||||
if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||
foreach (var album in subject.Albums)
|
||||
{
|
||||
var trackFiles = _mediaFileService.GetFilesByAlbum(album.ArtistId, album.Id);
|
||||
|
||||
if (trackFiles.Any())
|
||||
{
|
||||
_logger.Debug("Cutoff already met, rejecting.");
|
||||
return Decision.Reject("Existing file meets cutoff: {0}", subject.Series.Profile.Value.Cutoff);
|
||||
var lowestQuality = trackFiles.Select(c => c.Quality).OrderBy(c => c.Quality.Id).First();
|
||||
|
||||
_logger.Debug("Comparing file quality with report. Existing file is {0}", lowestQuality);
|
||||
|
||||
if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Artist.Profile, lowestQuality, subject.ParsedAlbumInfo.Quality))
|
||||
{
|
||||
_logger.Debug("Cutoff already met, rejecting.");
|
||||
return Decision.Reject("Existing file meets cutoff: {0}", subject.Artist.Profile.Value.Cutoff);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class DiscographySpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DiscographySpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class FullSeasonSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
|
||||
public FullSeasonSpecification(Logger logger, IEpisodeService episodeService)
|
||||
{
|
||||
_logger = logger;
|
||||
_episodeService = episodeService;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.ParsedEpisodeInfo.FullSeason)
|
||||
{
|
||||
_logger.Debug("Checking if all episodes in full season release have aired. {0}", subject.Release.Title);
|
||||
|
||||
if (subject.Episodes.Any(e => !e.AirDateUtc.HasValue || e.AirDateUtc.Value.After(DateTime.UtcNow)))
|
||||
{
|
||||
_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 Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
@@ -15,16 +15,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var wantedLanguage = subject.Series.Profile.Value.Language;
|
||||
|
||||
_logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language);
|
||||
var wantedLanguage = subject.Artist.Profile.Value.Language;
|
||||
|
||||
if (subject.ParsedEpisodeInfo.Language != wantedLanguage)
|
||||
_logger.Debug("Checking if report meets language requirements. {0}", subject.ParsedAlbumInfo.Language);
|
||||
|
||||
if (subject.ParsedAlbumInfo.Language != wantedLanguage)
|
||||
{
|
||||
_logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.ParsedEpisodeInfo.Language, wantedLanguage);
|
||||
return Decision.Reject("{0} is wanted, but found {1}", wantedLanguage, subject.ParsedEpisodeInfo.Language);
|
||||
_logger.Debug("Report Language: {0} rejected because it is not wanted, wanted {1}", subject.ParsedAlbumInfo.Language, wantedLanguage);
|
||||
return Decision.Reject("{0} is wanted, but found {1}", wantedLanguage, subject.ParsedAlbumInfo.Language);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NLog;
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
@@ -15,15 +16,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 70.Megabytes())
|
||||
{
|
||||
_logger.Debug("Sample release, rejecting.");
|
||||
return Decision.Reject("Sample");
|
||||
}
|
||||
if (subject.Release.Title.ToLower().Contains("sample") && subject.Release.Size < 20.Megabytes())
|
||||
{
|
||||
_logger.Debug("Sample release, rejecting.");
|
||||
return Decision.Reject("Sample");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -20,20 +20,20 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var delayProfile = _delayProfileService.BestForTags(subject.Series.Tags);
|
||||
var delayProfile = _delayProfileService.BestForTags(subject.Artist.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");
|
||||
_logger.Debug("[{0}] Usenet is not enabled for this artist", subject.Release.Title);
|
||||
return Decision.Reject("Usenet is not enabled for this artist");
|
||||
}
|
||||
|
||||
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");
|
||||
_logger.Debug("[{0}] Torrent is not enabled for this artist", subject.Release.Title);
|
||||
return Decision.Reject("Torrent is not enabled for this artist");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
@@ -15,13 +15,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedEpisodeInfo.Quality);
|
||||
if (!subject.Series.Profile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedEpisodeInfo.Quality.Quality))
|
||||
_logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedAlbumInfo.Quality);
|
||||
if (!subject.Artist.Profile.Value.Items.Exists(v => v.Allowed && v.Quality == subject.ParsedAlbumInfo.Quality.Quality))
|
||||
{
|
||||
_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);
|
||||
_logger.Debug("Quality {0} rejected by Artist's quality profile", subject.ParsedAlbumInfo.Quality);
|
||||
return Decision.Reject("{0} is not wanted in profile", subject.ParsedAlbumInfo.Quality.Quality);
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -23,32 +24,33 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var queue = _queueService.GetQueue()
|
||||
.Select(q => q.RemoteEpisode).ToList();
|
||||
.Select(q => q.RemoteAlbum).ToList();
|
||||
|
||||
var matchingSeries = queue.Where(q => q.Series.Id == subject.Series.Id);
|
||||
var matchingEpisode = matchingSeries.Where(q => q.Episodes.Select(e => e.Id).Intersect(subject.Episodes.Select(e => e.Id)).Any());
|
||||
var matchingArtist = queue.Where(q => q.Artist.Id == subject.Artist.Id);
|
||||
var matchingAlbum = matchingArtist.Where(q => q.Albums.Select(e => e.Id).Intersect(subject.Albums.Select(e => e.Id)).Any());
|
||||
|
||||
foreach (var remoteEpisode in matchingEpisode)
|
||||
foreach (var remoteAlbum in matchingAlbum)
|
||||
{
|
||||
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
|
||||
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteAlbum.ParsedAlbumInfo.Quality);
|
||||
|
||||
if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, remoteEpisode.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||
if (!_qualityUpgradableSpecification.CutoffNotMet(subject.Artist.Profile, remoteAlbum.ParsedAlbumInfo.Quality, subject.ParsedAlbumInfo.Quality))
|
||||
{
|
||||
return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
|
||||
return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteAlbum.ParsedAlbumInfo.Quality);
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
|
||||
_logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteAlbum.ParsedAlbumInfo.Quality);
|
||||
|
||||
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, remoteEpisode.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Artist.Profile, remoteAlbum.ParsedAlbumInfo.Quality, subject.ParsedAlbumInfo.Quality))
|
||||
{
|
||||
return Decision.Reject("Quality for release in queue is of equal or higher preference: {0}", remoteEpisode.ParsedEpisodeInfo.Quality);
|
||||
return Decision.Reject("Quality for release in queue is of equal or higher preference: {0}", remoteAlbum.ParsedAlbumInfo.Quality);
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
@@ -8,9 +8,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RawDiskSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private static readonly string[] _dvdContainerTypes = new[] { "vob", "iso" };
|
||||
|
||||
private static readonly string[] _blurayContainerTypes = new[] { "m2ts" };
|
||||
private static readonly string[] _cdContainerTypes = new[] { "vob", "iso" };
|
||||
|
||||
private readonly Logger _logger;
|
||||
|
||||
@@ -21,24 +19,18 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release == null || subject.Release.Container.IsNullOrWhiteSpace())
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
if (_dvdContainerTypes.Contains(subject.Release.Container.ToLower()))
|
||||
{
|
||||
_logger.Debug("Release contains raw DVD, rejecting.");
|
||||
return Decision.Reject("Raw DVD release");
|
||||
}
|
||||
|
||||
if (_blurayContainerTypes.Contains(subject.Release.Container.ToLower()))
|
||||
{
|
||||
_logger.Debug("Release contains raw Bluray, rejecting.");
|
||||
return Decision.Reject("Raw Bluray release");
|
||||
}
|
||||
if (_cdContainerTypes.Contains(subject.Release.Container.ToLower()))
|
||||
{
|
||||
_logger.Debug("Release contains raw CD, rejecting.");
|
||||
return Decision.Reject("Raw CD release");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
@@ -22,19 +22,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Checking if release meets restrictions: {0}", subject);
|
||||
|
||||
var title = subject.Release.Title;
|
||||
var restrictions = _restrictionService.AllForTags(subject.Series.Tags);
|
||||
var restrictions = _restrictionService.AllForTags(subject.Artist.Tags);
|
||||
|
||||
var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace());
|
||||
var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace());
|
||||
|
||||
foreach (var r in required)
|
||||
{
|
||||
var requiredTerms = r.Required.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
var requiredTerms = r.Required.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
|
||||
var foundTerms = ContainsAny(requiredTerms, title);
|
||||
if (foundTerms.Empty())
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Profiles.Delay;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
@@ -13,22 +14,25 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
private readonly IPendingReleaseService _pendingReleaseService;
|
||||
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly IDelayProfileService _delayProfileService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DelaySpecification(IPendingReleaseService pendingReleaseService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IDelayProfileService delayProfileService,
|
||||
IMediaFileService mediaFileService,
|
||||
Logger logger)
|
||||
{
|
||||
_pendingReleaseService = pendingReleaseService;
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_delayProfileService = delayProfileService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Temporary;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null && searchCriteria.UserInvokedSearch)
|
||||
{
|
||||
@@ -36,8 +40,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var profile = subject.Series.Profile.Value;
|
||||
var delayProfile = _delayProfileService.BestForTags(subject.Series.Tags);
|
||||
var profile = subject.Artist.Profile.Value;
|
||||
var delayProfile = _delayProfileService.BestForTags(subject.Artist.Tags);
|
||||
var delay = delayProfile.GetProtocolDelay(subject.Release.DownloadProtocol);
|
||||
var isPreferredProtocol = subject.Release.DownloadProtocol == delayProfile.PreferredProtocol;
|
||||
|
||||
@@ -51,18 +55,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
if (isPreferredProtocol)
|
||||
{
|
||||
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
|
||||
foreach (var album in subject.Albums)
|
||||
{
|
||||
var upgradable = _qualityUpgradableSpecification.IsUpgradable(profile, file.Quality, subject.ParsedEpisodeInfo.Quality);
|
||||
var trackFiles = _mediaFileService.GetFilesByAlbum(album.ArtistId, album.Id);
|
||||
|
||||
if (upgradable)
|
||||
if (trackFiles.Any())
|
||||
{
|
||||
var revisionUpgrade = _qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality);
|
||||
var lowestQuality = trackFiles.Select(c => c.Quality).OrderBy(c => c.Quality.Id).First();
|
||||
var upgradable = _qualityUpgradableSpecification.IsUpgradable(profile, lowestQuality, subject.ParsedAlbumInfo.Quality);
|
||||
|
||||
if (revisionUpgrade)
|
||||
if (upgradable)
|
||||
{
|
||||
_logger.Debug("New quality is a better revision for existing quality, skipping delay");
|
||||
return Decision.Accept();
|
||||
var revisionUpgrade = _qualityUpgradableSpecification.IsRevisionUpgrade(lowestQuality, subject.ParsedAlbumInfo.Quality);
|
||||
|
||||
if (revisionUpgrade)
|
||||
{
|
||||
_logger.Debug("New quality is a better revision for existing quality, skipping delay");
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +80,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
// If quality meets or exceeds the best allowed quality in the profile accept it immediately
|
||||
var bestQualityInProfile = new QualityModel(profile.LastAllowedQuality());
|
||||
var isBestInProfile = comparer.Compare(subject.ParsedEpisodeInfo.Quality, bestQualityInProfile) >= 0;
|
||||
var isBestInProfile = comparer.Compare(subject.ParsedAlbumInfo.Quality, bestQualityInProfile) >= 0;
|
||||
|
||||
if (isBestInProfile && isPreferredProtocol)
|
||||
{
|
||||
@@ -78,9 +88,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var episodeIds = subject.Episodes.Select(e => e.Id);
|
||||
var albumIds = subject.Albums.Select(e => e.Id);
|
||||
|
||||
var oldest = _pendingReleaseService.OldestPendingRelease(subject.Series.Id, episodeIds);
|
||||
var oldest = _pendingReleaseService.OldestPendingRelease(subject.Artist.Id, albumIds);
|
||||
|
||||
if (oldest != null && oldest.Release.AgeMinutes > delay)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
@@ -28,7 +28,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
@@ -39,16 +39,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
|
||||
|
||||
_logger.Debug("Performing history status check on report");
|
||||
foreach (var episode in subject.Episodes)
|
||||
foreach (var album in subject.Albums)
|
||||
{
|
||||
_logger.Debug("Checking current status of episode [{0}] in history", episode.Id);
|
||||
var mostRecent = _historyService.MostRecentForEpisode(episode.Id);
|
||||
_logger.Debug("Checking current status of album [{0}] in history", album.Id);
|
||||
var mostRecent = _historyService.MostRecentForAlbum(album.Id);
|
||||
|
||||
if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
|
||||
{
|
||||
var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12));
|
||||
var cutoffUnmet = _qualityUpgradableSpecification.CutoffNotMet(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality);
|
||||
var upgradeable = _qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, mostRecent.Quality, subject.ParsedEpisodeInfo.Quality);
|
||||
var cutoffUnmet = _qualityUpgradableSpecification.CutoffNotMet(subject.Artist.Profile, mostRecent.Quality, subject.ParsedAlbumInfo.Quality);
|
||||
var upgradeable = _qualityUpgradableSpecification.IsUpgradable(subject.Artist.Profile, mostRecent.Quality, subject.ParsedAlbumInfo.Quality);
|
||||
|
||||
if (!recent && cdhEnabled)
|
||||
{
|
||||
@@ -59,7 +59,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
if (recent)
|
||||
{
|
||||
return Decision.Reject("Recent grab event in history already meets cutoff: {0}", mostRecent.Quality);
|
||||
return Decision.Reject("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);
|
||||
|
||||
+11
-11
@@ -1,22 +1,22 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
public class MonitoredEpisodeSpecification : IDecisionEngineSpecification
|
||||
public class MonitoredAlbumSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MonitoredEpisodeSpecification(Logger logger)
|
||||
public MonitoredAlbumSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
@@ -27,20 +27,20 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
}
|
||||
}
|
||||
|
||||
if (!subject.Series.Monitored)
|
||||
if (!subject.Artist.Monitored)
|
||||
{
|
||||
_logger.Debug("{0} is present in the DB but not tracked. skipping.", subject.Series);
|
||||
return Decision.Reject("Series is not monitored");
|
||||
_logger.Debug("{0} is present in the DB but not tracked. skipping.", subject.Artist);
|
||||
return Decision.Reject("Artist is not monitored");
|
||||
}
|
||||
|
||||
var monitoredCount = subject.Episodes.Count(episode => episode.Monitored);
|
||||
if (monitoredCount == subject.Episodes.Count)
|
||||
var monitoredCount = subject.Albums.Count(album => album.Monitored);
|
||||
if (monitoredCount == subject.Albums.Count)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Only {0}/{1} episodes are monitored. skipping.", monitoredCount, subject.Episodes.Count);
|
||||
return Decision.Reject("Episode is not monitored");
|
||||
_logger.Debug("Only {0}/{1} albums are monitored. skipping.", monitoredCount, subject.Albums.Count);
|
||||
return Decision.Reject("Album is not monitored");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
@@ -11,39 +12,52 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
|
||||
{
|
||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ProperSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, IConfigService configService, Logger logger)
|
||||
public ProperSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, IConfigService configService, IMediaFileService mediaFileService, Logger logger)
|
||||
{
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_configService = configService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria != null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
|
||||
foreach (var album in subject.Albums)
|
||||
{
|
||||
if (_qualityUpgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||
var trackFiles = _mediaFileService.GetFilesByAlbum(album.ArtistId, album.Id);
|
||||
|
||||
if (trackFiles.Any())
|
||||
{
|
||||
if (file.DateAdded < DateTime.Today.AddDays(-7))
|
||||
var lowestQuality = trackFiles.Select(c => c.Quality).OrderBy(c => c.Quality.Id).First();
|
||||
var dateAdded = trackFiles[0].DateAdded;
|
||||
|
||||
_logger.Debug("Comparing file quality with report. Existing file is {0}", lowestQuality);
|
||||
|
||||
if (_qualityUpgradableSpecification.IsRevisionUpgrade(lowestQuality, subject.ParsedAlbumInfo.Quality))
|
||||
{
|
||||
_logger.Debug("Proper for old file, rejecting: {0}", subject);
|
||||
return Decision.Reject("Proper for old file");
|
||||
if (dateAdded < DateTime.Today.AddDays(-7))
|
||||
{
|
||||
_logger.Debug("Proper for old file, rejecting: {0}", subject);
|
||||
return Decision.Reject("Proper for old file");
|
||||
}
|
||||
|
||||
if (!_configService.AutoDownloadPropers)
|
||||
{
|
||||
_logger.Debug("Auto downloading of propers is disabled");
|
||||
return Decision.Reject("Proper downloading is disabled");
|
||||
}
|
||||
}
|
||||
|
||||
if (!_configService.AutoDownloadPropers)
|
||||
{
|
||||
_logger.Debug("Auto downloading of propers is disabled");
|
||||
return Decision.Reject("Proper downloading is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class SameEpisodesGrabSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly SameEpisodesSpecification _sameEpisodesSpecification;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SameEpisodesGrabSpecification(SameEpisodesSpecification sameEpisodesSpecification, Logger logger)
|
||||
{
|
||||
_sameEpisodesSpecification = sameEpisodesSpecification;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (_sameEpisodesSpecification.IsSatisfiedBy(subject.Episodes))
|
||||
{
|
||||
return Decision.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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class SameTracksGrabSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly SameTracksSpecification _sameTracksSpecification;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SameTracksGrabSpecification(SameTracksSpecification sameTracksSpecification, Logger logger)
|
||||
{
|
||||
_sameTracksSpecification = sameTracksSpecification;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
// TODO: Rework for Tracks if we can parse from release details.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class AlbumRequestedSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AlbumRequestedSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var criteriaAlbum = searchCriteria.Albums.Select(v => v.Id).ToList();
|
||||
var remoteAlbums = remoteAlbum.Albums.Select(v => v.Id).ToList();
|
||||
|
||||
if (!criteriaAlbum.Intersect(remoteAlbums).Any())
|
||||
{
|
||||
_logger.Debug("Release rejected since the album wasn't requested: {0}", remoteAlbum.ParsedAlbumInfo);
|
||||
return Decision.Reject("Album wasn't requested");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class ArtistSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ArtistSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if artist matches searched artist");
|
||||
|
||||
if (remoteAlbum.Artist.Id != searchCriteria.Artist.Id)
|
||||
{
|
||||
_logger.Debug("Artist {0} does not match {1}", remoteAlbum.Artist, searchCriteria.Artist);
|
||||
return Decision.Reject("Wrong artist");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class DailyAudioMatchSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DailyAudioMatchSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
// TODO Rework for Daily Audio/Podcasts
|
||||
}
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class DailyEpisodeMatchSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
|
||||
public DailyEpisodeMatchSpecification(Logger logger, IEpisodeService episodeService)
|
||||
{
|
||||
_logger = logger;
|
||||
_episodeService = episodeService;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var dailySearchSpec = searchCriteria as DailyEpisodeSearchCriteria;
|
||||
|
||||
if (dailySearchSpec == null) return Decision.Accept();
|
||||
|
||||
var episode = _episodeService.GetEpisode(dailySearchSpec.Series.Id, dailySearchSpec.AirDate.ToString(Episode.AIR_DATE_FORMAT));
|
||||
|
||||
if (!remoteEpisode.ParsedEpisodeInfo.IsDaily || remoteEpisode.ParsedEpisodeInfo.AirDate != episode.AirDate)
|
||||
{
|
||||
_logger.Debug("Episode AirDate does not match searched episode number, skipping.");
|
||||
return Decision.Reject("Episode does not match");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class EpisodeRequestedSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public EpisodeRequestedSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var criteriaEpisodes = searchCriteria.Episodes.Select(v => v.Id).ToList();
|
||||
var remoteEpisodes = remoteEpisode.Episodes.Select(v => v.Id).ToList();
|
||||
|
||||
if (!criteriaEpisodes.Intersect(remoteEpisodes).Any())
|
||||
{
|
||||
_logger.Debug("Release rejected since the episode wasn't requested: {0}", remoteEpisode.ParsedEpisodeInfo);
|
||||
return Decision.Reject("Episode wasn't requested");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
@@ -15,23 +16,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var singleEpisodeSpec = searchCriteria as SeasonSearchCriteria;
|
||||
if (singleEpisodeSpec == null) return Decision.Accept();
|
||||
|
||||
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
|
||||
{
|
||||
_logger.Debug("Season number does not match searched season number, skipping.");
|
||||
return Decision.Reject("Wrong season");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class SeriesSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SeriesSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
_logger.Debug("Checking if series matches searched series");
|
||||
|
||||
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 Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class SingleAlbumSearchMatchSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SingleAlbumSearchMatchSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var singleAlbumSpec = searchCriteria as AlbumSearchCriteria;
|
||||
if (singleAlbumSpec == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
if (Parser.Parser.CleanArtistTitle(singleAlbumSpec.AlbumTitle) != Parser.Parser.CleanArtistTitle(remoteAlbum.ParsedAlbumInfo.AlbumTitle))
|
||||
{
|
||||
_logger.Debug("Album does not match searched album title, skipping.");
|
||||
return Decision.Reject("Wrong album");
|
||||
}
|
||||
|
||||
if (!remoteAlbum.ParsedAlbumInfo.AlbumTitle.Any())
|
||||
{
|
||||
_logger.Debug("Full discography result during single album search, skipping.");
|
||||
return Decision.Reject("Full artist pack");
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class SingleEpisodeSearchMatchSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SingleEpisodeSearchMatchSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var singleEpisodeSpec = searchCriteria as SingleEpisodeSearchCriteria;
|
||||
if (singleEpisodeSpec == null) return Decision.Accept();
|
||||
|
||||
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
|
||||
{
|
||||
_logger.Debug("Season number does not match searched season number, skipping.");
|
||||
return Decision.Reject("Wrong season");
|
||||
}
|
||||
|
||||
if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Any())
|
||||
{
|
||||
_logger.Debug("Full season result during single episode search, skipping.");
|
||||
return Decision.Reject("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 Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -1,4 +1,4 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
@@ -15,10 +15,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
|
||||
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
public Decision IsSatisfiedBy(RemoteAlbum remoteAlbum, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var torrentInfo = remoteEpisode.Release as TorrentInfo;
|
||||
var torrentInfo = remoteAlbum.Release as TorrentInfo;
|
||||
|
||||
if (torrentInfo == null)
|
||||
{
|
||||
|
||||
@@ -1,39 +1,44 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class UpgradeDiskSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public UpgradeDiskSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, Logger logger)
|
||||
public UpgradeDiskSpecification(QualityUpgradableSpecification qualityUpgradableSpecification, IMediaFileService mediaFileService, Logger logger)
|
||||
{
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_mediaFileService = mediaFileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
public virtual Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
|
||||
|
||||
foreach (var album in subject.Albums)
|
||||
{
|
||||
if(file == null)
|
||||
var trackFiles = _mediaFileService.GetFilesByAlbum(album.ArtistId, album.Id);
|
||||
|
||||
if (trackFiles.Any())
|
||||
{
|
||||
_logger.Debug("File is no longer available, skipping this file.");
|
||||
continue;
|
||||
var lowestQuality = trackFiles.Select(c => c.Quality).OrderBy(c => c.Quality.Id).First();
|
||||
|
||||
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Artist.Profile, lowestQuality, subject.ParsedAlbumInfo.Quality))
|
||||
{
|
||||
return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0}", lowestQuality);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality);
|
||||
|
||||
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.Profile, file.Quality, subject.ParsedEpisodeInfo.Quality))
|
||||
{
|
||||
return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0}", file.Quality);
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
|
||||
Reference in New Issue
Block a user