From a77cb2551304de933daa85381cd89aade3175db1 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) Fixes #8388 Closes #8389 --- .../HealthCheck/Checks/RecyclingBinCheck.cs | 40 +++++++++++++++++++ src/NzbDrone.Core/Localization/Core/en.json | 1 + .../MovieImport/ImportApprovedMovie.cs | 7 ++++ .../MovieImport/RecycleBinException.cs | 28 +++++++++++++ .../MediaFiles/RecycleBinProvider.cs | 5 ++- 5 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs create mode 100644 src/NzbDrone.Core/MediaFiles/MovieImport/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 0000000000..be1e8fb9f4 --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs @@ -0,0 +1,40 @@ +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(MovieFileImportedEvent), CheckOnCondition.FailedOnly)] + [CheckOn(typeof(MovieImportFailedEvent), 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 b6146b2211..3ea948433d 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -766,6 +766,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": "Movie 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 Radarr", "RecyclingBin": "Recycling Bin", "RecyclingBinCleanup": "Recycling Bin Cleanup", "Reddit": "Reddit", diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs index ffd45e4eba..34c5498e55 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs @@ -164,6 +164,13 @@ public List Import(List decisions, bool newDownloa _commandQueueManager.Push(new RescanMovieCommand(localMovie.Movie.Id)); } + catch (RecycleBinException e) + { + _logger.Warn(e, "Couldn't import movie " + localMovie); + _eventAggregator.PublishEvent(new MovieImportFailedEvent(e, localMovie, newDownload, downloadClientItem)); + + importResults.Add(new ImportResult(importDecision, "Failed to import movie, unable to move existing file to the Recycle Bin.")); + } catch (Exception e) { _logger.Warn(e, "Couldn't import movie " + localMovie); diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/RecycleBinException.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/RecycleBinException.cs new file mode 100644 index 0000000000..34e4eac9ef --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/RecycleBinException.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Runtime.Serialization; + +namespace NzbDrone.Core.MediaFiles.MovieImport +{ + 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 7165327f35..ef2cc880e3 100644 --- a/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs +++ b/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.MediaFiles.MovieImport; using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles @@ -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);