New: Add support for prioritizing indexers (#5000)

pull/5013/head
nitsua 4 years ago committed by GitHub
parent 4fafdcabb7
commit 9a46d5165c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -43,13 +43,14 @@ function EditIndexerModalContent(props) {
enableInteractiveSearch,
supportsRss,
supportsSearch,
fields
fields,
priority
} = item;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{`${id ? 'Edit' : 'Add'} Indexer - ${implementationName}`}
{`${id ? translate('EditIndexer') : translate('AddIndexer')} - ${implementationName}`}
</ModalHeader>
<ModalBody>
@ -134,7 +135,22 @@ function EditIndexerModalContent(props) {
);
})
}
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('IndexerPriority')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="priority"
helpText={translate('IndexerPriorityHelpText')}
min={1}
max={50}
{...priority}
onChange={onInputChange}
/>
</FormGroup>
</Form>
}
</ModalBody>

@ -69,7 +69,9 @@ class Indexer extends Component {
enableAutomaticSearch,
enableInteractiveSearch,
supportsRss,
supportsSearch
supportsSearch,
priority,
showPriority
} = this.props;
return (
@ -103,24 +105,30 @@ class Indexer extends Component {
{
supportsSearch && enableAutomaticSearch &&
<Label kind={kinds.SUCCESS}>
Automatic Search
{translate('AutomaticSearch')}
</Label>
}
{
supportsSearch && enableInteractiveSearch &&
<Label kind={kinds.SUCCESS}>
Interactive Search
{translate('InteractiveSearch')}
</Label>
}
{
showPriority &&
<Label kind={kinds.DEFAULT}>
{translate('Priority')}: {priority}
</Label>
}
{
!enableRss && !enableAutomaticSearch && !enableInteractiveSearch &&
<Label
kind={kinds.DISABLED}
outline={true}
>
Disabled
{translate('Disabled')}
</Label>
}
</div>
@ -155,7 +163,9 @@ Indexer.propTypes = {
supportsRss: PropTypes.bool.isRequired,
supportsSearch: PropTypes.bool.isRequired,
onCloneIndexerPress: PropTypes.func.isRequired,
onConfirmDeleteIndexer: PropTypes.func.isRequired
onConfirmDeleteIndexer: PropTypes.func.isRequired,
priority: PropTypes.number.isRequired,
showPriority: PropTypes.bool.isRequired
};
export default Indexer;

@ -64,6 +64,8 @@ class Indexers extends Component {
isEditIndexerModalOpen
} = this.state;
const showPriority = items.some((index) => index.priority !== 25);
return (
<FieldSet legend={translate('Indexers')}>
<PageSectionContent
@ -77,6 +79,7 @@ class Indexers extends Component {
<Indexer
key={item.id}
{...item}
showPriority={showPriority}
onCloneIndexerPress={this.onCloneIndexerPress}
onConfirmDeleteIndexer={onConfirmDeleteIndexer}
/>

@ -18,6 +18,7 @@ namespace NzbDrone.Api.Indexers
resource.SupportsRss = definition.SupportsRss;
resource.SupportsSearch = definition.SupportsSearch;
resource.Protocol = definition.Protocol;
resource.Priority = definition.Priority;
}
protected override void MapToModel(IndexerDefinition definition, IndexerResource resource)
@ -27,6 +28,7 @@ namespace NzbDrone.Api.Indexers
definition.EnableRss = resource.EnableRss;
definition.EnableAutomaticSearch = resource.EnableSearch;
definition.EnableInteractiveSearch = resource.EnableSearch;
definition.Priority = resource.Priority;
}
protected override void Validate(IndexerDefinition definition, bool includeWarnings)

@ -9,5 +9,6 @@ namespace NzbDrone.Api.Indexers
public bool SupportsRss { get; set; }
public bool SupportsSearch { get; set; }
public DownloadProtocol Protocol { get; set; }
public int Priority { get; set; }
}
}

