Option to ignore items when removing from queue instead of removing from client

New: Option to not remove item from download client when removing from queue

Closes #1710
This commit is contained in:
Mark McDowall 2019-08-05 00:25:49 -07:00 committed by Mark McDowall
parent 3916495329
commit c6ea7d7e63
21 changed files with 281 additions and 55 deletions

View file

@ -232,6 +232,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

View file

@ -23,6 +23,8 @@ function getHeaderTitle(eventType) {
return 'Episode File Deleted'; return 'Episode File Deleted';
case 'episodeFileRenamed': case 'episodeFileRenamed':
return 'Episode File Renamed'; return 'Episode File Renamed';
case 'downloadIgnored':
return 'Download Ignored';
default: default:
return 'Unknown'; return 'Unknown';
} }

View file

@ -19,6 +19,8 @@ function getIconName(eventType) {
return icons.DELETE; return icons.DELETE;
case 'episodeFileRenamed': case 'episodeFileRenamed':
return icons.ORGANIZE; return icons.ORGANIZE;
case 'downloadIgnored':
return icons.IGNORE;
default: default:
return icons.UNKNOWN; return icons.UNKNOWN;
} }
@ -47,6 +49,8 @@ function getTooltip(eventType, data) {
return 'Episode file deleted'; return 'Episode file deleted';
case 'episodeFileRenamed': case 'episodeFileRenamed':
return 'Episode file renamed'; return 'Episode file renamed';
case 'downloadIgnored':
return 'Episode Download Ignored';
default: default:
return 'Unknown event'; return 'Unknown event';
} }

View file

@ -107,8 +107,8 @@ class Queue extends Component {
this.setState({ isConfirmRemoveModalOpen: true }); this.setState({ isConfirmRemoveModalOpen: true });
} }
onRemoveSelectedConfirmed = (blacklist) => { onRemoveSelectedConfirmed = (payload) => {
this.props.onRemoveSelectedPress(this.getSelectedIds(), blacklist); 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 || isEpisodesFetching || isRefreshMonitoredDownloadsExecuting; const isRefreshing = isFetching || isEpisodesFetching || isRefreshMonitoredDownloadsExecuting;
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length || items.every((e) => !e.episodeId)); const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length || items.every((e) => !e.episodeId));
const hasError = error || episodesError; const hasError = error || episodesError;
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.seriesId && item.episodeId);
})
)}
onRemovePress={this.onRemoveSelectedConfirmed} onRemovePress={this.onRemoveSelectedConfirmed}
onModalClose={this.onConfirmRemoveModalClose} onModalClose={this.onConfirmRemoveModalClose}
/> />

View file

@ -137,8 +137,8 @@ class QueueConnector extends Component {
this.props.grabQueueItems({ ids }); this.props.grabQueueItems({ ids });
} }
onRemoveSelectedPress = (ids, blacklist) => { onRemoveSelectedPress = (payload) => {
this.props.removeQueueItems({ ids, blacklist }); this.props.removeQueueItems(payload);
} }
// //

View file

@ -352,6 +352,7 @@ class QueueRow extends Component {
<RemoveQueueItemModal <RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen} isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title} sourceTitle={title}
canIgnore={!!(series && episode)}
onRemovePress={this.onRemoveQueueItemModalConfirmed} onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose} onModalClose={this.onRemoveQueueItemModalClose}
/> />

View file

@ -43,8 +43,8 @@ class QueueRowConnector extends Component {
this.props.grabQueueItem({ id: this.props.id }); this.props.grabQueueItem({ id: this.props.id });
} }
onRemoveQueueItemPress = (blacklist) => { onRemoveQueueItemPress = (payload) => {
this.props.removeQueueItem({ id: this.props.id, blacklist }); this.props.removeQueueItem({ id: this.props.id, ...payload });
} }
// //

View file

@ -1,4 +0,0 @@
.messageRemove {
margin-bottom: 30px;
color: $dangerColor;
}

View file

