mirror of
https://github.com/Radarr/Radarr.git
synced 2025-04-24 14:37:07 -04:00
Fixed: Updating movie files via Manage Files
This commit is contained in:
parent
1d855aed00
commit
35f1a61bf8
9 changed files with 85 additions and 513 deletions
|
@ -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);
|
||||
|
|
|
@ -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 (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<SelectLanguageModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectLanguageModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectLanguageModal;
|
|
@ -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 (
|
||||
<SelectLanguageModalContent
|
||||
{...this.props}
|
||||
onLanguageSelect={this.onLanguageSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
|
@ -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 (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<SelectQualityModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectQualityModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectQualityModal;
|
|
@ -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 (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('ManualImportSelectQuality')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('QualitiesLoadError')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !error &&
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Quality')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="quality"
|
||||
value={qualityId}
|
||||
values={qualityOptions}
|
||||
onChange={this.onQualityChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Proper')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="proper"
|
||||
value={proper}
|
||||
onChange={this.onProperChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Real')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="real"
|
||||
value={real}
|
||||
onChange={this.onRealChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.SUCCESS}
|
||||
onPress={this.onQualitySelect}
|
||||
>
|
||||
{translate('SelectQuality')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
|
@ -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 (
|
||||
<SelectQualityModalContent
|
||||
{...this.props}
|
||||
onQualitySelect={this.onQualitySelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -183,6 +183,55 @@ namespace Radarr.Api.V3.MovieFiles
|
|||
return new { };
|
||||
}
|
||||
|
||||
[HttpPut("bulk")]
|
||||
[Consumes("application/json")]
|
||||
public object SetPropertiesBulk([FromBody] List<MovieFileResource> 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)
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue