From c56c83e169a2fa64579d9986b107a9fd89c429ee Mon Sep 17 00:00:00 2001 From: margaale Date: Mon, 13 Feb 2017 17:18:35 -0300 Subject: [PATCH] New: Added support for nzb downloads in Synology Download Station. --- .../TorrentDownloadStationFixture.cs | 112 ++--- .../UsenetDownloadStationFixture.cs | 442 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + ...ationTorrent.cs => DownloadStationTask.cs} | 4 +- ...al.cs => DownloadStationTaskAdditional.cs} | 4 +- ...rentFile.cs => DownloadStationTaskFile.cs} | 4 +- .../Proxies/DownloadStationProxy.cs | 30 +- .../DownloadStationTaskInfoResponse.cs | 2 +- .../DownloadStation/TorrentDownloadStation.cs | 26 +- .../DownloadStation/UsenetDownloadStation.cs | 356 ++++++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 7 +- 11 files changed, 894 insertions(+), 94 deletions(-) create mode 100644 src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs rename src/NzbDrone.Core/Download/Clients/DownloadStation/{DownloadStationTorrent.cs => DownloadStationTask.cs} (91%) rename src/NzbDrone.Core/Download/Clients/DownloadStation/{DownloadStationTorrentAdditional.cs => DownloadStationTaskAdditional.cs} (71%) rename src/NzbDrone.Core/Download/Clients/DownloadStation/{DownloadStationTorrentFile.cs => DownloadStationTaskFile.cs} (90%) create mode 100644 src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs index 4ce293e9b..f49ad27dd 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/TorrentDownloadStationFixture.cs @@ -20,16 +20,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests { protected DownloadStationSettings _settings; - protected DownloadStationTorrent _queued; - protected DownloadStationTorrent _downloading; - protected DownloadStationTorrent _failed; - protected DownloadStationTorrent _completed; - protected DownloadStationTorrent _seeding; - protected DownloadStationTorrent _magnet; - protected DownloadStationTorrent _singleFile; - protected DownloadStationTorrent _multipleFiles; - protected DownloadStationTorrent _singleFileCompleted; - protected DownloadStationTorrent _multipleFilesCompleted; + protected DownloadStationTask _queued; + protected DownloadStationTask _downloading; + protected DownloadStationTask _failed; + protected DownloadStationTask _completed; + protected DownloadStationTask _seeding; + protected DownloadStationTask _magnet; + protected DownloadStationTask _singleFile; + protected DownloadStationTask _multipleFiles; + protected DownloadStationTask _singleFileCompleted; + protected DownloadStationTask _multipleFilesCompleted; protected string _serialNumber = "SERIALNUMBER"; protected string _category = "sonarr"; @@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Subject.Definition = new DownloadClientDefinition(); Subject.Definition.Settings = _settings; - _queued = new DownloadStationTorrent() + _queued = new DownloadStationTask() { Id = "id1", Size = 1000, @@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "title", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } }; - _completed = new DownloadStationTorrent() + _completed = new DownloadStationTask() { Id = "id2", Size = 1000, @@ -86,7 +86,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "title", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -101,7 +101,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } }; - _seeding = new DownloadStationTorrent() + _seeding = new DownloadStationTask() { Id = "id2", Size = 1000, @@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "title", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -124,7 +124,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } }; - _downloading = new DownloadStationTorrent() + _downloading = new DownloadStationTask() { Id = "id3", Size = 1000, @@ -132,7 +132,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "title", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -147,7 +147,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } }; - _failed = new DownloadStationTorrent() + _failed = new DownloadStationTask() { Id = "id4", Size = 1000, @@ -155,7 +155,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "title", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -170,7 +170,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } }; - _singleFile = new DownloadStationTorrent() + _singleFile = new DownloadStationTask() { Id = "id5", Size = 1000, @@ -178,7 +178,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "a.mkv", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -193,7 +193,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } }; - _multipleFiles = new DownloadStationTorrent() + _multipleFiles = new DownloadStationTask() { Id = "id6", Size = 1000, @@ -201,7 +201,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "title", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -216,7 +216,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } }; - _singleFileCompleted = new DownloadStationTorrent() + _singleFileCompleted = new DownloadStationTask() { Id = "id6", Size = 1000, @@ -224,7 +224,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "a.mkv", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -239,7 +239,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } }; - _multipleFilesCompleted = new DownloadStationTorrent() + _multipleFilesCompleted = new DownloadStationTask() { Id = "id6", Size = 1000, @@ -247,7 +247,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Type = DownloadStationTaskType.BT, Username = "admin", Title = "title", - Additional = new DownloadStationTorrentAdditional + Additional = new DownloadStationTaskAdditional { Detail = new Dictionary { @@ -304,21 +304,21 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests _settings.TvDirectory = _tvDirectory; } - protected virtual void GivenTorrents(List torrents) + protected virtual void GivenTorrents(List torrents) { if (torrents == null) { - torrents = new List(); + torrents = new List(); } Mocker.GetMock() - .Setup(s => s.GetTorrents(It.IsAny())) + .Setup(s => s.GetTasks(DownloadStationTaskType.BT, It.IsAny())) .Returns(torrents); } protected void PrepareClientToReturnQueuedItem() { - GivenTorrents(new List + GivenTorrents(new List { _queued }); @@ -331,11 +331,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[1000])); Mocker.GetMock() - .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.AddTaskFromUrl(It.IsAny(), It.IsAny(), It.IsAny())) .Callback(PrepareClientToReturnQueuedItem); Mocker.GetMock() - .Setup(s => s.AddTorrentFromData(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.AddTaskFromData(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(PrepareClientToReturnQueuedItem); } @@ -350,10 +350,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests protected int GivenAllKindOfTasks() { - var tasks = new List() { _queued, _completed, _failed, _downloading, _seeding }; + var tasks = new List() { _queued, _completed, _failed, _downloading, _seeding }; Mocker.GetMock() - .Setup(d => d.GetTorrents(_settings)) + .Setup(d => d.GetTasks(DownloadStationTaskType.BT, _settings)) .Returns(tasks); return tasks.Count; @@ -373,7 +373,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); Mocker.GetMock() - .Verify(v => v.AddTorrentFromUrl(It.IsAny(), _tvDirectory, It.IsAny()), Times.Once()); + .Verify(v => v.AddTaskFromUrl(It.IsAny(), _tvDirectory, It.IsAny()), Times.Once()); } [Test] @@ -390,7 +390,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); Mocker.GetMock() - .Verify(v => v.AddTorrentFromUrl(It.IsAny(), $"{_defaultDestination}/{_category}", It.IsAny()), Times.Once()); + .Verify(v => v.AddTaskFromUrl(It.IsAny(), $"{_defaultDestination}/{_category}", It.IsAny()), Times.Once()); } [Test] @@ -406,7 +406,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests id.Should().NotBeNullOrEmpty(); Mocker.GetMock() - .Verify(v => v.AddTorrentFromUrl(It.IsAny(), null, It.IsAny()), Times.Once()); + .Verify(v => v.AddTaskFromUrl(It.IsAny(), null, It.IsAny()), Times.Once()); } [Test] @@ -416,7 +416,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests GivenSerialNumber(); GivenSharedFolder(); - GivenTorrents(new List { _completed }); + GivenTorrents(new List { _completed }); Subject.GetItems().Should().BeEmpty(); } @@ -450,7 +450,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } [Test] - public void Download_should_throw_and_not_add_torrent_if_cannot_get_serial_number() + public void Download_should_throw_and_not_add_task_if_cannot_get_serial_number() { var remoteEpisode = CreateRemoteEpisode(); @@ -461,16 +461,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests Assert.Throws(Is.InstanceOf(), () => Subject.Download(remoteEpisode)); Mocker.GetMock() - .Verify(v => v.AddTorrentFromUrl(It.IsAny(), null, _settings), Times.Never()); + .Verify(v => v.AddTaskFromUrl(It.IsAny(), null, _settings), Times.Never()); } [Test] - public void GetItems_should_set_outputPath_to_base_folder_when_single_file_non_finished_torrent() + public void GetItems_should_set_outputPath_to_base_folder_when_single_file_non_finished_tasks() { GivenSerialNumber(); GivenSharedFolder(); - GivenTorrents(new List() { _singleFile }); + GivenTorrents(new List() { _singleFile }); var items = Subject.GetItems(); @@ -479,12 +479,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } [Test] - public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_non_finished_torrent() + public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_non_finished_tasks() { GivenSerialNumber(); GivenSharedFolder(); - GivenTorrents(new List() { _multipleFiles }); + GivenTorrents(new List() { _multipleFiles }); var items = Subject.GetItems(); @@ -493,12 +493,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } [Test] - public void GetItems_should_set_outputPath_to_base_folder_when_single_file_finished_torrent() + public void GetItems_should_set_outputPath_to_base_folder_when_single_file_finished_tasks() { GivenSerialNumber(); GivenSharedFolder(); - GivenTorrents(new List() { _singleFileCompleted }); + GivenTorrents(new List() { _singleFileCompleted }); var items = Subject.GetItems(); @@ -507,12 +507,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } [Test] - public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_finished_torrent() + public void GetItems_should_set_outputPath_to_torrent_folder_when_multiple_files_finished_tasks() { GivenSerialNumber(); GivenSharedFolder(); - GivenTorrents(new List() { _multipleFilesCompleted }); + GivenTorrents(new List() { _multipleFilesCompleted }); var items = Subject.GetItems(); @@ -521,12 +521,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } [Test] - public void GetItems_should_not_map_outputpath_for_queued_or_downloading_torrents() + public void GetItems_should_not_map_outputpath_for_queued_or_downloading_tasks() { GivenSerialNumber(); GivenSharedFolder(); - GivenTorrents(new List + GivenTorrents(new List { _queued, _downloading }); @@ -538,12 +538,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests } [Test] - public void GetItems_should_map_outputpath_for_completed_or_failed_torrents() + public void GetItems_should_map_outputpath_for_completed_or_failed_tasks() { GivenSerialNumber(); GivenSharedFolder(); - GivenTorrents(new List + GivenTorrents(new List { _completed, _failed, _seeding }); @@ -565,7 +565,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests _queued.Status = apiStatus; - GivenTorrents(new List() { _queued }); + GivenTorrents(new List() { _queued }); var items = Subject.GetItems(); @@ -589,7 +589,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests _queued.Status = apiStatus; - GivenTorrents(new List() { _queued }); + GivenTorrents(new List() { _queued }); var items = Subject.GetItems(); items.Should().HaveCount(1); diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs new file mode 100644 index 000000000..f578465ff --- /dev/null +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/DownloadStationTests/UsenetDownloadStationFixture.cs @@ -0,0 +1,442 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Http; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients.DownloadStation; +using NzbDrone.Core.Download.Clients.DownloadStation.Proxies; +using NzbDrone.Core.MediaFiles.TorrentInfo; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Test.Common; +using NzbDrone.Core.Organizer; + +namespace NzbDrone.Core.Test.Download.DownloadClientTests.DownloadStationTests +{ + [TestFixture] + public class UsenetDownloadStationFixture : DownloadClientFixtureBase + { + protected DownloadStationSettings _settings; + + protected DownloadStationTask _queued; + protected DownloadStationTask _downloading; + protected DownloadStationTask _failed; + protected DownloadStationTask _completed; + protected DownloadStationTask _seeding; + + protected string _serialNumber = "SERIALNUMBER"; + protected string _category = "sonarr"; + protected string _tvDirectory = @"video/Series"; + protected string _defaultDestination = "somepath"; + protected OsPath _physicalPath = new OsPath("/mnt/sdb1/mydata"); + + protected RemoteEpisode _remoteEpisode; + + protected Dictionary _downloadStationConfigItems; + + [SetUp] + public void Setup() + { + _remoteEpisode = CreateRemoteEpisode(); + + _settings = new DownloadStationSettings() + { + Host = "127.0.0.1", + Port = 5000, + Username = "admin", + Password = "pass" + }; + + Subject.Definition = new DownloadClientDefinition(); + Subject.Definition.Settings = _settings; + + _queued = new DownloadStationTask() + { + Id = "id1", + Size = 1000, + Status = DownloadStationTaskStatus.Waiting, + Type = DownloadStationTaskType.NZB, + Username = "admin", + Title = "title", + Additional = new DownloadStationTaskAdditional + { + Detail = new Dictionary + { + { "destination","shared/folder" }, + { "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" } + }, + Transfer = new Dictionary + { + { "size_downloaded", "0"}, + { "speed_download", "0" } + } + } + }; + + _completed = new DownloadStationTask() + { + Id = "id2", + Size = 1000, + Status = DownloadStationTaskStatus.Finished, + Type = DownloadStationTaskType.NZB, + Username = "admin", + Title = "title", + Additional = new DownloadStationTaskAdditional + { + Detail = new Dictionary + { + { "destination","shared/folder" }, + { "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" } + }, + Transfer = new Dictionary + { + { "size_downloaded", "1000"}, + { "speed_download", "0" } + }, + } + }; + + _seeding = new DownloadStationTask() + { + Id = "id2", + Size = 1000, + Status = DownloadStationTaskStatus.Seeding, + Type = DownloadStationTaskType.NZB, + Username = "admin", + Title = "title", + Additional = new DownloadStationTaskAdditional + { + Detail = new Dictionary + { + { "destination","shared/folder" }, + { "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" } + }, + Transfer = new Dictionary + { + { "size_downloaded", "1000"}, + { "speed_download", "0" } + } + } + }; + + _downloading = new DownloadStationTask() + { + Id = "id3", + Size = 1000, + Status = DownloadStationTaskStatus.Downloading, + Type = DownloadStationTaskType.NZB, + Username = "admin", + Title = "title", + Additional = new DownloadStationTaskAdditional + { + Detail = new Dictionary + { + { "destination","shared/folder" }, + { "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" } + }, + Transfer = new Dictionary + { + { "size_downloaded", "100"}, + { "speed_download", "50" } + } + } + }; + + _failed = new DownloadStationTask() + { + Id = "id4", + Size = 1000, + Status = DownloadStationTaskStatus.Error, + Type = DownloadStationTaskType.NZB, + Username = "admin", + Title = "title", + Additional = new DownloadStationTaskAdditional + { + Detail = new Dictionary + { + { "destination","shared/folder" }, + { "uri", FileNameBuilder.CleanFileName(_remoteEpisode.Release.Title) + ".nzb" } + }, + Transfer = new Dictionary + { + { "size_downloaded", "10"}, + { "speed_download", "0" } + } + } + }; + + Mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[0])); + + _downloadStationConfigItems = new Dictionary + { + { "default_destination", _defaultDestination }, + }; + + Mocker.GetMock() + .Setup(v => v.GetConfig(It.IsAny())) + .Returns(_downloadStationConfigItems); + } + + protected void GivenSharedFolder() + { + Mocker.GetMock() + .Setup(s => s.RemapToFullPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((path, setttings, serial) => _physicalPath); + } + + protected void GivenSerialNumber() + { + Mocker.GetMock() + .Setup(s => s.GetSerialNumber(It.IsAny())) + .Returns(_serialNumber); + } + + protected void GivenTvCategory() + { + _settings.TvCategory = _category; + } + + protected void GivenTvDirectory() + { + _settings.TvDirectory = _tvDirectory; + } + + protected virtual void GivenNZBs(List nzbs) + { + if (nzbs == null) + { + nzbs = new List(); + } + + Mocker.GetMock() + .Setup(s => s.GetTasks(DownloadStationTaskType.NZB, It.IsAny())) + .Returns(nzbs); + } + + protected void PrepareClientToReturnQueuedItem() + { + GivenNZBs(new List + { + _queued + }); + } + + 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.AddTaskFromData(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(PrepareClientToReturnQueuedItem); + } + + protected void GivenAllKindOfTasks() + { + var tasks = new List() { _queued, _completed, _failed, _downloading, _seeding }; + + Mocker.GetMock() + .Setup(d => d.GetTasks(DownloadStationTaskType.NZB, _settings)) + .Returns(tasks); + } + + [Test] + public void Download_with_TvDirectory_should_force_directory() + { + GivenSerialNumber(); + GivenTvDirectory(); + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + + Mocker.GetMock() + .Verify(v => v.AddTaskFromData(It.IsAny(), It.IsAny(), _tvDirectory, It.IsAny()), Times.Once()); + } + + [Test] + public void Download_with_category_should_force_directory() + { + GivenSerialNumber(); + GivenTvCategory(); + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + + Mocker.GetMock() + .Verify(v => v.AddTaskFromData(It.IsAny(), It.IsAny(), $"{_defaultDestination}/{_category}", It.IsAny()), Times.Once()); + } + + [Test] + public void Download_without_TvDirectory_and_Category_should_use_default() + { + GivenSerialNumber(); + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + + Mocker.GetMock() + .Verify(v => v.AddTaskFromData(It.IsAny(), It.IsAny(), null, It.IsAny()), Times.Once()); + } + + [Test] + public void GetItems_should_return_empty_list_if_no_tasks_available() + { + _settings.TvDirectory = @"/shared/folder/sub"; + + GivenSerialNumber(); + GivenSharedFolder(); + GivenNZBs(new List()); + + Subject.GetItems().Should().BeEmpty(); + } + + [Test] + public void GetItems_should_ignore_downloads_in_wrong_folder() + { + _settings.TvDirectory = @"/shared/folder/sub"; + + GivenSerialNumber(); + GivenSharedFolder(); + GivenNZBs(new List { _completed }); + + Subject.GetItems().Should().BeEmpty(); + } + + [Test] + public void GetItems_should_throw_if_shared_folder_resolve_fails() + { + Mocker.GetMock() + .Setup(s => s.RemapToFullPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException")); + + GivenSerialNumber(); + GivenAllKindOfTasks(); + + Assert.Throws(Is.InstanceOf(), () => Subject.GetItems()); + ExceptionVerification.ExpectedErrors(0); + } + + [Test] + public void GetItems_should_throw_if_serial_number_unavailable() + { + Mocker.GetMock() + .Setup(s => s.GetSerialNumber(_settings)) + .Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException")); + + GivenSharedFolder(); + GivenAllKindOfTasks(); + + Assert.Throws(Is.InstanceOf(), () => Subject.GetItems()); + ExceptionVerification.ExpectedErrors(0); + } + + [Test] + public void Download_should_throw_and_not_add_tasks_if_cannot_get_serial_number() + { + var remoteEpisode = CreateRemoteEpisode(); + + Mocker.GetMock() + .Setup(s => s.GetSerialNumber(_settings)) + .Throws(new ApplicationException("Some unknown exception, HttpException or DownloadClientException")); + + Assert.Throws(Is.InstanceOf(), () => Subject.Download(remoteEpisode)); + + Mocker.GetMock() + .Verify(v => v.AddTaskFromUrl(It.IsAny(), null, _settings), Times.Never()); + } + + [Test] + public void GetItems_should_not_map_outputpath_for_queued_or_downloading_tasks() + { + GivenSerialNumber(); + GivenSharedFolder(); + + GivenNZBs(new List + { + _queued, _downloading + }); + + var items = Subject.GetItems(); + + items.Should().HaveCount(2); + items.Should().OnlyContain(v => v.OutputPath.IsEmpty); + } + + [Test] + public void GetItems_should_map_outputpath_for_completed_or_failed_tasks() + { + GivenSerialNumber(); + GivenSharedFolder(); + + GivenNZBs(new List + { + _completed, _failed, _seeding + }); + + var items = Subject.GetItems(); + + items.Should().HaveCount(3); + items.Should().OnlyContain(v => !v.OutputPath.IsEmpty); + } + + [TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading, true)] + [TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed, false)] + [TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued, true)] + public void GetItems_should_return_readonly_expected(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus, bool readOnlyExpected) + { + GivenSerialNumber(); + GivenSharedFolder(); + + _queued.Status = apiStatus; + + GivenNZBs(new List() { _queued }); + + var items = Subject.GetItems(); + + items.Should().HaveCount(1); + items.First().IsReadOnly.Should().Be(readOnlyExpected); + } + + [TestCase(DownloadStationTaskStatus.Downloading, DownloadItemStatus.Downloading)] + [TestCase(DownloadStationTaskStatus.Error, DownloadItemStatus.Failed)] + [TestCase(DownloadStationTaskStatus.Extracting, DownloadItemStatus.Downloading)] + [TestCase(DownloadStationTaskStatus.Finished, DownloadItemStatus.Completed)] + [TestCase(DownloadStationTaskStatus.Finishing, DownloadItemStatus.Downloading)] + [TestCase(DownloadStationTaskStatus.HashChecking, DownloadItemStatus.Downloading)] + [TestCase(DownloadStationTaskStatus.Paused, DownloadItemStatus.Paused)] + [TestCase(DownloadStationTaskStatus.Waiting, DownloadItemStatus.Queued)] + public void GetItems_should_return_item_as_downloadItemStatus(DownloadStationTaskStatus apiStatus, DownloadItemStatus expectedItemStatus) + { + GivenSerialNumber(); + GivenSharedFolder(); + + _queued.Status = apiStatus; + + GivenNZBs(new List() { _queued }); + + var items = Subject.GetItems(); + items.Should().HaveCount(1); + + items.First().Status.Should().Be(expectedItemStatus); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 3030aabf5..0526f7826 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -176,6 +176,7 @@ + diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrent.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTask.cs similarity index 91% rename from src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrent.cs rename to src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTask.cs index e840fddb0..1714ac3ba 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTask.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json.Converters; namespace NzbDrone.Core.Download.Clients.DownloadStation { - public class DownloadStationTorrent + public class DownloadStationTask { public string Username { get; set; } @@ -24,7 +24,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation [JsonConverter(typeof(StringEnumConverter))] public DownloadStationTaskStatus Status { get; set; } - public DownloadStationTorrentAdditional Additional { get; set; } + public DownloadStationTaskAdditional Additional { get; set; } public override string ToString() { diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrentAdditional.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTaskAdditional.cs similarity index 71% rename from src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrentAdditional.cs rename to src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTaskAdditional.cs index f10c84b00..45c9fd137 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrentAdditional.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTaskAdditional.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; namespace NzbDrone.Core.Download.Clients.DownloadStation { - public class DownloadStationTorrentAdditional + public class DownloadStationTaskAdditional { public Dictionary Detail { get; set; } public Dictionary Transfer { get; set; } [JsonProperty("File")] - public List Files { get; set; } + public List Files { get; set; } } } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrentFile.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTaskFile.cs similarity index 90% rename from src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrentFile.cs rename to src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTaskFile.cs index 186911c77..d49c5d7dd 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTorrentFile.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/DownloadStationTaskFile.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; -using static NzbDrone.Core.Download.Clients.DownloadStation.DownloadStationTorrent; +using static NzbDrone.Core.Download.Clients.DownloadStation.DownloadStationTask; namespace NzbDrone.Core.Download.Clients.DownloadStation { - public class DownloadStationTorrentFile + public class DownloadStationTaskFile { public string FileName { get; set; } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationProxy.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationProxy.cs index 48fd0330f..ab823e884 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Proxies/DownloadStationProxy.cs @@ -10,11 +10,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies { public interface IDownloadStationProxy { - IEnumerable GetTorrents(DownloadStationSettings settings); + IEnumerable GetTasks(DownloadStationTaskType type, DownloadStationSettings settings); Dictionary GetConfig(DownloadStationSettings settings); - void RemoveTorrent(string downloadId, DownloadStationSettings settings); - void AddTorrentFromUrl(string url, string downloadDirectory, DownloadStationSettings settings); - void AddTorrentFromData(byte[] torrentData, string filename, string downloadDirectory, DownloadStationSettings settings); + void RemoveTask(string downloadId, DownloadStationSettings settings); + void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings); + void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings); IEnumerable GetApiVersion(DownloadStationSettings settings); } @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies { } - public void AddTorrentFromData(byte[] torrentData, string filename, string downloadDirectory, DownloadStationSettings settings) + public void AddTaskFromData(byte[] data, string filename, string downloadDirectory, DownloadStationSettings settings) { var arguments = new Dictionary { @@ -39,19 +39,19 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies arguments.Add("destination", downloadDirectory); } - arguments.Add("file", new Dictionary() { { "name", filename }, { "data", torrentData } }); + arguments.Add("file", new Dictionary() { { "name", filename }, { "data", data } }); - var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add torrent from data {filename}", HttpMethod.POST); + var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from data {filename}", HttpMethod.POST); } - public void AddTorrentFromUrl(string torrentUrl, string downloadDirectory, DownloadStationSettings settings) + public void AddTaskFromUrl(string url, string downloadDirectory, DownloadStationSettings settings) { var arguments = new Dictionary { { "api", "SYNO.DownloadStation.Task" }, { "version", "3" }, { "method", "create" }, - { "uri", torrentUrl } + { "uri", url } }; if (downloadDirectory.IsNotNullOrWhiteSpace()) @@ -59,10 +59,10 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies arguments.Add("destination", downloadDirectory); } - var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add torrent from url {torrentUrl}", HttpMethod.GET); + var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, $"add task from url {url}"); } - public IEnumerable GetTorrents(DownloadStationSettings settings) + public IEnumerable GetTasks(DownloadStationTaskType type, DownloadStationSettings settings) { var arguments = new Dictionary { @@ -74,14 +74,14 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies try { - var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, "get torrents"); + var response = ProcessRequest(DiskStationApi.DownloadStationTask, arguments, settings, "get tasks"); - return response.Data.Tasks.Where(t => t.Type == DownloadStationTaskType.BT); + return response.Data.Tasks.Where(t => t.Type == type); } catch (DownloadClientException e) { _logger.Error(e); - return new List(); + return new List(); } } @@ -99,7 +99,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Proxies return response.Data; } - public void RemoveTorrent(string downloadId, DownloadStationSettings settings) + public void RemoveTask(string downloadId, DownloadStationSettings settings) { var arguments = new Dictionary { diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DownloadStationTaskInfoResponse.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DownloadStationTaskInfoResponse.cs index 1da16621c..1d974b913 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DownloadStationTaskInfoResponse.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/Responses/DownloadStationTaskInfoResponse.cs @@ -5,7 +5,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation.Responses public class DownloadStationTaskInfoResponse { public int Offset { get; set; } - public List Tasks {get;set;} + public List Tasks {get;set;} public int Total { get; set; } } } diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs index 9d8818d9a..25b99d8ef 100644 --- a/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/TorrentDownloadStation.cs @@ -45,7 +45,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation public override IEnumerable GetItems() { - var torrents = _proxy.GetTorrents(Settings); + var torrents = _proxy.GetTasks(DownloadStationTaskType.BT, Settings); var serialNumber = _serialNumberProvider.GetSerialNumber(Settings); var items = new List(); @@ -122,11 +122,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation DeleteItemData(downloadId); } - _proxy.RemoveTorrent(ParseDownloadId(downloadId), Settings); + _proxy.RemoveTask(ParseDownloadId(downloadId), Settings); _logger.Debug("{0} removed correctly", downloadId); } - protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTorrent torrent, string serialNumber) + protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTask torrent, string serialNumber) { var fullPath = _sharedFolderResolver.RemapToFullPath(outputPath, Settings, serialNumber); @@ -141,9 +141,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); - _proxy.AddTorrentFromUrl(magnetLink, GetDownloadDirectory(), Settings); + _proxy.AddTaskFromUrl(magnetLink, GetDownloadDirectory(), Settings); - var item = _proxy.GetTorrents(Settings).SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink); + var item = _proxy.GetTasks(DownloadStationTaskType.BT, Settings).SingleOrDefault(t => t.Additional.Detail["uri"] == magnetLink); if (item != null) { @@ -160,9 +160,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); - _proxy.AddTorrentFromData(fileContent, filename, GetDownloadDirectory(), Settings); + _proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); - var items = _proxy.GetTorrents(Settings).Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename)); + var items = _proxy.GetTasks(DownloadStationTaskType.BT, Settings).Where(t => t.Additional.Detail["uri"] == Path.GetFileNameWithoutExtension(filename)); var item = items.SingleOrDefault(); @@ -232,12 +232,12 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation return null; } - protected bool IsFinished(DownloadStationTorrent torrent) + protected bool IsFinished(DownloadStationTask torrent) { return torrent.Status == DownloadStationTaskStatus.Finished; } - protected string GetMessage(DownloadStationTorrent torrent) + protected string GetMessage(DownloadStationTask torrent) { if (torrent.StatusExtra != null) { @@ -255,7 +255,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation return null; } - protected DownloadItemStatus GetStatus(DownloadStationTorrent torrent) + protected DownloadItemStatus GetStatus(DownloadStationTask torrent) { switch (torrent.Status) { @@ -273,7 +273,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation return DownloadItemStatus.Downloading; } - protected long GetRemainingSize(DownloadStationTorrent torrent) + protected long GetRemainingSize(DownloadStationTask torrent) { var downloadedString = torrent.Additional.Transfer["size_downloaded"]; long downloadedSize; @@ -287,7 +287,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation return torrent.Size - Math.Max(0, downloadedSize); } - protected TimeSpan? GetRemainingTime(DownloadStationTorrent torrent) + protected TimeSpan? GetRemainingTime(DownloadStationTask torrent) { var speedString = torrent.Additional.Transfer["speed_download"]; long downloadSpeed; @@ -312,7 +312,7 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation { try { - _proxy.GetTorrents(Settings); + _proxy.GetTasks(DownloadStationTaskType.BT, Settings); return null; } catch (Exception ex) diff --git a/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs new file mode 100644 index 000000000..198c6786e --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/DownloadStation/UsenetDownloadStation.cs @@ -0,0 +1,356 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Download.Clients.DownloadStation.Proxies; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.RemotePathMappings; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Download.Clients.DownloadStation +{ + public class UsenetDownloadStation : UsenetClientBase + { + protected readonly IDownloadStationProxy _proxy; + protected readonly ISharedFolderResolver _sharedFolderResolver; + protected readonly ISerialNumberProvider _serialNumberProvider; + + public UsenetDownloadStation(IDownloadStationProxy proxy, + IHttpClient httpClient, + IConfigService configService, + IDiskProvider diskProvider, + IRemotePathMappingService remotePathMappingService, + Logger logger, + ISharedFolderResolver sharedFolderResolver, + ISerialNumberProvider serialNumberProvider) + : base(httpClient, configService, diskProvider, remotePathMappingService, logger) + { + _proxy = proxy; + _sharedFolderResolver = sharedFolderResolver; + _serialNumberProvider = serialNumberProvider; + } + + public override string Name => "Download Station"; + + public override IEnumerable GetItems() + { + var nzbTasks = _proxy.GetTasks(DownloadStationTaskType.NZB, Settings); + var serialNumber = _serialNumberProvider.GetSerialNumber(Settings); + + var items = new List(); + + long totalRemainingSize = 0; + long globalSpeed = nzbTasks.Where(t => t.Status == DownloadStationTaskStatus.Downloading) + .Select(GetDownloadSpeed) + .Sum(); + + foreach (var nzb in nzbTasks) + { + var outputPath = new OsPath($"/{nzb.Additional.Detail["destination"]}"); + + var taskRemainingSize = GetRemainingSize(nzb); + + if (nzb.Status != DownloadStationTaskStatus.Paused) + { + totalRemainingSize += taskRemainingSize; + } + + 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; + } + } + + var item = new DownloadClientItem() + { + Category = Settings.TvCategory, + DownloadClient = Definition.Name, + DownloadId = CreateDownloadId(nzb.Id, serialNumber), + Title = nzb.Title, + TotalSize = nzb.Size, + RemainingSize = taskRemainingSize, + Status = GetStatus(nzb), + Message = GetMessage(nzb), + IsReadOnly = !IsFinished(nzb) + }; + + if (item.Status != DownloadItemStatus.Paused) + { + item.RemainingTime = GetRemainingTime(totalRemainingSize, globalSpeed); + } + + if (item.Status == DownloadItemStatus.Completed || item.Status == DownloadItemStatus.Failed) + { + item.OutputPath = GetOutputPath(outputPath, nzb, serialNumber); + } + + items.Add(item); + } + + return items; + } + + protected OsPath GetOutputPath(OsPath outputPath, DownloadStationTask task, string serialNumber) + { + var fullPath = _sharedFolderResolver.RemapToFullPath(outputPath, Settings, serialNumber); + + var remotePath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, fullPath); + + var finalPath = remotePath + task.Title; + + return finalPath; + } + + public override DownloadClientStatus GetStatus() + { + try + { + var path = GetDownloadDirectory(); + + return new DownloadClientStatus + { + IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost", + OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(path)) } + }; + } + catch (DownloadClientException e) + { + _logger.Debug(e, "Failed to get config from Download Station"); + + throw e; + } + } + + public override void RemoveItem(string downloadId, bool deleteData) + { + if (deleteData) + { + DeleteItemData(downloadId); + } + + _proxy.RemoveTask(ParseDownloadId(downloadId), Settings); + _logger.Debug("{0} removed correctly", downloadId); + } + + protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent) + { + var hashedSerialNumber = _serialNumberProvider.GetSerialNumber(Settings); + + _proxy.AddTaskFromData(fileContent, filename, GetDownloadDirectory(), Settings); + + var items = _proxy.GetTasks(DownloadStationTaskType.NZB, Settings).Where(t => t.Additional.Detail["uri"] == filename); + + var item = items.SingleOrDefault(); + + if (item != null) + { + _logger.Debug("{0} added correctly", remoteEpisode); + return CreateDownloadId(item.Id, hashedSerialNumber); + } + + _logger.Debug("No such task {0} in Download Station", filename); + + throw new DownloadClientException("Failed to add NZB task to Download Station"); + } + + protected override void Test(List failures) + { + failures.AddIfNotNull(TestConnection()); + if (failures.Any()) return; + failures.AddIfNotNull(TestGetNZB()); + } + + protected ValidationFailure TestConnection() + { + try + { + return ValidateVersion(); + } + catch (DownloadClientAuthenticationException ex) + { + _logger.Error(ex, ex.Message); + return new NzbDroneValidationFailure("Username", "Authentication failure") + { + DetailedDescription = $"Please verify your username and password. Also verify if the host running Sonarr isn't blocked from accessing {Name} by WhiteList limitations in the {Name} configuration." + }; + } + catch (WebException ex) + { + _logger.Error(ex); + + 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); + return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + } + } + + protected ValidationFailure ValidateVersion() + { + var versionRange = _proxy.GetApiVersion(Settings); + + _logger.Debug("Download Station api version information: Min {0} - Max {1}", versionRange.Min(), versionRange.Max()); + + if (!versionRange.Contains(2)) + { + return new ValidationFailure(string.Empty, $"Download Station API version not supported, should be at least 2. It supports from {versionRange.Min()} to {versionRange.Max()}"); + } + + return null; + } + + protected bool IsFinished(DownloadStationTask task) + { + return task.Status == DownloadStationTaskStatus.Finished; + } + + protected string GetMessage(DownloadStationTask task) + { + if (task.StatusExtra != null) + { + if (task.Status == DownloadStationTaskStatus.Extracting) + { + return $"Extracting: {int.Parse(task.StatusExtra["unzip_progress"])}%"; + } + + if (task.Status == DownloadStationTaskStatus.Error) + { + return task.StatusExtra["error_detail"]; + } + } + + return null; + } + + protected DownloadItemStatus GetStatus(DownloadStationTask task) + { + switch (task.Status) + { + case DownloadStationTaskStatus.Waiting: + return task.Size == 0 || GetRemainingSize(task) > 0 ? DownloadItemStatus.Queued : DownloadItemStatus.Completed; + case DownloadStationTaskStatus.Paused: + return DownloadItemStatus.Paused; + case DownloadStationTaskStatus.Finished: + case DownloadStationTaskStatus.Seeding: + return DownloadItemStatus.Completed; + case DownloadStationTaskStatus.Error: + return DownloadItemStatus.Failed; + } + + return DownloadItemStatus.Downloading; + } + + protected long GetRemainingSize(DownloadStationTask task) + { + var downloadedString = task.Additional.Transfer["size_downloaded"]; + long downloadedSize; + + if (downloadedString.IsNullOrWhiteSpace() || !long.TryParse(downloadedString, out downloadedSize)) + { + _logger.Debug("Task {0} has invalid size_downloaded: {1}", task.Title, downloadedString); + downloadedSize = 0; + } + + return task.Size - Math.Max(0, downloadedSize); + } + + protected long GetDownloadSpeed(DownloadStationTask task) + { + var speedString = task.Additional.Transfer["speed_download"]; + long downloadSpeed; + + if (speedString.IsNullOrWhiteSpace() || !long.TryParse(speedString, out downloadSpeed)) + { + _logger.Debug("Task {0} has invalid speed_download: {1}", task.Title, speedString); + downloadSpeed = 0; + } + + return Math.Max(downloadSpeed, 0); + } + + protected TimeSpan? GetRemainingTime(long remainingSize, long downloadSpeed) + { + if (downloadSpeed > 0) + { + return TimeSpan.FromSeconds(remainingSize / downloadSpeed); + } + else + { + return null; + } + } + + protected ValidationFailure TestGetNZB() + { + try + { + _proxy.GetTasks(DownloadStationTaskType.NZB, Settings); + return null; + } + catch (Exception ex) + { + return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of NZBs: " + ex.Message); + } + } + + protected string ParseDownloadId(string id) + { + return id.Split(':')[1]; + } + + protected string CreateDownloadId(string id, string hashedSerialNumber) + { + return $"{hashedSerialNumber}:{id}"; + } + + protected string GetDefaultDir() + { + var config = _proxy.GetConfig(Settings); + + var path = config["default_destination"] as string; + + return path; + } + + protected string GetDownloadDirectory() + { + if (Settings.TvDirectory.IsNotNullOrWhiteSpace()) + { + return Settings.TvDirectory.TrimStart('/'); + } + else if (Settings.TvCategory.IsNotNullOrWhiteSpace()) + { + var destDir = GetDefaultDir(); + + return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}"; + } + + return null; + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 938ab1f17..d057bb822 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -356,13 +356,13 @@ - - + + - + @@ -375,6 +375,7 @@ +