New: Will now temporarily stop using an indexer if the indexer reported an error.

This commit is contained in:
Taloth Saldono 2015-06-27 11:43:17 +02:00
parent 6d046a8df8
commit f2a70677e4
61 changed files with 994 additions and 173 deletions

View File

@ -16,6 +16,7 @@ namespace NzbDrone.Api.Indexers
public Double AgeHours { get; set; } public Double AgeHours { get; set; }
public Double AgeMinutes { get; set; } public Double AgeMinutes { get; set; }
public Int64 Size { get; set; } public Int64 Size { get; set; }
public Int32 IndexerId { get; set; }
public String Indexer { get; set; } public String Indexer { get; set; }
public String ReleaseGroup { get; set; } public String ReleaseGroup { get; set; }
public String SubGroup { get; set; } public String SubGroup { get; set; }

View File

@ -249,6 +249,16 @@ namespace NzbDrone.Common.Test.Http
ExceptionVerification.IgnoreErrors(); ExceptionVerification.IgnoreErrors();
} }
[Test]
public void should_throw_on_http429_too_many_requests()
{
var request = new HttpRequest("http://eu.httpbin.org/status/429");
Assert.Throws<TooManyRequestsException>(() => Subject.Get(request));
ExceptionVerification.IgnoreWarns();
}
} }
public class HttpBinResource public class HttpBinResource

View File

@ -94,7 +94,15 @@ namespace NzbDrone.Common.Http
if (!request.SuppressHttpError && response.HasHttpError) if (!request.SuppressHttpError && response.HasHttpError)
{ {
_logger.Warn("HTTP Error - {0}", response); _logger.Warn("HTTP Error - {0}", response);
throw new HttpException(request, response);
if ((int)response.StatusCode == 429)
{
throw new TooManyRequestsException(request, response);
}
else
{
throw new HttpException(request, response);
}
} }
return response; return response;

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Common.Http
{
public class TooManyRequestsException : HttpException
{
public TimeSpan RetryAfter { get; private set; }
public TooManyRequestsException(HttpRequest request, HttpResponse response)
: base(request, response)
{
if (response.Headers.ContainsKey("Retry-After"))
{
RetryAfter = TimeSpan.FromSeconds(int.Parse(response.Headers["Retry-After"].ToString()));
}
}
}
}

View File

@ -158,6 +158,7 @@
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="Http\HttpRequestBuilder.cs" /> <Compile Include="Http\HttpRequestBuilder.cs" />
<Compile Include="Http\TooManyRequestsException.cs" />
<Compile Include="Http\UriExtensions.cs" /> <Compile Include="Http\UriExtensions.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" /> <Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Http\UserAgentBuilder.cs" /> <Compile Include="Http\UserAgentBuilder.cs" />

View File

@ -1,15 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Core.Test.Download namespace NzbDrone.Core.Test.Download
{ {
@ -107,6 +112,53 @@ namespace NzbDrone.Core.Test.Download
VerifyEventNotPublished<EpisodeGrabbedEvent>(); VerifyEventNotPublished<EpisodeGrabbedEvent>();
} }
[Test]
public void Download_report_should_trigger_indexer_backoff_on_indexer_error()
{
var mock = WithUsenetClient();
mock.Setup(s => s.Download(It.IsAny<RemoteEpisode>()))
.Callback<RemoteEpisode>(v => {
throw new ReleaseDownloadException(v.Release, "Error", new WebException());
});
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Once());
}
[Test]
public void Download_report_should_trigger_indexer_backoff_on_http429_with_long_time()
{
var request = new HttpRequest("http://my.indexer.com");
var response = new HttpResponse(request, new HttpHeader(), new byte[0], (HttpStatusCode)429);
response.Headers["Retry-After"] = "300";
var mock = WithUsenetClient();
mock.Setup(s => s.Download(It.IsAny<RemoteEpisode>()))
.Callback<RemoteEpisode>(v => {
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
});
Assert.Throws<ReleaseDownloadException>(() => Subject.DownloadReport(_parseResult));
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.FromMinutes(5)), Times.Once());
}
[Test]
public void Download_report_should_not_trigger_indexer_backoff_on_downloadclient_error()
{
var mock = WithUsenetClient();
mock.Setup(s => s.Download(It.IsAny<RemoteEpisode>()))
.Throws(new DownloadClientException("Some Error"));
Assert.Throws<DownloadClientException>(() => Subject.DownloadReport(_parseResult));
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
}
[Test] [Test]
public void should_not_attempt_download_if_client_isnt_configure() public void should_not_attempt_download_if_client_isnt_configure()
{ {

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using FizzWare.NBuilder;
using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using FluentAssertions;
namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
{
[TestFixture]
public class PendingReleaseServiceFixture : CoreTest<PendingReleaseService>
{
private void GivenPendingRelease()
{
Mocker.GetMock<IPendingReleaseRepository>()
.Setup(v => v.All())
.Returns(new List<PendingRelease> {
new PendingRelease { Release = new ReleaseInfo { IndexerId = 1 } }
});
}
[Test]
public void should_not_ignore_pending_items_from_available_indexer()
{
Mocker.GetMock<IIndexerStatusService>()
.Setup(v => v.GetBlockedIndexers())
.Returns(new List<IndexerStatus>());
GivenPendingRelease();
var results = Subject.GetPending();
results.Should().NotBeEmpty();
Mocker.GetMock<IMakeDownloadDecision>()
.Verify(v => v.GetRssDecision(It.Is<List<ReleaseInfo>>(d => d.Count == 0)), Times.Never());
}
[Test]
public void should_ignore_pending_items_from_unavailable_indexer()
{
Mocker.GetMock<IIndexerStatusService>()
.Setup(v => v.GetBlockedIndexers())
.Returns(new List<IndexerStatus> { new IndexerStatus { IndexerId = 1, DisabledTill = DateTime.UtcNow.AddHours(2) } });
GivenPendingRelease();
var results = Subject.GetPending();
results.Should().BeEmpty();
}
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class IndexerStatusCheckFixture : CoreTest<IndexerStatusCheck>
{
private List<IIndexer> _indexers = new List<IIndexer>();
private List<IndexerStatus> _blockedIndexers = new List<IndexerStatus>();
[SetUp]
public void SetUp()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.GetAvailableProviders())
.Returns(_indexers);
Mocker.GetMock<IIndexerStatusService>()
.Setup(v => v.GetBlockedIndexers())
.Returns(_blockedIndexers);
}
private Mock<IIndexer> GivenIndexer(int i, double backoffHours, double failureHours)
{
var id = i;
var mockIndexer = new Mock<IIndexer>();
mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition { Id = id });
mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true);
_indexers.Add(mockIndexer.Object);
if (backoffHours != 0.0)
{
_blockedIndexers.Add(new IndexerStatus
{
IndexerId = id,
InitialFailure = DateTime.UtcNow.AddHours(-failureHours),
MostRecentFailure = DateTime.UtcNow.AddHours(-0.1),
EscalationLevel = 5,
DisabledTill = DateTime.UtcNow.AddHours(backoffHours)
});
}
return mockIndexer;
}
[Test]
public void should_not_return_error_when_no_indexers()
{
Subject.Check().ShouldBeOk();
}
[Test]
public void should_not_return_error_when_indexer_failed_less_than_an_hour()
{
GivenIndexer(1, 0.1, 0.5);
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_warning_if_indexer_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
GivenIndexer(2, 0.0, 0.0);
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_error_if_all_indexers_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_warning_if_few_indexers_unavailable()
{
GivenIndexer(1, 10.0, 24.0);
GivenIndexer(2, 10.0, 24.0);
GivenIndexer(3, 0.0, 0.0);
Subject.Check().ShouldBeWarning();
}
}
}

View File

@ -0,0 +1,56 @@
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class CleanupOrphanedIndexerStatusFixture : DbTest<CleanupOrphanedIndexerStatus, IndexerStatus>
{
private IndexerDefinition _indexer;
[SetUp]
public void Setup()
{
_indexer = Builder<IndexerDefinition>.CreateNew()
.BuildNew();
}
private void GivenIndexer()
{
Db.Insert(_indexer);
}
[Test]
public void should_delete_orphaned_indexerstatus()
{
var status = Builder<IndexerStatus>.CreateNew()
.With(h => h.IndexerId = _indexer.Id)
.BuildNew();
Db.Insert(status);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_unorphaned_indexerstatus()
{
GivenIndexer();
var status = Builder<IndexerStatus>.CreateNew()
.With(h => h.IndexerId = _indexer.Id)
.BuildNew();
Db.Insert(status);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
AllStoredModels.Should().Contain(h => h.IndexerId == _indexer.Id);
}
}
}

