From ac8283d7339b3d9e8ccf70f3d1ff031d1b0a71d1 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 7 Apr 2021 18:03:00 -0700 Subject: [PATCH] New: Remove completed downloads from disk when removing from SABnzbd Closes #4423 --- .../SabnzbdTests/SabnzbdFixture.cs | 110 ++++++++++++++++++ .../Download/Clients/Sabnzbd/Sabnzbd.cs | 54 ++++++++- 2 files changed, 163 insertions(+), 1 deletion(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs index b95e189cc..8865159d3 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs @@ -576,5 +576,115 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests result.IsValid.Should().BeFalse(); } + + [Test] + public void should_remove_output_path_folder_when_deleting_a_completed_item_and_delete_data_is_true() + { + var path = @"C:\Test\Series.Title.S01E01".AsOsAgnostic(); + + Mocker.GetMock() + .Setup(s => s.FolderExists(path)) + .Returns(true); + + _completed.Items.First().Storage = path; + + GivenQueue(null); + GivenHistory(_completed); + + Subject.RemoveItem(_completed.Items.First().Id, true); + + Mocker.GetMock() + .Verify(v => v.DeleteFolder(path, true), Times.Once); + + Mocker.GetMock() + .Verify(v => v.DeleteFile(path), Times.Never); + } + + [Test] + public void should_remove_output_path_file_when_deleting_a_completed_item_and_delete_data_is_true() + { + var path = @"C:\Test\Series.Title.S01E01.mkv".AsOsAgnostic(); + + Mocker.GetMock() + .Setup(s => s.FolderExists(path)) + .Returns(false); + + Mocker.GetMock() + .Setup(s => s.FileExists(path)) + .Returns(true); + + _completed.Items.First().Storage = path; + + GivenQueue(null); + GivenHistory(_completed); + + Subject.RemoveItem(_completed.Items.First().Id, true); + + Mocker.GetMock() + .Verify(v => v.DeleteFolder(path, true), Times.Never); + + Mocker.GetMock() + .Verify(v => v.DeleteFile(path), Times.Once); + } + + [Test] + public void should_not_remove_output_path_file_when_deleting_a_completed_item_and_delete_data_is_true_if_it_does_not_exist() + { + var path = @"C:\Test\Series.Title.S01E01.mkv".AsOsAgnostic(); + + Mocker.GetMock() + .Setup(s => s.FolderExists(path)) + .Returns(false); + + Mocker.GetMock() + .Setup(s => s.FileExists(path)) + .Returns(false); + + _completed.Items.First().Storage = path; + + GivenQueue(null); + GivenHistory(_completed); + + Subject.RemoveItem(_completed.Items.First().Id, true); + + Mocker.GetMock() + .Verify(v => v.DeleteFolder(path, true), Times.Never); + + Mocker.GetMock() + .Verify(v => v.DeleteFile(path), Times.Never); + } + + [Test] + public void should_not_remove_output_path_file_when_deleting_a_completed_item_and_delete_data_is_false() + { + var path = @"C:\Test\Series.Title.S01E01.mkv".AsOsAgnostic(); + + Mocker.GetMock() + .Setup(s => s.FolderExists(path)) + .Returns(false); + + Mocker.GetMock() + .Setup(s => s.FileExists(path)) + .Returns(false); + + _completed.Items.First().Storage = path; + + GivenQueue(null); + GivenHistory(_completed); + + Subject.RemoveItem(_completed.Items.First().Id, false); + + Mocker.GetMock() + .Verify(v => v.FolderExists(path), Times.Never); + + Mocker.GetMock() + .Verify(v => v.FileExists(path), Times.Never); + + Mocker.GetMock() + .Verify(v => v.DeleteFolder(path, true), Times.Never); + + Mocker.GetMock() + .Verify(v => v.DeleteFile(path), Times.Never); + } } } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index fb76d2731..0ed069f14 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; @@ -192,13 +193,46 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd public override void RemoveItem(string downloadId, bool deleteData) { - if (GetQueue().Any(v => v.DownloadId == downloadId)) + var historyItem = GetHistory().SingleOrDefault(v => v.DownloadId == downloadId); + + if (historyItem == null) { _proxy.RemoveFrom("queue", downloadId, deleteData, Settings); } else { _proxy.RemoveFrom("history", downloadId, deleteData, Settings); + + // Completed items in SAB's history do not remove the files from the file system when deleted, even if instructed to. + // If the output path is valid delete the file(s), otherwise warn that they cannot be deleted. + + if (deleteData && historyItem.Status == DownloadItemStatus.Completed) + { + if (ValidatePath(historyItem)) + { + var outputPath = historyItem.OutputPath; + + try + { + if (_diskProvider.FolderExists(outputPath.FullPath)) + { + _diskProvider.DeleteFolder(outputPath.FullPath.ToString(), true); + } + else if (_diskProvider.FileExists(outputPath.FullPath)) + { + _diskProvider.DeleteFile(outputPath.FullPath.ToString()); + } + } + catch (Exception e) + { + _logger.Error("Unable to delete output path: '{0}'. Delete file(s) manually", outputPath.FullPath); + } + } + else + { + _logger.Warn("Invalid path '{0}'. Delete file(s) manually"); + } + } } } @@ -489,5 +523,23 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd return categories.Contains(category); } + + private bool ValidatePath(DownloadClientItem downloadClientItem) + { + var downloadItemOutputPath = downloadClientItem.OutputPath; + + if (downloadItemOutputPath.IsEmpty) + { + return false; + } + + if ((OsInfo.IsWindows && !downloadItemOutputPath.IsWindowsPath) || + (OsInfo.IsNotWindows && !downloadItemOutputPath.IsUnixPath)) + { + return false; + } + + return true; + } } }