mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
New: Add download client per indexer setting
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
|
||||||
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.downloadClients,
|
||||||
|
(state, { includeAny }) => includeAny,
|
||||||
|
(state, { protocol }) => protocol,
|
||||||
|
(downloadClients, includeAny, protocolFilter) => {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items
|
||||||
|
} = downloadClients;
|
||||||
|
|
||||||
|
const values = items
|
||||||
|
.filter((downloadClient) => downloadClient.protocol === protocolFilter)
|
||||||
|
.sort(sortByName)
|
||||||
|
.map((downloadClient) => ({
|
||||||
|
key: downloadClient.id,
|
||||||
|
value: downloadClient.name
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (includeAny) {
|
||||||
|
values.unshift({
|
||||||
|
key: 0,
|
||||||
|
value: '(Any)'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
values
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchFetchDownloadClients: fetchDownloadClients
|
||||||
|
};
|
||||||
|
|
||||||
|
class DownloadClientSelectInputConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (!this.props.isPopulated) {
|
||||||
|
this.props.dispatchFetchDownloadClients();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onChange = ({ name, value }) => {
|
||||||
|
this.props.onChange({ name, value: parseInt(value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<EnhancedSelectInput
|
||||||
|
{...this.props}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadClientSelectInputConnector.propTypes = {
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||||
|
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
includeAny: PropTypes.bool.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchDownloadClients: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
DownloadClientSelectInputConnector.defaultProps = {
|
||||||
|
includeAny: false,
|
||||||
|
protocol: 'torrent'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);
|
@@ -10,6 +10,7 @@ import CaptchaInputConnector from './CaptchaInputConnector';
|
|||||||
import CardigannCaptchaInputConnector from './CardigannCaptchaInputConnector';
|
import CardigannCaptchaInputConnector from './CardigannCaptchaInputConnector';
|
||||||
import CheckInput from './CheckInput';
|
import CheckInput from './CheckInput';
|
||||||
import DeviceInputConnector from './DeviceInputConnector';
|
import DeviceInputConnector from './DeviceInputConnector';
|
||||||
|
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||||
import FormInputHelpText from './FormInputHelpText';
|
import FormInputHelpText from './FormInputHelpText';
|
||||||
@@ -72,6 +73,9 @@ function getComponent(type) {
|
|||||||
case inputTypes.CATEGORY_SELECT:
|
case inputTypes.CATEGORY_SELECT:
|
||||||
return NewznabCategorySelectInputConnector;
|
return NewznabCategorySelectInputConnector;
|
||||||
|
|
||||||
|
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
||||||
|
return DownloadClientSelectInputConnector;
|
||||||
|
|
||||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||||
return IndexerFlagsSelectInputConnector;
|
return IndexerFlagsSelectInputConnector;
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ export const KEY_VALUE_LIST = 'keyValueList';
|
|||||||
export const INFO = 'info';
|
export const INFO = 'info';
|
||||||
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
||||||
export const CATEGORY_SELECT = 'newznabCategorySelect';
|
export const CATEGORY_SELECT = 'newznabCategorySelect';
|
||||||
|
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
|
||||||
export const NUMBER = 'number';
|
export const NUMBER = 'number';
|
||||||
export const OAUTH = 'oauth';
|
export const OAUTH = 'oauth';
|
||||||
export const PASSWORD = 'password';
|
export const PASSWORD = 'password';
|
||||||
|
@@ -48,7 +48,9 @@ function EditIndexerModalContent(props) {
|
|||||||
appProfileId,
|
appProfileId,
|
||||||
tags,
|
tags,
|
||||||
fields,
|
fields,
|
||||||
priority
|
priority,
|
||||||
|
protocol,
|
||||||
|
downloadClientId
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
const indexerDisplayName = implementationName === definitionName ? implementationName : `${implementationName} (${definitionName})`;
|
const indexerDisplayName = implementationName === definitionName ? implementationName : `${implementationName} (${definitionName})`;
|
||||||
@@ -156,6 +158,23 @@ function EditIndexerModalContent(props) {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
>
|
||||||
|
<FormLabel>{translate('DownloadClient')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.DOWNLOAD_CLIENT_SELECT}
|
||||||
|
name="downloadClientId"
|
||||||
|
helpText={translate('IndexerDownloadClientHelpText')}
|
||||||
|
{...downloadClientId}
|
||||||
|
includeAny={true}
|
||||||
|
protocol={protocol.value}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('Tags')}</FormLabel>
|
<FormLabel>{translate('Tags')}</FormLabel>
|
||||||
|
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(035)]
|
||||||
|
public class download_client_per_indexer : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("Indexers").AddColumn("DownloadClientId").AsInt32().WithDefaultValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,13 +2,14 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Core.Download.Clients;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public interface IProvideDownloadClient
|
public interface IProvideDownloadClient
|
||||||
{
|
{
|
||||||
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol);
|
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0);
|
||||||
IEnumerable<IDownloadClient> GetDownloadClients();
|
IEnumerable<IDownloadClient> GetDownloadClients();
|
||||||
IDownloadClient Get(int id);
|
IDownloadClient Get(int id);
|
||||||
}
|
}
|
||||||
@@ -18,17 +19,23 @@ namespace NzbDrone.Core.Download
|
|||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private readonly IDownloadClientFactory _downloadClientFactory;
|
private readonly IDownloadClientFactory _downloadClientFactory;
|
||||||
private readonly IDownloadClientStatusService _downloadClientStatusService;
|
private readonly IDownloadClientStatusService _downloadClientStatusService;
|
||||||
|
private readonly IIndexerFactory _indexerFactory;
|
||||||
private readonly ICached<int> _lastUsedDownloadClient;
|
private readonly ICached<int> _lastUsedDownloadClient;
|
||||||
|
|
||||||
public DownloadClientProvider(IDownloadClientStatusService downloadClientStatusService, IDownloadClientFactory downloadClientFactory, ICacheManager cacheManager, Logger logger)
|
public DownloadClientProvider(IDownloadClientStatusService downloadClientStatusService,
|
||||||
|
IDownloadClientFactory downloadClientFactory,
|
||||||
|
IIndexerFactory indexerFactory,
|
||||||
|
ICacheManager cacheManager,
|
||||||
|
Logger logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_downloadClientFactory = downloadClientFactory;
|
_downloadClientFactory = downloadClientFactory;
|
||||||
_downloadClientStatusService = downloadClientStatusService;
|
_downloadClientStatusService = downloadClientStatusService;
|
||||||
|
_indexerFactory = indexerFactory;
|
||||||
_lastUsedDownloadClient = cacheManager.GetCache<int>(GetType(), "lastDownloadClientId");
|
_lastUsedDownloadClient = cacheManager.GetCache<int>(GetType(), "lastDownloadClientId");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol)
|
public IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol, int indexerId = 0)
|
||||||
{
|
{
|
||||||
var availableProviders = _downloadClientFactory.GetAvailableProviders().Where(v => v.Protocol == downloadProtocol).ToList();
|
var availableProviders = _downloadClientFactory.GetAvailableProviders().Where(v => v.Protocol == downloadProtocol).ToList();
|
||||||
|
|
||||||
@@ -37,6 +44,23 @@ namespace NzbDrone.Core.Download
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (indexerId > 0)
|
||||||
|
{
|
||||||
|
var indexer = _indexerFactory.Find(indexerId);
|
||||||
|
|
||||||
|
if (indexer is { DownloadClientId: > 0 })
|
||||||
|
{
|
||||||
|
var client = availableProviders.SingleOrDefault(d => d.Definition.Id == indexer.DownloadClientId);
|
||||||
|
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
throw new DownloadClientUnavailableException("Indexer specified download client is not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var blockedProviders = new HashSet<int>(_downloadClientStatusService.GetBlockedProviders().Select(v => v.ProviderId));
|
var blockedProviders = new HashSet<int>(_downloadClientStatusService.GetBlockedProviders().Select(v => v.ProviderId));
|
||||||
|
|
||||||
if (blockedProviders.Any())
|
if (blockedProviders.Any())
|
||||||
@@ -54,7 +78,7 @@ namespace NzbDrone.Core.Download
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use the first priority clients first
|
// Use the first priority clients first
|
||||||
availableProviders = availableProviders.GroupBy(v => (v.Definition as DownloadClientDefinition).Priority)
|
availableProviders = availableProviders.GroupBy(v => ((DownloadClientDefinition)v.Definition).Priority)
|
||||||
.OrderBy(v => v.Key)
|
.OrderBy(v => v.Key)
|
||||||
.First().OrderBy(v => v.Definition.Id).ToList();
|
.First().OrderBy(v => v.Definition.Id).ToList();
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Download
|
|||||||
{
|
{
|
||||||
public interface IDownloadService
|
public interface IDownloadService
|
||||||
{
|
{
|
||||||
Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect);
|
Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect, int? downloadClientId);
|
||||||
Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title);
|
Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title);
|
||||||
void RecordRedirect(string link, int indexerId, string source, string host, string title);
|
void RecordRedirect(string link, int indexerId, string source, string host, string title);
|
||||||
}
|
}
|
||||||
@@ -48,10 +48,18 @@ namespace NzbDrone.Core.Download
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect)
|
public async Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect, int? downloadClientId)
|
||||||
|
{
|
||||||
|
var downloadClient = downloadClientId.HasValue
|
||||||
|
? _downloadClientProvider.Get(downloadClientId.Value)
|
||||||
|
: _downloadClientProvider.GetDownloadClient(release.DownloadProtocol, release.IndexerId);
|
||||||
|
|
||||||
|
await SendReportToClient(release, source, host, redirect, downloadClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendReportToClient(ReleaseInfo release, string source, string host, bool redirect, IDownloadClient downloadClient)
|
||||||
{
|
{
|
||||||
var downloadTitle = release.Title;
|
var downloadTitle = release.Title;
|
||||||
var downloadClient = _downloadClientProvider.GetDownloadClient(release.DownloadProtocol);
|
|
||||||
|
|
||||||
if (downloadClient == null)
|
if (downloadClient == null)
|
||||||
{
|
{
|
||||||
|
@@ -24,6 +24,7 @@ namespace NzbDrone.Core.Indexers
|
|||||||
public IndexerCapabilities Capabilities { get; set; }
|
public IndexerCapabilities Capabilities { get; set; }
|
||||||
public int Priority { get; set; } = 25;
|
public int Priority { get; set; } = 25;
|
||||||
public bool Redirect { get; set; }
|
public bool Redirect { get; set; }
|
||||||
|
public int DownloadClientId { get; set; }
|
||||||
public DateTime Added { get; set; }
|
public DateTime Added { get; set; }
|
||||||
public int AppProfileId { get; set; }
|
public int AppProfileId { get; set; }
|
||||||
public LazyLoaded<AppSyncProfile> AppProfile { get; set; }
|
public LazyLoaded<AppSyncProfile> AppProfile { get; set; }
|
||||||
|
@@ -204,6 +204,7 @@
|
|||||||
"IndexerCategories": "Indexer Categories",
|
"IndexerCategories": "Indexer Categories",
|
||||||
"IndexerDetails": "Indexer Details",
|
"IndexerDetails": "Indexer Details",
|
||||||
"IndexerDisabled": "Indexer Disabled",
|
"IndexerDisabled": "Indexer Disabled",
|
||||||
|
"IndexerDownloadClientHelpText": "Specify which download client is used for grabs made within Prowlarr from this indexer",
|
||||||
"IndexerFailureRate": "Indexer Failure Rate",
|
"IndexerFailureRate": "Indexer Failure Rate",
|
||||||
"IndexerFlags": "Indexer Flags",
|
"IndexerFlags": "Indexer Flags",
|
||||||
"IndexerHealthCheckNoIndexers": "No indexers enabled, Prowlarr will not return search results",
|
"IndexerHealthCheckNoIndexers": "No indexers enabled, Prowlarr will not return search results",
|
||||||
|
@@ -30,6 +30,7 @@ namespace Prowlarr.Api.V1.Indexers
|
|||||||
public IndexerPrivacy Privacy { get; set; }
|
public IndexerPrivacy Privacy { get; set; }
|
||||||
public IndexerCapabilityResource Capabilities { get; set; }
|
public IndexerCapabilityResource Capabilities { get; set; }
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
public int DownloadClientId { get; set; }
|
||||||
public DateTime Added { get; set; }
|
public DateTime Added { get; set; }
|
||||||
public IndexerStatusResource Status { get; set; }
|
public IndexerStatusResource Status { get; set; }
|
||||||
public string SortName { get; set; }
|
public string SortName { get; set; }
|
||||||
@@ -96,6 +97,7 @@ namespace Prowlarr.Api.V1.Indexers
|
|||||||
resource.Protocol = definition.Protocol;
|
resource.Protocol = definition.Protocol;
|
||||||
resource.Privacy = definition.Privacy;
|
resource.Privacy = definition.Privacy;
|
||||||
resource.Priority = definition.Priority;
|
resource.Priority = definition.Priority;
|
||||||
|
resource.DownloadClientId = definition.DownloadClientId;
|
||||||
resource.Added = definition.Added;
|
resource.Added = definition.Added;
|
||||||
resource.SortName = definition.Name.NormalizeTitle();
|
resource.SortName = definition.Name.NormalizeTitle();
|
||||||
|
|
||||||
@@ -142,6 +144,7 @@ namespace Prowlarr.Api.V1.Indexers
|
|||||||
definition.IndexerUrls = resource.IndexerUrls;
|
definition.IndexerUrls = resource.IndexerUrls;
|
||||||
definition.Priority = resource.Priority;
|
definition.Priority = resource.Priority;
|
||||||
definition.Privacy = resource.Privacy;
|
definition.Privacy = resource.Privacy;
|
||||||
|
definition.DownloadClientId = resource.DownloadClientId;
|
||||||
definition.Added = resource.Added;
|
definition.Added = resource.Added;
|
||||||
|
|
||||||
return definition;
|
return definition;
|
||||||
|
@@ -73,7 +73,7 @@ namespace Prowlarr.Api.V1.Search
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_downloadService.SendReportToClient(releaseInfo, source, host, indexerDef.Redirect);
|
_downloadService.SendReportToClient(releaseInfo, source, host, indexerDef.Redirect, null).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch (ReleaseDownloadException ex)
|
catch (ReleaseDownloadException ex)
|
||||||
{
|
{
|
||||||
@@ -106,7 +106,7 @@ namespace Prowlarr.Api.V1.Search
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_downloadService.SendReportToClient(releaseInfo, source, host, indexerDef.Redirect);
|
_downloadService.SendReportToClient(releaseInfo, source, host, indexerDef.Redirect, null).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch (ReleaseDownloadException ex)
|
catch (ReleaseDownloadException ex)
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user