New: Application Status Warnings

pull/8/head
Qstick 3 years ago
parent 96cf058017
commit ed0e11847a

@ -61,9 +61,7 @@ class MovieIndexRow extends Component {
const {
id,
name,
enableRss,
enableAutomaticSearch,
enableInteractiveSearch,
enable,
tags,
protocol,
privacy,
@ -114,7 +112,7 @@ class MovieIndexRow extends Component {
<IndexerStatusCell
key={column.name}
className={styles[column.name]}
enabled={enableRss || enableAutomaticSearch || enableInteractiveSearch}
enabled={enable}
status={status}
longDateFormat={longDateFormat}
timeFormat={timeFormat}
@ -255,9 +253,7 @@ MovieIndexRow.propTypes = {
privacy: PropTypes.string.isRequired,
priority: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
enableRss: PropTypes.bool.isRequired,
enableAutomaticSearch: PropTypes.bool.isRequired,
enableInteractiveSearch: PropTypes.bool.isRequired,
enable: PropTypes.bool.isRequired,
status: PropTypes.object,
capabilities: PropTypes.object.isRequired,
added: PropTypes.string.isRequired,

@ -38,11 +38,8 @@ function EditIndexerModalContent(props) {
id,
implementationName,
name,
enableRss,
enableAutomaticSearch,
enableInteractiveSearch,
enable,
supportsRss,
supportsSearch,
fields,
priority
} = item;
@ -81,42 +78,14 @@ function EditIndexerModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>{translate('EnableRSS')}</FormLabel>
<FormLabel>{translate('Enable')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableRss"
name="enable"
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
isDisabled={!supportsRss.value}
{...enableRss}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('EnableAutomaticSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableAutomaticSearch"
helpText={supportsSearch.value ? translate('EnableAutomaticSearchHelpText') : undefined}
helpTextWarning={supportsSearch.value ? undefined : translate('EnableAutomaticSearchHelpTextWarning')}
isDisabled={!supportsSearch.value}
{...enableAutomaticSearch}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('EnableInteractiveSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableInteractiveSearch"
helpText={supportsSearch.value ? translate('EnableInteractiveSearchHelpText') : undefined}
helpTextWarning={supportsSearch.value ? undefined : translate('EnableInteractiveSearchHelpTextWarning')}
isDisabled={!supportsSearch.value}
{...enableInteractiveSearch}
{...enable}
onChange={onInputChange}
/>
</FormGroup>

@ -65,11 +65,8 @@ class Indexer extends Component {
const {
id,
name,
enableRss,
enableAutomaticSearch,
enableInteractiveSearch,
enable,
supportsRss,
supportsSearch,
priority,
showPriority
} = this.props;
@ -96,26 +93,12 @@ class Indexer extends Component {
<div className={styles.enabled}>
{
supportsRss && enableRss &&
supportsRss && enable &&
<Label kind={kinds.SUCCESS}>
RSS
</Label>
}
{
supportsSearch && enableAutomaticSearch &&
<Label kind={kinds.SUCCESS}>
{translate('AutomaticSearch')}
</Label>
}
{
supportsSearch && enableInteractiveSearch &&
<Label kind={kinds.SUCCESS}>
{translate('InteractiveSearch')}
</Label>
}
{
showPriority &&
<Label kind={kinds.DEFAULT}>
@ -123,7 +106,7 @@ class Indexer extends Component {
</Label>
}
{
!enableRss && !enableAutomaticSearch && !enableInteractiveSearch &&
!enable &&
<Label
kind={kinds.DISABLED}
outline={true}
@ -157,9 +140,7 @@ class Indexer extends Component {
Indexer.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
enableRss: PropTypes.bool.isRequired,
enableAutomaticSearch: PropTypes.bool.isRequired,
enableInteractiveSearch: PropTypes.bool.isRequired,
enable: PropTypes.bool.isRequired,
supportsRss: PropTypes.bool.isRequired,
supportsSearch: PropTypes.bool.isRequired,
onCloneIndexerPress: PropTypes.func.isRequired,

@ -142,9 +142,7 @@ export const reducers = createHandleActions({
[SELECT_INDEXER_SCHEMA]: (state, { payload }) => {
return selectSchema(state, payload, (selectedSchema) => {
selectedSchema.enableRss = selectedSchema.supportsRss;
selectedSchema.enableAutomaticSearch = selectedSchema.supportsSearch;
selectedSchema.enableInteractiveSearch = selectedSchema.supportsSearch;
selectedSchema.enable = selectedSchema.supportsRss;
return selectedSchema;
});

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Applications;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class ApplicationStatusCheckFixture : CoreTest<ApplicationStatusCheck>
{
private List<IApplication> _applications = new List<IApplication>();
private List<ApplicationStatus> _blockedApplications = new List<ApplicationStatus>();
[SetUp]
public void SetUp()
{
Mocker.GetMock<IApplicationFactory>()
.Setup(v => v.GetAvailableProviders())
.Returns(_applications);
Mocker.GetMock<IApplicationStatusService>()
.Setup(v => v.GetBlockedProviders())
.Returns(_blockedApplications);
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
}
private Mock<IApplication> GivenIndexer(int i, double backoffHours, double failureHours)
{
var id = i;
var mockIndexer = new Mock<IApplication>();
mockIndexer.SetupGet(s => s.Definition).Returns(new ApplicationDefinition { Id = id });
_applications.Add(mockIndexer.Object);
if (backoffHours != 0.0)
{
_blockedApplications.Add(new ApplicationStatus
{
ProviderId = 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_return_warning_if_indexer_unavailable()
{
GivenIndexer(1, 2.0, 4.0);
GivenIndexer(2, 0.0, 0.0);
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_error_if_all_indexers_unavailable()
{
GivenIndexer(1, 2.0, 4.0);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_warning_if_few_indexers_unavailable()
{
GivenIndexer(1, 2.0, 4.0);
GivenIndexer(2, 2.0, 4.0);
GivenIndexer(3, 0.0, 0.0);
Subject.Check().ShouldBeWarning();
}
}
}

@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Returns(new List<IIndexer>());
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.RssEnabled(It.IsAny<bool>()))
.Setup(s => s.Enabled(It.IsAny<bool>()))
.Returns(new List<IIndexer>());
Mocker.GetMock<ILocalizationService>()
@ -43,14 +43,14 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
private void GivenRssEnabled()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.RssEnabled(It.IsAny<bool>()))
.Setup(s => s.Enabled(It.IsAny<bool>()))
.Returns(new List<IIndexer> { _indexerMock.Object });
}
private void GivenRssFiltered()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.RssEnabled(false))
.Setup(s => s.Enabled(false))
.Returns(new List<IIndexer> { _indexerMock.Object });
Mocker.GetMock<ILocalizationService>()

@ -1,135 +0,0 @@
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class IndexerSearchCheckFixture : CoreTest<IndexerSearchCheck>
{
private Mock<IIndexer> _indexerMock;
[SetUp]
public void SetUp()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer>());
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.AutomaticSearchEnabled(It.IsAny<bool>()))
.Returns(new List<IIndexer>());
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.InteractiveSearchEnabled(It.IsAny<bool>()))
.Returns(new List<IIndexer>());
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("Some Warning Message");
}
private void GivenIndexer(bool supportsRss, bool supportsSearch)
{
_indexerMock = Mocker.GetMock<IIndexer>();
_indexerMock.SetupGet(s => s.SupportsRss).Returns(supportsRss);
_indexerMock.SetupGet(s => s.SupportsSearch).Returns(supportsSearch);
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.GetAvailableProviders())
.Returns(new List<IIndexer> { _indexerMock.Object });
}
private void GivenAutomaticSearchEnabled()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.AutomaticSearchEnabled(It.IsAny<bool>()))
.Returns(new List<IIndexer> { _indexerMock.Object });
}
private void GivenInteractiveSearchEnabled()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.InteractiveSearchEnabled(It.IsAny<bool>()))
.Returns(new List<IIndexer> { _indexerMock.Object });
}
private void GivenSearchFiltered()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.AutomaticSearchEnabled(false))
.Returns(new List<IIndexer> { _indexerMock.Object });
Mocker.GetMock<IIndexerFactory>()
.Setup(s => s.InteractiveSearchEnabled(false))
.Returns(new List<IIndexer> { _indexerMock.Object });
Mocker.GetMock<ILocalizationService>()
.Setup(s => s.GetLocalizedString(It.IsAny<string>()))
.Returns("recent indexer errors");
}
[Test]
public void should_return_warning_when_no_indexer_present()
{
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_warning_when_no_search_supported_indexer_present()
{
GivenIndexer(true, false);
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_ok_when_automatic_and__search_is_enabled()
{
GivenIndexer(false, true);
GivenAutomaticSearchEnabled();
GivenInteractiveSearchEnabled();
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_warning_when_only_automatic_search_is_enabled()
{
GivenIndexer(false, true);
GivenAutomaticSearchEnabled();
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_warning_when_only_interactive_search_is_enabled()
{
GivenIndexer(false, true);
GivenInteractiveSearchEnabled();
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_warning_if_search_is_supported_but_disabled()
{
GivenIndexer(false, true);
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_filter_warning_if_search_is_enabled_but_filtered()
{
GivenIndexer(false, true);
GivenSearchFiltered();
Subject.Check().ShouldBeWarning("recent indexer errors");
}
}
}

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Composition;
using NzbDrone.Core.Messaging.Events;
@ -8,13 +9,53 @@ namespace NzbDrone.Core.Applications
{
public interface IApplicationFactory : IProviderFactory<IApplication, ApplicationDefinition>
{
List<IApplication> SyncEnabled(bool filterBlockedIndexers = true);
}
public class ApplicationFactory : ProviderFactory<IApplication, ApplicationDefinition>, IApplicationFactory
{
public ApplicationFactory(IApplicationsRepository providerRepository, IEnumerable<IApplication> providers, IContainer container, IEventAggregator eventAggregator, Logger logger)
private readonly IApplicationStatusService _applicationStatusService;
private readonly Logger _logger;
public ApplicationFactory(IApplicationStatusService applicationStatusService,
IApplicationsRepository providerRepository,
IEnumerable<IApplication> providers,
IContainer container,
IEventAggregator eventAggregator,
Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger)
{
_applicationStatusService = applicationStatusService;
_logger = logger;
}
public List<IApplication> SyncEnabled(bool filterBlockedClients = true)
{
var enabledClients = GetAvailableProviders().Where(n => ((ApplicationDefinition)n.Definition).Enable);
if (filterBlockedClients)
{
return FilterBlockedApplications(enabledClients).ToList();
}
return enabledClients.ToList();
}
private IEnumerable<IApplication> FilterBlockedApplications(IEnumerable<IApplication> applications)
{
var blockedApplications = _applicationStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v);
foreach (var application in applications)
{
ApplicationStatus blockedApplicationStatus;
if (blockedApplications.TryGetValue(application.Definition.Id, out blockedApplicationStatus))
{
_logger.Debug("Temporarily ignoring application {0} till {1} due to recent failures.", application.Definition.Name, blockedApplicationStatus.DisabledTill.Value.ToLocalTime());
continue;
}
yield return application;
}
}
}
}

@ -1,5 +1,8 @@
using System;
using System.Linq;
using System.Net;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
@ -14,11 +17,13 @@ namespace NzbDrone.Core.Applications
IExecute<ApplicationIndexerSyncCommand>
{
private readonly IApplicationFactory _applicationsFactory;
private readonly IApplicationStatusService _applicationStatusService;
private readonly Logger _logger;
public ApplicationService(IApplicationFactory applicationsFactory, Logger logger)
public ApplicationService(IApplicationFactory applicationsFactory, IApplicationStatusService applicationStatusService, Logger logger)
{
_applicationsFactory = applicationsFactory;
_applicationStatusService = applicationStatusService;
_logger = logger;
}
@ -31,51 +36,103 @@ namespace NzbDrone.Core.Applications
{
var app = _applicationsFactory.GetInstance(appDefinition);
app.SyncIndexers();
ExecuteAction(a => a.SyncIndexers(), app);
}
}
public void HandleAsync(ProviderAddedEvent<IIndexer> message)
{
var enabledApps = _applicationsFactory.GetAvailableProviders()
.Where(n => ((ApplicationDefinition)n.Definition).Enable);
var enabledApps = _applicationsFactory.SyncEnabled();
foreach (var app in enabledApps)
{
app.AddIndexer((IndexerDefinition)message.Definition);
ExecuteAction(a => a.AddIndexer((IndexerDefinition)message.Definition), app);
}
}
public void HandleAsync(ProviderDeletedEvent<IIndexer> message)
{
var enabledApps = _applicationsFactory.GetAvailableProviders()
var enabledApps = _applicationsFactory.SyncEnabled()
.Where(n => ((ApplicationDefinition)n.Definition).SyncLevel == ApplicationSyncLevel.FullSync);
foreach (var app in enabledApps)
{
app.RemoveIndexer(message.ProviderId);
ExecuteAction(a => a.RemoveIndexer(message.ProviderId), app);
}
}
public void HandleAsync(ProviderUpdatedEvent<IIndexer> message)
{
var enabledApps = _applicationsFactory.GetAvailableProviders()
var enabledApps = _applicationsFactory.SyncEnabled()
.Where(n => ((ApplicationDefinition)n.Definition).SyncLevel == ApplicationSyncLevel.FullSync);
foreach (var app in enabledApps)
{
app.UpdateIndexer((IndexerDefinition)message.Definition);
ExecuteAction(a => a.UpdateIndexer((IndexerDefinition)message.Definition), app);
}
}
public void Execute(ApplicationIndexerSyncCommand message)
{
var enabledApps = _applicationsFactory.GetAvailableProviders()
.Where(n => ((ApplicationDefinition)n.Definition).Enable);
var enabledApps = _applicationsFactory.SyncEnabled();
foreach (var app in enabledApps)
{
app.SyncIndexers();
ExecuteAction(a => a.SyncIndexers(), app);
}
}
private void ExecuteAction(Action<IApplication> applicationAction, IApplication application)
{
try
{
applicationAction(application);
_applicationStatusService.RecordSuccess(application.Definition.Id);
}
catch (WebException webException)
{
if (webException.Status == WebExceptionStatus.NameResolutionFailure ||
webException.Status == WebExceptionStatus.ConnectFailure)
{
_applicationStatusService.RecordConnectionFailure(application.Definition.Id);
}
else
{
_applicationStatusService.RecordFailure(application.Definition.Id);
}
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
webException.Message.Contains("timed out"))
{
_logger.Warn("{0} server is currently unavailable. {1}", this, webException.Message);
}
else
{
_logger.Warn("{0} {1}", this, webException.Message);
}
}
catch (TooManyRequestsException ex)
{
if (ex.RetryAfter != TimeSpan.Zero)
{
_applicationStatusService.RecordFailure(application.Definition.Id, ex.RetryAfter);
}
else
{
_applicationStatusService.RecordFailure(application.Definition.Id, TimeSpan.FromHours(1));
}
_logger.Warn("API Request Limit reached for {0}", this);
}
catch (HttpException ex)
{
_applicationStatusService.RecordFailure(application.Definition.Id);
_logger.Warn("{0} {1}", this, ex.Message);
}
catch (Exception ex)
{
_applicationStatusService.RecordFailure(application.Definition.Id);
_logger.Error(ex, "An error occurred while talking to application.");
}
}
}

@ -0,0 +1,8 @@
using NzbDrone.Core.ThingiProvider.Status;
namespace NzbDrone.Core.Applications
{
public class ApplicationStatus : ProviderStatusBase
{
}
}

@ -0,0 +1,18 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider.Status;
namespace NzbDrone.Core.Applications
{
public interface IApplicationStatusRepository : IProviderStatusRepository<ApplicationStatus>
{
}
public class ApplicationStatusRepository : ProviderStatusRepository<ApplicationStatus>, IApplicationStatusRepository
{
public ApplicationStatusRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
}
}

@ -0,0 +1,22 @@
using System;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider.Status;
namespace NzbDrone.Core.Applications
{
public interface IApplicationStatusService : IProviderStatusServiceBase<ApplicationStatus>
{
}
public class ApplicationStatusService : ProviderStatusServiceBase<IApplication, ApplicationStatus>, IApplicationStatusService
{
public ApplicationStatusService(IApplicationStatusRepository providerStatusRepository, IEventAggregator eventAggregator, IRuntimeInfo runtimeInfo, Logger logger)
: base(providerStatusRepository, eventAggregator, runtimeInfo, logger)
{
MinimumTimeSinceInitialFailure = TimeSpan.FromMinutes(5);
MaximumEscalationLevel = 5;
}
}
}

@ -76,7 +76,7 @@ namespace NzbDrone.Core.Applications.Lidarr
var indexers = _lidarrV1Proxy.GetIndexers(Settings);
//Pull all local indexers (TODO only those that support movie categories.)
var prowlarrIndexers = _indexerFactory.GetAvailableProviders();
var prowlarrIndexers = _indexerFactory.Enabled();
//Pull mapping so we can check the mapping to see what already exists.
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
@ -107,9 +107,9 @@ namespace NzbDrone.Core.Applications.Lidarr
{
Id = 0,
Name = $"{indexer.Name} (Prowlarr)",
EnableRss = indexer.EnableRss,
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
EnableRss = true,
EnableAutomaticSearch = true,
EnableInteractiveSearch = true,
Priority = indexer.Priority,
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
ConfigContract = schema.ConfigContract,

@ -76,7 +76,7 @@ namespace NzbDrone.Core.Applications.Radarr
var indexers = _radarrV3Proxy.GetIndexers(Settings);
//Pull all local indexers (TODO only those that support movie categories.)
var prowlarrIndexers = _indexerFactory.GetAvailableProviders();
var prowlarrIndexers = _indexerFactory.Enabled();
//Pull mapping so we can check the mapping to see what already exists.
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
@ -107,9 +107,9 @@ namespace NzbDrone.Core.Applications.Radarr
{
Id = 0,
Name = $"{indexer.Name} (Prowlarr)",
EnableRss = indexer.EnableRss,
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
EnableRss = true,
EnableAutomaticSearch = true,
EnableInteractiveSearch = true,
Priority = indexer.Priority,
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
ConfigContract = schema.ConfigContract,

@ -76,7 +76,7 @@ namespace NzbDrone.Core.Applications.Readarr
var indexers = _readarrV1Proxy.GetIndexers(Settings);
//Pull all local indexers (TODO only those that support movie categories.)
var prowlarrIndexers = _indexerFactory.GetAvailableProviders();
var prowlarrIndexers = _indexerFactory.Enabled();
//Pull mapping so we can check the mapping to see what already exists.
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
@ -107,9 +107,9 @@ namespace NzbDrone.Core.Applications.Readarr
{
Id = 0,
Name = $"{indexer.Name} (Prowlarr)",
EnableRss = indexer.EnableRss,
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
EnableRss = true,
EnableAutomaticSearch = true,
EnableInteractiveSearch = true,
Priority = indexer.Priority,
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
ConfigContract = schema.ConfigContract,

@ -76,7 +76,7 @@ namespace NzbDrone.Core.Applications.Sonarr
var indexers = _sonarrV3Proxy.GetIndexers(Settings);
//Pull all local indexers (TODO only those that support movie categories.)
var prowlarrIndexers = _indexerFactory.GetAvailableProviders();
var prowlarrIndexers = _indexerFactory.Enabled();
//Pull mapping so we can check the mapping to see what already exists.
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
@ -107,9 +107,9 @@ namespace NzbDrone.Core.Applications.Sonarr
{
Id = 0,
Name = $"{indexer.Name} (Prowlarr)",
EnableRss = indexer.EnableRss,
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
EnableRss = true,
EnableAutomaticSearch = true,
EnableInteractiveSearch = true,
Priority = indexer.Priority,
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
ConfigContract = schema.ConfigContract,

@ -0,0 +1,24 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(2)]
public class ApplicationStatus : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.Column("EnableAutomaticSearch").FromTable("Indexers");
Delete.Column("EnableInteractiveSearch").FromTable("Indexers");
Rename.Column("EnableRss").OnTable("Indexers").To("Enable");
Create.TableForModel("ApplicationStatus")
.WithColumn("ProviderId").AsInt32().NotNullable().Unique()
.WithColumn("InitialFailure").AsDateTime().Nullable()
.WithColumn("MostRecentFailure").AsDateTime().Nullable()
.WithColumn("EscalationLevel").AsInt32().NotNullable()
.WithColumn("DisabledTill").AsDateTime().Nullable();
}
}
}

@ -40,7 +40,6 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<IndexerDefinition>("Indexers").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.Enable)
.Ignore(i => i.Protocol)
.Ignore(i => i.Privacy)
.Ignore(i => i.SupportsRss)
@ -69,6 +68,8 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<IndexerStatus>("IndexerStatus").RegisterModel();
Mapper.Entity<ApplicationStatus>("ApplicationStatus").RegisterModel();
Mapper.Entity<CustomFilter>("CustomFilters").RegisterModel();
}

@ -0,0 +1,57 @@
using System;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Applications;
using NzbDrone.Core.Localization;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ProviderUpdatedEvent<IApplication>))]
[CheckOn(typeof(ProviderDeletedEvent<IApplication>))]
[CheckOn(typeof(ProviderStatusChangedEvent<IApplication>))]
public class ApplicationStatusCheck : HealthCheckBase
{
private readonly IApplicationFactory _providerFactory;
private readonly IApplicationStatusService _providerStatusService;
public ApplicationStatusCheck(IApplicationFactory providerFactory, IApplicationStatusService providerStatusService, ILocalizationService localizationService)
: base(localizationService)
{
_providerFactory = providerFactory;
_providerStatusService = providerStatusService;
}
public override HealthCheck Check()
{
var enabledProviders = _providerFactory.GetAvailableProviders();
var backOffProviders = enabledProviders.Join(_providerStatusService.GetBlockedProviders(),
i => i.Definition.Id,
s => s.ProviderId,
(i, s) => new { Provider = i, Status = s })
.Where(p => p.Status.InitialFailure.HasValue &&
p.Status.InitialFailure.Value.After(
DateTime.UtcNow.AddHours(-6)))
.ToList();
if (backOffProviders.Empty())
{
return new HealthCheck(GetType());
}
if (backOffProviders.Count == enabledProviders.Count)
{
return new HealthCheck(GetType(),
HealthCheckResult.Error,
_localizationService.GetLocalizedString("ApplicationStatusCheckAllClientMessage"),
"#applications-are-unavailable-due-to-failures");
}
return new HealthCheck(GetType(),
HealthCheckResult.Warning,
string.Format(_localizationService.GetLocalizedString("ApplicationStatusCheckSingleClientMessage"),
string.Join(", ", backOffProviders.Select(v => v.Provider.Definition.Name))),
"#applications-are-unavailable-due-to-failures");
}
}
}

@ -21,14 +21,14 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var enabled = _indexerFactory.RssEnabled(false);
var enabled = _indexerFactory.Enabled(false);
if (enabled.Empty())
{
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("IndexerRssHealthCheckNoIndexers"));
}
var active = _indexerFactory.RssEnabled(true);
var active = _indexerFactory.Enabled(true);
if (active.Empty())
{

@ -1,48 +0,0 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ProviderAddedEvent<IIndexer>))]
[CheckOn(typeof(ProviderUpdatedEvent<IIndexer>))]
[CheckOn(typeof(ProviderDeletedEvent<IIndexer>))]
[CheckOn(typeof(ProviderStatusChangedEvent<IIndexer>))]
public class IndexerSearchCheck : HealthCheckBase
{
private readonly IIndexerFactory _indexerFactory;
public IndexerSearchCheck(IIndexerFactory indexerFactory, ILocalizationService localizationService)
: base(localizationService)
{
_indexerFactory = indexerFactory;
}
public override HealthCheck Check()
{
var automaticSearchEnabled = _indexerFactory.AutomaticSearchEnabled(false);
if (automaticSearchEnabled.Empty())
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, _localizationService.GetLocalizedString("IndexerSearchCheckNoAutomaticMessage"));
}
var interactiveSearchEnabled = _indexerFactory.InteractiveSearchEnabled(false);
if (interactiveSearchEnabled.Empty())
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, _localizationService.GetLocalizedString("IndexerSearchCheckNoInteractiveMessage"));
}
var active = _indexerFactory.AutomaticSearchEnabled(true);
if (active.Empty())
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, _localizationService.GetLocalizedString("IndexerSearchCheckNoAvailableIndexersMessage"));
}
return new HealthCheck(GetType());
}
}
}

