diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientFactory/ResolveDownloadClientFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientFactory/ResolveDownloadClientFixture.cs new file mode 100644 index 000000000..f4b86ad16 --- /dev/null +++ b/src/NzbDrone.Core.Test/Download/DownloadClientFactory/ResolveDownloadClientFixture.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Download; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Download +{ + [TestFixture] + public class ResolveDownloadClientFixture : CoreTest + { + private List _downloadClients; + + [SetUp] + public void SetUp() + { + _downloadClients = Builder.CreateListOfSize(3) + .TheFirst(2) + .With(v => v.Enable = true) + .TheNext(1) + .With(v => v.Enable = false) + .Build() + .ToList(); + + Mocker.GetMock() + .Setup(v => v.All()) + .Returns(_downloadClients); + } + + [Test] + public void should_throw_if_download_client_with_id_cannot_be_found() + { + Assert.Throws(() => Subject.ResolveDownloadClient(10, null)); + } + + [Test] + public void should_throw_if_download_client_with_name_cannot_be_found() + { + Assert.Throws(() => Subject.ResolveDownloadClient(null, "Not a Real Client")); + } + + [Test] + public void should_throw_if_download_client_with_id_does_not_match_download_client_with_name() + { + Assert.Throws(() => Subject.ResolveDownloadClient(_downloadClients[0].Id, _downloadClients[1].Name)); + } + + [Test] + public void should_throw_if_download_client_is_not_enabled() + { + Assert.Throws(() => Subject.ResolveDownloadClient(_downloadClients[2].Id, null)); + } + + [Test] + public void should_return_download_client_when_only_id_is_provided() + { + var result = Subject.ResolveDownloadClient(_downloadClients[0].Id, null); + + result.Should().NotBeNull(); + result.Should().Be(_downloadClients[0]); + } + + [Test] + public void should_return_download_client_when_only_name_is_provided() + { + var result = Subject.ResolveDownloadClient(null, _downloadClients[0].Name); + + result.Should().NotBeNull(); + result.Should().Be(_downloadClients[0]); + } + + [Test] + public void should_return_download_client_when_id_and_name_provided_for_the_same_download_client() + { + var result = Subject.ResolveDownloadClient(_downloadClients[0].Id, _downloadClients[0].Name); + + result.Should().NotBeNull(); + result.Should().Be(_downloadClients[0]); + } + + [Test] + public void should_return_null_if_both_id_and_name_are_not_provided() + { + var result = Subject.ResolveDownloadClient(null, null); + + result.Should().BeNull(); + } + } +} diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientProvider/GetDownloadClientFixture.cs similarity index 63% rename from src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs rename to src/NzbDrone.Core.Test/Download/DownloadClientProvider/GetDownloadClientFixture.cs index 5e42fe0d1..45b41c55e 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientProviderFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientProvider/GetDownloadClientFixture.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using Moq; @@ -13,7 +12,7 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Download { [TestFixture] - public class DownloadClientProviderFixture : CoreTest + public class GetDownloadClientFixture : CoreTest { private List _downloadClients; private List _blockedProviders; @@ -326,207 +325,5 @@ namespace NzbDrone.Core.Test.Download Assert.Throws(() => Subject.GetDownloadClient(DownloadProtocol.Torrent, 1)); } - - [Test] - public void should_return_all_available_clients_for_protocol() - { - WithUsenetClient(); - WithTorrentClient(); - WithTorrentClient(); - WithTorrentClient(); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent); - - clients.Should().HaveCount(3); - clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 2, 3, 4 }); - } - - [Test] - public void should_return_empty_when_no_clients_available_for_protocol() - { - WithUsenetClient(); - WithUsenetClient(); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent); - - clients.Should().BeEmpty(); - } - - [Test] - public void should_return_clients_ordered_by_priority_then_by_last_used() - { - WithTorrentClient(priority: 1); - WithTorrentClient(priority: 0); - WithTorrentClient(priority: 0); - WithTorrentClient(priority: 2); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent); - - clients.Should().HaveCount(4); - var clientIds = clients.Select(c => c.Definition.Id).ToArray(); - clientIds[0].Should().Be(2); - clientIds[1].Should().Be(3); - clientIds[2].Should().Be(1); - clientIds[3].Should().Be(4); - } - - [Test] - public void should_rotate_clients_within_same_priority_based_on_last_used() - { - WithTorrentClient(priority: 0); - WithTorrentClient(priority: 0); - WithTorrentClient(priority: 0); - - var clients1 = Subject.GetDownloadClients(DownloadProtocol.Torrent); - clients1.First().Definition.Id.Should().Be(1); - - Subject.ReportSuccessfulDownloadClient(DownloadProtocol.Torrent, 2); - - var clients2 = Subject.GetDownloadClients(DownloadProtocol.Torrent); - var clientIds = clients2.Select(c => c.Definition.Id).ToArray(); - clientIds[0].Should().Be(3); - clientIds[1].Should().Be(1); - clientIds[2].Should().Be(2); - } - - [Test] - public void should_filter_clients_by_tags() - { - var seriesTags = new HashSet { 1, 2 }; - - WithTorrentClient(tags: new HashSet { 1 }); - WithTorrentClient(tags: new HashSet { 3 }); - WithTorrentClient(tags: new HashSet { 2 }); - WithTorrentClient(); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, tags: seriesTags); - - clients.Should().HaveCount(2); - clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 1, 3 }); - } - - [Test] - public void should_return_non_tagged_clients_when_no_matching_tags() - { - var seriesTags = new HashSet { 5 }; - - WithTorrentClient(tags: new HashSet { 1 }); - WithTorrentClient(tags: new HashSet { 2 }); - WithTorrentClient(); - WithTorrentClient(); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, tags: seriesTags); - - clients.Should().HaveCount(2); - clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 3, 4 }); - } - - [Test] - public void should_throw_when_all_clients_have_non_matching_tags() - { - var seriesTags = new HashSet { 5 }; - - WithTorrentClient(tags: new HashSet { 1 }); - WithTorrentClient(tags: new HashSet { 2 }); - - Assert.Throws(() => Subject.GetDownloadClients(DownloadProtocol.Torrent, tags: seriesTags)); - } - - [Test] - public void should_return_indexer_specific_client_when_specified() - { - WithTorrentClient(); - WithTorrentClient(); - WithTorrentClient(); - WithTorrentIndexer(2); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, indexerId: 1); - - clients.Should().HaveCount(1); - clients.First().Definition.Id.Should().Be(2); - } - - [Test] - public void should_throw_when_indexer_client_does_not_exist() - { - WithTorrentClient(); - WithTorrentClient(); - WithTorrentIndexer(5); - - Assert.Throws(() => Subject.GetDownloadClients(DownloadProtocol.Torrent, indexerId: 1)); - } - - [Test] - public void should_filter_blocked_clients_when_requested() - { - WithTorrentClient(); - WithTorrentClient(); - WithTorrentClient(); - - GivenBlockedClient(2); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, filterBlockedClients: true); - - clients.Should().HaveCount(2); - clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 1, 3 }); - } - - [Test] - public void should_throw_when_all_clients_blocked_and_filter_enabled() - { - WithTorrentClient(); - WithTorrentClient(); - - GivenBlockedClient(1); - GivenBlockedClient(2); - - Assert.Throws(() => Subject.GetDownloadClients(DownloadProtocol.Torrent, filterBlockedClients: true)); - } - - [Test] - public void should_return_blocked_clients_when_filter_disabled() - { - WithTorrentClient(); - WithTorrentClient(); - - GivenBlockedClient(1); - GivenBlockedClient(2); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, filterBlockedClients: false); - - clients.Should().HaveCount(2); - clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 1, 2 }); - } - - [Test] - public void should_throw_when_indexer_client_is_blocked_and_filter_enabled() - { - WithTorrentClient(); - WithTorrentClient(); - WithTorrentIndexer(2); - - GivenBlockedClient(2); - - Assert.Throws(() => Subject.GetDownloadClients(DownloadProtocol.Torrent, indexerId: 1, filterBlockedClients: true)); - } - - [Test] - public void should_combine_tags_and_priority_filtering() - { - var seriesTags = new HashSet { 1 }; - - WithTorrentClient(priority: 1, tags: new HashSet { 1 }); - WithTorrentClient(priority: 0, tags: new HashSet { 1 }); - WithTorrentClient(priority: 0, tags: new HashSet { 2 }); - WithTorrentClient(priority: 2); - - var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, tags: seriesTags); - - clients.Should().HaveCount(2); - var clientIds = clients.Select(c => c.Definition.Id).ToArray(); - - clientIds[0].Should().Be(2); - clientIds[1].Should().Be(1); - } } } diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientProvider/GetDownloadClientsFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientProvider/GetDownloadClientsFixture.cs new file mode 100644 index 000000000..a43127ee3 --- /dev/null +++ b/src/NzbDrone.Core.Test/Download/DownloadClientProvider/GetDownloadClientsFixture.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Download +{ + [TestFixture] + public class GetDownloadClientsFixture : CoreTest + { + private List _downloadClients; + private List _blockedProviders; + private int _nextId; + + [SetUp] + public void SetUp() + { + _downloadClients = new List(); + _blockedProviders = new List(); + _nextId = 1; + + Mocker.GetMock() + .Setup(v => v.GetAvailableProviders()) + .Returns(_downloadClients); + + Mocker.GetMock() + .Setup(v => v.GetBlockedProviders()) + .Returns(_blockedProviders); + } + + private Mock WithUsenetClient(int priority = 0, HashSet tags = null) + { + var mock = new Mock(MockBehavior.Default); + mock.SetupGet(s => s.Definition) + .Returns(Builder + .CreateNew() + .With(v => v.Id = _nextId++) + .With(v => v.Priority = priority) + .With(v => v.Tags = tags ?? new HashSet()) + .Build()); + + _downloadClients.Add(mock.Object); + + mock.SetupGet(v => v.Protocol).Returns(DownloadProtocol.Usenet); + + return mock; + } + + private Mock WithTorrentClient(int priority = 0, HashSet tags = null) + { + var mock = new Mock(MockBehavior.Default); + mock.SetupGet(s => s.Definition) + .Returns(Builder + .CreateNew() + .With(v => v.Id = _nextId++) + .With(v => v.Priority = priority) + .With(v => v.Tags = tags ?? new HashSet()) + .Build()); + + _downloadClients.Add(mock.Object); + + mock.SetupGet(v => v.Protocol).Returns(DownloadProtocol.Torrent); + + return mock; + } + + private void WithTorrentIndexer(int downloadClientId) + { + Mocker.GetMock() + .Setup(v => v.Find(It.IsAny())) + .Returns(Builder + .CreateNew() + .With(v => v.Id = _nextId++) + .With(v => v.DownloadClientId = downloadClientId) + .Build()); + } + + private void GivenBlockedClient(int id) + { + _blockedProviders.Add(new DownloadClientStatus + { + ProviderId = id, + DisabledTill = DateTime.UtcNow.AddHours(3) + }); + } + + [Test] + public void should_return_all_available_clients_for_protocol() + { + WithUsenetClient(); + WithTorrentClient(); + WithTorrentClient(); + WithTorrentClient(); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent); + + clients.Should().HaveCount(3); + clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 2, 3, 4 }); + } + + [Test] + public void should_return_empty_when_no_clients_available_for_protocol() + { + WithUsenetClient(); + WithUsenetClient(); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent); + + clients.Should().BeEmpty(); + } + + [Test] + public void should_return_clients_ordered_by_priority_then_by_last_used() + { + WithTorrentClient(priority: 1); + WithTorrentClient(priority: 0); + WithTorrentClient(priority: 0); + WithTorrentClient(priority: 2); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent); + + clients.Should().HaveCount(4); + var clientIds = clients.Select(c => c.Definition.Id).ToArray(); + clientIds[0].Should().Be(2); + clientIds[1].Should().Be(3); + clientIds[2].Should().Be(1); + clientIds[3].Should().Be(4); + } + + [Test] + public void should_rotate_clients_within_same_priority_based_on_last_used() + { + WithTorrentClient(priority: 0); + WithTorrentClient(priority: 0); + WithTorrentClient(priority: 0); + + var clients1 = Subject.GetDownloadClients(DownloadProtocol.Torrent); + clients1.First().Definition.Id.Should().Be(1); + + Subject.ReportSuccessfulDownloadClient(DownloadProtocol.Torrent, 2); + + var clients2 = Subject.GetDownloadClients(DownloadProtocol.Torrent); + var clientIds = clients2.Select(c => c.Definition.Id).ToArray(); + clientIds[0].Should().Be(3); + clientIds[1].Should().Be(1); + clientIds[2].Should().Be(2); + } + + [Test] + public void should_filter_clients_by_tags() + { + var seriesTags = new HashSet { 1, 2 }; + + WithTorrentClient(tags: new HashSet { 1 }); + WithTorrentClient(tags: new HashSet { 3 }); + WithTorrentClient(tags: new HashSet { 2 }); + WithTorrentClient(); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, tags: seriesTags); + + clients.Should().HaveCount(2); + clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 1, 3 }); + } + + [Test] + public void should_return_non_tagged_clients_when_no_matching_tags() + { + var seriesTags = new HashSet { 5 }; + + WithTorrentClient(tags: new HashSet { 1 }); + WithTorrentClient(tags: new HashSet { 2 }); + WithTorrentClient(); + WithTorrentClient(); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, tags: seriesTags); + + clients.Should().HaveCount(2); + clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 3, 4 }); + } + + [Test] + public void should_throw_when_all_clients_have_non_matching_tags() + { + var seriesTags = new HashSet { 5 }; + + WithTorrentClient(tags: new HashSet { 1 }); + WithTorrentClient(tags: new HashSet { 2 }); + + Assert.Throws(() => Subject.GetDownloadClients(DownloadProtocol.Torrent, tags: seriesTags)); + } + + [Test] + public void should_return_indexer_specific_client_when_specified() + { + WithTorrentClient(); + WithTorrentClient(); + WithTorrentClient(); + WithTorrentIndexer(2); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, indexerId: 1); + + clients.Should().HaveCount(1); + clients.First().Definition.Id.Should().Be(2); + } + + [Test] + public void should_throw_when_indexer_client_does_not_exist() + { + WithTorrentClient(); + WithTorrentClient(); + WithTorrentIndexer(5); + + Assert.Throws(() => Subject.GetDownloadClients(DownloadProtocol.Torrent, indexerId: 1)); + } + + [Test] + public void should_filter_blocked_clients_when_requested() + { + WithTorrentClient(); + WithTorrentClient(); + WithTorrentClient(); + + GivenBlockedClient(2); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, filterBlockedClients: true); + + clients.Should().HaveCount(2); + clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 1, 3 }); + } + + [Test] + public void should_throw_when_all_clients_blocked_and_filter_enabled() + { + WithTorrentClient(); + WithTorrentClient(); + + GivenBlockedClient(1); + GivenBlockedClient(2); + + Assert.Throws(() => Subject.GetDownloadClients(DownloadProtocol.Torrent, filterBlockedClients: true)); + } + + [Test] + public void should_return_blocked_clients_when_filter_disabled() + { + WithTorrentClient(); + WithTorrentClient(); + + GivenBlockedClient(1); + GivenBlockedClient(2); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, filterBlockedClients: false); + + clients.Should().HaveCount(2); + clients.Select(c => c.Definition.Id).Should().BeEquivalentTo(new[] { 1, 2 }); + } + + [Test] + public void should_throw_when_indexer_client_is_blocked_and_filter_enabled() + { + WithTorrentClient(); + WithTorrentClient(); + WithTorrentIndexer(2); + + GivenBlockedClient(2); + + Assert.Throws(() => Subject.GetDownloadClients(DownloadProtocol.Torrent, indexerId: 1, filterBlockedClients: true)); + } + + [Test] + public void should_combine_tags_and_priority_filtering() + { + var seriesTags = new HashSet { 1 }; + + WithTorrentClient(priority: 1, tags: new HashSet { 1 }); + WithTorrentClient(priority: 0, tags: new HashSet { 1 }); + WithTorrentClient(priority: 0, tags: new HashSet { 2 }); + WithTorrentClient(priority: 2); + + var clients = Subject.GetDownloadClients(DownloadProtocol.Torrent, tags: seriesTags); + + clients.Should().HaveCount(2); + var clientIds = clients.Select(c => c.Definition.Id).ToArray(); + + clientIds[0].Should().Be(2); + clientIds[1].Should().Be(1); + } + } +} diff --git a/src/NzbDrone.Core.Test/Indexers/IndexerFactory/ResolveIndexerFixture.cs b/src/NzbDrone.Core.Test/Indexers/IndexerFactory/ResolveIndexerFixture.cs new file mode 100644 index 000000000..b3260d93a --- /dev/null +++ b/src/NzbDrone.Core.Test/Indexers/IndexerFactory/ResolveIndexerFixture.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Indexer +{ + [TestFixture] + public class ResolveIndexerFixture : CoreTest + { + private List _indexers; + + [SetUp] + public void SetUp() + { + _indexers = Builder.CreateListOfSize(3) + .Build() + .ToList(); + + Mocker.GetMock() + .Setup(v => v.All()) + .Returns(_indexers); + } + + [Test] + public void should_throw_if_indexer_with_id_cannot_be_found() + { + Assert.Throws(() => Subject.ResolveIndexer(10, null)); + } + + [Test] + public void should_throw_if_indexer_with_name_cannot_be_found() + { + Assert.Throws(() => Subject.ResolveIndexer(null, "Not a Real Client")); + } + + [Test] + public void should_throw_if_indexer_with_id_does_not_match_indexer_with_name() + { + Assert.Throws(() => Subject.ResolveIndexer(_indexers[0].Id, _indexers[1].Name)); + } + + [Test] + public void should_return_indexer_when_only_id_is_provided() + { + var result = Subject.ResolveIndexer(_indexers[0].Id, null); + + result.Should().NotBeNull(); + result.Should().Be(_indexers[0]); + } + + [Test] + public void should_return_indexer_when_only_name_is_provided() + { + var result = Subject.ResolveIndexer(null, _indexers[0].Name); + + result.Should().NotBeNull(); + result.Should().Be(_indexers[0]); + } + + [Test] + public void should_return_indexer_when_id_and_name_provided_for_the_same_indexer() + { + var result = Subject.ResolveIndexer(_indexers[0].Id, _indexers[0].Name); + + result.Should().NotBeNull(); + result.Should().Be(_indexers[0]); + } + + [Test] + public void should_return_null_if_both_id_and_name_are_not_provided() + { + var result = Subject.ResolveIndexer(null, null); + + result.Should().BeNull(); + } + } +} diff --git a/src/NzbDrone.Core/Download/DownloadClientFactory.cs b/src/NzbDrone.Core/Download/DownloadClientFactory.cs index 9b11e05ac..1d98012c4 100644 --- a/src/NzbDrone.Core/Download/DownloadClientFactory.cs +++ b/src/NzbDrone.Core/Download/DownloadClientFactory.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using FluentValidation.Results; using NLog; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.ThingiProvider; @@ -11,6 +12,7 @@ namespace NzbDrone.Core.Download public interface IDownloadClientFactory : IProviderFactory { List DownloadHandlingEnabled(bool filterBlockedClients = true); + DownloadClientDefinition ResolveDownloadClient(int? id, string name); } public class DownloadClientFactory : ProviderFactory, IDownloadClientFactory @@ -54,6 +56,42 @@ namespace NzbDrone.Core.Download return enabledClients.ToList(); } + public DownloadClientDefinition ResolveDownloadClient(int? id, string name) + { + var all = All(); + var clientByName = name.IsNullOrWhiteSpace() ? null : all.FirstOrDefault(c => c.Name.EqualsIgnoreCase(name)); + var clientById = id.HasValue ? all.FirstOrDefault(c => c.Id == id.Value) : null; + + if (id.HasValue && clientById == null) + { + throw new ResolveDownloadClientException("Download client with ID '{0}' could not be found", id.Value); + } + + if (name.IsNotNullOrWhiteSpace() && clientByName == null) + { + throw new ResolveDownloadClientException("Download client with name '{0}' could not be found", name); + } + + if (clientByName == null && clientById == null) + { + return null; + } + + if (clientByName != null && clientById != null && clientByName.Id != clientById.Id) + { + throw new ResolveDownloadClientException("Download client with name '{0}' does not match download client with ID '{1}'", name, id.Value); + } + + var client = clientById ?? clientByName; + + if (!client.Enable) + { + throw new ResolveDownloadClientException("Download client '{0}' ({1}) is not enabled", client.Name, id); + } + + return client; + } + private IEnumerable FilterBlockedClients(IEnumerable clients) { var blockedClients = _downloadClientStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v); diff --git a/src/NzbDrone.Core/Download/DownloadClientProvider.cs b/src/NzbDrone.Core/Download/DownloadClientProvider.cs index 6c74dc9d5..fbc9d8b50 100644 --- a/src/NzbDrone.Core/Download/DownloadClientProvider.cs +++ b/src/NzbDrone.Core/Download/DownloadClientProvider.cs @@ -121,6 +121,42 @@ namespace NzbDrone.Core.Download _lastUsedDownloadClient.Set(downloadProtocol.ToString(), downloadClientId); } + public DownloadClientDefinition ResolveDownloadClient(int? downloadClientId, string downloadClientName) + { + var all = _downloadClientFactory.All(); + var clientByName = downloadClientName.IsNullOrWhiteSpace() ? null : all.FirstOrDefault(c => c.Name.EqualsIgnoreCase(downloadClientName)); + var clientById = downloadClientId.HasValue ? all.FirstOrDefault(c => c.Id == downloadClientId.Value) : null; + + if (downloadClientId.HasValue && clientById == null) + { + throw new ResolveDownloadClientException("Download client with ID '{0}' could not be found", downloadClientId.Value); + } + + if (downloadClientName.IsNotNullOrWhiteSpace() && clientByName == null) + { + throw new ResolveDownloadClientException("Download client with name '{0}' could not be found", downloadClientName); + } + + if (clientByName == null && clientById == null) + { + return null; + } + + if (clientByName != null && clientById != null && clientByName.Id != clientById.Id) + { + throw new ResolveDownloadClientException("Download client with name '{0}' does not match download client with ID '{1}'", downloadClientName, downloadClientId.Value); + } + + var client = clientById ?? clientByName; + + if (!client.Enable) + { + throw new ResolveDownloadClientException("Download client '{0}' ({1}) is not enabled", client.Name, downloadClientId); + } + + return clientById ?? clientByName; + } + private IEnumerable FilterBlockedDownloadClients(IEnumerable clients) { var blockedClients = _downloadClientStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v); diff --git a/src/NzbDrone.Core/Download/ResolveDownloadClientException.cs b/src/NzbDrone.Core/Download/ResolveDownloadClientException.cs new file mode 100644 index 000000000..eb987f0e6 --- /dev/null +++ b/src/NzbDrone.Core/Download/ResolveDownloadClientException.cs @@ -0,0 +1,12 @@ +using System.Net; +using NzbDrone.Core.Exceptions; + +namespace NzbDrone.Core.Download; + +public class ResolveDownloadClientException : NzbDroneClientException +{ + public ResolveDownloadClientException(string message, params object[] args) + : base(HttpStatusCode.BadRequest, message, args) + { + } +} diff --git a/src/NzbDrone.Core/Indexers/IndexerFactory.cs b/src/NzbDrone.Core/Indexers/IndexerFactory.cs index 620d36ed2..6fca9e540 100644 --- a/src/NzbDrone.Core/Indexers/IndexerFactory.cs +++ b/src/NzbDrone.Core/Indexers/IndexerFactory.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using FluentValidation.Results; using NLog; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.ThingiProvider; @@ -14,6 +15,7 @@ namespace NzbDrone.Core.Indexers List AutomaticSearchEnabled(bool filterBlockedIndexers = true); List InteractiveSearchEnabled(bool filterBlockedIndexers = true); IndexerDefinition FindByName(string name); + IndexerDefinition ResolveIndexer(int? id, string name); } public class IndexerFactory : ProviderFactory, IIndexerFactory @@ -90,6 +92,35 @@ namespace NzbDrone.Core.Indexers return _indexerRepository.FindByName(name); } + public IndexerDefinition ResolveIndexer(int? id, string name) + { + var all = All(); + var clientByName = name.IsNullOrWhiteSpace() ? null : all.FirstOrDefault(c => c.Name.EqualsIgnoreCase(name)); + var clientById = id.HasValue ? all.FirstOrDefault(c => c.Id == id.Value) : null; + + if (id.HasValue && clientById == null) + { + throw new ResolveIndexerException("Indexer with ID '{0}' could not be found", id.Value); + } + + if (name.IsNotNullOrWhiteSpace() && clientByName == null) + { + throw new ResolveIndexerException("Indexer with name '{0}' could not be found", name); + } + + if (clientByName == null && clientById == null) + { + return null; + } + + if (clientByName != null && clientById != null && clientByName.Id != clientById.Id) + { + throw new ResolveIndexerException("Indexer with name '{0}' does not match Indexerwith ID '{1}'", name, id.Value); + } + + return clientById ?? clientByName; + } + private IEnumerable FilterBlockedIndexers(IEnumerable indexers) { var blockedIndexers = _indexerStatusService.GetBlockedProviders().ToDictionary(v => v.ProviderId, v => v); diff --git a/src/NzbDrone.Core/Indexers/ResolveDownloadClientException.cs b/src/NzbDrone.Core/Indexers/ResolveDownloadClientException.cs new file mode 100644 index 000000000..c547c51aa --- /dev/null +++ b/src/NzbDrone.Core/Indexers/ResolveDownloadClientException.cs @@ -0,0 +1,12 @@ +using System.Net; +using NzbDrone.Core.Exceptions; + +namespace NzbDrone.Core.Indexers; + +public class ResolveIndexerException : NzbDroneClientException +{ + public ResolveIndexerException(string message, params object[] args) + : base(HttpStatusCode.BadRequest, message, args) + { + } +} diff --git a/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs b/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs index b5ad89cc7..c3192aea5 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleasePushController.cs @@ -5,7 +5,6 @@ using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; using NLog; using NzbDrone.Common.Extensions; -using NzbDrone.Core.Datastore; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; @@ -84,59 +83,35 @@ namespace Sonarr.Api.V3.Indexers private void ResolveIndexer(ReleaseInfo release) { - if (release.IndexerId == 0 && release.Indexer.IsNotNullOrWhiteSpace()) - { - var indexer = _indexerFactory.All().FirstOrDefault(v => v.Name.EqualsIgnoreCase(release.Indexer)); + var indexer = _indexerFactory.ResolveIndexer(release.IndexerId, release.Indexer); - if (indexer != null) - { - release.IndexerId = indexer.Id; - _logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer); - } - else - { - _logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.Indexer); - } - } - else if (release.IndexerId != 0 && release.Indexer.IsNullOrWhiteSpace()) + if (indexer == null) { - try - { - var indexer = _indexerFactory.Get(release.IndexerId); - release.Indexer = indexer.Name; - _logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer); - } - catch (ModelNotFoundException) - { - _logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.IndexerId); - release.IndexerId = 0; - } + _logger.Debug("Push Release {0} not associated with an indexer.", release.Title); } else { - _logger.Debug("Push Release {0} not associated with an indexer.", release.Title); + _logger.Debug("Push Release {0} associated with indexer '{1} ({2})", release.Title, indexer.Name, indexer.Id); + + release.IndexerId = indexer.Id; + release.Indexer = indexer.Name; } } private int? ResolveDownloadClientId(ReleaseResource release) { - var downloadClientId = release.DownloadClientId.GetValueOrDefault(); + var downloadClient = _downloadClientFactory.ResolveDownloadClient(release.DownloadClientId, release.DownloadClient); - if (downloadClientId == 0 && release.DownloadClient.IsNotNullOrWhiteSpace()) + if (downloadClient == null) { - var downloadClient = _downloadClientFactory.All().FirstOrDefault(v => v.Name.EqualsIgnoreCase(release.DownloadClient)); - - if (downloadClient != null) - { - _logger.Debug("Push Release {0} associated with download client {1} - {2}.", release.Title, downloadClientId, release.DownloadClient); - - return downloadClient.Id; - } - - _logger.Debug("Push Release {0} not associated with known download client {1}.", release.Title, release.DownloadClient); + _logger.Debug("Push Release {0} not associated with a download client.", release.Title); + } + else + { + _logger.Debug("Push Release {0} associated with download client '{1} ({2})", release.Title, downloadClient.Name, downloadClient.Id); } - return release.DownloadClientId; + return downloadClient?.Id; } } } diff --git a/src/Sonarr.Api.V5/Release/ReleasePushController.cs b/src/Sonarr.Api.V5/Release/ReleasePushController.cs index 5a8adc510..a23e99422 100644 --- a/src/Sonarr.Api.V5/Release/ReleasePushController.cs +++ b/src/Sonarr.Api.V5/Release/ReleasePushController.cs @@ -3,7 +3,6 @@ using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; using NLog; using NzbDrone.Common.Extensions; -using NzbDrone.Core.Datastore; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Indexers; @@ -86,58 +85,34 @@ public class ReleasePushController : RestController private void ResolveIndexer(ReleaseInfo release) { - if (release.IndexerId == 0 && release.Indexer.IsNotNullOrWhiteSpace()) - { - var indexer = _indexerFactory.All().FirstOrDefault(v => v.Name.EqualsIgnoreCase(release.Indexer)); + var indexer = _indexerFactory.ResolveIndexer(release.IndexerId, release.Indexer); - if (indexer != null) - { - release.IndexerId = indexer.Id; - _logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer); - } - else - { - _logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.Indexer); - } - } - else if (release.IndexerId != 0 && release.Indexer.IsNullOrWhiteSpace()) + if (indexer == null) { - try - { - var indexer = _indexerFactory.Get(release.IndexerId); - release.Indexer = indexer.Name; - _logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer); - } - catch (ModelNotFoundException) - { - _logger.Debug("Push Release {0} not associated with known indexer {1}.", release.Title, release.IndexerId); - release.IndexerId = 0; - } + _logger.Debug("Push Release {0} not associated with an indexer.", release.Title); } else { - _logger.Debug("Push Release {0} not associated with an indexer.", release.Title); + _logger.Debug("Push Release {0} associated with indexer '{1} ({2})", release.Title, indexer.Name, indexer.Id); + + release.IndexerId = indexer.Id; + release.Indexer = indexer.Name; } } private int? ResolveDownloadClientId(ReleasePushResource release) { - var downloadClientId = release.DownloadClientId.GetValueOrDefault(); + var downloadClient = _downloadClientFactory.ResolveDownloadClient(release.DownloadClientId, release.DownloadClientName); - if (downloadClientId == 0 && release.DownloadClientName.IsNotNullOrWhiteSpace()) + if (downloadClient == null) { - var downloadClient = _downloadClientFactory.All().FirstOrDefault(v => v.Name.EqualsIgnoreCase(release.DownloadClientName)); - - if (downloadClient != null) - { - _logger.Debug("Push Release {0} associated with download client {1} - {2}.", release.Title, downloadClientId, release.DownloadClientName); - - return downloadClient.Id; - } - - _logger.Debug("Push Release {0} not associated with known download client {1}.", release.Title, release.DownloadClientName); + _logger.Debug("Push Release {0} not associated with a download client.", release.Title); + } + else + { + _logger.Debug("Push Release {0} associated with download client '{1} ({2})", release.Title, downloadClient.Name, downloadClient.Id); } - return release.DownloadClientId; + return downloadClient?.Id; } }