mirror of
https://github.com/fergalmoran/Readarr.git
synced 2025-12-22 09:29:59 +00:00
Fixed: Manual Import Reprocessing
This commit is contained in:
@@ -53,7 +53,7 @@ class SelectAuthorModalContentConnector extends Component {
|
||||
});
|
||||
});
|
||||
|
||||
this.props.saveInteractiveImportItem({ id: ids });
|
||||
this.props.saveInteractiveImportItem({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ class SelectBookModalContentConnector extends Component {
|
||||
});
|
||||
});
|
||||
|
||||
this.props.saveInteractiveImportItem({ id: ids });
|
||||
this.props.saveInteractiveImportItem({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ class SelectEditionModalContentConnector extends Component {
|
||||
});
|
||||
});
|
||||
|
||||
this.props.saveInteractiveImportItem({ id: ids });
|
||||
this.props.saveInteractiveImportItem({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,9 @@ import SelectBookModal from 'InteractiveImport/Book/SelectBookModal';
|
||||
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
|
||||
import SelectEditionModal from 'InteractiveImport/Edition/SelectEditionModal';
|
||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
@@ -46,6 +48,11 @@ const columns = [
|
||||
label: 'Book',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseGroup',
|
||||
label: 'Release Group',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: 'Quality',
|
||||
@@ -75,14 +82,16 @@ const filterExistingFilesOptions = {
|
||||
};
|
||||
|
||||
const importModeOptions = [
|
||||
{ key: 'move', value: 'Move Files' },
|
||||
{ key: 'copy', value: 'Hardlink/Copy Files' }
|
||||
{ key: 'chooseImportMode', value: translate('ChooseImportMethod'), disabled: true },
|
||||
{ key: 'move', value: translate('MoveFiles') },
|
||||
{ key: 'copy', value: translate('HardlinkCopyFiles') }
|
||||
];
|
||||
|
||||
const SELECT = 'select';
|
||||
const AUTHOR = 'author';
|
||||
const BOOK = 'book';
|
||||
const EDITION = 'edition';
|
||||
const RELEASE_GROUP = 'releaseGroup';
|
||||
const QUALITY = 'quality';
|
||||
|
||||
const replaceExistingFilesOptions = {
|
||||
@@ -280,7 +289,8 @@ class InteractiveImportModalContent extends Component {
|
||||
{ key: SELECT, value: 'Select...', disabled: true },
|
||||
{ key: BOOK, value: 'Select Book' },
|
||||
{ key: EDITION, value: 'Select Edition' },
|
||||
{ key: QUALITY, value: 'Select Quality' }
|
||||
{ key: QUALITY, value: 'Select Quality' },
|
||||
{ key: RELEASE_GROUP, value: 'Select ReleaseGroup' }
|
||||
];
|
||||
|
||||
if (allowAuthorChange) {
|
||||
@@ -483,6 +493,13 @@ class InteractiveImportModalContent extends Component {
|
||||
onModalClose={this.onSelectModalClose}
|
||||
/>
|
||||
|
||||
<SelectReleaseGroupModal
|
||||
isOpen={selectModalOpen === RELEASE_GROUP}
|
||||
ids={selectedIds}
|
||||
releaseGroup=""
|
||||
onModalClose={this.onSelectModalClose}
|
||||
/>
|
||||
|
||||
<SelectQualityModal
|
||||
isOpen={selectModalOpen === QUALITY}
|
||||
ids={selectedIds}
|
||||
|
||||
@@ -120,6 +120,11 @@ class InteractiveImportModalContentConnector extends Component {
|
||||
onImportSelectedPress = (selected, importMode) => {
|
||||
const files = [];
|
||||
|
||||
if (importMode === 'chooseImportMethod') {
|
||||
this.setState({ interactiveImportErrorMessage: 'An import mode must be selected' });
|
||||
return;
|
||||
}
|
||||
|
||||
_.forEach(this.props.items, (item) => {
|
||||
const isSelected = selected.indexOf(item.id) > -1;
|
||||
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading {
|
||||
.reprocessing {
|
||||
composes: loading from '~Components/Loading/LoadingIndicator.css';
|
||||
|
||||
margin-top: 0;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.additionalFile {
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
||||
import BookQuality from 'Book/BookQuality';
|
||||
import FileDetails from 'BookFile/FileDetails';
|
||||
import Icon from 'Components/Icon';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton';
|
||||
@@ -15,6 +14,7 @@ import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||
import SelectAuthorModal from 'InteractiveImport/Author/SelectAuthorModal';
|
||||
import SelectBookModal from 'InteractiveImport/Book/SelectBookModal';
|
||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
|
||||
@@ -32,6 +32,7 @@ class InteractiveImportRow extends Component {
|
||||
isDetailsModalOpen: false,
|
||||
isSelectAuthorModalOpen: false,
|
||||
isSelectBookModalOpen: false,
|
||||
isSelectReleaseGroupModalOpen: false,
|
||||
isSelectQualityModalOpen: false
|
||||
};
|
||||
}
|
||||
@@ -123,6 +124,10 @@ class InteractiveImportRow extends Component {
|
||||
this.setState({ isSelectBookModalOpen: true });
|
||||
};
|
||||
|
||||
onSelectReleaseGroupPress = () => {
|
||||
this.setState({ isSelectReleaseGroupModalOpen: true });
|
||||
};
|
||||
|
||||
onSelectQualityPress = () => {
|
||||
this.setState({ isSelectQualityModalOpen: true });
|
||||
};
|
||||
@@ -137,6 +142,11 @@ class InteractiveImportRow extends Component {
|
||||
this.selectRowAfterChange(changed);
|
||||
};
|
||||
|
||||
onSelectReleaseGroupModalClose = (changed) => {
|
||||
this.setState({ isSelectReleaseGroupModalOpen: false });
|
||||
this.selectRowAfterChange(changed);
|
||||
};
|
||||
|
||||
onSelectQualityModalClose = (changed) => {
|
||||
this.setState({ isSelectQualityModalOpen: false });
|
||||
this.selectRowAfterChange(changed);
|
||||
@@ -153,19 +163,21 @@ class InteractiveImportRow extends Component {
|
||||
author,
|
||||
book,
|
||||
quality,
|
||||
releaseGroup,
|
||||
size,
|
||||
rejections,
|
||||
additionalFile,
|
||||
isSelected,
|
||||
isReprocessing,
|
||||
onSelectedChange,
|
||||
audioTags,
|
||||
isSaving
|
||||
audioTags
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isDetailsModalOpen,
|
||||
isSelectAuthorModalOpen,
|
||||
isSelectBookModalOpen,
|
||||
isSelectReleaseGroupModalOpen,
|
||||
isSelectQualityModalOpen
|
||||
} = this.state;
|
||||
|
||||
@@ -176,7 +188,8 @@ class InteractiveImportRow extends Component {
|
||||
}
|
||||
|
||||
const showAuthorPlaceholder = isSelected && !author;
|
||||
const showBookNumberPlaceholder = isSelected && !!author && !book;
|
||||
const showBookNumberPlaceholder = !isReprocessing && isSelected && !!author && !book;
|
||||
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
||||
const showQualityPlaceholder = isSelected && !quality;
|
||||
|
||||
const pathCellContents = (
|
||||
@@ -237,6 +250,17 @@ class InteractiveImportRow extends Component {
|
||||
}
|
||||
</TableRowCellButton>
|
||||
|
||||
<TableRowCellButton
|
||||
title={translate('ClickToChangeReleaseGroup')}
|
||||
onPress={this.onSelectReleaseGroupPress}
|
||||
>
|
||||
{
|
||||
showReleaseGroupPlaceholder ?
|
||||
<InteractiveImportRowCellPlaceholder /> :
|
||||
releaseGroup
|
||||
}
|
||||
</TableRowCellButton>
|
||||
|
||||
<TableRowCellButton
|
||||
className={styles.quality}
|
||||
title={translate('ClickToChangeQuality')}
|
||||
@@ -262,14 +286,7 @@ class InteractiveImportRow extends Component {
|
||||
|
||||
<TableRowCell>
|
||||
{
|
||||
isSaving &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
{
|
||||
!isSaving && rejections && rejections.length ?
|
||||
rejections && rejections.length ?
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
@@ -292,6 +309,7 @@ class InteractiveImportRow extends Component {
|
||||
</ul>
|
||||
}
|
||||
position={tooltipPositions.LEFT}
|
||||
canFlip={false}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
@@ -322,6 +340,13 @@ class InteractiveImportRow extends Component {
|
||||
onModalClose={this.onSelectBookModalClose}
|
||||
/>
|
||||
|
||||
<SelectReleaseGroupModal
|
||||
isOpen={isSelectReleaseGroupModalOpen}
|
||||
ids={[id]}
|
||||
releaseGroup={releaseGroup ?? ''}
|
||||
onModalClose={this.onSelectReleaseGroupModalClose}
|
||||
/>
|
||||
|
||||
<SelectQualityModal
|
||||
isOpen={isSelectQualityModalOpen}
|
||||
ids={[id]}
|
||||
@@ -343,13 +368,14 @@ InteractiveImportRow.propTypes = {
|
||||
author: PropTypes.object,
|
||||
book: PropTypes.object,
|
||||
foreignEditionId: PropTypes.string,
|
||||
releaseGroup: PropTypes.string,
|
||||
quality: PropTypes.object,
|
||||
size: PropTypes.number.isRequired,
|
||||
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
audioTags: PropTypes.object.isRequired,
|
||||
additionalFile: PropTypes.bool.isRequired,
|
||||
isReprocessing: PropTypes.bool,
|
||||
isSelected: PropTypes.bool,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onValidRowChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import InteractiveImportSelectFolderModalContentConnector from './Folder/InteractiveImportSelectFolderModalContentConnector';
|
||||
import InteractiveImportModalContentConnector from './Interactive/InteractiveImportModalContentConnector';
|
||||
|
||||
@@ -47,6 +48,7 @@ class InteractiveImportModal extends Component {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
size={sizes.EXTRA_LARGE}
|
||||
closeOnBackgroundClick={false}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
@@ -73,7 +75,12 @@ InteractiveImportModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
folder: PropTypes.string,
|
||||
downloadId: PropTypes.string,
|
||||
modalTitle: PropTypes.string.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
InteractiveImportModal.defaultProps = {
|
||||
modalTitle: 'Manual Import'
|
||||
};
|
||||
|
||||
export default InteractiveImportModal;
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import { saveInteractiveImportItem, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||
import getQualities from 'Utilities/Quality/getQualities';
|
||||
import SelectQualityModalContent from './SelectQualityModalContent';
|
||||
@@ -31,7 +31,8 @@ function createMapStateToProps() {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
|
||||
dispatchSaveInteractiveImportItems: saveInteractiveImportItem
|
||||
};
|
||||
|
||||
class SelectQualityModalContentConnector extends Component {
|
||||
@@ -49,6 +50,12 @@ class SelectQualityModalContentConnector extends Component {
|
||||
// Listeners
|
||||
|
||||
onQualitySelect = ({ qualityId, proper, real }) => {
|
||||
const {
|
||||
ids,
|
||||
dispatchUpdateInteractiveImportItems,
|
||||
dispatchSaveInteractiveImportItems
|
||||
} = this.props;
|
||||
|
||||
const quality = _.find(this.props.items,
|
||||
(item) => item.id === qualityId);
|
||||
|
||||
@@ -57,14 +64,16 @@ class SelectQualityModalContentConnector extends Component {
|
||||
real: real ? 1 : 0
|
||||
};
|
||||
|
||||
this.props.dispatchUpdateInteractiveImportItems({
|
||||
ids: this.props.ids,
|
||||
dispatchUpdateInteractiveImportItems({
|
||||
ids,
|
||||
quality: {
|
||||
quality,
|
||||
revision
|
||||
}
|
||||
});
|
||||
|
||||
dispatchSaveInteractiveImportItems({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
};
|
||||
|
||||
@@ -89,6 +98,7 @@ SelectQualityModalContentConnector.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||
dispatchSaveInteractiveImportItems: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import SelectReleaseGroupModalContentConnector from './SelectReleaseGroupModalContentConnector';
|
||||
|
||||
class SelectReleaseGroupModal extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
onModalClose,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<SelectReleaseGroupModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectReleaseGroupModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectReleaseGroupModal;
|
||||
@@ -0,0 +1,7 @@
|
||||
.modalBody {
|
||||
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
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 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, scrollDirections } from 'Helpers/Props';
|
||||
import styles from './SelectReleaseGroupModalContent.css';
|
||||
|
||||
class SelectReleaseGroupModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const {
|
||||
releaseGroup
|
||||
} = props;
|
||||
|
||||
this.state = {
|
||||
releaseGroup
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onReleaseGroupChange = ({ value }) => {
|
||||
this.setState({ releaseGroup: value });
|
||||
};
|
||||
|
||||
onReleaseGroupSelect = () => {
|
||||
this.props.onReleaseGroupSelect(this.state);
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
releaseGroup
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Manual Import - Set Release Group
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody
|
||||
className={styles.modalBody}
|
||||
scrollDirection={scrollDirections.NONE}
|
||||
>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>Release Group</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="releaseGroup"
|
||||
value={releaseGroup}
|
||||
autoFocus={true}
|
||||
onChange={this.onReleaseGroupChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.SUCCESS}
|
||||
onPress={this.onReleaseGroupSelect}
|
||||
>
|
||||
Set Release Group
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectReleaseGroupModalContent.propTypes = {
|
||||
releaseGroup: PropTypes.string.isRequired,
|
||||
onReleaseGroupSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectReleaseGroupModalContent;
|
||||
@@ -0,0 +1,54 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { saveInteractiveImportItem, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import SelectReleaseGroupModalContent from './SelectReleaseGroupModalContent';
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
|
||||
dispatchSaveInteractiveImportItems: saveInteractiveImportItem
|
||||
};
|
||||
|
||||
class SelectReleaseGroupModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onReleaseGroupSelect = ({ releaseGroup }) => {
|
||||
const {
|
||||
ids,
|
||||
dispatchUpdateInteractiveImportItems,
|
||||
dispatchSaveInteractiveImportItems
|
||||
} = this.props;
|
||||
|
||||
dispatchUpdateInteractiveImportItems({
|
||||
ids,
|
||||
releaseGroup
|
||||
});
|
||||
|
||||
dispatchSaveInteractiveImportItems({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SelectReleaseGroupModalContent
|
||||
{...this.props}
|
||||
onReleaseGroupSelect={this.onReleaseGroupSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectReleaseGroupModalContentConnector.propTypes = {
|
||||
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||
dispatchSaveInteractiveImportItems: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, mapDispatchToProps)(SelectReleaseGroupModalContentConnector);
|
||||
@@ -5,10 +5,9 @@ import { sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import { set, update } from './baseActions';
|
||||
import { set, update, updateItem } from './baseActions';
|
||||
import createFetchHandler from './Creators/createFetchHandler';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
|
||||
//
|
||||
@@ -18,6 +17,8 @@ export const section = 'interactiveImport';
|
||||
|
||||
const booksSection = `${section}.books`;
|
||||
const bookFilesSection = `${section}.bookFiles`;
|
||||
let abortCurrentRequest = null;
|
||||
let currentIds = [];
|
||||
|
||||
const MAXIMUM_RECENT_FOLDERS = 10;
|
||||
|
||||
@@ -34,7 +35,7 @@ export const defaultState = {
|
||||
sortKey: 'quality',
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
recentFolders: [],
|
||||
importMode: 'move',
|
||||
importMode: 'chooseImportMode',
|
||||
sortPredicates: {
|
||||
path: function(item, direction) {
|
||||
const path = item.path;
|
||||
@@ -156,7 +157,82 @@ export const actionHandlers = handleThunks({
|
||||
});
|
||||
},
|
||||
|
||||
[SAVE_INTERACTIVE_IMPORT_ITEM]: createSaveProviderHandler(section, '/manualimport', {}, true),
|
||||
[SAVE_INTERACTIVE_IMPORT_ITEM]: function(getState, payload, dispatch) {
|
||||
if (abortCurrentRequest) {
|
||||
abortCurrentRequest();
|
||||
}
|
||||
|
||||
dispatch(batchActions([
|
||||
...currentIds.map((id) => updateItem({
|
||||
section,
|
||||
id,
|
||||
isReprocessing: false,
|
||||
updateOnly: true
|
||||
})),
|
||||
...payload.ids.map((id) => updateItem({
|
||||
section,
|
||||
id,
|
||||
isReprocessing: true,
|
||||
updateOnly: true
|
||||
}))
|
||||
]));
|
||||
|
||||
const items = getState()[section].items;
|
||||
|
||||
const requestPayload = payload.ids.map((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
|
||||
return {
|
||||
id,
|
||||
path: item.path,
|
||||
authorId: item.author ? item.author.id : undefined,
|
||||
bookId: item.book ? item.book.id : undefined,
|
||||
foreignEditionId: item.foreignEditionId ? item.ForeignEditionId : undefined,
|
||||
quality: item.quality,
|
||||
releaseGroup: item.releaseGroup,
|
||||
downloadId: item.downloadId,
|
||||
additionalFile: item.additionalFile,
|
||||
replaceExistingFiles: item.replaceExistingFiles,
|
||||
disableReleaseSwitching: item.disableReleaseSwitching
|
||||
};
|
||||
});
|
||||
|
||||
const { request, abortRequest } = createAjaxRequest({
|
||||
method: 'POST',
|
||||
url: '/manualimport',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(requestPayload)
|
||||
});
|
||||
|
||||
abortCurrentRequest = abortRequest;
|
||||
currentIds = payload.ids;
|
||||
|
||||
request.done((data) => {
|
||||
dispatch(batchActions(
|
||||
data.map((item) => updateItem({
|
||||
section,
|
||||
...item,
|
||||
isReprocessing: false,
|
||||
updateOnly: true
|
||||
}))
|
||||
));
|
||||
});
|
||||
|
||||
request.fail((xhr) => {
|
||||
if (xhr.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(batchActions(
|
||||
payload.ids.map((id) => updateItem({
|
||||
section,
|
||||
id,
|
||||
isReprocessing: false,
|
||||
updateOnly: true
|
||||
}))
|
||||
));
|
||||
});
|
||||
},
|
||||
|
||||
[FETCH_INTERACTIVE_IMPORT_BOOKS]: createFetchHandler(booksSection, '/book'),
|
||||
|
||||
|
||||
@@ -110,11 +110,13 @@
|
||||
"ChmodFolder": "chmod Folder",
|
||||
"ChmodFolderHelpText": "Octal, applied during import/rename to media folders and files (without execute bits)",
|
||||
"ChmodFolderHelpTextWarning": "This only works if the user running Readarr is the owner of the file. It's better to ensure the download client sets the permissions properly.",
|
||||
"ChooseImportMethod": "Choose Import Method",
|
||||
"ChownGroup": "chown Group",
|
||||
"ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.",
|
||||
"ChownGroupHelpTextWarning": "This only works if the user running Readarr is the owner of the file. It's better to ensure the download client uses the same group as Readarr.",
|
||||
"Clear": "Clear",
|
||||
"ClickToChangeQuality": "Click to change quality",
|
||||
"ClickToChangeReleaseGroup": "Click to change release group",
|
||||
"ClientPriority": "Client Priority",
|
||||
"CloneIndexer": "Clone Indexer",
|
||||
"CloneProfile": "Clone Profile",
|
||||
@@ -298,6 +300,7 @@
|
||||
"GrabReleaseMessageText": "Readarr was unable to determine which author and book this release was for. Readarr may be unable to automatically import this release. Do you want to grab '{0}'?",
|
||||
"GrabSelected": "Grab Selected",
|
||||
"Group": "Group",
|
||||
"HardlinkCopyFiles": "Hardlink/Copy Files",
|
||||
"HasMonitoredBooksNoMonitoredBooksForThisAuthor": "No monitored books for this author",
|
||||
"HasPendingChangesNoChanges": "No Changes",
|
||||
"HasPendingChangesSaveChanges": "Save Changes",
|
||||
@@ -457,6 +460,7 @@
|
||||
"MonoVersion": "Mono Version",
|
||||
"MoreInfo": "More Info",
|
||||
"MountCheckMessage": "Mount containing an author path is mounted read-only: ",
|
||||
"MoveFiles": "Move Files",
|
||||
"MusicBrainzAuthorID": "MusicBrainz Author ID",
|
||||
"MusicBrainzBookID": "MusicBrainz Book ID",
|
||||
"MusicbrainzId": "Musicbrainz Id",
|
||||
@@ -487,6 +491,8 @@
|
||||
"NotificationTriggers": "Notification Triggers",
|
||||
"NotMonitored": "Not Monitored",
|
||||
"NoUpdatesAreAvailable": "No updates are available",
|
||||
"OnApplicationUpdate": "On Application Update",
|
||||
"OnApplicationUpdateHelpText": "On Application Update",
|
||||
"OnAuthorDelete": "On Author Delete",
|
||||
"OnAuthorDeleteHelpText": "On Author Delete",
|
||||
"OnBookDelete": "On Book Delete",
|
||||
@@ -497,8 +503,6 @@
|
||||
"OnBookFileDeleteHelpText": "On Book File Delete",
|
||||
"OnBookRetagHelpText": "On Book Retag",
|
||||
"OnBookTagUpdate": "On Book Tag Update",
|
||||
"OnApplicationUpdate": "On Application Update",
|
||||
"OnApplicationUpdateHelpText": "On Application Update",
|
||||
"OnDownloadFailure": "On Download Failure",
|
||||
"OnDownloadFailureHelpText": "On Download Failure",
|
||||
"OnGrab": "On Grab",
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Manual
|
||||
public Book Book { get; set; }
|
||||
public Edition Edition { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
public ParsedTrackInfo Tags { get; set; }
|
||||
|
||||
@@ -19,6 +19,7 @@ using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.BookImport.Manual
|
||||
@@ -236,7 +237,18 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Manual
|
||||
item.Edition = decision.Item.Edition;
|
||||
}
|
||||
|
||||
if (item.Quality?.Quality == Quality.Unknown)
|
||||
{
|
||||
item.Quality = decision.Item.Quality;
|
||||
}
|
||||
|
||||
if (item.ReleaseGroup.IsNullOrWhiteSpace())
|
||||
{
|
||||
item.ReleaseGroup = decision.Item.ReleaseGroup;
|
||||
}
|
||||
|
||||
item.Rejections = decision.Rejections;
|
||||
item.Size = decision.Item.Size;
|
||||
|
||||
result.Add(item);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace Readarr.Api.V1.ManualImport
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public IActionResult UpdateItems(List<ManualImportResource> resource)
|
||||
[HttpPost]
|
||||
public IActionResult UpdateItems(List<ManualImportUpdateResource> resource)
|
||||
{
|
||||
return Accepted(UpdateImportItems(resource));
|
||||
}
|
||||
@@ -65,7 +65,7 @@ namespace Readarr.Api.V1.ManualImport
|
||||
return item;
|
||||
}
|
||||
|
||||
private List<ManualImportResource> UpdateImportItems(List<ManualImportResource> resources)
|
||||
private List<ManualImportResource> UpdateImportItems(List<ManualImportUpdateResource> resources)
|
||||
{
|
||||
var items = new List<ManualImportItem>();
|
||||
foreach (var resource in resources)
|
||||
@@ -75,11 +75,11 @@ namespace Readarr.Api.V1.ManualImport
|
||||
Id = resource.Id,
|
||||
Path = resource.Path,
|
||||
Name = resource.Name,
|
||||
Size = resource.Size,
|
||||
Author = resource.Author == null ? null : _authorService.GetAuthor(resource.Author.Id),
|
||||
Book = resource.Book == null ? null : _bookService.GetBook(resource.Book.Id),
|
||||
Author = resource.AuthorId.HasValue ? _authorService.GetAuthor(resource.AuthorId.Value) : null,
|
||||
Book = resource.BookId.HasValue ? _bookService.GetBook(resource.BookId.Value) : null,
|
||||
Edition = resource.ForeignEditionId == null ? null : _editionService.GetEditionByForeignEditionId(resource.ForeignEditionId),
|
||||
Quality = resource.Quality,
|
||||
ReleaseGroup = resource.ReleaseGroup,
|
||||
DownloadId = resource.DownloadId,
|
||||
AdditionalFile = resource.AdditionalFile,
|
||||
ReplaceExistingFiles = resource.ReplaceExistingFiles,
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Readarr.Api.V1.ManualImport
|
||||
public BookResource Book { get; set; }
|
||||
public string ForeignEditionId { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public int QualityWeight { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
@@ -47,6 +48,7 @@ namespace Readarr.Api.V1.ManualImport
|
||||
Book = model.Book.ToResource(),
|
||||
ForeignEditionId = model.Edition?.ForeignEditionId ?? model.Book?.Editions.Value.Single(x => x.Monitored).ForeignEditionId,
|
||||
Quality = model.Quality,
|
||||
ReleaseGroup = model.ReleaseGroup,
|
||||
|
||||
//QualityWeight
|
||||
DownloadId = model.DownloadId,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using Readarr.Http.REST;
|
||||
|
||||
namespace Readarr.Api.V1.ManualImport
|
||||
{
|
||||
public class ManualImportUpdateResource : RestResource
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int? AuthorId { get; set; }
|
||||
public int? BookId { get; set; }
|
||||
public string ForeignEditionId { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public bool AdditionalFile { get; set; }
|
||||
public bool ReplaceExistingFiles { get; set; }
|
||||
public bool DisableReleaseSwitching { get; set; }
|
||||
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user