From a6b1a1fc0d502a45cfc006cf11357a4a6825714c Mon Sep 17 00:00:00 2001 From: Igal Tabachnik Date: Tue, 3 May 2016 10:58:28 +0300 Subject: [PATCH] New: Vuze torrent client support --- .../TransmissionTests/TransmissionFixture.cs | 211 +----------- .../TransmissionFixtureBase.cs | 197 +++++++++++ .../VuzeTests/VuzeFixture.cs | 315 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 2 + .../Clients/Transmission/Transmission.cs | 234 +------------ .../Clients/Transmission/TransmissionBase.cs | 250 ++++++++++++++ .../Clients/Transmission/TransmissionProxy.cs | 15 +- .../Download/Clients/Vuze/Vuze.cs | 60 ++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 2 + 9 files changed, 863 insertions(+), 423 deletions(-) create mode 100644 src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixtureBase.cs create mode 100644 src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Transmission/TransmissionBase.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs index fa49d4cdb..acbdc59b2 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixture.cs @@ -1,201 +1,17 @@ using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; using FluentAssertions; using Moq; using NUnit.Framework; -using NzbDrone.Common.Http; -using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Download; using NzbDrone.Core.Download.Clients.Transmission; namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests { [TestFixture] - public class TransmissionFixture : DownloadClientFixtureBase + public class TransmissionFixture : TransmissionFixtureBase { - protected TransmissionSettings _settings; - protected TransmissionTorrent _queued; - protected TransmissionTorrent _downloading; - protected TransmissionTorrent _failed; - protected TransmissionTorrent _completed; - protected TransmissionTorrent _magnet; - protected Dictionary _transmissionConfigItems; - - [SetUp] - public void Setup() - { - _settings = new TransmissionSettings - { - Host = "127.0.0.1", - Port = 2222, - Username = "admin", - Password = "pass" - }; - - Subject.Definition = new DownloadClientDefinition(); - Subject.Definition.Settings = _settings; - - _queued = new TransmissionTorrent - { - HashString = "HASH", - IsFinished = false, - Status = TransmissionTorrentStatus.Queued, - Name = _title, - TotalSize = 1000, - LeftUntilDone = 1000, - DownloadDir = "somepath" - }; - - _downloading = new TransmissionTorrent - { - HashString = "HASH", - IsFinished = false, - Status = TransmissionTorrentStatus.Downloading, - Name = _title, - TotalSize = 1000, - LeftUntilDone = 100, - DownloadDir = "somepath" - }; - - _failed = new TransmissionTorrent - { - HashString = "HASH", - IsFinished = false, - Status = TransmissionTorrentStatus.Stopped, - Name = _title, - TotalSize = 1000, - LeftUntilDone = 100, - ErrorString = "Error", - DownloadDir = "somepath" - }; - - _completed = new TransmissionTorrent - { - HashString = "HASH", - IsFinished = true, - Status = TransmissionTorrentStatus.Stopped, - Name = _title, - TotalSize = 1000, - LeftUntilDone = 0, - DownloadDir = "somepath" - }; - - _magnet = new TransmissionTorrent - { - HashString = "HASH", - IsFinished = false, - Status = TransmissionTorrentStatus.Downloading, - Name = _title, - TotalSize = 0, - LeftUntilDone = 100, - DownloadDir = "somepath" - }; - - Mocker.GetMock() - .Setup(s => s.GetHashFromTorrentFile(It.IsAny())) - .Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951"); - - Mocker.GetMock() - .Setup(s => s.Get(It.IsAny())) - .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[0])); - - _transmissionConfigItems = new Dictionary(); - - _transmissionConfigItems.Add("download-dir", @"C:/Downloads/Finished/transmission"); - _transmissionConfigItems.Add("incomplete-dir", null); - _transmissionConfigItems.Add("incomplete-dir-enabled", false); - - Mocker.GetMock() - .Setup(v => v.GetConfig(It.IsAny())) - .Returns(_transmissionConfigItems); - - } - - protected void GivenTvCategory() - { - _settings.TvCategory = "sonarr"; - } - - protected void GivenTvDirectory() - { - _settings.TvDirectory = @"C:/Downloads/Finished/sonarr"; - } - - protected void GivenFailedDownload() - { - Mocker.GetMock() - .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny(), It.IsAny())) - .Throws(); - } - - protected void GivenSuccessfulDownload() - { - Mocker.GetMock() - .Setup(s => s.Get(It.IsAny())) - .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[1000])); - - Mocker.GetMock() - .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback(PrepareClientToReturnQueuedItem); - - Mocker.GetMock() - .Setup(s => s.AddTorrentFromData(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback(PrepareClientToReturnQueuedItem); - } - - protected virtual void GivenTorrents(List torrents) - { - if (torrents == null) - { - torrents = new List(); - } - - Mocker.GetMock() - .Setup(s => s.GetTorrents(It.IsAny())) - .Returns(torrents); - } - - protected void PrepareClientToReturnQueuedItem() - { - GivenTorrents(new List - { - _queued - }); - } - - protected void PrepareClientToReturnDownloadingItem() - { - GivenTorrents(new List - { - _downloading - }); - } - - protected void PrepareClientToReturnFailedItem() - { - GivenTorrents(new List - { - _failed - }); - } - - protected void PrepareClientToReturnCompletedItem() - { - GivenTorrents(new List - { - _completed - }); - } - - protected void PrepareClientToReturnMagnetItem() - { - GivenTorrents(new List - { - _magnet - }); - } - [Test] public void queued_item_should_have_required_properties() { @@ -260,7 +76,7 @@ public void Download_with_TvDirectory_should_force_directory() id.Should().NotBeNullOrEmpty(); Mocker.GetMock() - .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/sonarr", It.IsAny()), Times.Once()); + .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/sonarr", It.IsAny()), Times.Once()); } [Test] @@ -276,7 +92,7 @@ public void Download_with_category_should_force_directory() id.Should().NotBeNullOrEmpty(); Mocker.GetMock() - .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny()), Times.Once()); + .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny()), Times.Once()); } [Test] @@ -294,7 +110,7 @@ public void Download_with_category_should_not_have_double_slashes() id.Should().NotBeNullOrEmpty(); Mocker.GetMock() - .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny()), Times.Once()); + .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny()), Times.Once()); } [Test] @@ -309,7 +125,7 @@ public void Download_without_TvDirectory_and_Category_should_use_default() id.Should().NotBeNullOrEmpty(); Mocker.GetMock() - .Verify(v => v.AddTorrentFromData(It.IsAny(), null, It.IsAny()), Times.Once()); + .Verify(v => v.AddTorrentFromData(It.IsAny(), null, It.IsAny()), Times.Once()); } [TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")] @@ -366,7 +182,7 @@ public void GetItems_should_return_downloading_item_as_downloadItemStatus(Transm public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) { _completed.Status = apiStatus; - + PrepareClientToReturnCompletedItem(); var item = Subject.GetItems().Single(); @@ -392,7 +208,7 @@ public void should_exclude_items_not_in_category() _downloading.DownloadDir = @"C:/Downloads/Finished/transmission/sonarr"; - GivenTorrents(new List + GivenTorrents(new List { _downloading, _queued @@ -404,13 +220,14 @@ public void should_exclude_items_not_in_category() items.First().Status.Should().Be(DownloadItemStatus.Downloading); } + [Test] public void should_exclude_items_not_in_TvDirectory() { GivenTvDirectory(); _downloading.DownloadDir = @"C:/Downloads/Finished/sonarr/subdir"; - GivenTorrents(new List + GivenTorrents(new List { _downloading, _queued @@ -429,7 +246,7 @@ public void should_fix_forward_slashes() _downloading.DownloadDir = @"C:/Downloads/Finished/transmission"; - GivenTorrents(new List + GivenTorrents(new List { _downloading }); @@ -444,13 +261,13 @@ public void should_fix_forward_slashes() [TestCase("2.84+ ()")] [TestCase("2.84 (other info)")] [TestCase("2.84 (2.84)")] - public void should_version_should_only_check_version_number(string version) + public void should_only_check_version_number(string version) { Mocker.GetMock() - .Setup(s => s.GetVersion(It.IsAny())) + .Setup(s => s.GetClientVersion(It.IsAny())) .Returns(version); - Subject.Test(); + Subject.Test().IsValid.Should().BeTrue(); } [TestCase(-1)] // Infinite/Unknown diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixtureBase.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixtureBase.cs new file mode 100644 index 000000000..d46f9a30e --- /dev/null +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/TransmissionTests/TransmissionFixtureBase.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients.Transmission; +using NzbDrone.Core.MediaFiles.TorrentInfo; + +namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests +{ + public abstract class TransmissionFixtureBase : DownloadClientFixtureBase + where TClient : class, IDownloadClient + { + protected TransmissionSettings _settings; + protected TransmissionTorrent _queued; + protected TransmissionTorrent _downloading; + protected TransmissionTorrent _failed; + protected TransmissionTorrent _completed; + protected TransmissionTorrent _magnet; + protected Dictionary _transmissionConfigItems; + + [SetUp] + public void Setup() + { + _settings = new TransmissionSettings + { + Host = "127.0.0.1", + Port = 2222, + Username = "admin", + Password = "pass" + }; + + Subject.Definition = new DownloadClientDefinition(); + Subject.Definition.Settings = _settings; + + _queued = new TransmissionTorrent + { + HashString = "HASH", + IsFinished = false, + Status = TransmissionTorrentStatus.Queued, + Name = _title, + TotalSize = 1000, + LeftUntilDone = 1000, + DownloadDir = "somepath" + }; + + _downloading = new TransmissionTorrent + { + HashString = "HASH", + IsFinished = false, + Status = TransmissionTorrentStatus.Downloading, + Name = _title, + TotalSize = 1000, + LeftUntilDone = 100, + DownloadDir = "somepath" + }; + + _failed = new TransmissionTorrent + { + HashString = "HASH", + IsFinished = false, + Status = TransmissionTorrentStatus.Stopped, + Name = _title, + TotalSize = 1000, + LeftUntilDone = 100, + ErrorString = "Error", + DownloadDir = "somepath" + }; + + _completed = new TransmissionTorrent + { + HashString = "HASH", + IsFinished = true, + Status = TransmissionTorrentStatus.Stopped, + Name = _title, + TotalSize = 1000, + LeftUntilDone = 0, + DownloadDir = "somepath" + }; + + _magnet = new TransmissionTorrent + { + HashString = "HASH", + IsFinished = false, + Status = TransmissionTorrentStatus.Downloading, + Name = _title, + TotalSize = 0, + LeftUntilDone = 100, + DownloadDir = "somepath" + }; + + Mocker.GetMock() + .Setup(s => s.GetHashFromTorrentFile(It.IsAny())) + .Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951"); + + Mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[0])); + + _transmissionConfigItems = new Dictionary(); + + _transmissionConfigItems.Add("download-dir", @"C:/Downloads/Finished/transmission"); + _transmissionConfigItems.Add("incomplete-dir", null); + _transmissionConfigItems.Add("incomplete-dir-enabled", false); + + Mocker.GetMock() + .Setup(v => v.GetConfig(It.IsAny())) + .Returns(_transmissionConfigItems); + + } + + protected void GivenTvCategory() + { + _settings.TvCategory = "sonarr"; + } + + protected void GivenTvDirectory() + { + _settings.TvDirectory = @"C:/Downloads/Finished/sonarr"; + } + + protected void GivenFailedDownload() + { + Mocker.GetMock() + .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(); + } + + protected void GivenSuccessfulDownload() + { + Mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[1000])); + + Mocker.GetMock() + .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(PrepareClientToReturnQueuedItem); + + Mocker.GetMock() + .Setup(s => s.AddTorrentFromData(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(PrepareClientToReturnQueuedItem); + } + + protected virtual void GivenTorrents(List torrents) + { + if (torrents == null) + { + torrents = new List(); + } + + Mocker.GetMock() + .Setup(s => s.GetTorrents(It.IsAny())) + .Returns(torrents); + } + + protected void PrepareClientToReturnQueuedItem() + { + GivenTorrents(new List + { + _queued + }); + } + + protected void PrepareClientToReturnDownloadingItem() + { + GivenTorrents(new List + { + _downloading + }); + } + + protected void PrepareClientToReturnFailedItem() + { + GivenTorrents(new List + { + _failed + }); + } + + protected void PrepareClientToReturnCompletedItem() + { + GivenTorrents(new List + { + _completed + }); + } + + protected void PrepareClientToReturnMagnetItem() + { + GivenTorrents(new List + { + _magnet + }); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs new file mode 100644 index 000000000..00278c811 --- /dev/null +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/VuzeTests/VuzeFixture.cs @@ -0,0 +1,315 @@ +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients.Transmission; +using NzbDrone.Core.Download.Clients.Vuze; +using NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests; + +namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests +{ + [TestFixture] + public class VuzeFixture : TransmissionFixtureBase + { + [Test] + public void queued_item_should_have_required_properties() + { + PrepareClientToReturnQueuedItem(); + var item = Subject.GetItems().Single(); + VerifyQueued(item); + } + + [Test] + public void downloading_item_should_have_required_properties() + { + PrepareClientToReturnDownloadingItem(); + var item = Subject.GetItems().Single(); + VerifyDownloading(item); + } + + [Test] + public void failed_item_should_have_required_properties() + { + PrepareClientToReturnFailedItem(); + var item = Subject.GetItems().Single(); + VerifyWarning(item); + } + + [Test] + public void completed_download_should_have_required_properties() + { + PrepareClientToReturnCompletedItem(); + var item = Subject.GetItems().Single(); + VerifyCompleted(item); + } + + [Test] + public void magnet_download_should_not_return_the_item() + { + PrepareClientToReturnMagnetItem(); + Subject.GetItems().Count().Should().Be(0); + } + + [Test] + public void Download_should_return_unique_id() + { + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + } + + [Test] + public void Download_with_TvDirectory_should_force_directory() + { + GivenTvDirectory(); + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + + Mocker.GetMock() + .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/sonarr", It.IsAny()), Times.Once()); + } + + [Test] + public void Download_with_category_should_force_directory() + { + GivenTvCategory(); + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + + Mocker.GetMock() + .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny()), Times.Once()); + } + + [Test] + public void Download_with_category_should_not_have_double_slashes() + { + GivenTvCategory(); + GivenSuccessfulDownload(); + + _transmissionConfigItems["download-dir"] += "/"; + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + + Mocker.GetMock() + .Verify(v => v.AddTorrentFromData(It.IsAny(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny()), Times.Once()); + } + + [Test] + public void Download_without_TvDirectory_and_Category_should_use_default() + { + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + + Mocker.GetMock() + .Verify(v => v.AddTorrentFromData(It.IsAny(), null, It.IsAny()), Times.Once()); + } + + [TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")] + public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash) + { + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + remoteEpisode.Release.DownloadUrl = magnetUrl; + + var id = Subject.Download(remoteEpisode); + + id.Should().Be(expectedHash); + } + + [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Downloading)] + [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading)] + [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading)] + [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] + [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] + [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed)] + [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] + public void GetItems_should_return_queued_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) + { + _queued.Status = apiStatus; + + PrepareClientToReturnQueuedItem(); + + var item = Subject.GetItems().Single(); + + item.Status.Should().Be(expectedItemStatus); + } + + [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued)] + [TestCase(TransmissionTorrentStatus.Downloading, DownloadItemStatus.Downloading)] + [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed)] + public void GetItems_should_return_downloading_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) + { + _downloading.Status = apiStatus; + + PrepareClientToReturnDownloadingItem(); + + var item = Subject.GetItems().Single(); + + item.Status.Should().Be(expectedItemStatus); + } + + [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)] + [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)] + [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, true)] + [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, true)] + [TestCase(TransmissionTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)] + [TestCase(TransmissionTorrentStatus.Seeding, DownloadItemStatus.Completed, true)] + public void GetItems_should_return_completed_item_as_downloadItemStatus(TransmissionTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) + { + _completed.Status = apiStatus; + + PrepareClientToReturnCompletedItem(); + + var item = Subject.GetItems().Single(); + + item.Status.Should().Be(expectedItemStatus); + item.IsReadOnly.Should().Be(expectedReadOnly); + } + + [Test] + public void should_return_status_with_outputdirs() + { + var result = Subject.GetStatus(); + + result.IsLocalhost.Should().BeTrue(); + result.OutputRootFolders.Should().NotBeNull(); + result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\transmission"); + } + + [Test] + public void should_exclude_items_not_in_category() + { + GivenTvCategory(); + + _downloading.DownloadDir = @"C:/Downloads/Finished/transmission/sonarr"; + + GivenTorrents(new List + { + _downloading, + _queued + }); + + var items = Subject.GetItems().ToList(); + + items.Count.Should().Be(1); + items.First().Status.Should().Be(DownloadItemStatus.Downloading); + } + + [Test] + public void should_exclude_items_not_in_TvDirectory() + { + GivenTvDirectory(); + + _downloading.DownloadDir = @"C:/Downloads/Finished/sonarr/subdir"; + + GivenTorrents(new List + { + _downloading, + _queued + }); + + var items = Subject.GetItems().ToList(); + + items.Count.Should().Be(1); + items.First().Status.Should().Be(DownloadItemStatus.Downloading); + } + + [Test] + public void should_fix_forward_slashes() + { + WindowsOnly(); + + _downloading.DownloadDir = @"C:/Downloads/Finished/transmission/" + _title; + + GivenTorrents(new List + { + _downloading + }); + + var items = Subject.GetItems().ToList(); + + items.Should().HaveCount(1); + items.First().OutputPath.Should().Be(@"C:\Downloads\Finished\transmission\" + _title); + } + + [TestCase(-1)] // Infinite/Unknown + [TestCase(-2)] // Magnet Downloading + public void should_ignore_negative_eta(int eta) + { + _completed.Eta = eta; + + PrepareClientToReturnCompletedItem(); + var item = Subject.GetItems().Single(); + item.RemainingTime.Should().NotHaveValue(); + } + + [TestCase("14")] + [TestCase("15")] + [TestCase("20")] + public void should_only_check_protocol_version_number(string version) + { + Mocker.GetMock() + .Setup(s => s.GetProtocolVersion(It.IsAny())) + .Returns(version); + + Subject.Test().IsValid.Should().BeTrue(); + } + + [TestCase("")] + [TestCase("10")] + [TestCase("foo")] + public void should_fail_with_unsupported_protocol_version(string version) + { + Mocker.GetMock() + .Setup(s => s.GetProtocolVersion(It.IsAny())) + .Returns(version); + + Subject.Test().IsValid.Should().BeFalse(); + } + + [Test] + public void should_have_correct_output_directory() + { + WindowsOnly(); + + _downloading.DownloadDir = @"C:/Downloads/" + _title; + + GivenTorrents(new List + { + _downloading + }); + + var items = Subject.GetItems().ToList(); + + items.Should().HaveCount(1); + items.First().OutputPath.Should().Be(@"C:\Downloads\" + _title); + } + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 2e61cbcdc..b0760522d 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -171,7 +171,9 @@ + + diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs index 3c4b2b1d1..4ea717d49 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/Transmission.cs @@ -1,25 +1,17 @@ using System; -using System.Linq; -using System.Collections.Generic; using System.Text.RegularExpressions; using NzbDrone.Common.Disk; -using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NLog; using FluentValidation.Results; using NzbDrone.Core.MediaFiles.TorrentInfo; -using NzbDrone.Core.Validation; -using System.Net; -using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; namespace NzbDrone.Core.Download.Clients.Transmission { - public class Transmission : TorrentClientBase + public class Transmission : TransmissionBase { - private readonly ITransmissionProxy _proxy; - public Transmission(ITransmissionProxy proxy, ITorrentFileInfoReader torrentFileInfoReader, IHttpClient httpClient, @@ -27,59 +19,25 @@ public Transmission(ITransmissionProxy proxy, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, Logger logger) - : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) { - _proxy = proxy; } - - protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink) + protected override ValidationFailure ValidateVersion() { - _proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings); + var versionString = _proxy.GetClientVersion(Settings); - var isRecentEpisode = remoteEpisode.IsRecentEpisode(); + _logger.Debug("Transmission version information: {0}", versionString); - if (isRecentEpisode && Settings.RecentTvPriority == (int)TransmissionPriority.First || - !isRecentEpisode && Settings.OlderTvPriority == (int)TransmissionPriority.First) + var versionResult = Regex.Match(versionString, @"(? GetItems() - { - List torrents; - - try - { - torrents = _proxy.GetTorrents(Settings); - } - catch (DownloadClientException ex) - { - _logger.Error(ex, ex.Message); - return Enumerable.Empty(); - } - - var items = new List(); - - foreach (var torrent in torrents) - { - // If totalsize == 0 the torrent is a magnet downloading metadata - if (torrent.TotalSize == 0) - continue; - - var outputPath = new OsPath(torrent.DownloadDir); - - if (Settings.TvDirectory.IsNotNullOrWhiteSpace()) - { - if (!new OsPath(Settings.TvDirectory).Contains(outputPath)) continue; - } - else if (Settings.TvCategory.IsNotNullOrWhiteSpace()) - { - var directories = outputPath.FullPath.Split('\\', '/'); - if (!directories.Contains(string.Format("{0}", Settings.TvCategory))) continue; - } - - outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath); - - var item = new DownloadClientItem(); - item.DownloadId = torrent.HashString.ToUpper(); - item.Category = Settings.TvCategory; - item.Title = torrent.Name; - - item.DownloadClient = Definition.Name; - - item.OutputPath = outputPath + torrent.Name; - item.TotalSize = torrent.TotalSize; - item.RemainingSize = torrent.LeftUntilDone; - if (torrent.Eta >= 0) - { - item.RemainingTime = TimeSpan.FromSeconds(torrent.Eta); - } - - if (!torrent.ErrorString.IsNullOrWhiteSpace()) - { - item.Status = DownloadItemStatus.Warning; - item.Message = torrent.ErrorString; - } - else if (torrent.Status == TransmissionTorrentStatus.Seeding || torrent.Status == TransmissionTorrentStatus.SeedingWait) - { - item.Status = DownloadItemStatus.Completed; - } - else if (torrent.IsFinished && torrent.Status != TransmissionTorrentStatus.Check && torrent.Status != TransmissionTorrentStatus.CheckWait) - { - item.Status = DownloadItemStatus.Completed; - } - else if (torrent.Status == TransmissionTorrentStatus.Queued) - { - item.Status = DownloadItemStatus.Queued; - } - else - { - item.Status = DownloadItemStatus.Downloading; - } - - item.IsReadOnly = torrent.Status != TransmissionTorrentStatus.Stopped; - - items.Add(item); - } - - return items; - } - - public override void RemoveItem(string downloadId, bool deleteData) - { - _proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings); - } - - public override DownloadClientStatus GetStatus() - { - var config = _proxy.GetConfig(Settings); - var destDir = config.GetValueOrDefault("download-dir") as string; - - if (Settings.TvCategory.IsNotNullOrWhiteSpace()) - { - destDir = string.Format("{0}/.{1}", destDir, Settings.TvCategory); - } - - return new DownloadClientStatus - { - IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost", - OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) } - }; - } - - protected override void Test(List failures) - { - failures.AddIfNotNull(TestConnection()); - if (failures.Any()) return; - failures.AddIfNotNull(TestGetTorrents()); - } - - private ValidationFailure TestConnection() - { - try - { - var versionString = _proxy.GetVersion(Settings); - - _logger.Debug("Transmission version information: {0}", versionString); - - var versionResult = Regex.Match(versionString, @"(? + { + protected readonly ITransmissionProxy _proxy; + + public TransmissionBase(ITransmissionProxy proxy, + ITorrentFileInfoReader torrentFileInfoReader, + IHttpClient httpClient, + IConfigService configService, + IDiskProvider diskProvider, + IRemotePathMappingService remotePathMappingService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + { + _proxy = proxy; + } + + public override IEnumerable GetItems() + { + List torrents; + + try + { + torrents = _proxy.GetTorrents(Settings); + } + catch (DownloadClientException ex) + { + _logger.Error(ex, ex.Message); + return Enumerable.Empty(); + } + + var items = new List(); + + foreach (var torrent in torrents) + { + // If totalsize == 0 the torrent is a magnet downloading metadata + if (torrent.TotalSize == 0) continue; + + var outputPath = new OsPath(torrent.DownloadDir); + + if (Settings.TvDirectory.IsNotNullOrWhiteSpace()) + { + if (!new OsPath(Settings.TvDirectory).Contains(outputPath)) continue; + } + else if (Settings.TvCategory.IsNotNullOrWhiteSpace()) + { + var directories = outputPath.FullPath.Split('\\', '/'); + if (!directories.Contains(Settings.TvCategory)) continue; + } + + outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, outputPath); + + var item = new DownloadClientItem(); + item.DownloadId = torrent.HashString.ToUpper(); + item.Category = Settings.TvCategory; + item.Title = torrent.Name; + + item.DownloadClient = Definition.Name; + + item.OutputPath = GetOutputPath(outputPath, torrent); + item.TotalSize = torrent.TotalSize; + item.RemainingSize = torrent.LeftUntilDone; + if (torrent.Eta >= 0) + { + item.RemainingTime = TimeSpan.FromSeconds(torrent.Eta); + } + + if (!torrent.ErrorString.IsNullOrWhiteSpace()) + { + item.Status = DownloadItemStatus.Warning; + item.Message = torrent.ErrorString; + } + else if (torrent.Status == TransmissionTorrentStatus.Seeding || + torrent.Status == TransmissionTorrentStatus.SeedingWait) + { + item.Status = DownloadItemStatus.Completed; + } + else if (torrent.IsFinished && torrent.Status != TransmissionTorrentStatus.Check && + torrent.Status != TransmissionTorrentStatus.CheckWait) + { + item.Status = DownloadItemStatus.Completed; + } + else if (torrent.Status == TransmissionTorrentStatus.Queued) + { + item.Status = DownloadItemStatus.Queued; + } + else + { + item.Status = DownloadItemStatus.Downloading; + } + + item.IsReadOnly = torrent.Status != TransmissionTorrentStatus.Stopped; + + items.Add(item); + } + + return items; + } + + public override void RemoveItem(string downloadId, bool deleteData) + { + _proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings); + } + + public override DownloadClientStatus GetStatus() + { + var config = _proxy.GetConfig(Settings); + var destDir = config.GetValueOrDefault("download-dir") as string; + + if (Settings.TvCategory.IsNotNullOrWhiteSpace()) + { + destDir = string.Format("{0}/.{1}", destDir, Settings.TvCategory); + } + + return new DownloadClientStatus + { + IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost", + OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) } + }; + } + + protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink) + { + _proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings); + + var isRecentEpisode = remoteEpisode.IsRecentEpisode(); + + if (isRecentEpisode && Settings.RecentTvPriority == (int)TransmissionPriority.First || + !isRecentEpisode && Settings.OlderTvPriority == (int)TransmissionPriority.First) + { + _proxy.MoveTorrentToTopInQueue(hash, Settings); + } + + return hash; + } + + protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent) + { + _proxy.AddTorrentFromData(fileContent, GetDownloadDirectory(), Settings); + + var isRecentEpisode = remoteEpisode.IsRecentEpisode(); + + if (isRecentEpisode && Settings.RecentTvPriority == (int)TransmissionPriority.First || + !isRecentEpisode && Settings.OlderTvPriority == (int)TransmissionPriority.First) + { + _proxy.MoveTorrentToTopInQueue(hash, Settings); + } + + return hash; + } + + protected override void Test(List failures) + { + failures.AddIfNotNull(TestConnection()); + if (failures.Any()) return; + failures.AddIfNotNull(TestGetTorrents()); + } + + protected virtual OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent) + { + return outputPath + torrent.Name; + } + + protected string GetDownloadDirectory() + { + if (Settings.TvDirectory.IsNotNullOrWhiteSpace()) + { + return Settings.TvDirectory; + } + else if (Settings.TvCategory.IsNotNullOrWhiteSpace()) + { + var config = _proxy.GetConfig(Settings); + var destDir = (string)config.GetValueOrDefault("download-dir"); + + return string.Format("{0}/{1}", destDir.TrimEnd('/'), Settings.TvCategory); + } + else + { + return null; + } + } + + protected ValidationFailure TestConnection() + { + try + { + return ValidateVersion(); + } + catch (DownloadClientAuthenticationException ex) + { + _logger.Error(ex, ex.Message); + return new NzbDroneValidationFailure("Username", "Authentication failure") + { + DetailedDescription = string.Format("Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {0} by WhiteList limitations in the {0} configuration.", Name) + }; + } + catch (WebException ex) + { + _logger.Error(ex, ex.Message); + if (ex.Status == WebExceptionStatus.ConnectFailure) + { + return new NzbDroneValidationFailure("Host", "Unable to connect") + { + DetailedDescription = "Please verify the hostname and port." + }; + } + return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + } + catch (Exception ex) + { + _logger.Error(ex, ex.Message); + return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + } + } + + protected abstract ValidationFailure ValidateVersion(); + + private ValidationFailure TestGetTorrents() + { + try + { + _proxy.GetTorrents(Settings); + } + catch (Exception ex) + { + _logger.Error(ex, ex.Message); + return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message); + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs index 777c846fb..a8957ad43 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs @@ -19,7 +19,8 @@ public interface ITransmissionProxy void AddTorrentFromData(byte[] torrentData, string downloadDirectory, TransmissionSettings settings); void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, TransmissionSettings settings); Dictionary GetConfig(TransmissionSettings settings); - string GetVersion(TransmissionSettings settings); + string GetProtocolVersion(TransmissionSettings settings); + string GetClientVersion(TransmissionSettings settings); void RemoveTorrent(string hash, bool removeData, TransmissionSettings settings); void MoveTorrentToTopInQueue(string hashString, TransmissionSettings settings); } @@ -94,9 +95,17 @@ public void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration ProcessRequest("torrent-set", arguments, settings); } - public string GetVersion(TransmissionSettings settings) + public string GetProtocolVersion(TransmissionSettings settings) + { + var config = GetConfig(settings); + + var version = config["rpc-version"]; + + return version.ToString(); + } + + public string GetClientVersion(TransmissionSettings settings) { - // Gets the transmission version. var config = GetConfig(settings); var version = config["version"]; diff --git a/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs b/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs new file mode 100644 index 000000000..79721b120 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Vuze/Vuze.cs @@ -0,0 +1,60 @@ +using System; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Download.Clients.Transmission; +using NzbDrone.Core.MediaFiles.TorrentInfo; +using NzbDrone.Core.RemotePathMappings; + +namespace NzbDrone.Core.Download.Clients.Vuze +{ + public class Vuze : TransmissionBase + { + private const int MINIMUM_SUPPORTED_PROTOCOL_VERSION = 14; + + public Vuze(ITransmissionProxy proxy, + ITorrentFileInfoReader torrentFileInfoReader, + IHttpClient httpClient, + IConfigService configService, + IDiskProvider diskProvider, + IRemotePathMappingService remotePathMappingService, + Logger logger) + : base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + { + } + + protected override OsPath GetOutputPath(OsPath outputPath, TransmissionTorrent torrent) + { + _logger.Debug("Vuze output directory: {0}", outputPath); + + return outputPath; + } + + protected override ValidationFailure ValidateVersion() + { + var versionString = _proxy.GetProtocolVersion(Settings); + + _logger.Debug("Vuze protocol version information: {0}", versionString); + + int version; + if (!int.TryParse(versionString, out version) || version < MINIMUM_SUPPORTED_PROTOCOL_VERSION) + { + { + return new ValidationFailure(string.Empty, "Protocol version not supported, use Vuze 5.0.0.0 or higher with Vuze Web Remote plugin."); + } + } + + return null; + } + + public override string Name + { + get + { + return "Vuze"; + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 1f906f43c..a5a945d9a 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -425,6 +425,7 @@ + @@ -441,6 +442,7 @@ +