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;
}