@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.Returns(new QualityDefinition { PreferredSize = size });
}
private RemoteMovie GivenRemoteMovie(QualityModel quality, int age = 0, long size = 0, DownloadProtocol downloadProtocol = DownloadProtocol.Usenet, int runtime = 150)
private RemoteMovie GivenRemoteMovie(QualityModel quality, int age = 0, long size = 0, DownloadProtocol downloadProtocol = DownloadProtocol.Usenet, int runtime = 150, int indexerPriority = 25)
{
var remoteMovie = new RemoteMovie();
remoteMovie.ParsedMovieInfo = new ParsedMovieInfo();
@ -73,6 +73,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
remoteMovie.Release.Size = size;
remoteMovie.Release.DownloadProtocol = downloadProtocol;
remoteMovie.Release.Title = "A Movie 1998";
remoteMovie.Release.IndexerPriority = indexerPriority;
remoteMovie.CustomFormats = new List<CustomFormat>();
remoteMovie.CustomFormatScore = 0;
@ -449,13 +450,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_prefer_2_custom_formats()
{
var quality1 = new QualityModel(Quality.Bluray720p);
var remoteMovie1 = GivenRemoteMovie(quality1);
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.Bluray720p));
remoteMovie1.CustomFormats.Add(_customFormat1);
remoteMovie1.CustomFormatScore = remoteMovie1.Movie.Profile.CalculateCustomFormatScore(remoteMovie1.CustomFormats);
var quality2 = new QualityModel(Quality.Bluray720p);
var remoteMovie2 = GivenRemoteMovie(quality2);
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.Bluray720p));
remoteMovie2.CustomFormats.AddRange(new List<CustomFormat> { _customFormat1, _customFormat2 });
remoteMovie2.CustomFormatScore = remoteMovie2.Movie.Profile.CalculateCustomFormatScore(remoteMovie2.CustomFormats);
@ -555,5 +554,39 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
qualifiedReports.First().RemoteMovie.ParsedMovieInfo.Quality.Revision.Real.Should().Be(0);
qualifiedReports.First().RemoteMovie.CustomFormatScore.Should().Be(10);
}
[Test]
public void sort_download_decisions_based_on_indexer_priority()
{
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.WEBDL1080p), indexerPriority: 25);
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.WEBDL1080p), indexerPriority: 50);
var remoteMovie3 = GivenRemoteMovie(new QualityModel(Quality.WEBDL1080p), indexerPriority: 1);
var decisions = new List<DownloadDecision>();
decisions.AddRange(new[] { new DownloadDecision(remoteMovie1), new DownloadDecision(remoteMovie2), new DownloadDecision(remoteMovie3) });
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Should().Be(remoteMovie3);
qualifiedReports.Skip(1).First().RemoteMovie.Should().Be(remoteMovie1);
qualifiedReports.Last().RemoteMovie.Should().Be(remoteMovie2);
}
[Test]
public void ensure_download_decisions_indexer_priority_is_not_perfered_over_quality()
{
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), indexerPriority: 25);
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.WEBDL1080p), indexerPriority: 50);
var remoteMovie3 = GivenRemoteMovie(new QualityModel(Quality.SDTV), indexerPriority: 1);
var remoteMovie4 = GivenRemoteMovie(new QualityModel(Quality.WEBDL1080p), indexerPriority: 25);
var decisions = new List<DownloadDecision>();
decisions.AddRange(new[] { new DownloadDecision(remoteMovie1), new DownloadDecision(remoteMovie2), new DownloadDecision(remoteMovie3), new DownloadDecision(remoteMovie4) });
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Should().Be(remoteMovie4);
qualifiedReports.Skip(1).First().RemoteMovie.Should().Be(remoteMovie2);
qualifiedReports.Skip(2).First().RemoteMovie.Should().Be(remoteMovie1);
qualifiedReports.Last().RemoteMovie.Should().Be(remoteMovie3);
}
}
}

@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(184)]
public class add_priority_to_indexers : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Indexers").AddColumn("Priority").AsInt32().NotNullable().WithDefaultValue(25);
}
}
}

