Fixed: better root folder validation

pull/755/head
ta264 3 years ago
parent 6a79c2f3a1
commit cab92745da

@ -0,0 +1,156 @@
// This is the output of eg. http://192.168.0.3:8089/conversion/book-data/100
const profileData = {
cybookg3: {
name: 'Cybook G3',
description: 'This profile is intended for the Cybook G3. [Screen size: 600 x 800 pixels]'
},
cybook_opus: {
name: 'Cybook Opus',
description: 'This profile is intended for the Cybook Opus. [Screen size: 590 x 775 pixels]'
},
default: {
name: 'Default Output Profile',
description: 'This profile tries to provide sane defaults and is useful if you want to produce a document intended to be read at a computer or on a range of devices. [Screen size: 1600 x 1200 pixels]'
},
generic_eink: {
name: 'Generic e-ink',
description: 'Suitable for use with any e-ink device [Screen size: 590 x 775 pixels]'
},
generic_eink_hd: {
name: 'Generic e-ink HD',
description: 'Suitable for use with any modern high resolution e-ink device [Screen size: unlimited]'
},
generic_eink_large: {
name: 'Generic e-ink large',
description: 'Suitable for use with any large screen e-ink device [Screen size: 600 x 999 pixels]'
},
hanlinv3: {
name: 'Hanlin V3',
description: 'This profile is intended for the Hanlin V3 and its clones. [Screen size: 584 x 754 pixels]'
},
hanlinv5: {
name: 'Hanlin V5',
description: 'This profile is intended for the Hanlin V5 and its clones. [Screen size: 584 x 754 pixels]'
},
illiad: {
name: 'Illiad',
description: 'This profile is intended for the Irex Illiad. [Screen size: 760 x 925 pixels]'
},
ipad: {
name: 'iPad',
description: 'Intended for the iPad and similar devices with a resolution of 768x1024 [Screen size: 768 x 1024 pixels]'
},
ipad3: {
name: 'iPad 3',
description: 'Intended for the iPad 3 and similar devices with a resolution of 1536x2048 [Screen size: 2048 x 1536 pixels]'
},
irexdr1000: {
name: 'IRex Digital Reader 1000',
description: 'This profile is intended for the IRex Digital Reader 1000. [Screen size: 1024 x 1280 pixels]'
},
irexdr800: {
name: 'IRex Digital Reader 800',
description: 'This profile is intended for the IRex Digital Reader 800. [Screen size: 768 x 1024 pixels]'
},
jetbook5: {
name: 'JetBook 5-inch',
description: 'This profile is intended for the 5-inch JetBook. [Screen size: 480 x 640 pixels]'
},
kindle: {
name: 'Kindle',
description: 'This profile is intended for the Amazon Kindle. [Screen size: 525 x 640 pixels]'
},
kindle_dx: {
name: 'Kindle DX',
description: 'This profile is intended for the Amazon Kindle DX. [Screen size: 744 x 1022 pixels]'
},
kindle_fire: {
name: 'Kindle Fire',
description: 'This profile is intended for the Amazon Kindle Fire. [Screen size: 570 x 1016 pixels]'
},
kindle_oasis: {
name: 'Kindle Oasis',
description: 'This profile is intended for the Amazon Kindle Oasis 2017 and above [Screen size: 1264 x 1680 pixels]'
},
kindle_pw: {
name: 'Kindle PaperWhite',
description: 'This profile is intended for the Amazon Kindle PaperWhite 1 and 2 [Screen size: 658 x 940 pixels]'
},
kindle_pw3: {
name: 'Kindle PaperWhite 3',
description: 'This profile is intended for the Amazon Kindle PaperWhite 3 and above [Screen size: 1072 x 1430 pixels]'
},
kindle_voyage: {
name: 'Kindle Voyage',
description: 'This profile is intended for the Amazon Kindle Voyage [Screen size: 1080 x 1430 pixels]'
},
kobo: {
name: 'Kobo Reader',
description: 'This profile is intended for the Kobo Reader. [Screen size: 536 x 710 pixels]'
},
msreader: {
name: 'Microsoft Reader',
description: 'This profile is intended for the Microsoft Reader. [Screen size: 480 x 652 pixels]'
},
mobipocket: {
name: 'Mobipocket Books',
description: 'This profile is intended for the Mobipocket books. [Screen size: 600 x 800 pixels]'
},
nook: {
name: 'Nook',
description: 'This profile is intended for the B&N Nook. [Screen size: 600 x 730 pixels]'
},
nook_color: {
name: 'Nook Color',
description: 'This profile is intended for the B&N Nook Color. [Screen size: 600 x 900 pixels]'
},
nook_hd_plus: {
name: 'Nook HD+',
description: 'Intended for the Nook HD+ and similar tablet devices with a resolution of 1280x1920 [Screen size: 1280 x 1920 pixels]'
},
pocketbook_900: {
name: 'PocketBook Pro 900',
description: 'This profile is intended for the PocketBook Pro 900 series of devices. [Screen size: 810 x 1180 pixels]'
},
pocketbook_pro_912: {
name: 'PocketBook Pro 912',
description: 'This profile is intended for the PocketBook Pro 912 series of devices. [Screen size: 825 x 1200 pixels]'
},
galaxy: {
name: 'Samsung Galaxy',
description: 'Intended for the Samsung Galaxy and similar tablet devices with a resolution of 600x1280 [Screen size: 600 x 1280 pixels]'
},
sony: {
name: 'Sony Reader',
description: 'This profile is intended for the SONY PRS line. The 500/505/600/700 etc. [Screen size: 590 x 775 pixels]'
},
sony300: {
name: 'Sony Reader 300',
description: 'This profile is intended for the SONY PRS-300. [Screen size: 590 x 775 pixels]'
},
sony900: {
name: 'Sony Reader 900',
description: 'This profile is intended for the SONY PRS-900. [Screen size: 600 x 999 pixels]'
},
sony_landscape: {
name: 'Sony Reader Landscape',
description: 'This profile is intended for the SONY PRS line. The 500/505/700 etc, in landscape mode. Mainly useful for comics. [Screen size: 784 x 1012 pixels]'
},
sonyt3: {
name: 'Sony Reader T3',
description: 'This profile is intended for the SONY PRS-T3. [Screen size: 758 x 934 pixels]'
},
tablet: {
name: 'Tablet',
description: 'Intended for generic tablet devices, does no resizing of images [Screen size: unlimited]'
}
};
export const options = Object.entries(profileData).map(([key, object]) => {
return {
key,
value: object.name,
description: object.description
};
});

