decision engine now reports it's own errors rather than just dying.

This commit is contained in:
kay.one 2013-04-28 17:39:17 -07:00
parent 9bbbc19869
commit d6d524e624
15 changed files with 182 additions and 130 deletions

View File

@ -42,8 +42,8 @@ namespace NzbDrone.Api.ErrorManagement
return new ErrorModel() return new ErrorModel()
{ {
Message = exception.Message, Message = exception.Message,
Description = exception.ToString() Description = exception.ToString()
}.AsResponse(HttpStatusCode.InternalServerError); }.AsResponse(HttpStatusCode.InternalServerError);
} }
} }

View File

@ -59,7 +59,6 @@ namespace NzbDrone.Api.Indexers
public Int32 SeasonNumber { get; set; } public Int32 SeasonNumber { get; set; }
public Language Language { get; set; } public Language Language { get; set; }
public DateTime? AirDate { get; set; } public DateTime? AirDate { get; set; }
public String OriginalString { get; set; }
public String SeriesTitle { get; set; } public String SeriesTitle { get; set; }
public int[] EpisodeNumbers { get; set; } public int[] EpisodeNumbers { get; set; }
public Boolean Approved { get; set; } public Boolean Approved { get; set; }

View File

@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
{ {
Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Returns(_fakeMappings); Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Returns(_fakeMappings);
Subject.UpdateMappings(); Subject.Execute(new UpdateSceneMappingCommand());
AssertMappingUpdated(); AssertMappingUpdated();
} }
@ -42,7 +42,7 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Throws(new WebException()); Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Throws(new WebException());
Subject.UpdateMappings(); Subject.Execute(new UpdateSceneMappingCommand());
AssertNoUpdate(); AssertNoUpdate();
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Returns(new List<SceneMapping>()); Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Returns(new List<SceneMapping>());
Subject.UpdateMappings(); Subject.Execute(new UpdateSceneMappingCommand());
AssertNoUpdate(); AssertNoUpdate();

View File

@ -3,34 +3,61 @@ using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DataAugmentation.Scene namespace NzbDrone.Core.DataAugmentation.Scene
{ {
public interface ISceneMappingService public interface ISceneMappingService
{ {
void UpdateMappings(); string GetSceneName(int tvdbId, int seasonNumber = -1);
string GetSceneName(int seriesId, int seasonNumber = -1);
Nullable<int> GetTvDbId(string cleanName); Nullable<int> GetTvDbId(string cleanName);
string GetCleanName(int tvdbId);
} }
public class SceneMappingService : ISceneMappingService,IHandleAsync<ApplicationStartedEvent> public class SceneMappingService : ISceneMappingService,
IHandleAsync<ApplicationStartedEvent>,
IExecute<UpdateSceneMappingCommand>
{ {
private readonly ISceneMappingRepository _repository; private readonly ISceneMappingRepository _repository;
private readonly ISceneMappingProxy _sceneMappingProxy; private readonly ISceneMappingProxy _sceneMappingProxy;
private readonly ISeriesService _seriesService;
private readonly Logger _logger; private readonly Logger _logger;
public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, ISeriesService seriesService, Logger logger) public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, Logger logger)
{ {
_repository = repository; _repository = repository;
_sceneMappingProxy = sceneMappingProxy; _sceneMappingProxy = sceneMappingProxy;
_seriesService = seriesService;
_logger = logger; _logger = logger;
} }
public void UpdateMappings() public string GetSceneName(int tvdbId, int seasonNumber = -1)
{
var mapping = _repository.FindByTvdbId(tvdbId);
if (mapping == null) return null;
return mapping.SceneName;
}
public Nullable<Int32> GetTvDbId(string cleanName)
{
var mapping = _repository.FindByCleanTitle(cleanName);
if (mapping == null)
return null;
return mapping.TvdbId;
}
public void HandleAsync(ApplicationStartedEvent message)
{
if (!_repository.HasItems())
{
UpdateMappings();
}
}
private void UpdateMappings()
{ {
try try
{ {
@ -52,45 +79,9 @@ namespace NzbDrone.Core.DataAugmentation.Scene
} }
} }
public string GetSceneName(int seriesId, int seasonNumber = -1) public void Execute(UpdateSceneMappingCommand message)
{ {
var tvDbId = _seriesService.FindByTvdbId(seriesId).TvdbId; UpdateMappings();
var mapping = _repository.FindByTvdbId(tvDbId);
if (mapping == null) return null;
return mapping.SceneName;
}
public Nullable<Int32> GetTvDbId(string cleanName)
{
var mapping = _repository.FindByCleanTitle(cleanName);
if (mapping == null)
return null;
return mapping.TvdbId;
}
public string GetCleanName(int tvdbId)
{
var mapping = _repository.FindByTvdbId(tvdbId);
if (mapping == null) return null;
return mapping.CleanTitle;
}
public void HandleAsync(ApplicationStartedEvent message)
{
if (!_repository.HasItems())
{
UpdateMappings();
}
} }
} }
} }

