mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-21 05:53:33 -07:00
rewrite of indexer/episode search
This commit is contained in:
parent
9ae21cf7a1
commit
a6a4932b44
114 changed files with 2045 additions and 5577 deletions
|
@ -1,11 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public class DownloadDecision
|
||||
{
|
||||
public EpisodeParseResult ParseResult { get; private set; }
|
||||
public IEnumerable<string> Rejections { get; private set; }
|
||||
|
||||
public bool Approved
|
||||
{
|
||||
get
|
||||
|
@ -14,9 +17,23 @@ namespace NzbDrone.Core.DecisionEngine
|
|||
}
|
||||
}
|
||||
|
||||
public DownloadDecision(params string[] rejections)
|
||||
public DownloadDecision(EpisodeParseResult parseResult, params string[] rejections)
|
||||
{
|
||||
ParseResult = parseResult;
|
||||
Rejections = rejections.ToList();
|
||||
}
|
||||
|
||||
|
||||
public static EpisodeParseResult PickBestReport(IEnumerable<DownloadDecision> downloadDecisions)
|
||||
{
|
||||
var reports = downloadDecisions
|
||||
.Where(c => c.Approved)
|
||||
.Select(c => c.ParseResult)
|
||||
.OrderByDescending(c => c.Quality)
|
||||
.ThenBy(c => c.EpisodeNumbers.MinOrDefault())
|
||||
.ThenBy(c => c.Age);
|
||||
|
||||
return reports.SingleOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
64
NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs
Normal file
64
NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications.Search;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public interface IMakeDownloadDecision
|
||||
{
|
||||
IEnumerable<DownloadDecision> GetRssDecision(IEnumerable<EpisodeParseResult> episodeParseResults);
|
||||
IEnumerable<DownloadDecision> GetSearchDecision(IEnumerable<EpisodeParseResult> episodeParseResult, SearchDefinitionBase searchDefinitionBase);
|
||||
}
|
||||
|
||||
public class DownloadDecisionMaker : IMakeDownloadDecision
|
||||
{
|
||||
private readonly IEnumerable<IRejectWithReason> _specifications;
|
||||
|
||||
public DownloadDecisionMaker(IEnumerable<IRejectWithReason> specifications)
|
||||
{
|
||||
_specifications = specifications;
|
||||
}
|
||||
|
||||
public IEnumerable<DownloadDecision> GetRssDecision(IEnumerable<EpisodeParseResult> episodeParseResults)
|
||||
{
|
||||
foreach (var parseResult in episodeParseResults)
|
||||
{
|
||||
parseResult.Decision = new DownloadDecision(parseResult, GetGeneralRejectionReasons(parseResult).ToArray());
|
||||
yield return parseResult.Decision;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<DownloadDecision> GetSearchDecision(IEnumerable<EpisodeParseResult> episodeParseResults, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
foreach (var parseResult in episodeParseResults)
|
||||
{
|
||||
var generalReasons = GetGeneralRejectionReasons(parseResult);
|
||||
var searchReasons = GetSearchRejectionReasons(parseResult, searchDefinitionBase);
|
||||
|
||||
parseResult.Decision = new DownloadDecision(parseResult, generalReasons.Union(searchReasons).ToArray());
|
||||
|
||||
yield return parseResult.Decision;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private IEnumerable<string> GetGeneralRejectionReasons(EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
return _specifications
|
||||
.OfType<IDecisionEngineSpecification>()
|
||||
.Where(spec => !spec.IsSatisfiedBy(episodeParseResult))
|
||||
.Select(spec => spec.RejectionReason);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetSearchRejectionReasons(EpisodeParseResult episodeParseResult, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
return _specifications
|
||||
.OfType<IDecisionEngineSearchSpecification>()
|
||||
.Where(spec => !spec.IsSatisfiedBy(episodeParseResult, searchDefinitionBase))
|
||||
.Select(spec => spec.RejectionReason);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public interface IDownloadDirector
|
||||
{
|
||||
DownloadDecision GetDownloadDecision(EpisodeParseResult episodeParseResult);
|
||||
}
|
||||
|
||||
public class DownloadDirector : IDownloadDirector
|
||||
{
|
||||
private readonly IEnumerable<IFetchableSpecification> _specifications;
|
||||
|
||||
public DownloadDirector(IEnumerable<IFetchableSpecification> specifications)
|
||||
{
|
||||
_specifications = specifications;
|
||||
}
|
||||
|
||||
public DownloadDecision GetDownloadDecision(EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
var rejections = _specifications
|
||||
.Where(spec => !spec.IsSatisfiedBy(episodeParseResult))
|
||||
.Select(spec => spec.RejectionReason).ToArray();
|
||||
|
||||
episodeParseResult.Decision = new DownloadDecision(rejections);
|
||||
|
||||
return episodeParseResult.Decision;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,9 +2,8 @@ using NzbDrone.Core.Model;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public interface IFetchableSpecification
|
||||
public interface IDecisionEngineSpecification : IRejectWithReason
|
||||
{
|
||||
string RejectionReason { get; }
|
||||
bool IsSatisfiedBy(EpisodeParseResult subject);
|
||||
}
|
||||
}
|
7
NzbDrone.Core/DecisionEngine/IRejectWithReason.cs
Normal file
7
NzbDrone.Core/DecisionEngine/IRejectWithReason.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public interface IRejectWithReason
|
||||
{
|
||||
string RejectionReason { get; }
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ using NzbDrone.Core.Tv;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class AcceptableSizeSpecification : IFetchableSpecification
|
||||
public class AcceptableSizeSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IQualitySizeService _qualityTypeProvider;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
|
|
|
@ -5,7 +5,7 @@ using NzbDrone.Core.Model;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class AllowedReleaseGroupSpecification : IFetchableSpecification
|
||||
public class AllowedReleaseGroupSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
|
|
@ -4,7 +4,7 @@ using NzbDrone.Core.Model;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class CustomStartDateSpecification : IFetchableSpecification
|
||||
public class CustomStartDateSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ using NzbDrone.Core.Model;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class LanguageSpecification : IFetchableSpecification
|
||||
public class LanguageSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ using NzbDrone.Core.Tv;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class MonitoredEpisodeSpecification : IFetchableSpecification
|
||||
public class MonitoredEpisodeSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly ISeriesRepository _seriesRepository;
|
||||
|
|
|
@ -4,7 +4,7 @@ using NzbDrone.Core.Model;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class NotInQueueSpecification : IFetchableSpecification
|
||||
public class NotInQueueSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ using NzbDrone.Core.Model;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class QualityAllowedByProfileSpecification : IFetchableSpecification
|
||||
public class QualityAllowedByProfileSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ using NzbDrone.Core.Model;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class RetentionSpecification : IFetchableSpecification
|
||||
public class RetentionSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class DailyEpisodeMatchSpecification : IDecisionEngineSearchSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
|
||||
public DailyEpisodeMatchSpecification(Logger logger, IEpisodeService episodeService)
|
||||
{
|
||||
_logger = logger;
|
||||
_episodeService = episodeService;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Episode doesn't match";
|
||||
}
|
||||
}
|
||||
public bool IsSatisfiedBy(EpisodeParseResult episodeParseResult, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
var dailySearchSpec = searchDefinitionBase as DailyEpisodeSearchDefinition;
|
||||
|
||||
if (dailySearchSpec == null) return true;
|
||||
|
||||
var episode = _episodeService.GetEpisode(dailySearchSpec.SeriesId, dailySearchSpec.Airtime);
|
||||
|
||||
if (!episodeParseResult.AirDate.HasValue || episodeParseResult.AirDate.Value != episode.AirDate.Value)
|
||||
{
|
||||
_logger.Trace("Episode AirDate does not match searched episode number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public interface IDecisionEngineSearchSpecification : IRejectWithReason
|
||||
{
|
||||
bool IsSatisfiedBy(EpisodeParseResult subject, SearchDefinitionBase searchDefinitionBase);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class SeasonMatchSpecification : IDecisionEngineSearchSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SeasonMatchSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Episode doesn't match";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSatisfiedBy(EpisodeParseResult episodeParseResult, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
var singleEpisodeSpec = searchDefinitionBase as SeasonSearchDefinition;
|
||||
if (singleEpisodeSpec == null) return true;
|
||||
|
||||
if (singleEpisodeSpec.SeasonNumber != episodeParseResult.SeasonNumber)
|
||||
{
|
||||
_logger.Trace("Season number does not match searched season number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class SingleEpisodeMatchSpecification : IDecisionEngineSearchSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SingleEpisodeMatchSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Episode doesn't match";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSatisfiedBy(EpisodeParseResult episodeParseResult, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
var singleEpisodeSpec = searchDefinitionBase as SingleEpisodeSearchDefinition;
|
||||
if (singleEpisodeSpec == null) return true;
|
||||
|
||||
if (singleEpisodeSpec.SeasonNumber != episodeParseResult.SeasonNumber)
|
||||
{
|
||||
_logger.Trace("Season number does not match searched season number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!episodeParseResult.EpisodeNumbers.Contains(singleEpisodeSpec.EpisodeNumber))
|
||||
{
|
||||
_logger.Trace("Episode number does not match searched episode number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ using NzbDrone.Core.Tv;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class UpgradeDiskSpecification : IFetchableSpecification
|
||||
public class UpgradeDiskSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly Logger _logger;
|
||||
|
|
|
@ -4,7 +4,7 @@ using NzbDrone.Core.Model;
|
|||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class UpgradeHistorySpecification : IFetchableSpecification
|
||||
public class UpgradeHistorySpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue