diff --git a/frontend/src/Components/Form/RootFolderSelectInputConnector.js b/frontend/src/Components/Form/RootFolderSelectInputConnector.js index ea4cd2ae2..5a06c9e54 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputConnector.js +++ b/frontend/src/Components/Form/RootFolderSelectInputConnector.js @@ -9,8 +9,10 @@ const ADD_NEW_KEY = 'addNew'; function createMapStateToProps() { return createSelector( (state) => state.settings.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, @@ -25,7 +27,8 @@ function createMapStateToProps() { key: 'noChange', value: '', name: 'No Change', - isDisabled: true + isDisabled: true, + isMissing: false }); } @@ -39,6 +42,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: '', diff --git a/frontend/src/Components/Form/RootFolderSelectInputOption.css b/frontend/src/Components/Form/RootFolderSelectInputOption.css index f7a2759fd..0c62c6646 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputOption.css +++ b/frontend/src/Components/Form/RootFolderSelectInputOption.css @@ -18,3 +18,9 @@ color: var(--darkGray); font-size: $smallFontSize; } + +.isMissing { + margin-left: 15px; + color: var(--dangerColor); + font-size: $smallFontSize; +} diff --git a/frontend/src/Components/Form/RootFolderSelectInputOption.css.d.ts b/frontend/src/Components/Form/RootFolderSelectInputOption.css.d.ts index 0c549f7a9..327affebe 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputOption.css.d.ts +++ b/frontend/src/Components/Form/RootFolderSelectInputOption.css.d.ts @@ -2,6 +2,7 @@ // Please do not change this file! interface CssExports { 'freeSpace': string; + 'isMissing': string; 'isMobile': string; 'optionText': string; } diff --git a/frontend/src/Components/Form/RootFolderSelectInputOption.js b/frontend/src/Components/Form/RootFolderSelectInputOption.js index 929b9f342..00825f993 100644 --- a/frontend/src/Components/Form/RootFolderSelectInputOption.js +++ b/frontend/src/Components/Form/RootFolderSelectInputOption.js @@ -10,6 +10,7 @@ function RootFolderSelectInputOption(props) { value, name, freeSpace, + isMissing, isMobile, ...otherProps } = props; @@ -29,11 +30,20 @@ function RootFolderSelectInputOption(props) {
{text}
{ - freeSpace != null && + freeSpace == null ? + null :
{formatBytes(freeSpace)} Free
} + + { + isMissing ? +
+ Missing +
: + null + } ); @@ -43,6 +53,7 @@ RootFolderSelectInputOption.propTypes = { name: PropTypes.string.isRequired, value: PropTypes.string.isRequired, freeSpace: PropTypes.number, + isMissing: PropTypes.bool, isMobile: PropTypes.bool.isRequired }; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css.d.ts b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css.d.ts index 995399ce7..408cea659 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css.d.ts +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css.d.ts @@ -4,6 +4,7 @@ interface CssExports { 'deleteButton': string; 'hideMetadataProfile': string; 'labelIcon': string; + 'message': string; } export const cssExports: CssExports; export default cssExports; diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index b3f1a027f..b88d242a6 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -222,6 +222,7 @@ function EditImportListModalContent(props) { name="rootFolderPath" helpText={translate('RootFolderPathHelpText')} {...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..1e5cfc3dc --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/ImportListRootFolderCheck.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Disk; +using NzbDrone.Core.ImportLists; +using NzbDrone.Core.Localization; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Music.Events; + +namespace NzbDrone.Core.HealthCheck.Checks +{ + [CheckOn(typeof(ArtistsDeletedEvent))] + [CheckOn(typeof(ArtistMovedEvent))] + [CheckOn(typeof(AlbumImportedEvent), CheckOnCondition.FailedOnly)] + [CheckOn(typeof(AlbumImportIncompleteEvent), CheckOnCondition.SuccessfulOnly)] + public class ImportListRootFolderCheck : HealthCheckBase + { + private readonly IImportListFactory _importListFactory; + private readonly IDiskProvider _diskProvider; + + public ImportListRootFolderCheck(IImportListFactory importListFactory, IDiskProvider diskProvider, ILocalizationService localizationService) + : base(localizationService) + { + _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, + string.Format(_localizationService.GetLocalizedString("ImportListRootFolderMissingRootHealthCheckMessage"), FormatRootFolder(missingRootFolder.Key, missingRootFolder.Value)), + "#import-list-missing-root-folder"); + } + + return new HealthCheck(GetType(), + HealthCheckResult.Error, + string.Format(_localizationService.GetLocalizedString("ImportListRootFolderMultipleMissingRootsHealthCheckMessage"), string.Join(" | ", missingRootFolders.Select(m => FormatRootFolder(m.Key, m.Value)))), + "#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))})"; + } + } +}