View File

@ -0,0 +1,8 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.DataAugmentation.Scene
{
public class UpdateSceneMappingCommand : ICommand
{
}
}

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine.Specifications.Search; using NzbDrone.Core.DecisionEngine.Specifications.Search;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
@ -18,38 +19,26 @@ namespace NzbDrone.Core.DecisionEngine
{ {
private readonly IEnumerable<IRejectWithReason> _specifications; private readonly IEnumerable<IRejectWithReason> _specifications;
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
private readonly Logger _logger;
public DownloadDecisionMaker(IEnumerable<IRejectWithReason> specifications, IParsingService parsingService) public DownloadDecisionMaker(IEnumerable<IRejectWithReason> specifications, IParsingService parsingService, Logger logger)
{ {
_specifications = specifications; _specifications = specifications;
_parsingService = parsingService; _parsingService = parsingService;
_logger = logger;
} }
public List<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports) public List<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports)
{ {
return GetDecisions(reports, GetGeneralRejectionReasons).ToList(); return GetDecisions(reports).ToList();
} }
public List<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchDefinitionBase searchDefinitionBase) public List<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchDefinitionBase searchDefinitionBase)
{ {
return GetDecisions(reports, remoteEpisode => return GetDecisions(reports).ToList();
{
var generalReasons = GetGeneralRejectionReasons(remoteEpisode);
var searchReasons = GetSearchRejectionReasons(remoteEpisode, searchDefinitionBase);
return generalReasons.Union(searchReasons);
}).ToList();
} }
private IEnumerable<DownloadDecision> GetDecisions(IEnumerable<ReportInfo> reports, SearchDefinitionBase searchDefinition = null)
private IEnumerable<string> GetGeneralRejectionReasons(RemoteEpisode report)
{
return _specifications
.OfType<IDecisionEngineSpecification>()
.Where(spec => !spec.IsSatisfiedBy(report))
.Select(spec => spec.RejectionReason);
}
private IEnumerable<DownloadDecision> GetDecisions(IEnumerable<ReportInfo> reports, Func<RemoteEpisode, IEnumerable<string>> decisionCallback)
{ {
foreach (var report in reports) foreach (var report in reports)
{ {
@ -62,7 +51,7 @@ namespace NzbDrone.Core.DecisionEngine
if (remoteEpisode.Series != null) if (remoteEpisode.Series != null)
{ {
yield return new DownloadDecision(remoteEpisode, decisionCallback(remoteEpisode).ToArray()); yield return GetDecisionForReport(remoteEpisode, searchDefinition);
} }
else else
{ {
@ -72,12 +61,40 @@ namespace NzbDrone.Core.DecisionEngine
} }
} }
private IEnumerable<string> GetSearchRejectionReasons(RemoteEpisode report, SearchDefinitionBase searchDefinitionBase) private DownloadDecision GetDecisionForReport(RemoteEpisode remoteEpisode, SearchDefinitionBase searchDefinition = null)
{ {
return _specifications var reasons = _specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchDefinition))
.OfType<IDecisionEngineSearchSpecification>() .Where(c => !string.IsNullOrWhiteSpace(c));
.Where(spec => !spec.IsSatisfiedBy(report, searchDefinitionBase))
.Select(spec => spec.RejectionReason); return new DownloadDecision(remoteEpisode, reasons.ToArray());
}
private string EvaluateSpec(IRejectWithReason spec, RemoteEpisode remoteEpisode, SearchDefinitionBase searchDefinitionBase = null)
{
try
{
var searchSpecification = spec as IDecisionEngineSearchSpecification;
if (searchSpecification != null && searchDefinitionBase != null)
{
if (!searchSpecification.IsSatisfiedBy(remoteEpisode, searchDefinitionBase))
{
return spec.RejectionReason;
}
}
var generalSpecification = spec as IDecisionEngineSpecification;
if (generalSpecification != null && !generalSpecification.IsSatisfiedBy(remoteEpisode))
{
return spec.RejectionReason;
}
}
catch (Exception e)
{
_logger.ErrorException("Couldn't evaluate decision", e);
return string.Format("{0}: {1}", spec.GetType().Name, e.Message);
}
return null;
} }
} }
} }