@ -32,6 +32,7 @@ namespace NzbDrone.Core.DecisionEngine
CompareQuality,
CompareCustomFormatScore,
CompareProtocol,
CompareIndexerPriority,
CompareIndexerFlags,
ComparePeersIfTorrent,
CompareAgeIfUsenet,
@ -50,11 +51,22 @@ namespace NzbDrone.Core.DecisionEngine
return leftValue.CompareTo(rightValue);
}
private int CompareByReverse<TSubject, TValue>(TSubject left, TSubject right, Func<TSubject, TValue> funcValue)
where TValue : IComparable<TValue>
{
return CompareBy(left, right, funcValue) * -1;
}
private int CompareAll(params int[] comparers)
{
return comparers.Select(comparer => comparer).FirstOrDefault(result => result != 0);
}
private int CompareIndexerPriority(DownloadDecision x, DownloadDecision y)
{
return CompareByReverse(x.RemoteMovie.Release, y.RemoteMovie.Release, release => release.IndexerPriority);
}
private int CompareQuality(DownloadDecision x, DownloadDecision y)
{
if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer)

@ -48,7 +48,7 @@ namespace NzbDrone.Core.Download
foreach (var report in prioritizedDecisions)
{
var remoteEpisode = report.RemoteMovie;
var remoteMovie = report.RemoteMovie;
var downloadProtocol = report.RemoteMovie.Release.DownloadProtocol;
// Skip if already grabbed
@ -72,19 +72,20 @@ namespace NzbDrone.Core.Download
try
{
_downloadService.DownloadReport(remoteEpisode);
_logger.Trace("Grabbing from Indexer {0} at priority {1}.", remoteMovie.Release.Indexer, remoteMovie.Release.IndexerPriority);
_downloadService.DownloadReport(remoteMovie);
grabbed.Add(report);
}
catch (ReleaseUnavailableException)
{
_logger.Warn("Failed to download release from indexer, no longer available. " + remoteEpisode);
_logger.Warn("Failed to download release from indexer, no longer available. " + remoteMovie);
rejected.Add(report);
}
catch (Exception ex)
{
if (ex is DownloadClientUnavailableException || ex is DownloadClientAuthenticationException)
{
_logger.Debug(ex, "Failed to send release to download client, storing until later. " + remoteEpisode);
_logger.Debug(ex, "Failed to send release to download client, storing until later. " + remoteMovie);
PreparePending(pendingAddQueue, grabbed, pending, report, PendingReleaseReason.DownloadClientUnavailable);
if (downloadProtocol == DownloadProtocol.Usenet)
@ -98,7 +99,7 @@ namespace NzbDrone.Core.Download
}
else
{
_logger.Warn(ex, "Couldn't add report to download queue. " + remoteEpisode);
_logger.Warn(ex, "Couldn't add report to download queue. " + remoteMovie);
}
}
}
@ -129,7 +130,7 @@ namespace NzbDrone.Core.Download
private void PreparePending(List<Tuple<DownloadDecision, PendingReleaseReason>> queue, List<DownloadDecision> grabbed, List<DownloadDecision> pending, DownloadDecision report, PendingReleaseReason reason)
{
// If a release was already grabbed with matching episodes we should store it as a fallback
// If a release was already grabbed with a matching movie we should store it as a fallback
// and filter it out the next time it is processed.
// If a higher quality release failed to add to the download client, but a lower quality release
// was sent to another client we still list it normally so it apparent that it'll grab next time.

@ -22,6 +22,7 @@ namespace NzbDrone.Core.Indexers
public abstract string Name { get; }
public abstract DownloadProtocol Protocol { get; }
public int Priority { get; set; }
public abstract bool SupportsRss { get; }
public abstract bool SupportsSearch { get; }
@ -77,6 +78,7 @@ namespace NzbDrone.Core.Indexers
c.IndexerId = Definition.Id;
c.Indexer = Definition.Name;
c.DownloadProtocol = Protocol;
c.IndexerPriority = ((IndexerDefinition)Definition).Priority;
});
return result;

@ -10,6 +10,7 @@ namespace NzbDrone.Core.Indexers
public DownloadProtocol Protocol { get; set; }
public bool SupportsRss { get; set; }
public bool SupportsSearch { get; set; }
public int Priority { get; set; } = 25;
public override bool Enable => EnableRss || EnableAutomaticSearch || EnableInteractiveSearch;

