diff --git a/frontend/src/Artist/Delete/DeleteArtistModalContent.js b/frontend/src/Artist/Delete/DeleteArtistModalContent.js
index c17cf69f9..c80dca7d9 100644
--- a/frontend/src/Artist/Delete/DeleteArtistModalContent.js
+++ b/frontend/src/Artist/Delete/DeleteArtistModalContent.js
@@ -22,7 +22,8 @@ class DeleteArtistModalContent extends Component {
super(props, context);
this.state = {
- deleteFiles: false
+ deleteFiles: false,
+ addImportListExclusion: false
};
}
@@ -33,11 +34,17 @@ class DeleteArtistModalContent extends Component {
this.setState({ deleteFiles: value });
}
+ onAddImportListExclusionChange = ({ value }) => {
+ this.setState({ addImportListExclusion: value });
+ }
+
onDeleteArtistConfirmed = () => {
const deleteFiles = this.state.deleteFiles;
+ const addImportListExclusion = this.state.addImportListExclusion;
this.setState({ deleteFiles: false });
- this.props.onDeletePress(deleteFiles);
+ this.setState({ addImportListExclusion: false });
+ this.props.onDeletePress(deleteFiles, addImportListExclusion);
}
//
@@ -57,6 +64,8 @@ class DeleteArtistModalContent extends Component {
} = statistics;
const deleteFiles = this.state.deleteFiles;
+ const addImportListExclusion = this.state.addImportListExclusion;
+
let deleteFilesLabel = `Delete ${trackFileCount} Track Files`;
let deleteFilesHelpText = 'Delete the track files and artist folder';
@@ -96,6 +105,19 @@ class DeleteArtistModalContent extends Component {
/>
+
+ Add List Exclusion
+
+
+
+
{
deleteFiles &&
diff --git a/frontend/src/Artist/Delete/DeleteArtistModalContentConnector.js b/frontend/src/Artist/Delete/DeleteArtistModalContentConnector.js
index 938ac5a96..e0ea034ab 100644
--- a/frontend/src/Artist/Delete/DeleteArtistModalContentConnector.js
+++ b/frontend/src/Artist/Delete/DeleteArtistModalContentConnector.js
@@ -24,10 +24,11 @@ class DeleteArtistModalContentConnector extends Component {
//
// Listeners
- onDeletePress = (deleteFiles) => {
+ onDeletePress = (deleteFiles, addImportListExclusion) => {
this.props.deleteArtist({
id: this.props.artistId,
- deleteFiles
+ deleteFiles,
+ addImportListExclusion
});
this.props.onModalClose(true);
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModal.js b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModal.js
new file mode 100644
index 000000000..72566b289
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModal.js
@@ -0,0 +1,27 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { sizes } from 'Helpers/Props';
+import Modal from 'Components/Modal/Modal';
+import EditImportListExclusionModalContentConnector from './EditImportListExclusionModalContentConnector';
+
+function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) {
+ return (
+
+
+
+ );
+}
+
+EditImportListExclusionModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default EditImportListExclusionModal;
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalConnector.js b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalConnector.js
new file mode 100644
index 000000000..f9a511675
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalConnector.js
@@ -0,0 +1,43 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { clearPendingChanges } from 'Store/Actions/baseActions';
+import EditImportListExclusionModal from './EditImportListExclusionModal';
+
+function mapStateToProps() {
+ return {};
+}
+
+const mapDispatchToProps = {
+ clearPendingChanges
+};
+
+class EditImportListExclusionModalConnector extends Component {
+
+ //
+ // Listeners
+
+ onModalClose = () => {
+ this.props.clearPendingChanges({ section: 'settings.importListExclusions' });
+ this.props.onModalClose();
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+EditImportListExclusionModalConnector.propTypes = {
+ onModalClose: PropTypes.func.isRequired,
+ clearPendingChanges: PropTypes.func.isRequired
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(EditImportListExclusionModalConnector);
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.css b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.css
new file mode 100644
index 000000000..5481cccc0
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.css
@@ -0,0 +1,11 @@
+.body {
+ composes: modalBody from 'Components/Modal/ModalBody.css';
+
+ flex: 1 1 430px;
+}
+
+.deleteButton {
+ composes: button from 'Components/Link/Button.css';
+
+ margin-right: auto;
+}
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js
new file mode 100644
index 000000000..ccb2fa04a
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContent.js
@@ -0,0 +1,135 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { inputTypes, kinds } from 'Helpers/Props';
+import { stringSettingShape } from 'Helpers/Props/Shapes/settingShape';
+import Button from 'Components/Link/Button';
+import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
+import LoadingIndicator from 'Components/Loading/LoadingIndicator';
+import ModalContent from 'Components/Modal/ModalContent';
+import ModalHeader from 'Components/Modal/ModalHeader';
+import ModalBody from 'Components/Modal/ModalBody';
+import ModalFooter from 'Components/Modal/ModalFooter';
+import Form from 'Components/Form/Form';
+import FormGroup from 'Components/Form/FormGroup';
+import FormLabel from 'Components/Form/FormLabel';
+import FormInputGroup from 'Components/Form/FormInputGroup';
+import styles from './EditImportListExclusionModalContent.css';
+
+function EditImportListExclusionModalContent(props) {
+ const {
+ id,
+ isFetching,
+ error,
+ isSaving,
+ saveError,
+ item,
+ onInputChange,
+ onSavePress,
+ onModalClose,
+ onDeleteImportListExclusionPress,
+ ...otherProps
+ } = props;
+
+ const {
+ artistName,
+ foreignId
+ } = item;
+
+ return (
+
+
+ {id ? 'Edit Import List Exclusion' : 'Add Import List Exclusion'}
+
+
+
+ {
+ isFetching &&
+
+ }
+
+ {
+ !isFetching && !!error &&
+ Unable to add a new import list exclusion, please try again.
+ }
+
+ {
+ !isFetching && !error &&
+
+ }
+
+
+
+ {
+ id &&
+
+ }
+
+
+
+
+ Save
+
+
+
+ );
+}
+
+const ImportListExclusionShape = {
+ artistName: PropTypes.shape(stringSettingShape).isRequired,
+ foreignId: PropTypes.shape(stringSettingShape).isRequired
+};
+
+EditImportListExclusionModalContent.propTypes = {
+ id: PropTypes.number,
+ isFetching: PropTypes.bool.isRequired,
+ error: PropTypes.object,
+ isSaving: PropTypes.bool.isRequired,
+ saveError: PropTypes.object,
+ item: PropTypes.shape(ImportListExclusionShape).isRequired,
+ onInputChange: PropTypes.func.isRequired,
+ onSavePress: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onDeleteImportListExclusionPress: PropTypes.func
+};
+
+export default EditImportListExclusionModalContent;
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContentConnector.js b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContentConnector.js
new file mode 100644
index 000000000..2516ca25b
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/EditImportListExclusionModalContentConnector.js
@@ -0,0 +1,118 @@
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import selectSettings from 'Store/Selectors/selectSettings';
+import { setImportListExclusionValue, saveImportListExclusion } from 'Store/Actions/settingsActions';
+import EditImportListExclusionModalContent from './EditImportListExclusionModalContent';
+
+const newImportListExclusion = {
+ artistName: '',
+ foreignId: ''
+};
+
+function createImportListExclusionSelector() {
+ return createSelector(
+ (state, { id }) => id,
+ (state) => state.settings.importListExclusions,
+ (id, importListExclusions) => {
+ const {
+ isFetching,
+ error,
+ isSaving,
+ saveError,
+ pendingChanges,
+ items
+ } = importListExclusions;
+
+ const mapping = id ? _.find(items, { id }) : newImportListExclusion;
+ const settings = selectSettings(mapping, pendingChanges, saveError);
+
+ return {
+ id,
+ isFetching,
+ error,
+ isSaving,
+ saveError,
+ item: settings.settings,
+ ...settings
+ };
+ }
+ );
+}
+
+function createMapStateToProps() {
+ return createSelector(
+ createImportListExclusionSelector(),
+ (importListExclusion) => {
+ return {
+ ...importListExclusion
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ setImportListExclusionValue,
+ saveImportListExclusion
+};
+
+class EditImportListExclusionModalContentConnector extends Component {
+
+ //
+ // Lifecycle
+
+ componentDidMount() {
+ if (!this.props.id) {
+ Object.keys(newImportListExclusion).forEach((name) => {
+ this.props.setImportListExclusionValue({
+ name,
+ value: newImportListExclusion[name]
+ });
+ });
+ }
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
+ this.props.onModalClose();
+ }
+ }
+
+ //
+ // Listeners
+
+ onInputChange = ({ name, value }) => {
+ this.props.setImportListExclusionValue({ name, value });
+ }
+
+ onSavePress = () => {
+ this.props.saveImportListExclusion({ id: this.props.id });
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+EditImportListExclusionModalContentConnector.propTypes = {
+ id: PropTypes.number,
+ isSaving: PropTypes.bool.isRequired,
+ saveError: PropTypes.object,
+ item: PropTypes.object.isRequired,
+ setImportListExclusionValue: PropTypes.func.isRequired,
+ saveImportListExclusion: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(EditImportListExclusionModalContentConnector);
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.css b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.css
new file mode 100644
index 000000000..4c274831c
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.css
@@ -0,0 +1,23 @@
+.importListExclusion {
+ display: flex;
+ align-items: stretch;
+ margin-bottom: 10px;
+ height: 30px;
+ border-bottom: 1px solid $borderColor;
+ line-height: 30px;
+}
+
+.artistName {
+ flex: 0 0 300px;
+}
+
+.foreignId {
+ flex: 0 0 400px;
+}
+
+.actions {
+ display: flex;
+ justify-content: flex-end;
+ flex: 1 0 auto;
+ padding-right: 10px;
+}
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.js b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.js
new file mode 100644
index 000000000..9de6c45ee
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusion.js
@@ -0,0 +1,111 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import classNames from 'classnames';
+import { icons, kinds } from 'Helpers/Props';
+import Icon from 'Components/Icon';
+import Link from 'Components/Link/Link';
+import ConfirmModal from 'Components/Modal/ConfirmModal';
+import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
+import styles from './ImportListExclusion.css';
+
+class ImportListExclusion extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isEditImportListExclusionModalOpen: false,
+ isDeleteImportListExclusionModalOpen: false
+ };
+ }
+
+ //
+ // Listeners
+
+ onEditImportListExclusionPress = () => {
+ this.setState({ isEditImportListExclusionModalOpen: true });
+ }
+
+ onEditImportListExclusionModalClose = () => {
+ this.setState({ isEditImportListExclusionModalOpen: false });
+ }
+
+ onDeleteImportListExclusionPress = () => {
+ this.setState({
+ isEditImportListExclusionModalOpen: false,
+ isDeleteImportListExclusionModalOpen: true
+ });
+ }
+
+ onDeleteImportListExclusionModalClose = () => {
+ this.setState({ isDeleteImportListExclusionModalOpen: false });
+ }
+
+ onConfirmDeleteImportListExclusion = () => {
+ this.props.onConfirmDeleteImportListExclusion(this.props.id);
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ id,
+ artistName,
+ foreignId
+ } = this.props;
+
+ return (
+
+
{artistName}
+
{foreignId}
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+ImportListExclusion.propTypes = {
+ id: PropTypes.number.isRequired,
+ artistName: PropTypes.string.isRequired,
+ foreignId: PropTypes.string.isRequired,
+ onConfirmDeleteImportListExclusion: PropTypes.func.isRequired
+};
+
+ImportListExclusion.defaultProps = {
+ // The drag preview will not connect the drag handle.
+ connectDragSource: (node) => node
+};
+
+export default ImportListExclusion;
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusions.css b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusions.css
new file mode 100644
index 000000000..99e1c1e99
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusions.css
@@ -0,0 +1,23 @@
+.importListExclusionsHeader {
+ display: flex;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+.host {
+ flex: 0 0 300px;
+}
+
+.path {
+ flex: 0 0 400px;
+}
+
+.addImportListExclusion {
+ display: flex;
+ justify-content: flex-end;
+ padding-right: 10px;
+}
+
+.addButton {
+ text-align: center;
+}
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusions.js b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusions.js
new file mode 100644
index 000000000..f84015e56
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusions.js
@@ -0,0 +1,100 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { icons } from 'Helpers/Props';
+import FieldSet from 'Components/FieldSet';
+import Icon from 'Components/Icon';
+import Link from 'Components/Link/Link';
+import PageSectionContent from 'Components/Page/PageSectionContent';
+import ImportListExclusion from './ImportListExclusion';
+import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
+import styles from './ImportListExclusions.css';
+
+class ImportListExclusions extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isAddImportListExclusionModalOpen: false
+ };
+ }
+
+ //
+ // Listeners
+
+ onAddImportListExclusionPress = () => {
+ this.setState({ isAddImportListExclusionModalOpen: true });
+ }
+
+ onModalClose = () => {
+ this.setState({ isAddImportListExclusionModalOpen: false });
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ items,
+ onConfirmDeleteImportListExclusion,
+ ...otherProps
+ } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+ImportListExclusions.propTypes = {
+ isFetching: PropTypes.bool.isRequired,
+ error: PropTypes.object,
+ items: PropTypes.arrayOf(PropTypes.object).isRequired,
+ onConfirmDeleteImportListExclusion: PropTypes.func.isRequired
+};
+
+export default ImportListExclusions;
diff --git a/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusionsConnector.js b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusionsConnector.js
new file mode 100644
index 000000000..c5f15f43d
--- /dev/null
+++ b/frontend/src/Settings/ImportLists/ImportListExclusions/ImportListExclusionsConnector.js
@@ -0,0 +1,59 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { fetchImportListExclusions, deleteImportListExclusion } from 'Store/Actions/settingsActions';
+import ImportListExclusions from './ImportListExclusions';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state) => state.settings.importListExclusions,
+ (importListExclusions) => {
+ return {
+ ...importListExclusions
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ fetchImportListExclusions,
+ deleteImportListExclusion
+};
+
+class ImportListExclusionsConnector extends Component {
+
+ //
+ // Lifecycle
+
+ componentDidMount() {
+ this.props.fetchImportListExclusions();
+ }
+
+ //
+ // Listeners
+
+ onConfirmDeleteImportListExclusion = (id) => {
+ this.props.deleteImportListExclusion({ id });
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+ImportListExclusionsConnector.propTypes = {
+ fetchImportListExclusions: PropTypes.func.isRequired,
+ deleteImportListExclusion: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(ImportListExclusionsConnector);
diff --git a/frontend/src/Settings/ImportLists/ImportListSettings.js b/frontend/src/Settings/ImportLists/ImportListSettings.js
index 561bcfd5a..63a8a6733 100644
--- a/frontend/src/Settings/ImportLists/ImportListSettings.js
+++ b/frontend/src/Settings/ImportLists/ImportListSettings.js
@@ -7,6 +7,7 @@ import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import ImportListsConnector from './ImportLists/ImportListsConnector';
+import ImportListsExclusionsConnector from './ImportListExclusions/ImportListExclusionsConnector';
class ImportListSettings extends Component {
@@ -74,6 +75,7 @@ class ImportListSettings extends Component {
+
);
diff --git a/frontend/src/Store/Actions/Settings/importListExclusions.js b/frontend/src/Store/Actions/Settings/importListExclusions.js
new file mode 100644
index 000000000..b584e9e28
--- /dev/null
+++ b/frontend/src/Store/Actions/Settings/importListExclusions.js
@@ -0,0 +1,69 @@
+import { createAction } from 'redux-actions';
+import { createThunk } from 'Store/thunks';
+import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
+import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
+import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
+import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
+
+//
+// Variables
+
+const section = 'settings.importListExclusions';
+
+//
+// Actions Types
+
+export const FETCH_IMPORT_LIST_EXCLUSIONS = 'settings/importListExclusions/fetchImportListExclusions';
+export const SAVE_IMPORT_LIST_EXCLUSION = 'settings/importListExclusions/saveImportListExclusion';
+export const DELETE_IMPORT_LIST_EXCLUSION = 'settings/importListExclusions/deleteImportListExclusion';
+export const SET_IMPORT_LIST_EXCLUSION_VALUE = 'settings/importListExclusions/setImportListExclusionValue';
+
+//
+// Action Creators
+
+export const fetchImportListExclusions = createThunk(FETCH_IMPORT_LIST_EXCLUSIONS);
+export const saveImportListExclusion = createThunk(SAVE_IMPORT_LIST_EXCLUSION);
+export const deleteImportListExclusion = createThunk(DELETE_IMPORT_LIST_EXCLUSION);
+
+export const setImportListExclusionValue = createAction(SET_IMPORT_LIST_EXCLUSION_VALUE, (payload) => {
+ return {
+ section,
+ ...payload
+ };
+});
+
+//
+// Details
+
+export default {
+
+ //
+ // State
+
+ defaultState: {
+ isFetching: false,
+ isPopulated: false,
+ error: null,
+ items: [],
+ isSaving: false,
+ saveError: null,
+ pendingChanges: {}
+ },
+
+ //
+ // Action Handlers
+
+ actionHandlers: {
+ [FETCH_IMPORT_LIST_EXCLUSIONS]: createFetchHandler(section, '/importlistexclusion'),
+ [SAVE_IMPORT_LIST_EXCLUSION]: createSaveProviderHandler(section, '/importlistexclusion'),
+ [DELETE_IMPORT_LIST_EXCLUSION]: createRemoveItemHandler(section, '/importlistexclusion')
+ },
+
+ //
+ // Reducers
+
+ reducers: {
+ [SET_IMPORT_LIST_EXCLUSION_VALUE]: createSetSettingValueReducer(section)
+ }
+
+};
diff --git a/frontend/src/Store/Actions/artistActions.js b/frontend/src/Store/Actions/artistActions.js
index 500c5e84c..b7fe3069e 100644
--- a/frontend/src/Store/Actions/artistActions.js
+++ b/frontend/src/Store/Actions/artistActions.js
@@ -186,7 +186,8 @@ export const deleteArtist = createThunk(DELETE_ARTIST, (payload) => {
return {
...payload,
queryParams: {
- deleteFiles: payload.deleteFiles
+ deleteFiles: payload.deleteFiles,
+ addImportListExclusion: payload.addImportListExclusion
}
};
});
diff --git a/frontend/src/Store/Actions/settingsActions.js b/frontend/src/Store/Actions/settingsActions.js
index 1455a6935..b549c914d 100644
--- a/frontend/src/Store/Actions/settingsActions.js
+++ b/frontend/src/Store/Actions/settingsActions.js
@@ -9,6 +9,7 @@ import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers';
import languageProfiles from './Settings/languageProfiles';
import importLists from './Settings/importLists';
+import importListExclusions from './Settings/importListExclusions';
import metadataProfiles from './Settings/metadataProfiles';
import mediaManagement from './Settings/mediaManagement';
import metadata from './Settings/metadata';
@@ -27,6 +28,7 @@ export * from './Settings/downloadClients';
export * from './Settings/downloadClientOptions';
export * from './Settings/general';
export * from './Settings/importLists';
+export * from './Settings/importListExclusions';
export * from './Settings/indexerOptions';
export * from './Settings/indexers';
export * from './Settings/languageProfiles';
@@ -62,6 +64,7 @@ export const defaultState = {
indexers: indexers.defaultState,
languageProfiles: languageProfiles.defaultState,
importLists: importLists.defaultState,
+ importListExclusions: importListExclusions.defaultState,
metadataProfiles: metadataProfiles.defaultState,
mediaManagement: mediaManagement.defaultState,
metadata: metadata.defaultState,
@@ -102,6 +105,7 @@ export const actionHandlers = handleThunks({
...indexers.actionHandlers,
...languageProfiles.actionHandlers,
...importLists.actionHandlers,
+ ...importListExclusions.actionHandlers,
...metadataProfiles.actionHandlers,
...mediaManagement.actionHandlers,
...metadata.actionHandlers,
@@ -133,6 +137,7 @@ export const reducers = createHandleActions({
...indexers.reducers,
...languageProfiles.reducers,
...importLists.reducers,
+ ...importListExclusions.reducers,
...metadataProfiles.reducers,
...mediaManagement.reducers,
...metadata.reducers,
diff --git a/src/Lidarr.Api.V1/Artist/ArtistModule.cs b/src/Lidarr.Api.V1/Artist/ArtistModule.cs
index faeaf0b06..3f72ea898 100644
--- a/src/Lidarr.Api.V1/Artist/ArtistModule.cs
+++ b/src/Lidarr.Api.V1/Artist/ArtistModule.cs
@@ -171,8 +171,9 @@ namespace Lidarr.Api.V1.Artist
private void DeleteArtist(int id)
{
var deleteFiles = Request.GetBooleanQueryParameter("deleteFiles");
+ var addImportListExclusion = Request.GetBooleanQueryParameter("addImportListExclusion");
- _artistService.DeleteArtist(id, deleteFiles);
+ _artistService.DeleteArtist(id, deleteFiles, addImportListExclusion);
}
private void MapCoversToLocal(params ArtistResource[] artists)
diff --git a/src/Lidarr.Api.V1/ImportLists/ImportListExclusionModule.cs b/src/Lidarr.Api.V1/ImportLists/ImportListExclusionModule.cs
new file mode 100644
index 000000000..c5fdd0bbe
--- /dev/null
+++ b/src/Lidarr.Api.V1/ImportLists/ImportListExclusionModule.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using NzbDrone.Core.ImportLists.Exclusions;
+using Lidarr.Http;
+using FluentValidation;
+using NzbDrone.Core.Validation;
+
+namespace Lidarr.Api.V1.ImportLists
+{
+ public class ImportListExclusionModule : LidarrRestModule
+ {
+ private readonly IImportListExclusionService _importListExclusionService;
+
+ public ImportListExclusionModule(IImportListExclusionService importListExclusionService,
+ ImportListExclusionExistsValidator importListExclusionExistsValidator,
+ GuidValidator guidValidator)
+ {
+ _importListExclusionService = importListExclusionService;
+
+ GetResourceById = GetImportListExclusion;
+ GetResourceAll = GetImportListExclusions;
+ CreateResource = AddImportListExclusion;
+ UpdateResource = UpdateImportListExclusion;
+ DeleteResource = DeleteImportListExclusionResource;
+
+ SharedValidator.RuleFor(c => c.ForeignId).NotEmpty().SetValidator(guidValidator).SetValidator(importListExclusionExistsValidator);
+ SharedValidator.RuleFor(c => c.ArtistName).NotEmpty();
+ }
+
+ private ImportListExclusionResource GetImportListExclusion(int id)
+ {
+ return _importListExclusionService.Get(id).ToResource();
+ }
+
+ private List GetImportListExclusions()
+ {
+ return _importListExclusionService.All().ToResource();
+ }
+
+ private int AddImportListExclusion(ImportListExclusionResource resource)
+ {
+ var customFilter = _importListExclusionService.Add(resource.ToModel());
+
+ return customFilter.Id;
+ }
+
+ private void UpdateImportListExclusion(ImportListExclusionResource resource)
+ {
+ _importListExclusionService.Update(resource.ToModel());
+ }
+
+ private void DeleteImportListExclusionResource(int id)
+ {
+ _importListExclusionService.Delete(id);
+ }
+ }
+}
diff --git a/src/Lidarr.Api.V1/ImportLists/ImportListExclusionResource.cs b/src/Lidarr.Api.V1/ImportLists/ImportListExclusionResource.cs
new file mode 100644
index 000000000..91d7f52d3
--- /dev/null
+++ b/src/Lidarr.Api.V1/ImportLists/ImportListExclusionResource.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Linq;
+using NzbDrone.Core.ImportLists.Exclusions;
+using Lidarr.Http.REST;
+
+namespace Lidarr.Api.V1.ImportLists
+{
+ public class ImportListExclusionResource : RestResource
+ {
+ public string ForeignId { get; set; }
+ public string ArtistName { get; set; }
+ }
+
+ public static class ImportListExclusionResourceMapper
+ {
+ public static ImportListExclusionResource ToResource(this ImportListExclusion model)
+ {
+ if (model == null) return null;
+
+ return new ImportListExclusionResource
+ {
+ Id = model.Id,
+ ForeignId = model.ForeignId,
+ ArtistName = model.Name,
+ };
+ }
+
+ public static ImportListExclusion ToModel(this ImportListExclusionResource resource)
+ {
+ if (resource == null) return null;
+
+ return new ImportListExclusion
+ {
+ Id = resource.Id,
+ ForeignId = resource.ForeignId,
+ Name = resource.ArtistName
+ };
+ }
+
+ public static List ToResource(this IEnumerable filters)
+ {
+ return filters.Select(ToResource).ToList();
+ }
+ }
+}
diff --git a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj
index 4a4fd06fd..9dd489523 100644
--- a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj
+++ b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj
@@ -99,6 +99,8 @@
+
+
diff --git a/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs b/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
index 307e66759..df76a7cc8 100644
--- a/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/ImportListTests/ImportListSyncServiceFixture.cs
@@ -7,6 +7,7 @@ using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Music;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.ImportLists.Exclusions;
namespace NzbDrone.Core.Test.ImportListTests
{
@@ -43,6 +44,10 @@ namespace NzbDrone.Core.Test.ImportListTests
Mocker.GetMock()
.Setup(v => v.Fetch())
.Returns(_importListReports);
+
+ Mocker.GetMock()
+ .Setup(v => v.All())
+ .Returns(new List());
}
private void WithAlbum()
@@ -67,6 +72,17 @@ namespace NzbDrone.Core.Test.ImportListTests
.Returns(new Artist{ForeignArtistId = _importListReports.First().ArtistMusicBrainzId });
}
+ private void WithExcludedArtist()
+ {
+ Mocker.GetMock()
+ .Setup(v => v.All())
+ .Returns(new List {
+ new ImportListExclusion {
+ ForeignId = "f59c5520-5f46-4d2c-b2c4-822eabf53419"
+ }
+ });
+ }
+
[Test]
public void should_search_if_artist_title_and_no_artist_id()
{
@@ -123,7 +139,7 @@ namespace NzbDrone.Core.Test.ImportListTests
}
[Test]
- public void should_not_try_add_if_existing_artist()
+ public void should_not_add_if_existing_artist()
{
WithArtistId();
WithAlbum();
@@ -149,6 +165,20 @@ namespace NzbDrone.Core.Test.ImportListTests
.Verify(v => v.AddArtists(It.Is>(t => t.Count == 1)));
}
+ [Test]
+ public void should_not_add_if_excluded_artist()
+ {
+ WithArtistId();
+ WithAlbum();
+ WithAlbumId();
+ WithExcludedArtist();
+
+ Subject.Execute(new ImportListSyncCommand());
+
+ Mocker.GetMock()
+ .Verify(v => v.AddArtists(It.Is>(t => t.Count == 0)));
+ }
+
[Test]
public void should_mark_album_for_monitor_if_album_id()
{
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 00dcad6ae..dce67f8c2 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -387,6 +387,7 @@
+
@@ -610,9 +611,6 @@
-
+
-
+
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/ValidationTests/GuidValidationFixture.cs b/src/NzbDrone.Core.Test/ValidationTests/GuidValidationFixture.cs
new file mode 100644
index 000000000..6c03bedf4
--- /dev/null
+++ b/src/NzbDrone.Core.Test/ValidationTests/GuidValidationFixture.cs
@@ -0,0 +1,44 @@
+using FizzWare.NBuilder;
+using FluentAssertions;
+using NUnit.Framework;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Validation;
+using NzbDrone.Test.Common;
+using NzbDrone.Core.ImportLists.Exclusions;
+
+namespace NzbDrone.Core.Test.ValidationTests
+{
+ public class GuidValidationFixture : CoreTest
+ {
+ private TestValidator _validator;
+
+ [SetUp]
+ public void Setup()
+ {
+ _validator = new TestValidator
+ {
+ v => v.RuleFor(s => s.ForeignId).SetValidator(Subject)
+ };
+ }
+
+ [Test]
+ public void should_not_be_valid_if_invalid_guid()
+ {
+ var listExclusion = Builder.CreateNew()
+ .With(s => s.ForeignId = "e1f1e33e-2e4c-4d43-b91b-7064068d328")
+ .Build();
+
+ _validator.Validate(listExclusion).IsValid.Should().BeFalse();
+ }
+
+ [Test]
+ public void should_be_valid_if_valid_guid()
+ {
+ var listExclusion = Builder.CreateNew()
+ .With(s => s.ForeignId = "e1f1e33e-2e4c-4d43-b91b-7064068d3283")
+ .Build();
+
+ _validator.Validate(listExclusion).IsValid.Should().BeTrue();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Datastore/Migration/027_add_import_exclusions.cs b/src/NzbDrone.Core/Datastore/Migration/027_add_import_exclusions.cs
new file mode 100644
index 000000000..7832ea365
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/027_add_import_exclusions.cs
@@ -0,0 +1,16 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+ [Migration(027)]
+ public class add_import_exclusions : NzbDroneMigrationBase
+ {
+ protected override void MainDbUpgrade()
+ {
+ Create.TableForModel("ImportListExclusions")
+ .WithColumn("ForeignId").AsString().NotNullable().Unique()
+ .WithColumn("Name").AsString().NotNullable();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs
index 20bf4042e..23060b636 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -12,6 +12,7 @@ using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.ImportLists;
+using NzbDrone.Core.ImportLists.Exclusions;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.MediaFiles;
@@ -191,6 +192,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity().RegisterModel("ImportListStatus");
Mapper.Entity().RegisterModel("CustomFilters");
+ Mapper.Entity().RegisterModel("ImportListExclusions");
}
private static void RegisterMappers()
diff --git a/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusion.cs b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusion.cs
new file mode 100644
index 000000000..e91f58b30
--- /dev/null
+++ b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusion.cs
@@ -0,0 +1,10 @@
+using NzbDrone.Core.Datastore;
+
+namespace NzbDrone.Core.ImportLists.Exclusions
+{
+ public class ImportListExclusion : ModelBase
+ {
+ public string ForeignId { get; set; }
+ public string Name { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionExistsValidator.cs b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionExistsValidator.cs
new file mode 100644
index 000000000..db2179493
--- /dev/null
+++ b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionExistsValidator.cs
@@ -0,0 +1,22 @@
+using FluentValidation.Validators;
+
+namespace NzbDrone.Core.ImportLists.Exclusions
+{
+ public class ImportListExclusionExistsValidator : PropertyValidator
+ {
+ private readonly IImportListExclusionService _importListExclusionService;
+
+ public ImportListExclusionExistsValidator(IImportListExclusionService importListExclusionService)
+ : base("This exclusion has already been added.")
+ {
+ _importListExclusionService = importListExclusionService;
+ }
+
+ protected override bool IsValid(PropertyValidatorContext context)
+ {
+ if (context.PropertyValue == null) return true;
+
+ return (!_importListExclusionService.All().Exists(s => s.ForeignId == context.PropertyValue.ToString()));
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionRepository.cs b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionRepository.cs
new file mode 100644
index 000000000..11e538374
--- /dev/null
+++ b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionRepository.cs
@@ -0,0 +1,24 @@
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging.Events;
+using System.Linq;
+
+namespace NzbDrone.Core.ImportLists.Exclusions
+{
+ public interface IImportListExclusionRepository : IBasicRepository
+ {
+ ImportListExclusion FindByForeignId(string foreignId);
+ }
+
+ public class ImportListExclusionRepository : BasicRepository, IImportListExclusionRepository
+ {
+ public ImportListExclusionRepository(IMainDatabase database, IEventAggregator eventAggregator)
+ : base(database, eventAggregator)
+ {
+ }
+
+ public ImportListExclusion FindByForeignId(string foreignId)
+ {
+ return Query.Where(m => m.ForeignId == foreignId).SingleOrDefault();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionService.cs b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionService.cs
new file mode 100644
index 000000000..343e04a88
--- /dev/null
+++ b/src/NzbDrone.Core/ImportLists/Exclusions/ImportListExclusionService.cs
@@ -0,0 +1,80 @@
+using NzbDrone.Core.Messaging.Events;
+using NzbDrone.Core.Music.Events;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NzbDrone.Core.ImportLists.Exclusions
+{
+ public interface IImportListExclusionService
+ {
+ ImportListExclusion Add(ImportListExclusion importListExclusion);
+ List All();
+ void Delete(int id);
+ ImportListExclusion Get(int id);
+ ImportListExclusion FindByForeignId(string foreignId);
+ ImportListExclusion Update(ImportListExclusion importListExclusion);
+ }
+
+ public class ImportListExclusionService : IImportListExclusionService, IHandleAsync
+ {
+ private readonly IImportListExclusionRepository _repo;
+
+ public ImportListExclusionService(IImportListExclusionRepository repo)
+ {
+ _repo = repo;
+ }
+
+ public ImportListExclusion Add(ImportListExclusion importListExclusion)
+ {
+ return _repo.Insert(importListExclusion);
+ }
+
+ public ImportListExclusion Update(ImportListExclusion importListExclusion)
+ {
+ return _repo.Update(importListExclusion);
+ }
+
+ public void Delete(int id)
+ {
+ _repo.Delete(id);
+ }
+
+ public ImportListExclusion Get(int id)
+ {
+ return _repo.Get(id);
+ }
+
+ public ImportListExclusion FindByForeignId(string foreignId)
+ {
+ return _repo.FindByForeignId(foreignId);
+ }
+
+ public List All()
+ {
+ return _repo.All().ToList();
+ }
+
+ public void HandleAsync(ArtistDeletedEvent message)
+ {
+ if (!message.AddImportListExclusion)
+ {
+ return;
+ }
+
+ var existingExclusion = _repo.FindByForeignId(message.Artist.ForeignArtistId);
+
+ if (existingExclusion != null)
+ {
+ return;
+ }
+
+ var importExclusion = new ImportListExclusion
+ {
+ ForeignId = message.Artist.ForeignArtistId,
+ Name = message.Artist.Name
+ };
+
+ _repo.Insert(importExclusion);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs
index 700de0cbb..c771254c9 100644
--- a/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs
+++ b/src/NzbDrone.Core/ImportLists/ImportListSyncService.cs
@@ -3,6 +3,7 @@ using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
+using NzbDrone.Core.ImportLists.Exclusions;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource;
@@ -15,6 +16,7 @@ namespace NzbDrone.Core.ImportLists
{
private readonly IImportListStatusService _importListStatusService;
private readonly IImportListFactory _importListFactory;
+ private readonly IImportListExclusionService _importListExclusionService;
private readonly IFetchAndParseImportList _listFetcherAndParser;
private readonly ISearchForNewAlbum _albumSearchService;
private readonly ISearchForNewArtist _artistSearchService;
@@ -25,6 +27,7 @@ namespace NzbDrone.Core.ImportLists
public ImportListSyncService(IImportListStatusService importListStatusService,
IImportListFactory importListFactory,
+ IImportListExclusionService importListExclusionService,
IFetchAndParseImportList listFetcherAndParser,
ISearchForNewAlbum albumSearchService,
ISearchForNewArtist artistSearchService,
@@ -35,6 +38,7 @@ namespace NzbDrone.Core.ImportLists
{
_importListStatusService = importListStatusService;
_importListFactory = importListFactory;
+ _importListExclusionService = importListExclusionService;
_listFetcherAndParser = listFetcherAndParser;
_albumSearchService = albumSearchService;
_artistSearchService = artistSearchService;
@@ -78,6 +82,8 @@ namespace NzbDrone.Core.ImportLists
var reportNumber = 1;
+ var listExclusions = _importListExclusionService.All();
+
foreach (var report in reports)
{
_logger.ProgressTrace("Processing list item {0}/{1}", reportNumber, reports.Count);
@@ -112,9 +118,17 @@ namespace NzbDrone.Core.ImportLists
// Check to see if artist in DB
var existingArtist = _artistService.FindById(report.ArtistMusicBrainzId);
+
+ // Check to see if artist excluded
+ var excludedArtist = listExclusions.Where(s => s.ForeignId == report.ArtistMusicBrainzId).SingleOrDefault();
+
+ if (excludedArtist != null)
+ {
+ _logger.Debug("{0} [{1}] Rejected due to list exlcusion", report.ArtistMusicBrainzId, report.Artist);
+ }
// Append Artist if not already in DB or already on add list
- if (existingArtist == null && artistsToAdd.All(s => s.Metadata.Value.ForeignArtistId != report.ArtistMusicBrainzId))
+ if (existingArtist == null && excludedArtist == null && artistsToAdd.All(s => s.Metadata.Value.ForeignArtistId != report.ArtistMusicBrainzId))
{
artistsToAdd.Add(new Artist
{
diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs
index 9d708ec77..8f977ef19 100644
--- a/src/NzbDrone.Core/Music/ArtistService.cs
+++ b/src/NzbDrone.Core/Music/ArtistService.cs
@@ -4,9 +4,10 @@ using NzbDrone.Core.Music.Events;
using System;
using System.Collections.Generic;
using System.Linq;
-using NzbDrone.Core.Parser;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Cache;
+using NzbDrone.Core.ImportLists.Exclusions;
+using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Music
{
@@ -21,7 +22,7 @@ namespace NzbDrone.Core.Music
Artist FindByName(string title);
Artist FindByNameInexact(string title);
List GetCandidates(string title);
- void DeleteArtist(int artistId, bool deleteFiles);
+ void DeleteArtist(int artistId, bool deleteFiles, bool addImportListExclusion = false);
List GetAllArtists();
List AllForTag(int tagId);
Artist UpdateArtist(Artist artist);
@@ -36,6 +37,7 @@ namespace NzbDrone.Core.Music
private readonly IArtistMetadataRepository _artistMetadataRepository;
private readonly IEventAggregator _eventAggregator;
private readonly ITrackService _trackService;
+ private readonly IImportListExclusionService _importListExclusionService;
private readonly IBuildArtistPaths _artistPathBuilder;
private readonly Logger _logger;
private readonly ICached> _cache;
@@ -44,6 +46,7 @@ namespace NzbDrone.Core.Music
IArtistMetadataRepository artistMetadataRepository,
IEventAggregator eventAggregator,
ITrackService trackService,
+ IImportListExclusionService importListExclusionService,
IBuildArtistPaths artistPathBuilder,
ICacheManager cacheManager,
Logger logger)
@@ -52,6 +55,7 @@ namespace NzbDrone.Core.Music
_artistMetadataRepository = artistMetadataRepository;
_eventAggregator = eventAggregator;
_trackService = trackService;
+ _importListExclusionService = importListExclusionService;
_artistPathBuilder = artistPathBuilder;
_cache = cacheManager.GetCache>(GetType());
_logger = logger;
@@ -82,12 +86,12 @@ namespace NzbDrone.Core.Music
return _artistRepository.ArtistPathExists(folder);
}
- public void DeleteArtist(int artistId, bool deleteFiles)
+ public void DeleteArtist(int artistId, bool deleteFiles, bool addImportListExclusion = false)
{
_cache.Clear();
var artist = _artistRepository.Get(artistId);
_artistRepository.Delete(artistId);
- _eventAggregator.PublishEvent(new ArtistDeletedEvent(artist, deleteFiles));
+ _eventAggregator.PublishEvent(new ArtistDeletedEvent(artist, deleteFiles, addImportListExclusion));
}
public Artist FindById(string spotifyId)
diff --git a/src/NzbDrone.Core/Music/Events/ArtistDeletedEvent.cs b/src/NzbDrone.Core/Music/Events/ArtistDeletedEvent.cs
index b889816a6..5c448f133 100644
--- a/src/NzbDrone.Core/Music/Events/ArtistDeletedEvent.cs
+++ b/src/NzbDrone.Core/Music/Events/ArtistDeletedEvent.cs
@@ -1,4 +1,4 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Messaging;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,11 +10,13 @@ namespace NzbDrone.Core.Music.Events
{
public Artist Artist { get; private set; }
public bool DeleteFiles { get; private set; }
+ public bool AddImportListExclusion { get; private set; }
- public ArtistDeletedEvent(Artist artist, bool deleteFiles)
+ public ArtistDeletedEvent(Artist artist, bool deleteFiles, bool addImportListExclusion)
{
Artist = artist;
DeleteFiles = deleteFiles;
+ AddImportListExclusion = addImportListExclusion;
}
}
}
diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs
index a4e0235e9..e916815dc 100644
--- a/src/NzbDrone.Core/Music/RefreshArtistService.cs
+++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs
@@ -14,6 +14,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
+using NzbDrone.Core.ImportLists.Exclusions;
namespace NzbDrone.Core.Music
{
@@ -29,6 +30,7 @@ namespace NzbDrone.Core.Music
private readonly IDiskScanService _diskScanService;
private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed;
private readonly IConfigService _configService;
+ private readonly IImportListExclusionService _importListExclusionService;
private readonly Logger _logger;
public RefreshArtistService(IProvideArtistInfo artistInfo,
@@ -41,6 +43,7 @@ namespace NzbDrone.Core.Music
IDiskScanService diskScanService,
ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed,
IConfigService configService,
+ IImportListExclusionService importListExclusionService,
Logger logger)
{
_artistInfo = artistInfo;
@@ -53,6 +56,7 @@ namespace NzbDrone.Core.Music
_diskScanService = diskScanService;
_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed;
_configService = configService;
+ _importListExclusionService = importListExclusionService;
_logger = logger;
}
@@ -75,6 +79,16 @@ namespace NzbDrone.Core.Music
if (artist.Metadata.Value.ForeignArtistId != artistInfo.Metadata.Value.ForeignArtistId)
{
_logger.Warn("Artist '{0}' (Artist {1}) was replaced with '{2}' (LidarrAPI {3}), because the original was a duplicate.", artist.Name, artist.Metadata.Value.ForeignArtistId, artistInfo.Name, artistInfo.Metadata.Value.ForeignArtistId);
+
+ // Update list exclusion if one exists
+ var importExclusion = _importListExclusionService.FindByForeignId(artist.Metadata.Value.ForeignArtistId);
+
+ if (importExclusion != null)
+ {
+ importExclusion.ForeignId = artistInfo.Metadata.Value.ForeignArtistId;
+ _importListExclusionService.Update(importExclusion);
+ }
+
artist.Metadata.Value.ForeignArtistId = artistInfo.Metadata.Value.ForeignArtistId;
}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index e8abd34ae..d20c63480 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -194,6 +194,7 @@
+
@@ -545,6 +546,10 @@
+
+
+
+
@@ -1200,6 +1205,7 @@
+
diff --git a/src/NzbDrone.Core/Validation/GuidValidator.cs b/src/NzbDrone.Core/Validation/GuidValidator.cs
new file mode 100644
index 000000000..37e9d0a9d
--- /dev/null
+++ b/src/NzbDrone.Core/Validation/GuidValidator.cs
@@ -0,0 +1,20 @@
+using System;
+using FluentValidation.Validators;
+
+namespace NzbDrone.Core.Validation
+{
+ public class GuidValidator : PropertyValidator
+ {
+ public GuidValidator()
+ : base("String is not a valid Guid")
+ {
+ }
+
+ protected override bool IsValid(PropertyValidatorContext context)
+ {
+ if (context.PropertyValue == null) return false;
+
+ return Guid.TryParse(context.PropertyValue.ToString(), out Guid guidOutput);
+ }
+ }
+}