New: Option to control which new author books get monitored

pull/1362/head
ta264 2 years ago
parent 1d694af98e
commit c51ae664aa

@ -0,0 +1,27 @@
import React from 'react';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import translate from 'Utilities/String/translate';
function AuthorMonitorNewItemsOptionsPopoverContent() {
return (
<DescriptionList>
<DescriptionListItem
title={translate('AllBooks')}
data="Monitor all new books"
/>
<DescriptionListItem
title={translate('NewBooks')}
data="Monitor new books released after the newest existing book"
/>
<DescriptionListItem
title={translate('None')}
data="Don't monitor any new books"
/>
</DescriptionList>
);
}
export default AuthorMonitorNewItemsOptionsPopoverContent;

@ -1,46 +1,52 @@
import React from 'react';
import Alert from 'Components/Alert';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import translate from 'Utilities/String/translate';
function AuthorMonitoringOptionsPopoverContent() {
return (
<DescriptionList>
<DescriptionListItem
title={translate('AllBooks')}
data="Monitor all books"
/>
<DescriptionListItem
title={translate('FutureBooks')}
data="Monitor books that have not released yet"
/>
<DescriptionListItem
title={translate('MissingBooks')}
data="Monitor books that do not have files or have not released yet"
/>
<DescriptionListItem
title={translate('ExistingBooks')}
data="Monitor books that have files or have not released yet"
/>
<DescriptionListItem
title={translate('FirstBook')}
data="Monitor the first book. All other books will be ignored"
/>
<DescriptionListItem
title={translate('LatestBook')}
data="Monitor the latest book and future books"
/>
<DescriptionListItem
title={translate('None')}
data="No books will be monitored"
/>
</DescriptionList>
<>
<Alert>
This is a one time adjustment to set which books are monitored
</Alert>
<DescriptionList>
<DescriptionListItem
title={translate('AllBooks')}
data="Monitor all books"
/>
<DescriptionListItem
title={translate('FutureBooks')}
data="Monitor books that have not released yet"
/>
<DescriptionListItem
title={translate('MissingBooks')}
data="Monitor books that do not have files or have not released yet"
/>
<DescriptionListItem
title={translate('ExistingBooks')}
data="Monitor books that have files or have not released yet"
/>
<DescriptionListItem
title={translate('FirstBook')}
data="Monitor the first book. All other books will be ignored"
/>
<DescriptionListItem
title={translate('LatestBook')}
data="Monitor the latest book and future books"
/>
<DescriptionListItem
title={translate('None')}
data="No books will be monitored"
/>
</DescriptionList>
</>
);
}

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AuthorMetadataProfilePopoverContent from 'AddAuthor/AuthorMetadataProfilePopoverContent';
import AuthorMonitorNewItemsOptionsPopoverContent from 'AddAuthor/AuthorMonitorNewItemsOptionsPopoverContent';
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@ -73,6 +74,7 @@ class EditAuthorModalContent extends Component {
const {
monitored,
monitorNewItems,
qualityProfileId,
metadataProfileId,
path,
@ -101,6 +103,31 @@ class EditAuthorModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitorNewItems')}
body={<AuthorMonitorNewItemsOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_NEW_ITEMS_SELECT}
name="monitorNewItems"
helpText={translate('MonitorNewItemsHelpText')}
{...monitorNewItems}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('QualityProfile')}

@ -39,6 +39,7 @@ function createMapStateToProps() {
const authorSettings = _.pick(author, [
'monitored',
'monitorNewItems',
'qualityProfileId',
'metadataProfileId',
'path',

@ -1,11 +1,23 @@
.footer {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.dropdownContainer {
display: flex;
flex-wrap: wrap;
margin-bottom: 10px;
}
.inputContainer {
flex: 1;
margin-right: 20px;
min-width: 150px;
}
.buttonContainer {
display: flex;
justify-content: flex-end;
flex-grow: 1;
}
@ -24,12 +36,14 @@
composes: button from '~Components/Link/SpinnerButton.css';
margin-right: 10px;
margin-bottom: 10px;
height: 35px;
}
.deleteSelectedButton {
composes: button from '~Components/Link/SpinnerButton.css';
margin-bottom: 10px;
margin-left: 50px;
height: 35px;
}
@ -48,6 +62,10 @@
}
@media only screen and (max-width: $breakpointSmall) {
.dropdownContainer {
display: block;
}
.inputContainer {
margin-right: 0;
}
@ -61,6 +79,7 @@
}
.buttons {
display: block;
justify-content: space-between;
}

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import MetadataProfileSelectInputConnector from 'Components/Form/MetadataProfileSelectInputConnector';
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector';
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
import SelectInput from 'Components/Form/SelectInput';
@ -26,6 +27,7 @@ class AuthorEditorFooter extends Component {
this.state = {
monitored: NO_CHANGE,
monitorNewItems: NO_CHANGE,
qualityProfileId: NO_CHANGE,
metadataProfileId: NO_CHANGE,
rootFolderPath: NO_CHANGE,
@ -46,6 +48,7 @@ class AuthorEditorFooter extends Component {
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitored: NO_CHANGE,
monitorNewItems: NO_CHANGE,
qualityProfileId: NO_CHANGE,
metadataProfileId: NO_CHANGE,
rootFolderPath: NO_CHANGE,
@ -145,6 +148,7 @@ class AuthorEditorFooter extends Component {
const {
monitored,
monitorNewItems,
qualityProfileId,
metadataProfileId,
rootFolderPath,
@ -163,83 +167,99 @@ class AuthorEditorFooter extends Component {
return (
<PageContentFooter>
<div className={styles.inputContainer}>
<AuthorEditorFooterLabel
label={translate('MonitorAuthor')}
isSaving={isSaving && monitored !== NO_CHANGE}
/>
<SelectInput
name="monitored"
value={monitored}
values={monitoredOptions}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.footer}>
<div className={styles.dropdownContainer}>
<div className={styles.inputContainer}>
<AuthorEditorFooterLabel
label={translate('MonitorAuthor')}
isSaving={isSaving && monitored !== NO_CHANGE}
/>
<SelectInput
name="monitored"
value={monitored}
values={monitoredOptions}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label={translate('QualityProfile')}
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/>
<QualityProfileSelectInputConnector
name="qualityProfileId"
value={qualityProfileId}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<AuthorEditorFooterLabel
label={translate('MonitorNewItems')}
isSaving={isSaving && monitored !== NO_CHANGE}
/>
<MonitorNewItemsSelectInput
name="monitorNewItems"
value={monitorNewItems}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label={translate('MetadataProfile')}
isSaving={isSaving && metadataProfileId !== NO_CHANGE}
/>
<MetadataProfileSelectInputConnector
name="metadataProfileId"
value={metadataProfileId}
includeNoChange={true}
includeNone={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<AuthorEditorFooterLabel
label={translate('QualityProfile')}
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/>
<QualityProfileSelectInputConnector
name="qualityProfileId"
value={qualityProfileId}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label={translate('RootFolder')}
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/>
<RootFolderSelectInputConnector
name="rootFolderPath"
value={rootFolderPath}
includeNoChange={true}
isDisabled={!selectedCount}
selectedValueOptions={{ includeFreeSpace: false }}
onChange={this.onInputChange}
/>
</div>
<div
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label={translate('MetadataProfile')}
isSaving={isSaving && metadataProfileId !== NO_CHANGE}
/>
<MetadataProfileSelectInputConnector
name="metadataProfileId"
value={metadataProfileId}
includeNoChange={true}
includeNone={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div
className={styles.inputContainer}
>
<AuthorEditorFooterLabel
label={translate('RootFolder')}
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/>
<RootFolderSelectInputConnector
name="rootFolderPath"
value={rootFolderPath}
includeNoChange={true}
isDisabled={!selectedCount}
selectedValueOptions={{ includeFreeSpace: false }}
onChange={this.onInputChange}
/>
</div>
</div>
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<AuthorEditorFooterLabel
label={translate('SelectedCountAuthorsSelectedInterp', [selectedCount])}
isSaving={false}
/>
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<AuthorEditorFooterLabel
label={translate('SelectedCountAuthorsSelectedInterp', [selectedCount])}
isSaving={false}
/>
<div className={styles.buttons}>
<div className={styles.buttons}>
<div>
<SpinnerButton
className={styles.organizeSelectedButton}
kind={kinds.WARNING}
@ -268,17 +288,18 @@ class AuthorEditorFooter extends Component {
>
Set Readarr Tags
</SpinnerButton>
</div>
<SpinnerButton
className={styles.deleteSelectedButton}
kind={kinds.DANGER}
isSpinning={isDeleting}
isDisabled={!selectedCount || isDeleting}
onPress={this.onDeleteSelectedPress}
>
Delete
</SpinnerButton>
<SpinnerButton
className={styles.deleteSelectedButton}
kind={kinds.DANGER}
isSpinning={isDeleting}
isDisabled={!selectedCount || isDeleting}
onPress={this.onDeleteSelectedPress}
>
Delete
</SpinnerButton>
</div>
</div>
</div>
</div>

@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@ -10,7 +11,7 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const NO_CHANGE = 'noChange';
@ -92,6 +93,12 @@ class MonitoringOptionsModalContent extends Component {
</ModalHeader>
<ModalBody>
<Alert kind={kinds.INFO}>
<div>
{translate('MonitorBookExistingOnlyWarning')}
</div>
</Alert>
<Form {...otherProps}>
<FormGroup>
<FormLabel>{translate('Monitoring')}</FormLabel>

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import MonitorBooksSelectInput from 'Components/Form/MonitorBooksSelectInput';
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
@ -19,7 +20,8 @@ class BookshelfFooter extends Component {
this.state = {
monitored: NO_CHANGE,
monitor: NO_CHANGE
monitor: NO_CHANGE,
monitorNewItems: NO_CHANGE
};
}
@ -32,7 +34,8 @@ class BookshelfFooter extends Component {
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitored: NO_CHANGE,
monitor: NO_CHANGE
monitor: NO_CHANGE,
monitorNewItems: NO_CHANGE
});
}
}
@ -47,7 +50,8 @@ class BookshelfFooter extends Component {
onUpdateSelectedPress = () => {
const {
monitor,
monitored
monitored,
monitorNewItems
} = this.state;
const changes = {};
@ -60,6 +64,10 @@ class BookshelfFooter extends Component {
changes.monitor = monitor;
}
if (monitorNewItems !== NO_CHANGE) {
changes.monitorNewItems = monitorNewItems;
}
this.props.onUpdateSelectedPress(changes);
}
@ -74,7 +82,8 @@ class BookshelfFooter extends Component {
const {
monitored,
monitor
monitor,
monitorNewItems
} = this.state;
const monitoredOptions = [
@ -83,7 +92,9 @@ class BookshelfFooter extends Component {
{ key: 'unmonitored', value: 'Unmonitored' }
];
const noChanges = monitored === NO_CHANGE && monitor === NO_CHANGE;
const noChanges = monitored === NO_CHANGE &&
monitor === NO_CHANGE &&
monitorNewItems === NO_CHANGE;
return (
<PageContentFooter>
@ -103,7 +114,7 @@ class BookshelfFooter extends Component {
<div className={styles.inputContainer}>
<div className={styles.label}>
Monitor Books
Monitor Existing Books
</div>
<MonitorBooksSelectInput
@ -115,6 +126,20 @@ class BookshelfFooter extends Component {
/>
</div>
<div className={styles.inputContainer}>
<div className={styles.label}>
Monitor New Books
</div>
<MonitorNewItemsSelectInput
name="monitorNewItems"
value={monitorNewItems}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div>
<div className={styles.label}>
{selectedCount} Author(s) Selected

@ -16,6 +16,7 @@ import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import KeyValueListInput from './KeyValueListInput';
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
import MonitorBooksSelectInput from './MonitorBooksSelectInput';
import MonitorNewItemsSelectInput from './MonitorNewItemsSelectInput';
import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector';
import PasswordInput from './PasswordInput';
@ -51,6 +52,9 @@ function getComponent(type) {
case inputTypes.MONITOR_BOOKS_SELECT:
return MonitorBooksSelectInput;
case inputTypes.MONITOR_NEW_ITEMS_SELECT:
return MonitorNewItemsSelectInput;
case inputTypes.NUMBER:
return NumberInput;

@ -0,0 +1,50 @@
import PropTypes from 'prop-types';
import React from 'react';
import monitorNewItemsOptions from 'Utilities/Author/monitorNewItemsOptions';
import SelectInput from './SelectInput';
function MonitorNewItemsSelectInput(props) {
const {
includeNoChange,
includeMixed,
...otherProps
} = props;
const values = [...monitorNewItemsOptions];
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: 'No Change',
disabled: true
});
}
if (includeMixed) {
values.unshift({
key: 'mixed',
value: '(Mixed)',
disabled: true
});
}
return (
<SelectInput
values={values}
{...otherProps}
/>
);
}
MonitorNewItemsSelectInput.propTypes = {
includeNoChange: PropTypes.bool.isRequired,
includeMixed: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};
MonitorNewItemsSelectInput.defaultProps = {
includeNoChange: false,
includeMixed: false
};
export default MonitorNewItemsSelectInput;

@ -5,6 +5,7 @@ export const DEVICE = 'device';
export const BOOKSHELF = 'bookshelf';
export const KEY_VALUE_LIST = 'keyValueList';
export const MONITOR_BOOKS_SELECT = 'monitorBooksSelect';
export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect';
export const NUMBER = 'number';
export const OAUTH = 'oauth';
export const PASSWORD = 'password';
@ -29,6 +30,7 @@ export const all = [
BOOKSHELF,
KEY_VALUE_LIST,
MONITOR_BOOKS_SELECT,
MONITOR_NEW_ITEMS_SELECT,
NUMBER,
OAUTH,
PASSWORD,

@ -57,6 +57,7 @@ class AddNewAuthorModalContentConnector extends Component {
foreignAuthorId,
rootFolderPath,
monitor,
monitorNewItems,
qualityProfileId,
metadataProfileId,
tags
@ -66,6 +67,7 @@ class AddNewAuthorModalContentConnector extends Component {
foreignAuthorId,
rootFolderPath: rootFolderPath.value,
monitor: monitor.value,
monitorNewItems: monitorNewItems.value,
qualityProfileId: qualityProfileId.value,
metadataProfileId: metadataProfileId.value,
tags: tags.value,
@ -91,6 +93,7 @@ AddNewAuthorModalContentConnector.propTypes = {
foreignAuthorId: PropTypes.string.isRequired,
rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired,
monitorNewItems: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object,
metadataProfileId: PropTypes.object,
tags: PropTypes.object.isRequired,

@ -58,6 +58,7 @@ class AddNewBookModalContentConnector extends Component {
foreignBookId,
rootFolderPath,
monitor,
monitorNewItems,
qualityProfileId,
metadataProfileId,
tags
@ -67,6 +68,7 @@ class AddNewBookModalContentConnector extends Component {
foreignBookId,
rootFolderPath: rootFolderPath.value,
monitor: monitor.value,
monitorNewItems: monitorNewItems.value,
qualityProfileId: qualityProfileId.value,
metadataProfileId: metadataProfileId.value,
tags: tags.value,
@ -93,6 +95,7 @@ AddNewBookModalContentConnector.propTypes = {
foreignBookId: PropTypes.string.isRequired,
rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired,
monitorNewItems: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object,
metadataProfileId: PropTypes.object,
tags: PropTypes.object.isRequired,

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import AuthorMetadataProfilePopoverContent from 'AddAuthor/AuthorMetadataProfilePopoverContent';
import AuthorMonitoringOptionsPopoverContent from 'AddAuthor/AuthorMonitoringOptionsPopoverContent';
import AuthorMonitorNewItemsOptionsPopoverContent from 'AddAuthor/AuthorMonitorNewItemsOptionsPopoverContent';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@ -32,6 +33,7 @@ class AddAuthorOptionsForm extends Component {
const {
rootFolderPath,
monitor,
monitorNewItems,
qualityProfileId,
metadataProfileId,
includeNoneMetadataProfile,
@ -77,12 +79,38 @@ class AddAuthorOptionsForm extends Component {
<FormInputGroup
type={inputTypes.MONITOR_BOOKS_SELECT}
name="monitor"
helpText={translate('MonitoringOptionsHelpText')}
onChange={onInputChange}
includeSpecificBook={includeSpecificBookMonitor}
{...monitor}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitorNewItems')}
body={<AuthorMonitorNewItemsOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_NEW_ITEMS_SELECT}
name="monitorNewItems"
helpText={translate('MonitorNewItemsHelpText')}
{...monitorNewItems}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('QualityProfile')}
@ -145,6 +173,7 @@ class AddAuthorOptionsForm extends Component {
AddAuthorOptionsForm.propTypes = {
rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired,
monitorNewItems: PropTypes.string.isRequired,
qualityProfileId: PropTypes.object,
metadataProfileId: PropTypes.object,
showMetadataProfile: PropTypes.bool.isRequired,

@ -1,8 +1,10 @@
import PropTypes from 'prop-types';
import React from 'react';
import AuthorMonitorNewItemsOptionsPopoverContent from 'AddAuthor/AuthorMonitorNewItemsOptionsPopoverContent';
import Alert from 'Components/Alert';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import FieldSet from 'Components/FieldSet';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@ -76,6 +78,7 @@ function EditImportListModalContent(props) {
shouldMonitorExisting,
shouldSearch,
rootFolderPath,
monitorNewItems,
qualityProfileId,
metadataProfileId,
tags,
@ -114,148 +117,178 @@ function EditImportListModalContent(props) {
{message.value.message}
</Alert>
}
<FormGroup>
<FormLabel>
{translate('Name')}
</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="name"
{...name}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableAutomaticAdd')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableAutomaticAdd"
helpText={translate('EnableAutomaticAddHelpText')}
{...enableAutomaticAdd}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
Monitor
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitoringOptions')}
body={<ImportListMonitoringOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
<FieldSet legend={translate('ImportListSettings')} >
<FormGroup>
<FormLabel>
{translate('Name')}
</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="name"
{...name}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableAutomaticAdd')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableAutomaticAdd"
helpText={translate('EnableAutomaticAddHelpText')}
{...enableAutomaticAdd}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
Monitor
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitoringOptions')}
body={<ImportListMonitoringOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="shouldMonitor"
values={monitorOptions}
helpText={translate('ShouldMonitorHelpText')}
{...shouldMonitor}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShouldMonitorExisting')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="shouldMonitorExisting"
helpText={translate('ShouldMonitorExistingHelpText')}
{...shouldMonitorExisting}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('SearchForNewItems')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="shouldSearch"
helpText={translate('ShouldSearchHelpText')}
{...shouldSearch}
onChange={onInputChange}
/>
</FormGroup>
</FieldSet>
<FieldSet legend={translate('AddedAuthorSettings')} >
<FormGroup>
<FormLabel>
{translate('RootFolder')}
</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
helpText={translate('RootFolderPathHelpText')}
{...rootFolderPath}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitorNewItems')}
body={<AuthorMonitorNewItemsOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_NEW_ITEMS_SELECT}
name="monitorNewItems"
helpText={translate('MonitorNewItemsHelpText')}
{...monitorNewItems}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('QualityProfile')}
</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId"
helpText={translate('QualityProfileIdHelpText')}
{...qualityProfileId}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup className={showMetadataProfile ? undefined : styles.hideMetadataProfile}>
<FormLabel>
{translate('MetadataProfile')}
</FormLabel>
<FormInputGroup
type={inputTypes.METADATA_PROFILE_SELECT}
name="metadataProfileId"
helpText={translate('MetadataProfileIdHelpText')}
{...metadataProfileId}
includeNone={true}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ReadarrTags')}
</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText={translate('TagsHelpText')}
{...tags}
onChange={onInputChange}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="shouldMonitor"
values={monitorOptions}
helpText={translate('ShouldMonitorHelpText')}
{...shouldMonitor}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShouldMonitorExisting')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="shouldMonitorExisting"
helpText={translate('ShouldMonitorExistingHelpText')}
{...shouldMonitorExisting}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('SearchForNewItems')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="shouldSearch"
helpText={translate('ShouldSearchHelpText')}
{...shouldSearch}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('RootFolder')}
</FormLabel>
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
helpText={translate('RootFolderPathHelpText')}
{...rootFolderPath}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('QualityProfile')}
</FormLabel>
<FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId"
helpText={translate('QualityProfileIdHelpText')}
{...qualityProfileId}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup className={showMetadataProfile ? undefined : styles.hideMetadataProfile}>
<FormLabel>
{translate('MetadataProfile')}
</FormLabel>
<FormInputGroup
type={inputTypes.METADATA_PROFILE_SELECT}
name="metadataProfileId"
helpText={translate('MetadataProfileIdHelpText')}
{...metadataProfileId}
includeNone={true}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ReadarrTags')}
</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText={translate('TagsHelpText')}
{...tags}
onChange={onInputChange}
/>
</FormGroup>
</FormGroup>
</FieldSet>
{
!!fields && !!fields.length &&
<div>
<FieldSet legend={translate('ImportListSpecificSettings')} >
{
fields.map((field) => {
return (
@ -271,7 +304,7 @@ function EditImportListModalContent(props) {
);
})
}
</div>
</FieldSet>
}
</Form>

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import AuthorMetadataProfilePopoverContent from 'AddAuthor/AuthorMetadataProfilePopoverContent';
import AuthorMonitoringOptionsPopoverContent from 'AddAuthor/AuthorMonitoringOptionsPopoverContent';
import AuthorMonitorNewItemsOptionsPopoverContent from 'AddAuthor/AuthorMonitorNewItemsOptionsPopoverContent';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@ -43,6 +44,7 @@ function EditRootFolderModalContent(props) {
defaultQualityProfileId,
defaultMetadataProfileId,
defaultMonitorOption,
defaultNewItemMonitorOption,
defaultTags,
isCalibreLibrary,
host,
@ -295,7 +297,7 @@ function EditRootFolderModalContent(props) {
<FormGroup>
<FormLabel>
Monitor
{translate('Monitor')}
<Popover
anchor={
@ -320,6 +322,31 @@ function EditRootFolderModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitorNewItems')}
body={<AuthorMonitorNewItemsOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_NEW_ITEMS_SELECT}
name="defaultNewItemMonitorOption"
{...defaultNewItemMonitorOption}
onChange={onInputChange}
helpText={translate('MonitorNewItemsHelpText')}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('QualityProfile')}

@ -4,7 +4,6 @@ import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import { filterPredicates, filters } from './authorActions';
import { set } from './baseActions';
import { fetchBooks } from './bookActions';
import createHandleActions from './Creators/createHandleActions';
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
@ -97,7 +96,8 @@ export const actionHandlers = handleThunks({
const {
authorIds,
monitored,
monitor
monitor,
monitorNewItems
} = payload;
const authors = [];
@ -122,14 +122,13 @@ export const actionHandlers = handleThunks({
method: 'POST',
data: JSON.stringify({
authors,
monitoringOptions: { monitor }
monitoringOptions: { monitor },
monitorNewItems
}),
dataType: 'json'
}).request;
promise.done((data) => {
dispatch(fetchBooks());
dispatch(set({
section,
isSaving: false,

@ -3,6 +3,7 @@ function getNewAuthor(author, payload) {
const {
rootFolderPath,
monitor,
monitorNewItems,
qualityProfileId,
metadataProfileId,
tags,
@ -16,6 +17,7 @@ function getNewAuthor(author, payload) {
author.addOptions = addOptions;
author.monitored = true;
author.monitorNewItems = monitorNewItems;
author.qualityProfileId = qualityProfileId;
author.metadataProfileId = metadataProfileId;
author.rootFolderPath = rootFolderPath;

@ -0,0 +1,7 @@
const monitorNewItemsOptions = [
{ key: 'all', value: 'All Books' },
{ key: 'none', value: 'None' },
{ key: 'new', value: 'New' }
];
export default monitorNewItemsOptions;

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Books;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.BookTests
{
[TestFixture]
public class MonitorNewBookServiceFixture : CoreTest<MonitorNewBookService>
{
private List<Book> _books;
[SetUp]
public void Setup()
{
_books = Builder<Book>.CreateListOfSize(4)
.All()
.With(e => e.Monitored = true)
.With(e => e.ReleaseDate = DateTime.UtcNow.AddDays(-7))
//Future
.TheFirst(1)
.With(e => e.ReleaseDate = DateTime.UtcNow.AddDays(7))
//Future/TBA
.TheNext(1)
.With(e => e.ReleaseDate = null)
.Build()
.ToList();
}
[Test]
public void should_monitor_with_all()
{
foreach (var book in _books)
{
Subject.ShouldMonitorNewBook(book, _books, NewItemMonitorTypes.All).Should().BeTrue();
}
}
[Test]
public void should_not_monitor_with_none()
{
foreach (var book in _books)
{
Subject.ShouldMonitorNewBook(book, _books, NewItemMonitorTypes.None).Should().BeFalse();
}
}
[Test]
public void should_only_monitor_new_with_new()
{
Subject.ShouldMonitorNewBook(_books[0], _books, NewItemMonitorTypes.New).Should().BeTrue();
foreach (var book in _books.Skip(1))
{
Subject.ShouldMonitorNewBook(book, _books, NewItemMonitorTypes.New).Should().BeFalse();
}
}
}
}

@ -62,7 +62,7 @@ namespace NzbDrone.Core.Test.MusicTests
Mocker.GetMock<IMetadataProfileService>()
.Setup(s => s.FilterBooks(It.IsAny<Author>(), It.IsAny<int>()))
.Returns(_books);
.Returns(_remoteBooks);
Mocker.GetMock<IProvideAuthorInfo>()
.Setup(s => s.GetAuthorAndBooks(It.IsAny<string>(), It.IsAny<double>()))
@ -83,6 +83,10 @@ namespace NzbDrone.Core.Test.MusicTests
Mocker.GetMock<IRootFolderService>()
.Setup(x => x.All())
.Returns(new List<RootFolder>());
Mocker.GetMock<IMonitorNewBookService>()
.Setup(x => x.ShouldMonitorNewBook(It.IsAny<Book>(), It.IsAny<List<Book>>(), It.IsAny<NewItemMonitorTypes>()))
.Returns(true);
}
private void GivenNewAuthorInfo(Author author)
@ -151,6 +155,29 @@ namespace NzbDrone.Core.Test.MusicTests
VerifyEventPublished<AuthorRefreshCompleteEvent>();
}
[Test]
public void should_call_new_book_monitor_service_when_adding_book()
{
var newBook = Builder<Book>.CreateNew()
.With(x => x.Id = 0)
.With(x => x.ForeignBookId = "3")
.Build();
_remoteBooks.Add(newBook);
var newAuthorInfo = _author.JsonClone();
newAuthorInfo.Metadata = _author.Metadata.Value.JsonClone();
newAuthorInfo.Books = _remoteBooks;
GivenNewAuthorInfo(newAuthorInfo);
GivenBooksForRefresh(_books);
AllowAuthorUpdate();
Subject.Execute(new RefreshAuthorCommand(_author.Id));
Mocker.GetMock<IMonitorNewBookService>()
.Verify(x => x.ShouldMonitorNewBook(newBook, _books, _author.MonitorNewItems), Times.Once());
}
[Test]
public void should_log_error_and_delete_if_musicbrainz_id_not_found_and_author_has_no_files()
{

@ -20,6 +20,7 @@ namespace NzbDrone.Core.Books
public int AuthorMetadataId { get; set; }
public string CleanName { get; set; }
public bool Monitored { get; set; }
public NewItemMonitorTypes MonitorNewItems { get; set; }
public DateTime? LastInfoSync { get; set; }
public string Path { get; set; }
public string RootFolderPath { get; set; }
@ -70,6 +71,7 @@ namespace NzbDrone.Core.Books
Id = other.Id;
AuthorMetadataId = other.AuthorMetadataId;
Monitored = other.Monitored;
MonitorNewItems = other.MonitorNewItems;
LastInfoSync = other.LastInfoSync;
Path = other.Path;
RootFolderPath = other.RootFolderPath;
@ -93,6 +95,7 @@ namespace NzbDrone.Core.Books
AddOptions = other.AddOptions;
RootFolderPath = other.RootFolderPath;
Monitored = other.Monitored;
MonitorNewItems = other.MonitorNewItems;
}
}
}

@ -0,0 +1,14 @@
namespace NzbDrone.Core.Books
{
public enum MonitorTypes
{
All,
Future,
Missing,
Existing,
Latest,
First,
None,
Unknown
}
}

@ -14,16 +14,4 @@ namespace NzbDrone.Core.Books
public List<string> BooksToMonitor { get; set; }
public bool Monitored { get; set; }
}
public enum MonitorTypes
{
All,
Future,
Missing,
Existing,
Latest,
First,
None,
Unknown
}
}

@ -0,0 +1,9 @@
namespace NzbDrone.Core.Books
{
public enum NewItemMonitorTypes
{
All,
None,
New
}
}

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
namespace NzbDrone.Core.Books
{
public interface IMonitorNewBookService
{
bool ShouldMonitorNewBook(Book addedBook, List<Book> existingBooks, NewItemMonitorTypes author);
}
public class MonitorNewBookService : IMonitorNewBookService
{
private readonly Logger _logger;
public MonitorNewBookService(Logger logger)
{
_logger = logger;
}
public bool ShouldMonitorNewBook(Book addedBook, List<Book> existingBooks, NewItemMonitorTypes monitorNewItems)
{
if (monitorNewItems == NewItemMonitorTypes.None)
{
return false;
}
if (monitorNewItems == NewItemMonitorTypes.All)
{
return true;
}
if (monitorNewItems == NewItemMonitorTypes.New)
{
var newest = existingBooks.OrderByDescending(x => x.ReleaseDate ?? DateTime.MinValue).FirstOrDefault()?.ReleaseDate ?? DateTime.MinValue;
return (addedBook.ReleaseDate ?? DateTime.MinValue) >= newest;
}
throw new NotImplementedException($"Unknown new item monitor type {monitorNewItems}");
}
}
}

@ -42,6 +42,7 @@ namespace NzbDrone.Core.Books
private readonly IHistoryService _historyService;
private readonly IRootFolderService _rootFolderService;
private readonly ICheckIfAuthorShouldBeRefreshed _checkIfAuthorShouldBeRefreshed;
private readonly IMonitorNewBookService _monitorNewBookService;
private readonly IConfigService _configService;
private readonly IImportListExclusionService _importListExclusionService;
private readonly Logger _logger;
@ -59,6 +60,7 @@ namespace NzbDrone.Core.Books
IHistoryService historyService,
IRootFolderService rootFolderService,
ICheckIfAuthorShouldBeRefreshed checkIfAuthorShouldBeRefreshed,
IMonitorNewBookService monitorNewBookService,
IConfigService configService,
IImportListExclusionService importListExclusionService,
Logger logger)
@ -76,6 +78,7 @@ namespace NzbDrone.Core.Books
_historyService = historyService;
_rootFolderService = rootFolderService;
_checkIfAuthorShouldBeRefreshed = checkIfAuthorShouldBeRefreshed;
_monitorNewBookService = monitorNewBookService;
_configService = configService;
_importListExclusionService = importListExclusionService;
_logger = logger;
@ -264,6 +267,14 @@ namespace NzbDrone.Core.Books
remote.UseDbFieldsFrom(local);
}
protected override void ProcessChildren(Author entity, SortedChildren children)
{
foreach (var book in children.Added)
{
book.Monitored = _monitorNewBookService.ShouldMonitorNewBook(book, children.UpToDate, entity.MonitorNewItems);
}
}
protected override void AddChildren(List<Book> children)
{
_bookService.InsertMany(children);

@ -93,6 +93,11 @@ namespace NzbDrone.Core.Books
protected abstract void PrepareNewChild(TChild child, TEntity entity);
protected abstract void PrepareExistingChild(TChild local, TChild remote, TEntity entity);
protected virtual void ProcessChildren(TEntity entity, SortedChildren children)
{
}
protected abstract void AddChildren(List<TChild> children);
protected abstract bool RefreshChildren(SortedChildren localChildren, List<TChild> remoteChildren, Author remoteData, bool forceChildRefresh, bool forceUpdateFileTags, DateTime? lastUpdate);
@ -277,6 +282,8 @@ namespace NzbDrone.Core.Books
sortedChildren.Deleted.Count);
}
ProcessChildren(entity, sortedChildren);
// Add in the new children (we have checked that foreign IDs don't clash)
AddChildren(sortedChildren.Added);

@ -0,0 +1,16 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(19)]
public class AddNewItemMonitorType : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Authors").AddColumn("MonitorNewItems").AsInt32().WithDefaultValue(0);
Alter.Table("RootFolders").AddColumn("DefaultNewItemMonitorOption").AsInt32().WithDefaultValue(0);
Alter.Table("ImportLists").AddColumn("MonitorNewItems").AsInt32().WithDefaultValue(0);
}
}
}

@ -1,3 +1,4 @@
using NzbDrone.Core.Books;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.ImportLists
@ -8,6 +9,7 @@ namespace NzbDrone.Core.ImportLists
public ImportListMonitorType ShouldMonitor { get; set; }
public bool ShouldMonitorExisting { get; set; }
public bool ShouldSearch { get; set; }
public NewItemMonitorTypes MonitorNewItems { get; set; }
public int ProfileId { get; set; }
public int MetadataProfileId { get; set; }
public string RootFolderPath { get; set; }

@ -227,6 +227,7 @@ namespace NzbDrone.Core.ImportLists
var toAddAuthor = new Author
{
Monitored = monitored,
MonitorNewItems = importList.MonitorNewItems,
RootFolderPath = importList.RootFolderPath,
QualityProfileId = importList.ProfileId,
MetadataProfileId = importList.MetadataProfileId,

@ -5,6 +5,7 @@
"About": "About",
"Absolute": "Absolute",
"Actions": "Actions",
"AddedAuthorSettings": "Added Author Settings",
"AddImportListExclusionHelpText": "Prevent book from being added to Readarr by Import Lists or Author Refresh",
"AddingTag": "Adding tag",
"AddListExclusion": "Add List Exclusion",
@ -140,7 +141,7 @@
"Dates": "Dates",
"DBMigration": "DB Migration",
"DefaultMetadataProfileIdHelpText": "Default Metadata Profile for authors detected in this folder",
"DefaultMonitorOptionHelpText": "Default Monitoring Options for books by authors detected in this folder",
"DefaultMonitorOptionHelpText": "Which books should be monitored on initial add for authors detected in this folder",
"DefaultQualityProfileIdHelpText": "Default Quality Profile for authors detected in this folder",
"DefaultReadarrTags": "Default Readarr Tags",
"DefaultTagsHelpText": "Default Readarr Tags for authors detected in this folder",
@ -291,7 +292,8 @@
"Importing": "Importing",
"ImportListExclusions": "Import List Exclusions",
"ImportLists": "Import Lists",
"ImportListSettings": "Import List Settings",
"ImportListSettings": "General Import List Settings",
"ImportListSpecificSettings": "Import List Specific Settings",
"IncludeHealthWarningsHelpText": "Include Health Warnings",
"IncludePreferredWhenRenaming": "Include Preferred when Renaming",
"IncludeUnknownAuthorItemsHelpText": "Show items without a author in the queue, this could include removed authors, movies or anything else in Readarr's category",
@ -381,12 +383,16 @@
"Mode": "Mode",
"MonitorAuthor": "Monitor Author",
"MonitorBook": "Monitor Book",
"MonitorBookExistingOnlyWarning": "This is a one off adjustment of the monitored setting for each book. Use the option under Author/Edit to control what happens for newly added books",
"Monitored": "Monitored",
"MonitoredAuthorIsMonitored": "Author is monitored",
"MonitoredAuthorIsUnmonitored": "Author is unmonitored",
"MonitoredHelpText": "Readarr will search for and download book",
"Monitoring": "Monitoring",
"MonitoringOptions": "Monitoring Options",
"MonitoringOptionsHelpText": "Which books should be monitored after the author is added (one-time adjustment)",
"MonitorNewItems": "Monitor New Books",
"MonitorNewItemsHelpText": "Which new books should be monitored",
"MonoVersion": "Mono Version",
"MoreInfo": "More Info",
"MusicBrainzAuthorID": "MusicBrainz Author ID",
@ -404,6 +410,7 @@
"NamingSettings": "Naming Settings",
"NETCore": ".NET Core",
"New": "New",
"NewBooks": "New Books",
"NoBackupsAreAvailable": "No backups are available",
"NoHistory": "No history.",
"NoHistoryBlocklist": "No history blocklist",

@ -328,6 +328,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport
author.MetadataProfileId = rootFolder.DefaultMetadataProfileId;
author.QualityProfileId = rootFolder.DefaultQualityProfileId;
author.Monitored = rootFolder.DefaultMonitorOption != MonitorTypes.None;
author.MonitorNewItems = rootFolder.DefaultNewItemMonitorOption;
author.Tags = rootFolder.DefaultTags;
author.AddOptions = new AddAuthorOptions
{

@ -12,6 +12,7 @@ namespace NzbDrone.Core.RootFolders
public int DefaultMetadataProfileId { get; set; }
public int DefaultQualityProfileId { get; set; }
public MonitorTypes DefaultMonitorOption { get; set; }
public NewItemMonitorTypes DefaultNewItemMonitorOption { get; set; }
public HashSet<int> DefaultTags { get; set; }
public bool IsCalibreLibrary { get; set; }
public CalibreSettings CalibreSettings { get; set; }

@ -18,7 +18,6 @@ using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.Http.REST.Attributes;
using NzbDrone.SignalR;
using Readarr.Api.V1.Books;
using Readarr.Http;
using Readarr.Http.Extensions;
using Readarr.Http.REST;

@ -34,6 +34,11 @@ namespace Readarr.Api.V1.Author
author.Monitored = resource.Monitored.Value;
}
if (resource.MonitorNewItems.HasValue)
{
author.MonitorNewItems = resource.MonitorNewItems.Value;
}
if (resource.QualityProfileId.HasValue)
{
author.QualityProfileId = resource.QualityProfileId.Value;

@ -1,4 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Core.Books;
namespace Readarr.Api.V1.Author
{
@ -6,6 +7,7 @@ namespace Readarr.Api.V1.Author
{
public List<int> AuthorIds { get; set; }
public bool? Monitored { get; set; }
public NewItemMonitorTypes? MonitorNewItems { get; set; }
public int? QualityProfileId { get; set; }
public int? MetadataProfileId { get; set; }
public string RootFolderPath { get; set; }

@ -5,7 +5,6 @@ using Newtonsoft.Json;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Books;
using NzbDrone.Core.MediaCover;
using Readarr.Api.V1.Books;
using Readarr.Http.REST;
namespace Readarr.Api.V1.Author
@ -43,6 +42,7 @@ namespace Readarr.Api.V1.Author
//Editing Only
public bool Monitored { get; set; }
public NewItemMonitorTypes MonitorNewItems { get; set; }
public string RootFolderPath { get; set; }
public List<string> Genres { get; set; }
@ -91,6 +91,7 @@ namespace Readarr.Api.V1.Author
Links = model.Metadata.Value.Links,
Monitored = model.Monitored,
MonitorNewItems = model.MonitorNewItems,
CleanName = model.CleanName,
ForeignAuthorId = model.Metadata.Value.ForeignAuthorId,
@ -141,6 +142,7 @@ namespace Readarr.Api.V1.Author
MetadataProfileId = resource.MetadataProfileId,
Monitored = resource.Monitored,
MonitorNewItems = resource.MonitorNewItems,
CleanName = resource.CleanName,
RootFolderPath = resource.RootFolderPath,

@ -18,7 +18,7 @@ namespace Readarr.Api.V1.Bookshelf
}
[HttpPost]
public ActionResult<object> UpdateAll([FromBody] BookshelfResource request)
public IActionResult UpdateAll([FromBody] BookshelfResource request)
{
//Read from request
var authorToUpdate = _authorService.GetAuthors(request.Authors.Select(s => s.Id));
@ -37,10 +37,15 @@ namespace Readarr.Api.V1.Bookshelf
author.Monitored = false;
}
if (request.MonitorNewItems.HasValue)
{
author.MonitorNewItems = request.MonitorNewItems.Value;
}
_bookMonitoredService.SetBookMonitoredStatus(author, request.MonitoringOptions);
}
return Accepted(new object());
return Accepted(request);
}
}
}

@ -7,5 +7,6 @@ namespace Readarr.Api.V1.Bookshelf
{
public List<BookshelfAuthorResource> Authors { get; set; }
public MonitoringOptions MonitoringOptions { get; set; }
public NewItemMonitorTypes? MonitorNewItems { get; set; }
}
}

@ -1,3 +1,4 @@
using NzbDrone.Core.Books;
using NzbDrone.Core.ImportLists;
namespace Readarr.Api.V1.ImportLists
@ -9,6 +10,7 @@ namespace Readarr.Api.V1.ImportLists
public bool ShouldMonitorExisting { get; set; }
public bool ShouldSearch { get; set; }
public string RootFolderPath { get; set; }
public NewItemMonitorTypes MonitorNewItems { get; set; }
public int QualityProfileId { get; set; }
public int MetadataProfileId { get; set; }
public ImportListType ListType { get; set; }
@ -31,6 +33,7 @@ namespace Readarr.Api.V1.ImportLists
resource.ShouldMonitorExisting = definition.ShouldMonitorExisting;
resource.ShouldSearch = definition.ShouldSearch;
resource.RootFolderPath = definition.RootFolderPath;
resource.MonitorNewItems = definition.MonitorNewItems;
resource.QualityProfileId = definition.ProfileId;
resource.MetadataProfileId = definition.MetadataProfileId;
resource.ListType = definition.ListType;
@ -53,6 +56,7 @@ namespace Readarr.Api.V1.ImportLists
definition.ShouldMonitorExisting = resource.ShouldMonitorExisting;
definition.ShouldSearch = resource.ShouldSearch;
definition.RootFolderPath = resource.RootFolderPath;
definition.MonitorNewItems = resource.MonitorNewItems;
definition.ProfileId = resource.QualityProfileId;
definition.MetadataProfileId = resource.MetadataProfileId;
definition.ListType = resource.ListType;

@ -15,6 +15,7 @@ namespace Readarr.Api.V1.RootFolders
public int DefaultMetadataProfileId { get; set; }
public int DefaultQualityProfileId { get; set; }
public MonitorTypes DefaultMonitorOption { get; set; }
public NewItemMonitorTypes DefaultNewItemMonitorOption { get; set; }
public HashSet<int> DefaultTags { get; set; }
public bool IsCalibreLibrary { get; set; }
public string Host { get; set; }
@ -50,6 +51,7 @@ namespace Readarr.Api.V1.RootFolders
DefaultMetadataProfileId = model.DefaultMetadataProfileId,
DefaultQualityProfileId = model.DefaultQualityProfileId,
DefaultMonitorOption = model.DefaultMonitorOption,
DefaultNewItemMonitorOption = model.DefaultNewItemMonitorOption,
DefaultTags = model.DefaultTags,
IsCalibreLibrary = model.IsCalibreLibrary,
Host = model.CalibreSettings?.Host,
@ -105,6 +107,7 @@ namespace Readarr.Api.V1.RootFolders
DefaultMetadataProfileId = resource.DefaultMetadataProfileId,
DefaultQualityProfileId = resource.DefaultQualityProfileId,
DefaultMonitorOption = resource.DefaultMonitorOption,
DefaultNewItemMonitorOption = resource.DefaultNewItemMonitorOption,
DefaultTags = resource.DefaultTags,
IsCalibreLibrary = resource.IsCalibreLibrary,
CalibreSettings = cs

Loading…
Cancel
Save