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

pull/1689/head
Mark McDowall 4 years ago committed by Qstick
parent d83e20937d
commit 48750780fe

@ -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 (
<DescriptionList>
<DescriptionListItem

@ -29,6 +29,8 @@ function getHeaderTitle(eventType) {
return 'Album Import Incomplete';
case 'downloadImported':
return 'Download Completed';
case 'downloadIgnored':
return 'Download Ignored';
default:
return 'Unknown';
}

@ -25,6 +25,8 @@ function getIconName(eventType) {
return icons.DOWNLOADED;
case 'downloadImported':
return icons.DOWNLOADED;
case 'downloadIgnored':
return icons.IGNORE;
default:
return icons.UNKNOWN;
}
@ -61,6 +63,8 @@ function getTooltip(eventType, data) {
return 'Files downloaded but not all could be imported';
case 'downloadImported':
return 'Download completed and successfully imported';
case 'downloadIgnored':
return 'Album Download Ignored';
default:
return 'Unknown event';
}

@ -107,8 +107,8 @@ class Queue extends Component {
this.setState({ isConfirmRemoveModalOpen: true });
}
onRemoveSelectedConfirmed = (blacklist, skipredownload) => {
this.props.onRemoveSelectedPress(this.getSelectedIds(), blacklist, skipredownload);
onRemoveSelectedConfirmed = (payload) => {
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
this.setState({ isConfirmRemoveModalOpen: false });
}
@ -148,7 +148,8 @@ class Queue extends Component {
const isRefreshing = isFetching || isAlbumsFetching || isRefreshMonitoredDownloadsExecuting;
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.albumId));
const hasError = error || albumsError;
const selectedCount = this.getSelectedIds().length;
const selectedIds = this.getSelectedIds();
const selectedCount = selectedIds.length;
const disableSelectedActions = selectedCount === 0;
return (
@ -259,6 +260,13 @@ class Queue extends Component {
<RemoveQueueItemsModal
isOpen={isConfirmRemoveModalOpen}
selectedCount={selectedCount}
canIgnore={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
return !!(item && item.artistId && item.albumId);
})
)}
onRemovePress={this.onRemoveSelectedConfirmed}
onModalClose={this.onConfirmRemoveModalClose}
/>

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

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

