From 2fd030bd5c94ee2614354292ec93da7ad694f0a2 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 8 Feb 2021 23:12:23 -0800 Subject: [PATCH] New: Health check for import lists with missing root folders New: Show missing root folder path in edit for Import List Closes #5908 (cherry picked from commit ae196af2ad368d49fde2358f0987ed7650c7f29c) --- .../Form/RootFolderSelectInputConnector.js | 19 ++++- .../Form/RootFolderSelectInputOption.css | 6 ++ .../Form/RootFolderSelectInputOption.js | 13 +++- .../ImportLists/EditImportListModalContent.js | 1 + .../Checks/ImportListRootFolderCheck.cs | 69 +++++++++++++++++++ 5 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs diff --git a/frontend/src/Components/Form/RootFolderSelectInputConnector.js b/frontend/src/Components/Form/RootFolderSelectInputConnector.js index b6fcc0ef2..3e5e32307 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputConnector.js +++ b/frontend/src/Components/Form/RootFolderSelectInputConnector.js @@ -10,13 +10,16 @@ const ADD_NEW_KEY = 'addNew'; function createMapStateToProps() { return createSelector( (state) => state.rootFolders, + (state, { value }) => value, + (state, { includeMissingValue }) => includeMissingValue, (state, { includeNoChange }) => includeNoChange, - (rootFolders, includeNoChange) => { + (rootFolders, value, includeMissingValue, includeNoChange) => { const values = rootFolders.items.map((rootFolder) => { return { key: rootFolder.path, value: rootFolder.path, - freeSpace: rootFolder.freeSpace + freeSpace: rootFolder.freeSpace, + isMissing: false }; }); @@ -24,7 +27,8 @@ function createMapStateToProps() { values.unshift({ key: 'noChange', value: 'No Change', - isDisabled: true + isDisabled: true, + isMissing: false }); } @@ -37,6 +41,15 @@ function createMapStateToProps() { }); } + if (includeMissingValue && !values.find((v) => v.key === value)) { + values.push({ + key: value, + value, + isMissing: true, + isDisabled: true + }); + } + values.push({ key: ADD_NEW_KEY, value: 'Add a new path' diff --git a/frontend/src/Components/Form/RootFolderSelectInputOption.css b/frontend/src/Components/Form/RootFolderSelectInputOption.css index 86cba74bf..bf86485ea 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputOption.css +++ b/frontend/src/Components/Form/RootFolderSelectInputOption.css @@ -29,3 +29,9 @@ color: $darkGray; font-size: $smallFontSize; } + +.isMissing { + margin-left: 15px; + color: $dangerColor; + font-size: $smallFontSize; +} diff --git a/frontend/src/Components/Form/RootFolderSelectInputOption.js b/frontend/src/Components/Form/RootFolderSelectInputOption.js index 58d0c0a28..17e416403 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputOption.js +++ b/frontend/src/Components/Form/RootFolderSelectInputOption.js @@ -10,6 +10,7 @@ function RootFolderSelectInputOption(props) { id, value, freeSpace, + isMissing, movieFolder, isMobile, isWindows, @@ -43,11 +44,20 @@ function RootFolderSelectInputOption(props) { { - freeSpace != null && + freeSpace == null ? + null :
{formatBytes(freeSpace)} Free
} + + { + isMissing ? +
+ Missing +
: + null + } ); @@ -58,6 +68,7 @@ RootFolderSelectInputOption.propTypes = { value: PropTypes.string.isRequired, freeSpace: PropTypes.number, movieFolder: PropTypes.string, + isMissing: PropTypes.boolean, isMobile: PropTypes.bool.isRequired, isWindows: PropTypes.bool }; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index 2d3de5697..ff7431188 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -164,6 +164,7 @@ function EditImportListModalContent(props) { type={inputTypes.ROOT_FOLDER_SELECT} name="rootFolderPath" {...rootFolderPath} + includeMissingValue={true} onChange={onInputChange} /> diff --git a/src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs new file mode 100644 index 000000000..2f7e81663 --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Disk; +using NzbDrone.Core.ImportLists; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.RootFolders; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Tv.Events; + +namespace NzbDrone.Core.HealthCheck.Checks +{ + [CheckOn(typeof(SeriesDeletedEvent))] + [CheckOn(typeof(SeriesMovedEvent))] + [CheckOn(typeof(EpisodeImportedEvent), CheckOnCondition.FailedOnly)] + [CheckOn(typeof(EpisodeImportFailedEvent), CheckOnCondition.SuccessfulOnly)] + public class ImportListRootFolderCheck : HealthCheckBase + { + private readonly IImportListFactory _importListFactory; + private readonly IDiskProvider _diskProvider; + + public ImportListRootFolderCheck(IImportListFactory importListFactory, IDiskProvider diskProvider) + { + _importListFactory = importListFactory; + _diskProvider = diskProvider; + } + + public override HealthCheck Check() + { + var importLists = _importListFactory.All(); + var missingRootFolders = new Dictionary>(); + + foreach (var importList in importLists) + { + var rootFolderPath = importList.RootFolderPath; + + if (missingRootFolders.ContainsKey(rootFolderPath)) + { + missingRootFolders[rootFolderPath].Add(importList); + + continue; + } + + if (!_diskProvider.FolderExists(rootFolderPath)) + { + missingRootFolders.Add(rootFolderPath, new List { importList }); + } + } + + if (missingRootFolders.Any()) + { + if (missingRootFolders.Count == 1) + { + var missingRootFolder = missingRootFolders.First(); + return new HealthCheck(GetType(), HealthCheckResult.Error, $"Missing root folder for import list(s): {FormatRootFolder(missingRootFolder.Key, missingRootFolder.Value)}", "#import_list_missing_root_folder"); + } + + var message = string.Format("Multiple root folders are missing for import lists: {0}", string.Join(" | ", missingRootFolders.Select(m => FormatRootFolder(m.Key, m.Value)))); + return new HealthCheck(GetType(), HealthCheckResult.Error, message, "#import_list_missing_root_folder"); + } + + return new HealthCheck(GetType()); + } + + private string FormatRootFolder(string rootFolderPath, List importLists) + { + return $"{rootFolderPath} ({string.Join(", ", importLists.Select(l => l.Name))})"; + } + } +}