View File

@ -0,0 +1,45 @@
using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
{
public class SingleEpisodeMatchSpecification : IDecisionEngineSpecification
{
private readonly Logger _logger;
public SingleEpisodeMatchSpecification(Logger logger)
{
_logger = logger;
}
public string RejectionReason
{
get
{
return "Episode doesn't match";
}
}
public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchDefinitionBase searchDefinitionBase)
{
var singleEpisodeSpec = searchDefinitionBase as SingleEpisodeSearchDefinition;
if (singleEpisodeSpec == null) return true;
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
{
_logger.Trace("Season number does not match searched season number, skipping.");
return false;
}
if (!remoteEpisode.Episodes.Select(c => c.EpisodeNumber).Contains(singleEpisodeSpec.EpisodeNumber))
{
_logger.Trace("Episode number does not match searched episode number, skipping.");
return false;
}
return true;
}
}
}

View File

@ -101,8 +101,10 @@ namespace NzbDrone.Core.IndexerSearch
{ {
var spec = new TSpec(); var spec = new TSpec();
var tvdbId = _seriesService.GetSeries(seriesId).TvdbId;
spec.SeriesId = seriesId; spec.SeriesId = seriesId;
spec.SceneTitle = _sceneMapping.GetSceneName(seriesId, seasonNumber); spec.SceneTitle = _sceneMapping.GetSceneName(tvdbId, seasonNumber);
return spec; return spec;
} }

View File

@ -1,33 +0,0 @@
using System;
using System.Linq;
using NzbDrone.Core.DataAugmentation;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Model.Notification;
namespace NzbDrone.Core.Jobs.Implementations
{
public class UpdateSceneMappingsJob : IJob
{
private readonly SceneMappingService _sceneNameMappingService;
public UpdateSceneMappingsJob(SceneMappingService sceneNameMappingService)
{
_sceneNameMappingService = sceneNameMappingService;
}
public string Name
{
get { return "Update Scene Mappings"; }
}
public TimeSpan DefaultInterval
{
get { return TimeSpan.FromHours(6); }
}
public virtual void Start(ProgressNotification notification, dynamic options)
{
_sceneNameMappingService.UpdateMappings();
}
}
}

View File

