Fixed: List Exclusions, List Processing Tweaks

pull/3606/head
Qstick 5 years ago
parent ed0e69de53
commit 06b1c03053

@ -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);

@ -35,6 +35,11 @@
color: #37bc9b;
}
.exclusionIcon {
margin-left: 10px;
color: #bc3737;
}
.overview {
margin-top: 20px;
}

@ -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 &&
<Icon
className={styles.exclusionIcon}
name={icons.DANGER}
size={36}
title="Movie is on Net Import Exclusion List"
/>
}
</div>
<div>
@ -138,7 +149,7 @@ class AddNewMovieSearchResult extends Component {
year={year}
overview={overview}
images={images}
onModalClose={this.onAddSerisModalClose}
onModalClose={this.onAddMovieModalClose}
/>
</div>
);
@ -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
};

@ -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
};
}

@ -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 {
</div>
}
<FormGroup>
<FormLabel>Add List Exclusion</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="addNetImportExclusion"
value={addNetImportExclusion}
helpText="Prevent movie from being added to Radarr by lists"
kind={kinds.DANGER}
onChange={this.onAddNetImportExclusionChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter>

@ -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);

@ -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 (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
onModalClose={onModalClose}
>
<EditNetImportExclusionModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
EditNetImportExclusionModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditNetImportExclusionModal;

@ -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 (
<EditNetImportExclusionModal
{...this.props}
onModalClose={this.onModalClose}
/>
);
}
}
EditNetImportExclusionModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(EditNetImportExclusionModalConnector);

@ -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;
}

@ -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 (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? 'Edit List Exclusion' : 'Add List Exclusion'}
</ModalHeader>
<ModalBody className={styles.body}>
{
isFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div>Unable to add a new list exclusion, please try again.</div>
}
{
!isFetching && !error &&
<Form
{...otherProps}
>
<FormGroup>
<FormLabel>TMDB Id</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="tmdbId"
helpText="The TMDB Id of the movie to exclude"
{...tmdbId}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Movie Title</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="movieTitle"
helpText="The title of the movie to exclude (can be anything meaningful)"
{...movieTitle}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Movie Year</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="movieYear"
helpText="The year of the movie to exclude"
{...movieYear}
onChange={onInputChange}
/>
</FormGroup>
</Form>
}
</ModalBody>
<ModalFooter>
{
id &&
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteNetImportExclusionPress}
>
Delete
</Button>
}
<Button
onPress={onModalClose}
>
Cancel
</Button>
<SpinnerErrorButton
isSpinning={isSaving}
error={saveError}
onPress={onSavePress}
>
Save
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>
);
}
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;

@ -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 (
<EditNetImportExclusionModalContent
{...this.props}
onSavePress={this.onSavePress}
onInputChange={this.onInputChange}
/>
);
}
}
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);

@ -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;
}

@ -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 (
<div
className={classNames(
styles.netImportExclusion,
)}
>
<div className={styles.tmdbId}>{tmdbId}</div>
<div className={styles.movieTitle}>{movieTitle}</div>
<div className={styles.movieYear}>{movieYear}</div>
<div className={styles.actions}>
<Link
onPress={this.onEditNetImportExclusionPress}
>
<Icon name={icons.EDIT} />
</Link>
</div>
<EditNetImportExclusionModalConnector
id={id}
isOpen={this.state.isEditNetImportExclusionModalOpen}
onModalClose={this.onEditNetImportExclusionModalClose}
onDeleteNetImportExclusionPress={this.onDeleteNetImportExclusionPress}
/>
<ConfirmModal
isOpen={this.state.isDeleteNetImportExclusionModalOpen}
kind={kinds.DANGER}
title="Delete Import List Exclusion"
message="Are you sure you want to delete this import list exclusion?"
confirmLabel="Delete"
onConfirm={this.onConfirmDeleteNetImportExclusion}
onCancel={this.onDeleteNetImportExclusionModalClose}
/>
</div>
);
}
}
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;

@ -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;
}

@ -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 (
<FieldSet legend="List Exclusions">
<PageSectionContent
errorMessage="Unable to load List Exclusions"
{...otherProps}
>
<div className={styles.netImportExclusionsHeader}>
<div className={styles.tmdbId}>TMDB Id</div>
<div className={styles.title}>Title</div>
<div className={styles.movieYear}>Year</div>
</div>
<div>
{
items.map((item, index) => {
return (
<NetImportExclusion
key={item.id}
{...item}
{...otherProps}
index={index}
onConfirmDeleteNetImportExclusion={onConfirmDeleteNetImportExclusion}
/>
);
})
}
</div>
<div className={styles.addNetImportExclusion}>
<Link
className={styles.addButton}
onPress={this.onAddNetImportExclusionPress}
>
<Icon name={icons.ADD} />
</Link>
</div>
<EditNetImportExclusionModalConnector
isOpen={this.state.isAddNetImportExclusionModalOpen}
onModalClose={this.onModalClose}
/>
</PageSectionContent>
</FieldSet>
);
}
}
NetImportExclusions.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNetImportExclusion: PropTypes.func.isRequired
};
export default NetImportExclusions;

@ -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 (
<NetImportExclusions
{...this.state}
{...this.props}
onConfirmDeleteNetImportExclusion={this.onConfirmDeleteNetImportExclusion}
/>
);
}
}
NetImportExclusionsConnector.propTypes = {
fetchNetImportExclusions: PropTypes.func.isRequired,
deleteNetImportExclusion: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(NetImportExclusionsConnector);

@ -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}
/>
<NetImportExclusionsConnector />
</PageContentBodyConnector>
</PageContent>
);

@ -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)
}
};

@ -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,

@ -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;

@ -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.RootFolders;
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 @@ namespace NzbDrone.Core.NetImport
_logger.Info($"Found {listedMovies.Count()} movies on your auto enabled lists not in your library");
}
var importExclusions = new List<string>();
var moviesToAdd = new List<Movie>();
//var downloadedCount = 0;
foreach (var movie in listedMovies)
{
var mapped = _movieSearch.MapMovieToTmdbMovie(movie);
if (mapped != null && !_exclusionService.IsMovieExcluded(mapped.TmdbId))
if (mapped == null)
{
_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))
{
//List<DownloadDecision> 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($"{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<Movie> movies)

@ -117,7 +117,7 @@ namespace Radarr.Api.V2.Movies
private void DeleteMovie(int id)
{
var addExclusion = false;
var addExclusionQuery = Request.Query.addExclusion;
var addExclusionQuery = Request.Query.addNetImportExclusion;
var deleteFiles = Request.GetBooleanQueryParameter("deleteFiles");

@ -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 @@ namespace Radarr.Api.V2.NetImport
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<ImportExclusionsResource> GetAll()
@ -35,6 +37,8 @@ namespace Radarr.Api.V2.NetImport
{
var model = exclusionResource.ToModel();
// TODO: Add some more validation here and auto pull the title if not provided
return _exclusionService.AddExclusion(model).Id;
}

Loading…
Cancel
Save