mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-13 16:43:58 -07:00
New: Health check for import lists with missing root folders
New: Show missing root folder path in edit for Import List (cherry picked from commit ae196af2ad368d49fde2358f0987ed7650c7f29c) Closes #1998
This commit is contained in:
parent
af1e2fe2eb
commit
bfb86dafc9
8 changed files with 127 additions and 7 deletions
|
@ -9,14 +9,17 @@ const ADD_NEW_KEY = 'addNew';
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.rootFolders,
|
(state) => state.settings.rootFolders,
|
||||||
|
(state, { value }) => value,
|
||||||
|
(state, { includeMissingValue }) => includeMissingValue,
|
||||||
(state, { includeNoChange }) => includeNoChange,
|
(state, { includeNoChange }) => includeNoChange,
|
||||||
(rootFolders, includeNoChange) => {
|
(rootFolders, value, includeMissingValue, includeNoChange) => {
|
||||||
const values = rootFolders.items.map((rootFolder) => {
|
const values = rootFolders.items.map((rootFolder) => {
|
||||||
return {
|
return {
|
||||||
key: rootFolder.path,
|
key: rootFolder.path,
|
||||||
value: rootFolder.path,
|
value: rootFolder.path,
|
||||||
name: rootFolder.name,
|
name: rootFolder.name,
|
||||||
freeSpace: rootFolder.freeSpace
|
freeSpace: rootFolder.freeSpace,
|
||||||
|
isMissing: false
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,6 +28,16 @@ function createMapStateToProps() {
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: '',
|
value: '',
|
||||||
name: 'No Change',
|
name: 'No Change',
|
||||||
|
isDisabled: true,
|
||||||
|
isMissing: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeMissingValue && !values.find((v) => v.key === value)) {
|
||||||
|
values.push({
|
||||||
|
key: value,
|
||||||
|
value,
|
||||||
|
isMissing: true,
|
||||||
isDisabled: true
|
isDisabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,3 +27,10 @@
|
||||||
color: var(--darkGray);
|
color: var(--darkGray);
|
||||||
font-size: $smallFontSize;
|
font-size: $smallFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.isMissing {
|
||||||
|
margin-left: 15px;
|
||||||
|
color: var(--dangerColor);
|
||||||
|
font-size: $smallFontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'artistFolder': string;
|
'artistFolder': string;
|
||||||
'freeSpace': string;
|
'freeSpace': string;
|
||||||
|
'isMissing': string;
|
||||||
'isMobile': string;
|
'isMobile': string;
|
||||||
'optionText': string;
|
'optionText': string;
|
||||||
'value': string;
|
'value': string;
|
||||||
|
|
|
@ -11,6 +11,7 @@ function RootFolderSelectInputOption(props) {
|
||||||
value,
|
value,
|
||||||
name,
|
name,
|
||||||
freeSpace,
|
freeSpace,
|
||||||
|
isMissing,
|
||||||
artistFolder,
|
artistFolder,
|
||||||
isMobile,
|
isMobile,
|
||||||
isWindows,
|
isWindows,
|
||||||
|
@ -19,9 +20,7 @@ function RootFolderSelectInputOption(props) {
|
||||||
|
|
||||||
const slashCharacter = isWindows ? '\\' : '/';
|
const slashCharacter = isWindows ? '\\' : '/';
|
||||||
|
|
||||||
const text = value === '' ? name : `[${name}] ${value}`;
|
const text = name === '' ? value : `[${name}] ${value}`;
|
||||||
|
|
||||||
console.debug(props);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnhancedSelectInputOption
|
<EnhancedSelectInputOption
|
||||||
|
@ -48,11 +47,20 @@ function RootFolderSelectInputOption(props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
freeSpace != null &&
|
freeSpace == null ?
|
||||||
|
null :
|
||||||
<div className={styles.freeSpace}>
|
<div className={styles.freeSpace}>
|
||||||
{formatBytes(freeSpace)} Free
|
{formatBytes(freeSpace)} Free
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isMissing ?
|
||||||
|
<div className={styles.isMissing}>
|
||||||
|
Missing
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</EnhancedSelectInputOption>
|
</EnhancedSelectInputOption>
|
||||||
);
|
);
|
||||||
|
@ -63,9 +71,14 @@ RootFolderSelectInputOption.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
freeSpace: PropTypes.number,
|
freeSpace: PropTypes.number,
|
||||||
|
isMissing: PropTypes.bool,
|
||||||
artistFolder: PropTypes.string,
|
artistFolder: PropTypes.string,
|
||||||
isMobile: PropTypes.bool.isRequired,
|
isMobile: PropTypes.bool.isRequired,
|
||||||
isWindows: PropTypes.bool
|
isWindows: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RootFolderSelectInputOption.defaultProps = {
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
|
||||||
export default RootFolderSelectInputOption;
|
export default RootFolderSelectInputOption;
|
||||||
|
|
|
@ -17,7 +17,7 @@ function RootFolderSelectInputSelectedValue(props) {
|
||||||
|
|
||||||
const slashCharacter = isWindows ? '\\' : '/';
|
const slashCharacter = isWindows ? '\\' : '/';
|
||||||
|
|
||||||
const text = value === '' ? name : `[${name}] ${value}`;
|
const text = name === '' ? value : `[${name}] ${value}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnhancedSelectInputSelectedValue
|
<EnhancedSelectInputSelectedValue
|
||||||
|
@ -59,6 +59,7 @@ RootFolderSelectInputSelectedValue.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
RootFolderSelectInputSelectedValue.defaultProps = {
|
RootFolderSelectInputSelectedValue.defaultProps = {
|
||||||
|
name: '',
|
||||||
includeFreeSpace: true
|
includeFreeSpace: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -223,6 +223,7 @@ function EditImportListModalContent(props) {
|
||||||
name="rootFolderPath"
|
name="rootFolderPath"
|
||||||
helpText={translate('RootFolderPathHelpText')}
|
helpText={translate('RootFolderPathHelpText')}
|
||||||
{...rootFolderPath}
|
{...rootFolderPath}
|
||||||
|
includeMissingValue={true}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Datastore.Events;
|
||||||
|
using NzbDrone.Core.ImportLists;
|
||||||
|
using NzbDrone.Core.Localization;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Music.Events;
|
||||||
|
using NzbDrone.Core.RootFolders;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
[CheckOn(typeof(ModelEvent<RootFolder>))]
|
||||||
|
[CheckOn(typeof(ArtistsDeletedEvent))]
|
||||||
|
[CheckOn(typeof(ArtistMovedEvent))]
|
||||||
|
[CheckOn(typeof(TrackImportedEvent), CheckOnCondition.FailedOnly)]
|
||||||
|
[CheckOn(typeof(TrackImportFailedEvent), 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<string, List<ImportListDefinition>>();
|
||||||
|
|
||||||
|
Console.WriteLine(importLists.ToArray().ToJson());
|
||||||
|
|
||||||
|
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<ImportListDefinition> { 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<ImportListDefinition> importLists)
|
||||||
|
{
|
||||||
|
return $"{rootFolderPath} ({string.Join(", ", importLists.Select(l => l.Name))})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -437,6 +437,8 @@
|
||||||
"ImportFailedInterp": "Import failed: {0}",
|
"ImportFailedInterp": "Import failed: {0}",
|
||||||
"ImportFailures": "Import failures",
|
"ImportFailures": "Import failures",
|
||||||
"ImportListExclusions": "Import List Exclusions",
|
"ImportListExclusions": "Import List Exclusions",
|
||||||
|
"ImportListRootFolderMissingRootHealthCheckMessage": "Missing root folder for import list(s): {0}",
|
||||||
|
"ImportListRootFolderMultipleMissingRootsHealthCheckMessage": "Multiple root folders are missing for import lists: {0}",
|
||||||
"ImportListSettings": "General Import List Settings",
|
"ImportListSettings": "General Import List Settings",
|
||||||
"ImportListSpecificSettings": "Import List Specific Settings",
|
"ImportListSpecificSettings": "Import List Specific Settings",
|
||||||
"ImportListStatusCheckAllClientMessage": "All lists are unavailable due to failures",
|
"ImportListStatusCheckAllClientMessage": "All lists are unavailable due to failures",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue