Fixed: Manual Import Reprocessing

pull/3241/head
Qstick 1 year ago
parent 9e1009bc85
commit 84a758064c

@ -70,7 +70,7 @@ class SelectAlbumModalContentConnector extends Component {
}); });
}); });
this.props.saveInteractiveImportItem({ id: ids }); this.props.saveInteractiveImportItem({ ids });
this.props.onModalClose(true); this.props.onModalClose(true);
}; };

@ -37,7 +37,7 @@ class SelectAlbumReleaseModalContentConnector extends Component {
}); });
}); });
this.props.saveInteractiveImportItem({ id: ids }); this.props.saveInteractiveImportItem({ ids });
this.props.onModalClose(true); this.props.onModalClose(true);
}; };

@ -54,7 +54,7 @@ class SelectArtistModalContentConnector extends Component {
}); });
}); });
this.props.saveInteractiveImportItem({ id: ids }); this.props.saveInteractiveImportItem({ ids });
this.props.onModalClose(true); this.props.onModalClose(true);
}; };

@ -21,6 +21,7 @@ import SelectAlbumReleaseModal from 'InteractiveImport/AlbumRelease/SelectAlbumR
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal'; import ConfirmImportModal from 'InteractiveImport/Confirmation/ConfirmImportModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds'; import getSelectedIds from 'Utilities/Table/getSelectedIds';
@ -52,6 +53,11 @@ const columns = [
label: translate('Tracks'), label: translate('Tracks'),
isVisible: true isVisible: true
}, },
{
name: 'releaseGroup',
label: translate('ReleaseGroup'),
isVisible: true
},
{ {
name: 'quality', name: 'quality',
label: translate('Quality'), label: translate('Quality'),
@ -81,6 +87,7 @@ const filterExistingFilesOptions = {
}; };
const importModeOptions = [ const importModeOptions = [
{ key: 'chooseImportMode', value: translate('ChooseImportMethod'), disabled: true },
{ key: 'move', value: translate('MoveFiles') }, { key: 'move', value: translate('MoveFiles') },
{ key: 'copy', value: translate('HardlinkCopyFiles') } { key: 'copy', value: translate('HardlinkCopyFiles') }
]; ];
@ -89,6 +96,7 @@ const SELECT = 'select';
const ARTIST = 'artist'; const ARTIST = 'artist';
const ALBUM = 'album'; const ALBUM = 'album';
const ALBUM_RELEASE = 'albumRelease'; const ALBUM_RELEASE = 'albumRelease';
const RELEASE_GROUP = 'releaseGroup';
const QUALITY = 'quality'; const QUALITY = 'quality';
const replaceExistingFilesOptions = { const replaceExistingFilesOptions = {
@ -292,7 +300,8 @@ class InteractiveImportModalContent extends Component {
{ key: SELECT, value: translate('Select...'), disabled: true }, { key: SELECT, value: translate('Select...'), disabled: true },
{ key: ALBUM, value: translate('SelectAlbum') }, { key: ALBUM, value: translate('SelectAlbum') },
{ key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') }, { key: ALBUM_RELEASE, value: translate('SelectAlbumRelease') },
{ key: QUALITY, value: translate('SelectQuality') } { key: QUALITY, value: translate('SelectQuality') },
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
]; ];
if (allowArtistChange) { if (allowArtistChange) {
@ -513,6 +522,13 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectModalClose} onModalClose={this.onSelectModalClose}
/> />
<SelectReleaseGroupModal
isOpen={selectModalOpen === RELEASE_GROUP}
ids={selectedIds}
releaseGroup=""
onModalClose={this.onSelectModalClose}
/>
<SelectQualityModal <SelectQualityModal
isOpen={selectModalOpen === QUALITY} isOpen={selectModalOpen === QUALITY}
ids={selectedIds} ids={selectedIds}

@ -120,6 +120,11 @@ class InteractiveImportModalContentConnector extends Component {
onImportSelectedPress = (selected, importMode) => { onImportSelectedPress = (selected, importMode) => {
const files = []; const files = [];
if (importMode === 'chooseImportMethod') {
this.setState({ interactiveImportErrorMessage: 'An import mode must be selected' });
return;
}
_.forEach(this.props.items, (item) => { _.forEach(this.props.items, (item) => {
const isSelected = selected.indexOf(item.id) > -1; const isSelected = selected.indexOf(item.id) > -1;

@ -16,10 +16,11 @@
cursor: pointer; cursor: pointer;
} }
.loading { .reprocessing {
composes: loading from '~Components/Loading/LoadingIndicator.css'; composes: loading from '~Components/Loading/LoadingIndicator.css';
margin-top: 0; margin-top: 0;
text-align: start;
} }
.additionalFile { .additionalFile {

@ -13,6 +13,7 @@ import { icons, kinds, sortDirections, tooltipPositions } from 'Helpers/Props';
import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal'; import SelectAlbumModal from 'InteractiveImport/Album/SelectAlbumModal';
import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal'; import SelectArtistModal from 'InteractiveImport/Artist/SelectArtistModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal'; import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal'; import SelectTrackModal from 'InteractiveImport/Track/SelectTrackModal';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
@ -32,6 +33,7 @@ class InteractiveImportRow extends Component {
isSelectArtistModalOpen: false, isSelectArtistModalOpen: false,
isSelectAlbumModalOpen: false, isSelectAlbumModalOpen: false,
isSelectTrackModalOpen: false, isSelectTrackModalOpen: false,
isSelectReleaseGroupModalOpen: false,
isSelectQualityModalOpen: false isSelectQualityModalOpen: false
}; };
} }
@ -119,6 +121,10 @@ class InteractiveImportRow extends Component {
this.setState({ isSelectTrackModalOpen: true }); this.setState({ isSelectTrackModalOpen: true });
}; };
onSelectReleaseGroupPress = () => {
this.setState({ isSelectReleaseGroupModalOpen: true });
};
onSelectQualityPress = () => { onSelectQualityPress = () => {
this.setState({ isSelectQualityModalOpen: true }); this.setState({ isSelectQualityModalOpen: true });
}; };
@ -138,6 +144,11 @@ class InteractiveImportRow extends Component {
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
}; };
onSelectReleaseGroupModalClose = (changed) => {
this.setState({ isSelectReleaseGroupModalOpen: false });
this.selectRowAfterChange(changed);
};
onSelectQualityModalClose = (changed) => { onSelectQualityModalClose = (changed) => {
this.setState({ isSelectQualityModalOpen: false }); this.setState({ isSelectQualityModalOpen: false });
this.selectRowAfterChange(changed); this.selectRowAfterChange(changed);
@ -156,12 +167,13 @@ class InteractiveImportRow extends Component {
albumReleaseId, albumReleaseId,
tracks, tracks,
quality, quality,
releaseGroup,
size, size,
rejections, rejections,
isReprocessing,
audioTags, audioTags,
additionalFile, additionalFile,
isSelected, isSelected,
isSaving,
onSelectedChange onSelectedChange
} = this.props; } = this.props;
@ -169,6 +181,7 @@ class InteractiveImportRow extends Component {
isSelectArtistModalOpen, isSelectArtistModalOpen,
isSelectAlbumModalOpen, isSelectAlbumModalOpen,
isSelectTrackModalOpen, isSelectTrackModalOpen,
isSelectReleaseGroupModalOpen,
isSelectQualityModalOpen isSelectQualityModalOpen
} = this.state; } = this.state;
@ -185,8 +198,9 @@ class InteractiveImportRow extends Component {
const showArtistPlaceholder = isSelected && !artist; const showArtistPlaceholder = isSelected && !artist;
const showAlbumNumberPlaceholder = isSelected && !!artist && !album; const showAlbumNumberPlaceholder = isSelected && !!artist && !album;
const showTrackNumbersPlaceholder = !isSaving && isSelected && !!album && !tracks.length; const showTrackNumbersPlaceholder = !isReprocessing && isSelected && !!album && !tracks.length;
const showTrackNumbersLoading = isSaving && !tracks.length; const showTrackNumbersLoading = isReprocessing && !tracks.length;
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
const showQualityPlaceholder = isSelected && !quality; const showQualityPlaceholder = isSelected && !quality;
const pathCellContents = ( const pathCellContents = (
@ -253,6 +267,17 @@ class InteractiveImportRow extends Component {
} }
</TableRowCellButton> </TableRowCellButton>
<TableRowCellButton
title={translate('ClickToChangeReleaseGroup')}
onPress={this.onSelectReleaseGroupPress}
>
{
showReleaseGroupPlaceholder ?
<InteractiveImportRowCellPlaceholder /> :
releaseGroup
}
</TableRowCellButton>
<TableRowCellButton <TableRowCellButton
className={styles.quality} className={styles.quality}
title={translate('ClickToChangeQuality')} title={translate('ClickToChangeQuality')}
@ -301,6 +326,7 @@ class InteractiveImportRow extends Component {
</ul> </ul>
} }
position={tooltipPositions.LEFT} position={tooltipPositions.LEFT}
canFlip={false}
/> : /> :
null null
} }
@ -333,6 +359,13 @@ class InteractiveImportRow extends Component {
onModalClose={this.onSelectTrackModalClose} onModalClose={this.onSelectTrackModalClose}
/> />
<SelectReleaseGroupModal
isOpen={isSelectReleaseGroupModalOpen}
ids={[id]}
releaseGroup={releaseGroup ?? ''}
onModalClose={this.onSelectReleaseGroupModalClose}
/>
<SelectQualityModal <SelectQualityModal
isOpen={isSelectQualityModalOpen} isOpen={isSelectQualityModalOpen}
ids={[id]} ids={[id]}
@ -355,13 +388,14 @@ InteractiveImportRow.propTypes = {
album: PropTypes.object, album: PropTypes.object,
albumReleaseId: PropTypes.number, albumReleaseId: PropTypes.number,
tracks: PropTypes.arrayOf(PropTypes.object).isRequired, tracks: PropTypes.arrayOf(PropTypes.object).isRequired,
releaseGroup: PropTypes.string,
quality: PropTypes.object, quality: PropTypes.object,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
rejections: PropTypes.arrayOf(PropTypes.object).isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
audioTags: PropTypes.object.isRequired, audioTags: PropTypes.object.isRequired,
additionalFile: PropTypes.bool.isRequired, additionalFile: PropTypes.bool.isRequired,
isReprocessing: PropTypes.bool,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
isSaving: PropTypes.bool.isRequired,
onSelectedChange: PropTypes.func.isRequired, onSelectedChange: PropTypes.func.isRequired,
onValidRowChange: PropTypes.func.isRequired onValidRowChange: PropTypes.func.isRequired
}; };

@ -1,6 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import InteractiveImportSelectFolderModalContentConnector from './Folder/InteractiveImportSelectFolderModalContentConnector'; import InteractiveImportSelectFolderModalContentConnector from './Folder/InteractiveImportSelectFolderModalContentConnector';
import InteractiveImportModalContentConnector from './Interactive/InteractiveImportModalContentConnector'; import InteractiveImportModalContentConnector from './Interactive/InteractiveImportModalContentConnector';
@ -47,6 +48,7 @@ class InteractiveImportModal extends Component {
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
size={sizes.EXTRA_LARGE}
closeOnBackgroundClick={false} closeOnBackgroundClick={false}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
@ -73,7 +75,12 @@ InteractiveImportModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
folder: PropTypes.string, folder: PropTypes.string,
downloadId: PropTypes.string, downloadId: PropTypes.string,
modalTitle: PropTypes.string.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
InteractiveImportModal.defaultProps = {
modalTitle: 'Manual Import'
};
export default InteractiveImportModal; export default InteractiveImportModal;

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions'; import { saveInteractiveImportItem, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions'; import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
import getQualities from 'Utilities/Quality/getQualities'; import getQualities from 'Utilities/Quality/getQualities';
import SelectQualityModalContent from './SelectQualityModalContent'; import SelectQualityModalContent from './SelectQualityModalContent';
@ -31,7 +31,8 @@ function createMapStateToProps() {
const mapDispatchToProps = { const mapDispatchToProps = {
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema, dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
dispatchSaveInteractiveImportItems: saveInteractiveImportItem
}; };
class SelectQualityModalContentConnector extends Component { class SelectQualityModalContentConnector extends Component {
@ -49,6 +50,12 @@ class SelectQualityModalContentConnector extends Component {
// Listeners // Listeners
onQualitySelect = ({ qualityId, proper, real }) => { onQualitySelect = ({ qualityId, proper, real }) => {
const {
ids,
dispatchUpdateInteractiveImportItems,
dispatchSaveInteractiveImportItems
} = this.props;
const quality = _.find(this.props.items, const quality = _.find(this.props.items,
(item) => item.id === qualityId); (item) => item.id === qualityId);
@ -57,14 +64,16 @@ class SelectQualityModalContentConnector extends Component {
real: real ? 1 : 0 real: real ? 1 : 0
}; };
this.props.dispatchUpdateInteractiveImportItems({ dispatchUpdateInteractiveImportItems({
ids: this.props.ids, ids,
quality: { quality: {
quality, quality,
revision revision
} }
}); });
dispatchSaveInteractiveImportItems({ ids });
this.props.onModalClose(true); this.props.onModalClose(true);
}; };
@ -89,6 +98,7 @@ SelectQualityModalContentConnector.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired, dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired, dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
dispatchSaveInteractiveImportItems: PropTypes.func.isRequired,
onModalClose: 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 { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import updateSectionState from 'Utilities/State/updateSectionState'; import updateSectionState from 'Utilities/State/updateSectionState';
import { set, update } from './baseActions'; import { set, update, updateItem } from './baseActions';
import createFetchHandler from './Creators/createFetchHandler'; import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions'; import createHandleActions from './Creators/createHandleActions';
import createSaveProviderHandler from './Creators/createSaveProviderHandler';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
// //
@ -18,6 +17,8 @@ export const section = 'interactiveImport';
const albumsSection = `${section}.albums`; const albumsSection = `${section}.albums`;
const trackFilesSection = `${section}.trackFiles`; const trackFilesSection = `${section}.trackFiles`;
let abortCurrentRequest = null;
let currentIds = [];
const MAXIMUM_RECENT_FOLDERS = 10; const MAXIMUM_RECENT_FOLDERS = 10;
@ -34,7 +35,7 @@ export const defaultState = {
sortKey: 'path', sortKey: 'path',
sortDirection: sortDirections.ASCENDING, sortDirection: sortDirections.ASCENDING,
recentFolders: [], recentFolders: [],
importMode: 'move', importMode: 'chooseImportMode',
sortPredicates: { sortPredicates: {
path: function(item, direction) { path: function(item, direction) {
const path = item.path; const path = item.path;
@ -156,7 +157,83 @@ 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,
artistId: item.artist ? item.artist.id : undefined,
albumId: item.album ? item.album.id : undefined,
albumReleaseId: item.albumReleaseId ? item.albumReleaseId : undefined,
trackIds: (item.tracks || []).map((e) => e.id),
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_ALBUMS]: createFetchHandler(albumsSection, '/album'), [FETCH_INTERACTIVE_IMPORT_ALBUMS]: createFetchHandler(albumsSection, '/album'),

@ -32,8 +32,8 @@ namespace Lidarr.Api.V1.ManualImport
_logger = logger; _logger = logger;
} }
[HttpPut] [HttpPost]
public IActionResult UpdateItems(List<ManualImportResource> resource) public IActionResult UpdateItems(List<ManualImportUpdateResource> resource)
{ {
return Accepted(UpdateImportItems(resource)); return Accepted(UpdateImportItems(resource));
} }
@ -65,7 +65,7 @@ namespace Lidarr.Api.V1.ManualImport
return item; return item;
} }
private List<ManualImportResource> UpdateImportItems(List<ManualImportResource> resources) private List<ManualImportResource> UpdateImportItems(List<ManualImportUpdateResource> resources)
{ {
var items = new List<ManualImportItem>(); var items = new List<ManualImportItem>();
foreach (var resource in resources) foreach (var resource in resources)
@ -75,11 +75,11 @@ namespace Lidarr.Api.V1.ManualImport
Id = resource.Id, Id = resource.Id,
Path = resource.Path, Path = resource.Path,
Name = resource.Name, Name = resource.Name,
Size = resource.Size, Artist = resource.ArtistId.HasValue ? _artistService.GetArtist(resource.ArtistId.Value) : null,
Artist = resource.Artist == null ? null : _artistService.GetArtist(resource.Artist.Id), Album = resource.AlbumId.HasValue ? _albumService.GetAlbum(resource.AlbumId.Value) : null,
Album = resource.Album == null ? null : _albumService.GetAlbum(resource.Album.Id), Release = resource.AlbumReleaseId.HasValue ? _releaseService.GetRelease(resource.AlbumReleaseId.Value) : null,
Release = resource.AlbumReleaseId == 0 ? null : _releaseService.GetRelease(resource.AlbumReleaseId),
Quality = resource.Quality, Quality = resource.Quality,
ReleaseGroup = resource.ReleaseGroup,
DownloadId = resource.DownloadId, DownloadId = resource.DownloadId,
AdditionalFile = resource.AdditionalFile, AdditionalFile = resource.AdditionalFile,
ReplaceExistingFiles = resource.ReplaceExistingFiles, ReplaceExistingFiles = resource.ReplaceExistingFiles,

@ -21,6 +21,7 @@ namespace Lidarr.Api.V1.ManualImport
public int AlbumReleaseId { get; set; } public int AlbumReleaseId { get; set; }
public List<TrackResource> Tracks { get; set; } public List<TrackResource> Tracks { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public string ReleaseGroup { get; set; }
public int QualityWeight { get; set; } public int QualityWeight { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
@ -50,6 +51,7 @@ namespace Lidarr.Api.V1.ManualImport
AlbumReleaseId = model.Release?.Id ?? 0, AlbumReleaseId = model.Release?.Id ?? 0,
Tracks = model.Tracks.ToResource(), Tracks = model.Tracks.ToResource(),
Quality = model.Quality, Quality = model.Quality,
ReleaseGroup = model.ReleaseGroup,
// QualityWeight // QualityWeight
DownloadId = model.DownloadId, DownloadId = model.DownloadId,

@ -0,0 +1,29 @@
using System.Collections.Generic;
using Lidarr.Api.V1.Albums;
using Lidarr.Api.V1.Tracks;
using Lidarr.Http.REST;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Qualities;
namespace Lidarr.Api.V1.ManualImport
{
public class ManualImportUpdateResource : RestResource
{
public string Path { get; set; }
public string Name { get; set; }
public int? ArtistId { get; set; }
public int? AlbumId { get; set; }
public int? AlbumReleaseId { get; set; }
public List<TrackResource> Tracks { get; set; }
public List<int> TrackIds { 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; }
}
}

@ -119,11 +119,13 @@
"ChmodFolder": "chmod Folder", "ChmodFolder": "chmod Folder",
"ChmodFolderHelpText": "Octal, applied during import/rename to media folders and files (without execute bits)", "ChmodFolderHelpText": "Octal, applied during import/rename to media folders and files (without execute bits)",
"ChmodFolderHelpTextWarning": "This only works if the user running Lidarr is the owner of the file. It's better to ensure the download client sets the permissions properly.", "ChmodFolderHelpTextWarning": "This only works if the user running Lidarr 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", "ChownGroup": "chown Group",
"ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.", "ChownGroupHelpText": "Group name or gid. Use gid for remote file systems.",
"ChownGroupHelpTextWarning": "This only works if the user running Lidarr is the owner of the file. It's better to ensure the download client uses the same group as Lidarr.", "ChownGroupHelpTextWarning": "This only works if the user running Lidarr is the owner of the file. It's better to ensure the download client uses the same group as Lidarr.",
"Clear": "Clear", "Clear": "Clear",
"ClickToChangeQuality": "Click to change quality", "ClickToChangeQuality": "Click to change quality",
"ClickToChangeReleaseGroup": "Click to change release group",
"ClientPriority": "Client Priority", "ClientPriority": "Client Priority",
"CloneIndexer": "Clone Indexer", "CloneIndexer": "Clone Indexer",
"CloneProfile": "Clone Profile", "CloneProfile": "Clone Profile",
@ -497,11 +499,11 @@
"NotificationTriggers": "Notification Triggers", "NotificationTriggers": "Notification Triggers",
"NoUpdatesAreAvailable": "No updates are available", "NoUpdatesAreAvailable": "No updates are available",
"Ok": "Ok", "Ok": "Ok",
"OnAlbumDeleteHelpText": "On Album Delete",
"OnAlbumDelete": "On Album Delete", "OnAlbumDelete": "On Album Delete",
"OnArtistDelete": "On Artist Delete", "OnAlbumDeleteHelpText": "On Album Delete",
"OnApplicationUpdate": "On Application Update", "OnApplicationUpdate": "On Application Update",
"OnApplicationUpdateHelpText": "On Application Update", "OnApplicationUpdateHelpText": "On Application Update",
"OnArtistDelete": "On Artist Delete",
"OnArtistDeleteHelpText": "On Artist Delete", "OnArtistDeleteHelpText": "On Artist Delete",
"OnDownloadFailure": "On Download Failure", "OnDownloadFailure": "On Download Failure",
"OnDownloadFailureHelpText": "On Download Failure", "OnDownloadFailureHelpText": "On Download Failure",
@ -692,6 +694,7 @@
"SelectedCountArtistsSelectedInterp": "{0} Artist(s) Selected", "SelectedCountArtistsSelectedInterp": "{0} Artist(s) Selected",
"SelectFolder": "Select Folder", "SelectFolder": "Select Folder",
"SelectQuality": "Select Quality", "SelectQuality": "Select Quality",
"SelectReleaseGroup": "Select Release Group",
"SelectTracks": "Select Tracks", "SelectTracks": "Select Tracks",
"SendAnonymousUsageData": "Send Anonymous Usage Data", "SendAnonymousUsageData": "Send Anonymous Usage Data",
"SetPermissions": "Set Permissions", "SetPermissions": "Set Permissions",

@ -22,6 +22,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
public AlbumRelease Release { get; set; } public AlbumRelease Release { get; set; }
public List<Track> Tracks { get; set; } public List<Track> Tracks { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public string ReleaseGroup { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; } public IEnumerable<Rejection> Rejections { get; set; }
public ParsedTrackInfo Tags { get; set; } public ParsedTrackInfo Tags { get; set; }

@ -17,6 +17,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.RootFolders; using NzbDrone.Core.RootFolders;
namespace NzbDrone.Core.MediaFiles.TrackImport.Manual namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
@ -237,7 +238,18 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
item.Tracks = decision.Item.Tracks; item.Tracks = decision.Item.Tracks;
} }
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.Rejections = decision.Rejections;
item.Size = decision.Item.Size;
result.Add(item); result.Add(item);
} }

Loading…
Cancel
Save