From 8c50cd061e691914d9fcce119b9f838f1276950c 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 Closes #4692 --- .../HealthCheck/Checks/RecyclingBinCheck.cs | 38 +++++++++++++++++++ .../HealthCheck/HealthCheckService.cs | 2 +- .../EpisodeImport/ImportApprovedEpisodes.cs | 7 ++++ .../EpisodeImport/RecycleBinException.cs | 28 ++++++++++++++ .../MediaFiles/RecycleBinProvider.cs | 7 ++-- 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/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..8a83faac2 --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/RecyclingBinCheck.cs @@ -0,0 +1,38 @@ +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MediaFiles.Events; + +namespace NzbDrone.Core.HealthCheck.Checks +{ + [CheckOn(typeof(EpisodeImportedEvent), CheckOnCondition.FailedOnly)] + [CheckOn(typeof(EpisodeImportFailedEvent), CheckOnCondition.SuccessfulOnly)] + public class RecyclingBinCheck : HealthCheckBase + { + private readonly IConfigService _configService; + private readonly IDiskProvider _diskProvider; + + public RecyclingBinCheck(IConfigService configService, IDiskProvider diskProvider) + { + _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, $"Unable to write to configured recycling bin folder: {recycleBin}. Ensure this path exists and is writable by the user running Sonarr"); + } + + return new HealthCheck(GetType()); + } + } +} diff --git a/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs b/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs index ac734fcce..c24616c71 100644 --- a/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs +++ b/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NLog; diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 096ccd0cf..a5feeaf4a 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -151,6 +151,13 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport _commandQueueManager.Push(new RescanSeriesCommand(localEpisode.Series.Id)); } + catch (RecycleBinException e) + { + _logger.Warn(e, "Couldn't import episode " + localEpisode); + _eventAggregator.PublishEvent(new EpisodeImportFailedEvent(e, localEpisode, newDownload, downloadClientItem)); + + importResults.Add(new ImportResult(importDecision, "Failed to import episode, unable to move existing file to the Recycle Bin.")); + } catch (Exception e) { _logger.Warn(e, "Couldn't import episode " + localEpisode); diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/RecycleBinException.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/RecycleBinException.cs new file mode 100644 index 000000000..645951518 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/RecycleBinException.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Runtime.Serialization; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport +{ + 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 70060021a..fd35deb9d 100644 --- a/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs +++ b/src/NzbDrone.Core/MediaFiles/RecycleBinProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using NLog; using NzbDrone.Common.Disk; @@ -6,6 +6,7 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv.Events; @@ -98,7 +99,7 @@ namespace NzbDrone.Core.MediaFiles 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; @@ -123,7 +124,7 @@ namespace NzbDrone.Core.MediaFiles 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);