mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-12 08:07:10 -07:00
New: Option to ignore items when removing from queue instead of removing from client
This commit is contained in:
parent
d83e20937d
commit
48750780fe
21 changed files with 277 additions and 65 deletions
|
@ -400,6 +400,30 @@ function HistoryDetails(props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType === 'downloadIgnored') {
|
||||||
|
const {
|
||||||
|
message
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DescriptionList>
|
||||||
|
<DescriptionListItem
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title="Name"
|
||||||
|
data={sourceTitle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
!!message &&
|
||||||
|
<DescriptionListItem
|
||||||
|
title="Message"
|
||||||
|
data={message}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</DescriptionList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DescriptionList>
|
<DescriptionList>
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
|
|
|
@ -29,6 +29,8 @@ function getHeaderTitle(eventType) {
|
||||||
return 'Album Import Incomplete';
|
return 'Album Import Incomplete';
|
||||||
case 'downloadImported':
|
case 'downloadImported':
|
||||||
return 'Download Completed';
|
return 'Download Completed';
|
||||||
|
case 'downloadIgnored':
|
||||||
|
return 'Download Ignored';
|
||||||
default:
|
default:
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ function getIconName(eventType) {
|
||||||
return icons.DOWNLOADED;
|
return icons.DOWNLOADED;
|
||||||
case 'downloadImported':
|
case 'downloadImported':
|
||||||
return icons.DOWNLOADED;
|
return icons.DOWNLOADED;
|
||||||
|
case 'downloadIgnored':
|
||||||
|
return icons.IGNORE;
|
||||||
default:
|
default:
|
||||||
return icons.UNKNOWN;
|
return icons.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
@ -61,6 +63,8 @@ function getTooltip(eventType, data) {
|
||||||
return 'Files downloaded but not all could be imported';
|
return 'Files downloaded but not all could be imported';
|
||||||
case 'downloadImported':
|
case 'downloadImported':
|
||||||
return 'Download completed and successfully imported';
|
return 'Download completed and successfully imported';
|
||||||
|
case 'downloadIgnored':
|
||||||
|
return 'Album Download Ignored';
|
||||||
default:
|
default:
|
||||||
return 'Unknown event';
|
return 'Unknown event';
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,8 +107,8 @@ class Queue extends Component {
|
||||||
this.setState({ isConfirmRemoveModalOpen: true });
|
this.setState({ isConfirmRemoveModalOpen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveSelectedConfirmed = (blacklist, skipredownload) => {
|
onRemoveSelectedConfirmed = (payload) => {
|
||||||
this.props.onRemoveSelectedPress(this.getSelectedIds(), blacklist, skipredownload);
|
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
|
||||||
this.setState({ isConfirmRemoveModalOpen: false });
|
this.setState({ isConfirmRemoveModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,8 @@ class Queue extends Component {
|
||||||
const isRefreshing = isFetching || isAlbumsFetching || isRefreshMonitoredDownloadsExecuting;
|
const isRefreshing = isFetching || isAlbumsFetching || isRefreshMonitoredDownloadsExecuting;
|
||||||
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.albumId));
|
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.albumId));
|
||||||
const hasError = error || albumsError;
|
const hasError = error || albumsError;
|
||||||
const selectedCount = this.getSelectedIds().length;
|
const selectedIds = this.getSelectedIds();
|
||||||
|
const selectedCount = selectedIds.length;
|
||||||
const disableSelectedActions = selectedCount === 0;
|
const disableSelectedActions = selectedCount === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -259,6 +260,13 @@ class Queue extends Component {
|
||||||
<RemoveQueueItemsModal
|
<RemoveQueueItemsModal
|
||||||
isOpen={isConfirmRemoveModalOpen}
|
isOpen={isConfirmRemoveModalOpen}
|
||||||
selectedCount={selectedCount}
|
selectedCount={selectedCount}
|
||||||
|
canIgnore={isConfirmRemoveModalOpen && (
|
||||||
|
selectedIds.every((id) => {
|
||||||
|
const item = items.find((i) => i.id === id);
|
||||||
|
|
||||||
|
return !!(item && item.artistId && item.albumId);
|
||||||
|
})
|
||||||
|
)}
|
||||||
onRemovePress={this.onRemoveSelectedConfirmed}
|
onRemovePress={this.onRemoveSelectedConfirmed}
|
||||||
onModalClose={this.onConfirmRemoveModalClose}
|
onModalClose={this.onConfirmRemoveModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -137,8 +137,8 @@ class QueueConnector extends Component {
|
||||||
this.props.grabQueueItems({ ids });
|
this.props.grabQueueItems({ ids });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveSelectedPress = (ids, blacklist, skipredownload) => {
|
onRemoveSelectedPress = (payload) => {
|
||||||
this.props.removeQueueItems({ ids, blacklist, skipredownload });
|
this.props.removeQueueItems(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -335,6 +335,7 @@ class QueueRow extends Component {
|
||||||
<RemoveQueueItemModal
|
<RemoveQueueItemModal
|
||||||
isOpen={isRemoveQueueItemModalOpen}
|
isOpen={isRemoveQueueItemModalOpen}
|
||||||
sourceTitle={title}
|
sourceTitle={title}
|
||||||
|
canIgnore={!!(artist && album)}
|
||||||
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
||||||
onModalClose={this.onRemoveQueueItemModalClose}
|
onModalClose={this.onRemoveQueueItemModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -43,8 +43,8 @@ class QueueRowConnector extends Component {
|
||||||
this.props.grabQueueItem({ id: this.props.id });
|
this.props.grabQueueItem({ id: this.props.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveQueueItemPress = (blacklist, skipredownload) => {
|
onRemoveQueueItemPress = (payload) => {
|
||||||
this.props.removeQueueItem({ id: this.props.id, blacklist, skipredownload });
|
this.props.removeQueueItem({ id: this.props.id, ...payload });
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
.messageRemove {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
color: $dangerColor;
|
|
||||||
}
|
|
|
@ -10,7 +10,6 @@ import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import styles from './RemoveQueueItemModal.css';
|
|
||||||
|
|
||||||
class RemoveQueueItemModal extends Component {
|
class RemoveQueueItemModal extends Component {
|
||||||
|
|
||||||
|
@ -21,14 +20,30 @@ class RemoveQueueItemModal extends Component {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
remove: true,
|
||||||
blacklist: false,
|
blacklist: false,
|
||||||
skipredownload: false
|
skipredownload: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Control
|
||||||
|
|
||||||
|
resetState = function() {
|
||||||
|
this.setState({
|
||||||
|
remove: true,
|
||||||
|
blacklist: false,
|
||||||
|
skipredownload: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
|
onRemoveChange = ({ value }) => {
|
||||||
|
this.setState({ remove: value });
|
||||||
|
}
|
||||||
|
|
||||||
onBlacklistChange = ({ value }) => {
|
onBlacklistChange = ({ value }) => {
|
||||||
this.setState({ blacklist: value });
|
this.setState({ blacklist: value });
|
||||||
}
|
}
|
||||||
|
@ -37,22 +52,15 @@ class RemoveQueueItemModal extends Component {
|
||||||
this.setState({ skipredownload: value });
|
this.setState({ skipredownload: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveQueueItemConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
const blacklist = this.state.blacklist;
|
const state = this.state;
|
||||||
const skipredownload = this.state.skipredownload;
|
|
||||||
|
|
||||||
this.setState({
|
this.resetState();
|
||||||
blacklist: false,
|
this.props.onRemovePress(state);
|
||||||
skipredownload: false
|
|
||||||
});
|
|
||||||
this.props.onRemovePress(blacklist, skipredownload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onModalClose = () => {
|
onModalClose = () => {
|
||||||
this.setState({
|
this.resetState();
|
||||||
blacklist: false,
|
|
||||||
skipredownload: false
|
|
||||||
});
|
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +70,11 @@ class RemoveQueueItemModal extends Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
sourceTitle
|
sourceTitle,
|
||||||
|
canIgnore
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const blacklist = this.state.blacklist;
|
const { remove, blacklist, skipredownload } = this.state;
|
||||||
const skipredownload = this.state.skipredownload;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -86,12 +94,22 @@ class RemoveQueueItemModal extends Component {
|
||||||
Are you sure you want to remove '{sourceTitle}' from the queue?
|
Are you sure you want to remove '{sourceTitle}' from the queue?
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.messageRemove}>
|
<FormGroup>
|
||||||
Removing will remove the download and the file(s) from the download client.
|
<FormLabel>Remove From Download Client</FormLabel>
|
||||||
</div>
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="remove"
|
||||||
|
value={remove}
|
||||||
|
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||||
|
isDisabled={!canIgnore}
|
||||||
|
onChange={this.onRemoveChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Blacklist Release</FormLabel>
|
<FormLabel>Blacklist Release</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="blacklist"
|
name="blacklist"
|
||||||
|
@ -124,7 +142,7 @@ class RemoveQueueItemModal extends Component {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={this.onRemoveQueueItemConfirmed}
|
onPress={this.onRemoveConfirmed}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -138,6 +156,7 @@ class RemoveQueueItemModal extends Component {
|
||||||
RemoveQueueItemModal.propTypes = {
|
RemoveQueueItemModal.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
|
canIgnore: PropTypes.bool.isRequired,
|
||||||
onRemovePress: PropTypes.func.isRequired,
|
onRemovePress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,14 +21,30 @@ class RemoveQueueItemsModal extends Component {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
remove: true,
|
||||||
blacklist: false,
|
blacklist: false,
|
||||||
skipredownload: false
|
skipredownload: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Control
|
||||||
|
|
||||||
|
resetState = function() {
|
||||||
|
this.setState({
|
||||||
|
remove: true,
|
||||||
|
blacklist: false,
|
||||||
|
skipredownload: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
|
onRemoveChange = ({ value }) => {
|
||||||
|
this.setState({ remove: value });
|
||||||
|
}
|
||||||
|
|
||||||
onBlacklistChange = ({ value }) => {
|
onBlacklistChange = ({ value }) => {
|
||||||
this.setState({ blacklist: value });
|
this.setState({ blacklist: value });
|
||||||
}
|
}
|
||||||
|
@ -37,22 +53,15 @@ class RemoveQueueItemsModal extends Component {
|
||||||
this.setState({ skipredownload: value });
|
this.setState({ skipredownload: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveQueueItemConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
const blacklist = this.state.blacklist;
|
const state = this.state;
|
||||||
const skipredownload = this.state.skipredownload;
|
|
||||||
|
|
||||||
this.setState({
|
this.resetState();
|
||||||
blacklist: false,
|
this.props.onRemovePress(state);
|
||||||
skipredownload: false
|
|
||||||
});
|
|
||||||
this.props.onRemovePress(blacklist, skipredownload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onModalClose = () => {
|
onModalClose = () => {
|
||||||
this.setState({
|
this.resetState();
|
||||||
blacklist: false,
|
|
||||||
skipredownload: false
|
|
||||||
});
|
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +71,11 @@ class RemoveQueueItemsModal extends Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
selectedCount
|
selectedCount,
|
||||||
|
canIgnore
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const blacklist = this.state.blacklist;
|
const { remove, blacklist, skipredownload } = this.state;
|
||||||
const skipredownload = this.state.skipredownload;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -87,7 +96,23 @@ class RemoveQueueItemsModal extends Component {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Blacklist Release</FormLabel>
|
<FormLabel>Remove From Download Client</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="remove"
|
||||||
|
value={remove}
|
||||||
|
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||||
|
isDisabled={!canIgnore}
|
||||||
|
onChange={this.onRemoveChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
Blacklist Release{selectedCount > 1 ? 's' : ''}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="blacklist"
|
name="blacklist"
|
||||||
|
@ -120,7 +145,7 @@ class RemoveQueueItemsModal extends Component {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={this.onRemoveQueueItemConfirmed}
|
onPress={this.onRemoveConfirmed}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -134,6 +159,7 @@ class RemoveQueueItemsModal extends Component {
|
||||||
RemoveQueueItemsModal.propTypes = {
|
RemoveQueueItemsModal.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
selectedCount: PropTypes.number.isRequired,
|
selectedCount: PropTypes.number.isRequired,
|
||||||
|
canIgnore: PropTypes.bool.isRequired,
|
||||||
onRemovePress: PropTypes.func.isRequired,
|
onRemovePress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -157,6 +157,7 @@ export const HEALTH = fasMedkit;
|
||||||
export const HEART = fasHeart;
|
export const HEART = fasHeart;
|
||||||
export const HISTORY = fasHistory;
|
export const HISTORY = fasHistory;
|
||||||
export const HOUSEKEEPING = fasHome;
|
export const HOUSEKEEPING = fasHome;
|
||||||
|
export const IGNORE = fasTimesCircle;
|
||||||
export const INFO = fasInfoCircle;
|
export const INFO = fasInfoCircle;
|
||||||
export const INTERACTIVE = fasUser;
|
export const INTERACTIVE = fasUser;
|
||||||
export const KEYBOARD = farKeyboard;
|
export const KEYBOARD = farKeyboard;
|
||||||
|
|
|
@ -179,6 +179,17 @@ export const defaultState = {
|
||||||
type: filterTypes.EQUAL
|
type: filterTypes.EQUAL
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ignored',
|
||||||
|
label: 'Ignored',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
key: 'eventType',
|
||||||
|
value: '7',
|
||||||
|
type: filterTypes.EQUAL
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -345,6 +345,7 @@ export const actionHandlers = handleThunks({
|
||||||
[REMOVE_QUEUE_ITEM]: function(getState, payload, dispatch) {
|
[REMOVE_QUEUE_ITEM]: function(getState, payload, dispatch) {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
|
remove,
|
||||||
blacklist,
|
blacklist,
|
||||||
skipredownload
|
skipredownload
|
||||||
} = payload;
|
} = payload;
|
||||||
|
@ -352,7 +353,7 @@ export const actionHandlers = handleThunks({
|
||||||
dispatch(updateItem({ section: paged, id, isRemoving: true }));
|
dispatch(updateItem({ section: paged, id, isRemoving: true }));
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: `/queue/${id}?blacklist=${blacklist}&skipredownload=${skipredownload}`,
|
url: `/queue/${id}?removeFromClient=${remove}&blacklist=${blacklist}&skipredownload=${skipredownload}`,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
|
@ -368,6 +369,7 @@ export const actionHandlers = handleThunks({
|
||||||
[REMOVE_QUEUE_ITEMS]: function(getState, payload, dispatch) {
|
[REMOVE_QUEUE_ITEMS]: function(getState, payload, dispatch) {
|
||||||
const {
|
const {
|
||||||
ids,
|
ids,
|
||||||
|
remove,
|
||||||
blacklist,
|
blacklist,
|
||||||
skipredownload
|
skipredownload
|
||||||
} = payload;
|
} = payload;
|
||||||
|
@ -385,7 +387,7 @@ export const actionHandlers = handleThunks({
|
||||||
]));
|
]));
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: `/queue/bulk?blacklist=${blacklist}&skipredownload=${skipredownload}`,
|
url: `/queue/bulk?removeFromClient=${remove}&blacklist=${blacklist}&skipredownload=${skipredownload}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: JSON.stringify({ ids })
|
data: JSON.stringify({ ids })
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Lidarr.Http;
|
using Lidarr.Http;
|
||||||
using Lidarr.Http.Extensions;
|
using Lidarr.Http.Extensions;
|
||||||
using Lidarr.Http.REST;
|
using Lidarr.Http.REST;
|
||||||
|
@ -15,6 +16,7 @@ namespace Lidarr.Api.V1.Queue
|
||||||
private readonly IQueueService _queueService;
|
private readonly IQueueService _queueService;
|
||||||
private readonly ITrackedDownloadService _trackedDownloadService;
|
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||||
private readonly IFailedDownloadService _failedDownloadService;
|
private readonly IFailedDownloadService _failedDownloadService;
|
||||||
|
private readonly IIgnoredDownloadService _ignoredDownloadService;
|
||||||
private readonly IProvideDownloadClient _downloadClientProvider;
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||||
private readonly IPendingReleaseService _pendingReleaseService;
|
private readonly IPendingReleaseService _pendingReleaseService;
|
||||||
private readonly IDownloadService _downloadService;
|
private readonly IDownloadService _downloadService;
|
||||||
|
@ -22,6 +24,7 @@ namespace Lidarr.Api.V1.Queue
|
||||||
public QueueActionModule(IQueueService queueService,
|
public QueueActionModule(IQueueService queueService,
|
||||||
ITrackedDownloadService trackedDownloadService,
|
ITrackedDownloadService trackedDownloadService,
|
||||||
IFailedDownloadService failedDownloadService,
|
IFailedDownloadService failedDownloadService,
|
||||||
|
IIgnoredDownloadService ignoredDownloadService,
|
||||||
IProvideDownloadClient downloadClientProvider,
|
IProvideDownloadClient downloadClientProvider,
|
||||||
IPendingReleaseService pendingReleaseService,
|
IPendingReleaseService pendingReleaseService,
|
||||||
IDownloadService downloadService)
|
IDownloadService downloadService)
|
||||||
|
@ -29,6 +32,7 @@ namespace Lidarr.Api.V1.Queue
|
||||||
_queueService = queueService;
|
_queueService = queueService;
|
||||||
_trackedDownloadService = trackedDownloadService;
|
_trackedDownloadService = trackedDownloadService;
|
||||||
_failedDownloadService = failedDownloadService;
|
_failedDownloadService = failedDownloadService;
|
||||||
|
_ignoredDownloadService = ignoredDownloadService;
|
||||||
_downloadClientProvider = downloadClientProvider;
|
_downloadClientProvider = downloadClientProvider;
|
||||||
_pendingReleaseService = pendingReleaseService;
|
_pendingReleaseService = pendingReleaseService;
|
||||||
_downloadService = downloadService;
|
_downloadService = downloadService;
|
||||||
|
@ -75,10 +79,11 @@ namespace Lidarr.Api.V1.Queue
|
||||||
|
|
||||||
private object Remove(int id)
|
private object Remove(int id)
|
||||||
{
|
{
|
||||||
|
var removeFromClient = Request.GetBooleanQueryParameter("removeFromClient", true);
|
||||||
var blacklist = Request.GetBooleanQueryParameter("blacklist");
|
var blacklist = Request.GetBooleanQueryParameter("blacklist");
|
||||||
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
|
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
|
||||||
|
|
||||||
var trackedDownload = Remove(id, blacklist, skipReDownload);
|
var trackedDownload = Remove(id, removeFromClient, blacklist, skipReDownload);
|
||||||
|
|
||||||
if (trackedDownload != null)
|
if (trackedDownload != null)
|
||||||
{
|
{
|
||||||
|
@ -90,6 +95,7 @@ namespace Lidarr.Api.V1.Queue
|
||||||
|
|
||||||
private object Remove()
|
private object Remove()
|
||||||
{
|
{
|
||||||
|
var removeFromClient = Request.GetBooleanQueryParameter("removeFromClient", true);
|
||||||
var blacklist = Request.GetBooleanQueryParameter("blacklist");
|
var blacklist = Request.GetBooleanQueryParameter("blacklist");
|
||||||
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
|
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
|
||||||
|
|
||||||
|
@ -98,7 +104,7 @@ namespace Lidarr.Api.V1.Queue
|
||||||
|
|
||||||
foreach (var id in resource.Ids)
|
foreach (var id in resource.Ids)
|
||||||
{
|
{
|
||||||
var trackedDownload = Remove(id, blacklist, skipReDownload);
|
var trackedDownload = Remove(id, removeFromClient, blacklist, skipReDownload);
|
||||||
|
|
||||||
if (trackedDownload != null)
|
if (trackedDownload != null)
|
||||||
{
|
{
|
||||||
|
@ -111,7 +117,7 @@ namespace Lidarr.Api.V1.Queue
|
||||||
return new object();
|
return new object();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackedDownload Remove(int id, bool blacklist, bool skipReDownload)
|
private TrackedDownload Remove(int id, bool removeFromClient, bool blacklist, bool skipReDownload)
|
||||||
{
|
{
|
||||||
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
|
||||||
|
|
||||||
|
@ -129,6 +135,8 @@ namespace Lidarr.Api.V1.Queue
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (removeFromClient)
|
||||||
|
{
|
||||||
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
||||||
|
|
||||||
if (downloadClient == null)
|
if (downloadClient == null)
|
||||||
|
@ -137,12 +145,21 @@ namespace Lidarr.Api.V1.Queue
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
|
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (blacklist)
|
if (blacklist)
|
||||||
{
|
{
|
||||||
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipReDownload);
|
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipReDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!removeFromClient && !blacklist)
|
||||||
|
{
|
||||||
|
if (!_ignoredDownloadService.IgnoreDownload(trackedDownload))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return trackedDownload;
|
return trackedDownload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs
Normal file
17
src/NzbDrone.Core/Download/DownloadIgnoredEvent.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class DownloadIgnoredEvent : IEvent
|
||||||
|
{
|
||||||
|
public int ArtistId { get; set; }
|
||||||
|
public List<int> AlbumIds { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public string SourceTitle { get; set; }
|
||||||
|
public string DownloadClient { get; set; }
|
||||||
|
public string DownloadId { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
52
src/NzbDrone.Core/Download/IgnoredDownloadService.cs
Normal file
52
src/NzbDrone.Core/Download/IgnoredDownloadService.cs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public interface IIgnoredDownloadService
|
||||||
|
{
|
||||||
|
bool IgnoreDownload(TrackedDownload trackedDownload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IgnoredDownloadService : IIgnoredDownloadService
|
||||||
|
{
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public IgnoredDownloadService(IEventAggregator eventAggregator,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IgnoreDownload(TrackedDownload trackedDownload)
|
||||||
|
{
|
||||||
|
var artist = trackedDownload.RemoteAlbum.Artist;
|
||||||
|
var albums = trackedDownload.RemoteAlbum.Albums;
|
||||||
|
|
||||||
|
if (artist == null || albums.Empty())
|
||||||
|
{
|
||||||
|
_logger.Warn("Unable to ignore download for unknown artist/album");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadIgnoredEvent = new DownloadIgnoredEvent
|
||||||
|
{
|
||||||
|
ArtistId = artist.Id,
|
||||||
|
AlbumIds = albums.Select(e => e.Id).ToList(),
|
||||||
|
Quality = trackedDownload.RemoteAlbum.ParsedAlbumInfo.Quality,
|
||||||
|
SourceTitle = trackedDownload.DownloadItem.Title,
|
||||||
|
DownloadClient = trackedDownload.DownloadItem.DownloadClient,
|
||||||
|
DownloadId = trackedDownload.DownloadItem.DownloadId,
|
||||||
|
Message = "Manually ignored"
|
||||||
|
};
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(downloadIgnoredEvent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -131,8 +131,10 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
|
|
||||||
private bool DownloadIsTrackable(TrackedDownload trackedDownload)
|
private bool DownloadIsTrackable(TrackedDownload trackedDownload)
|
||||||
{
|
{
|
||||||
// If the download has already been imported or failed don't track it
|
// If the download has already been imported or failed or the user ignored it don't track it
|
||||||
if (trackedDownload.State == TrackedDownloadState.Imported || trackedDownload.State == TrackedDownloadState.DownloadFailed)
|
if (trackedDownload.State == TrackedDownloadState.Imported ||
|
||||||
|
trackedDownload.State == TrackedDownloadState.DownloadFailed ||
|
||||||
|
trackedDownload.State == TrackedDownloadState.Ignored)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
ImportPending,
|
ImportPending,
|
||||||
Importing,
|
Importing,
|
||||||
ImportFailed,
|
ImportFailed,
|
||||||
Imported
|
Imported,
|
||||||
|
Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TrackedDownloadStatus
|
public enum TrackedDownloadStatus
|
||||||
|
|
|
@ -252,6 +252,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
return TrackedDownloadState.Imported;
|
return TrackedDownloadState.Imported;
|
||||||
case HistoryEventType.DownloadFailed:
|
case HistoryEventType.DownloadFailed:
|
||||||
return TrackedDownloadState.DownloadFailed;
|
return TrackedDownloadState.DownloadFailed;
|
||||||
|
case HistoryEventType.DownloadIgnored:
|
||||||
|
return TrackedDownloadState.Ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since DownloadComplete is a new event type, we can't assume it exists for old downloads
|
// Since DownloadComplete is a new event type, we can't assume it exists for old downloads
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace NzbDrone.Core.History
|
||||||
TrackFileRenamed = 6,
|
TrackFileRenamed = 6,
|
||||||
AlbumImportIncomplete = 7,
|
AlbumImportIncomplete = 7,
|
||||||
DownloadImported = 8,
|
DownloadImported = 8,
|
||||||
TrackFileRetagged = 9
|
TrackFileRetagged = 9,
|
||||||
|
DownloadIgnored = 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,8 @@ namespace NzbDrone.Core.History
|
||||||
IHandle<TrackFileDeletedEvent>,
|
IHandle<TrackFileDeletedEvent>,
|
||||||
IHandle<TrackFileRenamedEvent>,
|
IHandle<TrackFileRenamedEvent>,
|
||||||
IHandle<TrackFileRetaggedEvent>,
|
IHandle<TrackFileRetaggedEvent>,
|
||||||
IHandle<ArtistDeletedEvent>
|
IHandle<ArtistDeletedEvent>,
|
||||||
|
IHandle<DownloadIgnoredEvent>
|
||||||
{
|
{
|
||||||
private readonly IHistoryRepository _historyRepository;
|
private readonly IHistoryRepository _historyRepository;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
@ -369,6 +370,31 @@ namespace NzbDrone.Core.History
|
||||||
_historyRepository.DeleteForArtist(message.Artist.Id);
|
_historyRepository.DeleteForArtist(message.Artist.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(DownloadIgnoredEvent message)
|
||||||
|
{
|
||||||
|
var historyToAdd = new List<History>();
|
||||||
|
foreach (var albumId in message.AlbumIds)
|
||||||
|
{
|
||||||
|
var history = new History
|
||||||
|
{
|
||||||
|
EventType = HistoryEventType.DownloadIgnored,
|
||||||
|
Date = DateTime.UtcNow,
|
||||||
|
Quality = message.Quality,
|
||||||
|
SourceTitle = message.SourceTitle,
|
||||||
|
ArtistId = message.ArtistId,
|
||||||
|
AlbumId = albumId,
|
||||||
|
DownloadId = message.DownloadId
|
||||||
|
};
|
||||||
|
|
||||||
|
history.Data.Add("DownloadClient", message.DownloadClient);
|
||||||
|
history.Data.Add("Message", message.Message);
|
||||||
|
|
||||||
|
historyToAdd.Add(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
_historyRepository.InsertMany(historyToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
public List<History> Since(DateTime date, HistoryEventType? eventType)
|
public List<History> Since(DateTime date, HistoryEventType? eventType)
|
||||||
{
|
{
|
||||||
return _historyRepository.Since(date, eventType);
|
return _historyRepository.Since(date, eventType);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue