New: Option to control which new artist albums get monitored

(cherry picked from commit c51ae664aa6e6f5330be67e68476af76c55352f5)
pull/2804/head
ta264 2 years ago committed by Qstick
parent d0fd0024e3
commit 2318c43536

@ -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 ArtistMonitorNewItemsOptionsPopoverContent() {
return (
<DescriptionList>
<DescriptionListItem
title={translate('AllAlbums')}
data="Monitor all new albums"
/>
<DescriptionListItem
title={translate('NewAlbums')}
data="Monitor new albums released after the newest existing album"
/>
<DescriptionListItem
title={translate('None')}
data="Don't monitor any new albums"
/>
</DescriptionList>
);
}
export default ArtistMonitorNewItemsOptionsPopoverContent;

@ -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 ArtistMonitoringOptionsPopoverContent() {
return (
<DescriptionList>
<DescriptionListItem
title={translate('AllAlbums')}
data={translate('AllAlbumsData')}
/>
<DescriptionListItem
title={translate('FutureAlbums')}
data={translate('FutureAlbumsData')}
/>
<DescriptionListItem
title={translate('MissingAlbums')}
data={translate('MissingAlbumsData')}
/>
<DescriptionListItem
title={translate('ExistingAlbums')}
data={translate('ExistingAlbumsData')}
/>
<DescriptionListItem
title={translate('FirstAlbum')}
data={translate('FirstAlbumData')}
/>
<DescriptionListItem
title={translate('LatestAlbum')}
data={translate('LatestAlbumData')}
/>
<DescriptionListItem
title={translate('None')}
data={translate('NoneData')}
/>
</DescriptionList>
<>
<Alert>
This is a one time adjustment to set which albums are monitored
</Alert>
<DescriptionList>
<DescriptionListItem
title={translate('AllAlbums')}
data={translate('AllAlbumsData')}
/>
<DescriptionListItem
title={translate('FutureAlbums')}
data={translate('FutureAlbumsData')}
/>
<DescriptionListItem
title={translate('MissingAlbums')}
data={translate('MissingAlbumsData')}
/>
<DescriptionListItem
title={translate('ExistingAlbums')}
data={translate('ExistingAlbumsData')}
/>
<DescriptionListItem
title={translate('FirstAlbum')}
data={translate('FirstAlbumData')}
/>
<DescriptionListItem
title={translate('LatestAlbum')}
data={translate('LatestAlbumData')}
/>
<DescriptionListItem
title={translate('None')}
data={translate('NoneData')}
/>
</DescriptionList>
</>
);
}

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import MonitorAlbumsSelectInput from 'Components/Form/MonitorAlbumsSelectInput';
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 AlbumStudioFooter extends Component {
this.state = {
monitored: NO_CHANGE,
monitor: NO_CHANGE
monitor: NO_CHANGE,
monitorNewItems: NO_CHANGE
};
}
@ -32,7 +34,8 @@ class AlbumStudioFooter 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 AlbumStudioFooter extends Component {
onUpdateSelectedPress = () => {
const {
monitor,
monitored
monitored,
monitorNewItems
} = this.state;
const changes = {};
@ -60,6 +64,10 @@ class AlbumStudioFooter extends Component {
changes.monitor = monitor;
}
if (monitorNewItems !== NO_CHANGE) {
changes.monitorNewItems = monitorNewItems;
}
this.props.onUpdateSelectedPress(changes);
};
@ -74,7 +82,8 @@ class AlbumStudioFooter extends Component {
const {
monitored,
monitor
monitor,
monitorNewItems
} = this.state;
const monitoredOptions = [
@ -83,7 +92,9 @@ class AlbumStudioFooter 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 AlbumStudioFooter extends Component {
<div className={styles.inputContainer}>
<div className={styles.label}>
Monitor Albums
Monitor Existing Albums
</div>
<MonitorAlbumsSelectInput
@ -115,6 +126,20 @@ class AlbumStudioFooter extends Component {
/>
</div>
<div className={styles.inputContainer}>
<div className={styles.label}>
Monitor New Albums
</div>
<MonitorNewItemsSelectInput
name="monitorNewItems"
value={monitorNewItems}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div>
<div className={styles.label}>
{selectedCount} Artist(s) Selected

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ArtistMetadataProfilePopoverContent from 'AddArtist/ArtistMetadataProfilePopoverContent';
import ArtistMonitorNewItemsOptionsPopoverContent from 'AddArtist/ArtistMonitorNewItemsOptionsPopoverContent';
import MoveArtistModal from 'Artist/MoveArtist/MoveArtistModal';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
@ -73,6 +74,7 @@ class EditArtistModalContent extends Component {
const {
monitored,
monitorNewItems,
qualityProfileId,
metadataProfileId,
path,
@ -101,6 +103,31 @@ class EditArtistModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitorNewItems')}
body={<ArtistMonitorNewItemsOptionsPopoverContent />}
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 artistSettings = _.pick(artist, [
'monitored',
'monitorNewItems',
'qualityProfileId',
'metadataProfileId',
'path',

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import MoveArtistModal from 'Artist/MoveArtist/MoveArtistModal';
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 ArtistEditorFooter extends Component {
this.state = {
monitored: NO_CHANGE,
monitorNewItems: NO_CHANGE,
qualityProfileId: NO_CHANGE,
metadataProfileId: NO_CHANGE,
rootFolderPath: NO_CHANGE,
@ -46,6 +48,7 @@ class ArtistEditorFooter extends Component {
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitored: NO_CHANGE,
monitorNewItems: NO_CHANGE,
qualityProfileId: NO_CHANGE,
metadataProfileId: NO_CHANGE,
rootFolderPath: NO_CHANGE,
@ -146,6 +149,7 @@ class ArtistEditorFooter extends Component {
const {
monitored,
monitorNewItems,
qualityProfileId,
metadataProfileId,
rootFolderPath,
@ -179,6 +183,21 @@ class ArtistEditorFooter extends Component {
/>
</div>
<div className={styles.inputContainer}>
<ArtistEditorFooterLabel
label={translate('MonitorNewItems')}
isSaving={isSaving && monitored !== NO_CHANGE}
/>
<MonitorNewItemsSelectInput
name="monitorNewItems"
value={monitorNewItems}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
{
columns.map((column) => {
const {

@ -0,0 +1,149 @@
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';
import FormLabel from 'Components/Form/FormLabel';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
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, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const NO_CHANGE = 'noChange';
class MonitoringOptionsModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
monitor: NO_CHANGE
};
}
componentDidUpdate(prevProps) {
const {
isSaving,
saveError
} = prevProps;
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitor: NO_CHANGE
});
}
}
onInputChange = ({ name, value }) => {
this.setState({ [name]: value });
};
//
// Listeners
onSavePress = () => {
const {
onSavePress,
isSaving
} = this.props;
const {
monitor
} = this.state;
if (monitor !== NO_CHANGE) {
onSavePress({ monitor });
}
if (!isSaving) {
this.onModalClose();
}
};
onModalClose = () => {
this.props.onModalClose();
};
//
// Render
render() {
const {
isSaving,
onInputChange,
onModalClose,
...otherProps
} = this.props;
const {
monitor
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('MonitorAlbum')}
</ModalHeader>
<ModalBody>
<Alert kind={kinds.INFO}>
<div>
{translate('MonitorAlbumExistingOnlyWarning')}
</div>
</Alert>
<Form {...otherProps}>
<FormGroup>
<FormLabel>{translate('Monitoring')}</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_ALBUMS_SELECT}
name="monitor"
value={monitor}
includeNoChange={true}
onChange={this.onInputChange}
/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
{translate('Cancel')}
</Button>
<SpinnerButton
isSpinning={isSaving}
onPress={this.onSavePress}
>
{translate('Save')}
</SpinnerButton>
</ModalFooter>
</ModalContent>
);
}
}
MonitoringOptionsModalContent.propTypes = {
authorId: PropTypes.number.isRequired,
saveError: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
MonitoringOptionsModalContent.defaultProps = {
isSaving: false
};
export default MonitoringOptionsModalContent;

@ -14,6 +14,7 @@ import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import KeyValueListInput from './KeyValueListInput';
import MetadataProfileSelectInputConnector from './MetadataProfileSelectInputConnector';
import MonitorAlbumsSelectInput from './MonitorAlbumsSelectInput';
import MonitorNewItemsSelectInput from './MonitorNewItemsSelectInput';
import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector';
import PasswordInput from './PasswordInput';
@ -52,6 +53,9 @@ function getComponent(type) {
case inputTypes.MONITOR_ALBUMS_SELECT:
return MonitorAlbumsSelectInput;
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/Artist/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 PLAYLIST = 'playlist';
export const KEY_VALUE_LIST = 'keyValueList';
export const MONITOR_ALBUMS_SELECT = 'monitorAlbumsSelect';
export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect';
export const NUMBER = 'number';
export const OAUTH = 'oauth';
export const PASSWORD = 'password';
@ -31,6 +32,7 @@ export const all = [
PLAYLIST,
KEY_VALUE_LIST,
MONITOR_ALBUMS_SELECT,
MONITOR_NEW_ITEMS_SELECT,
NUMBER,
OAUTH,
PASSWORD,

@ -85,6 +85,7 @@ class AddNewAlbumModalContentConnector extends Component {
foreignAlbumId,
rootFolderPath,
monitor,
monitorNewItems,
qualityProfileId,
metadataProfileId,
tags
@ -94,6 +95,7 @@ class AddNewAlbumModalContentConnector extends Component {
foreignAlbumId,
rootFolderPath: rootFolderPath.value,
monitor: monitor.value,
monitorNewItems: monitorNewItems.value,
qualityProfileId: qualityProfileId.value,
metadataProfileId: metadataProfileId.value,
tags: tags.value,
@ -120,6 +122,7 @@ AddNewAlbumModalContentConnector.propTypes = {
foreignAlbumId: PropTypes.string.isRequired,
rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired,
monitorNewItems: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object,
metadataProfileId: PropTypes.object,
noneMetadataProfileId: PropTypes.number.isRequired,

@ -57,6 +57,7 @@ class AddNewArtistModalContentConnector extends Component {
foreignArtistId,
rootFolderPath,
monitor,
monitorNewItems,
qualityProfileId,
metadataProfileId,
tags
@ -66,6 +67,7 @@ class AddNewArtistModalContentConnector extends Component {
foreignArtistId,
rootFolderPath: rootFolderPath.value,
monitor: monitor.value,
monitorNewItems: monitorNewItems.value,
qualityProfileId: qualityProfileId.value,
metadataProfileId: metadataProfileId.value,
tags: tags.value,
@ -91,6 +93,7 @@ AddNewArtistModalContentConnector.propTypes = {
foreignArtistId: 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 ArtistMetadataProfilePopoverContent from 'AddArtist/ArtistMetadataProfilePopoverContent';
import ArtistMonitoringOptionsPopoverContent from 'AddArtist/ArtistMonitoringOptionsPopoverContent';
import ArtistMonitorNewItemsOptionsPopoverContent from 'AddArtist/ArtistMonitorNewItemsOptionsPopoverContent';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@ -32,6 +33,7 @@ class AddArtistOptionsForm extends Component {
const {
rootFolderPath,
monitor,
monitorNewItems,
qualityProfileId,
metadataProfileId,
includeNoneMetadataProfile,
@ -76,11 +78,37 @@ class AddArtistOptionsForm extends Component {
<FormInputGroup
type={inputTypes.MONITOR_ALBUMS_SELECT}
name="monitor"
helpText={translate('MonitoringOptionsHelpText')}
onChange={onInputChange}
{...monitor}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitorNewItems')}
body={<ArtistMonitorNewItemsOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_NEW_ITEMS_SELECT}
name="monitorNewItems"
helpText={translate('MonitorNewItemsHelpText')}
{...monitorNewItems}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('QualityProfile')}
@ -143,6 +171,7 @@ class AddArtistOptionsForm extends Component {
AddArtistOptionsForm.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 ArtistMonitorNewItemsOptionsPopoverContent from 'AddArtist/ArtistMonitorNewItemsOptionsPopoverContent';
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';
@ -74,6 +76,7 @@ function EditImportListModalContent(props) {
enableAutomaticAdd,
shouldMonitor,
rootFolderPath,
monitorNewItems,
qualityProfileId,
metadataProfileId,
tags,
@ -112,120 +115,150 @@ 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>
</FieldSet>
<FieldSet legend={translate('AddedArtistSettings')} >
<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={<ArtistMonitorNewItemsOptionsPopoverContent />}
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('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('LidarrTags')}
</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 (
@ -241,7 +274,7 @@ function EditImportListModalContent(props) {
);
})
}
</div>
</FieldSet>
}
</Form>

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import ArtistMetadataProfilePopoverContent from 'AddArtist/ArtistMetadataProfilePopoverContent';
import ArtistMonitoringOptionsPopoverContent from 'AddArtist/ArtistMonitoringOptionsPopoverContent';
import ArtistMonitorNewItemsOptionsPopoverContent from 'AddArtist/ArtistMonitorNewItemsOptionsPopoverContent';
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
} = item;
@ -99,7 +101,7 @@ function EditRootFolderModalContent(props) {
<FormGroup>
<FormLabel>
Monitor
{translate('Monitor')}
<Popover
anchor={
@ -124,6 +126,31 @@ function EditRootFolderModalContent(props) {
</FormGroup>
<FormGroup>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitorNewItems')}
body={<ArtistMonitorNewItemsOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_NEW_ITEMS_SELECT}
name="defaultNewItemMonitorOption"
{...defaultNewItemMonitorOption}
onChange={onInputChange}
helpText={translate('MonitorNewItemsHelpText')}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('QualityProfile')}

@ -102,7 +102,8 @@ export const actionHandlers = handleThunks({
const {
artistIds,
monitored,
monitor
monitor,
monitorNewItems
} = payload;
const artist = [];
@ -127,7 +128,8 @@ export const actionHandlers = handleThunks({
method: 'POST',
data: JSON.stringify({
artist,
monitoringOptions: { monitor }
monitoringOptions: { monitor },
monitorNewItems
}),
dataType: 'json'
}).request;

@ -3,6 +3,7 @@ function getNewArtist(artist, payload) {
const {
rootFolderPath,
monitor,
monitorNewItems,
qualityProfileId,
metadataProfileId,
artistType,
@ -17,6 +18,7 @@ function getNewArtist(artist, payload) {
artist.addOptions = addOptions;
artist.monitored = true;
artist.monitorNewItems = monitorNewItems;
artist.qualityProfileId = qualityProfileId;
artist.metadataProfileId = metadataProfileId;
artist.rootFolderPath = rootFolderPath;

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

@ -36,10 +36,15 @@ namespace Lidarr.Api.V1.AlbumStudio
artist.Monitored = false;
}
if (request.MonitorNewItems.HasValue)
{
artist.MonitorNewItems = request.MonitorNewItems.Value;
}
_albumMonitoredService.SetAlbumMonitoredStatus(artist, request.MonitoringOptions);
}
return Accepted();
return Accepted(request);
}
}
}

@ -7,5 +7,6 @@ namespace Lidarr.Api.V1.AlbumStudio
{
public List<AlbumStudioArtistResource> Artist { get; set; }
public MonitoringOptions MonitoringOptions { get; set; }
public NewItemMonitorTypes? MonitorNewItems { get; set; }
}
}

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

@ -1,4 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Core.Music;
namespace Lidarr.Api.V1.Artist
{
@ -6,6 +7,7 @@ namespace Lidarr.Api.V1.Artist
{
public List<int> ArtistIds { 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; }

@ -46,6 +46,7 @@ namespace Lidarr.Api.V1.Artist
//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 +92,7 @@ namespace Lidarr.Api.V1.Artist
Links = model.Metadata.Value.Links,
Monitored = model.Monitored,
MonitorNewItems = model.MonitorNewItems,
CleanName = model.CleanName,
ForeignArtistId = model.Metadata.Value.ForeignArtistId,
@ -138,6 +140,7 @@ namespace Lidarr.Api.V1.Artist
MetadataProfileId = resource.MetadataProfileId,
Monitored = resource.Monitored,
MonitorNewItems = resource.MonitorNewItems,
CleanName = resource.CleanName,
RootFolderPath = resource.RootFolderPath,

@ -1,4 +1,5 @@
using NzbDrone.Core.ImportLists;
using NzbDrone.Core.Music;
namespace Lidarr.Api.V1.ImportLists
{
@ -7,6 +8,7 @@ namespace Lidarr.Api.V1.ImportLists
public bool EnableAutomaticAdd { get; set; }
public ImportListMonitorType ShouldMonitor { 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; }
@ -27,6 +29,7 @@ namespace Lidarr.Api.V1.ImportLists
resource.EnableAutomaticAdd = definition.EnableAutomaticAdd;
resource.ShouldMonitor = definition.ShouldMonitor;
resource.RootFolderPath = definition.RootFolderPath;
resource.MonitorNewItems = definition.MonitorNewItems;
resource.QualityProfileId = definition.ProfileId;
resource.MetadataProfileId = definition.MetadataProfileId;
resource.ListType = definition.ListType;
@ -47,6 +50,7 @@ namespace Lidarr.Api.V1.ImportLists
definition.EnableAutomaticAdd = resource.EnableAutomaticAdd;
definition.ShouldMonitor = resource.ShouldMonitor;
definition.RootFolderPath = resource.RootFolderPath;
definition.MonitorNewItems = resource.MonitorNewItems;
definition.ProfileId = resource.QualityProfileId;
definition.MetadataProfileId = resource.MetadataProfileId;
definition.ListType = resource.ListType;

@ -13,6 +13,7 @@ namespace Lidarr.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 Accessible { get; set; }
@ -38,6 +39,7 @@ namespace Lidarr.Api.V1.RootFolders
DefaultMetadataProfileId = model.DefaultMetadataProfileId,
DefaultQualityProfileId = model.DefaultQualityProfileId,
DefaultMonitorOption = model.DefaultMonitorOption,
DefaultNewItemMonitorOption = model.DefaultNewItemMonitorOption,
DefaultTags = model.DefaultTags,
Accessible = model.Accessible,
@ -62,7 +64,8 @@ namespace Lidarr.Api.V1.RootFolders
DefaultMetadataProfileId = resource.DefaultMetadataProfileId,
DefaultQualityProfileId = resource.DefaultQualityProfileId,
DefaultMonitorOption = resource.DefaultMonitorOption,
DefaultTags = resource.DefaultTags
DefaultNewItemMonitorOption = resource.DefaultNewItemMonitorOption,
DefaultTags = resource.DefaultTags,
};
}

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.AlbumTests
{
[TestFixture]
public class MonitorNewAlbumServiceFixture : CoreTest<MonitorNewAlbumService>
{
private List<Album> _albums;
[SetUp]
public void Setup()
{
_albums = Builder<Album>.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 album in _albums)
{
Subject.ShouldMonitorNewAlbum(album, _albums, NewItemMonitorTypes.All).Should().BeTrue();
}
}
[Test]
public void should_not_monitor_with_none()
{
foreach (var album in _albums)
{
Subject.ShouldMonitorNewAlbum(album, _albums, NewItemMonitorTypes.None).Should().BeFalse();
}
}
[Test]
public void should_only_monitor_new_with_new()
{
Subject.ShouldMonitorNewAlbum(_albums[0], _albums, NewItemMonitorTypes.New).Should().BeTrue();
foreach (var album in _albums.Skip(1))
{
Subject.ShouldMonitorNewAlbum(album, _albums, NewItemMonitorTypes.New).Should().BeFalse();
}
}
}
}

@ -74,6 +74,10 @@ namespace NzbDrone.Core.Test.MusicTests
Mocker.GetMock<IRootFolderService>()
.Setup(x => x.All())
.Returns(new List<RootFolder>());
Mocker.GetMock<IMonitorNewAlbumService>()
.Setup(x => x.ShouldMonitorNewAlbum(It.IsAny<Album>(), It.IsAny<List<Album>>(), It.IsAny<NewItemMonitorTypes>()))
.Returns(true);
}
private void GivenNewArtistInfo(Artist artist)
@ -143,7 +147,30 @@ namespace NzbDrone.Core.Test.MusicTests
}
[Test]
public void should_log_error_and_delete_if_musicbrainz_id_not_found_and_artist_has_no_files()
public void should_call_new_album_monitor_service_when_adding_album()
{
var newAlbum = Builder<Album>.CreateNew()
.With(x => x.Id = 0)
.With(x => x.ForeignAlbumId = "3")
.Build();
_remoteAlbums.Add(newAlbum);
var newAuthorInfo = _artist.JsonClone();
newAuthorInfo.Metadata = _artist.Metadata.Value.JsonClone();
newAuthorInfo.Albums = _remoteAlbums;
GivenNewArtistInfo(newAuthorInfo);
GivenAlbumsForRefresh(_albums);
AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Mocker.GetMock<IMonitorNewAlbumService>()
.Verify(x => x.ShouldMonitorNewAlbum(newAlbum, _albums, _artist.MonitorNewItems), Times.Once());
}
[Test]
public void should_log_error_and_delete_if_musicbrainz_id_not_found_and_author_has_no_files()
{
Mocker.GetMock<IArtistService>()
.Setup(x => x.DeleteArtist(It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<bool>()));

@ -0,0 +1,16 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(56)]
public class AddNewItemMonitorType : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Artists").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.Music;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.ImportLists
@ -6,6 +7,7 @@ namespace NzbDrone.Core.ImportLists
{
public bool EnableAutomaticAdd { get; set; }
public ImportListMonitorType ShouldMonitor { get; set; }
public NewItemMonitorTypes MonitorNewItems { get; set; }
public int ProfileId { get; set; }
public int MetadataProfileId { get; set; }
public string RootFolderPath { get; set; }

@ -270,6 +270,7 @@ namespace NzbDrone.Core.ImportLists
Name = report.Artist
},
Monitored = monitored,
MonitorNewItems = importList.MonitorNewItems,
RootFolderPath = importList.RootFolderPath,
QualityProfileId = importList.ProfileId,
MetadataProfileId = importList.MetadataProfileId,

@ -5,6 +5,7 @@
"About": "About",
"Absolute": "Absolute",
"Actions": "Actions",
"AddedArtistSettings": "Added Artist Settings",
"AddImportListExclusionHelpText": "Prevent artist from being added to Lidarr by Import lists",
"AddingTag": "Adding tag",
"AddListExclusion": "Add List Exclusion",
@ -113,7 +114,7 @@
"DBMigration": "DB Migration",
"DefaultLidarrTags": "Default Lidarr Tags",
"DefaultMetadataProfileIdHelpText": "Default Metadata Profile for artists detected in this folder",
"DefaultMonitorOptionHelpText": "Default Monitoring Options for albums by artists detected in this folder",
"DefaultMonitorOptionHelpText": "Which albums should be monitored on initial add for artists detected in this folder",
"DefaultQualityProfileIdHelpText": "Default Quality Profile for artists detected in this folder",
"DefaultTagsHelpText": "Default Lidarr Tags for artists detected in this folder",
"DelayingDownloadUntilInterp": "Delaying download until {0} at {1}",
@ -261,7 +262,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",
"IncludeUnknownArtistItemsHelpText": "Show items without a artist in the queue, this could include removed artists, movies or anything else in Lidarr's category",
@ -348,10 +350,15 @@
"MissingTracksArtistMonitored": "Missing Tracks (Artist monitored)",
"MissingTracksArtistNotMonitored": "Missing Tracks (Artist not monitored)",
"Mode": "Mode",
"MonitorAlbumExistingOnlyWarning": "This is a one off adjustment of the monitored setting for each album. Use the option under Artist/Edit to control what happens for newly added albums",
"MonitorArtist": "Monitor Artist",
"Monitored": "Monitored",
"MonitoredHelpText": "Download monitored albums from this artist",
"MonitoringOptions": "Monitoring Options",
"MonitoringOptionsHelpText": "Which albums should be monitored after the artist is added (one-time adjustment)",
"MonitorNewItems": "Monitor New Albums",
"MonitorNewItemsHelpText": "Which new albums should be monitored",
"MonoVersion": "Mono Version",
"MoreInfo": "More Info",
"MultiDiscTrackFormat": "Multi Disc Track Format",
"MusicBrainzAlbumID": "MusicBrainz Album ID",
@ -366,6 +373,7 @@
"NamingSettings": "Naming Settings",
"NETCore": ".NET",
"New": "New",
"NewAlbums": "New Albums",
"NoBackupsAreAvailable": "No backups are available",
"NoHistory": "No history.",
"NoLeaveIt": "No, Leave It",

@ -337,6 +337,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
artist.MetadataProfileId = rootFolder.DefaultMetadataProfileId;
artist.QualityProfileId = rootFolder.DefaultQualityProfileId;
artist.Monitored = rootFolder.DefaultMonitorOption != MonitorTypes.None;
artist.MonitorNewItems = rootFolder.DefaultNewItemMonitorOption;
artist.Tags = rootFolder.DefaultTags;
artist.AddOptions = new AddArtistOptions
{

@ -21,6 +21,7 @@ namespace NzbDrone.Core.Music
public string CleanName { get; set; }
public string SortName { 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.Music
Id = other.Id;
ArtistMetadataId = other.ArtistMetadataId;
Monitored = other.Monitored;
MonitorNewItems = other.MonitorNewItems;
LastInfoSync = other.LastInfoSync;
Path = other.Path;
RootFolderPath = other.RootFolderPath;
@ -93,6 +95,7 @@ namespace NzbDrone.Core.Music
AddOptions = other.AddOptions;
RootFolderPath = other.RootFolderPath;
Monitored = other.Monitored;
MonitorNewItems = other.MonitorNewItems;
}
}
}

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

@ -14,16 +14,4 @@ namespace NzbDrone.Core.Music
public List<string> AlbumsToMonitor { 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.Music
{
public enum NewItemMonitorTypes
{
All,
None,
New
}
}

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

@ -103,10 +103,8 @@ namespace NzbDrone.Core.Music
local.AlbumRelease = entity;
local.AlbumReleaseId = entity.Id;
local.ArtistMetadataId = remote.ArtistMetadata.Value.Id;
remote.Id = local.Id;
remote.TrackFileId = local.TrackFileId;
remote.AlbumReleaseId = local.AlbumReleaseId;
remote.ArtistMetadataId = local.ArtistMetadataId;
remote.UseDbFieldsFrom(local);
}
protected override void AddChildren(List<Track> children)

@ -35,6 +35,7 @@ namespace NzbDrone.Core.Music
private readonly IHistoryService _historyService;
private readonly IRootFolderService _rootFolderService;
private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed;
private readonly IMonitorNewAlbumService _monitorNewAlbumService;
private readonly IConfigService _configService;
private readonly IImportListExclusionService _importListExclusionService;
private readonly Logger _logger;
@ -50,6 +51,7 @@ namespace NzbDrone.Core.Music
IHistoryService historyService,
IRootFolderService rootFolderService,
ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed,
IMonitorNewAlbumService monitorNewAlbumService,
IConfigService configService,
IImportListExclusionService importListExclusionService,
Logger logger)
@ -65,6 +67,7 @@ namespace NzbDrone.Core.Music
_historyService = historyService;
_rootFolderService = rootFolderService;
_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed;
_monitorNewAlbumService = monitorNewAlbumService;
_configService = configService;
_importListExclusionService = importListExclusionService;
_logger = logger;
@ -238,6 +241,15 @@ namespace NzbDrone.Core.Music
local.ArtistMetadataId = entity.Metadata.Value.Id;
}
protected override void ProcessChildren(Artist entity, SortedChildren children)
{
foreach (var album in children.Added)
{
// all existing child albums count as updated as we don't have proper data yet.
album.Monitored = _monitorNewAlbumService.ShouldMonitorNewAlbum(album, children.Updated, entity.MonitorNewItems);
}
}
protected override void AddChildren(List<Album> children)
{
_albumService.InsertMany(children);

@ -93,6 +93,11 @@ namespace NzbDrone.Core.Music
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, bool forceChildRefresh, bool forceUpdateFileTags, DateTime? lastUpdate);
@ -274,6 +279,8 @@ namespace NzbDrone.Core.Music
sortedChildren.Merged.Count,
sortedChildren.Deleted.Count);
ProcessChildren(entity, sortedChildren);
// Add in the new children (we have checked that foreign IDs don't clash)
AddChildren(sortedChildren.Added);

@ -11,6 +11,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 Accessible { get; set; }

Loading…
Cancel
Save