mirror of
https://github.com/Sonarr/Sonarr
synced 2026-05-05 03:20:49 +02:00
Add Seeders Preference setting for torrent prioritization
Adds a configurable dropdown in Indexer Options (advanced) that controls where seeder count appears in the release comparison priority chain: - Default: seeders evaluated in original position (low priority tiebreaker) - High: seeders evaluated after quality - Highest: seeders evaluated before quality (most important factor) Includes translations for French, Spanish, German, Chinese (Simplified), and Portuguese (Brazilian).
This commit is contained in:
parent
bee7e4325f
commit
15b37d35c7
14 changed files with 236 additions and 12 deletions
|
|
@ -6,6 +6,7 @@ import Form from 'Components/Form/Form';
|
|||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import { EnhancedSelectInputValue } from 'Components/Form/Select/EnhancedSelectInput';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import { useShowAdvancedSettings } from 'Settings/advancedSettingsStore';
|
||||
|
|
@ -25,6 +26,27 @@ import translate from 'Utilities/String/translate';
|
|||
|
||||
const SECTION = 'indexerOptions';
|
||||
|
||||
const seedersPreferenceOptions: EnhancedSelectInputValue<string>[] = [
|
||||
{
|
||||
key: 'default',
|
||||
get value() {
|
||||
return translate('SeedersPreferenceDefault');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'high',
|
||||
get value() {
|
||||
return translate('SeedersPreferenceHigh');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'highest',
|
||||
get value() {
|
||||
return translate('SeedersPreferenceHighest');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
interface IndexerOptionsProps {
|
||||
setChildSave: SetChildSave;
|
||||
onChildStateChange: OnChildStateChange;
|
||||
|
|
@ -143,6 +165,19 @@ function IndexerOptions({
|
|||
{...settings.rssSyncInterval}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup advancedSettings={showAdvancedSettings} isAdvanced={true}>
|
||||
<FormLabel>{translate('SeedersPreference')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="seedersPreference"
|
||||
helpText={translate('SeedersPreferenceHelpText')}
|
||||
values={seedersPreferenceOptions}
|
||||
onChange={handleInputChange}
|
||||
{...settings.seedersPreference}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
) : null}
|
||||
</FieldSet>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ export default interface IndexerOptions {
|
|||
retention: number;
|
||||
maximumSize: number;
|
||||
rssSyncInterval: number;
|
||||
seedersPreference: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -638,5 +638,128 @@ public void ensure_download_decisions_indexer_priority_is_not_perfered_over_qual
|
|||
qualifiedReports.Skip(2).First().RemoteEpisode.Should().Be(remoteEpisode1);
|
||||
qualifiedReports.Last().RemoteEpisode.Should().Be(remoteEpisode3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_prefer_quality_over_seeders_when_seeders_preference_is_default()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.SeedersPreference)
|
||||
.Returns(SeedersPreferenceType.Default);
|
||||
|
||||
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.Bluray1080p), Language.English);
|
||||
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.English);
|
||||
|
||||
var torrentInfo1 = new TorrentInfo();
|
||||
torrentInfo1.PublishDate = DateTime.Now;
|
||||
torrentInfo1.DownloadProtocol = DownloadProtocol.Torrent;
|
||||
torrentInfo1.Seeders = 10;
|
||||
torrentInfo1.Size = 200.Megabytes();
|
||||
|
||||
var torrentInfo2 = torrentInfo1.JsonClone();
|
||||
torrentInfo2.Seeders = 1000;
|
||||
|
||||
remoteEpisode1.Release = torrentInfo1;
|
||||
remoteEpisode2.Release = torrentInfo2;
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode1));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||
|
||||
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.Bluray1080p);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_prefer_seeders_over_quality_when_seeders_preference_is_highest()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.SeedersPreference)
|
||||
.Returns(SeedersPreferenceType.Highest);
|
||||
|
||||
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.Bluray1080p), Language.English);
|
||||
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.English);
|
||||
|
||||
var torrentInfo1 = new TorrentInfo();
|
||||
torrentInfo1.PublishDate = DateTime.Now;
|
||||
torrentInfo1.DownloadProtocol = DownloadProtocol.Torrent;
|
||||
torrentInfo1.Seeders = 10;
|
||||
torrentInfo1.Size = 200.Megabytes();
|
||||
|
||||
var torrentInfo2 = torrentInfo1.JsonClone();
|
||||
torrentInfo2.Seeders = 1000;
|
||||
|
||||
remoteEpisode1.Release = torrentInfo1;
|
||||
remoteEpisode2.Release = torrentInfo2;
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode1));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||
|
||||
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||
((TorrentInfo)qualifiedReports.First().RemoteEpisode.Release).Seeders.Should().Be(1000);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_prefer_quality_over_seeders_when_seeders_preference_is_high()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.SeedersPreference)
|
||||
.Returns(SeedersPreferenceType.High);
|
||||
|
||||
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.Bluray1080p), Language.English);
|
||||
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.English);
|
||||
|
||||
var torrentInfo1 = new TorrentInfo();
|
||||
torrentInfo1.PublishDate = DateTime.Now;
|
||||
torrentInfo1.DownloadProtocol = DownloadProtocol.Torrent;
|
||||
torrentInfo1.Seeders = 10;
|
||||
torrentInfo1.Size = 200.Megabytes();
|
||||
|
||||
var torrentInfo2 = torrentInfo1.JsonClone();
|
||||
torrentInfo2.Seeders = 1000;
|
||||
|
||||
remoteEpisode1.Release = torrentInfo1;
|
||||
remoteEpisode2.Release = torrentInfo2;
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode1));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||
|
||||
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||
qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.Bluray1080p);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_prefer_seeders_over_custom_format_score_when_seeders_preference_is_high()
|
||||
{
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.Setup(s => s.SeedersPreference)
|
||||
.Returns(SeedersPreferenceType.High);
|
||||
|
||||
var remoteEpisode1 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
|
||||
var remoteEpisode2 = GivenRemoteEpisode(new List<Episode> { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.English);
|
||||
|
||||
remoteEpisode1.CustomFormatScore = 100;
|
||||
remoteEpisode2.CustomFormatScore = 0;
|
||||
|
||||
var torrentInfo1 = new TorrentInfo();
|
||||
torrentInfo1.PublishDate = DateTime.Now;
|
||||
torrentInfo1.DownloadProtocol = DownloadProtocol.Torrent;
|
||||
torrentInfo1.Seeders = 10;
|
||||
torrentInfo1.Size = 200.Megabytes();
|
||||
|
||||
var torrentInfo2 = torrentInfo1.JsonClone();
|
||||
torrentInfo2.Seeders = 1000;
|
||||
|
||||
remoteEpisode1.Release = torrentInfo1;
|
||||
remoteEpisode2.Release = torrentInfo2;
|
||||
|
||||
var decisions = new List<DownloadDecision>();
|
||||
decisions.Add(new DownloadDecision(remoteEpisode1));
|
||||
decisions.Add(new DownloadDecision(remoteEpisode2));
|
||||
|
||||
var qualifiedReports = Subject.PrioritizeDecisions(decisions);
|
||||
((TorrentInfo)qualifiedReports.First().RemoteEpisode.Release).Seeders.Should().Be(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,13 @@ public int MinimumAge
|
|||
set { SetValue("MinimumAge", value); }
|
||||
}
|
||||
|
||||
public SeedersPreferenceType SeedersPreference
|
||||
{
|
||||
get { return GetValueEnum("SeedersPreference", SeedersPreferenceType.Default); }
|
||||
|
||||
set { SetValue("SeedersPreference", value); }
|
||||
}
|
||||
|
||||
public ProperDownloadTypes DownloadPropersAndRepacks
|
||||
{
|
||||
get { return GetValueEnum("DownloadPropersAndRepacks", ProperDownloadTypes.PreferAndUpgrade); }
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ public interface IConfigService
|
|||
int RssSyncInterval { get; set; }
|
||||
int MaximumSize { get; set; }
|
||||
int MinimumAge { get; set; }
|
||||
SeedersPreferenceType SeedersPreference { get; set; }
|
||||
|
||||
ListSyncLevelType ListSyncLevel { get; set; }
|
||||
int ListSyncTag { get; set; }
|
||||
|
|
|
|||
9
src/NzbDrone.Core/Configuration/SeedersPreferenceType.cs
Normal file
9
src/NzbDrone.Core/Configuration/SeedersPreferenceType.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
public enum SeedersPreferenceType
|
||||
{
|
||||
Default,
|
||||
High,
|
||||
Highest
|
||||
}
|
||||
}
|
||||
|
|
@ -27,18 +27,34 @@ public DownloadDecisionComparer(IConfigService configService, IDelayProfileServi
|
|||
|
||||
public int Compare(DownloadDecision x, DownloadDecision y)
|
||||
{
|
||||
var comparers = new List<CompareDelegate>
|
||||
var comparers = new List<CompareDelegate>();
|
||||
var seedersPreference = _configService.SeedersPreference;
|
||||
|
||||
if (seedersPreference == SeedersPreferenceType.Highest)
|
||||
{
|
||||
CompareQuality,
|
||||
CompareCustomFormatScore,
|
||||
CompareProtocol,
|
||||
CompareEpisodeCount,
|
||||
CompareEpisodeNumber,
|
||||
CompareIndexerPriority,
|
||||
ComparePeersIfTorrent,
|
||||
CompareAgeIfUsenet,
|
||||
CompareSize
|
||||
};
|
||||
comparers.Add(ComparePeersIfTorrent);
|
||||
}
|
||||
|
||||
comparers.Add(CompareQuality);
|
||||
|
||||
if (seedersPreference == SeedersPreferenceType.High)
|
||||
{
|
||||
comparers.Add(ComparePeersIfTorrent);
|
||||
}
|
||||
|
||||
comparers.Add(CompareCustomFormatScore);
|
||||
comparers.Add(CompareProtocol);
|
||||
comparers.Add(CompareEpisodeCount);
|
||||
comparers.Add(CompareEpisodeNumber);
|
||||
comparers.Add(CompareIndexerPriority);
|
||||
|
||||
if (seedersPreference == SeedersPreferenceType.Default)
|
||||
{
|
||||
comparers.Add(ComparePeersIfTorrent);
|
||||
}
|
||||
|
||||
comparers.Add(CompareAgeIfUsenet);
|
||||
comparers.Add(CompareSize);
|
||||
|
||||
return comparers.Select(comparer => comparer(x, y)).FirstOrDefault(result => result != 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1832,6 +1832,11 @@
|
|||
"SecretToken": "Geheimer Token",
|
||||
"Security": "Sicherheit",
|
||||
"Seeders": "Seeders",
|
||||
"SeedersPreference": "Seeder-Präferenz",
|
||||
"SeedersPreferenceDefault": "Standard",
|
||||
"SeedersPreferenceHelpText": "Nur Torrent: Steuert, wie die Seeder-Anzahl die Release-Priorisierung beeinflusst. Standard verwendet Seeder als niedrig priorisierten Tiebreaker. Hoch priorisiert Seeder direkt nach der Qualität. Höchste macht Seeder zum wichtigsten Faktor.",
|
||||
"SeedersPreferenceHigh": "Hoch (nach Qualität)",
|
||||
"SeedersPreferenceHighest": "Höchste (vor Qualität)",
|
||||
"SelectAll": "Alles auswählen",
|
||||
"SelectDownloadClientModalTitle": "{modalTitle} – Wähle Download-Client",
|
||||
"SelectDropdown": "Auswählen...",
|
||||
|
|
|
|||
|
|
@ -1908,6 +1908,11 @@
|
|||
"SecretToken": "Secret Token",
|
||||
"Security": "Security",
|
||||
"Seeders": "Seeders",
|
||||
"SeedersPreference": "Seeders Preference",
|
||||
"SeedersPreferenceDefault": "Default",
|
||||
"SeedersPreferenceHelpText": "Torrent only: Controls how seeder count affects release prioritization. Default uses seeders as a low-priority tiebreaker. High prioritizes seeders right after quality. Highest makes seeders the most important factor.",
|
||||
"SeedersPreferenceHigh": "High (after quality)",
|
||||
"SeedersPreferenceHighest": "Highest (before quality)",
|
||||
"SelectAll": "Select All",
|
||||
"SelectDownloadClientModalTitle": "{modalTitle} - Select Download Client",
|
||||
"SelectDropdown": "Select...",
|
||||
|
|
|
|||
|
|
@ -1900,6 +1900,11 @@
|
|||
"SecretToken": "Token secreto",
|
||||
"Security": "Seguridad",
|
||||
"Seeders": "Semillas",
|
||||
"SeedersPreference": "Preferencia de semillas",
|
||||
"SeedersPreferenceDefault": "Por defecto",
|
||||
"SeedersPreferenceHelpText": "Solo torrent: Controla cómo el número de semillas afecta la priorización de releases. Por defecto usa las semillas como desempate de baja prioridad. Alto prioriza las semillas justo después de la calidad. Máximo hace que las semillas sean el factor más importante.",
|
||||
"SeedersPreferenceHigh": "Alto (después de calidad)",
|
||||
"SeedersPreferenceHighest": "Máximo (antes de calidad)",
|
||||
"SelectAll": "Seleccionar todo",
|
||||
"SelectDownloadClientModalTitle": "{modalTitle} - Seleccionar cliente de descarga",
|
||||
"SelectDropdown": "Seleccionar...",
|
||||
|
|
|
|||
|
|
@ -1891,6 +1891,11 @@
|
|||
"SecretToken": "Jeton secret",
|
||||
"Security": "Sécurité",
|
||||
"Seeders": "Seeders",
|
||||
"SeedersPreference": "Préférence de seeders",
|
||||
"SeedersPreferenceDefault": "Par défaut",
|
||||
"SeedersPreferenceHelpText": "Torrent uniquement : Contrôle l'impact du nombre de seeders sur la priorité des releases. Par défaut utilise les seeders comme critère de départage de faible priorité. Élevé priorise les seeders juste après la qualité. Maximum fait des seeders le facteur le plus important.",
|
||||
"SeedersPreferenceHigh": "Élevé (après la qualité)",
|
||||
"SeedersPreferenceHighest": "Maximum (avant la qualité)",
|
||||
"SelectAll": "Tout sélectionner",
|
||||
"SelectDownloadClientModalTitle": "{modalTitle} – Sélectionnez le client de téléchargement",
|
||||
"SelectDropdown": "Sélectionner...",
|
||||
|
|
|
|||
|
|
@ -1899,6 +1899,11 @@
|
|||
"SecretToken": "Token Secreto",
|
||||
"Security": "Segurança",
|
||||
"Seeders": "Sementes",
|
||||
"SeedersPreference": "Preferência de Sementes",
|
||||
"SeedersPreferenceDefault": "Padrão",
|
||||
"SeedersPreferenceHelpText": "Apenas torrent: Controla como a contagem de sementes afeta a priorização de releases. Padrão usa sementes como desempate de baixa prioridade. Alto prioriza sementes logo após a qualidade. Máximo faz das sementes o fator mais importante.",
|
||||
"SeedersPreferenceHigh": "Alto (após qualidade)",
|
||||
"SeedersPreferenceHighest": "Máximo (antes da qualidade)",
|
||||
"SelectAll": "Selecionar Tudo",
|
||||
"SelectDownloadClientModalTitle": "{modalTitle} - Selecionar Cliente de Download",
|
||||
"SelectDropdown": "Selecionar...",
|
||||
|
|
|
|||
|
|
@ -1665,6 +1665,11 @@
|
|||
"SecretToken": "密钥令牌",
|
||||
"Security": "安全",
|
||||
"Seeders": "种子",
|
||||
"SeedersPreference": "种子偏好",
|
||||
"SeedersPreferenceDefault": "默认",
|
||||
"SeedersPreferenceHelpText": "仅限种子:控制种子数量如何影响发布优先级。默认将种子作为低优先级的决胜因素。高优先级在质量之后优先考虑种子。最高优先级使种子成为最重要的因素。",
|
||||
"SeedersPreferenceHigh": "高(质量之后)",
|
||||
"SeedersPreferenceHighest": "最高(质量之前)",
|
||||
"SelectAll": "全选",
|
||||
"SelectDownloadClientModalTitle": "{modalTitle} - 选择下载客户端",
|
||||
"SelectDropdown": "选择…",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public class IndexerConfigResource : RestResource
|
|||
public int Retention { get; set; }
|
||||
public int MaximumSize { get; set; }
|
||||
public int RssSyncInterval { get; set; }
|
||||
public SeedersPreferenceType SeedersPreference { get; set; }
|
||||
}
|
||||
|
||||
public static class IndexerConfigResourceMapper
|
||||
|
|
@ -20,7 +21,8 @@ public static IndexerConfigResource ToResource(IConfigService model)
|
|||
MinimumAge = model.MinimumAge,
|
||||
Retention = model.Retention,
|
||||
MaximumSize = model.MaximumSize,
|
||||
RssSyncInterval = model.RssSyncInterval
|
||||
RssSyncInterval = model.RssSyncInterval,
|
||||
SeedersPreference = model.SeedersPreference
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue