diff --git a/frontend/src/Activity/Queue/Queue.js b/frontend/src/Activity/Queue/Queue.js index c4463dbff..7d16d75a0 100644 --- a/frontend/src/Activity/Queue/Queue.js +++ b/frontend/src/Activity/Queue/Queue.js @@ -279,6 +279,17 @@ class Queue extends Component { return !!(item && item.seriesId && item.episodeId); }) )} + 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 c8a808456..8e6a0ac2f 100644 --- a/frontend/src/Activity/Queue/QueueRow.js +++ b/frontend/src/Activity/Queue/QueueRow.js @@ -377,6 +377,7 @@ class QueueRow extends Component { isOpen={isRemoveQueueItemModalOpen} sourceTitle={title} canIgnore={!!series} + 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 988748c6e..c2e71a48d 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemModal.js @@ -65,7 +65,8 @@ class RemoveQueueItemModal extends Component { const { isOpen, sourceTitle, - canIgnore + canIgnore, + isPending } = this.props; const { remove, blocklist } = this.state; @@ -88,18 +89,22 @@ class RemoveQueueItemModal extends Component { Are you sure you want to remove '{sourceTitle}' from the queue? - - Remove From Download Client + { + isPending ? + null : + + Remove From Download Client - - + + + } Add Release To Blocklist @@ -137,6 +142,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 5bf18a7d6..b80427ffe 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemsModal.js +++ b/frontend/src/Activity/Queue/RemoveQueueItemsModal.js @@ -66,7 +66,8 @@ class RemoveQueueItemsModal extends Component { const { isOpen, selectedCount, - canIgnore + canIgnore, + allPending } = this.props; const { remove, blocklist } = this.state; @@ -89,18 +90,22 @@ class RemoveQueueItemsModal extends Component { Are you sure you want to remove {selectedCount} item{selectedCount > 1 ? 's' : ''} from the queue? - - Remove From Download Client + { + allPending ? + null : + + Remove From Download Client - - + + + } @@ -140,6 +145,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/NzbDrone.Core/Blocklisting/BlocklistService.cs b/src/NzbDrone.Core/Blocklisting/BlocklistService.cs index b03179045..a4fb72b6d 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 seriesId, ReleaseInfo release); PagingSpec Paged(PagingSpec pagingSpec); + void Block(RemoteEpisode remoteEpisode, string message); void Delete(int id); void Delete(List ids); } @@ -61,6 +62,32 @@ namespace NzbDrone.Core.Blocklisting return _blocklistRepository.GetPaged(pagingSpec); } + public void Block(RemoteEpisode remoteEpisode, string message) + { + var blocklist = new Blocklist + { + SeriesId = remoteEpisode.Series.Id, + EpisodeIds = remoteEpisode.Episodes.Select(e => e.Id).ToList(), + SourceTitle = remoteEpisode.Release.Title, + Quality = remoteEpisode.ParsedEpisodeInfo.Quality, + Date = DateTime.UtcNow, + PublishedDate = remoteEpisode.Release.PublishDate, + Size = remoteEpisode.Release.Size, + Indexer = remoteEpisode.Release.Indexer, + Protocol = remoteEpisode.Release.DownloadProtocol, + Message = message, + Language = remoteEpisode.ParsedEpisodeInfo.Language + }; + + + if (remoteEpisode.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 d0ca1be47..59267fd7e 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -130,13 +130,6 @@ namespace NzbDrone.Core.Download.Pending } } - private ILookup CreateEpisodeLookup(IEnumerable alreadyPending) - { - return alreadyPending.SelectMany(v => v.RemoteEpisode.Episodes - .Select(d => new { Episode = d, PendingRelease = v })) - .ToLookup(v => v.Episode.Id, v => v.PendingRelease); - } - public List GetPending() { var releases = _repository.All().Select(p => p.Release).ToList(); @@ -149,13 +142,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 GetPendingRemoteEpisodes(int seriesId) { return IncludeRemoteEpisodes(_repository.AllBySeriesId(seriesId)).Select(v => v.RemoteEpisode).ToList(); @@ -252,6 +238,20 @@ namespace NzbDrone.Core.Download.Pending .FirstOrDefault(); } + private ILookup CreateEpisodeLookup(IEnumerable alreadyPending) + { + return alreadyPending.SelectMany(v => v.RemoteEpisode.Episodes + .Select(d => new { Episode = d, PendingRelease = v })) + .ToLookup(v => v.Episode.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 IncludeRemoteEpisodes(_repository.All().ToList()); @@ -345,13 +345,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(RemoteEpisode remoteEpisode) { var delayProfile = _delayProfileService.AllForTags(remoteEpisode.Series.Tags).OrderBy(d => d.Order).First(); @@ -446,5 +439,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; + } } } diff --git a/src/Sonarr.Api.V3/Queue/QueueActionModule.cs b/src/Sonarr.Api.V3/Queue/QueueActionModule.cs index 73098fc46..a7fbb0f90 100644 --- a/src/Sonarr.Api.V3/Queue/QueueActionModule.cs +++ b/src/Sonarr.Api.V3/Queue/QueueActionModule.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Nancy; +using NzbDrone.Core.Blocklisting; using NzbDrone.Core.Download; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.TrackedDownloads; @@ -21,6 +22,7 @@ namespace Sonarr.Api.V3.Queue private readonly IProvideDownloadClient _downloadClientProvider; private readonly IPendingReleaseService _pendingReleaseService; private readonly IDownloadService _downloadService; + private readonly IBlocklistService _blocklistService; public QueueActionModule(IQueueService queueService, ITrackedDownloadService trackedDownloadService, @@ -28,7 +30,8 @@ namespace Sonarr.Api.V3.Queue IIgnoredDownloadService ignoredDownloadService, IProvideDownloadClient downloadClientProvider, IPendingReleaseService pendingReleaseService, - IDownloadService downloadService) + IDownloadService downloadService, + IBlocklistService blocklistService) { _queueService = queueService; _trackedDownloadService = trackedDownloadService; @@ -37,6 +40,7 @@ namespace Sonarr.Api.V3.Queue _downloadClientProvider = downloadClientProvider; _pendingReleaseService = pendingReleaseService; _downloadService = downloadService; + _blocklistService = blocklistService; Post(@"/grab/(?[\d]{1,10})", x => Grab((int)x.Id)); Post("/grab/bulk", x => Grab()); @@ -126,6 +130,7 @@ namespace Sonarr.Api.V3.Queue if (pendingRelease != null) { + _blocklistService.Block(pendingRelease.RemoteEpisode, "Pending release manually blocklisted"); _pendingReleaseService.RemovePendingQueueItems(pendingRelease.Id); return null;