From 2318c43536b3caf8d07d8499d25dc32a7393932e Mon Sep 17 00:00:00 2001 From: ta264 Date: Thu, 28 Apr 2022 21:04:09 +0100 Subject: [PATCH] New: Option to control which new artist albums get monitored (cherry picked from commit c51ae664aa6e6f5330be67e68476af76c55352f5) --- ...istMonitorNewItemsOptionsPopoverContent.js | 27 ++ .../ArtistMonitoringOptionsPopoverContent.js | 78 +++--- frontend/src/AlbumStudio/AlbumStudioFooter.js | 37 ++- .../src/Artist/Edit/EditArtistModalContent.js | 27 ++ .../Edit/EditArtistModalContentConnector.js | 1 + .../src/Artist/Editor/ArtistEditorFooter.js | 19 ++ .../MonitoringOptionsModalContent.js | 149 ++++++++++ .../src/Components/Form/FormInputGroup.js | 4 + .../Form/MonitorNewItemsSelectInput.js | 50 ++++ frontend/src/Helpers/Props/inputTypes.js | 2 + .../Album/AddNewAlbumModalContentConnector.js | 3 + .../AddNewArtistModalContentConnector.js | 3 + .../src/Search/Common/AddArtistOptionsForm.js | 29 ++ .../ImportLists/EditImportListModalContent.js | 255 ++++++++++-------- .../RootFolder/EditRootFolderModalContent.js | 29 +- .../src/Store/Actions/albumStudioActions.js | 6 +- frontend/src/Utilities/Artist/getNewArtist.js | 2 + .../Artist/monitorNewItemsOptions.js | 7 + .../AlbumStudio/AlbumStudioController.cs | 7 +- .../AlbumStudio/AlbumStudioResource.cs | 1 + .../Artist/ArtistEditorController.cs | 5 + .../Artist/ArtistEditorResource.cs | 2 + src/Lidarr.Api.V1/Artist/ArtistResource.cs | 3 + .../ImportLists/ImportListResource.cs | 4 + .../RootFolders/RootFolderResource.cs | 5 +- .../MonitorNewAlbumServiceFixture.cs | 65 +++++ .../MusicTests/RefreshArtistServiceFixture.cs | 29 +- .../056_add_new_item_monitor_type.cs | 16 ++ .../ImportLists/ImportListDefinition.cs | 2 + .../ImportLists/ImportListSyncService.cs | 1 + src/NzbDrone.Core/Localization/Core/en.json | 12 +- .../TrackImport/ImportApprovedTracks.cs | 1 + src/NzbDrone.Core/Music/Model/Artist.cs | 3 + src/NzbDrone.Core/Music/Model/MonitorTypes.cs | 14 + .../Music/Model/MonitoringOptions.cs | 12 - .../Music/Model/NewItemMonitorTypes.cs | 9 + .../Music/Services/MonitorNewAlbumService.cs | 44 +++ .../Services/RefreshAlbumReleaseService.cs | 6 +- .../Music/Services/RefreshArtistService.cs | 12 + .../Services/RefreshEntityServiceBase.cs | 7 + src/NzbDrone.Core/RootFolders/RootFolder.cs | 1 + 41 files changed, 812 insertions(+), 177 deletions(-) create mode 100644 frontend/src/AddArtist/ArtistMonitorNewItemsOptionsPopoverContent.js create mode 100644 frontend/src/Artist/MonitoringOptions/MonitoringOptionsModalContent.js create mode 100644 frontend/src/Components/Form/MonitorNewItemsSelectInput.js create mode 100644 frontend/src/Utilities/Artist/monitorNewItemsOptions.js create mode 100644 src/NzbDrone.Core.Test/MusicTests/MonitorNewAlbumServiceFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/056_add_new_item_monitor_type.cs create mode 100644 src/NzbDrone.Core/Music/Model/MonitorTypes.cs create mode 100644 src/NzbDrone.Core/Music/Model/NewItemMonitorTypes.cs create mode 100644 src/NzbDrone.Core/Music/Services/MonitorNewAlbumService.cs diff --git a/frontend/src/AddArtist/ArtistMonitorNewItemsOptionsPopoverContent.js b/frontend/src/AddArtist/ArtistMonitorNewItemsOptionsPopoverContent.js new file mode 100644 index 000000000..5a837b1eb --- /dev/null +++ b/frontend/src/AddArtist/ArtistMonitorNewItemsOptionsPopoverContent.js @@ -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 ( + + + + + + + + ); +} + +export default ArtistMonitorNewItemsOptionsPopoverContent; diff --git a/frontend/src/AddArtist/ArtistMonitoringOptionsPopoverContent.js b/frontend/src/AddArtist/ArtistMonitoringOptionsPopoverContent.js index 9af0b2254..653e313e1 100644 --- a/frontend/src/AddArtist/ArtistMonitoringOptionsPopoverContent.js +++ b/frontend/src/AddArtist/ArtistMonitoringOptionsPopoverContent.js @@ -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 ( - - - - - - - - - - - - - - - + <> + + This is a one time adjustment to set which albums are monitored + + + + + + + + + + + + + + + + + ); } diff --git a/frontend/src/AlbumStudio/AlbumStudioFooter.js b/frontend/src/AlbumStudio/AlbumStudioFooter.js index c422dd1c3..0d649e635 100644 --- a/frontend/src/AlbumStudio/AlbumStudioFooter.js +++ b/frontend/src/AlbumStudio/AlbumStudioFooter.js @@ -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 ( @@ -103,7 +114,7 @@ class AlbumStudioFooter extends Component {
- Monitor Albums + Monitor Existing Albums
+
+
+ Monitor New Albums +
+ + +
+
{selectedCount} Artist(s) Selected diff --git a/frontend/src/Artist/Edit/EditArtistModalContent.js b/frontend/src/Artist/Edit/EditArtistModalContent.js index 8cd311269..7205ee032 100644 --- a/frontend/src/Artist/Edit/EditArtistModalContent.js +++ b/frontend/src/Artist/Edit/EditArtistModalContent.js @@ -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 { /> + + + {translate('MonitorNewItems')} + + } + title={translate('MonitorNewItems')} + body={} + position={tooltipPositions.RIGHT} + /> + + + + + {translate('QualityProfile')} diff --git a/frontend/src/Artist/Edit/EditArtistModalContentConnector.js b/frontend/src/Artist/Edit/EditArtistModalContentConnector.js index 9c9ae4433..bd9592d42 100644 --- a/frontend/src/Artist/Edit/EditArtistModalContentConnector.js +++ b/frontend/src/Artist/Edit/EditArtistModalContentConnector.js @@ -39,6 +39,7 @@ function createMapStateToProps() { const artistSettings = _.pick(artist, [ 'monitored', + 'monitorNewItems', 'qualityProfileId', 'metadataProfileId', 'path', diff --git a/frontend/src/Artist/Editor/ArtistEditorFooter.js b/frontend/src/Artist/Editor/ArtistEditorFooter.js index f6cf007d1..174996ad0 100644 --- a/frontend/src/Artist/Editor/ArtistEditorFooter.js +++ b/frontend/src/Artist/Editor/ArtistEditorFooter.js @@ -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 { />
+
+ + + +
+ { columns.map((column) => { const { diff --git a/frontend/src/Artist/MonitoringOptions/MonitoringOptionsModalContent.js b/frontend/src/Artist/MonitoringOptions/MonitoringOptionsModalContent.js new file mode 100644 index 000000000..0057ab30f --- /dev/null +++ b/frontend/src/Artist/MonitoringOptions/MonitoringOptionsModalContent.js @@ -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 ( + + + {translate('MonitorAlbum')} + + + + +
+ {translate('MonitorAlbumExistingOnlyWarning')} +
+
+ +
+ + {translate('Monitoring')} + + + +
+
+ + + + + + {translate('Save')} + + +
+ ); + } +} + +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; diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js index 5338786b4..4919e5027 100644 --- a/frontend/src/Components/Form/FormInputGroup.js +++ b/frontend/src/Components/Form/FormInputGroup.js @@ -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; diff --git a/frontend/src/Components/Form/MonitorNewItemsSelectInput.js b/frontend/src/Components/Form/MonitorNewItemsSelectInput.js new file mode 100644 index 000000000..0152c0a2d --- /dev/null +++ b/frontend/src/Components/Form/MonitorNewItemsSelectInput.js @@ -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 ( + + ); +} + +MonitorNewItemsSelectInput.propTypes = { + includeNoChange: PropTypes.bool.isRequired, + includeMixed: PropTypes.bool.isRequired, + onChange: PropTypes.func.isRequired +}; + +MonitorNewItemsSelectInput.defaultProps = { + includeNoChange: false, + includeMixed: false +}; + +export default MonitorNewItemsSelectInput; diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js index 4c4c55784..2f04d5213 100644 --- a/frontend/src/Helpers/Props/inputTypes.js +++ b/frontend/src/Helpers/Props/inputTypes.js @@ -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, diff --git a/frontend/src/Search/Album/AddNewAlbumModalContentConnector.js b/frontend/src/Search/Album/AddNewAlbumModalContentConnector.js index cbdb93df5..fa18374e6 100644 --- a/frontend/src/Search/Album/AddNewAlbumModalContentConnector.js +++ b/frontend/src/Search/Album/AddNewAlbumModalContentConnector.js @@ -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, diff --git a/frontend/src/Search/Artist/AddNewArtistModalContentConnector.js b/frontend/src/Search/Artist/AddNewArtistModalContentConnector.js index b5454ca43..acd168f0b 100644 --- a/frontend/src/Search/Artist/AddNewArtistModalContentConnector.js +++ b/frontend/src/Search/Artist/AddNewArtistModalContentConnector.js @@ -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, diff --git a/frontend/src/Search/Common/AddArtistOptionsForm.js b/frontend/src/Search/Common/AddArtistOptionsForm.js index dbabed7c9..e620ad379 100644 --- a/frontend/src/Search/Common/AddArtistOptionsForm.js +++ b/frontend/src/Search/Common/AddArtistOptionsForm.js @@ -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 { + + + {translate('MonitorNewItems')} + + } + title={translate('MonitorNewItems')} + body={} + position={tooltipPositions.RIGHT} + /> + + + + + {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, diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index dd01331d5..8f18926fa 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -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} } - - - {translate('Name')} - - - - - - - - {translate('EnableAutomaticAdd')} - - - - - - - - Monitor - - - } - title={translate('MonitoringOptions')} - body={} - position={tooltipPositions.RIGHT} + +
+ + + {translate('Name')} + + + + + + + + {translate('EnableAutomaticAdd')} + + + + + + + + Monitor + + + } + title={translate('MonitoringOptions')} + body={} + position={tooltipPositions.RIGHT} + /> + + + + +
+ +
+ + + {translate('RootFolder')} + + + + + + + + {translate('MonitorNewItems')} + + } + title={translate('MonitorNewItems')} + body={} + position={tooltipPositions.RIGHT} + /> + + + + + + + + {translate('QualityProfile')} + + + + + + + + {translate('MetadataProfile')} + + + + + + + + {translate('ReadarrTags')} + + + - - - - - - - - {translate('RootFolder')} - - - - - - - - {translate('QualityProfile')} - - - - - - - - {translate('MetadataProfile')} - - - - - - - - {translate('LidarrTags')} - - - - + +
{ !!fields && !!fields.length && -
+
{ fields.map((field) => { return ( @@ -241,7 +274,7 @@ function EditImportListModalContent(props) { ); }) } -
+ } diff --git a/frontend/src/Settings/MediaManagement/RootFolder/EditRootFolderModalContent.js b/frontend/src/Settings/MediaManagement/RootFolder/EditRootFolderModalContent.js index 237cdcc22..e68f03459 100644 --- a/frontend/src/Settings/MediaManagement/RootFolder/EditRootFolderModalContent.js +++ b/frontend/src/Settings/MediaManagement/RootFolder/EditRootFolderModalContent.js @@ -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) { - Monitor + {translate('Monitor')} + + + {translate('MonitorNewItems')} + + } + title={translate('MonitorNewItems')} + body={} + position={tooltipPositions.RIGHT} + /> + + + + + {translate('QualityProfile')} diff --git a/frontend/src/Store/Actions/albumStudioActions.js b/frontend/src/Store/Actions/albumStudioActions.js index c473950a2..786e3a575 100644 --- a/frontend/src/Store/Actions/albumStudioActions.js +++ b/frontend/src/Store/Actions/albumStudioActions.js @@ -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; diff --git a/frontend/src/Utilities/Artist/getNewArtist.js b/frontend/src/Utilities/Artist/getNewArtist.js index 6f6ee7051..ed1a6fb11 100644 --- a/frontend/src/Utilities/Artist/getNewArtist.js +++ b/frontend/src/Utilities/Artist/getNewArtist.js @@ -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; diff --git a/frontend/src/Utilities/Artist/monitorNewItemsOptions.js b/frontend/src/Utilities/Artist/monitorNewItemsOptions.js new file mode 100644 index 000000000..32e86e41f --- /dev/null +++ b/frontend/src/Utilities/Artist/monitorNewItemsOptions.js @@ -0,0 +1,7 @@ +const monitorNewItemsOptions = [ + { key: 'all', value: 'All Albums' }, + { key: 'none', value: 'None' }, + { key: 'new', value: 'New' } +]; + +export default monitorNewItemsOptions; diff --git a/src/Lidarr.Api.V1/AlbumStudio/AlbumStudioController.cs b/src/Lidarr.Api.V1/AlbumStudio/AlbumStudioController.cs index 2561d6d26..edde38573 100644 --- a/src/Lidarr.Api.V1/AlbumStudio/AlbumStudioController.cs +++ b/src/Lidarr.Api.V1/AlbumStudio/AlbumStudioController.cs @@ -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); } } } diff --git a/src/Lidarr.Api.V1/AlbumStudio/AlbumStudioResource.cs b/src/Lidarr.Api.V1/AlbumStudio/AlbumStudioResource.cs index f89b6c962..97dbbc81f 100644 --- a/src/Lidarr.Api.V1/AlbumStudio/AlbumStudioResource.cs +++ b/src/Lidarr.Api.V1/AlbumStudio/AlbumStudioResource.cs @@ -7,5 +7,6 @@ namespace Lidarr.Api.V1.AlbumStudio { public List Artist { get; set; } public MonitoringOptions MonitoringOptions { get; set; } + public NewItemMonitorTypes? MonitorNewItems { get; set; } } } diff --git a/src/Lidarr.Api.V1/Artist/ArtistEditorController.cs b/src/Lidarr.Api.V1/Artist/ArtistEditorController.cs index 99860c3f1..a7e103105 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistEditorController.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistEditorController.cs @@ -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; diff --git a/src/Lidarr.Api.V1/Artist/ArtistEditorResource.cs b/src/Lidarr.Api.V1/Artist/ArtistEditorResource.cs index 805348a69..71b0eb396 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistEditorResource.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistEditorResource.cs @@ -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 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; } diff --git a/src/Lidarr.Api.V1/Artist/ArtistResource.cs b/src/Lidarr.Api.V1/Artist/ArtistResource.cs index 96a7805e5..4d6977104 100644 --- a/src/Lidarr.Api.V1/Artist/ArtistResource.cs +++ b/src/Lidarr.Api.V1/Artist/ArtistResource.cs @@ -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 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, diff --git a/src/Lidarr.Api.V1/ImportLists/ImportListResource.cs b/src/Lidarr.Api.V1/ImportLists/ImportListResource.cs index 87950df72..3a7596094 100644 --- a/src/Lidarr.Api.V1/ImportLists/ImportListResource.cs +++ b/src/Lidarr.Api.V1/ImportLists/ImportListResource.cs @@ -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; diff --git a/src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs b/src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs index 563eb4c08..3e82f3585 100644 --- a/src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs +++ b/src/Lidarr.Api.V1/RootFolders/RootFolderResource.cs @@ -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 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, }; } diff --git a/src/NzbDrone.Core.Test/MusicTests/MonitorNewAlbumServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/MonitorNewAlbumServiceFixture.cs new file mode 100644 index 000000000..83e937e1e --- /dev/null +++ b/src/NzbDrone.Core.Test/MusicTests/MonitorNewAlbumServiceFixture.cs @@ -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 + { + private List _albums; + + [SetUp] + public void Setup() + { + _albums = Builder.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(); + } + } + } +} diff --git a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs index 18d3fe716..f6a865f52 100644 --- a/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MusicTests/RefreshArtistServiceFixture.cs @@ -74,6 +74,10 @@ namespace NzbDrone.Core.Test.MusicTests Mocker.GetMock() .Setup(x => x.All()) .Returns(new List()); + + Mocker.GetMock() + .Setup(x => x.ShouldMonitorNewAlbum(It.IsAny(), It.IsAny>(), It.IsAny())) + .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.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() + .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() .Setup(x => x.DeleteArtist(It.IsAny(), It.IsAny(), It.IsAny())); diff --git a/src/NzbDrone.Core/Datastore/Migration/056_add_new_item_monitor_type.cs b/src/NzbDrone.Core/Datastore/Migration/056_add_new_item_monitor_type.cs new file mode 100644 index 000000000..77749365a --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/056_add_new_item_monitor_type.cs @@ -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); + } + } +} diff --git a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs index 39473a044..825bf0d68 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs @@ -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; } diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs index 0a99a2195..74dba1253 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs @@ -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, diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index d6092b948..8dc396cab 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -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", diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs index aecf64d3a..7b89b343b 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs @@ -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 { diff --git a/src/NzbDrone.Core/Music/Model/Artist.cs b/src/NzbDrone.Core/Music/Model/Artist.cs index cd9d5cbf8..a1f840e46 100644 --- a/src/NzbDrone.Core/Music/Model/Artist.cs +++ b/src/NzbDrone.Core/Music/Model/Artist.cs @@ -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; } } } diff --git a/src/NzbDrone.Core/Music/Model/MonitorTypes.cs b/src/NzbDrone.Core/Music/Model/MonitorTypes.cs new file mode 100644 index 000000000..26bd89015 --- /dev/null +++ b/src/NzbDrone.Core/Music/Model/MonitorTypes.cs @@ -0,0 +1,14 @@ +namespace NzbDrone.Core.Music +{ + public enum MonitorTypes + { + All, + Future, + Missing, + Existing, + Latest, + First, + None, + Unknown + } +} diff --git a/src/NzbDrone.Core/Music/Model/MonitoringOptions.cs b/src/NzbDrone.Core/Music/Model/MonitoringOptions.cs index 382c40a57..d50e278c8 100644 --- a/src/NzbDrone.Core/Music/Model/MonitoringOptions.cs +++ b/src/NzbDrone.Core/Music/Model/MonitoringOptions.cs @@ -14,16 +14,4 @@ namespace NzbDrone.Core.Music public List AlbumsToMonitor { get; set; } public bool Monitored { get; set; } } - - public enum MonitorTypes - { - All, - Future, - Missing, - Existing, - Latest, - First, - None, - Unknown - } } diff --git a/src/NzbDrone.Core/Music/Model/NewItemMonitorTypes.cs b/src/NzbDrone.Core/Music/Model/NewItemMonitorTypes.cs new file mode 100644 index 000000000..4ea719b5c --- /dev/null +++ b/src/NzbDrone.Core/Music/Model/NewItemMonitorTypes.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.Music +{ + public enum NewItemMonitorTypes + { + All, + None, + New + } +} diff --git a/src/NzbDrone.Core/Music/Services/MonitorNewAlbumService.cs b/src/NzbDrone.Core/Music/Services/MonitorNewAlbumService.cs new file mode 100644 index 000000000..767913f94 --- /dev/null +++ b/src/NzbDrone.Core/Music/Services/MonitorNewAlbumService.cs @@ -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 existingAlbums, NewItemMonitorTypes monitorNewItems); + } + + public class MonitorNewAlbumService : IMonitorNewAlbumService + { + private readonly Logger _logger; + + public MonitorNewAlbumService(Logger logger) + { + _logger = logger; + } + + public bool ShouldMonitorNewAlbum(Album addedAlbum, List 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}"); + } + } +} diff --git a/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs b/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs index 294610a35..a02791501 100644 --- a/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs +++ b/src/NzbDrone.Core/Music/Services/RefreshAlbumReleaseService.cs @@ -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 children) diff --git a/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs b/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs index b2aee5efc..bd6b82a87 100644 --- a/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/Services/RefreshArtistService.cs @@ -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 children) { _albumService.InsertMany(children); diff --git a/src/NzbDrone.Core/Music/Services/RefreshEntityServiceBase.cs b/src/NzbDrone.Core/Music/Services/RefreshEntityServiceBase.cs index 34a9a0936..b0ee23ef8 100644 --- a/src/NzbDrone.Core/Music/Services/RefreshEntityServiceBase.cs +++ b/src/NzbDrone.Core/Music/Services/RefreshEntityServiceBase.cs @@ -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 children); protected abstract bool RefreshChildren(SortedChildren localChildren, List 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); diff --git a/src/NzbDrone.Core/RootFolders/RootFolder.cs b/src/NzbDrone.Core/RootFolders/RootFolder.cs index 528d02cd8..e26704c82 100644 --- a/src/NzbDrone.Core/RootFolders/RootFolder.cs +++ b/src/NzbDrone.Core/RootFolders/RootFolder.cs @@ -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 DefaultTags { get; set; } public bool Accessible { get; set; }