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/Lidarr.Api.V1/DownloadClient/DownloadClientResource.cs b/src/Lidarr.Api.V1/DownloadClient/DownloadClientResource.cs index bab6faa84..aa2d79a49 100644 --- a/src/Lidarr.Api.V1/DownloadClient/DownloadClientResource.cs +++ b/src/Lidarr.Api.V1/DownloadClient/DownloadClientResource.cs @@ -7,6 +7,7 @@ namespace Lidarr.Api.V1.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 Lidarr.Api.V1.DownloadClient resource.Enable = definition.Enable; resource.Protocol = definition.Protocol; + resource.Priority = definition.Priority; return resource; } @@ -31,6 +33,7 @@ namespace Lidarr.Api.V1.DownloadClient definition.Enable = resource.Enable; definition.Protocol = resource.Protocol; + definition.Priority = resource.Priority; return definition; } diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/036_add_download_client_priorityFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/036_add_download_client_priorityFixture.cs new file mode 100644 index 000000000..965c37973 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/Migration/036_add_download_client_priorityFixture.cs @@ -0,0 +1,178 @@ +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 DelugeSettings36 + { + Host = "127.0.0.1", + MusicCategory = "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 DelugeSettings36 + { + Host = "127.0.0.1", + MusicCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }).Row(new + { + Enable = 1, + Name = "Deluge2", + Implementation = "Deluge", + Settings = new DelugeSettings36 + { + Host = "127.0.0.1", + MusicCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }).Row(new + { + Enable = 1, + Name = "sab", + Implementation = "Sabnzbd", + Settings = new SabnzbdSettings36 + { + Host = "127.0.0.1", + MusicCategory = "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 DelugeSettings36 + { + Host = "127.0.0.1", + MusicCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }).Row(new + { + Enable = 0, + Name = "Deluge2", + Implementation = "Deluge", + Settings = new DelugeSettings36 + { + Host = "127.0.0.1", + MusicCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }).Row(new + { + Enable = 0, + Name = "sab", + Implementation = "Sabnzbd", + Settings = new SabnzbdSettings36 + { + Host = "127.0.0.1", + MusicCategory = "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 DownloadClientDefinition036 + { + 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; } + } + + public class DelugeSettings36 + { + public string Host { get; set; } + public int Port { get; set; } + public string UrlBase { get; set; } + public string Password { get; set; } + public string MusicCategory { get; set; } + public int RecentTvPriority { get; set; } + public int OlderTvPriority { get; set; } + public bool UseSsl { get; set; } + } + + public class SabnzbdSettings36 + { + public string Host { get; set; } + public int Port { get; set; } + public string ApiKey { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string MusicCategory { get; set; } + public int RecentTvPriority { get; set; } + public int OlderTvPriority { get; set; } + public bool UseSsl { 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/Datastore/Migration/036_add_download_client_priority.cs b/src/NzbDrone.Core/Datastore/Migration/036_add_download_client_priority.cs new file mode 100644 index 000000000..d11dc88ff --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/036_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(036)] + 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 0e799c609..09d7d712e 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();