View File

@ -25,6 +25,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
public void SetUp() public void SetUp()
{ {
_mockIndexer = Mocker.GetMock<IIndexer>(); _mockIndexer = Mocker.GetMock<IIndexer>();
_mockIndexer.SetupGet(s => s.Definition).Returns(new IndexerDefinition { Id = 1 });
_mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true); _mockIndexer.SetupGet(s => s.SupportsSearch).Returns(true);
Mocker.GetMock<IIndexerFactory>() Mocker.GetMock<IIndexerFactory>()

View File

@ -59,7 +59,8 @@ namespace NzbDrone.Core.Test.IndexerTests.BroadcastheNetTests
private void VerifyBackOff() private void VerifyBackOff()
{ {
// TODO How to detect (and implement) back-off logic. Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Once());
} }
[Test] [Test]
@ -89,8 +90,6 @@ namespace NzbDrone.Core.Test.IndexerTests.BroadcastheNetTests
results.Should().BeEmpty(); results.Should().BeEmpty();
results.Should().BeEmpty();
VerifyBackOff(); VerifyBackOff();
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);

View File

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests
{
public class IndexerStatusServiceFixture : CoreTest<IndexerStatusService>
{
private DateTime _epoch;
[SetUp]
public void SetUp()
{
_epoch = DateTime.UtcNow;
}
private void WithStatus(IndexerStatus status)
{
Mocker.GetMock<IIndexerStatusRepository>()
.Setup(v => v.FindByIndexerId(1))
.Returns(status);
Mocker.GetMock<IIndexerStatusRepository>()
.Setup(v => v.All())
.Returns(new[] { status });
}
private void VerifyUpdate(bool updated = true)
{
Mocker.GetMock<IIndexerStatusRepository>()
.Verify(v => v.Upsert(It.IsAny<IndexerStatus>()), Times.Exactly(updated ? 1 : 0));
}
[Test]
public void should_start_backoff_on_first_failure()
{
WithStatus(new IndexerStatus());
Subject.RecordFailure(1);
VerifyUpdate();
var status = Subject.GetBlockedIndexers().FirstOrDefault();
status.Should().NotBeNull();
status.DisabledTill.Should().HaveValue();
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(5), 500);
}
[Test]
public void should_cancel_backoff_on_success()
{
WithStatus(new IndexerStatus { EscalationLevel = 2 });
Subject.RecordSuccess(1);
VerifyUpdate();
var status = Subject.GetBlockedIndexers().FirstOrDefault();
status.Should().BeNull();
}
[Test]
public void should_not_store_update_if_already_okay()
{
WithStatus(new IndexerStatus { EscalationLevel = 0 });
Subject.RecordSuccess(1);
VerifyUpdate(false);
}
[Test]
public void should_preserve_escalation_on_intermittent_success()
{
WithStatus(new IndexerStatus { MostRecentFailure = _epoch - TimeSpan.FromSeconds(4), EscalationLevel = 3 });
Subject.RecordSuccess(1);
Subject.RecordSuccess(1);
Subject.RecordFailure(1);
var status = Subject.GetBlockedIndexers().FirstOrDefault();
status.Should().NotBeNull();
status.DisabledTill.Should().HaveValue();
status.DisabledTill.Value.Should().BeCloseTo(_epoch + TimeSpan.FromMinutes(15), 500);
}
}
}

View File

@ -18,6 +18,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
{ {
Subject.Definition = new IndexerDefinition() Subject.Definition = new IndexerDefinition()
{ {
Id = 5,
Name = "Newznab", Name = "Newznab",
Settings = new NewznabSettings() Settings = new NewznabSettings()
{ {
@ -47,6 +48,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
releaseInfo.DownloadUrl.Should().Be("http://nzb.su/getnzb/24967ef4c2e26296c65d3bbfa97aa8fe.nzb&i=37292&r=xxx"); releaseInfo.DownloadUrl.Should().Be("http://nzb.su/getnzb/24967ef4c2e26296c65d3bbfa97aa8fe.nzb&i=37292&r=xxx");
releaseInfo.InfoUrl.Should().Be("http://nzb.su/details/24967ef4c2e26296c65d3bbfa97aa8fe"); releaseInfo.InfoUrl.Should().Be("http://nzb.su/details/24967ef4c2e26296c65d3bbfa97aa8fe");
releaseInfo.CommentUrl.Should().Be("http://nzb.su/details/24967ef4c2e26296c65d3bbfa97aa8fe#comments"); releaseInfo.CommentUrl.Should().Be("http://nzb.su/details/24967ef4c2e26296c65d3bbfa97aa8fe#comments");
releaseInfo.IndexerId.Should().Be(Subject.Definition.Id);
releaseInfo.Indexer.Should().Be(Subject.Definition.Name); releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("2012/02/27 16:09:39")); releaseInfo.PublishDate.Should().Be(DateTime.Parse("2012/02/27 16:09:39"));
releaseInfo.Size.Should().Be(1183105773); releaseInfo.Size.Should().Be(1183105773);

View File

@ -30,18 +30,6 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
AbsoluteEpisodeNumber = 100 AbsoluteEpisodeNumber = 100
}; };
} }
[Test]
public void should_return_one_page_for_feed()
{
var results = Subject.GetRecentRequests();
results.Should().HaveCount(1);
var pages = results.First().Take(10).ToList();
pages.Should().HaveCount(1);
}
[Test] [Test]
public void should_use_all_categories_for_feed() public void should_use_all_categories_for_feed()

View File

@ -22,8 +22,8 @@ namespace NzbDrone.Core.Test.IndexerTests
public Int32 _supportedPageSize; public Int32 _supportedPageSize;
public override Int32 PageSize { get { return _supportedPageSize; } } public override Int32 PageSize { get { return _supportedPageSize; } }
public TestIndexer(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public TestIndexer(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -13,8 +13,8 @@ namespace NzbDrone.Core.Test.IndexerTests.TorrentRssIndexerTests
{ {
public class TestTorrentRssIndexer : TorrentRssIndexer public class TestTorrentRssIndexer : TorrentRssIndexer
{ {
public TestTorrentRssIndexer(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, ITorrentRssParserFactory torrentRssParserFactory, Logger logger) public TestTorrentRssIndexer(ITorrentRssParserFactory torrentRssParserFactory, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, torrentRssParserFactory, logger) : base(torrentRssParserFactory, httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -48,18 +48,6 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
.Setup(v => v.GetCapabilities(It.IsAny<TorznabSettings>())) .Setup(v => v.GetCapabilities(It.IsAny<TorznabSettings>()))
.Returns(_capabilities); .Returns(_capabilities);
} }
[Test]
public void should_return_one_page_for_feed()
{
var results = Subject.GetRecentRequests();
results.Should().HaveCount(1);
var pages = results.First().Take(10).ToList();
pages.Should().HaveCount(1);
}
[Test] [Test]
public void should_use_all_categories_for_feed() public void should_use_all_categories_for_feed()

View File

@ -168,6 +168,7 @@
<Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" /> <Compile Include="Download\DownloadClientTests\UTorrentTests\UTorrentFixture.cs" />
<Compile Include="Download\DownloadServiceFixture.cs" /> <Compile Include="Download\DownloadServiceFixture.cs" />
<Compile Include="Download\FailedDownloadServiceFixture.cs" /> <Compile Include="Download\FailedDownloadServiceFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\RemovePendingFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\RemovePendingFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\RemoveRejectedFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\RemoveRejectedFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\RemoveGrabbedFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\RemoveGrabbedFixture.cs" />
@ -193,6 +194,7 @@
<Compile Include="HealthCheck\Checks\ImportMechanismCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\ImportMechanismCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\IndexerCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\IndexerCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\MonoVersionCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\MonoVersionCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\IndexerStatusCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\RootFolderCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\RootFolderCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\UpdateCheckFixture.cs" /> <Compile Include="HealthCheck\Checks\UpdateCheckFixture.cs" />
<Compile Include="HealthCheck\HealthCheckFixture.cs" /> <Compile Include="HealthCheck\HealthCheckFixture.cs" />
@ -203,6 +205,7 @@
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedBlacklistFixture.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedBlacklistFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFilesFixture.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFilesFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodesFixture.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodesFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedIndexerStatusFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleasesFixture.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleasesFixture.cs" />
@ -215,6 +218,7 @@
<Compile Include="IndexerTests\BroadcastheNetTests\BroadcastheNetFixture.cs" /> <Compile Include="IndexerTests\BroadcastheNetTests\BroadcastheNetFixture.cs" />
<Compile Include="IndexerTests\HDBitsTests\HDBitsFixture.cs" /> <Compile Include="IndexerTests\HDBitsTests\HDBitsFixture.cs" />
<Compile Include="IndexerTests\IndexerServiceFixture.cs" /> <Compile Include="IndexerTests\IndexerServiceFixture.cs" />
<Compile Include="IndexerTests\IndexerStatusServiceFixture.cs" />
<Compile Include="IndexerTests\IntegrationTests\IndexerIntegrationTests.cs" /> <Compile Include="IndexerTests\IntegrationTests\IndexerIntegrationTests.cs" />
<Compile Include="IndexerTests\RarbgTests\RarbgFixture.cs" /> <Compile Include="IndexerTests\RarbgTests\RarbgFixture.cs" />
<Compile Include="IndexerTests\TorrentRssIndexerTests\TorrentRssParserFactoryFixture.cs" /> <Compile Include="IndexerTests\TorrentRssIndexerTests\TorrentRssParserFactoryFixture.cs" />

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Data;
using FluentMigrator;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(91)]
public class added_indexerstatus : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Create.TableForModel("IndexerStatus")
.WithColumn("IndexerId").AsInt32().NotNullable().Unique()
.WithColumn("InitialFailure").AsDateTime().Nullable()
.WithColumn("MostRecentFailure").AsDateTime().Nullable()
.WithColumn("EscalationLevel").AsInt32().NotNullable()
.WithColumn("DisabledTill").AsDateTime().Nullable()
.WithColumn("LastRssSyncReleaseInfo").AsString().Nullable();
}
}
}

View File

@ -111,6 +111,8 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<User>().RegisterModel("Users"); Mapper.Entity<User>().RegisterModel("Users");
Mapper.Entity<CommandModel>().RegisterModel("Commands") Mapper.Entity<CommandModel>().RegisterModel("Commands")
.Ignore(c => c.Message); .Ignore(c => c.Message);
Mapper.Entity<IndexerStatus>().RegisterModel("IndexerStatus");
} }
private static void RegisterMappers() private static void RegisterMappers()