@ -186,6 +186,7 @@
<Compile Include="DataAugmentation\Scene\SceneMappingService.cs" /> <Compile Include="DataAugmentation\Scene\SceneMappingService.cs" />
<Compile Include="DataAugmentation\Scene\SceneMappingProxy.cs" /> <Compile Include="DataAugmentation\Scene\SceneMappingProxy.cs" />
<Compile Include="DataAugmentation\Scene\SceneMappingRepository.cs" /> <Compile Include="DataAugmentation\Scene\SceneMappingRepository.cs" />
<Compile Include="DataAugmentation\Scene\UpdateSceneMappingCommand.cs" />
<Compile Include="Datastore\Converters\BooleanIntConverter.cs" /> <Compile Include="Datastore\Converters\BooleanIntConverter.cs" />
<Compile Include="Datastore\Converters\QualityIntConverter.cs" /> <Compile Include="Datastore\Converters\QualityIntConverter.cs" />
<Compile Include="Datastore\Converters\Int32Converter.cs" /> <Compile Include="Datastore\Converters\Int32Converter.cs" />
@ -295,7 +296,6 @@
<Compile Include="Jobs\Implementations\RenameSeriesJob.cs" /> <Compile Include="Jobs\Implementations\RenameSeriesJob.cs" />
<Compile Include="Jobs\Implementations\RssSyncJob.cs" /> <Compile Include="Jobs\Implementations\RssSyncJob.cs" />
<Compile Include="Jobs\Implementations\UpdateInfoJob.cs" /> <Compile Include="Jobs\Implementations\UpdateInfoJob.cs" />
<Compile Include="Jobs\Implementations\UpdateSceneMappingsJob.cs" />
<Compile Include="Jobs\Implementations\XemUpdateJob.cs" /> <Compile Include="Jobs\Implementations\XemUpdateJob.cs" />
<Compile Include="Jobs\JobController.cs" /> <Compile Include="Jobs\JobController.cs" />
<Compile Include="Jobs\JobDefinition.cs" /> <Compile Include="Jobs\JobDefinition.cs" />
@ -394,6 +394,7 @@
<Compile Include="Instrumentation\TrimLogsJob.cs" /> <Compile Include="Instrumentation\TrimLogsJob.cs" />
<Compile Include="SeriesStats\SeriesStatistics.cs" /> <Compile Include="SeriesStats\SeriesStatistics.cs" />
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" /> <Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
<Compile Include="Tv\SeriesTypes.cs" />
<Compile Include="Update\AppUpdateJob.cs" /> <Compile Include="Update\AppUpdateJob.cs" />
<Compile Include="Model\Xbmc\TvShowResponse.cs" /> <Compile Include="Model\Xbmc\TvShowResponse.cs" />
<Compile Include="Model\Xbmc\TvShow.cs" /> <Compile Include="Model\Xbmc\TvShow.cs" />

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Marr.Data; using Marr.Data;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.MetadataSource.Trakt;
using NzbDrone.Core.Model; using NzbDrone.Core.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.RootFolders; using NzbDrone.Core.RootFolders;
@ -10,13 +9,6 @@ using NzbDrone.Core.RootFolders;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
public enum SeriesTypes
{
Standard = 0,
Daily = 1,
Anime = 2,
}
public class Series : ModelBase public class Series : ModelBase
{ {
public Series() public Series()

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.SeriesStats;
namespace NzbDrone.Core.Tv namespace NzbDrone.Core.Tv
{ {
@ -13,7 +12,6 @@ namespace NzbDrone.Core.Tv
Series FindByTitle(string cleanTitle); Series FindByTitle(string cleanTitle);
Series FindByTvdbId(int tvdbId); Series FindByTvdbId(int tvdbId);
void SetSeriesType(int seriesId, SeriesTypes seriesTypes); void SetSeriesType(int seriesId, SeriesTypes seriesTypes);
void SetTvRageId(int seriesId, int tvRageId);
} }
public class SeriesRepository : BasicRepository<Series>, ISeriesRepository public class SeriesRepository : BasicRepository<Series>, ISeriesRepository
@ -48,9 +46,5 @@ namespace NzbDrone.Core.Tv
SetFields(new Series { Id = seriesId, SeriesType = seriesType }, s => s.SeriesType); SetFields(new Series { Id = seriesId, SeriesType = seriesType }, s => s.SeriesType);
} }
public void SetTvRageId(int seriesId, int tvRageId)
{
SetFields(new Series { Id = seriesId, TvRageId = tvRageId }, s => s.TvRageId);
}
} }
} }