@ -6,6 +6,7 @@
"AddExclusion": "Add Exclusion",
"AddImportExclusionHelpText": "Prevent movie from being added to Radarr by lists",
"AddingTag": "Adding tag",
"AddIndexer": "Add Indexer",
"AddList": "Add List",
"AddListExclusion": "Add List Exclusion",
"AddMovies": "Add Movies",
@ -47,6 +48,7 @@
"Authentication": "Authentication",
"AuthenticationMethodHelpText": "Require Username and Password to access Radarr",
"Automatic": "Automatic",
"AutomaticSearch": "Automatic Search",
"AutoRedownloadFailedHelpText": "Automatically search for and attempt to download a different release",
"AutoUnmonitorPreviouslyDownloadedMoviesHelpText": "Movies deleted from disk are automatically unmonitored in Radarr",
"AvailabilityDelay": "Availability Delay",
@ -165,6 +167,7 @@
"DetailedProgressBarHelpText": "Show text on progess bar",
"Details": "Details",
"DigitalRelease": "Digital Release",
"Disabled": "Disabled",
"Discover": "Discover",
"DiskSpace": "Disk Space",
"Docker": "Docker",
@ -194,6 +197,7 @@
"DownloadWarningCheckDownloadClientForMoreDetails": "Download warning: check download client for more details",
"Edit": "Edit",
"Edition": "Edition",
"EditIndexer": "Edit Indexer",
"EditMovie": "Edit Movie",
"EditPerson": "Edit Person",
"EditRemotePathMapping": "Edit Remote Path Mapping",
@ -317,6 +321,8 @@
"IncludeUnmonitored": "Include Unmonitored",
"Indexer": "Indexer",
"IndexerFlags": "Indexer Flags",
"IndexerPriority": "Indexer Priority",
"IndexerPriorityHelpText": "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25.",
"IndexerRssHealthCheckNoAvailableIndexers": "All rss-capable indexers are temporarily unavailable due to recent indexer errors",
"IndexerRssHealthCheckNoIndexers": "No indexers available with RSS sync enabled, Radarr will not grab new releases automatically",
"Indexers": "Indexers",
@ -329,6 +335,7 @@
"IndexerStatusCheckSingleClientMessage": "Indexers unavailable due to failures: {0}",
"Info": "Info",
"InteractiveImport": "Interactive Import",
"InteractiveSearch": "Interactive Search",
"Interval": "Interval",
"KeyboardShortcuts": "Keyboard Shortcuts",
"Language": "Language",
@ -494,6 +501,7 @@
"PreferIndexerFlagsHelpText": "Prioritize releases with special flags",
"PreferredSize": "Preferred Size",
"PreviewRename": "Preview Rename",
"Priority": "Priority",
"PriorityHelpText": "Prioritize multiple Download Clients. Round-Robin is used for clients with the same priority.",
"Profiles": "Profiles",
"ProfilesSettingsSummary": "Quality, Language and Delay profiles",

@ -14,6 +14,7 @@ namespace NzbDrone.Core.Parser.Model
public string CommentUrl { get; set; }
public int IndexerId { get; set; }
public string Indexer { get; set; }
public int IndexerPriority { get; set; }
public DownloadProtocol DownloadProtocol { get; set; }
public int TvdbId { get; set; }
public int TvRageId { get; set; }

@ -10,6 +10,7 @@ namespace Radarr.Api.V3.Indexers
public bool SupportsRss { get; set; }
public bool SupportsSearch { get; set; }
public DownloadProtocol Protocol { get; set; }
public int Priority { get; set; }
}
public class IndexerResourceMapper : ProviderResourceMapper<IndexerResource, IndexerDefinition>
@ -29,6 +30,7 @@ namespace Radarr.Api.V3.Indexers
resource.SupportsRss = definition.SupportsRss;
resource.SupportsSearch = definition.SupportsSearch;
resource.Protocol = definition.Protocol;
resource.Priority = definition.Priority;
return resource;
}
@ -45,6 +47,7 @@ namespace Radarr.Api.V3.Indexers
definition.EnableRss = resource.EnableRss;
definition.EnableAutomaticSearch = resource.EnableAutomaticSearch;
definition.EnableInteractiveSearch = resource.EnableInteractiveSearch;
definition.Priority = resource.Priority;
return definition;
}

Loading…
Cancel
Save