Add: option to skip automatic redownload when removing from queue (#734)

* Add: option to skip automatic redownload when removing from queue

* Add tests for RedownloadFailedDownloadService

* Fix formatting

* Make re-download dialog conditional
pull/6/head
ta264 6 years ago committed by GitHub
parent 0f6a3bca0c
commit 8cd9ab4a9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -107,8 +107,8 @@ class Queue extends Component {
this.setState({ isConfirmRemoveModalOpen: true }); this.setState({ isConfirmRemoveModalOpen: true });
} }
onRemoveSelectedConfirmed = (blacklist) => { onRemoveSelectedConfirmed = (blacklist, skipredownload) => {
this.props.onRemoveSelectedPress(this.getSelectedIds(), blacklist); this.props.onRemoveSelectedPress(this.getSelectedIds(), blacklist, skipredownload);
this.setState({ isConfirmRemoveModalOpen: false }); this.setState({ isConfirmRemoveModalOpen: false });
} }

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

@ -42,8 +42,8 @@ class QueueRow extends Component {
this.setState({ isRemoveQueueItemModalOpen: true }); this.setState({ isRemoveQueueItemModalOpen: true });
} }
onRemoveQueueItemModalConfirmed = (blacklist) => { onRemoveQueueItemModalConfirmed = (blacklist, skipredownload) => {
this.props.onRemoveQueueItemPress(blacklist); this.props.onRemoveQueueItemPress(blacklist, skipredownload);
this.setState({ isRemoveQueueItemModalOpen: false }); this.setState({ isRemoveQueueItemModalOpen: false });
} }

@ -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 = (blacklist, skipredownload) => {
this.props.removeQueueItem({ id: this.props.id, blacklist }); this.props.removeQueueItem({ id: this.props.id, blacklist, skipredownload });
} }
// //