@ -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,26 +20,41 @@ class RemoveQueueItemModal extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
remove: true,
blacklist: false blacklist: false
}; };
} }
//
// Control
resetState = function() {
this.setState({
remove: true,
blacklist: false
});
}
// //
// Listeners // Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
}
onBlacklistChange = ({ value }) => { onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value }); this.setState({ blacklist: value });
} }
onRemoveQueueItemConfirmed = () => { onRemoveConfirmed = () => {
const blacklist = this.state.blacklist; const state = this.state;
this.setState({ blacklist: false }); this.resetState();
this.props.onRemovePress(blacklist); this.props.onRemovePress(state);
} }
onModalClose = () => { onModalClose = () => {
this.setState({ blacklist: false }); this.resetState();
this.props.onModalClose(); this.props.onModalClose();
} }
@ -50,10 +64,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 } = this.state;
return ( return (
<Modal <Modal
@ -73,17 +88,27 @@ 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"
value={blacklist} value={blacklist}
helpText="Prevents Sonarr from automatically grabbing this release again" helpText="Starts a search for this episode again and prevents this release from being grabbed again"
onChange={this.onBlacklistChange} onChange={this.onBlacklistChange}
/> />
</FormGroup> </FormGroup>
@ -97,7 +122,7 @@ class RemoveQueueItemModal extends Component {
<Button <Button
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={this.onRemoveQueueItemConfirmed} onPress={this.onRemoveConfirmed}
> >
Remove Remove
</Button> </Button>
@ -111,6 +136,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
}; };

View file

@ -21,26 +21,41 @@ class RemoveQueueItemsModal extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
remove: true,
blacklist: false blacklist: false
}; };
} }
//
// Control
resetState = function() {
this.setState({
remove: true,
blacklist: false
});
}
// //
// Listeners // Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
}
onBlacklistChange = ({ value }) => { onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value }); this.setState({ blacklist: value });
} }
onRemoveQueueItemConfirmed = () => { onRemoveConfirmed = () => {
const blacklist = this.state.blacklist; const state = this.state;
this.setState({ blacklist: false }); this.resetState();
this.props.onRemovePress(blacklist); this.props.onRemovePress(state);
} }
onModalClose = () => { onModalClose = () => {
this.setState({ blacklist: false }); this.resetState();
this.props.onModalClose(); this.props.onModalClose();
} }
@ -50,10 +65,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 } = this.state;
return ( return (
<Modal <Modal
@ -74,7 +90,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"
@ -93,7 +125,7 @@ class RemoveQueueItemsModal extends Component {
<Button <Button
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={this.onRemoveQueueItemConfirmed} onPress={this.onRemoveConfirmed}
> >
Remove Remove
</Button> </Button>
@ -107,6 +139,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
}; };

View file

@ -145,6 +145,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;

View file

@ -150,6 +150,17 @@ export const defaultState = {
type: filterTypes.EQUAL type: filterTypes.EQUAL
} }
] ]
},
{
key: 'ignored',
label: 'Ignored',
filters: [
{
key: 'eventType',
value: '7',
type: filterTypes.EQUAL
}
]
} }
] ]

View file

