mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
New: Application Status Warnings
This commit is contained in:
@@ -61,9 +61,7 @@ class MovieIndexRow extends Component {
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
enableRss,
|
enable,
|
||||||
enableAutomaticSearch,
|
|
||||||
enableInteractiveSearch,
|
|
||||||
tags,
|
tags,
|
||||||
protocol,
|
protocol,
|
||||||
privacy,
|
privacy,
|
||||||
@@ -114,7 +112,7 @@ class MovieIndexRow extends Component {
|
|||||||
<IndexerStatusCell
|
<IndexerStatusCell
|
||||||
key={column.name}
|
key={column.name}
|
||||||
className={styles[column.name]}
|
className={styles[column.name]}
|
||||||
enabled={enableRss || enableAutomaticSearch || enableInteractiveSearch}
|
enabled={enable}
|
||||||
status={status}
|
status={status}
|
||||||
longDateFormat={longDateFormat}
|
longDateFormat={longDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
@@ -255,9 +253,7 @@ MovieIndexRow.propTypes = {
|
|||||||
privacy: PropTypes.string.isRequired,
|
privacy: PropTypes.string.isRequired,
|
||||||
priority: PropTypes.number.isRequired,
|
priority: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
enableRss: PropTypes.bool.isRequired,
|
enable: PropTypes.bool.isRequired,
|
||||||
enableAutomaticSearch: PropTypes.bool.isRequired,
|
|
||||||
enableInteractiveSearch: PropTypes.bool.isRequired,
|
|
||||||
status: PropTypes.object,
|
status: PropTypes.object,
|
||||||
capabilities: PropTypes.object.isRequired,
|
capabilities: PropTypes.object.isRequired,
|
||||||
added: PropTypes.string.isRequired,
|
added: PropTypes.string.isRequired,
|
||||||
|
@@ -38,11 +38,8 @@ function EditIndexerModalContent(props) {
|
|||||||
id,
|
id,
|
||||||
implementationName,
|
implementationName,
|
||||||
name,
|
name,
|
||||||
enableRss,
|
enable,
|
||||||
enableAutomaticSearch,
|
|
||||||
enableInteractiveSearch,
|
|
||||||
supportsRss,
|
supportsRss,
|
||||||
supportsSearch,
|
|
||||||
fields,
|
fields,
|
||||||
priority
|
priority
|
||||||
} = item;
|
} = item;
|
||||||
@@ -81,42 +78,14 @@ function EditIndexerModalContent(props) {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('EnableRSS')}</FormLabel>
|
<FormLabel>{translate('Enable')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="enableRss"
|
name="enable"
|
||||||
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
|
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
|
||||||
isDisabled={!supportsRss.value}
|
isDisabled={!supportsRss.value}
|
||||||
{...enableRss}
|
{...enable}
|
||||||
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}
|
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@@ -65,11 +65,8 @@ class Indexer extends Component {
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
enableRss,
|
enable,
|
||||||
enableAutomaticSearch,
|
|
||||||
enableInteractiveSearch,
|
|
||||||
supportsRss,
|
supportsRss,
|
||||||
supportsSearch,
|
|
||||||
priority,
|
priority,
|
||||||
showPriority
|
showPriority
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -96,26 +93,12 @@ class Indexer extends Component {
|
|||||||
<div className={styles.enabled}>
|
<div className={styles.enabled}>
|
||||||
|
|
||||||
{
|
{
|
||||||
supportsRss && enableRss &&
|
supportsRss && enable &&
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
RSS
|
RSS
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
supportsSearch && enableAutomaticSearch &&
|
|
||||||
<Label kind={kinds.SUCCESS}>
|
|
||||||
{translate('AutomaticSearch')}
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
supportsSearch && enableInteractiveSearch &&
|
|
||||||
<Label kind={kinds.SUCCESS}>
|
|
||||||
{translate('InteractiveSearch')}
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
showPriority &&
|
showPriority &&
|
||||||
<Label kind={kinds.DEFAULT}>
|
<Label kind={kinds.DEFAULT}>
|
||||||
@@ -123,7 +106,7 @@ class Indexer extends Component {
|
|||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!enableRss && !enableAutomaticSearch && !enableInteractiveSearch &&
|
!enable &&
|
||||||
<Label
|
<Label
|
||||||
kind={kinds.DISABLED}
|
kind={kinds.DISABLED}
|
||||||
outline={true}
|
outline={true}
|
||||||
@@ -157,9 +140,7 @@ class Indexer extends Component {
|
|||||||
Indexer.propTypes = {
|
Indexer.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
enableRss: PropTypes.bool.isRequired,
|
enable: PropTypes.bool.isRequired,
|
||||||
enableAutomaticSearch: PropTypes.bool.isRequired,
|
|
||||||
enableInteractiveSearch: PropTypes.bool.isRequired,
|
|
||||||
supportsRss: PropTypes.bool.isRequired,
|
supportsRss: PropTypes.bool.isRequired,
|
||||||
supportsSearch: PropTypes.bool.isRequired,
|
supportsSearch: PropTypes.bool.isRequired,
|
||||||
onCloneIndexerPress: PropTypes.func.isRequired,
|
onCloneIndexerPress: PropTypes.func.isRequired,
|
||||||
|
@@ -142,9 +142,7 @@ export const reducers = createHandleActions({
|
|||||||
|
|
||||||
[SELECT_INDEXER_SCHEMA]: (state, { payload }) => {
|
[SELECT_INDEXER_SCHEMA]: (state, { payload }) => {
|
||||||
return selectSchema(state, payload, (selectedSchema) => {
|
return selectSchema(state, payload, (selectedSchema) => {
|
||||||
selectedSchema.enableRss = selectedSchema.supportsRss;
|
selectedSchema.enable = selectedSchema.supportsRss;
|
||||||
selectedSchema.enableAutomaticSearch = selectedSchema.supportsSearch;
|
|
||||||
selectedSchema.enableInteractiveSearch = selectedSchema.supportsSearch;
|
|
||||||
|
|
||||||
return selectedSchema;
|
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>());
|
.Returns(new List<IIndexer>());
|
||||||
|
|
||||||
Mocker.GetMock<IIndexerFactory>()
|
Mocker.GetMock<IIndexerFactory>()
|
||||||
.Setup(s => s.RssEnabled(It.IsAny<bool>()))
|
.Setup(s => s.Enabled(It.IsAny<bool>()))
|
||||||
.Returns(new List<IIndexer>());
|
.Returns(new List<IIndexer>());
|
||||||
|
|
||||||
Mocker.GetMock<ILocalizationService>()
|
Mocker.GetMock<ILocalizationService>()
|
||||||
@@ -43,14 +43,14 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
|
|||||||
private void GivenRssEnabled()
|
private void GivenRssEnabled()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IIndexerFactory>()
|
Mocker.GetMock<IIndexerFactory>()
|
||||||
.Setup(s => s.RssEnabled(It.IsAny<bool>()))
|
.Setup(s => s.Enabled(It.IsAny<bool>()))
|
||||||
.Returns(new List<IIndexer> { _indexerMock.Object });
|
.Returns(new List<IIndexer> { _indexerMock.Object });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenRssFiltered()
|
private void GivenRssFiltered()
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IIndexerFactory>()
|
Mocker.GetMock<IIndexerFactory>()
|
||||||
.Setup(s => s.RssEnabled(false))
|
.Setup(s => s.Enabled(false))
|
||||||
.Returns(new List<IIndexer> { _indexerMock.Object });
|
.Returns(new List<IIndexer> { _indexerMock.Object });
|
||||||
|
|
||||||
Mocker.GetMock<ILocalizationService>()
|
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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common.Composition;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
@@ -8,13 +9,53 @@ namespace NzbDrone.Core.Applications
|
|||||||
{
|
{
|
||||||
public interface IApplicationFactory : IProviderFactory<IApplication, ApplicationDefinition>
|
public interface IApplicationFactory : IProviderFactory<IApplication, ApplicationDefinition>
|
||||||
{
|
{
|
||||||
|
List<IApplication> SyncEnabled(bool filterBlockedIndexers = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ApplicationFactory : ProviderFactory<IApplication, ApplicationDefinition>, IApplicationFactory
|
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)
|
: 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.Linq;
|
||||||
|
using System.Net;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
@@ -14,11 +17,13 @@ namespace NzbDrone.Core.Applications
|
|||||||
IExecute<ApplicationIndexerSyncCommand>
|
IExecute<ApplicationIndexerSyncCommand>
|
||||||
{
|
{
|
||||||
private readonly IApplicationFactory _applicationsFactory;
|
private readonly IApplicationFactory _applicationsFactory;
|
||||||
|
private readonly IApplicationStatusService _applicationStatusService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public ApplicationService(IApplicationFactory applicationsFactory, Logger logger)
|
public ApplicationService(IApplicationFactory applicationsFactory, IApplicationStatusService applicationStatusService, Logger logger)
|
||||||
{
|
{
|
||||||
_applicationsFactory = applicationsFactory;
|
_applicationsFactory = applicationsFactory;
|
||||||
|
_applicationStatusService = applicationStatusService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,51 +36,103 @@ namespace NzbDrone.Core.Applications
|
|||||||
{
|
{
|
||||||
var app = _applicationsFactory.GetInstance(appDefinition);
|
var app = _applicationsFactory.GetInstance(appDefinition);
|
||||||
|
|
||||||
app.SyncIndexers();
|
ExecuteAction(a => a.SyncIndexers(), app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleAsync(ProviderAddedEvent<IIndexer> message)
|
public void HandleAsync(ProviderAddedEvent<IIndexer> message)
|
||||||
{
|
{
|
||||||
var enabledApps = _applicationsFactory.GetAvailableProviders()
|
var enabledApps = _applicationsFactory.SyncEnabled();
|
||||||
.Where(n => ((ApplicationDefinition)n.Definition).Enable);
|
|
||||||
|
|
||||||
foreach (var app in enabledApps)
|
foreach (var app in enabledApps)
|
||||||
{
|
{
|
||||||
app.AddIndexer((IndexerDefinition)message.Definition);
|
ExecuteAction(a => a.AddIndexer((IndexerDefinition)message.Definition), app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleAsync(ProviderDeletedEvent<IIndexer> message)
|
public void HandleAsync(ProviderDeletedEvent<IIndexer> message)
|
||||||
{
|
{
|
||||||
var enabledApps = _applicationsFactory.GetAvailableProviders()
|
var enabledApps = _applicationsFactory.SyncEnabled()
|
||||||
.Where(n => ((ApplicationDefinition)n.Definition).SyncLevel == ApplicationSyncLevel.FullSync);
|
.Where(n => ((ApplicationDefinition)n.Definition).SyncLevel == ApplicationSyncLevel.FullSync);
|
||||||
|
|
||||||
foreach (var app in enabledApps)
|
foreach (var app in enabledApps)
|
||||||
{
|
{
|
||||||
app.RemoveIndexer(message.ProviderId);
|
ExecuteAction(a => a.RemoveIndexer(message.ProviderId), app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleAsync(ProviderUpdatedEvent<IIndexer> message)
|
public void HandleAsync(ProviderUpdatedEvent<IIndexer> message)
|
||||||
{
|
{
|
||||||
var enabledApps = _applicationsFactory.GetAvailableProviders()
|
var enabledApps = _applicationsFactory.SyncEnabled()
|
||||||
.Where(n => ((ApplicationDefinition)n.Definition).SyncLevel == ApplicationSyncLevel.FullSync);
|
.Where(n => ((ApplicationDefinition)n.Definition).SyncLevel == ApplicationSyncLevel.FullSync);
|
||||||
|
|
||||||
foreach (var app in enabledApps)
|
foreach (var app in enabledApps)
|
||||||
{
|
{
|
||||||
app.UpdateIndexer((IndexerDefinition)message.Definition);
|
ExecuteAction(a => a.UpdateIndexer((IndexerDefinition)message.Definition), app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(ApplicationIndexerSyncCommand message)
|
public void Execute(ApplicationIndexerSyncCommand message)
|
||||||
{
|
{
|
||||||
var enabledApps = _applicationsFactory.GetAvailableProviders()
|
var enabledApps = _applicationsFactory.SyncEnabled();
|
||||||
.Where(n => ((ApplicationDefinition)n.Definition).Enable);
|
|
||||||
|
|
||||||
foreach (var app in enabledApps)
|
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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
src/NzbDrone.Core/Applications/ApplicationStatus.cs
Normal file
8
src/NzbDrone.Core/Applications/ApplicationStatus.cs
Normal file
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/NzbDrone.Core/Applications/ApplicationStatusService.cs
Normal file
22
src/NzbDrone.Core/Applications/ApplicationStatusService.cs
Normal file
@@ -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);
|
var indexers = _lidarrV1Proxy.GetIndexers(Settings);
|
||||||
|
|
||||||
//Pull all local indexers (TODO only those that support movie categories.)
|
//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.
|
//Pull mapping so we can check the mapping to see what already exists.
|
||||||
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
||||||
@@ -107,9 +107,9 @@ namespace NzbDrone.Core.Applications.Lidarr
|
|||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Name = $"{indexer.Name} (Prowlarr)",
|
Name = $"{indexer.Name} (Prowlarr)",
|
||||||
EnableRss = indexer.EnableRss,
|
EnableRss = true,
|
||||||
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
|
EnableAutomaticSearch = true,
|
||||||
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
|
EnableInteractiveSearch = true,
|
||||||
Priority = indexer.Priority,
|
Priority = indexer.Priority,
|
||||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||||
ConfigContract = schema.ConfigContract,
|
ConfigContract = schema.ConfigContract,
|
||||||
|
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
var indexers = _radarrV3Proxy.GetIndexers(Settings);
|
var indexers = _radarrV3Proxy.GetIndexers(Settings);
|
||||||
|
|
||||||
//Pull all local indexers (TODO only those that support movie categories.)
|
//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.
|
//Pull mapping so we can check the mapping to see what already exists.
|
||||||
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
||||||
@@ -107,9 +107,9 @@ namespace NzbDrone.Core.Applications.Radarr
|
|||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Name = $"{indexer.Name} (Prowlarr)",
|
Name = $"{indexer.Name} (Prowlarr)",
|
||||||
EnableRss = indexer.EnableRss,
|
EnableRss = true,
|
||||||
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
|
EnableAutomaticSearch = true,
|
||||||
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
|
EnableInteractiveSearch = true,
|
||||||
Priority = indexer.Priority,
|
Priority = indexer.Priority,
|
||||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||||
ConfigContract = schema.ConfigContract,
|
ConfigContract = schema.ConfigContract,
|
||||||
|
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Applications.Readarr
|
|||||||
var indexers = _readarrV1Proxy.GetIndexers(Settings);
|
var indexers = _readarrV1Proxy.GetIndexers(Settings);
|
||||||
|
|
||||||
//Pull all local indexers (TODO only those that support movie categories.)
|
//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.
|
//Pull mapping so we can check the mapping to see what already exists.
|
||||||
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
||||||
@@ -107,9 +107,9 @@ namespace NzbDrone.Core.Applications.Readarr
|
|||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Name = $"{indexer.Name} (Prowlarr)",
|
Name = $"{indexer.Name} (Prowlarr)",
|
||||||
EnableRss = indexer.EnableRss,
|
EnableRss = true,
|
||||||
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
|
EnableAutomaticSearch = true,
|
||||||
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
|
EnableInteractiveSearch = true,
|
||||||
Priority = indexer.Priority,
|
Priority = indexer.Priority,
|
||||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||||
ConfigContract = schema.ConfigContract,
|
ConfigContract = schema.ConfigContract,
|
||||||
|
@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
var indexers = _sonarrV3Proxy.GetIndexers(Settings);
|
var indexers = _sonarrV3Proxy.GetIndexers(Settings);
|
||||||
|
|
||||||
//Pull all local indexers (TODO only those that support movie categories.)
|
//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.
|
//Pull mapping so we can check the mapping to see what already exists.
|
||||||
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
|
||||||
@@ -107,9 +107,9 @@ namespace NzbDrone.Core.Applications.Sonarr
|
|||||||
{
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Name = $"{indexer.Name} (Prowlarr)",
|
Name = $"{indexer.Name} (Prowlarr)",
|
||||||
EnableRss = indexer.EnableRss,
|
EnableRss = true,
|
||||||
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
|
EnableAutomaticSearch = true,
|
||||||
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
|
EnableInteractiveSearch = true,
|
||||||
Priority = indexer.Priority,
|
Priority = indexer.Priority,
|
||||||
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
|
||||||
ConfigContract = schema.ConfigContract,
|
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()
|
Mapper.Entity<IndexerDefinition>("Indexers").RegisterModel()
|
||||||
.Ignore(x => x.ImplementationName)
|
.Ignore(x => x.ImplementationName)
|
||||||
.Ignore(i => i.Enable)
|
|
||||||
.Ignore(i => i.Protocol)
|
.Ignore(i => i.Protocol)
|
||||||
.Ignore(i => i.Privacy)
|
.Ignore(i => i.Privacy)
|
||||||
.Ignore(i => i.SupportsRss)
|
.Ignore(i => i.SupportsRss)
|
||||||
@@ -69,6 +68,8 @@ namespace NzbDrone.Core.Datastore
|
|||||||
|
|
||||||
Mapper.Entity<IndexerStatus>("IndexerStatus").RegisterModel();
|
Mapper.Entity<IndexerStatus>("IndexerStatus").RegisterModel();
|
||||||
|
|
||||||
|
Mapper.Entity<ApplicationStatus>("ApplicationStatus").RegisterModel();
|
||||||
|
|
||||||
Mapper.Entity<CustomFilter>("CustomFilters").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()
|
public override HealthCheck Check()
|
||||||
{
|
{
|
||||||
var enabled = _indexerFactory.RssEnabled(false);
|
var enabled = _indexerFactory.Enabled(false);
|
||||||
|
|
||||||
if (enabled.Empty())
|
if (enabled.Empty())
|
||||||
{
|
{
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("IndexerRssHealthCheckNoIndexers"));
|
return new HealthCheck(GetType(), HealthCheckResult.Error, _localizationService.GetLocalizedString("IndexerRssHealthCheckNoIndexers"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var active = _indexerFactory.RssEnabled(true);
|
var active = _indexerFactory.Enabled(true);
|
||||||
|
|
||||||
if (active.Empty())
|
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)
|
private List<ReleaseInfo> Dispatch(Func<IIndexer, IndexerPageableQueryResult> searchAction, SearchCriteriaBase criteriaBase)
|
||||||
{
|
{
|
||||||
var indexers = criteriaBase.InteractiveSearch ?
|
var indexers = _indexerFactory.GetAvailableProviders();
|
||||||
_indexerFactory.InteractiveSearchEnabled() :
|
|
||||||
_indexerFactory.AutomaticSearchEnabled();
|
|
||||||
|
|
||||||
if (criteriaBase.IndexerIds != null && criteriaBase.IndexerIds.Count > 0)
|
if (criteriaBase.IndexerIds != null && criteriaBase.IndexerIds.Count > 0)
|
||||||
{
|
{
|
||||||
|
@@ -63,9 +63,7 @@ namespace NzbDrone.Core.Indexers.Cardigann
|
|||||||
{
|
{
|
||||||
return new IndexerDefinition
|
return new IndexerDefinition
|
||||||
{
|
{
|
||||||
EnableRss = false,
|
Enable = true,
|
||||||
EnableAutomaticSearch = false,
|
|
||||||
EnableInteractiveSearch = false,
|
|
||||||
Name = definition.Name,
|
Name = definition.Name,
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
Settings = new CardigannSettings { DefinitionFile = definition.File },
|
Settings = new CardigannSettings { DefinitionFile = definition.File },
|
||||||
|
@@ -91,9 +91,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
|||||||
{
|
{
|
||||||
return new IndexerDefinition
|
return new IndexerDefinition
|
||||||
{
|
{
|
||||||
EnableRss = false,
|
Enable = true,
|
||||||
EnableAutomaticSearch = false,
|
|
||||||
EnableInteractiveSearch = false,
|
|
||||||
Name = name,
|
Name = name,
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
Settings = settings,
|
Settings = settings,
|
||||||
|
@@ -23,9 +23,7 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
|
|||||||
{
|
{
|
||||||
return new IndexerDefinition
|
return new IndexerDefinition
|
||||||
{
|
{
|
||||||
EnableRss = false,
|
Enable = true,
|
||||||
EnableAutomaticSearch = false,
|
|
||||||
EnableInteractiveSearch = false,
|
|
||||||
Name = name,
|
Name = name,
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
Settings = settings,
|
Settings = settings,
|
||||||
|
@@ -57,9 +57,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
|||||||
{
|
{
|
||||||
return new IndexerDefinition
|
return new IndexerDefinition
|
||||||
{
|
{
|
||||||
EnableRss = false,
|
Enable = true,
|
||||||
EnableAutomaticSearch = false,
|
|
||||||
EnableInteractiveSearch = false,
|
|
||||||
Name = name,
|
Name = name,
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
Settings = settings,
|
Settings = settings,
|
||||||
|
@@ -50,9 +50,7 @@ namespace NzbDrone.Core.Indexers
|
|||||||
yield return new IndexerDefinition
|
yield return new IndexerDefinition
|
||||||
{
|
{
|
||||||
Name = GetType().Name,
|
Name = GetType().Name,
|
||||||
EnableRss = config.Validate().IsValid && SupportsRss,
|
Enable = config.Validate().IsValid && SupportsRss,
|
||||||
EnableAutomaticSearch = config.Validate().IsValid && SupportsSearch,
|
|
||||||
EnableInteractiveSearch = config.Validate().IsValid && SupportsSearch,
|
|
||||||
Implementation = GetType().Name,
|
Implementation = GetType().Name,
|
||||||
Settings = config
|
Settings = config
|
||||||
};
|
};
|
||||||
|
@@ -7,9 +7,6 @@ namespace NzbDrone.Core.Indexers
|
|||||||
{
|
{
|
||||||
public class IndexerDefinition : ProviderDefinition
|
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 DownloadProtocol Protocol { get; set; }
|
||||||
public IndexerPrivacy Privacy { get; set; }
|
public IndexerPrivacy Privacy { get; set; }
|
||||||
public bool SupportsRss { get; set; }
|
public bool SupportsRss { get; set; }
|
||||||
@@ -18,8 +15,6 @@ namespace NzbDrone.Core.Indexers
|
|||||||
public int Priority { get; set; } = 25;
|
public int Priority { get; set; } = 25;
|
||||||
public DateTime Added { get; set; }
|
public DateTime Added { get; set; }
|
||||||
|
|
||||||
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;
|
|
||||||
|
|
||||||
public IndexerStatus Status { get; set; }
|
public IndexerStatus Status { get; set; }
|
||||||
|
|
||||||
public List<SettingsField> ExtraFields { get; set; } = new List<SettingsField>();
|
public List<SettingsField> ExtraFields { get; set; } = new List<SettingsField>();
|
||||||
|
@@ -14,9 +14,7 @@ namespace NzbDrone.Core.Indexers
|
|||||||
{
|
{
|
||||||
public interface IIndexerFactory : IProviderFactory<IIndexer, IndexerDefinition>
|
public interface IIndexerFactory : IProviderFactory<IIndexer, IndexerDefinition>
|
||||||
{
|
{
|
||||||
List<IIndexer> RssEnabled(bool filterBlockedIndexers = true);
|
List<IIndexer> Enabled(bool filterBlockedIndexers = true);
|
||||||
List<IIndexer> AutomaticSearchEnabled(bool filterBlockedIndexers = true);
|
|
||||||
List<IIndexer> InteractiveSearchEnabled(bool filterBlockedIndexers = true);
|
|
||||||
void DeleteIndexers(List<int> indexerIds);
|
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);
|
var enabledIndexers = GetAvailableProviders().Where(n => ((IndexerDefinition)n.Definition).Enable);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (filterBlockedIndexers)
|
if (filterBlockedIndexers)
|
||||||
{
|
{
|
||||||
|
@@ -35,6 +35,8 @@
|
|||||||
"ApiKey": "API Key",
|
"ApiKey": "API Key",
|
||||||
"AppDataDirectory": "AppData directory",
|
"AppDataDirectory": "AppData directory",
|
||||||
"AppDataLocationHealthCheckMessage": "Updating will not be possible to prevent deleting AppData on Update",
|
"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",
|
"Apply": "Apply",
|
||||||
"ApplyTags": "Apply Tags",
|
"ApplyTags": "Apply Tags",
|
||||||
"ApplyTagsHelpTexts1": "How to apply tags to the selected movies",
|
"ApplyTagsHelpTexts1": "How to apply tags to the selected movies",
|
||||||
@@ -185,8 +187,6 @@
|
|||||||
"DownloadClients": "Download Clients",
|
"DownloadClients": "Download Clients",
|
||||||
"DownloadClientSettings": "Download Client Settings",
|
"DownloadClientSettings": "Download Client Settings",
|
||||||
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
|
"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",
|
"DownloadClientUnavailable": "Download client is unavailable",
|
||||||
"Downloaded": "Downloaded",
|
"Downloaded": "Downloaded",
|
||||||
"DownloadedAndMonitored": "Downloaded and Monitored",
|
"DownloadedAndMonitored": "Downloaded and Monitored",
|
||||||
|
@@ -16,7 +16,7 @@ namespace NzbDrone.Integration.Test.ApiTests
|
|||||||
|
|
||||||
indexers.Should().NotBeEmpty();
|
indexers.Should().NotBeEmpty();
|
||||||
indexers.Should().NotContain(c => string.IsNullOrWhiteSpace(c.Name));
|
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
|
Indexers.Post(new Prowlarr.Api.V1.Indexers.IndexerResource
|
||||||
{
|
{
|
||||||
EnableRss = false,
|
Enable = false,
|
||||||
EnableInteractiveSearch = false,
|
|
||||||
EnableAutomaticSearch = false,
|
|
||||||
ConfigContract = nameof(FileListSettings),
|
ConfigContract = nameof(FileListSettings),
|
||||||
Implementation = nameof(FileList),
|
Implementation = nameof(FileList),
|
||||||
Name = "NewznabTest",
|
Name = "NewznabTest",
|
||||||
|
@@ -10,9 +10,7 @@ namespace Prowlarr.Api.V1.Indexers
|
|||||||
{
|
{
|
||||||
public class IndexerResource : ProviderResource
|
public class IndexerResource : ProviderResource
|
||||||
{
|
{
|
||||||
public bool EnableRss { get; set; }
|
public bool Enable { get; set; }
|
||||||
public bool EnableAutomaticSearch { get; set; }
|
|
||||||
public bool EnableInteractiveSearch { get; set; }
|
|
||||||
public bool SupportsRss { get; set; }
|
public bool SupportsRss { get; set; }
|
||||||
public bool SupportsSearch { get; set; }
|
public bool SupportsSearch { get; set; }
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
@@ -51,9 +49,7 @@ namespace Prowlarr.Api.V1.Indexers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource.EnableRss = definition.EnableRss;
|
resource.Enable = definition.Enable;
|
||||||
resource.EnableAutomaticSearch = definition.EnableAutomaticSearch;
|
|
||||||
resource.EnableInteractiveSearch = definition.EnableInteractiveSearch;
|
|
||||||
resource.SupportsRss = definition.SupportsRss;
|
resource.SupportsRss = definition.SupportsRss;
|
||||||
resource.SupportsSearch = definition.SupportsSearch;
|
resource.SupportsSearch = definition.SupportsSearch;
|
||||||
resource.Capabilities = definition.Capabilities.ToResource();
|
resource.Capabilities = definition.Capabilities.ToResource();
|
||||||
@@ -88,9 +84,7 @@ namespace Prowlarr.Api.V1.Indexers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
definition.EnableRss = resource.EnableRss;
|
definition.Enable = resource.Enable;
|
||||||
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;
|
|
||||||
definition.EnableInteractiveSearch = resource.EnableInteractiveSearch;
|
|
||||||
definition.Priority = resource.Priority;
|
definition.Priority = resource.Priority;
|
||||||
definition.Privacy = resource.Privacy;
|
definition.Privacy = resource.Privacy;
|
||||||
definition.Added = resource.Added;
|
definition.Added = resource.Added;
|
||||||
|
Reference in New Issue
Block a user