View File

@ -2,8 +2,11 @@
using NLog; using NLog;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.TPL; using NzbDrone.Common.TPL;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -18,16 +21,19 @@ namespace NzbDrone.Core.Download
public class DownloadService : IDownloadService public class DownloadService : IDownloadService
{ {
private readonly IProvideDownloadClient _downloadClientProvider; private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IIndexerStatusService _indexerStatusService;
private readonly IRateLimitService _rateLimitService; private readonly IRateLimitService _rateLimitService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
public DownloadService(IProvideDownloadClient downloadClientProvider, public DownloadService(IProvideDownloadClient downloadClientProvider,
IIndexerStatusService indexerStatusService,
IRateLimitService rateLimitService, IRateLimitService rateLimitService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
_downloadClientProvider = downloadClientProvider; _downloadClientProvider = downloadClientProvider;
_indexerStatusService = indexerStatusService;
_rateLimitService = rateLimitService; _rateLimitService = rateLimitService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
@ -54,7 +60,26 @@ namespace NzbDrone.Core.Download
_rateLimitService.WaitAndPulse(uri.Host, TimeSpan.FromSeconds(2)); _rateLimitService.WaitAndPulse(uri.Host, TimeSpan.FromSeconds(2));
} }
var downloadClientId = downloadClient.Download(remoteEpisode); string downloadClientId;
try
{
downloadClientId = downloadClient.Download(remoteEpisode);
_indexerStatusService.RecordSuccess(remoteEpisode.Release.IndexerId);
}
catch (ReleaseDownloadException ex)
{
var http429 = ex.InnerException as TooManyRequestsException;
if (http429 != null)
{
_indexerStatusService.RecordFailure(remoteEpisode.Release.IndexerId, http429.RetryAfter);
}
else
{
_indexerStatusService.RecordFailure(remoteEpisode.Release.IndexerId);
}
throw;
}
var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode); var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode);
episodeGrabbedEvent.DownloadClient = downloadClient.GetType().Name; episodeGrabbedEvent.DownloadClient = downloadClient.GetType().Name;

View File

