New: Add maximum single episode age option (per indexer)
This commit is contained in:
parent
e4c0e80e3e
commit
ac7afc351c
|
@ -45,6 +45,7 @@ function EditIndexerModalContent(props) {
|
|||
tags,
|
||||
fields,
|
||||
priority,
|
||||
seasonSearchMaximumSingleEpisodeAge,
|
||||
protocol,
|
||||
downloadClientId
|
||||
} = item;
|
||||
|
@ -153,6 +154,23 @@ function EditIndexerModalContent(props) {
|
|||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Maximum Single Episode Age</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="seasonSearchMaximumSingleEpisodeAge"
|
||||
helpText="During a full season search only season packs will be allowed when the season's last episode is older than this setting. Standard series only. Use 0 to disable."
|
||||
min={0}
|
||||
unit="days"
|
||||
{...seasonSearchMaximumSingleEpisodeAge}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NUnit.Framework;
|
||||
using FluentAssertions;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
|
||||
namespace NzbDrone.Core.Test.DecisionEngineTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class SingleEpisodeAgeDownloadDecisionFixture : CoreTest<SeasonPackOnlySpecification>
|
||||
{
|
||||
private RemoteEpisode parseResultMulti;
|
||||
private RemoteEpisode parseResultSingle;
|
||||
private Series series;
|
||||
private List<Episode> episodes;
|
||||
private SeasonSearchCriteria multiSearch;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
series = Builder<Series>.CreateNew()
|
||||
.With(s => s.Seasons = Builder<Season>.CreateListOfSize(1).Build().ToList())
|
||||
.With(s => s.SeriesType = SeriesTypes.Standard)
|
||||
.Build();
|
||||
|
||||
episodes = new List<Episode>();
|
||||
episodes.Add(CreateEpisodeStub(1, 400));
|
||||
episodes.Add(CreateEpisodeStub(2, 370));
|
||||
episodes.Add(CreateEpisodeStub(3, 340));
|
||||
episodes.Add(CreateEpisodeStub(4, 310));
|
||||
|
||||
multiSearch = new SeasonSearchCriteria();
|
||||
multiSearch.Episodes = episodes.ToList();
|
||||
multiSearch.SeasonNumber = 1;
|
||||
|
||||
parseResultMulti = new RemoteEpisode
|
||||
{
|
||||
Series = series,
|
||||
Release = new ReleaseInfo(),
|
||||
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, new Revision(version: 2)), FullSeason = true },
|
||||
Episodes = episodes.ToList()
|
||||
};
|
||||
|
||||
parseResultSingle = new RemoteEpisode
|
||||
{
|
||||
Series = series,
|
||||
Release = new ReleaseInfo(),
|
||||
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, new Revision(version: 2)) },
|
||||
Episodes = new List<Episode>()
|
||||
};
|
||||
}
|
||||
|
||||
Episode CreateEpisodeStub(int number, int age)
|
||||
{
|
||||
return new Episode() {
|
||||
SeasonNumber = 1,
|
||||
EpisodeNumber = number,
|
||||
AirDateUtc = DateTime.UtcNow.AddDays(-age)
|
||||
};
|
||||
}
|
||||
|
||||
[TestCase(1, 200, false)]
|
||||
[TestCase(4, 200, false)]
|
||||
[TestCase(1, 600, true)]
|
||||
[TestCase(1, 365, true)]
|
||||
[TestCase(4, 365, true)]
|
||||
[TestCase(1, 0, true)]
|
||||
public void single_episode_release(int episode, int SeasonSearchMaximumSingleEpisodeAge, bool expectedResult)
|
||||
{
|
||||
parseResultSingle.Release.SeasonSearchMaximumSingleEpisodeAge = SeasonSearchMaximumSingleEpisodeAge;
|
||||
parseResultSingle.Episodes.Clear();
|
||||
parseResultSingle.Episodes.Add(episodes.Find(e => e.EpisodeNumber == episode));
|
||||
|
||||
Subject.IsSatisfiedBy(parseResultSingle, multiSearch).Accepted.Should().Be(expectedResult);
|
||||
}
|
||||
|
||||
// should always accept all season packs
|
||||
[TestCase(200, true)]
|
||||
[TestCase(600, true)]
|
||||
[TestCase(365, true)]
|
||||
[TestCase(0, true)]
|
||||
public void multi_episode_release(int SeasonSearchMaximumSingleEpisodeAge, bool expectedResult)
|
||||
{
|
||||
parseResultMulti.Release.SeasonSearchMaximumSingleEpisodeAge = SeasonSearchMaximumSingleEpisodeAge;
|
||||
|
||||
Subject.IsSatisfiedBy(parseResultMulti, multiSearch).Accepted.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(172)]
|
||||
public class add_SeasonSearchMaximumSingleEpisodeAge_to_indexers : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("Indexers").AddColumn("SeasonSearchMaximumSingleEpisodeAge").AsInt32().NotNullable().WithDefaultValue(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
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 SeasonPackOnlySpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SeasonPackOnlySpecification(IConfigService configService, Logger logger)
|
||||
{
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||
public RejectionType Type => RejectionType.Permanent;
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null || searchCriteria.Episodes.Count == 1)
|
||||
{
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
if (subject.Release.SeasonSearchMaximumSingleEpisodeAge > 0)
|
||||
{
|
||||
if (subject.Series.SeriesType == SeriesTypes.Standard && !subject.ParsedEpisodeInfo.FullSeason && subject.Episodes.Count >= 1)
|
||||
{
|
||||
// test against episodes of the same season in the current search, and make sure they have an air date
|
||||
var subset = searchCriteria.Episodes.Where(e => e.AirDateUtc.HasValue && e.SeasonNumber == subject.Episodes.First().SeasonNumber).ToList();
|
||||
|
||||
if (subset.Count() > 0 && subset.Max(e => e.AirDateUtc).Value.Before(DateTime.UtcNow - TimeSpan.FromDays(subject.Release.SeasonSearchMaximumSingleEpisodeAge)))
|
||||
{
|
||||
_logger.Debug("Release {0}: last episode in this season aired more than {1} days ago, season pack required.", subject.Release.Title, subject.Release.SeasonSearchMaximumSingleEpisodeAge);
|
||||
return Decision.Reject("Last episode in this season aired more than {0} days ago, season pack required.", subject.Release.SeasonSearchMaximumSingleEpisodeAge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ namespace NzbDrone.Core.Indexers
|
|||
public abstract string Name { get; }
|
||||
public abstract DownloadProtocol Protocol { get; }
|
||||
public int Priority { get; set; }
|
||||
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||
|
||||
public abstract bool SupportsRss { get; }
|
||||
public abstract bool SupportsSearch { get; }
|
||||
|
@ -81,6 +82,7 @@ namespace NzbDrone.Core.Indexers
|
|||
c.Indexer = Definition.Name;
|
||||
c.DownloadProtocol = Protocol;
|
||||
c.IndexerPriority = ((IndexerDefinition)Definition).Priority;
|
||||
c.SeasonSearchMaximumSingleEpisodeAge = ((IndexerDefinition)Definition).SeasonSearchMaximumSingleEpisodeAge;
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace NzbDrone.Core.Indexers
|
|||
public bool SupportsRss { get; set; }
|
||||
public bool SupportsSearch { get; set; }
|
||||
public int Priority { get; set; } = 25;
|
||||
public int SeasonSearchMaximumSingleEpisodeAge { get; set; } = 0;
|
||||
|
||||
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace NzbDrone.Core.Parser.Model
|
|||
public int IndexerId { get; set; }
|
||||
public string Indexer { get; set; }
|
||||
public int IndexerPriority { get; set; }
|
||||
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||
public DownloadProtocol DownloadProtocol { get; set; }
|
||||
public int TvdbId { get; set; }
|
||||
public int TvRageId { get; set; }
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Sonarr.Api.V3.Indexers
|
|||
public bool SupportsSearch { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||
public int DownloadClientId { get; set; }
|
||||
}
|
||||
|
||||
|
@ -29,6 +30,7 @@ namespace Sonarr.Api.V3.Indexers
|
|||
resource.SupportsSearch = definition.SupportsSearch;
|
||||
resource.Protocol = definition.Protocol;
|
||||
resource.Priority = definition.Priority;
|
||||
resource.SeasonSearchMaximumSingleEpisodeAge = definition.SeasonSearchMaximumSingleEpisodeAge;
|
||||
resource.DownloadClientId = definition.DownloadClientId;
|
||||
|
||||
return resource;
|
||||
|
@ -44,6 +46,7 @@ namespace Sonarr.Api.V3.Indexers
|
|||
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;
|
||||
definition.EnableInteractiveSearch = resource.EnableInteractiveSearch;
|
||||
definition.Priority = resource.Priority;
|
||||
definition.SeasonSearchMaximumSingleEpisodeAge = resource.SeasonSearchMaximumSingleEpisodeAge;
|
||||
definition.DownloadClientId = resource.DownloadClientId;
|
||||
|
||||
return definition;
|
||||
|
|
Loading…
Reference in New Issue