mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-13 02:07:12 -07:00
New: Add maximum single episode age option (per indexer)
(cherry picked from commit ac7afc351ced2a4043fc6038f20495056d05b341)
This commit is contained in:
parent
c1dd253bc1
commit
1f23cf7e4c
8 changed files with 186 additions and 0 deletions
|
@ -45,6 +45,7 @@ function EditIndexerModalContent(props) {
|
||||||
supportsSearch,
|
supportsSearch,
|
||||||
fields,
|
fields,
|
||||||
priority,
|
priority,
|
||||||
|
seasonSearchMaximumSingleEpisodeAge,
|
||||||
protocol,
|
protocol,
|
||||||
downloadClientId
|
downloadClientId
|
||||||
} = item;
|
} = item;
|
||||||
|
@ -164,6 +165,23 @@ function EditIndexerModalContent(props) {
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</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
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace Lidarr.Api.V1.Indexers
|
||||||
public bool SupportsSearch { get; set; }
|
public bool SupportsSearch { get; set; }
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||||
public int DownloadClientId { get; set; }
|
public int DownloadClientId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ namespace Lidarr.Api.V1.Indexers
|
||||||
resource.SupportsSearch = definition.SupportsSearch;
|
resource.SupportsSearch = definition.SupportsSearch;
|
||||||
resource.Protocol = definition.Protocol;
|
resource.Protocol = definition.Protocol;
|
||||||
resource.Priority = definition.Priority;
|
resource.Priority = definition.Priority;
|
||||||
|
resource.SeasonSearchMaximumSingleEpisodeAge = definition.SeasonSearchMaximumSingleEpisodeAge;
|
||||||
resource.DownloadClientId = definition.DownloadClientId;
|
resource.DownloadClientId = definition.DownloadClientId;
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
|
@ -50,6 +52,7 @@ namespace Lidarr.Api.V1.Indexers
|
||||||
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;
|
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;
|
||||||
definition.EnableInteractiveSearch = resource.EnableInteractiveSearch;
|
definition.EnableInteractiveSearch = resource.EnableInteractiveSearch;
|
||||||
definition.Priority = resource.Priority;
|
definition.Priority = resource.Priority;
|
||||||
|
definition.SeasonSearchMaximumSingleEpisodeAge = resource.SeasonSearchMaximumSingleEpisodeAge;
|
||||||
definition.DownloadClientId = resource.DownloadClientId;
|
definition.DownloadClientId = resource.DownloadClientId;
|
||||||
|
|
||||||
return definition;
|
return definition;
|
||||||
|
|
|
@ -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 string Name { get; }
|
||||||
public abstract DownloadProtocol Protocol { get; }
|
public abstract DownloadProtocol Protocol { get; }
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||||
|
|
||||||
public abstract bool SupportsRss { get; }
|
public abstract bool SupportsRss { get; }
|
||||||
public abstract bool SupportsSearch { get; }
|
public abstract bool SupportsSearch { get; }
|
||||||
|
@ -82,6 +83,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
c.Indexer = Definition.Name;
|
c.Indexer = Definition.Name;
|
||||||
c.DownloadProtocol = Protocol;
|
c.DownloadProtocol = Protocol;
|
||||||
c.IndexerPriority = ((IndexerDefinition)Definition).Priority;
|
c.IndexerPriority = ((IndexerDefinition)Definition).Priority;
|
||||||
|
c.SeasonSearchMaximumSingleEpisodeAge = ((IndexerDefinition)Definition).SeasonSearchMaximumSingleEpisodeAge;
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
public bool SupportsRss { get; set; }
|
public bool SupportsRss { get; set; }
|
||||||
public bool SupportsSearch { get; set; }
|
public bool SupportsSearch { get; set; }
|
||||||
public int Priority { get; set; } = 25;
|
public int Priority { get; set; } = 25;
|
||||||
|
public int SeasonSearchMaximumSingleEpisodeAge { get; set; } = 0;
|
||||||
|
|
||||||
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;
|
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
public string Artist { get; set; }
|
public string Artist { get; set; }
|
||||||
public string Album { get; set; }
|
public string Album { get; set; }
|
||||||
public int IndexerPriority { get; set; }
|
public int IndexerPriority { get; set; }
|
||||||
|
public int SeasonSearchMaximumSingleEpisodeAge { get; set; }
|
||||||
public DownloadProtocol DownloadProtocol { get; set; }
|
public DownloadProtocol DownloadProtocol { get; set; }
|
||||||
public DateTime PublishDate { get; set; }
|
public DateTime PublishDate { get; set; }
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue