From 1d77c40d0ecdf0763908e7436d5cc7e33165c56c Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Thu, 30 May 2019 01:42:34 +0200 Subject: [PATCH] Support for primary and fallback download client --- frontend/src/Components/Form/TextInput.js | 6 + .../DownloadClients/DownloadClient.js | 14 +- .../EditDownloadClientModalContent.js | 18 +++ .../DownloadClient/DownloadClientModule.cs | 2 + .../DownloadClient/DownloadClientResource.cs | 1 + ..._add_remux_qualities_in_profileFixture.cs} | 0 ...132_add_download_client_priorityFixture.cs | 153 ++++++++++++++++++ .../Download/DownloadClientProviderFixture.cs | 48 +++++- .../NzbDrone.Core.Test.csproj | 3 +- .../132_add_download_client_priority.cs | 56 +++++++ .../Download/DownloadClientDefinition.cs | 1 + .../Download/DownloadClientProvider.cs | 5 + src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + .../DownloadClient/DownloadClientResource.cs | 3 + 14 files changed, 307 insertions(+), 4 deletions(-) rename src/NzbDrone.Core.Test/Datastore/Migration/{122_add_remux_qualities_in_profile.cs => 122_add_remux_qualities_in_profileFixture.cs} (100%) create mode 100644 src/NzbDrone.Core.Test/Datastore/Migration/132_add_download_client_priorityFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/132_add_download_client_priority.cs diff --git a/frontend/src/Components/Form/TextInput.js b/frontend/src/Components/Form/TextInput.js index 9feefa616..cc0cbca02 100644 --- a/frontend/src/Components/Form/TextInput.js +++ b/frontend/src/Components/Form/TextInput.js @@ -128,6 +128,8 @@ class TextInput extends Component { hasWarning, hasButton, step, + min, + max, onBlur } = this.props; @@ -148,6 +150,8 @@ class TextInput extends Component { name={name} value={value} step={step} + min={min} + max={max} onChange={this.onChange} onFocus={this.onFocus} onBlur={onBlur} @@ -171,6 +175,8 @@ TextInput.propTypes = { hasWarning: PropTypes.bool, hasButton: PropTypes.bool, step: PropTypes.number, + min: PropTypes.number, + max: PropTypes.number, onChange: PropTypes.func.isRequired, onFocus: PropTypes.func, onBlur: PropTypes.func, diff --git a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js index 6a86fef16..4724ab9ad 100644 --- a/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js +++ b/frontend/src/Settings/DownloadClients/DownloadClients/DownloadClient.js @@ -54,7 +54,8 @@ class DownloadClient extends Component { const { id, name, - enable + enable, + priority } = this.props; return ( @@ -80,6 +81,16 @@ class DownloadClient extends Component { Disabled } + + { + priority > 1 && + + } + Client Priority + + + + } diff --git a/src/NzbDrone.Api/DownloadClient/DownloadClientModule.cs b/src/NzbDrone.Api/DownloadClient/DownloadClientModule.cs index d7568189f..8b5ad25d4 100644 --- a/src/NzbDrone.Api/DownloadClient/DownloadClientModule.cs +++ b/src/NzbDrone.Api/DownloadClient/DownloadClientModule.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Api.DownloadClient resource.Enable = definition.Enable; resource.Protocol = definition.Protocol; + resource.Priority = definition.Priority; } protected override void MapToModel(DownloadClientDefinition definition, DownloadClientResource resource) @@ -23,6 +24,7 @@ namespace NzbDrone.Api.DownloadClient definition.Enable = resource.Enable; definition.Protocol = resource.Protocol; + definition.Priority = resource.Priority; } protected override void Validate(DownloadClientDefinition definition, bool includeWarnings) diff --git a/src/NzbDrone.Api/DownloadClient/DownloadClientResource.cs b/src/NzbDrone.Api/DownloadClient/DownloadClientResource.cs index a7156e08d..5e268578b 100644 --- a/src/NzbDrone.Api/DownloadClient/DownloadClientResource.cs +++ b/src/NzbDrone.Api/DownloadClient/DownloadClientResource.cs @@ -6,5 +6,6 @@ namespace NzbDrone.Api.DownloadClient { public bool Enable { get; set; } public DownloadProtocol Protocol { get; set; } + public int Priority { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/122_add_remux_qualities_in_profile.cs b/src/NzbDrone.Core.Test/Datastore/Migration/122_add_remux_qualities_in_profileFixture.cs similarity index 100% rename from src/NzbDrone.Core.Test/Datastore/Migration/122_add_remux_qualities_in_profile.cs rename to src/NzbDrone.Core.Test/Datastore/Migration/122_add_remux_qualities_in_profileFixture.cs diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/132_add_download_client_priorityFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/132_add_download_client_priorityFixture.cs new file mode 100644 index 000000000..373377d91 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/Migration/132_add_download_client_priorityFixture.cs @@ -0,0 +1,153 @@ +using System.Linq; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore.Migration +{ + [TestFixture] + public class add_download_client_priorityFixture : MigrationTest + { + [Test] + public void should_set_prio_to_one() + { + var db = WithMigrationTestDb(c => + { + c.Insert.IntoTable("DownloadClients").Row(new + { + Enable = 1, + Name = "Deluge", + Implementation = "Deluge", + Settings = new DelugeSettings85 + { + Host = "127.0.0.1", + TvCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }); + }); + + var items = db.Query("SELECT * FROM DownloadClients"); + + items.Should().HaveCount(1); + items.First().Priority.Should().Be(1); + } + + [Test] + public void should_renumber_prio_for_enabled_clients() + { + var db = WithMigrationTestDb(c => + { + c.Insert.IntoTable("DownloadClients").Row(new + { + Enable = 1, + Name = "Deluge", + Implementation = "Deluge", + Settings = new DelugeSettings85 + { + Host = "127.0.0.1", + TvCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }).Row(new + { + Enable = 1, + Name = "Deluge2", + Implementation = "Deluge", + Settings = new DelugeSettings85 + { + Host = "127.0.0.1", + TvCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }).Row(new + { + Enable = 1, + Name = "sab", + Implementation = "Sabnzbd", + Settings = new SabnzbdSettings81 + { + Host = "127.0.0.1", + TvCategory = "abc" + }.ToJson(), + ConfigContract = "SabnzbdSettings" + }); + }); + + var items = db.Query("SELECT * FROM DownloadClients"); + + items.Should().HaveCount(3); + items[0].Priority.Should().Be(1); + items[1].Priority.Should().Be(2); + items[2].Priority.Should().Be(1); + } + + [Test] + public void should_not_renumber_prio_for_disabled_clients() + { + var db = WithMigrationTestDb(c => + { + c.Insert.IntoTable("DownloadClients").Row(new + { + Enable = 0, + Name = "Deluge", + Implementation = "Deluge", + Settings = new DelugeSettings85 + { + Host = "127.0.0.1", + TvCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }).Row(new + { + Enable = 0, + Name = "Deluge2", + Implementation = "Deluge", + Settings = new DelugeSettings85 + { + Host = "127.0.0.1", + TvCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }).Row(new + { + Enable = 0, + Name = "sab", + Implementation = "Sabnzbd", + Settings = new SabnzbdSettings81 + { + Host = "127.0.0.1", + TvCategory = "abc" + }.ToJson(), + ConfigContract = "SabnzbdSettings" + }); + }); + + var items = db.Query("SELECT * FROM DownloadClients"); + + items.Should().HaveCount(3); + items[0].Priority.Should().Be(1); + items[1].Priority.Should().Be(1); + items[1].Priority.Should().Be(1); + } + } + + public class DownloadClientDefinition132 + { + public int Id { get; set; } + public bool Enable { get; set; } + public int Priority { get; set; } + public string Name { get; set; } + public string Implementation { get; set; } + public JObject Settings { get; set; } + public string ConfigContract { get; set; } + } +} diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs index a8e60165e..27fe13b81 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs @@ -35,13 +35,14 @@ namespace NzbDrone.Core.Test.Download .Returns(_blockedProviders); } - private Mock WithUsenetClient() + private Mock WithUsenetClient(int priority = 0) { var mock = new Mock(MockBehavior.Default); mock.SetupGet(s => s.Definition) .Returns(Builder .CreateNew() .With(v => v.Id = _nextId++) + .With(v => v.Priority = priority) .Build()); _downloadClients.Add(mock.Object); @@ -51,13 +52,14 @@ namespace NzbDrone.Core.Test.Download return mock; } - private Mock WithTorrentClient() + private Mock WithTorrentClient(int priority = 0) { var mock = new Mock(MockBehavior.Default); mock.SetupGet(s => s.Definition) .Returns(Builder .CreateNew() .With(v => v.Id = _nextId++) + .With(v => v.Priority = priority) .Build()); _downloadClients.Add(mock.Object); @@ -181,5 +183,47 @@ namespace NzbDrone.Core.Test.Download client3.Definition.Id.Should().Be(4); client4.Definition.Id.Should().Be(2); } + + [Test] + public void should_skip_secondary_prio_torrent_client() + { + WithUsenetClient(); + WithTorrentClient(2); + WithTorrentClient(); + WithTorrentClient(); + + var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + + client1.Definition.Id.Should().Be(3); + client2.Definition.Id.Should().Be(4); + client3.Definition.Id.Should().Be(3); + client4.Definition.Id.Should().Be(4); + } + + [Test] + public void should_not_skip_secondary_prio_torrent_client_if_primary_blocked() + { + WithUsenetClient(); + WithTorrentClient(2); + WithTorrentClient(2); + WithTorrentClient(); + + GivenBlockedClient(4); + + var client1 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + var client2 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + var client3 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + var client4 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + var client5 = Subject.GetDownloadClient(DownloadProtocol.Torrent); + + client1.Definition.Id.Should().Be(2); + client2.Definition.Id.Should().Be(3); + client3.Definition.Id.Should().Be(2); + client4.Definition.Id.Should().Be(3); + } } } diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 0fca5c37d..728fd0241 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -139,7 +139,8 @@ - + + diff --git a/src/NzbDrone.Core/Datastore/Migration/132_add_download_client_priority.cs b/src/NzbDrone.Core/Datastore/Migration/132_add_download_client_priority.cs new file mode 100644 index 000000000..8de02bf2b --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/132_add_download_client_priority.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Data; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(132)] + public class add_download_client_priority : NzbDroneMigrationBase + { + // Need snapshot in time without having to instantiate. + private static HashSet _usenetImplementations = new HashSet + { + "Sabnzbd", "NzbGet", "NzbVortex", "UsenetBlackhole", "UsenetDownloadStation" + }; + + protected override void MainDbUpgrade() + { + Alter.Table("DownloadClients").AddColumn("Priority").AsInt32().WithDefaultValue(1); + Execute.WithConnection(InitPriorityForBackwardCompatibility); + + } + + private void InitPriorityForBackwardCompatibility(IDbConnection conn, IDbTransaction tran) + { + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = tran; + cmd.CommandText = "SELECT Id, Implementation FROM DownloadClients WHERE Enable = 1"; + + using (var reader = cmd.ExecuteReader()) + { + int nextUsenet = 1; + int nextTorrent = 1; + while (reader.Read()) + { + var id = reader.GetInt32(0); + var implName = reader.GetString(1); + + var isUsenet = _usenetImplementations.Contains(implName); + + using (var updateCmd = conn.CreateCommand()) + { + updateCmd.Transaction = tran; + updateCmd.CommandText = "UPDATE DownloadClients SET Priority = ? WHERE Id = ?"; + updateCmd.AddParameter(isUsenet ? nextUsenet++ : nextTorrent++); + updateCmd.AddParameter(id); + + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/Download/DownloadClientDefinition.cs b/src/NzbDrone.Core/Download/DownloadClientDefinition.cs index b81536a53..1c0dfa927 100644 --- a/src/NzbDrone.Core/Download/DownloadClientDefinition.cs +++ b/src/NzbDrone.Core/Download/DownloadClientDefinition.cs @@ -6,5 +6,6 @@ namespace NzbDrone.Core.Download public class DownloadClientDefinition : ProviderDefinition { public DownloadProtocol Protocol { get; set; } + public int Priority { get; set; } = 1; } } diff --git a/src/NzbDrone.Core/Download/DownloadClientProvider.cs b/src/NzbDrone.Core/Download/DownloadClientProvider.cs index e606ad972..978f9ea60 100644 --- a/src/NzbDrone.Core/Download/DownloadClientProvider.cs +++ b/src/NzbDrone.Core/Download/DownloadClientProvider.cs @@ -50,6 +50,11 @@ namespace NzbDrone.Core.Download } } + // Use the first priority clients first + availableProviders = availableProviders.GroupBy(v => (v.Definition as DownloadClientDefinition).Priority) + .OrderBy(v => v.Key) + .First().OrderBy(v => v.Definition.Id).ToList(); + var lastId = _lastUsedDownloadClient.Find(downloadProtocol.ToString()); var provider = availableProviders.FirstOrDefault(v => v.Definition.Id > lastId) ?? availableProviders.First(); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index dce02434c..5065c9a09 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -136,6 +136,7 @@ + diff --git a/src/Sonarr.Api.V3/DownloadClient/DownloadClientResource.cs b/src/Sonarr.Api.V3/DownloadClient/DownloadClientResource.cs index 71fe480d3..43efc111a 100644 --- a/src/Sonarr.Api.V3/DownloadClient/DownloadClientResource.cs +++ b/src/Sonarr.Api.V3/DownloadClient/DownloadClientResource.cs @@ -7,6 +7,7 @@ namespace Sonarr.Api.V3.DownloadClient { public bool Enable { get; set; } public DownloadProtocol Protocol { get; set; } + public int Priority { get; set; } } public class DownloadClientResourceMapper : ProviderResourceMapper @@ -19,6 +20,7 @@ namespace Sonarr.Api.V3.DownloadClient resource.Enable = definition.Enable; resource.Protocol = definition.Protocol; + resource.Priority = definition.Priority; return resource; } @@ -31,6 +33,7 @@ namespace Sonarr.Api.V3.DownloadClient definition.Enable = resource.Enable; definition.Protocol = resource.Protocol; + definition.Priority = resource.Priority; return definition; }