New: Minimum Age setting to deal with propagation issues (Usenet only)

This commit is contained in:
Mark McDowall 2014-12-15 15:40:53 -08:00
parent eeafa2cb64
commit 580f03a5bc
12 changed files with 150 additions and 5 deletions

View File

@ -9,6 +9,12 @@ namespace NzbDrone.Api.Config
public IndexerConfigModule(IConfigService configService)
: base(configService)
{
SharedValidator.RuleFor(c => c.MinimumAge)
.GreaterThan(0);
SharedValidator.RuleFor(c => c.Retention)
.GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(c => c.RssSyncInterval)
.InclusiveBetween(10, 120)
.When(c => c.RssSyncInterval > 0);

View File

@ -5,6 +5,7 @@ namespace NzbDrone.Api.Config
{
public class IndexerConfigResource : RestResource
{
public Int32 MinimumAge { get; set; }
public Int32 Retention { get; set; }
public Int32 RssSyncInterval { get; set; }
}

View File

@ -0,0 +1,64 @@
using System;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class MinimumAgeSpecificationFixture : CoreTest<MinimumAgeSpecification>
{
private RemoteEpisode _remoteEpisode;
[SetUp]
public void Setup()
{
_remoteEpisode = new RemoteEpisode
{
Release = new ReleaseInfo() { DownloadProtocol = DownloadProtocol.Usenet }
};
}
private void WithMinimumAge(int minutes)
{
Mocker.GetMock<IConfigService>().SetupGet(c => c.MinimumAge).Returns(minutes);
}
private void WithAge(int minutes)
{
_remoteEpisode.Release.PublishDate = DateTime.UtcNow.AddMinutes(-minutes);
}
[Test]
public void should_return_true_when_minimum_age_is_set_to_zero()
{
WithMinimumAge(0);
WithAge(100);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_when_age_is_greater_than_minimum_age()
{
WithMinimumAge(30);
WithAge(100);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_when_age_is_less_than_minimum_age()
{
WithMinimumAge(30);
WithAge(10);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
}
}

View File

@ -13,7 +13,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public class RetentionSpecificationFixture : CoreTest<RetentionSpecification>
{
private RemoteEpisode _remoteEpisode;
[SetUp]
@ -32,7 +31,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void WithAge(int days)
{
_remoteEpisode.Release.PublishDate = DateTime.Now.AddDays(-days);
_remoteEpisode.Release.PublishDate = DateTime.UtcNow.AddDays(-days);
}
[Test]

View File

@ -136,6 +136,7 @@
<Compile Include="DecisionEngineTests\PrioritizeDownloadDecisionFixture.cs" />
<Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\QualityUpgradeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\MinimumAgeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />

View File

@ -108,6 +108,13 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RssSyncInterval", value); }
}
public int MinimumAge
{
get { return GetValueInt("MinimumAge", 0); }
set { SetValue("MinimumAge", value); }
}
public Boolean AutoDownloadPropers
{
get { return GetValueBoolean("AutoDownloadPropers", true); }

View File

@ -49,6 +49,7 @@ namespace NzbDrone.Core.Configuration
//Indexers
Int32 Retention { get; set; }
Int32 RssSyncInterval { get; set; }
Int32 MinimumAge { get; set; }
//UI
Int32 FirstDayOfWeek { get; set; }

View File

@ -0,0 +1,43 @@
using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class MinimumAgeSpecification : IDecisionEngineSpecification
{
private readonly IConfigService _configService;
private readonly Logger _logger;
public MinimumAgeSpecification(IConfigService configService, Logger logger)
{
_configService = configService;
_logger = logger;
}
public RejectionType Type { get { return RejectionType.Temporary; } }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
if (subject.Release.DownloadProtocol != Indexers.DownloadProtocol.Usenet)
{
_logger.Debug("Not checking minimum age requirement for non-usenet report");
return Decision.Accept();
}
var age = subject.Release.AgeMinutes;
var minimumAge = _configService.MinimumAge;
_logger.Debug("Checking if report meets minimum age requirements. {0}", age);
if (minimumAge > 0 && age < minimumAge)
{
_logger.Debug("Only {0} minutes old, minimum age is {1} minutes", age, minimumAge);
return Decision.Reject("Only {0} minutes old, minimum age is {1} minutes", age, minimumAge);
}
return Decision.Accept();
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Events;
@ -36,6 +37,7 @@ namespace NzbDrone.Core.Download.Pending
private readonly ISeriesService _seriesService;
private readonly IParsingService _parsingService;
private readonly IDelayProfileService _delayProfileService;
private readonly IConfigService _configService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
@ -43,6 +45,7 @@ namespace NzbDrone.Core.Download.Pending
ISeriesService seriesService,
IParsingService parsingService,
IDelayProfileService delayProfileService,
IConfigService configService,
IEventAggregator eventAggregator,
Logger logger)
{
@ -50,6 +53,7 @@ namespace NzbDrone.Core.Download.Pending
_seriesService = seriesService;
_parsingService = parsingService;
_delayProfileService = delayProfileService;
_configService = configService;
_eventAggregator = eventAggregator;
_logger = logger;
}
@ -202,8 +206,10 @@ namespace NzbDrone.Core.Download.Pending
private int GetDelay(RemoteEpisode remoteEpisode)
{
var delayProfile = _delayProfileService.AllForTags(remoteEpisode.Series.Tags).OrderBy(d => d.Order).First();
var delay = delayProfile.GetProtocolDelay(remoteEpisode.Release.DownloadProtocol);
var minimumAge = _configService.MinimumAge;
return delayProfile.GetProtocolDelay(remoteEpisode.Release.DownloadProtocol);
return new [] { delay, minimumAge }.Max();
}
private void RemoveGrabbed(RemoteEpisode remoteEpisode)

View File

@ -268,6 +268,7 @@
<Compile Include="DecisionEngine\Specifications\ReleaseRestrictionsSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\NotSampleSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\QualityAllowedByProfileSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\MinimumAgeSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RetentionSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RetrySpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RssSync\DelaySpecification.cs" />

View File

@ -18,7 +18,7 @@ define(
//If the release is pending we want to use the timeleft as the time it will be processed at
if (this.cellValue.get('status').toLowerCase() === 'pending') {
this.$el.html('-');
this.$el.attr('title', 'Will be processed {0}'.format(moment(this.cellValue.get('estimatedCompletionTime')).calendar()));
this.$el.attr('title', 'Will be processed during the first RSS Sync after {0}'.format(moment(this.cellValue.get('estimatedCompletionTime')).calendar()));
this.$el.attr('data-container', 'body');
return this;

View File

@ -1,10 +1,26 @@
<fieldset>
<legend>Options</legend>
<div class="form-group">
<label class="col-sm-3 control-label">Minimum Age</label>
<div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-nd-form-info" title="Usenet only: Reports that do not meet minimum age will not be grabbed, until they do"/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<input type="number" min="0" name="minimumAge" class="form-control"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Retention</label>
<div class="col-sm-2">
<div class="col-sm-1 col-sm-push-2 help-inline">
<i class="icon-nd-form-info" title="Usenet only: Set to zero to set to unlimited"/>
</div>
<div class="col-sm-2 col-sm-pull-1">
<input type="number" min="0" name="retention" class="form-control"/>
</div>
</div>