From 44e6de2e237761ded2decb49ea49e8705782b283 Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Tue, 12 Sep 2023 00:19:44 +0200 Subject: [PATCH] Add health check for dl clients removing completed downloads + enable for sab and qbit (cherry picked from commit 7f2cd8a0e99b537a1c616998514bacdd8468a016) Closes #2939 --- ...ntRemovesCompletedDownloadsCheckFixture.cs | 78 +++++++++++++++++++ .../Clients/QBittorrent/QBittorrent.cs | 3 +- .../Download/Clients/Sabnzbd/Sabnzbd.cs | 2 + .../Clients/Sabnzbd/SabnzbdCategory.cs | 1 + .../Download/DownloadClientInfo.cs | 1 + ...oadClientRemovesCompletedDownloadsCheck.cs | 64 +++++++++++++++ src/NzbDrone.Core/Localization/Core/en.json | 1 + 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core.Test/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheckFixture.cs create mode 100644 src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheckFixture.cs new file mode 100644 index 000000000..4d95966af --- /dev/null +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheckFixture.cs @@ -0,0 +1,78 @@ +using System; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients; +using NzbDrone.Core.HealthCheck.Checks; +using NzbDrone.Core.Localization; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.HealthCheck.Checks +{ + [TestFixture] + public class DownloadClientRemovesCompletedDownloadsCheckFixture : CoreTest + { + private DownloadClientInfo _clientStatus; + private Mock _downloadClient; + + private static Exception[] DownloadClientExceptions = + { + new DownloadClientUnavailableException("error"), + new DownloadClientAuthenticationException("error"), + new DownloadClientException("error") + }; + + [SetUp] + public void Setup() + { + _clientStatus = new DownloadClientInfo + { + IsLocalhost = true, + + // SortingMode = null, + RemovesCompletedDownloads = true + }; + + _downloadClient = Mocker.GetMock(); + _downloadClient.Setup(s => s.Definition) + .Returns(new DownloadClientDefinition { Name = "Test" }); + + _downloadClient.Setup(s => s.GetStatus()) + .Returns(_clientStatus); + + Mocker.GetMock() + .Setup(s => s.GetDownloadClients(It.IsAny())) + .Returns(new IDownloadClient[] { _downloadClient.Object }); + + Mocker.GetMock() + .Setup(s => s.GetLocalizedString(It.IsAny())) + .Returns("Some Warning Message"); + } + + [Test] + public void should_return_warning_if_removing_completed_downloads_is_enabled() + { + Subject.Check().ShouldBeWarning(); + } + + [Test] + public void should_return_ok_if_remove_completed_downloads_is_not_enabled() + { + _clientStatus.RemovesCompletedDownloads = false; + Subject.Check().ShouldBeOk(); + } + + [Test] + [TestCaseSource(nameof(DownloadClientExceptions))] + public void should_return_ok_if_client_throws_downloadclientexception(Exception ex) + { + _downloadClient.Setup(s => s.GetStatus()) + .Throws(ex); + + Subject.Check().ShouldBeOk(); + + ExceptionVerification.ExpectedErrors(0); + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 14dff5cf8..56324d28b 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -389,7 +389,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent return new DownloadClientInfo { IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost", - OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) } + OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, destDir) }, + RemovesCompletedDownloads = (config.MaxRatioEnabled || config.MaxSeedingTimeEnabled) && (config.MaxRatioAction == QBittorrentMaxRatioAction.Remove || config.MaxRatioAction == QBittorrentMaxRatioAction.DeleteFiles) }; } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index a491bf1b3..27d31f31a 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -263,6 +263,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd status.OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, category.FullPath) }; } + status.RemovesCompletedDownloads = config.Misc.history_retention != "0"; + return status; } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs index e25a91701..189b08257 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdCategory.cs @@ -29,6 +29,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd public string[] date_categories { get; set; } public bool enable_date_sorting { get; set; } public bool pre_check { get; set; } + public string history_retention { get; set; } } public class SabnzbdCategory diff --git a/src/NzbDrone.Core/Download/DownloadClientInfo.cs b/src/NzbDrone.Core/Download/DownloadClientInfo.cs index 734283ca4..2623f68d7 100644 --- a/src/NzbDrone.Core/Download/DownloadClientInfo.cs +++ b/src/NzbDrone.Core/Download/DownloadClientInfo.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.Download } public bool IsLocalhost { get; set; } + public bool RemovesCompletedDownloads { get; set; } public List OutputRootFolders { get; set; } } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs new file mode 100644 index 000000000..bf41a9343 --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/DownloadClientRemovesCompletedDownloadsCheck.cs @@ -0,0 +1,64 @@ +using System; +using NLog; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients; +using NzbDrone.Core.Localization; +using NzbDrone.Core.RemotePathMappings; +using NzbDrone.Core.RootFolders; +using NzbDrone.Core.ThingiProvider.Events; + +namespace NzbDrone.Core.HealthCheck.Checks +{ + [CheckOn(typeof(ProviderUpdatedEvent))] + [CheckOn(typeof(ProviderDeletedEvent))] + [CheckOn(typeof(ModelEvent))] + [CheckOn(typeof(ModelEvent))] + + public class DownloadClientRemovesCompletedDownloadsCheck : HealthCheckBase, IProvideHealthCheck + { + private readonly IProvideDownloadClient _downloadClientProvider; + private readonly Logger _logger; + + public DownloadClientRemovesCompletedDownloadsCheck(IProvideDownloadClient downloadClientProvider, + Logger logger, + ILocalizationService localizationService) + : base(localizationService) + { + _downloadClientProvider = downloadClientProvider; + _logger = logger; + } + + public override HealthCheck Check() + { + var clients = _downloadClientProvider.GetDownloadClients(true); + + foreach (var client in clients) + { + try + { + var clientName = client.Definition.Name; + var status = client.GetStatus(); + + if (status.RemovesCompletedDownloads) + { + return new HealthCheck(GetType(), + HealthCheckResult.Warning, + string.Format(_localizationService.GetLocalizedString("DownloadClientRemovesCompletedDownloadsHealthCheckMessage"), clientName, "Readarr"), + "#download-client-removes-completed-downloads"); + } + } + catch (DownloadClientException ex) + { + _logger.Debug(ex, "Unable to communicate with {0}", client.Definition.Name); + } + catch (Exception ex) + { + _logger.Error(ex, "Unknown error occurred in DownloadClientHistoryRetentionCheck HealthCheck"); + } + } + + return new HealthCheck(GetType()); + } + } +} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 5509381b0..111f9f209 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -265,6 +265,7 @@ "DownloadClientCheckDownloadingToRoot": "Download client {0} places downloads in the root folder {1}. You should not download to a root folder.", "DownloadClientCheckNoneAvailableMessage": "No download client is available", "DownloadClientCheckUnableToCommunicateMessage": "Unable to communicate with {0}.", + "DownloadClientRemovesCompletedDownloadsHealthCheckMessage": "Download client {0} is set to remove completed downloads. This can result in downloads being removed from your client before {1} can import them.", "DownloadClientSettings": "Download Client Settings", "DownloadClientStatusCheckAllClientMessage": "All download clients are unavailable due to failures", "DownloadClientStatusCheckSingleClientMessage": "Download clients unavailable due to failures: {0}",