@ -129,9 +129,7 @@ namespace NzbDrone.Core.IndexerSearch
private List<ReleaseInfo> Dispatch(Func<IIndexer, IndexerPageableQueryResult> searchAction, SearchCriteriaBase criteriaBase)
{
var indexers = criteriaBase.InteractiveSearch ?
_indexerFactory.InteractiveSearchEnabled() :
_indexerFactory.AutomaticSearchEnabled();
var indexers = _indexerFactory.GetAvailableProviders();
if (criteriaBase.IndexerIds != null && criteriaBase.IndexerIds.Count > 0)
{

@ -63,9 +63,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
{
return new IndexerDefinition
{
EnableRss = false,
EnableAutomaticSearch = false,
EnableInteractiveSearch = false,
Enable = true,
Name = definition.Name,
Implementation = GetType().Name,
Settings = new CardigannSettings { DefinitionFile = definition.File },

@ -91,9 +91,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{
return new IndexerDefinition
{
EnableRss = false,
EnableAutomaticSearch = false,
EnableInteractiveSearch = false,
Enable = true,
Name = name,
Implementation = GetType().Name,
Settings = settings,

@ -23,9 +23,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
{
return new IndexerDefinition
{
EnableRss = false,
EnableAutomaticSearch = false,
EnableInteractiveSearch = false,
Enable = true,
Name = name,
Implementation = GetType().Name,
Settings = settings,

@ -57,9 +57,7 @@ namespace NzbDrone.Core.Indexers.Torznab
{
return new IndexerDefinition
{
EnableRss = false,
EnableAutomaticSearch = false,
EnableInteractiveSearch = false,
Enable = true,
Name = name,
Implementation = GetType().Name,
Settings = settings,

@ -50,9 +50,7 @@ namespace NzbDrone.Core.Indexers
yield return new IndexerDefinition
{
Name = GetType().Name,
EnableRss = config.Validate().IsValid && SupportsRss,
EnableAutomaticSearch = config.Validate().IsValid && SupportsSearch,
EnableInteractiveSearch = config.Validate().IsValid && SupportsSearch,
Enable = config.Validate().IsValid && SupportsRss,
Implementation = GetType().Name,
Settings = config
};

@ -7,9 +7,6 @@ namespace NzbDrone.Core.Indexers
{
public class IndexerDefinition : ProviderDefinition
{
public bool EnableRss { get; set; }
public bool EnableAutomaticSearch { get; set; }
public bool EnableInteractiveSearch { get; set; }
public DownloadProtocol Protocol { get; set; }
public IndexerPrivacy Privacy { get; set; }
public bool SupportsRss { get; set; }
@ -18,8 +15,6 @@ namespace NzbDrone.Core.Indexers
public int Priority { get; set; } = 25;
public DateTime Added { get; set; }
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;
public IndexerStatus Status { get; set; }
public List<SettingsField> ExtraFields { get; set; } = new List<SettingsField>();

@ -14,9 +14,7 @@ namespace NzbDrone.Core.Indexers
{
public interface IIndexerFactory : IProviderFactory<IIndexer, IndexerDefinition>
{
List<IIndexer> RssEnabled(bool filterBlockedIndexers = true);
List<IIndexer> AutomaticSearchEnabled(bool filterBlockedIndexers = true);
List<IIndexer> InteractiveSearchEnabled(bool filterBlockedIndexers = true);
List<IIndexer> Enabled(bool filterBlockedIndexers = true);
void DeleteIndexers(List<int> indexerIds);
}
@ -165,33 +163,9 @@ namespace NzbDrone.Core.Indexers
}
}
public List<IIndexer> RssEnabled(bool filterBlockedIndexers = true)
public List<IIndexer> Enabled(bool filterBlockedIndexers = true)
{
var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableRss);
if (filterBlockedIndexers)
{
return FilterBlockedIndexers(enabledIndexers).ToList();
}
return enabledIndexers.ToList();
}
public List<IIndexer> AutomaticSearchEnabled(bool filterBlockedIndexers = true)
{
var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableAutomaticSearch);
if (filterBlockedIndexers)
{
return FilterBlockedIndexers(enabledIndexers).ToList();
}
return enabledIndexers.ToList();
}
public List<IIndexer> InteractiveSearchEnabled(bool filterBlockedIndexers = true)
{
var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).EnableInteractiveSearch);
var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).Enable);
if (filterBlockedIndexers)
{

@ -35,6 +35,8 @@
"ApiKey": "API Key",
"AppDataDirectory": "AppData directory",
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
"ApplicationStatusCheckAllClientMessage": "All applications are unavailable due to failures",
"ApplicationStatusCheckSingleClientMessage": "Applications unavailable due to failures: {0}",
"Apply": "Apply",
"ApplyTags": "Apply Tags",
"ApplyTagsHelpTexts1": "How to apply tags to the selected movies",
@ -185,8 +187,6 @@
"DownloadClients": "Download Clients",
"DownloadClientSettings": "Download Client Settings",
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
"DownloadClientStatusCheckAllClientMessage": "All download clients are unavailable due to failures",
"DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {0}",
"DownloadClientUnavailable": "Download client is unavailable",
"Downloaded": "Downloaded",
"DownloadedAndMonitored": "Downloaded and Monitored",

@ -16,7 +16,7 @@ namespace NzbDrone.Integration.Test.ApiTests
indexers.Should().NotBeEmpty();
indexers.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name));
indexers.Where(c => c.ConfigContract == typeof(NullConfig).Name).Should().OnlyContain(c => c.EnableRss);
indexers.Where(c => c.ConfigContract == typeof(NullConfig).Name).Should().OnlyContain(c => c.Enable);
}
}
}

@ -36,9 +36,7 @@ namespace NzbDrone.Integration.Test
{
Indexers.Post(new Prowlarr.Api.V1.Indexers.IndexerResource
{
EnableRss = false,
EnableInteractiveSearch = false,
EnableAutomaticSearch = false,
Enable = false,
ConfigContract = nameof(FileListSettings),
Implementation = nameof(FileList),
Name = "NewznabTest",

@ -10,9 +10,7 @@ namespace Prowlarr.Api.V1.Indexers
{
public class IndexerResource : ProviderResource
{
public bool EnableRss { get; set; }
public bool EnableAutomaticSearch { get; set; }
public bool EnableInteractiveSearch { get; set; }
public bool Enable { get; set; }
public bool SupportsRss { get; set; }
public bool SupportsSearch { get; set; }
public DownloadProtocol Protocol { get; set; }
@ -51,9 +49,7 @@ namespace Prowlarr.Api.V1.Indexers
}
}
resource.EnableRss = definition.EnableRss;
resource.EnableAutomaticSearch = definition.EnableAutomaticSearch;
resource.EnableInteractiveSearch = definition.EnableInteractiveSearch;
resource.Enable = definition.Enable;
resource.SupportsRss = definition.SupportsRss;
resource.SupportsSearch = definition.SupportsSearch;
resource.Capabilities = definition.Capabilities.ToResource();
@ -88,9 +84,7 @@ namespace Prowlarr.Api.V1.Indexers
}
}
definition.EnableRss = resource.EnableRss;
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;
definition.EnableInteractiveSearch = resource.EnableInteractiveSearch;
definition.Enable = resource.Enable;
definition.Priority = resource.Priority;
definition.Privacy = resource.Privacy;
definition.Added = resource.Added;

Loading…
Cancel
Save