diff --git a/frontend/src/MovieFile/Edit/FileEditModalContentConnector.js b/frontend/src/MovieFile/Edit/FileEditModalContentConnector.js
index 902f5801d..e3d2a493f 100644
--- a/frontend/src/MovieFile/Edit/FileEditModalContentConnector.js
+++ b/frontend/src/MovieFile/Edit/FileEditModalContentConnector.js
@@ -86,18 +86,18 @@ class FileEditModalContentConnector extends Component {
real: real ? 1 : 0
};
- const movieFileIds = [this.props.movieFileId];
-
this.props.dispatchUpdateMovieFiles({
- movieFileIds,
- languages,
- indexerFlags,
- edition,
- releaseGroup,
- quality: {
- quality,
- revision
- }
+ files: [{
+ id: this.props.movieFileId,
+ languages,
+ indexerFlags,
+ edition,
+ releaseGroup,
+ quality: {
+ quality,
+ revision
+ }
+ }]
});
this.props.onModalClose(true);
diff --git a/frontend/src/MovieFile/Language/SelectLanguageModal.js b/frontend/src/MovieFile/Language/SelectLanguageModal.js
deleted file mode 100644
index 938d26a6d..000000000
--- a/frontend/src/MovieFile/Language/SelectLanguageModal.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import Modal from 'Components/Modal/Modal';
-import SelectLanguageModalContentConnector from './SelectLanguageModalContentConnector';
-
-class SelectLanguageModal extends Component {
-
- //
- // Render
-
- render() {
- const {
- isOpen,
- onModalClose,
- ...otherProps
- } = this.props;
-
- return (
-
-
-
- );
- }
-}
-
-SelectLanguageModal.propTypes = {
- isOpen: PropTypes.bool.isRequired,
- onModalClose: PropTypes.func.isRequired
-};
-
-export default SelectLanguageModal;
diff --git a/frontend/src/MovieFile/Language/SelectLanguageModalContentConnector.js b/frontend/src/MovieFile/Language/SelectLanguageModalContentConnector.js
deleted file mode 100644
index a9d3094eb..000000000
--- a/frontend/src/MovieFile/Language/SelectLanguageModalContentConnector.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import _ from 'lodash';
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import SelectLanguageModalContent from 'InteractiveImport/Language/SelectLanguageModalContent';
-import { updateMovieFiles } from 'Store/Actions/movieFileActions';
-import { fetchLanguages } from 'Store/Actions/settingsActions';
-
-function createMapStateToProps() {
- return createSelector(
- (state) => state.settings.languages,
- (languages) => {
- const {
- isFetching,
- isPopulated,
- error,
- items
- } = languages;
-
- const filterItems = ['Any', 'Original'];
- const filteredLanguages = items.filter((lang) => !filterItems.includes(lang.name));
-
- return {
- isFetching,
- isPopulated,
- error,
- items: filteredLanguages
- };
- }
- );
-}
-
-const mapDispatchToProps = {
- dispatchFetchLanguages: fetchLanguages,
- dispatchupdateMovieFiles: updateMovieFiles
-};
-
-class SelectLanguageModalContentConnector extends Component {
-
- //
- // Lifecycle
-
- componentDidMount = () => {
- if (!this.props.isPopulated) {
- this.props.dispatchFetchLanguages();
- }
- };
-
- //
- // Listeners
-
- onLanguageSelect = ({ languageIds }) => {
- const languages = [];
-
- languageIds.forEach((languageId) => {
- const language = _.find(this.props.items,
- (item) => item.id === parseInt(languageId));
-
- if (language !== undefined) {
- languages.push(language);
- }
- });
-
- this.props.dispatchupdateMovieFiles({
- movieFileIds: this.props.ids,
- languages
- });
-
- this.props.onModalClose(true);
- };
-
- //
- // Render
-
- render() {
- return (
-
- );
- }
-}
-
-SelectLanguageModalContentConnector.propTypes = {
- ids: PropTypes.arrayOf(PropTypes.number).isRequired,
- isFetching: PropTypes.bool.isRequired,
- isPopulated: PropTypes.bool.isRequired,
- error: PropTypes.object,
- items: PropTypes.arrayOf(PropTypes.object).isRequired,
- dispatchFetchLanguages: PropTypes.func.isRequired,
- dispatchupdateMovieFiles: PropTypes.func.isRequired,
- onModalClose: PropTypes.func.isRequired
-};
-
-export default connect(createMapStateToProps, mapDispatchToProps)(SelectLanguageModalContentConnector);
diff --git a/frontend/src/MovieFile/Quality/SelectQualityModal.js b/frontend/src/MovieFile/Quality/SelectQualityModal.js
deleted file mode 100644
index d3e31d2dd..000000000
--- a/frontend/src/MovieFile/Quality/SelectQualityModal.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import Modal from 'Components/Modal/Modal';
-import SelectQualityModalContentConnector from './SelectQualityModalContentConnector';
-
-class SelectQualityModal extends Component {
-
- //
- // Render
-
- render() {
- const {
- isOpen,
- onModalClose,
- ...otherProps
- } = this.props;
-
- return (
-
-
-
- );
- }
-}
-
-SelectQualityModal.propTypes = {
- isOpen: PropTypes.bool.isRequired,
- onModalClose: PropTypes.func.isRequired
-};
-
-export default SelectQualityModal;
diff --git a/frontend/src/MovieFile/Quality/SelectQualityModalContent.js b/frontend/src/MovieFile/Quality/SelectQualityModalContent.js
deleted file mode 100644
index 268fa6e1a..000000000
--- a/frontend/src/MovieFile/Quality/SelectQualityModalContent.js
+++ /dev/null
@@ -1,170 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import Alert from 'Components/Alert';
-import Form from 'Components/Form/Form';
-import FormGroup from 'Components/Form/FormGroup';
-import FormInputGroup from 'Components/Form/FormInputGroup';
-import FormLabel from 'Components/Form/FormLabel';
-import Button from 'Components/Link/Button';
-import LoadingIndicator from 'Components/Loading/LoadingIndicator';
-import ModalBody from 'Components/Modal/ModalBody';
-import ModalContent from 'Components/Modal/ModalContent';
-import ModalFooter from 'Components/Modal/ModalFooter';
-import ModalHeader from 'Components/Modal/ModalHeader';
-import { inputTypes, kinds } from 'Helpers/Props';
-import translate from 'Utilities/String/translate';
-
-class SelectQualityModalContent extends Component {
-
- //
- // Lifecycle
-
- constructor(props, context) {
- super(props, context);
-
- const {
- qualityId,
- proper,
- real
- } = props;
-
- this.state = {
- qualityId,
- proper,
- real
- };
- }
-
- //
- // Listeners
-
- onQualityChange = ({ value }) => {
- this.setState({ qualityId: parseInt(value) });
- };
-
- onProperChange = ({ value }) => {
- this.setState({ proper: value });
- };
-
- onRealChange = ({ value }) => {
- this.setState({ real: value });
- };
-
- onQualitySelect = () => {
- this.props.onQualitySelect(this.state);
- };
-
- //
- // Render
-
- render() {
- const {
- isFetching,
- isPopulated,
- error,
- items,
- onModalClose
- } = this.props;
-
- const {
- qualityId,
- proper,
- real
- } = this.state;
-
- const qualityOptions = items.map(({ id, name }) => {
- return {
- key: id,
- value: name
- };
- });
-
- return (
-
-
- {translate('ManualImportSelectQuality')}
-
-
-
- {
- isFetching &&
-
- }
-
- {
- !isFetching && !!error &&
-
- {translate('QualitiesLoadError')}
-
- }
-
- {
- isPopulated && !error &&
-
- }
-
-
-
-
-
-
-
-
- );
- }
-}
-
-SelectQualityModalContent.propTypes = {
- qualityId: PropTypes.number.isRequired,
- proper: PropTypes.bool.isRequired,
- real: PropTypes.bool.isRequired,
- isFetching: PropTypes.bool.isRequired,
- isPopulated: PropTypes.bool.isRequired,
- error: PropTypes.object,
- items: PropTypes.arrayOf(PropTypes.object).isRequired,
- onQualitySelect: PropTypes.func.isRequired,
- onModalClose: PropTypes.func.isRequired
-};
-
-export default SelectQualityModalContent;
diff --git a/frontend/src/MovieFile/Quality/SelectQualityModalContentConnector.js b/frontend/src/MovieFile/Quality/SelectQualityModalContentConnector.js
deleted file mode 100644
index 70fb1733f..000000000
--- a/frontend/src/MovieFile/Quality/SelectQualityModalContentConnector.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import _ from 'lodash';
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import { updateMovieFiles } from 'Store/Actions/movieFileActions';
-import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
-import getQualities from 'Utilities/Quality/getQualities';
-import SelectQualityModalContent from './SelectQualityModalContent';
-
-function createMapStateToProps() {
- return createSelector(
- (state) => state.settings.qualityProfiles,
- (qualityProfiles) => {
- const {
- isSchemaFetching: isFetching,
- isSchemaPopulated: isPopulated,
- schemaError: error,
- schema
- } = qualityProfiles;
-
- return {
- isFetching,
- isPopulated,
- error,
- items: getQualities(schema.items)
- };
- }
- );
-}
-
-const mapDispatchToProps = {
- dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
- dispatchupdateMovieFiles: updateMovieFiles
-};
-
-class SelectQualityModalContentConnector extends Component {
-
- //
- // Lifecycle
-
- componentDidMount = () => {
- if (!this.props.isPopulated) {
- this.props.dispatchFetchQualityProfileSchema();
- }
- };
-
- //
- // Listeners
-
- onQualitySelect = ({ qualityId, proper, real }) => {
- const quality = _.find(this.props.items,
- (item) => item.id === qualityId);
-
- const revision = {
- version: proper ? 2 : 1,
- real: real ? 1 : 0
- };
-
- const movieFileIds = this.props.ids;
-
- this.props.dispatchupdateMovieFiles({
- movieFileIds,
- quality: {
- quality,
- revision
- }
- });
-
- this.props.onModalClose(true);
- };
-
- //
- // Render
-
- render() {
- return (
-
- );
- }
-}
-
-SelectQualityModalContentConnector.propTypes = {
- ids: PropTypes.arrayOf(PropTypes.number).isRequired,
- isFetching: PropTypes.bool.isRequired,
- isPopulated: PropTypes.bool.isRequired,
- error: PropTypes.object,
- items: PropTypes.arrayOf(PropTypes.object).isRequired,
- dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
- dispatchupdateMovieFiles: PropTypes.func.isRequired,
- onModalClose: PropTypes.func.isRequired
-};
-
-export default connect(createMapStateToProps, mapDispatchToProps)(SelectQualityModalContentConnector);
diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js
index 264f7f089..9539e659b 100644
--- a/frontend/src/Store/Actions/interactiveImportActions.js
+++ b/frontend/src/Store/Actions/interactiveImportActions.js
@@ -29,6 +29,7 @@ export const defaultState = {
isReprocessing: false,
error: null,
items: [],
+ originalItems: [],
sortKey: 'relativePath',
sortDirection: sortDirections.ASCENDING,
favoriteFolders: [],
@@ -119,7 +120,8 @@ export const actionHandlers = handleThunks({
section,
isFetching: false,
isPopulated: true,
- error: null
+ error: null,
+ originalItems: data
})
]));
});
@@ -228,13 +230,13 @@ export const reducers = createHandleActions({
},
[UPDATE_INTERACTIVE_IMPORT_ITEMS]: (state, { payload }) => {
- const ids = payload.ids;
+ const { ids, ...otherPayload } = payload;
const newState = Object.assign({}, state);
const items = [...newState.items];
ids.forEach((id) => {
const index = items.findIndex((item) => item.id === id);
- const item = Object.assign({}, items[index], payload);
+ const item = Object.assign({}, items[index], otherPayload);
items.splice(index, 1, item);
});
diff --git a/frontend/src/Store/Actions/movieFileActions.js b/frontend/src/Store/Actions/movieFileActions.js
index 86047a47f..3567da0d9 100644
--- a/frontend/src/Store/Actions/movieFileActions.js
+++ b/frontend/src/Store/Actions/movieFileActions.js
@@ -248,44 +248,14 @@ export const actionHandlers = handleThunks({
},
[UPDATE_MOVIE_FILES]: function(getState, payload, dispatch) {
-
- const {
- movieFileIds,
- languages,
- indexerFlags,
- quality,
- edition,
- releaseGroup
- } = payload;
+ const { files } = payload;
dispatch(set({ section, isSaving: true }));
- const requestData = {
- movieFileIds
- };
-
- if (languages) {
- requestData.languages = languages;
- }
-
- if (indexerFlags !== undefined) {
- requestData.indexerFlags = indexerFlags;
- }
-
- if (quality) {
- requestData.quality = quality;
- }
-
- if (releaseGroup !== undefined) {
- requestData.releaseGroup = releaseGroup;
- }
-
- if (edition !== undefined) {
- requestData.edition = edition;
- }
+ const requestData = files;
const promise = createAjaxRequest({
- url: '/movieFile/editor',
+ url: '/movieFile/bulk',
method: 'PUT',
dataType: 'json',
data: JSON.stringify(requestData)
@@ -293,36 +263,25 @@ export const actionHandlers = handleThunks({
promise.done((data) => {
dispatch(batchActions([
- ...movieFileIds.map((id) => {
- const movieFile = data.find((file) => file.id === id);
+ ...files.map((file) => {
+ const id = file.id;
+ const props = {};
+ const movieFile = data.find((f) => f.id === id);
- const props = {
- customFormats: movieFile.customFormats,
- customFormatScore: movieFile.customFormatScore,
- qualityCutoffNotMet: movieFile.qualityCutoffNotMet
- };
+ props.qualityCutoffNotMet = movieFile.qualityCutoffNotMet;
+ props.customFormats = movieFile.customFormats;
+ props.customFormatScore = movieFile.customFormatScore;
+ props.edition = movieFile.edition;
+ props.languages = file.languages;
+ props.quality = file.quality;
+ props.releaseGroup = file.releaseGroup;
+ props.indexerFlags = file.indexerFlags;
- if (languages) {
- props.languages = languages;
- }
-
- if (indexerFlags !== undefined) {
- props.indexerFlags = indexerFlags;
- }
-
- if (quality) {
- props.quality = quality;
- }
-
- if (edition !== undefined) {
- props.edition = edition;
- }
-
- if (releaseGroup !== undefined) {
- props.releaseGroup = releaseGroup;
- }
-
- return updateItem({ section, id, ...props });
+ return updateItem({
+ section,
+ id,
+ ...props
+ });
}),
set({
diff --git a/src/Radarr.Api.V3/MovieFiles/MovieFileController.cs b/src/Radarr.Api.V3/MovieFiles/MovieFileController.cs
index 77e8e5d73..bae311e52 100644
--- a/src/Radarr.Api.V3/MovieFiles/MovieFileController.cs
+++ b/src/Radarr.Api.V3/MovieFiles/MovieFileController.cs
@@ -183,6 +183,55 @@ namespace Radarr.Api.V3.MovieFiles
return new { };
}
+ [HttpPut("bulk")]
+ [Consumes("application/json")]
+ public object SetPropertiesBulk([FromBody] List resources)
+ {
+ var movieFiles = _mediaFileService.GetMovies(resources.Select(r => r.Id));
+
+ foreach (var movieFile in movieFiles)
+ {
+ var resourceMovieFile = resources.Single(r => r.Id == movieFile.Id);
+
+ if (resourceMovieFile.Languages != null)
+ {
+ // Don't allow user to set files with 'Any' or 'Original' language
+ movieFile.Languages = resourceMovieFile.Languages.Where(l => l != null && l != Language.Any && l != Language.Original).ToList();
+ }
+
+ if (resourceMovieFile.Quality != null)
+ {
+ movieFile.Quality = resourceMovieFile.Quality;
+ }
+
+ if (resourceMovieFile.SceneName != null && SceneChecker.IsSceneTitle(resourceMovieFile.SceneName))
+ {
+ movieFile.SceneName = resourceMovieFile.SceneName;
+ }
+
+ if (resourceMovieFile.Edition != null)
+ {
+ movieFile.Edition = resourceMovieFile.Edition;
+ }
+
+ if (resourceMovieFile.ReleaseGroup != null)
+ {
+ movieFile.ReleaseGroup = resourceMovieFile.ReleaseGroup;
+ }
+
+ if (resourceMovieFile.IndexerFlags.HasValue)
+ {
+ movieFile.IndexerFlags = (IndexerFlags)resourceMovieFile.IndexerFlags;
+ }
+ }
+
+ _mediaFileService.Update(movieFiles);
+
+ var movie = _movieService.GetMovie(movieFiles.First().MovieId);
+
+ return Accepted(movieFiles.ConvertAll(f => f.ToResource(movie, _upgradableSpecification, _formatCalculator)));
+ }
+
[NonAction]
public void Handle(MovieFileAddedEvent message)
{