@ -356,13 +356,14 @@ 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
} = payload; } = payload;
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}`, url: `/queue/${id}?removeFromClient=${remove}&blacklist=${blacklist}`,
method: 'DELETE' method: 'DELETE'
}).request; }).request;
@ -378,6 +379,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
} = payload; } = payload;
@ -394,7 +396,7 @@ export const actionHandlers = handleThunks({
])); ]));
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: `/queue/bulk?blacklist=${blacklist}`, url: `/queue/bulk?removeFromClient=${remove}&blacklist=${blacklist}`,
method: 'DELETE', method: 'DELETE',
dataType: 'json', dataType: 'json',
data: JSON.stringify({ ids }) data: JSON.stringify({ ids })

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.Download
{
public class DownloadIgnoredEvent : IEvent
{
public int SeriesId { get; set; }
public List<int> EpisodeIds { get; set; }
public Language Language { 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; }
}
}

View file

@ -0,0 +1,53 @@
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 series = trackedDownload.RemoteEpisode.Series;
var episodes = trackedDownload.RemoteEpisode.Episodes;
if (series == null || episodes.Empty())
{
_logger.Warn("Unable to ignore download for unknown series/episode");
return false;
}
var downloadIgnoredEvent = new DownloadIgnoredEvent
{
SeriesId = series.Id,
EpisodeIds = episodes.Select(e => e.Id).ToList(),
Language = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Language,
Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
SourceTitle = trackedDownload.DownloadItem.Title,
DownloadClient = trackedDownload.DownloadItem.DownloadClient,
DownloadId = trackedDownload.DownloadItem.DownloadId,
Message = "Manually ignored"
};
_eventAggregator.PublishEvent(downloadIgnoredEvent);
return true;
}
}
}

View file

@ -133,8 +133,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, failed or the user ignored it don't track it
if (trackedDownload.State == TrackedDownloadState.Imported || trackedDownload.State == TrackedDownloadState.Failed) if (trackedDownload.State == TrackedDownloadState.Imported ||
trackedDownload.State == TrackedDownloadState.Failed ||
trackedDownload.State == TrackedDownloadState.Ignored)
{ {
return false; return false;
} }

View file

@ -40,7 +40,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
Importing, Importing,
Imported, Imported,
FailedPending, FailedPending,
Failed Failed,
Ignored
} }
public enum TrackedDownloadStatus public enum TrackedDownloadStatus

View file

@ -201,6 +201,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
return TrackedDownloadState.Imported; return TrackedDownloadState.Imported;
case HistoryEventType.DownloadFailed: case HistoryEventType.DownloadFailed:
return TrackedDownloadState.Failed; return TrackedDownloadState.Failed;
case HistoryEventType.DownloadIgnored:
return TrackedDownloadState.Ignored;
default: default:
return TrackedDownloadState.Downloading; return TrackedDownloadState.Downloading;
} }

View file

@ -39,6 +39,7 @@ namespace NzbDrone.Core.History
DownloadFolderImported = 3, DownloadFolderImported = 3,
DownloadFailed = 4, DownloadFailed = 4,
EpisodeFileDeleted = 5, EpisodeFileDeleted = 5,
EpisodeFileRenamed = 6 EpisodeFileRenamed = 6,
DownloadIgnored = 7
} }
} }

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Marr.Data.QGen;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
@ -11,11 +10,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Profiles.Languages;
namespace NzbDrone.Core.History namespace NzbDrone.Core.History
{ {
@ -39,7 +34,8 @@ namespace NzbDrone.Core.History
IHandle<DownloadFailedEvent>, IHandle<DownloadFailedEvent>,
IHandle<EpisodeFileDeletedEvent>, IHandle<EpisodeFileDeletedEvent>,
IHandle<EpisodeFileRenamedEvent>, IHandle<EpisodeFileRenamedEvent>,
IHandle<SeriesDeletedEvent> IHandle<SeriesDeletedEvent>,
IHandle<DownloadIgnoredEvent>
{ {
private readonly IHistoryRepository _historyRepository; private readonly IHistoryRepository _historyRepository;
private readonly Logger _logger; private readonly Logger _logger;
@ -307,6 +303,33 @@ namespace NzbDrone.Core.History
} }
} }
public void Handle(DownloadIgnoredEvent message)
{
var historyToAdd = new List<History>();
foreach (var episodeId in message.EpisodeIds)
{
var history = new History
{
EventType = HistoryEventType.DownloadIgnored,
Date = DateTime.UtcNow,
Quality = message.Quality,
SourceTitle = message.SourceTitle,
SeriesId = message.SeriesId,
EpisodeId = episodeId,
DownloadId = message.DownloadId,
Language = message.Language
};
history.Data.Add("DownloadClient", message.DownloadClient);
history.Data.Add("Message", message.Message);
historyToAdd.Add(history);
}
_historyRepository.InsertMany(historyToAdd);
}
public void Handle(SeriesDeletedEvent message) public void Handle(SeriesDeletedEvent message)
{ {
_historyRepository.DeleteForSeries(message.Series.Id); _historyRepository.DeleteForSeries(message.Series.Id);

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Nancy; using Nancy;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.Pending;
@ -16,6 +17,7 @@ namespace Sonarr.Api.V3.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;
@ -23,6 +25,7 @@ namespace Sonarr.Api.V3.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)
@ -30,6 +33,7 @@ namespace Sonarr.Api.V3.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;
@ -76,9 +80,10 @@ namespace Sonarr.Api.V3.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 trackedDownload = Remove(id, blacklist); var trackedDownload = Remove(id, removeFromClient, blacklist);
if (trackedDownload != null) if (trackedDownload != null)
{ {
@ -90,6 +95,7 @@ namespace Sonarr.Api.V3.Queue
private object Remove() private object Remove()
{ {
var removeFromClient = Request.GetBooleanQueryParameter("removeFromClient", true);
var blacklist = Request.GetBooleanQueryParameter("blacklist"); var blacklist = Request.GetBooleanQueryParameter("blacklist");
var resource = Request.Body.FromJson<QueueBulkResource>(); var resource = Request.Body.FromJson<QueueBulkResource>();
@ -97,7 +103,7 @@ namespace Sonarr.Api.V3.Queue
foreach (var id in resource.Ids) foreach (var id in resource.Ids)
{ {
var trackedDownload = Remove(id, blacklist); var trackedDownload = Remove(id, removeFromClient, blacklist);
if (trackedDownload != null) if (trackedDownload != null)
{ {
@ -110,7 +116,7 @@ namespace Sonarr.Api.V3.Queue
return new object(); return new object();
} }
private TrackedDownload Remove(int id, bool blacklist) private TrackedDownload Remove(int id, bool removeFromClient, bool blacklist)
{ {
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id); var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
@ -128,6 +134,8 @@ namespace Sonarr.Api.V3.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)
@ -136,12 +144,21 @@ namespace Sonarr.Api.V3.Queue
} }
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true); downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
}
if (blacklist) if (blacklist)
{ {
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId); _failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId);
} }
if (!removeFromClient && !blacklist)
{
if (!_ignoredDownloadService.IgnoreDownload(trackedDownload))
{
return null;
}
}
return trackedDownload; return trackedDownload;
} }