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))})";
+ }
+ }
+}