@ -1,4 +1,5 @@
import * as align from './align';
import * as calibreProfiles from './calibreProfiles';
import * as filterBuilderTypes from './filterBuilderTypes';
import * as filterBuilderValueTypes from './filterBuilderValueTypes';
import filterTypePredicates from './filterTypePredicates';
@ -15,6 +16,7 @@ import * as tooltipPositions from './tooltipPositions';
export {
align,
calibreProfiles,
inputTypes,
filterBuilderTypes,
filterBuilderValueTypes,

@ -15,7 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import { calibreProfiles, icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import styles from './EditRootFolderModalContent.css';
function EditRootFolderModalContent(props) {
@ -55,6 +55,8 @@ function EditRootFolderModalContent(props) {
useSsl
} = item;
const profileHelpText = calibreProfiles.options.find((x) => x.key === outputProfile.value).description;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
@ -191,7 +193,20 @@ function EditRootFolderModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Convert to format</FormLabel>
<FormLabel>
Convert to format
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title="Calibre output format"
body={'Specify the output format. Options are: MOBI, EPUB, AZW3, DOCX, FB2, HTMLZ, LIT, LRF, PDB, PDF, PMLZ, RB, RTF, SNB, TCR, TXT, TXTZ, ZIP'}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
@ -203,12 +218,26 @@ function EditRootFolderModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>Calibre Output Profile</FormLabel>
<FormLabel>
Calibre Output Profile
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title="Calibre output profile"
body={'Specify the output profile. The output profile tells the calibre conversion system how to optimize the created document for the specified device (such as by resizing images for the device screen size). In some cases, an output profile can be used to optimize the output for a particular device, but this is rarely necessary.'}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
type={inputTypes.SELECT}
name="outputProfile"
helpText="Output profile for conversion"
values={calibreProfiles.options}
helpText={profileHelpText}
{...outputProfile}
onChange={onInputChange}
/>

@ -50,7 +50,7 @@ export default {
host: 'localhost',
port: 8080,
useSsl: false,
outputProfile: 0,
outputProfile: 'default',
defaultTags: []
},
isSaving: false,

@ -19,6 +19,7 @@ namespace NzbDrone.Core.Books.Calibre
RuleFor(c => c.Password).NotEmpty().When(c => !string.IsNullOrWhiteSpace(c.Username));
RuleFor(c => c.OutputFormat).Must(x => x.Split(',').All(y => Enum.TryParse<CalibreFormat>(y, true, out _))).WithMessage("Invalid output formats");
RuleFor(c => c.OutputProfile).InclusiveBetween(0, (int)Enum.GetValues(typeof(CalibreProfile)).Cast<CalibreProfile>().Max());
}
}

@ -27,7 +27,7 @@ namespace NzbDrone.Core.Books.Calibre
public enum CalibreProfile
{
Default,
@default,
cybookg3,
cybook_opus,
generic_eink,

@ -166,7 +166,7 @@ namespace NzbDrone.Core.MediaFiles
options.Conversion_options.Output_fmt = format;
if (settings.OutputProfile != (int)CalibreProfile.Default)
if (settings.OutputProfile != (int)CalibreProfile.@default)
{
options.Conversion_options.Options.Output_profile = ((CalibreProfile)settings.OutputProfile).ToString();
}

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Books.Calibre;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Validation;
@ -52,6 +53,11 @@ namespace Readarr.Api.V1.RootFolders
PostValidator.RuleFor(c => c.Path)
.SetValidator(rootFolderValidator);
SharedValidator.RuleFor(c => c)
.Must(x => CalibreLibraryOnlyUsedOnce(x))
.When(x => x.IsCalibreLibrary)
.WithMessage("Calibre library is already configured as a root folder");
SharedValidator.RuleFor(c => c.Name)
.NotEmpty();
@ -68,6 +74,25 @@ namespace Readarr.Api.V1.RootFolders
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => !string.IsNullOrWhiteSpace(c.Username));
SharedValidator.RuleFor(c => c.OutputFormat).Must(x => x.Split(',').All(y => Enum.TryParse<CalibreFormat>(y, true, out _))).When(x => x.OutputFormat.IsNotNullOrWhiteSpace()).WithMessage("Invalid output formats");
SharedValidator.RuleFor(c => c.OutputProfile).IsEnumName(typeof(CalibreProfile));
}
private bool CalibreLibraryOnlyUsedOnce(RootFolderResource settings)
{
var newUri = GetLibraryUri(settings);
return !_rootFolderService.All().Exists(x => x.Id != settings.Id &&
x.CalibreSettings != null &&
GetLibraryUri(x.CalibreSettings) == newUri);
}
private string GetLibraryUri(RootFolderResource settings)
{
return HttpUri.CombinePath(HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase), settings.Library);
}
private string GetLibraryUri(CalibreSettings settings)
{
return HttpUri.CombinePath(HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase), settings.Library);
}
private RootFolderResource GetRootFolder(int id)
@ -96,6 +121,11 @@ namespace Readarr.Api.V1.RootFolders
throw new BadRequestException("Cannot edit root folder path");
}
if (model.IsCalibreLibrary)
{
_calibreProxy.Test(model.CalibreSettings);
}
_rootFolderService.Update(model);
}

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Books;
@ -23,7 +24,7 @@ namespace Readarr.Api.V1.RootFolders
public string Password { get; set; }
public string Library { get; set; }
public string OutputFormat { get; set; }
public int OutputProfile { get; set; }
public string OutputProfile { get; set; }
public bool UseSsl { get; set; }
public bool Accessible { get; set; }
@ -58,7 +59,7 @@ namespace Readarr.Api.V1.RootFolders
Password = model.CalibreSettings?.Password,
Library = model.CalibreSettings?.Library,
OutputFormat = model.CalibreSettings?.OutputFormat,
OutputProfile = model.CalibreSettings?.OutputProfile ?? 0,
OutputProfile = ((CalibreProfile)(model.CalibreSettings?.OutputProfile ?? 0)).ToString(),
UseSsl = model.CalibreSettings?.UseSsl ?? false,
Accessible = model.Accessible,
@ -86,7 +87,7 @@ namespace Readarr.Api.V1.RootFolders
Password = resource.Password,
Library = resource.Library,
OutputFormat = resource.OutputFormat,
OutputProfile = resource.OutputProfile,
OutputProfile = (int)Enum.Parse(typeof(CalibreProfile), resource.OutputProfile, true),
UseSsl = resource.UseSsl
};
}

Loading…
Cancel
Save