From 4ea5c68216c5f2c9b80971776049599ee3c42f61 Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 20 Oct 2023 20:36:51 -0500 Subject: [PATCH] Fixed: Blocklisting pending releases Closes #2357 Closes #2478 Closes #3247 Co-Authored-By: Mark McDowall --- frontend/src/Activity/Queue/Queue.js | 11 +++ frontend/src/Activity/Queue/QueueRow.js | 1 + .../Activity/Queue/RemoveQueueItemModal.js | 32 +++++---- .../Activity/Queue/RemoveQueueItemsModal.js | 32 +++++---- src/Lidarr.Api.V1/Queue/QueueController.cs | 72 +++++++++++++------ .../Blocklisting/BlocklistService.cs | 25 +++++++ .../Download/Pending/PendingReleaseService.cs | 42 +++++------ 7 files changed, 144 insertions(+), 71 deletions(-) diff --git a/frontend/src/Activity/Queue/Queue.js b/frontend/src/Activity/Queue/Queue.js index b73ce0ad7..27d5f9e26 100644 --- a/frontend/src/Activity/Queue/Queue.js +++ b/frontend/src/Activity/Queue/Queue.js @@ -299,6 +299,17 @@ class Queue extends Component { return !!(item && item.artistId && item.albumId); }) )} + allPending={isConfirmRemoveModalOpen && ( + selectedIds.every((id) => { + const item = items.find((i) => i.id === id); + + if (!item) { + return false; + } + + return item.status === 'delay' || item.status === 'downloadClientUnavailable'; + }) + )} onRemovePress={this.onRemoveSelectedConfirmed} onModalClose={this.onConfirmRemoveModalClose} /> diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js index 08634d9f7..4c2829f55 100644 --- a/frontend/src/Activity/Queue/QueueRow.js +++ b/frontend/src/Activity/Queue/QueueRow.js @@ -394,6 +394,7 @@ class QueueRow extends Component { isOpen={isRemoveQueueItemModalOpen} sourceTitle={title} canIgnore={!!artist} + isPending={isPending} onRemovePress={this.onRemoveQueueItemModalConfirmed} onModalClose={this.onRemoveQueueItemModalClose} /> diff --git a/frontend/src/Activity/Queue/RemoveQueueItemModal.js b/frontend/src/Activity/Queue/RemoveQueueItemModal.js index d9e4dd7f6..4509791fc 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemModal.js @@ -72,7 +72,8 @@ class RemoveQueueItemModal extends Component { const { isOpen, sourceTitle, - canIgnore + canIgnore, + isPending } = this.props; const { removeFromClient, blocklist, skipRedownload } = this.state; @@ -95,20 +96,22 @@ class RemoveQueueItemModal extends Component { Are you sure you want to remove '{sourceTitle}' from the queue? - - - {translate('RemoveFromDownloadClient')} - + { + isPending ? + null : + + {translate('RemoveFromDownloadClient')} - - + + + } @@ -164,6 +167,7 @@ RemoveQueueItemModal.propTypes = { isOpen: PropTypes.bool.isRequired, sourceTitle: PropTypes.string.isRequired, canIgnore: PropTypes.bool.isRequired, + isPending: PropTypes.bool.isRequired, onRemovePress: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js index 3b9164e68..f607161b0 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js @@ -73,7 +73,8 @@ class RemoveQueueItemsModal extends Component { const { isOpen, selectedCount, - canIgnore + canIgnore, + allPending } = this.props; const { removeFromClient, blocklist, skipRedownload } = this.state; @@ -96,20 +97,22 @@ class RemoveQueueItemsModal extends Component { {selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', [selectedCount]) : translate('RemoveSelectedItemQueueMessageText')} - - - {translate('RemoveFromDownloadClient')} - + { + allPending ? + null : + + {translate('RemoveFromDownloadClient')} - - + + + } @@ -165,6 +168,7 @@ RemoveQueueItemsModal.propTypes = { isOpen: PropTypes.bool.isRequired, selectedCount: PropTypes.number.isRequired, canIgnore: PropTypes.bool.isRequired, + allPending: PropTypes.bool.isRequired, onRemovePress: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/src/Lidarr.Api.V1/Queue/QueueController.cs b/src/Lidarr.Api.V1/Queue/QueueController.cs index 4bc5af52a..ddc6cafe8 100644 --- a/src/Lidarr.Api.V1/Queue/QueueController.cs +++ b/src/Lidarr.Api.V1/Queue/QueueController.cs @@ -7,6 +7,7 @@ using Lidarr.Http.REST; using Lidarr.Http.REST.Attributes; using Microsoft.AspNetCore.Mvc; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Download; @@ -17,6 +18,7 @@ using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Queue; using NzbDrone.SignalR; +using Sentry.Protocol; namespace Lidarr.Api.V1.Queue { @@ -32,6 +34,7 @@ namespace Lidarr.Api.V1.Queue private readonly IFailedDownloadService _failedDownloadService; private readonly IIgnoredDownloadService _ignoredDownloadService; private readonly IProvideDownloadClient _downloadClientProvider; + private readonly IBlocklistService _blocklistService; public QueueController(IBroadcastSignalRMessage broadcastSignalRMessage, IQueueService queueService, @@ -40,7 +43,8 @@ namespace Lidarr.Api.V1.Queue ITrackedDownloadService trackedDownloadService, IFailedDownloadService failedDownloadService, IIgnoredDownloadService ignoredDownloadService, - IProvideDownloadClient downloadClientProvider) + IProvideDownloadClient downloadClientProvider, + IBlocklistService blocklistService) : base(broadcastSignalRMessage) { _queueService = queueService; @@ -49,6 +53,7 @@ namespace Lidarr.Api.V1.Queue _failedDownloadService = failedDownloadService; _ignoredDownloadService = ignoredDownloadService; _downloadClientProvider = downloadClientProvider; + _blocklistService = blocklistService; _qualityComparer = new QualityModelComparer(qualityProfileService.GetDefaultProfile(string.Empty)); } @@ -62,29 +67,62 @@ namespace Lidarr.Api.V1.Queue [RestDeleteById] public void RemoveAction(int id, bool removeFromClient = true, bool blocklist = false, bool skipRedownload = false) { - var trackedDownload = Remove(id, removeFromClient, blocklist, skipRedownload); + var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id); - if (trackedDownload != null) + if (pendingRelease != null) + { + Remove(pendingRelease); + + return; + } + + var trackedDownload = GetTrackedDownload(id); + + if (trackedDownload == null) { - _trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId); + throw new NotFoundException(); } + + Remove(trackedDownload, removeFromClient, blocklist, skipRedownload); + _trackedDownloadService.StopTracking(trackedDownload.DownloadItem.DownloadId); } [HttpDelete("bulk")] public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] bool removeFromClient = true, [FromQuery] bool blocklist = false, [FromQuery] bool skipRedownload = false) { var trackedDownloadIds = new List(); + var pendingToRemove = new List(); + var trackedToRemove = new List(); foreach (var id in resource.Ids) { - var trackedDownload = Remove(id, removeFromClient, blocklist, skipRedownload); + var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id); + + if (pendingRelease != null) + { + pendingToRemove.Add(pendingRelease); + continue; + } + + var trackedDownload = GetTrackedDownload(id); if (trackedDownload != null) { - trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId); + trackedToRemove.Add(trackedDownload); } } + foreach (var pendingRelease in pendingToRemove.DistinctBy(p => p.Id)) + { + Remove(pendingRelease); + } + + foreach (var trackedDownload in trackedToRemove.DistinctBy(t => t.DownloadItem.DownloadId)) + { + Remove(trackedDownload, removeFromClient, blocklist, skipRedownload); + trackedDownloadIds.Add(trackedDownload.DownloadItem.DownloadId); + } + _trackedDownloadService.StopTracking(trackedDownloadIds); return new { }; @@ -195,24 +233,14 @@ namespace Lidarr.Api.V1.Queue } } - private TrackedDownload Remove(int id, bool removeFromClient, bool blocklist, bool skipRedownload) + private void Remove(NzbDrone.Core.Queue.Queue pendingRelease) { - var pendingRelease = _pendingReleaseService.FindPendingQueueItem(id); - - if (pendingRelease != null) - { - _pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id); - - return null; - } - - var trackedDownload = GetTrackedDownload(id); - - if (trackedDownload == null) - { - throw new NotFoundException(); - } + _blocklistService.Block(pendingRelease.RemoteAlbum, "Pending release manually blocklisted"); + _pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id); + } + private TrackedDownload Remove(TrackedDownload trackedDownload, bool removeFromClient, bool blocklist, bool skipRedownload) + { if (removeFromClient) { var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient); diff --git a/src/NzbDrone.Core/Blocklisting/BlocklistService.cs b/src/NzbDrone.Core/Blocklisting/BlocklistService.cs index 707147b73..783d94782 100644 --- a/src/NzbDrone.Core/Blocklisting/BlocklistService.cs +++ b/src/NzbDrone.Core/Blocklisting/BlocklistService.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Blocklisting { bool Blocklisted(int artistId, ReleaseInfo release); PagingSpec Paged(PagingSpec pagingSpec); + void Block(RemoteAlbum remoteAlbum, string message); void Delete(int id); void Delete(List ids); } @@ -66,6 +67,30 @@ namespace NzbDrone.Core.Blocklisting return _blocklistRepository.GetPaged(pagingSpec); } + public void Block(RemoteAlbum remoteAlbum, string message) + { + var blocklist = new Blocklist + { + ArtistId = remoteAlbum.Artist.Id, + AlbumIds = remoteAlbum.Albums.Select(e => e.Id).ToList(), + SourceTitle = remoteAlbum.Release.Title, + Quality = remoteAlbum.ParsedAlbumInfo.Quality, + Date = DateTime.UtcNow, + PublishedDate = remoteAlbum.Release.PublishDate, + Size = remoteAlbum.Release.Size, + Indexer = remoteAlbum.Release.Indexer, + Protocol = remoteAlbum.Release.DownloadProtocol, + Message = message, + }; + + if (remoteAlbum.Release is TorrentInfo torrentRelease) + { + blocklist.TorrentInfoHash = torrentRelease.InfoHash; + } + + _blocklistRepository.Insert(blocklist); + } + public void Delete(int id) { _blocklistRepository.Delete(id); diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index ace9a46d5..b3a69d16c 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -137,13 +137,6 @@ namespace NzbDrone.Core.Download.Pending } } - private ILookup CreateAlbumLookup(IEnumerable alreadyPending) - { - return alreadyPending.SelectMany(v => v.RemoteAlbum.Albums - .Select(d => new { Album = d, PendingRelease = v })) - .ToLookup(v => v.Album.Id, v => v.PendingRelease); - } - public List GetPending() { var releases = _repository.All().Select(p => @@ -163,13 +156,6 @@ namespace NzbDrone.Core.Download.Pending return releases; } - private List FilterBlockedIndexers(List releases) - { - var blockedIndexers = new HashSet(_indexerStatusService.GetBlockedProviders().Select(v => v.ProviderId)); - - return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList(); - } - public List GetPendingRemoteAlbums(int artistId) { return IncludeRemoteAlbums(_repository.AllByArtistId(artistId)).Select(v => v.RemoteAlbum).ToList(); @@ -263,6 +249,20 @@ namespace NzbDrone.Core.Download.Pending .MaxBy(p => p.Release.AgeHours); } + private ILookup CreateAlbumLookup(IEnumerable alreadyPending) + { + return alreadyPending.SelectMany(v => v.RemoteAlbum.Albums + .Select(d => new { Album = d, PendingRelease = v })) + .ToLookup(v => v.Album.Id, v => v.PendingRelease); + } + + private List FilterBlockedIndexers(List releases) + { + var blockedIndexers = new HashSet(_indexerStatusService.GetBlockedProviders().Select(v => v.ProviderId)); + + return releases.Where(release => !blockedIndexers.Contains(release.IndexerId)).ToList(); + } + private List GetPendingReleases() { return IncludeRemoteAlbums(_repository.All().ToList()); @@ -354,13 +354,6 @@ namespace NzbDrone.Core.Download.Pending _eventAggregator.PublishEvent(new PendingReleasesUpdatedEvent()); } - private static Func MatchingReleasePredicate(ReleaseInfo release) - { - return p => p.Title == release.Title && - p.Release.PublishDate == release.PublishDate && - p.Release.Indexer == release.Indexer; - } - private int GetDelay(RemoteAlbum remoteAlbum) { var delayProfile = _delayProfileService.AllForTags(remoteAlbum.Artist.Tags).OrderBy(d => d.Order).First(); @@ -455,5 +448,12 @@ namespace NzbDrone.Core.Download.Pending { RemoveRejected(message.ProcessedDecisions.Rejected); } + + private static Func MatchingReleasePredicate(ReleaseInfo release) + { + return p => p.Title == release.Title && + p.Release.PublishDate == release.PublishDate && + p.Release.Indexer == release.Indexer; + } } }