From 20835291e6ff6704cc64985a9917ef08f78d0e4c Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 24 Apr 2023 20:23:20 -0700 Subject: [PATCH] New: Report health error if Recycling Bin folder is not writable (cherry picked from commit 8c50cd061e691914d9fcce119b9f838f1276950c) --- .../HealthCheck/Checks/RecyclingBinCheck.cs | 41 +++++++++++++++++++ src/NzbDrone.Core/Localization/Core/en.json | 3 +- .../BookImport/ImportApprovedBooks.cs | 7 ++++ .../BookImport/RecycleBinException.cs | 28 +++++++++++++ .../MediaFiles/RecycleBinProvider.cs | 5 ++- 5 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs create mode 100644 src/NzbDrone.Core/MediaFiles/BookImport/RecycleBinException.cs diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs new file mode 100644 index 000000000..02525d32c --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs @@ -0,0 +1,41 @@ +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Localization; +using NzbDrone.Core.MediaFiles.Events; + +namespace NzbDrone.Core.HealthCheck.Checks +{ + [CheckOn(typeof(BookImportedEvent), CheckOnCondition.FailedOnly)] + [CheckOn(typeof(TrackImportedEvent), CheckOnCondition.FailedOnly)] + [CheckOn(typeof(TrackImportFailedEvent), CheckOnCondition.SuccessfulOnly)] + public class RecyclingBinCheck : HealthCheckBase + { + private readonly IConfigService _configService; + private readonly IDiskProvider _diskProvider; + + public RecyclingBinCheck(IConfigService configService, IDiskProvider diskProvider, ILocalizationService localizationService) + : base(localizationService) + { + _configService = configService; + _diskProvider = diskProvider; + } + + public override HealthCheck Check() + { + var recycleBin = _configService.RecycleBin; + + if (recycleBin.IsNullOrWhiteSpace()) + { + return new HealthCheck(GetType()); + } + + if (!_diskProvider.FolderWritable(recycleBin)) + { + return new HealthCheck(GetType(), HealthCheckResult.Error, string.Format(_localizationService.GetLocalizedString("RecycleBinUnableToWriteHealthCheck"), recycleBin), "#cannot-write-recycle-bin"); + } + + return new HealthCheck(GetType()); + } + } +} diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 7d2e6f52e..d3740be0f 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -602,6 +602,7 @@ "RecycleBinCleanupDaysHelpText": "Set to 0 to disable automatic cleanup", "RecycleBinCleanupDaysHelpTextWarning": "Files in the recycle bin older than the selected number of days will be cleaned up automatically", "RecycleBinHelpText": "Book files will go here when deleted instead of being permanently deleted", + "RecycleBinUnableToWriteHealthCheck": "Unable to write to configured recycling bin folder: {0}. Ensure this path exists and is writable by the user running Readarr", "RecyclingBin": "Recycling Bin", "RecyclingBinCleanup": "Recycling Bin Cleanup", "Redownload": "Redownload", @@ -911,4 +912,4 @@ "Year": "Year", "YesCancel": "Yes, Cancel", "Yesterday": "Yesterday" -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs b/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs index 4d93cbce1..0b8a80560 100644 --- a/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs +++ b/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs @@ -267,6 +267,13 @@ public List Import(List> decisions, bool importResults.Add(new ImportResult(importDecision, "Failed to import book, permissions error")); } + catch (RecycleBinException e) + { + _logger.Warn(e, "Couldn't import book " + localTrack); + _eventAggregator.PublishEvent(new TrackImportFailedEvent(e, localTrack, !localTrack.ExistingFile, downloadClientItem)); + + importResults.Add(new ImportResult(importDecision, "Failed to import book, unable to move existing file to the Recycle Bin.")); + } catch (CalibreException e) { _logger.Warn(e, "Couldn't import book " + localTrack); diff --git a/src/NzbDrone.Core/MediaFiles/BookImport/RecycleBinException.cs b/src/NzbDrone.Core/MediaFiles/BookImport/RecycleBinException.cs new file mode 100644 index 000000000..9c1239cc1 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/BookImport/RecycleBinException.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Runtime.Serialization; + +namespace NzbDrone.Core.MediaFiles.BookImport +{ + public class RecycleBinException : DirectoryNotFoundException + { + public RecycleBinException() + { + } + + public RecycleBinException(string message) + : base(message) + { + } + + public RecycleBinException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected RecycleBinException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs b/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs index 2c3a29cf6..6d69d64b2 100644 --- a/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs +++ b/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs @@ -5,6 +5,7 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.MediaFiles.BookImport; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.Messaging.Commands; @@ -96,7 +97,7 @@ public void DeleteFile(string path, string subfolder = "") catch (IOException e) { _logger.Error(e, "Unable to create the folder '{0}' in the recycling bin for the file '{1}'", destinationFolder, fileInfo.Name); - throw; + throw new RecycleBinException($"Unable to create the folder '{destinationFolder}' in the recycling bin for the file '{fileInfo.Name}'", e); } var index = 1; @@ -121,7 +122,7 @@ public void DeleteFile(string path, string subfolder = "") catch (IOException e) { _logger.Error(e, "Unable to move '{0}' to the recycling bin: '{1}'", path, destination); - throw; + throw new RecycleBinException($"Unable to move '{path}' to the recycling bin: '{destination}'", e); } SetLastWriteTime(destination, DateTime.UtcNow);