diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieConnector.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieConnector.js
index bc4d71435..6c238a10d 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieConnector.js
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieConnector.js
@@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
import parseUrl from 'Utilities/String/parseUrl';
import { lookupMovie, clearAddMovie } from 'Store/Actions/addMovieActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
+import { fetchNetImportExclusions } from 'Store/Actions/Settings/netImportExclusions';
import AddNewMovie from './AddNewMovie';
function createMapStateToProps() {
@@ -25,7 +26,8 @@ function createMapStateToProps() {
const mapDispatchToProps = {
lookupMovie,
clearAddMovie,
- fetchRootFolders
+ fetchRootFolders,
+ fetchNetImportExclusions
};
class AddNewMovieConnector extends Component {
@@ -41,6 +43,7 @@ class AddNewMovieConnector extends Component {
componentDidMount() {
this.props.fetchRootFolders();
+ this.props.fetchNetImportExclusions();
}
componentWillUnmount() {
@@ -96,7 +99,8 @@ AddNewMovieConnector.propTypes = {
term: PropTypes.string,
lookupMovie: PropTypes.func.isRequired,
clearAddMovie: PropTypes.func.isRequired,
- fetchRootFolders: PropTypes.func.isRequired
+ fetchRootFolders: PropTypes.func.isRequired,
+ fetchNetImportExclusions: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.css b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.css
index 38ccffb4d..87959fe45 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.css
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.css
@@ -35,6 +35,11 @@
color: #37bc9b;
}
+.exclusionIcon {
+ margin-left: 10px;
+ color: #bc3737;
+}
+
.overview {
margin-top: 20px;
}
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.js
index 186459e95..1afdbb478 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.js
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResult.js
@@ -24,7 +24,7 @@ class AddNewMovieSearchResult extends Component {
componentDidUpdate(prevProps) {
if (!prevProps.isExistingMovie && this.props.isExistingMovie) {
- this.onAddSerisModalClose();
+ this.onAddMovieModalClose();
}
}
@@ -35,7 +35,7 @@ class AddNewMovieSearchResult extends Component {
this.setState({ isNewAddMovieModalOpen: true });
}
- onAddSerisModalClose = () => {
+ onAddMovieModalClose = () => {
this.setState({ isNewAddMovieModalOpen: false });
}
@@ -54,6 +54,7 @@ class AddNewMovieSearchResult extends Component {
ratings,
images,
isExistingMovie,
+ isExclusionMovie,
isSmallScreen
} = this.props;
const {
@@ -97,6 +98,16 @@ class AddNewMovieSearchResult extends Component {
title="Already in your library"
/>
}
+
+ {
+ isExclusionMovie &&
+
+ }
@@ -138,7 +149,7 @@ class AddNewMovieSearchResult extends Component {
year={year}
overview={overview}
images={images}
- onModalClose={this.onAddSerisModalClose}
+ onModalClose={this.onAddMovieModalClose}
/>
);
@@ -156,6 +167,7 @@ AddNewMovieSearchResult.propTypes = {
ratings: PropTypes.object.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingMovie: PropTypes.bool.isRequired,
+ isExclusionMovie: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired
};
diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResultConnector.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResultConnector.js
index fccbe6120..54bc03881 100644
--- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResultConnector.js
+++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieSearchResultConnector.js
@@ -1,16 +1,19 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector';
+import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import AddNewMovieSearchResult from './AddNewMovieSearchResult';
function createMapStateToProps() {
return createSelector(
createExistingMovieSelector(),
+ createExclusionMovieSelector(),
createDimensionsSelector(),
- (isExistingMovie, dimensions) => {
+ (isExistingMovie, isExclusionMovie, dimensions) => {
return {
isExistingMovie,
+ isExclusionMovie,
isSmallScreen: dimensions.isSmallScreen
};
}
diff --git a/frontend/src/Movie/Delete/DeleteMovieModalContent.js b/frontend/src/Movie/Delete/DeleteMovieModalContent.js
index f76a4de35..f0e288573 100644
--- a/frontend/src/Movie/Delete/DeleteMovieModalContent.js
+++ b/frontend/src/Movie/Delete/DeleteMovieModalContent.js
@@ -22,7 +22,8 @@ class DeleteMovieModalContent extends Component {
super(props, context);
this.state = {
- deleteFiles: false
+ deleteFiles: false,
+ addNetImportExclusion: false
};
}
@@ -33,11 +34,17 @@ class DeleteMovieModalContent extends Component {
this.setState({ deleteFiles: value });
}
+ onAddNetImportExclusionChange = ({ value }) => {
+ this.setState({ addNetImportExclusion: value });
+ }
+
onDeleteMovieConfirmed = () => {
const deleteFiles = this.state.deleteFiles;
+ const addNetImportExclusion = this.state.addNetImportExclusion;
this.setState({ deleteFiles: false });
- this.props.onDeletePress(deleteFiles);
+ this.setState({ addNetImportExclusion: false });
+ this.props.onDeletePress(deleteFiles, addNetImportExclusion);
}
//
@@ -57,6 +64,8 @@ class DeleteMovieModalContent extends Component {
} = statistics;
const deleteFiles = this.state.deleteFiles;
+ const addNetImportExclusion = this.state.addNetImportExclusion;
+
let deleteFilesLabel = `Delete ${movieFileCount} Movie Files`;
let deleteFilesHelpText = 'Delete the movie files and movie folder';
@@ -108,6 +117,19 @@ class DeleteMovieModalContent extends Component {
}
+
+ Add List Exclusion
+
+
+
+
diff --git a/frontend/src/Movie/Delete/DeleteMovieModalContentConnector.js b/frontend/src/Movie/Delete/DeleteMovieModalContentConnector.js
index d02c30294..2f2df1489 100644
--- a/frontend/src/Movie/Delete/DeleteMovieModalContentConnector.js
+++ b/frontend/src/Movie/Delete/DeleteMovieModalContentConnector.js
@@ -24,10 +24,11 @@ class DeleteMovieModalContentConnector extends Component {
//
// Listeners
- onDeletePress = (deleteFiles) => {
+ onDeletePress = (deleteFiles, addNetImportExclusion) => {
this.props.deleteMovie({
id: this.props.movieId,
- deleteFiles
+ deleteFiles,
+ addNetImportExclusion
});
this.props.onModalClose(true);
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModal.js b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModal.js
new file mode 100644
index 000000000..0a9601cf9
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModal.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 EditNetImportExclusionModalContentConnector from './EditNetImportExclusionModalContentConnector';
+
+function EditNetImportExclusionModal({ isOpen, onModalClose, ...otherProps }) {
+ return (
+
+
+
+ );
+}
+
+EditNetImportExclusionModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default EditNetImportExclusionModal;
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalConnector.js b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalConnector.js
new file mode 100644
index 000000000..3d5833f8e
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalConnector.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 EditNetImportExclusionModal from './EditNetImportExclusionModal';
+
+function mapStateToProps() {
+ return {};
+}
+
+const mapDispatchToProps = {
+ clearPendingChanges
+};
+
+class EditNetImportExclusionModalConnector extends Component {
+
+ //
+ // Listeners
+
+ onModalClose = () => {
+ this.props.clearPendingChanges({ section: 'settings.netImportExclusions' });
+ this.props.onModalClose();
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+EditNetImportExclusionModalConnector.propTypes = {
+ onModalClose: PropTypes.func.isRequired,
+ clearPendingChanges: PropTypes.func.isRequired
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(EditNetImportExclusionModalConnector);
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalContent.css b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalContent.css
new file mode 100644
index 000000000..97e132552
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalContent.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/NetImport/NetImportExclusions/EditNetImportExclusionModalContent.js b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalContent.js
new file mode 100644
index 000000000..47349e737
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalContent.js
@@ -0,0 +1,143 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { inputTypes, kinds } from 'Helpers/Props';
+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 './EditNetImportExclusionModalContent.css';
+
+function EditNetImportExclusionModalContent(props) {
+ const {
+ id,
+ isFetching,
+ error,
+ isSaving,
+ saveError,
+ item,
+ onInputChange,
+ onSavePress,
+ onModalClose,
+ onDeleteNetImportExclusionPress,
+ ...otherProps
+ } = props;
+
+ const {
+ movieTitle = '',
+ tmdbId,
+ movieYear
+ } = item;
+
+ return (
+
+
+ {id ? 'Edit List Exclusion' : 'Add List Exclusion'}
+
+
+
+ {
+ isFetching &&
+
+ }
+
+ {
+ !isFetching && !!error &&
+ Unable to add a new list exclusion, please try again.
+ }
+
+ {
+ !isFetching && !error &&
+
+ }
+
+
+
+ {
+ id &&
+
+ }
+
+
+
+
+ Save
+
+
+
+ );
+}
+
+EditNetImportExclusionModalContent.propTypes = {
+ id: PropTypes.number,
+ isFetching: PropTypes.bool.isRequired,
+ error: PropTypes.object,
+ isSaving: PropTypes.bool.isRequired,
+ saveError: PropTypes.object,
+ item: PropTypes.object.isRequired,
+ onInputChange: PropTypes.func.isRequired,
+ onSavePress: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onDeleteNetImportExclusionPress: PropTypes.func
+};
+
+export default EditNetImportExclusionModalContent;
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalContentConnector.js b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalContentConnector.js
new file mode 100644
index 000000000..967eb8aca
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/EditNetImportExclusionModalContentConnector.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 { setNetImportExclusionValue, saveNetImportExclusion } from 'Store/Actions/settingsActions';
+import EditNetImportExclusionModalContent from './EditNetImportExclusionModalContent';
+
+const newNetImportExclusion = {
+ artistName: '',
+ foreignId: ''
+};
+
+function createNetImportExclusionSelector() {
+ return createSelector(
+ (state, { id }) => id,
+ (state) => state.settings.netImportExclusions,
+ (id, netImportExclusions) => {
+ const {
+ isFetching,
+ error,
+ isSaving,
+ saveError,
+ pendingChanges,
+ items
+ } = netImportExclusions;
+
+ const mapping = id ? _.find(items, { id }) : newNetImportExclusion;
+ const settings = selectSettings(mapping, pendingChanges, saveError);
+
+ return {
+ id,
+ isFetching,
+ error,
+ isSaving,
+ saveError,
+ item: settings.settings,
+ ...settings
+ };
+ }
+ );
+}
+
+function createMapStateToProps() {
+ return createSelector(
+ createNetImportExclusionSelector(),
+ (netImportExclusion) => {
+ return {
+ ...netImportExclusion
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ setNetImportExclusionValue,
+ saveNetImportExclusion
+};
+
+class EditNetImportExclusionModalContentConnector extends Component {
+
+ //
+ // Lifecycle
+
+ componentDidMount() {
+ if (!this.props.id) {
+ Object.keys(newNetImportExclusion).forEach((name) => {
+ this.props.setNetImportExclusionValue({
+ name,
+ value: newNetImportExclusion[name]
+ });
+ });
+ }
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
+ this.props.onModalClose();
+ }
+ }
+
+ //
+ // Listeners
+
+ onInputChange = ({ name, value }) => {
+ this.props.setNetImportExclusionValue({ name, value });
+ }
+
+ onSavePress = () => {
+ this.props.saveNetImportExclusion({ id: this.props.id });
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+EditNetImportExclusionModalContentConnector.propTypes = {
+ id: PropTypes.number,
+ isSaving: PropTypes.bool.isRequired,
+ saveError: PropTypes.object,
+ item: PropTypes.object.isRequired,
+ setNetImportExclusionValue: PropTypes.func.isRequired,
+ saveNetImportExclusion: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(EditNetImportExclusionModalContentConnector);
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusion.css b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusion.css
new file mode 100644
index 000000000..780d6f8f1
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusion.css
@@ -0,0 +1,24 @@
+.netImportExclusion {
+ display: flex;
+ align-items: stretch;
+ margin-bottom: 10px;
+ height: 30px;
+ border-bottom: 1px solid $borderColor;
+ line-height: 30px;
+}
+
+.movieTitle {
+ flex: 0 0 400px;
+}
+
+.tmdbId,
+.movieYear {
+ flex: 0 0 200px;
+}
+
+.actions {
+ display: flex;
+ justify-content: flex-end;
+ flex: 1 0 auto;
+ padding-right: 10px;
+}
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusion.js b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusion.js
new file mode 100644
index 000000000..f766e3685
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusion.js
@@ -0,0 +1,114 @@
+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 EditNetImportExclusionModalConnector from './EditNetImportExclusionModalConnector';
+import styles from './NetImportExclusion.css';
+
+class NetImportExclusion extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isEditNetImportExclusionModalOpen: false,
+ isDeleteNetImportExclusionModalOpen: false
+ };
+ }
+
+ //
+ // Listeners
+
+ onEditNetImportExclusionPress = () => {
+ this.setState({ isEditNetImportExclusionModalOpen: true });
+ }
+
+ onEditNetImportExclusionModalClose = () => {
+ this.setState({ isEditNetImportExclusionModalOpen: false });
+ }
+
+ onDeleteNetImportExclusionPress = () => {
+ this.setState({
+ isEditNetImportExclusionModalOpen: false,
+ isDeleteNetImportExclusionModalOpen: true
+ });
+ }
+
+ onDeleteNetImportExclusionModalClose = () => {
+ this.setState({ isDeleteNetImportExclusionModalOpen: false });
+ }
+
+ onConfirmDeleteNetImportExclusion = () => {
+ this.props.onConfirmDeleteNetImportExclusion(this.props.id);
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ id,
+ movieTitle,
+ tmdbId,
+ movieYear
+ } = this.props;
+
+ return (
+
+
{tmdbId}
+
{movieTitle}
+
{movieYear}
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+NetImportExclusion.propTypes = {
+ id: PropTypes.number.isRequired,
+ movieTitle: PropTypes.string.isRequired,
+ tmdbId: PropTypes.number.isRequired,
+ movieYear: PropTypes.number.isRequired,
+ onConfirmDeleteNetImportExclusion: PropTypes.func.isRequired
+};
+
+NetImportExclusion.defaultProps = {
+ // The drag preview will not connect the drag handle.
+ connectDragSource: (node) => node
+};
+
+export default NetImportExclusion;
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusions.css b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusions.css
new file mode 100644
index 000000000..00b917a84
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusions.css
@@ -0,0 +1,24 @@
+.netImportExclusionsHeader {
+ display: flex;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+.title {
+ flex: 0 0 400px;
+}
+
+.tmdbId,
+.movieYear {
+ flex: 0 0 200px;
+}
+
+.addNetImportExclusion {
+ display: flex;
+ justify-content: flex-end;
+ padding-right: 10px;
+}
+
+.addButton {
+ text-align: center;
+}
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusions.js b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusions.js
new file mode 100644
index 000000000..198bf08a2
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusions.js
@@ -0,0 +1,101 @@
+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 NetImportExclusion from './NetImportExclusion';
+import EditNetImportExclusionModalConnector from './EditNetImportExclusionModalConnector';
+import styles from './NetImportExclusions.css';
+
+class NetImportExclusions extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isAddNetImportExclusionModalOpen: false
+ };
+ }
+
+ //
+ // Listeners
+
+ onAddNetImportExclusionPress = () => {
+ this.setState({ isAddNetImportExclusionModalOpen: true });
+ }
+
+ onModalClose = () => {
+ this.setState({ isAddNetImportExclusionModalOpen: false });
+ }
+
+ //
+ // Render
+
+ render() {
+ const {
+ items,
+ onConfirmDeleteNetImportExclusion,
+ ...otherProps
+ } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+NetImportExclusions.propTypes = {
+ isFetching: PropTypes.bool.isRequired,
+ error: PropTypes.object,
+ items: PropTypes.arrayOf(PropTypes.object).isRequired,
+ onConfirmDeleteNetImportExclusion: PropTypes.func.isRequired
+};
+
+export default NetImportExclusions;
diff --git a/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusionsConnector.js b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusionsConnector.js
new file mode 100644
index 000000000..5d561d431
--- /dev/null
+++ b/frontend/src/Settings/NetImport/NetImportExclusions/NetImportExclusionsConnector.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 { fetchNetImportExclusions, deleteNetImportExclusion } from 'Store/Actions/settingsActions';
+import NetImportExclusions from './NetImportExclusions';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state) => state.settings.netImportExclusions,
+ (netImportExclusions) => {
+ return {
+ ...netImportExclusions
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ fetchNetImportExclusions,
+ deleteNetImportExclusion
+};
+
+class NetImportExclusionsConnector extends Component {
+
+ //
+ // Lifecycle
+
+ componentDidMount() {
+ this.props.fetchNetImportExclusions();
+ }
+
+ //
+ // Listeners
+
+ onConfirmDeleteNetImportExclusion = (id) => {
+ this.props.deleteNetImportExclusion({ id });
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+NetImportExclusionsConnector.propTypes = {
+ fetchNetImportExclusions: PropTypes.func.isRequired,
+ deleteNetImportExclusion: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(NetImportExclusionsConnector);
diff --git a/frontend/src/Settings/NetImport/NetImportSettings.js b/frontend/src/Settings/NetImport/NetImportSettings.js
index 9da0370a8..eb614d68c 100644
--- a/frontend/src/Settings/NetImport/NetImportSettings.js
+++ b/frontend/src/Settings/NetImport/NetImportSettings.js
@@ -8,7 +8,7 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import NetImportsConnector from './NetImport/NetImportsConnector';
import NetImportOptionsConnector from './Options/NetImportOptionsConnector';
-// import ImportExclusionsConnector from './ImportExclusions/ImportExclusionsConnector';
+import NetImportExclusionsConnector from './NetImportExclusions/NetImportExclusionsConnector';
class NetImportSettings extends Component {
@@ -85,6 +85,8 @@ class NetImportSettings extends Component {
onChildStateChange={this.onChildStateChange}
/>
+
+
);
diff --git a/frontend/src/Store/Actions/Settings/netImportExclusions.js b/frontend/src/Store/Actions/Settings/netImportExclusions.js
new file mode 100644
index 000000000..01d82f999
--- /dev/null
+++ b/frontend/src/Store/Actions/Settings/netImportExclusions.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.netImportExclusions';
+
+//
+// Actions Types
+
+export const FETCH_NET_IMPORT_EXCLUSIONS = 'settings/netImportExclusions/fetchNetImportExclusions';
+export const SAVE_NET_IMPORT_EXCLUSION = 'settings/netImportExclusions/saveNetImportExclusion';
+export const DELETE_NET_IMPORT_EXCLUSION = 'settings/netImportExclusions/deleteNetImportExclusion';
+export const SET_NET_IMPORT_EXCLUSION_VALUE = 'settings/netImportExclusions/setNetImportExclusionValue';
+
+//
+// Action Creators
+
+export const fetchNetImportExclusions = createThunk(FETCH_NET_IMPORT_EXCLUSIONS);
+export const saveNetImportExclusion = createThunk(SAVE_NET_IMPORT_EXCLUSION);
+export const deleteNetImportExclusion = createThunk(DELETE_NET_IMPORT_EXCLUSION);
+
+export const setNetImportExclusionValue = createAction(SET_NET_IMPORT_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_NET_IMPORT_EXCLUSIONS]: createFetchHandler(section, '/exclusions'),
+ [SAVE_NET_IMPORT_EXCLUSION]: createSaveProviderHandler(section, '/exclusions'),
+ [DELETE_NET_IMPORT_EXCLUSION]: createRemoveItemHandler(section, '/exclusions')
+ },
+
+ //
+ // Reducers
+
+ reducers: {
+ [SET_NET_IMPORT_EXCLUSION_VALUE]: createSetSettingValueReducer(section)
+ }
+
+};
diff --git a/frontend/src/Store/Actions/settingsActions.js b/frontend/src/Store/Actions/settingsActions.js
index 2c8fd4f65..7cb2a3edd 100644
--- a/frontend/src/Store/Actions/settingsActions.js
+++ b/frontend/src/Store/Actions/settingsActions.js
@@ -9,6 +9,7 @@ import general from './Settings/general';
import indexerOptions from './Settings/indexerOptions';
import indexers from './Settings/indexers';
import languages from './Settings/languages';
+import netImportExclusions from './Settings/netImportExclusions';
import netImportOptions from './Settings/netImportOptions';
import netImports from './Settings/netImports';
import mediaManagement from './Settings/mediaManagement';
@@ -30,6 +31,7 @@ export * from './Settings/general';
export * from './Settings/indexerOptions';
export * from './Settings/indexers';
export * from './Settings/languages';
+export * from './Settings/netImportExclusions';
export * from './Settings/netImportOptions';
export * from './Settings/netImports';
export * from './Settings/mediaManagement';
@@ -62,6 +64,7 @@ export const defaultState = {
indexerOptions: indexerOptions.defaultState,
indexers: indexers.defaultState,
languages: languages.defaultState,
+ netImportExclusions: netImportExclusions.defaultState,
netImportOptions: netImportOptions.defaultState,
netImports: netImports.defaultState,
mediaManagement: mediaManagement.defaultState,
@@ -102,6 +105,7 @@ export const actionHandlers = handleThunks({
...indexerOptions.actionHandlers,
...indexers.actionHandlers,
...languages.actionHandlers,
+ ...netImportExclusions.actionHandlers,
...netImportOptions.actionHandlers,
...netImports.actionHandlers,
...mediaManagement.actionHandlers,
@@ -133,6 +137,7 @@ export const reducers = createHandleActions({
...indexerOptions.reducers,
...indexers.reducers,
...languages.reducers,
+ ...netImportExclusions.reducers,
...netImportOptions.reducers,
...netImports.reducers,
...mediaManagement.reducers,
diff --git a/frontend/src/Store/Selectors/createExclusionMovieSelector.js b/frontend/src/Store/Selectors/createExclusionMovieSelector.js
new file mode 100644
index 000000000..12adfd854
--- /dev/null
+++ b/frontend/src/Store/Selectors/createExclusionMovieSelector.js
@@ -0,0 +1,14 @@
+import _ from 'lodash';
+import { createSelector } from 'reselect';
+
+function createExclusionMovieSelector() {
+ return createSelector(
+ (state, { tmdbId }) => tmdbId,
+ (state) => state.settings.netImportExclusions,
+ (tmdbId, netImportExclusions) => {
+ return _.some(netImportExclusions.items, { tmdbId });
+ }
+ );
+}
+
+export default createExclusionMovieSelector;
diff --git a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs
index 706a82d31..ce7ac9dca 100644
--- a/src/NzbDrone.Core/NetImport/NetImportSearchService.cs
+++ b/src/NzbDrone.Core/NetImport/NetImportSearchService.cs
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System;
using System.Linq;
-using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.MetadataSource;
@@ -9,8 +8,6 @@
using NzbDrone.Core.Movies;
using NzbDrone.Core.Configuration;
using NzbDrone.Common.Extensions;
-using NzbDrone.Common.Instrumentation.Extensions;
-using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.NetImport.ImportExclusions;
@@ -134,43 +131,33 @@ public void Execute(NetImportSyncCommand message)
_logger.Info($"Found {listedMovies.Count()} movies on your auto enabled lists not in your library");
}
-
var importExclusions = new List();
+ var moviesToAdd = new List();
- //var downloadedCount = 0;
foreach (var movie in listedMovies)
{
var mapped = _movieSearch.MapMovieToTmdbMovie(movie);
- if (mapped != null && !_exclusionService.IsMovieExcluded(mapped.TmdbId))
+
+ if (mapped == null)
{
- //List decisions;
- mapped.AddOptions = new AddMovieOptions {SearchForMovie = true};
- _movieService.AddMovie(mapped);
-
- //// Search for movie
- //try
- //{
- // decisions = _nzbSearchService.MovieSearch(mapped.Id, false);
- //}
- //catch (Exception ex)
- //{
- // _logger.Error(ex, $"Unable to search in list for movie {mapped.Id}");
- // continue;
- //}
-
- //var processed = _processDownloadDecisions.ProcessDecisions(decisions);
- //downloadedCount += processed.Grabbed.Count;
+ _logger.Debug($"{movie.Title} could not be mapped to a valid tmdb ID, will not be added from list");
+ }
+ else if (_exclusionService.IsMovieExcluded(mapped.TmdbId))
+ {
+ _logger.Debug($"{mapped.Title} ({mapped.TitleSlug}) will not be added since it was found on the exclusions list");
+ }
+ else if (_movieService.MovieExists(mapped))
+ {
+ _logger.Debug($"{mapped.Title} ({mapped.TitleSlug}) will not be added since it exists in DB");
}
else
{
- if (mapped != null)
- {
- _logger.Info($"{mapped.Title} ({mapped.TitleSlug}) will not be added since it was found on the exclusions list");
- }
+ mapped.AddOptions = new AddMovieOptions { SearchForMovie = true };
+ moviesToAdd.Add(mapped);
}
}
- //_logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount);
+ _movieService.AddMovies(moviesToAdd);
}
private void CleanLibrary(List movies)
diff --git a/src/Radarr.Api.V2/Movies/MovieModule.cs b/src/Radarr.Api.V2/Movies/MovieModule.cs
index 3b213ba71..3b850394b 100644
--- a/src/Radarr.Api.V2/Movies/MovieModule.cs
+++ b/src/Radarr.Api.V2/Movies/MovieModule.cs
@@ -117,7 +117,7 @@ private void UpdateMovie(MovieResource moviesResource)
private void DeleteMovie(int id)
{
var addExclusion = false;
- var addExclusionQuery = Request.Query.addExclusion;
+ var addExclusionQuery = Request.Query.addNetImportExclusion;
var deleteFiles = Request.GetBooleanQueryParameter("deleteFiles");
diff --git a/src/Radarr.Api.V2/NetImport/ImportExclusionsModule.cs b/src/Radarr.Api.V2/NetImport/ImportExclusionsModule.cs
index 34d5599fd..c3a42696d 100644
--- a/src/Radarr.Api.V2/NetImport/ImportExclusionsModule.cs
+++ b/src/Radarr.Api.V2/NetImport/ImportExclusionsModule.cs
@@ -1,9 +1,7 @@
using System.Collections.Generic;
using FluentValidation;
-using Radarr.Http.ClientSchema;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.ImportExclusions;
-using NzbDrone.Core.Validation.Paths;
using Radarr.Http;
namespace Radarr.Api.V2.NetImport
@@ -19,6 +17,10 @@ public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusio
CreateResource = AddExclusion;
DeleteResource = RemoveExclusion;
GetResourceById = GetById;
+
+ SharedValidator.RuleFor(c => c.TmdbId).GreaterThan(0);
+ SharedValidator.RuleFor(c => c.MovieTitle).NotEmpty();
+ SharedValidator.RuleFor(c => c.MovieYear).GreaterThan(0);
}
public List GetAll()
@@ -35,6 +37,8 @@ public int AddExclusion(ImportExclusionsResource exclusionResource)
{
var model = exclusionResource.ToModel();
+ // TODO: Add some more validation here and auto pull the title if not provided
+
return _exclusionService.AddExclusion(model).Id;
}