View File

@ -8,6 +8,7 @@ using NzbDrone.Common;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.MetadataSource; using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Model; using NzbDrone.Core.Model;
@ -42,18 +43,20 @@ namespace NzbDrone.Core.Tv
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly IProvideSeriesInfo _seriesInfoProxy; private readonly IProvideSeriesInfo _seriesInfoProxy;
private readonly IMessageAggregator _messageAggregator; private readonly IMessageAggregator _messageAggregator;
private readonly ISceneMappingService _sceneMappingService;
private readonly IRootFolderService _rootFolderService; private readonly IRootFolderService _rootFolderService;
private readonly DiskProvider _diskProvider; private readonly DiskProvider _diskProvider;
private readonly Logger _logger; private readonly Logger _logger;
public SeriesService(ISeriesRepository seriesRepository, IConfigService configServiceService, public SeriesService(ISeriesRepository seriesRepository, IConfigService configServiceService,
IProvideSeriesInfo seriesInfoProxy, IMessageAggregator messageAggregator, IProvideSeriesInfo seriesInfoProxy, IMessageAggregator messageAggregator, ISceneMappingService sceneMappingService,
IRootFolderService rootFolderService, DiskProvider diskProvider, Logger logger) IRootFolderService rootFolderService, DiskProvider diskProvider, Logger logger)
{ {
_seriesRepository = seriesRepository; _seriesRepository = seriesRepository;
_configService = configServiceService; _configService = configServiceService;
_seriesInfoProxy = seriesInfoProxy; _seriesInfoProxy = seriesInfoProxy;
_messageAggregator = messageAggregator; _messageAggregator = messageAggregator;
_sceneMappingService = sceneMappingService;
_rootFolderService = rootFolderService; _rootFolderService = rootFolderService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_logger = logger; _logger = logger;
@ -146,6 +149,13 @@ namespace NzbDrone.Core.Tv
public Series FindByTitle(string title) public Series FindByTitle(string title)
{ {
var tvdbId = _sceneMappingService.GetTvDbId(title);
if (tvdbId.HasValue)
{
return FindByTvdbId(tvdbId.Value);
}
return _seriesRepository.FindByTitle(Parser.Parser.NormalizeTitle(title)); return _seriesRepository.FindByTitle(Parser.Parser.NormalizeTitle(title));
} }

View File

@ -0,0 +1,9 @@
namespace NzbDrone.Core.Tv
{
public enum SeriesTypes
{
Standard = 0,
Daily = 1,
Anime = 2,
}
}

View File

@ -1,5 +1,6 @@
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Api.Indexers;
namespace NzbDrone.Integration.Test namespace NzbDrone.Integration.Test
{ {
@ -9,9 +10,25 @@ namespace NzbDrone.Integration.Test
[Test] [Test]
public void should_only_have_unknown_series_releases() public void should_only_have_unknown_series_releases()
{ {
Releases.All().Should().OnlyContain(c => c.Rejections.Contains("Unknown Series")); var releases = Releases.All();
releases.Should().OnlyContain(c => c.Rejections.Contains("Unknown Series"));
releases.Should().OnlyContain(c=>BeValidRelease(c));
} }
private bool BeValidRelease(ReleaseResource releaseResource)
{
releaseResource.Age.Should().BeGreaterOrEqualTo(-1);
releaseResource.Title.Should().NotBeBlank();
releaseResource.NzbInfoUrl.Should().NotBeBlank();
releaseResource.NzbUrl.Should().NotBeBlank();
releaseResource.SeriesTitle.Should().NotBeBlank();
releaseResource.Size.Should().BeGreaterThan(0);
return true;
}
} }
} }