From cb596488f2fdd48913abda644de162396836e95b Mon Sep 17 00:00:00 2001 From: Mitchell Cash Date: Sat, 8 Apr 2017 21:36:16 +1000 Subject: [PATCH] SABnzbd 2.0 API compatibility (#1339) * Fixed: Sabnzbd 2.0 api compatibility. closes #1775 * fixed sab tests. * Fixed: Sabnzbd error when tv sorting enabled for all categories. --- .../SabnzbdTests/SabnzbdFixture.cs | 132 ++++++++++++++++-- .../Responses/SabnzbdFullStatusResponse.cs | 7 + .../Download/Clients/Sabnzbd/Sabnzbd.cs | 77 +++++----- .../Clients/Sabnzbd/SabnzbdFullStatus.cs | 16 +++ .../Download/Clients/Sabnzbd/SabnzbdProxy.cs | 13 +- .../Download/Clients/Sabnzbd/SabnzbdQueue.cs | 1 + .../Download/DownloadClientBase.cs | 2 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 2 + 8 files changed, 202 insertions(+), 48 deletions(-) create mode 100644 src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdFullStatus.cs diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs index c6a08394a..1656b8bde 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs @@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests private SabnzbdHistory _failed; private SabnzbdHistory _completed; private SabnzbdConfig _config; + private SabnzbdFullStatus _fullStatus; [SetUp] public void Setup() @@ -65,7 +66,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests { Status = SabnzbdDownloadStatus.Failed, Size = 1000, - Category = "tv", + Category = "tv", Id = "sabnzbd_nzb12345", Title = "Droned.1998.1080p.WEB-DL-DRONE" } @@ -80,7 +81,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests { Status = SabnzbdDownloadStatus.Completed, Size = 1000, - Category = "tv", + Category = "tv", Id = "sabnzbd_nzb12345", Title = "Droned.1998.1080p.WEB-DL-DRONE", Storage = "/remote/mount/vv/Droned.1998.1080p.WEB-DL-DRONE" @@ -100,9 +101,29 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests } }; + Mocker.GetMock() + .Setup(v => v.GetVersion(It.IsAny())) + .Returns("1.2.3"); + Mocker.GetMock() .Setup(s => s.GetConfig(It.IsAny())) .Returns(_config); + + _fullStatus = new SabnzbdFullStatus + { + CompleteDir = @"Y:\nzbget\root\complete".AsOsAgnostic() + }; + + Mocker.GetMock() + .Setup(s => s.GetFullStatus(It.IsAny())) + .Returns(_fullStatus); + } + + protected void GivenVersion(string version) + { + Mocker.GetMock() + .Setup(s => s.GetVersion(It.IsAny())) + .Returns(version); } protected void GivenFailedDownload() @@ -166,7 +187,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests GivenQueue(_queued); GivenHistory(null); - + var result = Subject.GetItems().Single(); VerifyQueued(result); @@ -387,23 +408,46 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests result.OutputPath.Should().Be(@"C:\sorted\somewhere\asdfasdf\asdfasdf.mkv".AsOsAgnostic()); } - [TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads\vv")] - [TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed\vv")] - [TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads/vv")] - [TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed/vv")] - public void should_return_status_with_outputdir(string rootFolder, string completeDir, string categoryDir, string expectedDir) + [TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")] + [TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")] + [TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")] + [TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed", @"/nzbget/root/completed/vv")] + public void should_return_status_with_outputdir_for_version_lt_2(string rootFolder, string completeDir, string categoryDir, string fullCompleteDir, string fullCategoryDir) { + _fullStatus.CompleteDir = null; _queued.DefaultRootFolder = rootFolder; _config.Misc.complete_dir = completeDir; _config.Categories.First().Dir = categoryDir; - + + GivenVersion("1.2.1"); GivenQueue(null); var result = Subject.GetStatus(); result.IsLocalhost.Should().BeTrue(); result.OutputRootFolders.Should().NotBeNull(); - result.OutputRootFolders.First().Should().Be(expectedDir); + result.OutputRootFolders.First().Should().Be(fullCategoryDir); + } + + [TestCase(@"Y:\nzbget\root", @"completed\downloads", @"vv", @"Y:\nzbget\root\completed\downloads", @"Y:\nzbget\root\completed\downloads\vv")] + [TestCase(@"Y:\nzbget\root", @"completed", @"vv", @"Y:\nzbget\root\completed", @"Y:\nzbget\root\completed\vv")] + [TestCase(@"/nzbget/root", @"completed/downloads", @"vv", @"/nzbget/root/completed/downloads", @"/nzbget/root/completed/downloads/vv")] + [TestCase(@"/nzbget/root", @"completed", @"vv", @"/nzbget/root/completed", @"/nzbget/root/completed/vv")] + public void should_return_status_with_outputdir_for_version_gte_2(string rootFolder, string completeDir, string categoryDir, string fullCompleteDir, string fullCategoryDir) + { + _fullStatus.CompleteDir = fullCompleteDir; + _queued.DefaultRootFolder = null; + _config.Misc.complete_dir = completeDir; + _config.Categories.First().Dir = categoryDir; + + GivenVersion("2.0.0beta1"); + GivenQueue(null); + + var result = Subject.GetStatus(); + + result.IsLocalhost.Should().BeTrue(); + result.OutputRootFolders.Should().NotBeNull(); + result.OutputRootFolders.First().Should().Be(fullCategoryDir); } [Test] @@ -451,5 +495,73 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests result.IsValid.Should().BeTrue(); result.HasWarnings.Should().BeTrue(); } + + [Test] + public void should_test_success_if_tv_sorting_disabled() + { + _config.Misc.enable_tv_sorting = false; + _config.Misc.tv_categories = null; + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeTrue(); + } + + [Test] + public void should_test_failed_if_tv_sorting_null() + { + _config.Misc.enable_tv_sorting = true; + _config.Misc.tv_categories = null; + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeFalse(); + } + + [Test] + public void should_test_failed_if_tv_sorting_empty() + { + _config.Misc.enable_tv_sorting = true; + _config.Misc.tv_categories = new string[0]; + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeFalse(); + } + + [Test] + public void should_test_success_if_tv_sorting_contains_different_category() + { + _config.Misc.enable_tv_sorting = true; + _config.Misc.tv_categories = new[] { "tv-custom" }; + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeTrue(); + } + + [Test] + public void should_test_failed_if_tv_sorting_contains_category() + { + _config.Misc.enable_tv_sorting = true; + _config.Misc.tv_categories = new[] { "tv" }; + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeFalse(); + } + + [Test] + public void should_test_failed_if_tv_sorting_default_category() + { + Subject.Definition.Settings.As().TvCategory = null; + + _config.Misc.enable_tv_sorting = true; + _config.Misc.tv_categories = new[] { "Default" }; + + var result = new NzbDroneValidationResult(Subject.Test()); + + result.IsValid.Should().BeFalse(); + } } } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs new file mode 100644 index 000000000..48c9710c5 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs @@ -0,0 +1,7 @@ +namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses +{ + public class SabnzbdFullStatusResponse + { + public SabnzbdFullStatus Status { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 5d11f6b4c..67910ef08 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -225,10 +225,18 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd if (!completeDir.IsRooted) { - var queue = _proxy.GetQueue(0, 1, Settings); - var defaultRootFolder = new OsPath(queue.DefaultRootFolder); + if (HasVersion(2, 0)) + { + var status = _proxy.GetFullStatus(Settings); + completeDir = new OsPath(status.CompleteDir); + } + else + { + var queue = _proxy.GetQueue(0, 1, Settings); + var defaultRootFolder = new OsPath(queue.DefaultRootFolder); - completeDir = defaultRootFolder + completeDir; + completeDir = defaultRootFolder + completeDir; + } } foreach (var category in config.Categories) @@ -448,50 +456,47 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd }; } } - - if (config.Misc.enable_tv_sorting) + if (config.Misc.enable_tv_sorting && ContainsCategory(config.Misc.tv_categories, Settings.TvCategory)) { - if (!config.Misc.tv_categories.Any() || - config.Misc.tv_categories.Contains(Settings.TvCategory) || - (Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.tv_categories.Contains("Default"))) + return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting") { - return new NzbDroneValidationFailure("TvCategory", "Disable TV Sorting") - { - InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), - DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it." - }; - } + InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), + DetailedDescription = "You must disable Sabnzbd TV Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it." + }; } - - if (config.Misc.enable_movie_sorting) + if (config.Misc.enable_movie_sorting && ContainsCategory(config.Misc.movie_categories, Settings.TvCategory)) { - if (!config.Misc.movie_categories.Any() || - config.Misc.movie_categories.Contains(Settings.TvCategory) || - (Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.movie_categories.Contains("Default"))) + return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting") { - return new NzbDroneValidationFailure("TvCategory", "Disable Movie Sorting") - { - InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), - DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it." - }; - } + InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), + DetailedDescription = "You must disable Sabnzbd Movie Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it." + }; } - - if (config.Misc.enable_date_sorting) + if (config.Misc.enable_date_sorting && ContainsCategory(config.Misc.date_categories, Settings.TvCategory)) { - if (!config.Misc.date_categories.Any() || - config.Misc.date_categories.Contains(Settings.TvCategory) || - (Settings.TvCategory.IsNullOrWhiteSpace() && config.Misc.date_categories.Contains("Default"))) + return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting") { - return new NzbDroneValidationFailure("TvCategory", "Disable Date Sorting") - { - InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), - DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it." - }; - } + InfoLink = string.Format("http://{0}:{1}/sabnzbd/config/sorting/", Settings.Host, Settings.Port), + DetailedDescription = "You must disable Sabnzbd Date Sorting for the category Radarr uses to prevent import issues. Go to Sabnzbd to fix it." + }; } return null; } + + private bool ContainsCategory(IEnumerable categories, string category) + { + if (categories == null || categories.Empty()) + { + return true; + } + + if (category.IsNullOrWhiteSpace()) + { + category = "Default"; + } + + return categories.Contains(category); + } } } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdFullStatus.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdFullStatus.cs new file mode 100644 index 000000000..d1d691fc6 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdFullStatus.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; + +namespace NzbDrone.Core.Download.Clients.Sabnzbd +{ + public class SabnzbdFullStatus + { + // Added in Sabnzbd 2.0.0, my_home was previously in &mode=queue. + // This is the already resolved completedir path. + [JsonProperty(PropertyName = "completedir")] + public string CompleteDir { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdProxy.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdProxy.cs index a3fc32f71..397771ff2 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdProxy.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd void RemoveFrom(string source, string id,bool deleteData, SabnzbdSettings settings); string GetVersion(SabnzbdSettings settings); SabnzbdConfig GetConfig(SabnzbdSettings settings); + SabnzbdFullStatus GetFullStatus(SabnzbdSettings settings); SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings); SabnzbdHistory GetHistory(int start, int limit, string category, SabnzbdSettings settings); string RetryDownload(string id, SabnzbdSettings settings); @@ -37,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd request.AddQueryParam("cat", category); request.AddQueryParam("priority", priority); - + request.AddFormUpload("name", filename, nzbData, "application/x-nzb"); SabnzbdAddResponse response; @@ -84,6 +85,16 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd return response.Config; } + public SabnzbdFullStatus GetFullStatus(SabnzbdSettings settings) + { + var request = BuildRequest("fullstatus", settings); + request.AddQueryParam("skip_dashboard", "1"); + + var response = Json.Deserialize(ProcessRequest(request, settings)); + + return response.Status; + } + public SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings) { var request = BuildRequest("queue", settings); diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdQueue.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdQueue.cs index 5640ae4ec..405d9dec9 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdQueue.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdQueue.cs @@ -5,6 +5,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { public class SabnzbdQueue { + // Removed in Sabnzbd 2.0.0, see mode=fullstatus instead. [JsonProperty(PropertyName = "my_home")] public string DefaultRootFolder { get; set; } diff --git a/src/NzbDrone.Core/Download/DownloadClientBase.cs b/src/NzbDrone.Core/Download/DownloadClientBase.cs index 14f7f1b71..84d12227d 100644 --- a/src/NzbDrone.Core/Download/DownloadClientBase.cs +++ b/src/NzbDrone.Core/Download/DownloadClientBase.cs @@ -111,7 +111,7 @@ namespace NzbDrone.Core.Download public ValidationResult Test() { var failures = new List(); - + try { Test(failures); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 13c1bcb76..9e5589ef5 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -506,6 +506,7 @@ + @@ -514,6 +515,7 @@ +