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

@ -31,8 +31,8 @@ namespace NzbDrone.Api.ErrorManagement
if (validationException != null)
{
_logger.Warn("Invalid request {0}", validationException.Message);
return validationException.Errors.AsResponse(HttpStatusCode.BadRequest);
}
@ -42,8 +42,8 @@ namespace NzbDrone.Api.ErrorManagement
return new ErrorModel()
{
Message = exception.Message,
Description = exception.ToString()
Message = exception.Message,
Description = exception.ToString()
}.AsResponse(HttpStatusCode.InternalServerError);
}
}

View File

@ -59,7 +59,6 @@ namespace NzbDrone.Api.Indexers
public Int32 SeasonNumber { get; set; }
public Language Language { get; set; }
public DateTime? AirDate { get; set; }
public String OriginalString { get; set; }
public String SeriesTitle { get; set; }
public int[] EpisodeNumbers { 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);
Subject.UpdateMappings();
Subject.Execute(new UpdateSceneMappingCommand());
AssertMappingUpdated();
}
@ -42,7 +42,7 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Throws(new WebException());
Subject.UpdateMappings();
Subject.Execute(new UpdateSceneMappingCommand());
AssertNoUpdate();
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
Mocker.GetMock<ISceneMappingProxy>().Setup(c => c.Fetch()).Returns(new List<SceneMapping>());
Subject.UpdateMappings();
Subject.Execute(new UpdateSceneMappingCommand());
AssertNoUpdate();

View File

@ -3,34 +3,61 @@ using System.Linq;
using NLog;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DataAugmentation.Scene
{
public interface ISceneMappingService
{
void UpdateMappings();
string GetSceneName(int seriesId, int seasonNumber = -1);
string GetSceneName(int tvdbId, int seasonNumber = -1);
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 ISceneMappingProxy _sceneMappingProxy;
private readonly ISeriesService _seriesService;
private readonly Logger _logger;
public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, ISeriesService seriesService, Logger logger)
public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, Logger logger)
{
_repository = repository;
_sceneMappingProxy = sceneMappingProxy;
_seriesService = seriesService;
_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
{
@ -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;
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();
}
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.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.DecisionEngine.Specifications.Search;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
@ -18,38 +19,26 @@ namespace NzbDrone.Core.DecisionEngine
{
private readonly IEnumerable<IRejectWithReason> _specifications;
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;
_parsingService = parsingService;
_logger = logger;
}
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)
{
return GetDecisions(reports, remoteEpisode =>
{
var generalReasons = GetGeneralRejectionReasons(remoteEpisode);
var searchReasons = GetSearchRejectionReasons(remoteEpisode, searchDefinitionBase);
return generalReasons.Union(searchReasons);
}).ToList();
return GetDecisions(reports).ToList();
}
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)
private IEnumerable<DownloadDecision> GetDecisions(IEnumerable<ReportInfo> reports, SearchDefinitionBase searchDefinition = null)
{
foreach (var report in reports)
{
@ -62,7 +51,7 @@ namespace NzbDrone.Core.DecisionEngine
if (remoteEpisode.Series != null)
{
yield return new DownloadDecision(remoteEpisode, decisionCallback(remoteEpisode).ToArray());
yield return GetDecisionForReport(remoteEpisode, searchDefinition);
}
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
.OfType<IDecisionEngineSearchSpecification>()
.Where(spec => !spec.IsSatisfiedBy(report, searchDefinitionBase))
.Select(spec => spec.RejectionReason);
var reasons = _specifications.Select(c => EvaluateSpec(c, remoteEpisode, searchDefinition))
.Where(c => !string.IsNullOrWhiteSpace(c));
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 tvdbId = _seriesService.GetSeries(seriesId).TvdbId;
spec.SeriesId = seriesId;
spec.SceneTitle = _sceneMapping.GetSceneName(seriesId, seasonNumber);
spec.SceneTitle = _sceneMapping.GetSceneName(tvdbId, seasonNumber);
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\SceneMappingProxy.cs" />
<Compile Include="DataAugmentation\Scene\SceneMappingRepository.cs" />
<Compile Include="DataAugmentation\Scene\UpdateSceneMappingCommand.cs" />
<Compile Include="Datastore\Converters\BooleanIntConverter.cs" />
<Compile Include="Datastore\Converters\QualityIntConverter.cs" />
<Compile Include="Datastore\Converters\Int32Converter.cs" />
@ -295,7 +296,6 @@
<Compile Include="Jobs\Implementations\RenameSeriesJob.cs" />
<Compile Include="Jobs\Implementations\RssSyncJob.cs" />
<Compile Include="Jobs\Implementations\UpdateInfoJob.cs" />
<Compile Include="Jobs\Implementations\UpdateSceneMappingsJob.cs" />
<Compile Include="Jobs\Implementations\XemUpdateJob.cs" />
<Compile Include="Jobs\JobController.cs" />
<Compile Include="Jobs\JobDefinition.cs" />
@ -394,6 +394,7 @@
<Compile Include="Instrumentation\TrimLogsJob.cs" />
<Compile Include="SeriesStats\SeriesStatistics.cs" />
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
<Compile Include="Tv\SeriesTypes.cs" />
<Compile Include="Update\AppUpdateJob.cs" />
<Compile Include="Model\Xbmc\TvShowResponse.cs" />
<Compile Include="Model\Xbmc\TvShow.cs" />

View File

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

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.SeriesStats;
namespace NzbDrone.Core.Tv
{
@ -13,7 +12,6 @@ namespace NzbDrone.Core.Tv
Series FindByTitle(string cleanTitle);
Series FindByTvdbId(int tvdbId);
void SetSeriesType(int seriesId, SeriesTypes seriesTypes);
void SetTvRageId(int seriesId, int tvRageId);
}
public class SeriesRepository : BasicRepository<Series>, ISeriesRepository
@ -48,9 +46,5 @@ namespace NzbDrone.Core.Tv
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.Messaging;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Model;
@ -42,18 +43,20 @@ namespace NzbDrone.Core.Tv
private readonly IConfigService _configService;
private readonly IProvideSeriesInfo _seriesInfoProxy;
private readonly IMessageAggregator _messageAggregator;
private readonly ISceneMappingService _sceneMappingService;
private readonly IRootFolderService _rootFolderService;
private readonly DiskProvider _diskProvider;
private readonly Logger _logger;
public SeriesService(ISeriesRepository seriesRepository, IConfigService configServiceService,
IProvideSeriesInfo seriesInfoProxy, IMessageAggregator messageAggregator,
IProvideSeriesInfo seriesInfoProxy, IMessageAggregator messageAggregator, ISceneMappingService sceneMappingService,
IRootFolderService rootFolderService, DiskProvider diskProvider, Logger logger)
{
_seriesRepository = seriesRepository;
_configService = configServiceService;
_seriesInfoProxy = seriesInfoProxy;
_messageAggregator = messageAggregator;
_sceneMappingService = sceneMappingService;
_rootFolderService = rootFolderService;
_diskProvider = diskProvider;
_logger = logger;
@ -146,6 +149,13 @@ namespace NzbDrone.Core.Tv
public Series FindByTitle(string title)
{
var tvdbId = _sceneMappingService.GetTvDbId(title);
if (tvdbId.HasValue)
{
return FindByTvdbId(tvdbId.Value);
}
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 NUnit.Framework;
using NzbDrone.Api.Indexers;
namespace NzbDrone.Integration.Test
{
@ -9,9 +10,25 @@ namespace NzbDrone.Integration.Test
[Test]
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;
}
}
}