mirror of https://github.com/Sonarr/Sonarr
Refactored IndexerStatusService into Thingy Provider architecture.
This commit is contained in:
parent
9f8091e4d7
commit
f4bea5512c
|
@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
public void should_not_ignore_pending_items_from_available_indexer()
|
public void should_not_ignore_pending_items_from_available_indexer()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IIndexerStatusService>()
|
Mocker.GetMock<IIndexerStatusService>()
|
||||||
.Setup(v => v.GetBlockedIndexers())
|
.Setup(v => v.GetBlockedProviders())
|
||||||
.Returns(new List<IndexerStatus>());
|
.Returns(new List<IndexerStatus>());
|
||||||
|
|
||||||
GivenPendingRelease();
|
GivenPendingRelease();
|
||||||
|
@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
public void should_ignore_pending_items_from_unavailable_indexer()
|
public void should_ignore_pending_items_from_unavailable_indexer()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IIndexerStatusService>()
|
Mocker.GetMock<IIndexerStatusService>()
|
||||||
.Setup(v => v.GetBlockedIndexers())
|
.Setup(v => v.GetBlockedProviders())
|
||||||
.Returns(new List<IndexerStatus> { new IndexerStatus { ProviderId = 1, DisabledTill = DateTime.UtcNow.AddHours(2) } });
|
.Returns(new List<IndexerStatus> { new IndexerStatus { ProviderId = 1, DisabledTill = DateTime.UtcNow.AddHours(2) } });
|
||||||
|
|
||||||
GivenPendingRelease();
|
GivenPendingRelease();
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
.Returns(_indexers);
|
.Returns(_indexers);
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerStatusService>()
|
Mocker.GetMock<IIndexerStatusService>()
|
||||||
.Setup(v => v.GetBlockedIndexers())
|
.Setup(v => v.GetBlockedProviders())
|
||||||
.Returns(_blockedIndexers);
|
.Returns(_blockedIndexers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||||
private void WithStatus(IndexerStatus status)
|
private void WithStatus(IndexerStatus status)
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IIndexerStatusRepository>()
|
Mocker.GetMock<IIndexerStatusRepository>()
|
||||||
.Setup(v => v.FindByIndexerId(1))
|
.Setup(v => v.FindByProviderId(1))
|
||||||
.Returns(status);
|
.Returns(status);
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerStatusRepository>()
|
Mocker.GetMock<IIndexerStatusRepository>()
|
||||||
|
@ -44,7 +44,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||||
|
|
||||||
VerifyUpdate();
|
VerifyUpdate();
|
||||||
|
|
||||||
var status = Subject.GetBlockedIndexers().FirstOrDefault();
|
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||||
status.Should().NotBeNull();
|
status.Should().NotBeNull();
|
||||||
status.DisabledTill.Should().HaveValue();
|
status.DisabledTill.Should().HaveValue();
|
||||||
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
|
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
|
||||||
|
@ -59,7 +59,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||||
|
|
||||||
VerifyUpdate();
|
VerifyUpdate();
|
||||||
|
|
||||||
var status = Subject.GetBlockedIndexers().FirstOrDefault();
|
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||||
status.Should().BeNull();
|
status.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ namespace NzbDrone.Core.Test.IndexerTests
|
||||||
Subject.RecordSuccess(1);
|
Subject.RecordSuccess(1);
|
||||||
Subject.RecordFailure(1);
|
Subject.RecordFailure(1);
|
||||||
|
|
||||||
var status = Subject.GetBlockedIndexers().FirstOrDefault();
|
var status = Subject.GetBlockedProviders().FirstOrDefault();
|
||||||
status.Should().NotBeNull();
|
status.Should().NotBeNull();
|
||||||
status.DisabledTill.Should().HaveValue();
|
status.DisabledTill.Should().HaveValue();
|
||||||
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(15), 500);
|
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(15), 500);
|
||||||
|
|
|
@ -101,7 +101,7 @@ namespace NzbDrone.Core.Download.Pending
|
||||||
|
|
||||||
private List<ReleaseInfo> FilterBlockedIndexers(List<ReleaseInfo> releases)
|
private List<ReleaseInfo> FilterBlockedIndexers(List<ReleaseInfo> releases)
|
||||||
{
|
{
|
||||||
var blockedIndexers = new HashSet<int>(_indexerStatusService.GetBlockedIndexers().Select(v => v.ProviderId));
|
var blockedIndexers = new HashSet<int>(_indexerStatusService.GetBlockedProviders().Select(v => v.ProviderId));
|
||||||
|
|
||||||
return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList();
|
return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,36 +7,36 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
{
|
{
|
||||||
public class IndexerStatusCheck : HealthCheckBase
|
public class IndexerStatusCheck : HealthCheckBase
|
||||||
{
|
{
|
||||||
private readonly IIndexerFactory _indexerFactory;
|
private readonly IIndexerFactory _providerFactory;
|
||||||
private readonly IIndexerStatusService _indexerStatusService;
|
private readonly IIndexerStatusService _providerStatusService;
|
||||||
|
|
||||||
public IndexerStatusCheck(IIndexerFactory indexerFactory, IIndexerStatusService indexerStatusService)
|
public IndexerStatusCheck(IIndexerFactory providerFactory, IIndexerStatusService providerStatusService)
|
||||||
{
|
{
|
||||||
_indexerFactory = indexerFactory;
|
_providerFactory = providerFactory;
|
||||||
_indexerStatusService = indexerStatusService;
|
_providerStatusService = providerStatusService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override HealthCheck Check()
|
public override HealthCheck Check()
|
||||||
{
|
{
|
||||||
var enabledIndexers = _indexerFactory.GetAvailableProviders();
|
var enabledProviders = _providerFactory.GetAvailableProviders();
|
||||||
var backOffIndexers = enabledIndexers.Join(_indexerStatusService.GetBlockedIndexers(),
|
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
|
||||||
i => i.Definition.Id,
|
i => i.Definition.Id,
|
||||||
s => s.ProviderId,
|
s => s.ProviderId,
|
||||||
(i, s) => new { Indexer = i, Status = s })
|
(i, s) => new { Provider = i, Status = s })
|
||||||
.Where(v => (v.Status.MostRecentFailure - v.Status.InitialFailure) > TimeSpan.FromHours(1))
|
.Where(v => (v.Status.MostRecentFailure - v.Status.InitialFailure) > TimeSpan.FromHours(1))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (backOffIndexers.Empty())
|
if (backOffProviders.Empty())
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType());
|
return new HealthCheck(GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backOffIndexers.Count == enabledIndexers.Count)
|
if (backOffProviders.Count == enabledProviders.Count)
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, "All indexers are unavailable due to failures", "#indexers-are-unavailable-due-to-failures");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, "All indexers are unavailable due to failures", "#indexers-are-unavailable-due-to-failures");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format("Indexers unavailable due to failures: {0}", string.Join(", ", backOffIndexers.Select(v => v.Indexer.Definition.Name))), "#indexers-are-unavailable-due-to-failures");
|
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format("Indexers unavailable due to failures: {0}", string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))), "#indexers-are-unavailable-due-to-failures");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,11 +96,6 @@ namespace NzbDrone.Core.Indexers
|
||||||
failures.Add(new ValidationFailure(string.Empty, "Test was aborted due to an error: " + ex.Message));
|
failures.Add(new ValidationFailure(string.Empty, "Test was aborted due to an error: " + ex.Message));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Definition.Id != 0)
|
|
||||||
{
|
|
||||||
_indexerStatusService.RecordSuccess(Definition.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ValidationResult(failures);
|
return new ValidationResult(failures);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using FluentValidation.Results;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common.Composition;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -70,7 +71,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
|
|
||||||
private IEnumerable<IIndexer> FilterBlockedIndexers(IEnumerable<IIndexer> indexers)
|
private IEnumerable<IIndexer> FilterBlockedIndexers(IEnumerable<IIndexer> indexers)
|
||||||
{
|
{
|
||||||
var blockedIndexers = _indexerStatusService.GetBlockedIndexers().ToDictionary(v => v.ProviderId, v => v);
|
var blockedIndexers = _indexerStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v);
|
||||||
|
|
||||||
foreach (var indexer in indexers)
|
foreach (var indexer in indexers)
|
||||||
{
|
{
|
||||||
|
@ -84,5 +85,17 @@ namespace NzbDrone.Core.Indexers
|
||||||
yield return indexer;
|
yield return indexer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test(IndexerDefinition definition)
|
||||||
|
{
|
||||||
|
var result = base.Test(definition);
|
||||||
|
|
||||||
|
if (result == null && definition.Id != 0)
|
||||||
|
{
|
||||||
|
_indexerStatusService.RecordSuccess(definition.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,23 +1,10 @@
|
||||||
using System;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.ThingiProvider.Status;
|
||||||
using NzbDrone.Core.Parser.Model;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers
|
namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
public class IndexerStatus : ModelBase
|
public class IndexerStatus : ProviderStatusBase
|
||||||
{
|
{
|
||||||
public int ProviderId { get; set; }
|
|
||||||
|
|
||||||
public DateTime? InitialFailure { get; set; }
|
|
||||||
public DateTime? MostRecentFailure { get; set; }
|
|
||||||
public int EscalationLevel { get; set; }
|
|
||||||
public DateTime? DisabledTill { get; set; }
|
|
||||||
|
|
||||||
public ReleaseInfo LastRssSyncReleaseInfo { get; set; }
|
public ReleaseInfo LastRssSyncReleaseInfo { get; set; }
|
||||||
|
|
||||||
public bool IsDisabled()
|
|
||||||
{
|
|
||||||
return DisabledTill.HasValue && DisabledTill.Value > DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,19 @@
|
||||||
using System.Linq;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider.Status;
|
||||||
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers
|
namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
public interface IIndexerStatusRepository : IProviderRepository<IndexerStatus>
|
public interface IIndexerStatusRepository : IProviderStatusRepository<IndexerStatus>
|
||||||
{
|
{
|
||||||
IndexerStatus FindByIndexerId(int indexerId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class IndexerStatusRepository : ProviderRepository<IndexerStatus>, IIndexerStatusRepository
|
public class IndexerStatusRepository : ProviderStatusRepository<IndexerStatus>, IIndexerStatusRepository
|
||||||
{
|
{
|
||||||
public IndexerStatusRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
public IndexerStatusRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||||
: base(database, eventAggregator)
|
: base(database, eventAggregator)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public IndexerStatus FindByIndexerId(int indexerId)
|
|
||||||
{
|
|
||||||
return Query.Where(c => c.ProviderId == indexerId).SingleOrDefault();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,149 +5,39 @@ using NLog;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.ThingiProvider.Events;
|
using NzbDrone.Core.ThingiProvider.Events;
|
||||||
|
using NzbDrone.Core.ThingiProvider.Status;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers
|
namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
public interface IIndexerStatusService
|
public interface IIndexerStatusService : IProviderStatusServiceBase<IndexerStatus>
|
||||||
{
|
{
|
||||||
List<IndexerStatus> GetBlockedIndexers();
|
|
||||||
ReleaseInfo GetLastRssSyncReleaseInfo(int indexerId);
|
ReleaseInfo GetLastRssSyncReleaseInfo(int indexerId);
|
||||||
void RecordSuccess(int indexerId);
|
|
||||||
void RecordFailure(int indexerId, TimeSpan minimumBackOff = default(TimeSpan));
|
|
||||||
void RecordConnectionFailure(int indexerId);
|
|
||||||
|
|
||||||
void UpdateRssSyncStatus(int indexerId, ReleaseInfo releaseInfo);
|
void UpdateRssSyncStatus(int indexerId, ReleaseInfo releaseInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class IndexerStatusService : IIndexerStatusService, IHandleAsync<ProviderDeletedEvent<IIndexer>>
|
public class IndexerStatusService : ProviderStatusServiceBase<IIndexer, IndexerStatus>, IIndexerStatusService
|
||||||
{
|
{
|
||||||
private static readonly int[] EscalationBackOffPeriods = {
|
public IndexerStatusService(IIndexerStatusRepository providerStatusRepository, Logger logger)
|
||||||
0,
|
: base(providerStatusRepository, logger)
|
||||||
5 * 60,
|
|
||||||
15 * 60,
|
|
||||||
30 * 60,
|
|
||||||
60 * 60,
|
|
||||||
3 * 60 * 60,
|
|
||||||
6 * 60 * 60,
|
|
||||||
12 * 60 * 60,
|
|
||||||
24 * 60 * 60
|
|
||||||
};
|
|
||||||
private static readonly int MaximumEscalationLevel = EscalationBackOffPeriods.Length - 1;
|
|
||||||
|
|
||||||
private static readonly object _syncRoot = new object();
|
|
||||||
|
|
||||||
private readonly IIndexerStatusRepository _indexerStatusRepository;
|
|
||||||
private readonly Logger _logger;
|
|
||||||
|
|
||||||
public IndexerStatusService(IIndexerStatusRepository indexerStatusRepository, Logger logger)
|
|
||||||
{
|
{
|
||||||
_indexerStatusRepository = indexerStatusRepository;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IndexerStatus> GetBlockedIndexers()
|
|
||||||
{
|
|
||||||
return _indexerStatusRepository.All().Where(v => v.IsDisabled()).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReleaseInfo GetLastRssSyncReleaseInfo(int indexerId)
|
public ReleaseInfo GetLastRssSyncReleaseInfo(int indexerId)
|
||||||
{
|
{
|
||||||
return GetIndexerStatus(indexerId).LastRssSyncReleaseInfo;
|
return GetProviderStatus(indexerId).LastRssSyncReleaseInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexerStatus GetIndexerStatus(int indexerId)
|
|
||||||
{
|
|
||||||
return _indexerStatusRepository.FindByIndexerId(indexerId) ?? new IndexerStatus { ProviderId = indexerId };
|
|
||||||
}
|
|
||||||
|
|
||||||
private TimeSpan CalculateBackOffPeriod(IndexerStatus status)
|
|
||||||
{
|
|
||||||
var level = Math.Min(MaximumEscalationLevel, status.EscalationLevel);
|
|
||||||
|
|
||||||
return TimeSpan.FromSeconds(EscalationBackOffPeriods[level]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RecordSuccess(int indexerId)
|
|
||||||
{
|
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
|
||||||
var status = GetIndexerStatus(indexerId);
|
|
||||||
|
|
||||||
if (status.EscalationLevel == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
status.EscalationLevel--;
|
|
||||||
status.DisabledTill = null;
|
|
||||||
|
|
||||||
_indexerStatusRepository.Upsert(status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void RecordFailure(int indexerId, TimeSpan minimumBackOff, bool escalate)
|
|
||||||
{
|
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
|
||||||
var status = GetIndexerStatus(indexerId);
|
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
|
|
||||||
if (status.EscalationLevel == 0)
|
|
||||||
{
|
|
||||||
status.InitialFailure = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
status.MostRecentFailure = now;
|
|
||||||
if (escalate)
|
|
||||||
{
|
|
||||||
status.EscalationLevel = Math.Min(MaximumEscalationLevel, status.EscalationLevel + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minimumBackOff != TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
while (status.EscalationLevel < MaximumEscalationLevel && CalculateBackOffPeriod(status) < minimumBackOff)
|
|
||||||
{
|
|
||||||
status.EscalationLevel++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status.DisabledTill = now + CalculateBackOffPeriod(status);
|
|
||||||
|
|
||||||
_indexerStatusRepository.Upsert(status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RecordFailure(int indexerId, TimeSpan minimumBackOff = default(TimeSpan))
|
|
||||||
{
|
|
||||||
RecordFailure(indexerId, minimumBackOff, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RecordConnectionFailure(int indexerId)
|
|
||||||
{
|
|
||||||
RecordFailure(indexerId, default(TimeSpan), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void UpdateRssSyncStatus(int indexerId, ReleaseInfo releaseInfo)
|
public void UpdateRssSyncStatus(int indexerId, ReleaseInfo releaseInfo)
|
||||||
{
|
{
|
||||||
lock (_syncRoot)
|
lock (_syncRoot)
|
||||||
{
|
{
|
||||||
var status = GetIndexerStatus(indexerId);
|
var status = GetProviderStatus(indexerId);
|
||||||
|
|
||||||
status.LastRssSyncReleaseInfo = releaseInfo;
|
status.LastRssSyncReleaseInfo = releaseInfo;
|
||||||
|
|
||||||
_indexerStatusRepository.Upsert(status);
|
_providerStatusRepository.Upsert(status);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleAsync(ProviderDeletedEvent<IIndexer> message)
|
|
||||||
{
|
|
||||||
var indexerStatus = _indexerStatusRepository.FindByIndexerId(message.ProviderId);
|
|
||||||
|
|
||||||
if (indexerStatus != null)
|
|
||||||
{
|
|
||||||
_indexerStatusRepository.Delete(indexerStatus);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1084,6 +1084,9 @@
|
||||||
<Compile Include="ThingiProvider\ProviderFactory.cs" />
|
<Compile Include="ThingiProvider\ProviderFactory.cs" />
|
||||||
<Compile Include="ThingiProvider\ProviderMessage.cs" />
|
<Compile Include="ThingiProvider\ProviderMessage.cs" />
|
||||||
<Compile Include="ThingiProvider\ProviderRepository.cs" />
|
<Compile Include="ThingiProvider\ProviderRepository.cs" />
|
||||||
|
<Compile Include="ThingiProvider\Status\ProviderStatusBase.cs" />
|
||||||
|
<Compile Include="ThingiProvider\Status\ProviderStatusRepository.cs" />
|
||||||
|
<Compile Include="ThingiProvider\Status\ProviderStatusServiceBase.cs" />
|
||||||
<Compile Include="TinyTwitter.cs" />
|
<Compile Include="TinyTwitter.cs" />
|
||||||
<Compile Include="Tv\Actor.cs" />
|
<Compile Include="Tv\Actor.cs" />
|
||||||
<Compile Include="Tv\AddSeriesOptions.cs" />
|
<Compile Include="Tv\AddSeriesOptions.cs" />
|
||||||
|
|
|
@ -76,7 +76,7 @@ namespace NzbDrone.Core.ThingiProvider
|
||||||
return definitions;
|
return definitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValidationResult Test(TProviderDefinition definition)
|
public virtual ValidationResult Test(TProviderDefinition definition)
|
||||||
{
|
{
|
||||||
return GetInstance(definition).Test();
|
return GetInstance(definition).Test();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ThingiProvider.Status
|
||||||
|
{
|
||||||
|
public abstract class ProviderStatusBase : ModelBase
|
||||||
|
{
|
||||||
|
public int ProviderId { get; set; }
|
||||||
|
|
||||||
|
public DateTime? InitialFailure { get; set; }
|
||||||
|
public DateTime? MostRecentFailure { get; set; }
|
||||||
|
public int EscalationLevel { get; set; }
|
||||||
|
public DateTime? DisabledTill { get; set; }
|
||||||
|
|
||||||
|
public virtual bool IsDisabled()
|
||||||
|
{
|
||||||
|
return DisabledTill.HasValue && DisabledTill.Value > DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ThingiProvider.Status
|
||||||
|
{
|
||||||
|
public interface IProviderStatusRepository<TModel> : IBasicRepository<TModel>
|
||||||
|
where TModel : ProviderStatusBase, new()
|
||||||
|
{
|
||||||
|
TModel FindByProviderId(int providerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProviderStatusRepository<TModel> : BasicRepository<TModel>, IProviderStatusRepository<TModel>
|
||||||
|
where TModel : ProviderStatusBase, new()
|
||||||
|
|
||||||
|
{
|
||||||
|
public ProviderStatusRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TModel FindByProviderId(int providerId)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.ProviderId == providerId).SingleOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.ThingiProvider.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ThingiProvider.Status
|
||||||
|
{
|
||||||
|
public interface IProviderStatusServiceBase<TModel>
|
||||||
|
where TModel : ProviderStatusBase, new()
|
||||||
|
{
|
||||||
|
List<TModel> GetBlockedProviders();
|
||||||
|
void RecordSuccess(int providerId);
|
||||||
|
void RecordFailure(int providerId, TimeSpan minimumBackOff = default(TimeSpan));
|
||||||
|
void RecordConnectionFailure(int providerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ProviderStatusServiceBase<TProvider, TModel> : IProviderStatusServiceBase<TModel>, IHandleAsync<ProviderDeletedEvent<TProvider>>
|
||||||
|
where TProvider : IProvider
|
||||||
|
where TModel : ProviderStatusBase, new()
|
||||||
|
{
|
||||||
|
private static readonly int[] EscalationBackOffPeriods = {
|
||||||
|
0,
|
||||||
|
5 * 60,
|
||||||
|
15 * 60,
|
||||||
|
30 * 60,
|
||||||
|
60 * 60,
|
||||||
|
3 * 60 * 60,
|
||||||
|
6 * 60 * 60,
|
||||||
|
12 * 60 * 60,
|
||||||
|
24 * 60 * 60
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly int MaximumEscalationLevel = EscalationBackOffPeriods.Length - 1;
|
||||||
|
|
||||||
|
protected readonly object _syncRoot = new object();
|
||||||
|
|
||||||
|
protected readonly IProviderStatusRepository<TModel> _providerStatusRepository;
|
||||||
|
protected readonly Logger _logger;
|
||||||
|
|
||||||
|
public ProviderStatusServiceBase(IProviderStatusRepository<TModel> providerStatusRepository, Logger logger)
|
||||||
|
{
|
||||||
|
_providerStatusRepository = providerStatusRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual List<TModel> GetBlockedProviders()
|
||||||
|
{
|
||||||
|
return _providerStatusRepository.All().Where(v => v.IsDisabled()).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual TModel GetProviderStatus(int providerId)
|
||||||
|
{
|
||||||
|
return _providerStatusRepository.FindByProviderId(providerId) ?? new TModel { ProviderId = providerId };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual TimeSpan CalculateBackOffPeriod(TModel status)
|
||||||
|
{
|
||||||
|
var level = Math.Min(MaximumEscalationLevel, status.EscalationLevel);
|
||||||
|
|
||||||
|
return TimeSpan.FromSeconds(EscalationBackOffPeriods[level]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RecordSuccess(int providerId)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
var status = GetProviderStatus(providerId);
|
||||||
|
|
||||||
|
if (status.EscalationLevel == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.EscalationLevel--;
|
||||||
|
status.DisabledTill = null;
|
||||||
|
|
||||||
|
_providerStatusRepository.Upsert(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void RecordFailure(int providerId, TimeSpan minimumBackOff, bool escalate)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
var status = GetProviderStatus(providerId);
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
|
if (status.EscalationLevel == 0)
|
||||||
|
{
|
||||||
|
status.InitialFailure = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.MostRecentFailure = now;
|
||||||
|
if (escalate)
|
||||||
|
{
|
||||||
|
status.EscalationLevel = Math.Min(MaximumEscalationLevel, status.EscalationLevel + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minimumBackOff != TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
while (status.EscalationLevel < MaximumEscalationLevel && CalculateBackOffPeriod(status) < minimumBackOff)
|
||||||
|
{
|
||||||
|
status.EscalationLevel++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status.DisabledTill = now + CalculateBackOffPeriod(status);
|
||||||
|
|
||||||
|
_providerStatusRepository.Upsert(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RecordFailure(int providerId, TimeSpan minimumBackOff = default(TimeSpan))
|
||||||
|
{
|
||||||
|
RecordFailure(providerId, minimumBackOff, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RecordConnectionFailure(int providerId)
|
||||||
|
{
|
||||||
|
RecordFailure(providerId, default(TimeSpan), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void HandleAsync(ProviderDeletedEvent<TProvider> message)
|
||||||
|
{
|
||||||
|
var providerStatus = _providerStatusRepository.FindByProviderId(message.ProviderId);
|
||||||
|
|
||||||
|
if (providerStatus != null)
|
||||||
|
{
|
||||||
|
_providerStatusRepository.Delete(providerStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue