From e90a796b27dc5e9d0dfe0335a7f873428d16e22a Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 1 May 2022 17:56:07 -0500 Subject: [PATCH] New: Seed Settings Sync --- .../Indexer/Edit/EditIndexerModalContent.js | 2 +- .../src/Indexer/Editor/IndexerEditorFooter.js | 2 +- .../Index/Menus/IndexerIndexSortMenu.js | 2 +- .../src/Settings/Profiles/App/AppProfiles.js | 2 +- .../App/EditAppProfileModalContent.js | 19 +++++++- .../src/Store/Actions/indexerIndexActions.js | 4 +- .../Cloud/ProwlarrCloudRequestBuilder.cs | 6 +++ .../Applications/Lidarr/Lidarr.cs | 7 +++ .../Applications/Lidarr/LidarrIndexer.cs | 15 +++++- .../Applications/Radarr/Radarr.cs | 7 +++ .../Applications/Radarr/RadarrIndexer.cs | 15 +++++- .../Applications/Readarr/Readarr.cs | 7 +++ .../Applications/Readarr/ReadarrIndexer.cs | 15 +++++- .../Applications/Sonarr/Sonarr.cs | 7 +++ .../Applications/Sonarr/SonarrIndexer.cs | 15 +++++- .../Applications/Whisparr/Whisparr.cs | 7 +++ .../Applications/Whisparr/WhisparrIndexer.cs | 15 +++++- .../Migration/018_minimum_seeders.cs | 15 ++++++ .../IndexerSearch/ReleaseAnalyticsService.cs | 48 +++++++++++++++++++ .../Definitions/Torznab/TorznabSettings.cs | 6 ++- .../Indexers/ITorrentIndexerSettings.cs | 13 +++++ .../Indexers/IndexerTorrentBaseSettings.cs | 47 ++++++++++++++++++ .../Settings/CookieTorrentBaseSettings.cs | 5 +- .../Settings/NoAuthTorrentBaseSettings.cs | 5 +- .../Settings/UserPassTorrentBaseSettings.cs | 5 +- src/NzbDrone.Core/Localization/Core/en.json | 10 ++-- src/NzbDrone.Core/Profiles/AppSyncProfile.cs | 1 + .../Profiles/AppSyncProfileService.cs | 3 +- .../Profiles/App/AppProfileResource.cs | 7 ++- 29 files changed, 288 insertions(+), 24 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/018_minimum_seeders.cs create mode 100644 src/NzbDrone.Core/IndexerSearch/ReleaseAnalyticsService.cs create mode 100644 src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs create mode 100644 src/NzbDrone.Core/Indexers/IndexerTorrentBaseSettings.cs diff --git a/frontend/src/Indexer/Edit/EditIndexerModalContent.js b/frontend/src/Indexer/Edit/EditIndexerModalContent.js index 08a097127..b83522fcf 100644 --- a/frontend/src/Indexer/Edit/EditIndexerModalContent.js +++ b/frontend/src/Indexer/Edit/EditIndexerModalContent.js @@ -112,7 +112,7 @@ function EditIndexerModalContent(props) { - {translate('AppProfile')} + {translate('SyncProfile')} diff --git a/frontend/src/Indexer/Index/Menus/IndexerIndexSortMenu.js b/frontend/src/Indexer/Index/Menus/IndexerIndexSortMenu.js index 3afd08e01..3ce324ff3 100644 --- a/frontend/src/Indexer/Index/Menus/IndexerIndexSortMenu.js +++ b/frontend/src/Indexer/Index/Menus/IndexerIndexSortMenu.js @@ -53,7 +53,7 @@ function IndexerIndexSortMenu(props) { sortDirection={sortDirection} onPress={onSortSelect} > - {translate('AppProfile')} + {translate('SyncProfile')} +
- {id ? translate('EditAppProfile') : translate('AddAppProfile')} + {id ? translate('EditSyncProfile') : translate('AddSyncProfile')} @@ -123,6 +124,20 @@ class EditAppProfileModalContent extends Component { onChange={onInputChange} /> + + + + {translate('MinimumSeeders')} + + + + } diff --git a/frontend/src/Store/Actions/indexerIndexActions.js b/frontend/src/Store/Actions/indexerIndexActions.js index fb5cd8c43..9a0c7679d 100644 --- a/frontend/src/Store/Actions/indexerIndexActions.js +++ b/frontend/src/Store/Actions/indexerIndexActions.js @@ -76,7 +76,7 @@ export const defaultState = { }, { name: 'appProfileId', - label: translate('AppProfile'), + label: translate('SyncProfile'), isSortable: true, isVisible: true }, @@ -152,7 +152,7 @@ export const defaultState = { }, { name: 'appProfileId', - label: translate('AppProfile'), + label: translate('SyncProfile'), type: filterBuilderTypes.EXACT, valueType: filterBuilderValueTypes.APP_PROFILE }, diff --git a/src/NzbDrone.Common/Cloud/ProwlarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/ProwlarrCloudRequestBuilder.cs index 86ced4c84..11356dde0 100644 --- a/src/NzbDrone.Common/Cloud/ProwlarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/ProwlarrCloudRequestBuilder.cs @@ -5,6 +5,7 @@ namespace NzbDrone.Common.Cloud public interface IProwlarrCloudRequestBuilder { IHttpRequestBuilderFactory Services { get; } + IHttpRequestBuilderFactory Releases { get; } } public class ProwlarrCloudRequestBuilder : IProwlarrCloudRequestBuilder @@ -13,8 +14,13 @@ namespace NzbDrone.Common.Cloud { Services = new HttpRequestBuilder("https://prowlarr.servarr.com/v1/") .CreateFactory(); + + Releases = new HttpRequestBuilder("https://releases.servarr.com/v1/") + .CreateFactory(); } public IHttpRequestBuilderFactory Services { get; private set; } + + public IHttpRequestBuilderFactory Releases { get; private set; } } } diff --git a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs index e275f8d09..0b03058d9 100644 --- a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs +++ b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs @@ -183,6 +183,13 @@ namespace NzbDrone.Core.Applications.Lidarr lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey; lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())); + if (indexer.Protocol == DownloadProtocol.Torrent) + { + lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders; + lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; + lidarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; + } + return lidarrIndexer; } } diff --git a/src/NzbDrone.Core/Applications/Lidarr/LidarrIndexer.cs b/src/NzbDrone.Core/Applications/Lidarr/LidarrIndexer.cs index ccd3c0db3..3fd8456eb 100644 --- a/src/NzbDrone.Core/Applications/Lidarr/LidarrIndexer.cs +++ b/src/NzbDrone.Core/Applications/Lidarr/LidarrIndexer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; @@ -31,6 +32,18 @@ namespace NzbDrone.Core.Applications.Lidarr var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value; var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value); + var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders; + + var seedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var seedTimeCompare = seedTime == otherSeedTime; + + var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var seedRatioCompare = seedRatio == otherSeedRatio; + return other.EnableRss == EnableRss && other.EnableAutomaticSearch == EnableAutomaticSearch && other.EnableInteractiveSearch == EnableInteractiveSearch && @@ -38,7 +51,7 @@ namespace NzbDrone.Core.Applications.Lidarr other.Implementation == Implementation && other.Priority == Priority && other.Id == Id && - apiKey && apiPath && baseUrl && cats; + apiKey && apiPath && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare; } } } diff --git a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs index e5719736e..13e4ffe8a 100644 --- a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs +++ b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs @@ -183,6 +183,13 @@ namespace NzbDrone.Core.Applications.Radarr radarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey; radarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())); + if (indexer.Protocol == DownloadProtocol.Torrent) + { + radarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders; + radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; + radarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; + } + return radarrIndexer; } } diff --git a/src/NzbDrone.Core/Applications/Radarr/RadarrIndexer.cs b/src/NzbDrone.Core/Applications/Radarr/RadarrIndexer.cs index 025b06044..c9b649913 100644 --- a/src/NzbDrone.Core/Applications/Radarr/RadarrIndexer.cs +++ b/src/NzbDrone.Core/Applications/Radarr/RadarrIndexer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; @@ -31,6 +32,18 @@ namespace NzbDrone.Core.Applications.Radarr var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value; var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value); + var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders; + + var seedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var seedTimeCompare = seedTime == otherSeedTime; + + var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var seedRatioCompare = seedRatio == otherSeedRatio; + return other.EnableRss == EnableRss && other.EnableAutomaticSearch == EnableAutomaticSearch && other.EnableInteractiveSearch == EnableInteractiveSearch && @@ -38,7 +51,7 @@ namespace NzbDrone.Core.Applications.Radarr other.Implementation == Implementation && other.Priority == Priority && other.Id == Id && - apiKey && apiPath && baseUrl && cats; + apiKey && apiPath && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare; } } } diff --git a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs index 606304a11..13cc761c8 100644 --- a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs +++ b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs @@ -183,6 +183,13 @@ namespace NzbDrone.Core.Applications.Readarr readarrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey; readarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())); + if (indexer.Protocol == DownloadProtocol.Torrent) + { + readarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders; + readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; + readarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; + } + return readarrIndexer; } } diff --git a/src/NzbDrone.Core/Applications/Readarr/ReadarrIndexer.cs b/src/NzbDrone.Core/Applications/Readarr/ReadarrIndexer.cs index 641a53e80..7e7d51dff 100644 --- a/src/NzbDrone.Core/Applications/Readarr/ReadarrIndexer.cs +++ b/src/NzbDrone.Core/Applications/Readarr/ReadarrIndexer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; @@ -31,6 +32,18 @@ namespace NzbDrone.Core.Applications.Readarr var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value; var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value); + var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders; + + var seedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var seedTimeCompare = seedTime == otherSeedTime; + + var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var seedRatioCompare = seedRatio == otherSeedRatio; + return other.EnableRss == EnableRss && other.EnableAutomaticSearch == EnableAutomaticSearch && other.EnableInteractiveSearch == EnableInteractiveSearch && @@ -38,7 +51,7 @@ namespace NzbDrone.Core.Applications.Readarr other.Implementation == Implementation && other.Priority == Priority && other.Id == Id && - apiKey && apiPath && baseUrl && cats; + apiKey && apiPath && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare; } } } diff --git a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs index 9538cee90..8fa513656 100644 --- a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs +++ b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs @@ -184,6 +184,13 @@ namespace NzbDrone.Core.Applications.Sonarr sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())); sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "animeCategories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.AnimeSyncCategories.ToArray())); + if (indexer.Protocol == DownloadProtocol.Torrent) + { + sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders; + sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; + sonarrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; + } + return sonarrIndexer; } } diff --git a/src/NzbDrone.Core/Applications/Sonarr/SonarrIndexer.cs b/src/NzbDrone.Core/Applications/Sonarr/SonarrIndexer.cs index fe0bfebf3..842af9776 100644 --- a/src/NzbDrone.Core/Applications/Sonarr/SonarrIndexer.cs +++ b/src/NzbDrone.Core/Applications/Sonarr/SonarrIndexer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; @@ -32,6 +33,18 @@ namespace NzbDrone.Core.Applications.Sonarr var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value); var animeCats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "animeCategories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "animeCategories").Value); + var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders; + + var seedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var seedTimeCompare = seedTime == otherSeedTime; + + var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var seedRatioCompare = seedRatio == otherSeedRatio; + return other.EnableRss == EnableRss && other.EnableAutomaticSearch == EnableAutomaticSearch && other.EnableInteractiveSearch == EnableInteractiveSearch && @@ -39,7 +52,7 @@ namespace NzbDrone.Core.Applications.Sonarr other.Implementation == Implementation && other.Priority == Priority && other.Id == Id && - apiKey && apiPath && baseUrl && cats && animeCats; + apiKey && apiPath && baseUrl && cats && animeCats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare; } } } diff --git a/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs b/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs index a9a45c06a..ff6a5966a 100644 --- a/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs +++ b/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs @@ -183,6 +183,13 @@ namespace NzbDrone.Core.Applications.Whisparr whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "apiKey").Value = _configFileProvider.ApiKey; whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "categories").Value = JArray.FromObject(indexer.Capabilities.Categories.SupportedCategories(Settings.SyncCategories.ToArray())); + if (indexer.Protocol == DownloadProtocol.Torrent) + { + whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value = indexer.AppProfile.Value.MinimumSeeders; + whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedRatio; + whisparrIndexer.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value = ((ITorrentIndexerSettings)indexer.Settings).TorrentBaseSettings.SeedTime; + } + return whisparrIndexer; } } diff --git a/src/NzbDrone.Core/Applications/Whisparr/WhisparrIndexer.cs b/src/NzbDrone.Core/Applications/Whisparr/WhisparrIndexer.cs index cd8c5df01..5898b1c96 100644 --- a/src/NzbDrone.Core/Applications/Whisparr/WhisparrIndexer.cs +++ b/src/NzbDrone.Core/Applications/Whisparr/WhisparrIndexer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; @@ -31,6 +32,18 @@ namespace NzbDrone.Core.Applications.Whisparr var apiKey = (string)Fields.FirstOrDefault(x => x.Name == "apiKey").Value == (string)other.Fields.FirstOrDefault(x => x.Name == "apiKey").Value; var cats = JToken.DeepEquals((JArray)Fields.FirstOrDefault(x => x.Name == "categories").Value, (JArray)other.Fields.FirstOrDefault(x => x.Name == "categories").Value); + var minimumSeeders = Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var otherMinimumSeeders = other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "minimumSeeders").Value); + var minimumSeedersCompare = minimumSeeders == otherMinimumSeeders; + + var seedTime = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var otherSeedTime = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime")?.Value == null ? null : (int?)Convert.ToInt32(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedTime").Value); + var seedTimeCompare = seedTime == otherSeedTime; + + var seedRatio = Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var otherSeedRatio = other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio")?.Value == null ? null : (double?)Convert.ToDouble(other.Fields.FirstOrDefault(x => x.Name == "seedCriteria.seedRatio").Value); + var seedRatioCompare = seedRatio == otherSeedRatio; + return other.EnableRss == EnableRss && other.EnableAutomaticSearch == EnableAutomaticSearch && other.EnableInteractiveSearch == EnableInteractiveSearch && @@ -38,7 +51,7 @@ namespace NzbDrone.Core.Applications.Whisparr other.Implementation == Implementation && other.Priority == Priority && other.Id == Id && - apiKey && apiPath && baseUrl && cats; + apiKey && apiPath && baseUrl && cats && minimumSeedersCompare && seedRatioCompare && seedTimeCompare; } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/018_minimum_seeders.cs b/src/NzbDrone.Core/Datastore/Migration/018_minimum_seeders.cs new file mode 100644 index 000000000..9569db660 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/018_minimum_seeders.cs @@ -0,0 +1,15 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(18)] + public class minimum_seeders : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("AppSyncProfiles") + .AddColumn("MinimumSeeders").AsInt32().NotNullable().WithDefaultValue(1); + } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/ReleaseAnalyticsService.cs b/src/NzbDrone.Core/IndexerSearch/ReleaseAnalyticsService.cs new file mode 100644 index 000000000..f4840ffcc --- /dev/null +++ b/src/NzbDrone.Core/IndexerSearch/ReleaseAnalyticsService.cs @@ -0,0 +1,48 @@ +using System.Linq; +using System.Net.Http; +using NzbDrone.Common.Cloud; +using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Analytics; +using NzbDrone.Core.Indexers.Events; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.IndexerSearch +{ + public class ReleaseAnalyticsService : IHandleAsync + { + private readonly IHttpClient _httpClient; + private readonly IHttpRequestBuilderFactory _requestBuilder; + private readonly IAnalyticsService _analyticsService; + + public ReleaseAnalyticsService(IHttpClient httpClient, IProwlarrCloudRequestBuilder requestBuilder, IAnalyticsService analyticsService) + { + _analyticsService = analyticsService; + _requestBuilder = requestBuilder.Releases; + _httpClient = httpClient; + } + + public void HandleAsync(IndexerQueryEvent message) + { + if (_analyticsService.IsEnabled) + { + var request = _requestBuilder.Create().Resource("release/push").Build(); + request.Method = HttpMethod.Post; + request.Headers.ContentType = "application/json"; + request.SuppressHttpError = true; + + var body = message.QueryResult.Releases.Select(x => new + { + Title = x.Title, + Categories = x.Categories.Where(c => c.Id < 10000).Select(c => c.Id), + Protocol = x.DownloadProtocol.ToString(), + Size = x.Size, + PublishDate = x.PublishDate + }); + + request.SetContent(body.ToJson()); + _httpClient.Post(request); + } + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Definitions/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Definitions/Torznab/TorznabSettings.cs index d2b7c1bb5..c1ea89cc9 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/Torznab/TorznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/Torznab/TorznabSettings.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text.RegularExpressions; using FluentValidation; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Annotations; using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Validation; @@ -36,7 +37,7 @@ namespace NzbDrone.Core.Indexers.Torznab } } - public class TorznabSettings : NewznabSettings + public class TorznabSettings : NewznabSettings, ITorrentIndexerSettings { private static readonly TorznabSettingsValidator Validator = new TorznabSettingsValidator(); @@ -44,6 +45,9 @@ namespace NzbDrone.Core.Indexers.Torznab { } + [FieldDefinition(3)] + public IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } = new IndexerTorrentBaseSettings(); + public override NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs b/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs new file mode 100644 index 000000000..11f0d543a --- /dev/null +++ b/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NzbDrone.Core.Indexers +{ + public interface ITorrentIndexerSettings : IIndexerSettings + { + IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } + } +} diff --git a/src/NzbDrone.Core/Indexers/IndexerTorrentBaseSettings.cs b/src/NzbDrone.Core/Indexers/IndexerTorrentBaseSettings.cs new file mode 100644 index 000000000..228b38858 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/IndexerTorrentBaseSettings.cs @@ -0,0 +1,47 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Indexers +{ + public class IndexerTorrentSettingsValidator : AbstractValidator + { + public IndexerTorrentSettingsValidator(double seedRatioMinimum = 0.0, int seedTimeMinimum = 0, int seasonPackSeedTimeMinimum = 0) + { + RuleFor(c => c.SeedRatio).GreaterThan(0.0) + .When(c => c.SeedRatio.HasValue) + .AsWarning().WithMessage("Should be greater than zero"); + + RuleFor(c => c.SeedTime).GreaterThan(0) + .When(c => c.SeedTime.HasValue) + .AsWarning().WithMessage("Should be greater than zero"); + + if (seedRatioMinimum != 0.0) + { + RuleFor(c => c.SeedRatio).GreaterThanOrEqualTo(seedRatioMinimum) + .When(c => c.SeedRatio > 0.0) + .AsWarning() + .WithMessage($"Under {seedRatioMinimum} leads to H&R"); + } + + if (seedTimeMinimum != 0) + { + RuleFor(c => c.SeedTime).GreaterThanOrEqualTo(seedTimeMinimum) + .When(c => c.SeedTime > 0) + .AsWarning() + .WithMessage($"Under {seedTimeMinimum} leads to H&R"); + } + } + } + + public class IndexerTorrentBaseSettings + { + private static readonly IndexerTorrentSettingsValidator Validator = new IndexerTorrentSettingsValidator(); + + [FieldDefinition(1, Type = FieldType.Textbox, Label = "Seed Ratio", HelpText = "The ratio a torrent should reach before stopping, empty is app's default", Advanced = true)] + public double? SeedRatio { get; set; } + + [FieldDefinition(2, Type = FieldType.Number, Label = "Seed Time", HelpText = "The time a torrent should be seeded before stopping, empty is app's default", Advanced = true)] + public int? SeedTime { get; set; } + } +} diff --git a/src/NzbDrone.Core/Indexers/Settings/CookieTorrentBaseSettings.cs b/src/NzbDrone.Core/Indexers/Settings/CookieTorrentBaseSettings.cs index 92e5e9983..653127007 100644 --- a/src/NzbDrone.Core/Indexers/Settings/CookieTorrentBaseSettings.cs +++ b/src/NzbDrone.Core/Indexers/Settings/CookieTorrentBaseSettings.cs @@ -4,7 +4,7 @@ using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.Settings { - public class CookieTorrentBaseSettings : IIndexerSettings + public class CookieTorrentBaseSettings : ITorrentIndexerSettings { public class CookieBaseSettingsValidator : AbstractValidator { @@ -30,6 +30,9 @@ namespace NzbDrone.Core.Indexers.Settings [FieldDefinition(3)] public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings(); + [FieldDefinition(4)] + public IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } = new IndexerTorrentBaseSettings(); + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Settings/NoAuthTorrentBaseSettings.cs b/src/NzbDrone.Core/Indexers/Settings/NoAuthTorrentBaseSettings.cs index eed3a814d..9aff60db1 100644 --- a/src/NzbDrone.Core/Indexers/Settings/NoAuthTorrentBaseSettings.cs +++ b/src/NzbDrone.Core/Indexers/Settings/NoAuthTorrentBaseSettings.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Indexers.Settings { } - public class NoAuthTorrentBaseSettings : IIndexerSettings + public class NoAuthTorrentBaseSettings : ITorrentIndexerSettings { private static readonly NoAuthSettingsValidator Validator = new NoAuthSettingsValidator(); @@ -18,6 +18,9 @@ namespace NzbDrone.Core.Indexers.Settings [FieldDefinition(2)] public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings(); + [FieldDefinition(3)] + public IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } = new IndexerTorrentBaseSettings(); + public virtual NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Settings/UserPassTorrentBaseSettings.cs b/src/NzbDrone.Core/Indexers/Settings/UserPassTorrentBaseSettings.cs index 552376e07..7b03f66ee 100644 --- a/src/NzbDrone.Core/Indexers/Settings/UserPassTorrentBaseSettings.cs +++ b/src/NzbDrone.Core/Indexers/Settings/UserPassTorrentBaseSettings.cs @@ -4,7 +4,7 @@ using NzbDrone.Core.Validation; namespace NzbDrone.Core.Indexers.Settings { - public class UserPassTorrentBaseSettings : IIndexerSettings + public class UserPassTorrentBaseSettings : ITorrentIndexerSettings { public class UserPassBaseSettingsValidator : AbstractValidator { @@ -35,6 +35,9 @@ namespace NzbDrone.Core.Indexers.Settings [FieldDefinition(4)] public IndexerBaseSettings BaseSettings { get; set; } = new IndexerBaseSettings(); + [FieldDefinition(5)] + public IndexerTorrentBaseSettings TorrentBaseSettings { get; set; } = new IndexerTorrentBaseSettings(); + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 7c0408572..f66aa4eb6 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -3,7 +3,6 @@ "AcceptConfirmationModal": "Accept Confirmation Modal", "Actions": "Actions", "Add": "Add", - "AddAppProfile": "Add App Sync Profile", "AddDownloadClient": "Add Download Client", "AddDownloadClientToProwlarr": "Adding a download client allows Prowlarr to send releases direct from the UI while doing a manual search.", "Added": "Added", @@ -13,6 +12,7 @@ "AddingTag": "Adding tag", "AddNewIndexer": "Add New Indexer", "AddRemoveOnly": "Add and Remove Only", + "AddSyncProfile": "Add Sync Profile", "AddToDownloadClient": "Add release to download client", "Age": "Age", "All": "All", @@ -32,10 +32,8 @@ "ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags", "ApplyTagsHelpTexts3": "Remove: Remove the entered tags", "ApplyTagsHelpTexts4": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)", - "AppProfile": "App Profile", "AppProfileDeleteConfirm": "Are you sure you want to delete {0}?", "AppProfileInUse": "App Profile in Use", - "AppProfiles": "App Profiles", "AppProfileSelectHelpText": "App profiles are used to control RSS, Automatic Search and Interactive Search settings on application sync", "Apps": "Apps", "AppSettingsSummary": "Applications and settings to configure how Prowlarr interacts with your PVR programs", @@ -119,8 +117,8 @@ "DownloadClientStatusCheckAllClientMessage": "All download clients are unavailable due to failures", "DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {0}", "Edit": "Edit", - "EditAppProfile": "Edit App Profile", "EditIndexer": "Edit Indexer", + "EditSyncProfile": "Edit Sync Profile", "Enable": "Enable", "EnableAutomaticSearch": "Enable Automatic Search", "EnableAutomaticSearchHelpText": "Will be used when automatic searches are performed via the UI or by Prowlarr", @@ -222,6 +220,8 @@ "Mechanism": "Mechanism", "Message": "Message", "MIA": "MIA", + "MinimumSeeders": "Minimum Seeders", + "MinimumSeedersHelpText": "Minimum seeders required by the Appliction for the indexer to grab", "Mode": "Mode", "MoreInfo": "More Info", "MovieIndexScrollBottom": "Movie Index: Scroll Bottom", @@ -372,6 +372,8 @@ "SyncLevel": "Sync Level", "SyncLevelAddRemove": "Add and Remove Only: When indexers are added or removed from Prowlarr, it will update this remote app.", "SyncLevelFull": "Full Sync: Will keep this app's indexers fully in sync. Changes made to indexers in Prowlarr are then synced to this app. Any change made to indexers remotely within this app will be overridden by Prowlarr on the next sync.", + "SyncProfile": "Sync Profile", + "SyncProfiles": "Sync Profiles", "System": "System", "SystemTimeCheckMessage": "System time is off by more than 1 day. Scheduled tasks may not run correctly until the time is corrected", "TableOptions": "Table Options", diff --git a/src/NzbDrone.Core/Profiles/AppSyncProfile.cs b/src/NzbDrone.Core/Profiles/AppSyncProfile.cs index e7a7e896e..a37a99c70 100644 --- a/src/NzbDrone.Core/Profiles/AppSyncProfile.cs +++ b/src/NzbDrone.Core/Profiles/AppSyncProfile.cs @@ -8,5 +8,6 @@ namespace NzbDrone.Core.Profiles public bool EnableRss { get; set; } public bool EnableAutomaticSearch { get; set; } public bool EnableInteractiveSearch { get; set; } + public int MinimumSeeders { get; set; } } } diff --git a/src/NzbDrone.Core/Profiles/AppSyncProfileService.cs b/src/NzbDrone.Core/Profiles/AppSyncProfileService.cs index 952a06d8e..73c07d0a8 100644 --- a/src/NzbDrone.Core/Profiles/AppSyncProfileService.cs +++ b/src/NzbDrone.Core/Profiles/AppSyncProfileService.cs @@ -88,7 +88,8 @@ namespace NzbDrone.Core.Profiles Name = name, EnableAutomaticSearch = true, EnableInteractiveSearch = true, - EnableRss = true + EnableRss = true, + MinimumSeeders = 1 }; return qualityProfile; diff --git a/src/Prowlarr.Api.V1/Profiles/App/AppProfileResource.cs b/src/Prowlarr.Api.V1/Profiles/App/AppProfileResource.cs index ebc968b48..aa88dfac6 100644 --- a/src/Prowlarr.Api.V1/Profiles/App/AppProfileResource.cs +++ b/src/Prowlarr.Api.V1/Profiles/App/AppProfileResource.cs @@ -11,6 +11,7 @@ namespace Prowlarr.Api.V1.Profiles.App public bool EnableRss { get; set; } public bool EnableInteractiveSearch { get; set; } public bool EnableAutomaticSearch { get; set; } + public int MinimumSeeders { get; set; } } public static class ProfileResourceMapper @@ -28,7 +29,8 @@ namespace Prowlarr.Api.V1.Profiles.App Name = model.Name, EnableRss = model.EnableRss, EnableInteractiveSearch = model.EnableInteractiveSearch, - EnableAutomaticSearch = model.EnableAutomaticSearch + EnableAutomaticSearch = model.EnableAutomaticSearch, + MinimumSeeders = model.MinimumSeeders }; } @@ -45,7 +47,8 @@ namespace Prowlarr.Api.V1.Profiles.App Name = resource.Name, EnableRss = resource.EnableRss, EnableInteractiveSearch = resource.EnableInteractiveSearch, - EnableAutomaticSearch = resource.EnableAutomaticSearch + EnableAutomaticSearch = resource.EnableAutomaticSearch, + MinimumSeeders = resource.MinimumSeeders }; }