@ -43,8 +43,8 @@ class QueueRowConnector extends Component {
this.props.grabQueueItem({ id: this.props.id });
}
onRemoveQueueItemPress = (blacklist, skipredownload) => {
this.props.removeQueueItem({ id: this.props.id, blacklist, skipredownload });
onRemoveQueueItemPress = (payload) => {
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 ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './RemoveQueueItemModal.css';
class RemoveQueueItemModal extends Component {
@ -21,14 +20,30 @@ class RemoveQueueItemModal extends Component {
super(props, context);
this.state = {
remove: true,
blacklist: false,
skipredownload: false
};
}
//
// Control
resetState = function() {
this.setState({
remove: true,
blacklist: false,
skipredownload: false
});
}
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
}
onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value });
}
@ -37,22 +52,15 @@ class RemoveQueueItemModal extends Component {
this.setState({ skipredownload: value });
}
onRemoveQueueItemConfirmed = () => {
const blacklist = this.state.blacklist;
const skipredownload = this.state.skipredownload;
onRemoveConfirmed = () => {
const state = this.state;
this.setState({
blacklist: false,
skipredownload: false
});
this.props.onRemovePress(blacklist, skipredownload);
this.resetState();
this.props.onRemovePress(state);
}
onModalClose = () => {
this.setState({
blacklist: false,
skipredownload: false
});
this.resetState();
this.props.onModalClose();
}
@ -62,11 +70,11 @@ class RemoveQueueItemModal extends Component {
render() {
const {
isOpen,
sourceTitle
sourceTitle,
canIgnore
} = this.props;
const blacklist = this.state.blacklist;
const skipredownload = this.state.skipredownload;
const { remove, blacklist, skipredownload } = this.state;
return (
<Modal
@ -86,12 +94,22 @@ class RemoveQueueItemModal extends Component {
Are you sure you want to remove '{sourceTitle}' from the queue?
</div>
<div className={styles.messageRemove}>
Removing will remove the download and the file(s) from the download client.
</div>
<FormGroup>
<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</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blacklist"
@ -124,7 +142,7 @@ class RemoveQueueItemModal extends Component {
<Button
kind={kinds.DANGER}
onPress={this.onRemoveQueueItemConfirmed}
onPress={this.onRemoveConfirmed}
>
Remove
</Button>
@ -138,6 +156,7 @@ class RemoveQueueItemModal extends Component {
RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

@ -21,13 +21,29 @@ class RemoveQueueItemsModal extends Component {
super(props, context);
this.state = {
remove: true,
blacklist: false,
skipredownload: false
};
}
//
// Listeners
// Control
resetState = function() {
this.setState({
remove: true,
blacklist: false,
skipredownload: false
});
}
//
// Listeners
onRemoveChange = ({ value }) => {
this.setState({ remove: value });
}
onBlacklistChange = ({ value }) => {
this.setState({ blacklist: value });
@ -37,22 +53,15 @@ class RemoveQueueItemsModal extends Component {
this.setState({ skipredownload: value });
}
onRemoveQueueItemConfirmed = () => {
const blacklist = this.state.blacklist;
const skipredownload = this.state.skipredownload;
onRemoveConfirmed = () => {
const state = this.state;
this.setState({
blacklist: false,
skipredownload: false
});
this.props.onRemovePress(blacklist, skipredownload);
this.resetState();
this.props.onRemovePress(state);
}
onModalClose = () => {
this.setState({
blacklist: false,
skipredownload: false
});
this.resetState();
this.props.onModalClose();
}
@ -62,11 +71,11 @@ class RemoveQueueItemsModal extends Component {
render() {
const {
isOpen,
selectedCount
selectedCount,
canIgnore
} = this.props;
const blacklist = this.state.blacklist;
const skipredownload = this.state.skipredownload;
const { remove, blacklist, skipredownload } = this.state;
return (
<Modal
@ -87,7 +96,23 @@ class RemoveQueueItemsModal extends Component {
</div>
<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
type={inputTypes.CHECK}
name="blacklist"
@ -120,7 +145,7 @@ class RemoveQueueItemsModal extends Component {
<Button
kind={kinds.DANGER}
onPress={this.onRemoveQueueItemConfirmed}
onPress={this.onRemoveConfirmed}
>
Remove
</Button>
@ -134,6 +159,7 @@ class RemoveQueueItemsModal extends Component {
RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

@ -157,6 +157,7 @@ export const HEALTH = fasMedkit;
export const HEART = fasHeart;
export const HISTORY = fasHistory;
export const HOUSEKEEPING = fasHome;
export const IGNORE = fasTimesCircle;
export const INFO = fasInfoCircle;
export const INTERACTIVE = fasUser;
export const KEYBOARD = farKeyboard;

@ -179,6 +179,17 @@ export const defaultState = {
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) {
const {
id,
remove,
blacklist,
skipredownload
} = payload;
@ -352,7 +353,7 @@ export const actionHandlers = handleThunks({
dispatch(updateItem({ section: paged, id, isRemoving: true }));
const promise = createAjaxRequest({
url: `/queue/${id}?blacklist=${blacklist}&skipredownload=${skipredownload}`,
url: `/queue/${id}?removeFromClient=${remove}&blacklist=${blacklist}&skipredownload=${skipredownload}`,
method: 'DELETE'
}).request;
@ -368,6 +369,7 @@ export const actionHandlers = handleThunks({
[REMOVE_QUEUE_ITEMS]: function(getState, payload, dispatch) {
const {
ids,
remove,
blacklist,
skipredownload
} = payload;
@ -385,7 +387,7 @@ export const actionHandlers = handleThunks({
]));
const promise = createAjaxRequest({
url: `/queue/bulk?blacklist=${blacklist}&skipredownload=${skipredownload}`,
url: `/queue/bulk?removeFromClient=${remove}&blacklist=${blacklist}&skipredownload=${skipredownload}`,
method: 'DELETE',
dataType: 'json',
data: JSON.stringify({ ids })

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Lidarr.Http;
using Lidarr.Http.Extensions;
using Lidarr.Http.REST;
@ -15,6 +16,7 @@ namespace Lidarr.Api.V1.Queue
private readonly IQueueService _queueService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IFailedDownloadService _failedDownloadService;
private readonly IIgnoredDownloadService _ignoredDownloadService;
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IPendingReleaseService _pendingReleaseService;
private readonly IDownloadService _downloadService;
@ -22,6 +24,7 @@ namespace Lidarr.Api.V1.Queue
public QueueActionModule(IQueueService queueService,
ITrackedDownloadService trackedDownloadService,
IFailedDownloadService failedDownloadService,
IIgnoredDownloadService ignoredDownloadService,
IProvideDownloadClient downloadClientProvider,
IPendingReleaseService pendingReleaseService,
IDownloadService downloadService)
@ -29,6 +32,7 @@ namespace Lidarr.Api.V1.Queue
_queueService = queueService;
_trackedDownloadService = trackedDownloadService;
_failedDownloadService = failedDownloadService;
_ignoredDownloadService = ignoredDownloadService;
_downloadClientProvider = downloadClientProvider;
_pendingReleaseService = pendingReleaseService;
_downloadService = downloadService;
@ -75,10 +79,11 @@ namespace Lidarr.Api.V1.Queue
private object Remove(int id)
{
var removeFromClient = Request.GetBooleanQueryParameter("removeFromClient", true);
var blacklist = Request.GetBooleanQueryParameter("blacklist");
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
var trackedDownload = Remove(id, blacklist, skipReDownload);
var trackedDownload = Remove(id, removeFromClient, blacklist, skipReDownload);
if (trackedDownload != null)
{
@ -90,6 +95,7 @@ namespace Lidarr.Api.V1.Queue
private object Remove()
{
var removeFromClient = Request.GetBooleanQueryParameter("removeFromClient", true);
var blacklist = Request.GetBooleanQueryParameter("blacklist");
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
@ -98,7 +104,7 @@ namespace Lidarr.Api.V1.Queue
foreach (var id in resource.Ids)
{
var trackedDownload = Remove(id, blacklist, skipReDownload);
var trackedDownload = Remove(id, removeFromClient, blacklist, skipReDownload);
if (trackedDownload != null)
{
@ -111,7 +117,7 @@ namespace Lidarr.Api.V1.Queue
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);
@ -129,20 +135,31 @@ namespace Lidarr.Api.V1.Queue
throw new NotFoundException();
}
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
if (downloadClient == null)
if (removeFromClient)
{
throw new BadRequestException();
}
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
if (downloadClient == null)
{
throw new BadRequestException();
}
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadId, true);
}
if (blacklist)
{
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipReDownload);
}
if (!removeFromClient && !blacklist)
{
if (!_ignoredDownloadService.IgnoreDownload(trackedDownload))
{
return null;
}
}
return trackedDownload;
}

@ -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; }
}
}

@ -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)
{
// If the download has already been imported or failed don't track it
if (trackedDownload.State == TrackedDownloadState.Imported || trackedDownload.State == TrackedDownloadState.DownloadFailed)
// 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 ||
trackedDownload.State == TrackedDownloadState.Ignored)
{
return false;
}

@ -41,7 +41,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
ImportPending,
Importing,
ImportFailed,
Imported
Imported,
Ignored
}
public enum TrackedDownloadStatus

@ -252,6 +252,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
return TrackedDownloadState.Imported;
case HistoryEventType.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

@ -41,6 +41,7 @@ namespace NzbDrone.Core.History
TrackFileRenamed = 6,
AlbumImportIncomplete = 7,
DownloadImported = 8,
TrackFileRetagged = 9
TrackFileRetagged = 9,
DownloadIgnored = 10
}
}

@ -39,7 +39,8 @@ namespace NzbDrone.Core.History
IHandle<TrackFileDeletedEvent>,
IHandle<TrackFileRenamedEvent>,
IHandle<TrackFileRetaggedEvent>,
IHandle<ArtistDeletedEvent>
IHandle<ArtistDeletedEvent>,
IHandle<DownloadIgnoredEvent>
{
private readonly IHistoryRepository _historyRepository;
private readonly Logger _logger;
@ -369,6 +370,31 @@ namespace NzbDrone.Core.History
_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)
{
return _historyRepository.Since(date, eventType);

Loading…
Cancel
Save