@ -21,7 +21,8 @@ class RemoveQueueItemModal extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
blacklist: false blacklist: false,
skipredownload: false
}; };
} }
@ -32,15 +33,26 @@ class RemoveQueueItemModal extends Component {
this.setState({ blacklist: value }); this.setState({ blacklist: value });
} }
onSkipReDownloadChange = ({ value }) => {
this.setState({ skipredownload: value });
}
onRemoveQueueItemConfirmed = () => { onRemoveQueueItemConfirmed = () => {
const blacklist = this.state.blacklist; const blacklist = this.state.blacklist;
const skipredownload = this.state.skipredownload;
this.setState({ blacklist: false }); this.setState({
this.props.onRemovePress(blacklist); blacklist: false,
skipredownload: false
});
this.props.onRemovePress(blacklist, skipredownload);
} }
onModalClose = () => { onModalClose = () => {
this.setState({ blacklist: false }); this.setState({
blacklist: false,
skipredownload: false
});
this.props.onModalClose(); this.props.onModalClose();
} }
@ -54,6 +66,7 @@ class RemoveQueueItemModal extends Component {
} = this.props; } = this.props;
const blacklist = this.state.blacklist; const blacklist = this.state.blacklist;
const skipredownload = this.state.skipredownload;
return ( return (
<Modal <Modal
@ -79,11 +92,25 @@ class RemoveQueueItemModal extends Component {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="blacklist" name="blacklist"
value={blacklist} value={blacklist}
helpText="Prevents Lidarr from automatically grabbing these files again" helpText="Prevents Lidarr from automatically grabbing this release again"
onChange={this.onBlacklistChange} onChange={this.onBlacklistChange}
/> />
</FormGroup> </FormGroup>
{
blacklist &&
<FormGroup>
<FormLabel>Skip Redownload</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipredownload"
value={skipredownload}
helpText="Prevents Lidarr from trying download an alternative release for this item"
onChange={this.onSkipReDownloadChange}
/>
</FormGroup>
}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>

@ -21,7 +21,8 @@ class RemoveQueueItemsModal extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
blacklist: false blacklist: false,
skipredownload: false
}; };
} }
@ -32,15 +33,26 @@ class RemoveQueueItemsModal extends Component {
this.setState({ blacklist: value }); this.setState({ blacklist: value });
} }
onSkipReDownloadChange = ({ value }) => {
this.setState({ skipredownload: value });
}
onRemoveQueueItemConfirmed = () => { onRemoveQueueItemConfirmed = () => {
const blacklist = this.state.blacklist; const blacklist = this.state.blacklist;
const skipredownload = this.state.skipredownload;
this.setState({ blacklist: false }); this.setState({
this.props.onRemovePress(blacklist); blacklist: false,
skipredownload: false
});
this.props.onRemovePress(blacklist, skipredownload);
} }
onModalClose = () => { onModalClose = () => {
this.setState({ blacklist: false }); this.setState({
blacklist: false,
skipredownload: false
});
this.props.onModalClose(); this.props.onModalClose();
} }
@ -54,6 +66,7 @@ class RemoveQueueItemsModal extends Component {
} = this.props; } = this.props;
const blacklist = this.state.blacklist; const blacklist = this.state.blacklist;
const skipredownload = this.state.skipredownload;
return ( return (
<Modal <Modal
@ -79,11 +92,25 @@ class RemoveQueueItemsModal extends Component {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="blacklist" name="blacklist"
value={blacklist} value={blacklist}
helpText="Prevents Lidarr from automatically grabbing this release again" helpText="Prevents Lidarr from automatically grabbing these files again"
onChange={this.onBlacklistChange} onChange={this.onBlacklistChange}
/> />
</FormGroup> </FormGroup>
{
blacklist &&
<FormGroup>
<FormLabel>Skip Redownload</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipredownload"
value={skipredownload}
helpText="Prevents Lidarr from trying download alternative releases for the removed items"
onChange={this.onSkipReDownloadChange}
/>
</FormGroup>
}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>

@ -351,13 +351,14 @@ export const actionHandlers = handleThunks({
[REMOVE_QUEUE_ITEM]: function(getState, payload, dispatch) { [REMOVE_QUEUE_ITEM]: function(getState, payload, dispatch) {
const { const {
id, id,
blacklist blacklist,
skipredownload
} = 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}?blacklist=${blacklist}&skipredownload=${skipredownload}`,
method: 'DELETE' method: 'DELETE'
}).request; }).request;
@ -373,7 +374,8 @@ export const actionHandlers = handleThunks({
[REMOVE_QUEUE_ITEMS]: function(getState, payload, dispatch) { [REMOVE_QUEUE_ITEMS]: function(getState, payload, dispatch) {
const { const {
ids, ids,
blacklist blacklist,
skipredownload
} = payload; } = payload;
dispatch(batchActions([ dispatch(batchActions([
@ -389,7 +391,7 @@ export const actionHandlers = handleThunks({
])); ]));
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url: `/queue/bulk?blacklist=${blacklist}`, url: `/queue/bulk?blacklist=${blacklist}&skipredownload=${skipredownload}`,
method: 'DELETE', method: 'DELETE',
dataType: 'json', dataType: 'json',
data: JSON.stringify({ ids }) data: JSON.stringify({ ids })

@ -77,8 +77,9 @@ namespace Lidarr.Api.V1.Queue
private Response Remove(int id) private Response Remove(int id)
{ {
var blacklist = Request.GetBooleanQueryParameter("blacklist"); var blacklist = Request.GetBooleanQueryParameter("blacklist");
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
var trackedDownload = Remove(id, blacklist); var trackedDownload = Remove(id, blacklist, skipReDownload);
if (trackedDownload != null) if (trackedDownload != null)
{ {
@ -91,13 +92,14 @@ namespace Lidarr.Api.V1.Queue
private Response Remove() private Response Remove()
{ {
var blacklist = Request.GetBooleanQueryParameter("blacklist"); var blacklist = Request.GetBooleanQueryParameter("blacklist");
var skipReDownload = Request.GetBooleanQueryParameter("skipredownload");
var resource = Request.Body.FromJson<QueueBulkResource>(); var resource = Request.Body.FromJson<QueueBulkResource>();
var trackedDownloadIds = new List<string>(); var trackedDownloadIds = new List<string>();
foreach (var id in resource.Ids) foreach (var id in resource.Ids)
{ {
var trackedDownload = Remove(id, blacklist); var trackedDownload = Remove(id, blacklist, skipReDownload);
if (trackedDownload != null) if (trackedDownload != null)
{ {
@ -110,7 +112,7 @@ namespace Lidarr.Api.V1.Queue
return new object().AsResponse(); return new object().AsResponse();
} }
private TrackedDownload Remove(int id, bool blacklist) private TrackedDownload Remove(int id, bool blacklist, bool skipReDownload)
{ {
var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id); var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id);
@ -139,7 +141,7 @@ namespace Lidarr.Api.V1.Queue
if (blacklist) if (blacklist)
{ {
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId); _failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipReDownload);
} }
return trackedDownload; return trackedDownload;

@ -0,0 +1,128 @@
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Messaging.Commands;
using Moq;
using System.Collections.Generic;
using NzbDrone.Core.Music;
using FizzWare.NBuilder;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch;
namespace NzbDrone.Core.Test.Download
{
[TestFixture]
public class RedownloadFailedDownloadServiceFixture : CoreTest<RedownloadFailedDownloadService>
{
[SetUp]
public void Setup()
{
Mocker.GetMock<IConfigService>()
.Setup(x => x.AutoRedownloadFailed)
.Returns(true);
Mocker.GetMock<IAlbumService>()
.Setup(x => x.GetAlbumsByArtist(It.IsAny<int>()))
.Returns(Builder<Album>.CreateListOfSize(3).Build() as List<Album>);
}
[Test]
public void should_skip_redownload_if_event_has_skipredownload_set()
{
var failedEvent = new DownloadFailedEvent {
ArtistId = 1,
AlbumIds = new List<int> { 1 },
SkipReDownload = true
};
Subject.HandleAsync(failedEvent);
Mocker.GetMock<IManageCommandQueue>()
.Verify(x => x.Push(It.IsAny<Command>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
Times.Never());
}
[Test]
public void should_skip_redownload_if_redownload_failed_disabled()
{
var failedEvent = new DownloadFailedEvent {
ArtistId = 1,
AlbumIds = new List<int> { 1 }
};
Mocker.GetMock<IConfigService>()
.Setup(x => x.AutoRedownloadFailed)
.Returns(false);
Subject.HandleAsync(failedEvent);
Mocker.GetMock<IManageCommandQueue>()
.Verify(x => x.Push(It.IsAny<Command>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
Times.Never());
}
[Test]
public void should_redownload_album_on_failure()
{
var failedEvent = new DownloadFailedEvent {
ArtistId = 1,
AlbumIds = new List<int> { 2 }
};
Subject.HandleAsync(failedEvent);
Mocker.GetMock<IManageCommandQueue>()
.Verify(x => x.Push(It.Is<AlbumSearchCommand>(c => c.AlbumIds.Count == 1 &&
c.AlbumIds[0] == 2),
It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
Times.Once());
Mocker.GetMock<IManageCommandQueue>()
.Verify(x => x.Push(It.IsAny<ArtistSearchCommand>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
Times.Never());
}
[Test]
public void should_redownload_multiple_albums_on_failure()
{
var failedEvent = new DownloadFailedEvent {
ArtistId = 1,
AlbumIds = new List<int> { 2, 3 }
};
Subject.HandleAsync(failedEvent);
Mocker.GetMock<IManageCommandQueue>()
.Verify(x => x.Push(It.Is<AlbumSearchCommand>(c => c.AlbumIds.Count == 2 &&
c.AlbumIds[0] == 2 &&
c.AlbumIds[1] == 3),
It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
Times.Once());
Mocker.GetMock<IManageCommandQueue>()
.Verify(x => x.Push(It.IsAny<ArtistSearchCommand>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
Times.Never());
}
[Test]
public void should_redownload_artist_on_failure()
{
// note that artist is set to have 3 albums in setup
var failedEvent = new DownloadFailedEvent {
ArtistId = 2,
AlbumIds = new List<int> { 1, 2, 3 }
};
Subject.HandleAsync(failedEvent);
Mocker.GetMock<IManageCommandQueue>()
.Verify(x => x.Push(It.Is<ArtistSearchCommand>(c => c.ArtistId == failedEvent.ArtistId),
It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
Times.Once());
Mocker.GetMock<IManageCommandQueue>()
.Verify(x => x.Push(It.IsAny<AlbumSearchCommand>(), It.IsAny<CommandPriority>(), It.IsAny<CommandTrigger>()),
Times.Never());
}
}
}

@ -139,6 +139,7 @@
<Compile Include="Download\DownloadClientTests\VuzeTests\VuzeFixture.cs" /> <Compile Include="Download\DownloadClientTests\VuzeTests\VuzeFixture.cs" />
<Compile Include="Download\DownloadServiceFixture.cs" /> <Compile Include="Download\DownloadServiceFixture.cs" />
<Compile Include="Download\FailedDownloadServiceFixture.cs" /> <Compile Include="Download\FailedDownloadServiceFixture.cs" />
<Compile Include="Download\RedownloadFailedDownloadServiceFixture.cs" />
<Compile Include="Download\NzbValidationServiceFixture.cs" /> <Compile Include="Download\NzbValidationServiceFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\PendingReleaseServiceFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\RemovePendingFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\RemovePendingFixture.cs" />

@ -23,5 +23,6 @@ namespace NzbDrone.Core.Download
public Dictionary<string, string> Data { get; set; } public Dictionary<string, string> Data { get; set; }
public TrackedDownload TrackedDownload { get; set; } public TrackedDownload TrackedDownload { get; set; }
public Language Language { get; set; } public Language Language { get; set; }
public bool SkipReDownload { get; set; }
} }
} }

@ -9,8 +9,8 @@ namespace NzbDrone.Core.Download
{ {
public interface IFailedDownloadService public interface IFailedDownloadService
{ {
void MarkAsFailed(int historyId); void MarkAsFailed(int historyId, bool skipReDownload = false);
void MarkAsFailed(string downloadId); void MarkAsFailed(string downloadId, bool skipReDownload = false);
void Process(TrackedDownload trackedDownload); void Process(TrackedDownload trackedDownload);
} }
@ -26,14 +26,14 @@ namespace NzbDrone.Core.Download
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
} }
public void MarkAsFailed(int historyId) public void MarkAsFailed(int historyId, bool skipReDownload = false)
{ {
var history = _historyService.Get(historyId); var history = _historyService.Get(historyId);
var downloadId = history.DownloadId; var downloadId = history.DownloadId;
if (downloadId.IsNullOrWhiteSpace()) if (downloadId.IsNullOrWhiteSpace())
{ {
PublishDownloadFailedEvent(new List<History.History> { history }, "Manually marked as failed"); PublishDownloadFailedEvent(new List<History.History> { history }, "Manually marked as failed", skipReDownload: skipReDownload);
} }
else else
{ {
@ -42,13 +42,13 @@ namespace NzbDrone.Core.Download
} }
} }
public void MarkAsFailed(string downloadId) public void MarkAsFailed(string downloadId, bool skipReDownload = false)
{ {
var history = _historyService.Find(downloadId, HistoryEventType.Grabbed); var history = _historyService.Find(downloadId, HistoryEventType.Grabbed);
if (history.Any()) if (history.Any())
{ {
PublishDownloadFailedEvent(history, "Manually marked as failed"); PublishDownloadFailedEvent(history, "Manually marked as failed", skipReDownload: skipReDownload);
} }
} }
@ -81,7 +81,7 @@ namespace NzbDrone.Core.Download
} }
} }
private void PublishDownloadFailedEvent(List<History.History> historyItems, string message, TrackedDownload trackedDownload = null) private void PublishDownloadFailedEvent(List<History.History> historyItems, string message, TrackedDownload trackedDownload = null, bool skipReDownload = false)
{ {
var historyItem = historyItems.First(); var historyItem = historyItems.First();
@ -96,7 +96,8 @@ namespace NzbDrone.Core.Download
Message = message, Message = message,
Data = historyItem.Data, Data = historyItem.Data,
TrackedDownload = trackedDownload, TrackedDownload = trackedDownload,
Language = historyItem.Language Language = historyItem.Language,
SkipReDownload = skipReDownload
}; };
_eventAggregator.PublishEvent(downloadFailedEvent); _eventAggregator.PublishEvent(downloadFailedEvent);

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.IndexerSearch;
@ -28,6 +28,12 @@ namespace NzbDrone.Core.Download
public void HandleAsync(DownloadFailedEvent message) public void HandleAsync(DownloadFailedEvent message)
{ {
if (message.SkipReDownload)
{
_logger.Debug("Skip redownloading requested by user");
return;
}
if (!_configService.AutoRedownloadFailed) if (!_configService.AutoRedownloadFailed)
{ {
_logger.Debug("Auto redownloading failed albums is disabled"); _logger.Debug("Auto redownloading failed albums is disabled");

Loading…
Cancel
Save