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();