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