@ -35,6 +35,7 @@ namespace NzbDrone.Core.Download.Pending
IHandle<EpisodeGrabbedEvent>, IHandle<EpisodeGrabbedEvent>,
IHandle<RssSyncCompleteEvent> IHandle<RssSyncCompleteEvent>
{ {
private readonly IIndexerStatusService _indexerStatusService;
private readonly IPendingReleaseRepository _repository; private readonly IPendingReleaseRepository _repository;
private readonly ISeriesService _seriesService; private readonly ISeriesService _seriesService;
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
@ -44,7 +45,8 @@ namespace NzbDrone.Core.Download.Pending
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
public PendingReleaseService(IPendingReleaseRepository repository, public PendingReleaseService(IIndexerStatusService indexerStatusService,
IPendingReleaseRepository repository,
ISeriesService seriesService, ISeriesService seriesService,
IParsingService parsingService, IParsingService parsingService,
IDelayProfileService delayProfileService, IDelayProfileService delayProfileService,
@ -53,6 +55,7 @@ namespace NzbDrone.Core.Download.Pending
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
_indexerStatusService = indexerStatusService;
_repository = repository; _repository = repository;
_seriesService = seriesService; _seriesService = seriesService;
_parsingService = parsingService; _parsingService = parsingService;
@ -86,7 +89,21 @@ namespace NzbDrone.Core.Download.Pending
public List<ReleaseInfo> GetPending() public List<ReleaseInfo> GetPending()
{ {
return _repository.All().Select(p => p.Release).ToList(); var releases = _repository.All().Select(p => p.Release).ToList();
if (releases.Any())
{
releases = FilterBlockedIndexers(releases);
}
return releases;
}
private List<ReleaseInfo> FilterBlockedIndexers(List<ReleaseInfo> releases)
{
var blockedIndexers = new HashSet<int>(_indexerStatusService.GetBlockedIndexers().Select(v => v.IndexerId));
return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList();
} }
public List<RemoteEpisode> GetPendingRemoteEpisodes(int seriesId) public List<RemoteEpisode> GetPendingRemoteEpisodes(int seriesId)

View File

@ -126,10 +126,16 @@ namespace NzbDrone.Core.Download
torrentFile = response.ResponseData; torrentFile = response.ResponseData;
} }
catch (HttpException ex)
{
_logger.ErrorException(String.Format("Downloading torrent file for episode '{0}' failed ({1})",
remoteEpisode.Release.Title, torrentUrl), ex);
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
}
catch (WebException ex) catch (WebException ex)
{ {
_logger.ErrorException(String.Format("Downloading torrentfile for episode '{0}' failed ({1})", _logger.ErrorException(String.Format("Downloading torrent file for episode '{0}' failed ({1})",
remoteEpisode.Release.Title, torrentUrl), ex); remoteEpisode.Release.Title, torrentUrl), ex);
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex); throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);

View File

@ -49,6 +49,13 @@ namespace NzbDrone.Core.Download
{ {
nzbData = _httpClient.Get(new HttpRequest(url)).ResponseData; nzbData = _httpClient.Get(new HttpRequest(url)).ResponseData;
} }
catch (HttpException ex)
{
_logger.ErrorException(String.Format("Downloading nzb for episode '{0}' failed ({1})",
remoteEpisode.Release.Title, url), ex);
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
}
catch (WebException ex) catch (WebException ex)
{ {
_logger.ErrorException(String.Format("Downloading nzb for episode '{0}' failed ({1})", _logger.ErrorException(String.Format("Downloading nzb for episode '{0}' failed ({1})",

View File

@ -0,0 +1,48 @@
using System;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class IndexerStatusCheck : HealthCheckBase
{
private readonly IIndexerFactory _indexerFactory;
private readonly IIndexerStatusService _indexerStatusService;
public IndexerStatusCheck(IIndexerFactory indexerFactory, IIndexerStatusService indexerStatusService)
{
_indexerFactory = indexerFactory;
_indexerStatusService = indexerStatusService;
}
public override HealthCheck Check()
{
var enabledIndexers = _indexerFactory.GetAvailableProviders();
var backOffIndexers = enabledIndexers.Join(_indexerStatusService.GetBlockedIndexers(),
i => i.Definition.Id,
s => s.IndexerId,
(i, s) => new { Indexer = i, Status = s })
.Where(v => (v.Status.MostRecentFailure - v.Status.InitialFailure) > TimeSpan.FromHours(1))
.ToList();
if (backOffIndexers.Empty())
{
return new HealthCheck(GetType());
}
if (backOffIndexers.Count == enabledIndexers.Count)
{
return new HealthCheck(GetType(), HealthCheckResult.Error, "All indexers are unavailable due to failures", "#indexers-are-unavailable-due-to-failures");
}
if (backOffIndexers.Count > 1)
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format("{0} indexers are unavailable due to failures", backOffIndexers.Count), "#indexers-are-unavailable-due-to-failures");
}
var indexer = backOffIndexers.First();
return new HealthCheck(GetType(), HealthCheckResult.Warning, string.Format("Indexer {0} is unavailable due to failures", indexer.Indexer.Definition.Name), "#indexers-are-unavailable-due-to-failures");
}
}
}

View File

@ -0,0 +1,26 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
public class CleanupOrphanedIndexerStatus : IHousekeepingTask
{
private readonly IMainDatabase _database;
public CleanupOrphanedIndexerStatus(IMainDatabase database)
{
_database = database;
}
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM IndexerStatus
WHERE Id IN (
SELECT IndexerStatus.Id FROM IndexerStatus
LEFT OUTER JOIN Indexers
ON IndexerStatus.IndexerId = Indexers.Id
WHERE Indexers.Id IS NULL)");
}
}
}

View File

@ -16,14 +16,7 @@ using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.IndexerSearch namespace NzbDrone.Core.IndexerSearch
{ {
public interface IEpisodeSearchService public class EpisodeSearchService : IExecute<EpisodeSearchCommand>, IExecute<MissingEpisodeSearchCommand>
{
void MissingEpisodesAiredAfter(DateTime dateTime, IEnumerable<Int32> grabbed);
}
public class EpisodeSearchService : IEpisodeSearchService,
IExecute<EpisodeSearchCommand>,
IExecute<MissingEpisodeSearchCommand>
{ {
private readonly ISearchForNzb _nzbSearchService; private readonly ISearchForNzb _nzbSearchService;
private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly IProcessDownloadDecisions _processDownloadDecisions;
@ -44,28 +37,6 @@ namespace NzbDrone.Core.IndexerSearch
_logger = logger; _logger = logger;
} }
public void MissingEpisodesAiredAfter(DateTime dateTime, IEnumerable<Int32> grabbed)
{
var missing = _episodeService.EpisodesBetweenDates(dateTime, DateTime.UtcNow, false)
.Where(e => !e.HasFile &&
!_queueService.GetQueue().Select(q => q.Episode.Id).Contains(e.Id) &&
!grabbed.Contains(e.Id))
.ToList();
var downloadedCount = 0;
_logger.Info("Searching for {0} missing episodes since last RSS Sync", missing.Count);
foreach (var episode in missing)
{
//TODO: Add a flag to the search to state it is a "scheduled" search
var decisions = _nzbSearchService.EpisodeSearch(episode);
var processed = _processDownloadDecisions.ProcessDecisions(decisions);
downloadedCount += processed.Grabbed.Count;
}
_logger.ProgressInfo("Completed search for {0} episodes. {1} reports downloaded.", missing.Count, downloadedCount);
}
private void SearchForMissingEpisodes(List<Episode> episodes) private void SearchForMissingEpisodes(List<Episode> episodes)
{ {
_logger.ProgressInfo("Performing missing search for {0} episodes", episodes.Count); _logger.ProgressInfo("Performing missing search for {0} episodes", episodes.Count);

View File

@ -247,7 +247,7 @@ namespace NzbDrone.Core.IndexerSearch
private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase) private List<DownloadDecision> Dispatch(Func<IIndexer, IEnumerable<ReleaseInfo>> searchAction, SearchCriteriaBase criteriaBase)
{ {
var indexers = _indexerFactory.SearchEnabled().ToList(); var indexers = _indexerFactory.SearchEnabled();
var reports = new List<ReleaseInfo>(); var reports = new List<ReleaseInfo>();
_logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase); _logger.ProgressInfo("Searching {0} indexers for {1}", indexers.Count, criteriaBase);

View File

@ -20,8 +20,8 @@ namespace NzbDrone.Core.Indexers.BitMeTv
public override Boolean SupportsSearch { get { return false; } } public override Boolean SupportsSearch { get { return false; } }
public override Int32 PageSize { get { return 0; } } public override Int32 PageSize { get { return 0; } }
public BitMeTv(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public BitMeTv(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -20,8 +20,8 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
public override bool SupportsSearch { get { return true; } } public override bool SupportsSearch { get { return true; } }
public override int PageSize { get { return 100; } } public override int PageSize { get { return 100; } }
public BroadcastheNet(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public BroadcastheNet(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
{ {
var pageableRequests = new List<IEnumerable<IndexerRequest>>(); var pageableRequests = new List<IEnumerable<IndexerRequest>>();
pageableRequests.AddIfNotNull(GetPagedRequests(1, null)); pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, null));
return pageableRequests; return pageableRequests;
} }

View File

@ -17,8 +17,8 @@ namespace NzbDrone.Core.Indexers.Fanzub
public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } }
public Fanzub(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public Fanzub(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -4,7 +4,8 @@ using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Common.TPL; using NzbDrone.Common.TPL;
using System.Collections;
using System;
namespace NzbDrone.Core.Indexers namespace NzbDrone.Core.Indexers
{ {
public interface IFetchAndParseRss public interface IFetchAndParseRss
@ -27,7 +28,7 @@ namespace NzbDrone.Core.Indexers
{ {
var result = new List<ReleaseInfo>(); var result = new List<ReleaseInfo>();
var indexers = _indexerFactory.RssEnabled().ToList(); var indexers = _indexerFactory.RssEnabled();
if (!indexers.Any()) if (!indexers.Any())
{ {

View File

@ -14,11 +14,8 @@ namespace NzbDrone.Core.Indexers.HDBits
public override bool SupportsSearch { get { return true; } } public override bool SupportsSearch { get { return true; } }
public override int PageSize { get { return 30; } } public override int PageSize { get { return 30; } }
public HDBits(IHttpClient httpClient, public HDBits(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
IConfigService configService, : base(httpClient, indexerStatusService, configService, parsingService, logger)
IParsingService parsingService,
Logger logger)
: base(httpClient, configService, parsingService, logger)
{ } { }
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()

View File

@ -33,8 +33,8 @@ namespace NzbDrone.Core.Indexers
public abstract IIndexerRequestGenerator GetRequestGenerator(); public abstract IIndexerRequestGenerator GetRequestGenerator();
public abstract IParseIndexerResponse GetParser(); public abstract IParseIndexerResponse GetParser();
public HttpIndexerBase(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public HttpIndexerBase(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(configService, parsingService, logger) : base(indexerStatusService, configService, parsingService, logger)
{ {
_httpClient = httpClient; _httpClient = httpClient;
} }
@ -48,7 +48,7 @@ namespace NzbDrone.Core.Indexers
var generator = GetRequestGenerator(); var generator = GetRequestGenerator();
return FetchReleases(generator.GetRecentRequests()); return FetchReleases(generator.GetRecentRequests(), true);
} }
public override IList<ReleaseInfo> Fetch(SingleEpisodeSearchCriteria searchCriteria) public override IList<ReleaseInfo> Fetch(SingleEpisodeSearchCriteria searchCriteria)
@ -111,7 +111,7 @@ namespace NzbDrone.Core.Indexers
return FetchReleases(generator.GetSearchRequests(searchCriteria)); return FetchReleases(generator.GetSearchRequests(searchCriteria));
} }
protected virtual IList<ReleaseInfo> FetchReleases(IList<IEnumerable<IndexerRequest>> pageableRequests) protected virtual IList<ReleaseInfo> FetchReleases(IList<IEnumerable<IndexerRequest>> pageableRequests, bool isRecent = false)
{ {
var releases = new List<ReleaseInfo>(); var releases = new List<ReleaseInfo>();
var url = String.Empty; var url = String.Empty;
@ -120,6 +120,13 @@ namespace NzbDrone.Core.Indexers
try try
{ {
var fullyUpdated = false;
ReleaseInfo lastReleaseInfo = null;
if (isRecent)
{
lastReleaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id);
}
foreach (var pageableRequest in pageableRequests) foreach (var pageableRequest in pageableRequests)
{ {
var pagedReleases = new List<ReleaseInfo>(); var pagedReleases = new List<ReleaseInfo>();
@ -132,7 +139,33 @@ namespace NzbDrone.Core.Indexers
pagedReleases.AddRange(page); pagedReleases.AddRange(page);
if (!IsFullPage(page) || pagedReleases.Count >= MaxNumResultsPerQuery) if (isRecent)
{
if (lastReleaseInfo == null)
{
fullyUpdated = true;
break;
}
var oldestReleaseDate = page.Select(v => v.PublishDate).Min();
if (oldestReleaseDate < lastReleaseInfo.PublishDate || page.Any(v => v.DownloadUrl == lastReleaseInfo.DownloadUrl))
{
fullyUpdated = true;
break;
}
if (pagedReleases.Count >= MaxNumResultsPerQuery &&
oldestReleaseDate < DateTime.UtcNow - TimeSpan.FromHours(24))
{
fullyUpdated = false;
break;
}
}
else if (pagedReleases.Count >= MaxNumResultsPerQuery)
{
break;
}
if (!IsFullPage(page))
{ {
break; break;
} }
@ -140,9 +173,26 @@ namespace NzbDrone.Core.Indexers
releases.AddRange(pagedReleases); releases.AddRange(pagedReleases);
} }
if (isRecent && !releases.Empty())
{
var ordered = releases.OrderByDescending(v => v.PublishDate).ToList();
if (!fullyUpdated && lastReleaseInfo != null)
{
var gapStart = lastReleaseInfo.PublishDate;
var gapEnd = ordered.Last();
_logger.Warn("Indexer {0} rss sync didn't cover the period between {1} and {2} UTC. Search may be required.", Definition.Name, gapStart, gapEnd);
}
lastReleaseInfo = ordered.First();
_indexerStatusService.UpdateRssSyncStatus(Definition.Id, lastReleaseInfo);
}
_indexerStatusService.RecordSuccess(Definition.Id);
} }
catch (WebException webException) catch (WebException webException)
{ {
_indexerStatusService.RecordFailure(Definition.Id);
if (webException.Message.Contains("502") || webException.Message.Contains("503") || if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
webException.Message.Contains("timed out")) webException.Message.Contains("timed out"))
{ {
@ -155,29 +205,36 @@ namespace NzbDrone.Core.Indexers
} }
catch (HttpException httpException) catch (HttpException httpException)
{ {
if ((int) httpException.Response.StatusCode == 429) if ((int)httpException.Response.StatusCode == 429)
{ {
_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1));
_logger.Warn("API Request Limit reached for {0}", this); _logger.Warn("API Request Limit reached for {0}", this);
} }
else
_logger.Warn("{0} {1}", this, httpException.Message); {
_indexerStatusService.RecordFailure(Definition.Id);
_logger.Warn("{0} {1}", this, httpException.Message);
}
} }
catch (RequestLimitReachedException) catch (RequestLimitReachedException)
{ {
// TODO: Backoff for x period. _indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1));
_logger.Warn("API Request Limit reached for {0}", this); _logger.Warn("API Request Limit reached for {0}", this);
} }
catch (ApiKeyException) catch (ApiKeyException)
{ {
_indexerStatusService.RecordFailure(Definition.Id);
_logger.Warn("Invalid API Key for {0} {1}", this, url); _logger.Warn("Invalid API Key for {0} {1}", this, url);
} }
catch (IndexerException ex) catch (IndexerException ex)
{ {
_indexerStatusService.RecordFailure(Definition.Id);
var message = String.Format("{0} - {1}", ex.Message, url); var message = String.Format("{0} - {1}", ex.Message, url);
_logger.WarnException(message, ex); _logger.WarnException(message, ex);
} }
catch (Exception feedEx) catch (Exception feedEx)
{ {
_indexerStatusService.RecordFailure(Definition.Id);
feedEx.Data.Add("FeedUrl", url); feedEx.Data.Add("FeedUrl", url);
_logger.ErrorException("An error occurred while processing feed. " + url, feedEx); _logger.ErrorException("An error occurred while processing feed. " + url, feedEx);
} }

View File

@ -20,8 +20,8 @@ namespace NzbDrone.Core.Indexers.IPTorrents
public override Boolean SupportsSearch { get { return false; } } public override Boolean SupportsSearch { get { return false; } }
public override Int32 PageSize { get { return 0; } } public override Int32 PageSize { get { return 0; } }
public IPTorrents(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public IPTorrents(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -15,6 +15,7 @@ namespace NzbDrone.Core.Indexers
public abstract class IndexerBase<TSettings> : IIndexer public abstract class IndexerBase<TSettings> : IIndexer
where TSettings : IProviderConfig, new() where TSettings : IProviderConfig, new()
{ {
protected readonly IIndexerStatusService _indexerStatusService;
protected readonly IConfigService _configService; protected readonly IConfigService _configService;
protected readonly IParsingService _parsingService; protected readonly IParsingService _parsingService;
protected readonly Logger _logger; protected readonly Logger _logger;
@ -25,8 +26,9 @@ namespace NzbDrone.Core.Indexers
public abstract Boolean SupportsRss { get; } public abstract Boolean SupportsRss { get; }
public abstract Boolean SupportsSearch { get; } public abstract Boolean SupportsSearch { get; }
public IndexerBase(IConfigService configService, IParsingService parsingService, Logger logger) public IndexerBase(IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
{ {
_indexerStatusService = indexerStatusService;
_configService = configService; _configService = configService;
_parsingService = parsingService; _parsingService = parsingService;
_logger = logger; _logger = logger;
@ -85,6 +87,7 @@ namespace NzbDrone.Core.Indexers
result.ForEach(c => result.ForEach(c =>
{ {
c.IndexerId = Definition.Id;
c.Indexer = Definition.Name; c.Indexer = Definition.Name;
c.DownloadProtocol = Protocol; c.DownloadProtocol = Protocol;
}); });
@ -106,6 +109,11 @@ 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);
} }

View File

@ -18,5 +18,7 @@ namespace NzbDrone.Core.Indexers
return EnableRss || EnableSearch; return EnableRss || EnableSearch;
} }
} }
public IndexerStatus Status { get; set; }
} }
} }

View File

@ -15,21 +15,21 @@ namespace NzbDrone.Core.Indexers
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, IIndexerFactory
{ {
private readonly IIndexerStatusService _indexerStatusService;
private readonly IIndexerRepository _providerRepository; private readonly IIndexerRepository _providerRepository;
private readonly Logger _logger;
public IndexerFactory(IIndexerRepository providerRepository, public IndexerFactory(IIndexerStatusService indexerStatusService,
IIndexerRepository providerRepository,
IEnumerable<IIndexer> providers, IEnumerable<IIndexer> providers,
IContainer container, IContainer container,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger) : base(providerRepository, providers, container, eventAggregator, logger)
{ {
_indexerStatusService = indexerStatusService;
_providerRepository = providerRepository; _providerRepository = providerRepository;
} _logger = logger;
protected override void InitializeProviders()
{
//_providerRepository.DeleteImplementations("Animezb");
} }
protected override List<IndexerDefinition> Active() protected override List<IndexerDefinition> Active()
@ -50,12 +50,37 @@ namespace NzbDrone.Core.Indexers
public List<IIndexer> RssEnabled() public List<IIndexer> RssEnabled()
{ {
return GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableRss).ToList(); var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableRss);
var indexers = FilterBlockedIndexers(enabledIndexers);
return indexers.ToList();
} }
public List<IIndexer> SearchEnabled() public List<IIndexer> SearchEnabled()
{ {
return GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableSearch).ToList(); var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableSearch);
}
var indexers = FilterBlockedIndexers(enabledIndexers);
return indexers.ToList();
}
private IEnumerable<IIndexer> FilterBlockedIndexers(IEnumerable<IIndexer> indexers)
{
var blockedIndexers = _indexerStatusService.GetBlockedIndexers().ToDictionary(v => v.IndexerId, v => v);
foreach (var indexer in indexers)
{
IndexerStatus blockedIndexerStatus;
if (blockedIndexers.TryGetValue(indexer.Definition.Id, out blockedIndexerStatus))
{
_logger.Debug("Temporarily ignoring indexer {0} till {1} due to recent failures.", indexer.Definition.Name, blockedIndexerStatus.DisabledTill.Value);
continue;
}
yield return indexer;
}
}
} }
} }

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers
{
public class IndexerStatus : ModelBase
{
public int IndexerId { 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 bool IsDisabled()
{
return DisabledTill.HasValue && DisabledTill.Value > DateTime.UtcNow;
}
}
}

View File

@ -0,0 +1,26 @@
using System.Linq;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public interface IIndexerStatusRepository : IProviderRepository<IndexerStatus>
{
IndexerStatus FindByIndexerId(int indexerId);
}
public class IndexerStatusRepository : ProviderRepository<IndexerStatus>, IIndexerStatusRepository
{
public IndexerStatusRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public IndexerStatus FindByIndexerId(int indexerId)
{
return Query.Where(c => c.IndexerId == indexerId).SingleOrDefault();
}
}
}

View File

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.Indexers
{
public interface IIndexerStatusService
{
List<IndexerStatus> GetBlockedIndexers();
ReleaseInfo GetLastRssSyncReleaseInfo(int indexerId);
void RecordSuccess(int indexerId);
void RecordFailure(int indexerId, TimeSpan minimumBackOff = default(TimeSpan));
void UpdateRssSyncStatus(int indexerId, ReleaseInfo releaseInfo);
}
public class IndexerStatusService : IIndexerStatusService, IHandleAsync<ProviderDeletedEvent<IIndexer>>
{
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;
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)
{
return GetIndexerStatus(indexerId).LastRssSyncReleaseInfo;
}
private IndexerStatus GetIndexerStatus(int indexerId)
{
return _indexerStatusRepository.FindByIndexerId(indexerId) ?? new IndexerStatus { IndexerId = 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);
}
}
public void RecordFailure(int indexerId, TimeSpan minimumBackOff = default(TimeSpan))
{
lock (_syncRoot)
{
var status = GetIndexerStatus(indexerId);
var now = DateTime.UtcNow;
if (status.EscalationLevel == 0)
{
status.InitialFailure = now;
}
status.MostRecentFailure = now;
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 UpdateRssSyncStatus(int indexerId, ReleaseInfo releaseInfo)
{
lock (_syncRoot)
{
var status = GetIndexerStatus(indexerId);
status.LastRssSyncReleaseInfo = releaseInfo;
_indexerStatusRepository.Upsert(status);
}
}
public void HandleAsync(ProviderDeletedEvent<IIndexer> message)
{
var indexerStatus = _indexerStatusRepository.FindByIndexerId(message.ProviderId);
if (indexerStatus != null)
{
_indexerStatusRepository.Delete(indexerStatus);
}
}
}
}

View File

@ -19,8 +19,8 @@ namespace NzbDrone.Core.Indexers.KickassTorrents
public override DownloadProtocol Protocol { get { return DownloadProtocol.Torrent; } } public override DownloadProtocol Protocol { get { return DownloadProtocol.Torrent; } }
public override Int32 PageSize { get { return 25; } } public override Int32 PageSize { get { return 25; } }
public KickassTorrents(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public KickassTorrents(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -24,8 +24,7 @@ namespace NzbDrone.Core.Indexers.KickassTorrents
{ {
var pageableRequests = new List<IEnumerable<IndexerRequest>>(); var pageableRequests = new List<IEnumerable<IndexerRequest>>();
// We give kat a bit more pages to get to 100 total for recent, coz users have been missing releases. pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, "tv"));
pageableRequests.AddIfNotNull(GetPagedRequests(4, "tv"));
return pageableRequests; return pageableRequests;
} }

View File

@ -50,8 +50,8 @@ namespace NzbDrone.Core.Indexers.Newznab
} }
} }
public Newznab(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public Newznab(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -23,8 +23,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{ {
var pageableRequests = new List<IEnumerable<IndexerRequest>>(); var pageableRequests = new List<IEnumerable<IndexerRequest>>();
// TODO: We might consider getting multiple pages in the future, but atm we limit it to 1 page. pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories.Concat(Settings.AnimeCategories), "tvsearch", ""));
pageableRequests.AddIfNotNull(GetPagedRequests(1, Settings.Categories.Concat(Settings.AnimeCategories), "tvsearch", ""));
return pageableRequests; return pageableRequests;
} }

View File

@ -19,8 +19,8 @@ namespace NzbDrone.Core.Indexers.Nyaa
public override DownloadProtocol Protocol { get { return DownloadProtocol.Torrent; } } public override DownloadProtocol Protocol { get { return DownloadProtocol.Torrent; } }
public override Int32 PageSize { get { return 100; } } public override Int32 PageSize { get { return 100; } }
public Nyaa(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public Nyaa(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.Nyaa
{ {
var pageableRequests = new List<IEnumerable<IndexerRequest>>(); var pageableRequests = new List<IEnumerable<IndexerRequest>>();
pageableRequests.AddIfNotNull(GetPagedRequests(1, null)); pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, null));
return pageableRequests; return pageableRequests;
} }

View File

@ -17,8 +17,8 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } }
public Omgwtfnzbs(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public Omgwtfnzbs(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -15,8 +15,8 @@ namespace NzbDrone.Core.Indexers.Rarbg
public override DownloadProtocol Protocol { get { return DownloadProtocol.Torrent; } } public override DownloadProtocol Protocol { get { return DownloadProtocol.Torrent; } }
public override TimeSpan RateLimit { get { return TimeSpan.FromSeconds(2); } } public override TimeSpan RateLimit { get { return TimeSpan.FromSeconds(2); } }
public Rarbg(IRarbgTokenProvider tokenProvider, IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public Rarbg(IRarbgTokenProvider tokenProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
_tokenProvider = tokenProvider; _tokenProvider = tokenProvider;
} }

View File

@ -9,31 +9,35 @@ using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers namespace NzbDrone.Core.Indexers
{ {
public class RssSyncService : IExecute<RssSyncCommand> public class RssSyncService : IExecute<RssSyncCommand>
{ {
private readonly IIndexerStatusService _indexerStatusService;
private readonly IIndexerFactory _indexerFactory;
private readonly IFetchAndParseRss _rssFetcherAndParser; private readonly IFetchAndParseRss _rssFetcherAndParser;
private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IProcessDownloadDecisions _processDownloadDecisions; private readonly IProcessDownloadDecisions _processDownloadDecisions;
private readonly IEpisodeSearchService _episodeSearchService;
private readonly IPendingReleaseService _pendingReleaseService; private readonly IPendingReleaseService _pendingReleaseService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
public RssSyncService(IFetchAndParseRss rssFetcherAndParser, public RssSyncService(IIndexerStatusService indexerStatusService,
IIndexerFactory indexerFactory,
IFetchAndParseRss rssFetcherAndParser,
IMakeDownloadDecision downloadDecisionMaker, IMakeDownloadDecision downloadDecisionMaker,
IProcessDownloadDecisions processDownloadDecisions, IProcessDownloadDecisions processDownloadDecisions,
IEpisodeSearchService episodeSearchService,
IPendingReleaseService pendingReleaseService, IPendingReleaseService pendingReleaseService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
_indexerStatusService = indexerStatusService;
_indexerFactory = indexerFactory;
_rssFetcherAndParser = rssFetcherAndParser; _rssFetcherAndParser = rssFetcherAndParser;
_downloadDecisionMaker = downloadDecisionMaker; _downloadDecisionMaker = downloadDecisionMaker;
_processDownloadDecisions = processDownloadDecisions; _processDownloadDecisions = processDownloadDecisions;
_episodeSearchService = episodeSearchService;
_pendingReleaseService = pendingReleaseService; _pendingReleaseService = pendingReleaseService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
@ -44,7 +48,10 @@ namespace NzbDrone.Core.Indexers
{ {
_logger.ProgressInfo("Starting RSS Sync"); _logger.ProgressInfo("Starting RSS Sync");
var reports = _rssFetcherAndParser.Fetch().Concat(_pendingReleaseService.GetPending()).ToList(); var rssReleases = _rssFetcherAndParser.Fetch();
var pendingReleases = _pendingReleaseService.GetPending();
var reports = rssReleases.Concat(pendingReleases).ToList();
var decisions = _downloadDecisionMaker.GetRssDecision(reports); var decisions = _downloadDecisionMaker.GetRssDecision(reports);
var processed = _processDownloadDecisions.ProcessDecisions(decisions); var processed = _processDownloadDecisions.ProcessDecisions(decisions);
@ -65,12 +72,6 @@ namespace NzbDrone.Core.Indexers
var processed = Sync(); var processed = Sync();
var grabbedOrPending = processed.Grabbed.Concat(processed.Pending).ToList(); var grabbedOrPending = processed.Grabbed.Concat(processed.Pending).ToList();
if (message.LastExecutionTime.HasValue && DateTime.UtcNow.Subtract(message.LastExecutionTime.Value).TotalHours > 3)
{
_logger.Info("RSS Sync hasn't run since: {0}. Searching for any missing episodes since then.", message.LastExecutionTime.Value);
_episodeSearchService.MissingEpisodesAiredAfter(message.LastExecutionTime.Value.AddDays(-1), grabbedOrPending.SelectMany(d => d.RemoteEpisode.Episodes).Select(e => e.Id));
}
_eventAggregator.PublishEvent(new RssSyncCompleteEvent(processed)); _eventAggregator.PublishEvent(new RssSyncCompleteEvent(processed));
} }
} }

View File

@ -22,8 +22,8 @@ namespace NzbDrone.Core.Indexers.TorrentRss
private readonly ITorrentRssParserFactory _torrentRssParserFactory; private readonly ITorrentRssParserFactory _torrentRssParserFactory;
public TorrentRssIndexer(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, ITorrentRssParserFactory torrentRssParserFactory, Logger logger) public TorrentRssIndexer(ITorrentRssParserFactory torrentRssParserFactory, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
_torrentRssParserFactory = torrentRssParserFactory; _torrentRssParserFactory = torrentRssParserFactory;
} }

View File

@ -20,8 +20,8 @@ namespace NzbDrone.Core.Indexers.Torrentleech
public override Boolean SupportsSearch { get { return false; } } public override Boolean SupportsSearch { get { return false; } }
public override Int32 PageSize { get { return 0; } } public override Int32 PageSize { get { return 0; } }
public Torrentleech(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public Torrentleech(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -48,8 +48,8 @@ namespace NzbDrone.Core.Indexers.Torznab
} }
} }
public Torznab(ITorznabCapabilitiesProvider torznabCapabilitiesProvider, IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public Torznab(ITorznabCapabilitiesProvider torznabCapabilitiesProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
_torznabCapabilitiesProvider = torznabCapabilitiesProvider; _torznabCapabilitiesProvider = torznabCapabilitiesProvider;
} }

View File

@ -70,8 +70,7 @@ namespace NzbDrone.Core.Indexers.Torznab
if (capabilities.SupportedTvSearchParameters != null) if (capabilities.SupportedTvSearchParameters != null)
{ {
// TODO: We might consider getting multiple pages in the future, but atm we limit it to 1 page. pageableRequests.AddIfNotNull(GetPagedRequests(MaxPages, Settings.Categories.Concat(Settings.AnimeCategories), "tvsearch", ""));
pageableRequests.AddIfNotNull(GetPagedRequests(1, Settings.Categories.Concat(Settings.AnimeCategories), "tvsearch", ""));
} }
return pageableRequests; return pageableRequests;

View File

@ -29,8 +29,8 @@ namespace NzbDrone.Core.Indexers.Wombles
return new RssIndexerRequestGenerator("http://newshost.co.za/rss/?sec=TV&fr=false"); return new RssIndexerRequestGenerator("http://newshost.co.za/rss/?sec=TV&fr=false");
} }
public Wombles(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) public Wombles(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ {
} }

View File

@ -235,37 +235,38 @@
<Compile Include="Datastore\Migration\057_convert_episode_file_path_to_relative.cs" /> <Compile Include="Datastore\Migration\057_convert_episode_file_path_to_relative.cs" />
<Compile Include="Datastore\Migration\058_drop_epsiode_file_path.cs" /> <Compile Include="Datastore\Migration\058_drop_epsiode_file_path.cs" />
<Compile Include="Datastore\Migration\059_add_enable_options_to_indexers.cs" /> <Compile Include="Datastore\Migration\059_add_enable_options_to_indexers.cs" />
<Compile Include="Datastore\Migration\060_remove_enable_from_indexers.cs" />
<Compile Include="Datastore\Migration\061_clear_bad_scene_names.cs" />
<Compile Include="Datastore\Migration\062_convert_quality_models.cs" />
<Compile Include="Datastore\Migration\063_add_remotepathmappings.cs" /> <Compile Include="Datastore\Migration\063_add_remotepathmappings.cs" />
<Compile Include="Datastore\Migration\064_add_remove_method_from_logs.cs" /> <Compile Include="Datastore\Migration\064_add_remove_method_from_logs.cs" />
<Compile Include="Datastore\Migration\061_clear_bad_scene_names.cs" />
<Compile Include="Datastore\Migration\060_remove_enable_from_indexers.cs" />
<Compile Include="Datastore\Migration\062_convert_quality_models.cs" />
<Compile Include="Datastore\Migration\065_make_scene_numbering_nullable.cs" /> <Compile Include="Datastore\Migration\065_make_scene_numbering_nullable.cs" />
<Compile Include="Datastore\Migration\087_remove_eztv.cs" />
<Compile Include="Datastore\Migration\078_add_commands_table.cs" />
<Compile Include="Datastore\Migration\068_add_release_restrictions.cs" />
<Compile Include="Datastore\Migration\066_add_tags.cs" /> <Compile Include="Datastore\Migration\066_add_tags.cs" />
<Compile Include="Datastore\Migration\067_add_added_to_series.cs" /> <Compile Include="Datastore\Migration\067_add_added_to_series.cs" />
<Compile Include="Datastore\Migration\068_add_release_restrictions.cs" />
<Compile Include="Datastore\Migration\069_quality_proper.cs" /> <Compile Include="Datastore\Migration\069_quality_proper.cs" />
<Compile Include="Datastore\Migration\070_delay_profile.cs" />
<Compile Include="Datastore\Migration\071_unknown_quality_in_profile.cs" /> <Compile Include="Datastore\Migration\071_unknown_quality_in_profile.cs" />
<Compile Include="Datastore\Migration\072_history_grabid.cs" /> <Compile Include="Datastore\Migration\072_history_grabid.cs" />
<Compile Include="Datastore\Migration\076_add_users_table.cs" />
<Compile Include="Datastore\Migration\075_force_lib_update.cs" />
<Compile Include="Datastore\Migration\074_disable_eztv.cs" />
<Compile Include="Datastore\Migration\073_clear_ratings.cs" /> <Compile Include="Datastore\Migration\073_clear_ratings.cs" />
<Compile Include="Datastore\Migration\074_disable_eztv.cs" />
<Compile Include="Datastore\Migration\075_force_lib_update.cs" />
<Compile Include="Datastore\Migration\076_add_users_table.cs" />
<Compile Include="Datastore\Migration\077_add_add_options_to_series.cs" /> <Compile Include="Datastore\Migration\077_add_add_options_to_series.cs" />
<Compile Include="Datastore\Migration\089_add_on_rename_to_notifcations.cs" /> <Compile Include="Datastore\Migration\078_add_commands_table.cs" />
<Compile Include="Datastore\Migration\079_dedupe_tags.cs" />
<Compile Include="Datastore\Migration\081_move_dot_prefix_to_transmission_category.cs" />
<Compile Include="Datastore\Migration\082_add_fanzub_settings.cs" />
<Compile Include="Datastore\Migration\083_additonal_blacklist_columns.cs" />
<Compile Include="Datastore\Migration\084_update_quality_minmax_size.cs" />
<Compile Include="Datastore\Migration\085_expand_transmission_urlbase.cs" /> <Compile Include="Datastore\Migration\085_expand_transmission_urlbase.cs" />
<Compile Include="Datastore\Migration\086_pushbullet_device_ids.cs" /> <Compile Include="Datastore\Migration\086_pushbullet_device_ids.cs" />
<Compile Include="Datastore\Migration\081_move_dot_prefix_to_transmission_category.cs" /> <Compile Include="Datastore\Migration\087_remove_eztv.cs" />
<Compile Include="Datastore\Migration\079_dedupe_tags.cs" />
<Compile Include="Datastore\Migration\070_delay_profile.cs" />
<Compile Include="Datastore\Migration\084_update_quality_minmax_size.cs" />
<Compile Include="Datastore\Migration\083_additonal_blacklist_columns.cs" />
<Compile Include="Datastore\Migration\082_add_fanzub_settings.cs" />
<Compile Include="Datastore\Migration\088_pushbullet_devices_channels_list.cs" /> <Compile Include="Datastore\Migration\088_pushbullet_devices_channels_list.cs" />
<Compile Include="Datastore\Migration\092_add_unverifiedscenenumbering.cs" /> <Compile Include="Datastore\Migration\089_add_on_rename_to_notifcations.cs" />
<Compile Include="Datastore\Migration\090_update_kickass_url.cs" /> <Compile Include="Datastore\Migration\090_update_kickass_url.cs" />
<Compile Include="Datastore\Migration\091_added_indexerstatus.cs" />
<Compile Include="Datastore\Migration\092_add_unverifiedscenenumbering.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />
@ -434,6 +435,7 @@
<Compile Include="HealthCheck\Checks\DownloadClientCheck.cs" /> <Compile Include="HealthCheck\Checks\DownloadClientCheck.cs" />
<Compile Include="HealthCheck\Checks\DroneFactoryCheck.cs" /> <Compile Include="HealthCheck\Checks\DroneFactoryCheck.cs" />
<Compile Include="HealthCheck\Checks\ImportMechanismCheck.cs" /> <Compile Include="HealthCheck\Checks\ImportMechanismCheck.cs" />
<Compile Include="HealthCheck\Checks\IndexerStatusCheck.cs" />
<Compile Include="HealthCheck\Checks\IndexerCheck.cs" /> <Compile Include="HealthCheck\Checks\IndexerCheck.cs" />
<Compile Include="HealthCheck\Checks\MediaInfoDllCheck.cs" /> <Compile Include="HealthCheck\Checks\MediaInfoDllCheck.cs" />
<Compile Include="HealthCheck\Checks\MonoVersionCheck.cs" /> <Compile Include="HealthCheck\Checks\MonoVersionCheck.cs" />
@ -453,6 +455,7 @@
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedBlacklist.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedBlacklist.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFiles.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFiles.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodes.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodes.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedIndexerStatus.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItems.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItems.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFiles.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFiles.cs" />
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleases.cs" /> <Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleases.cs" />
@ -493,10 +496,13 @@
<Compile Include="Indexers\IndexerBase.cs" /> <Compile Include="Indexers\IndexerBase.cs" />
<Compile Include="Indexers\IndexerDefinition.cs" /> <Compile Include="Indexers\IndexerDefinition.cs" />
<Compile Include="Indexers\IndexerFactory.cs" /> <Compile Include="Indexers\IndexerFactory.cs" />
<Compile Include="Indexers\IndexerStatusRepository.cs" />
<Compile Include="Indexers\IndexerRepository.cs" /> <Compile Include="Indexers\IndexerRepository.cs" />
<Compile Include="Indexers\IndexerRequest.cs" /> <Compile Include="Indexers\IndexerRequest.cs" />
<Compile Include="Indexers\IndexerResponse.cs" /> <Compile Include="Indexers\IndexerResponse.cs" />
<Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" /> <Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" />
<Compile Include="Indexers\IndexerStatus.cs" />
<Compile Include="Indexers\IndexerStatusService.cs" />
<Compile Include="Indexers\IProcessIndexerResponse.cs" /> <Compile Include="Indexers\IProcessIndexerResponse.cs" />
<Compile Include="Indexers\IPTorrents\IPTorrentsRequestGenerator.cs" /> <Compile Include="Indexers\IPTorrents\IPTorrentsRequestGenerator.cs" />
<Compile Include="Indexers\IPTorrents\IPTorrents.cs" /> <Compile Include="Indexers\IPTorrents\IPTorrents.cs" />
@ -890,6 +896,7 @@
<Compile Include="Tags\TagService.cs" /> <Compile Include="Tags\TagService.cs" />
<Compile Include="Tags\TagsUpdatedEvent.cs" /> <Compile Include="Tags\TagsUpdatedEvent.cs" />
<Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" /> <Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
<Compile Include="ThingiProvider\Events\ProviderDeletedEvent.cs" />
<Compile Include="ThingiProvider\Events\ProviderUpdatedEvent.cs" /> <Compile Include="ThingiProvider\Events\ProviderUpdatedEvent.cs" />
<Compile Include="ThingiProvider\IProvider.cs" /> <Compile Include="ThingiProvider\IProvider.cs" />
<Compile Include="ThingiProvider\IProviderConfig.cs" /> <Compile Include="ThingiProvider\IProviderConfig.cs" />

View File

@ -1,24 +1,24 @@
using System; using System;
using System.Text;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
namespace NzbDrone.Core.Parser.Model namespace NzbDrone.Core.Parser.Model
{ {
using System.Text;
public class ReleaseInfo public class ReleaseInfo
{ {
public String Guid { get; set; } public string Guid { get; set; }
public String Title { get; set; } public string Title { get; set; }
public Int64 Size { get; set; } public long Size { get; set; }
public String DownloadUrl { get; set; } public string DownloadUrl { get; set; }
public String InfoUrl { get; set; } public string InfoUrl { get; set; }
public String CommentUrl { get; set; } public string CommentUrl { get; set; }
public String Indexer { get; set; } public int IndexerId { get; set; }
public string Indexer { get; set; }
public DownloadProtocol DownloadProtocol { get; set; } public DownloadProtocol DownloadProtocol { get; set; }
public Int32 TvRageId { get; set; } public int TvRageId { get; set; }
public DateTime PublishDate { get; set; } public DateTime PublishDate { get; set; }
public Int32 Age public int Age
{ {
get get
{ {
@ -30,7 +30,7 @@ namespace NzbDrone.Core.Parser.Model
private set { } private set { }
} }
public Double AgeHours public double AgeHours
{ {
get get
{ {
@ -42,7 +42,7 @@ namespace NzbDrone.Core.Parser.Model
private set { } private set { }
} }
public Double AgeMinutes public double AgeMinutes
{ {
get get
{ {
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Parser.Model
public override string ToString() public override string ToString()
{ {
return String.Format("[{0}] {1} [{2}]", PublishDate, Title, Size); return string.Format("[{0}] {1} [{2}]", PublishDate, Title, Size);
} }
public virtual string ToString(string format) public virtual string ToString(string format)

View File

@ -0,0 +1,14 @@
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.ThingiProvider.Events
{
public class ProviderDeletedEvent<TProvider> : IEvent
{
public int ProviderId { get; private set; }
public ProviderDeletedEvent(int id)
{
ProviderId = id;
}
}
}

View File

@ -4,5 +4,11 @@ namespace NzbDrone.Core.ThingiProvider.Events
{ {
public class ProviderUpdatedEvent<TProvider> : IEvent public class ProviderUpdatedEvent<TProvider> : IEvent
{ {
public ProviderDefinition Definition { get; private set; }
public ProviderUpdatedEvent(ProviderDefinition definition)
{
Definition = definition;
}
} }
} }

View File

@ -99,12 +99,13 @@ namespace NzbDrone.Core.ThingiProvider
public virtual void Update(TProviderDefinition definition) public virtual void Update(TProviderDefinition definition)
{ {
_providerRepository.Update(definition); _providerRepository.Update(definition);
_eventAggregator.PublishEvent(new ProviderUpdatedEvent<TProvider>()); _eventAggregator.PublishEvent(new ProviderUpdatedEvent<TProvider>(definition));
} }
public void Delete(int id) public void Delete(int id)
{ {
_providerRepository.Delete(id); _providerRepository.Delete(id);
_eventAggregator.PublishEvent(new ProviderDeletedEvent<TProvider>(id));
} }
public TProvider GetInstance(TProviderDefinition definition) public TProvider GetInstance(TProviderDefinition definition)