1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-17 21:26:22 -04:00

Override release grab modal

New: Option to override release and grab
New: Option to select download client when multiple of the same type are configured

(cherry picked from commit 07f0fbf9a51d54e44681fd0f74df4e048bff561a)
This commit is contained in:
Mark McDowall
2023-03-27 16:49:12 -07:00
committed by Bogdan
parent 07b69e665d
commit 9b4f80535e
31 changed files with 1185 additions and 405 deletions

View File

@@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), null), Times.Once());
}
[Test]
@@ -93,7 +93,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), null), Times.Once());
}
[Test]
@@ -110,7 +110,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie2));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), null), Times.Once());
}
[Test]
@@ -175,7 +175,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovie));
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>())).Throws(new Exception());
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>(), null)).Throws(new Exception());
var result = await Subject.ProcessDecisions(decisions);
@@ -204,7 +204,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie, new Rejection("Failure!", RejectionType.Temporary)));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Never());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), null), Times.Never());
}
[Test]
@@ -242,11 +242,11 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
decisions.Add(new DownloadDecision(remoteMovie));
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>()))
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>(), null))
.Throws(new DownloadClientUnavailableException("Download client failed"));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>()), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.IsAny<RemoteMovie>(), null), Times.Once());
}
[Test]
@@ -259,12 +259,12 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
decisions.Add(new DownloadDecision(remoteMovie2));
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.Is<RemoteMovie>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)))
Mocker.GetMock<IDownloadService>().Setup(s => s.DownloadReport(It.Is<RemoteMovie>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet), null))
.Throws(new DownloadClientUnavailableException("Download client failed"));
await Subject.ProcessDecisions(decisions);
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteMovie>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet)), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteMovie>(r => r.Release.DownloadProtocol == DownloadProtocol.Torrent)), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteMovie>(r => r.Release.DownloadProtocol == DownloadProtocol.Usenet), null), Times.Once());
Mocker.GetMock<IDownloadService>().Verify(v => v.DownloadReport(It.Is<RemoteMovie>(r => r.Release.DownloadProtocol == DownloadProtocol.Torrent), null), Times.Once());
}
[Test]
@@ -276,7 +276,7 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
decisions.Add(new DownloadDecision(remoteMovie));
Mocker.GetMock<IDownloadService>()
.Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>()))
.Setup(s => s.DownloadReport(It.IsAny<RemoteMovie>(), null))
.Throws(new ReleaseUnavailableException(remoteMovie.Release, "That 404 Error is not just a Quirk"));
var result = await Subject.ProcessDecisions(decisions);

View File

@@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Download
var mock = WithUsenetClient();
mock.Setup(s => s.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()));
await Subject.DownloadReport(_parseResult);
await Subject.DownloadReport(_parseResult, null);
VerifyEventPublished<MovieGrabbedEvent>();
}
@@ -87,7 +87,7 @@ namespace NzbDrone.Core.Test.Download
var mock = WithUsenetClient();
mock.Setup(s => s.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()));
await Subject.DownloadReport(_parseResult);
await Subject.DownloadReport(_parseResult, null);
mock.Verify(s => s.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()), Times.Once());
}
@@ -99,7 +99,7 @@ namespace NzbDrone.Core.Test.Download
mock.Setup(s => s.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()))
.Throws(new WebException());
Assert.ThrowsAsync<WebException>(async () => await Subject.DownloadReport(_parseResult));
Assert.ThrowsAsync<WebException>(async () => await Subject.DownloadReport(_parseResult, null));
VerifyEventNotPublished<MovieGrabbedEvent>();
}
@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Test.Download
throw new ReleaseDownloadException(v.Release, "Error", new WebException());
});
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult, null));
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Once());
@@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test.Download
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
});
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult, null));
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), TimeSpan.FromMinutes(5.0)), Times.Once());
@@ -154,7 +154,7 @@ namespace NzbDrone.Core.Test.Download
throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response));
});
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult));
Assert.ThrowsAsync<ReleaseDownloadException>(async () => await Subject.DownloadReport(_parseResult, null));
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(),
@@ -168,7 +168,7 @@ namespace NzbDrone.Core.Test.Download
mock.Setup(s => s.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()))
.Throws(new DownloadClientException("Some Error"));
Assert.ThrowsAsync<DownloadClientException>(async () => await Subject.DownloadReport(_parseResult));
Assert.ThrowsAsync<DownloadClientException>(async () => await Subject.DownloadReport(_parseResult, null));
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
@@ -184,7 +184,7 @@ namespace NzbDrone.Core.Test.Download
throw new ReleaseUnavailableException(v.Release, "Error", new WebException());
});
Assert.ThrowsAsync<ReleaseUnavailableException>(async () => await Subject.DownloadReport(_parseResult));
Assert.ThrowsAsync<ReleaseUnavailableException>(async () => await Subject.DownloadReport(_parseResult, null));
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(), It.IsAny<TimeSpan>()), Times.Never());
@@ -193,7 +193,7 @@ namespace NzbDrone.Core.Test.Download
[Test]
public void should_not_attempt_download_if_client_isnt_configured()
{
Assert.ThrowsAsync<DownloadClientUnavailableException>(async () => await Subject.DownloadReport(_parseResult));
Assert.ThrowsAsync<DownloadClientUnavailableException>(async () => await Subject.DownloadReport(_parseResult, null));
Mocker.GetMock<IDownloadClient>().Verify(c => c.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()), Times.Never());
VerifyEventNotPublished<MovieGrabbedEvent>();
@@ -215,7 +215,7 @@ namespace NzbDrone.Core.Test.Download
}
});
await Subject.DownloadReport(_parseResult);
await Subject.DownloadReport(_parseResult, null);
Mocker.GetMock<IDownloadClientStatusService>().Verify(c => c.GetBlockedProviders(), Times.Never());
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()), Times.Once());
@@ -228,7 +228,7 @@ namespace NzbDrone.Core.Test.Download
var mockTorrent = WithTorrentClient();
var mockUsenet = WithUsenetClient();
await Subject.DownloadReport(_parseResult);
await Subject.DownloadReport(_parseResult, null);
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()), Times.Never());
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()), Times.Once());
@@ -242,7 +242,7 @@ namespace NzbDrone.Core.Test.Download
_parseResult.Release.DownloadProtocol = DownloadProtocol.Torrent;
await Subject.DownloadReport(_parseResult);
await Subject.DownloadReport(_parseResult, null);
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()), Times.Once());
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteMovie>(), It.IsAny<IIndexer>()), Times.Never());

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Download
{
public interface IDownloadService
{
Task DownloadReport(RemoteMovie remoteMovie);
Task DownloadReport(RemoteMovie remoteMovie, int? downloadClientId);
}
public class DownloadService : IDownloadService
@@ -50,13 +50,15 @@ namespace NzbDrone.Core.Download
_logger = logger;
}
public async Task DownloadReport(RemoteMovie remoteMovie)
public async Task DownloadReport(RemoteMovie remoteMovie, int? downloadClientId)
{
var filterBlockedClients = remoteMovie.Release.PendingReleaseReason == PendingReleaseReason.DownloadClientUnavailable;
var tags = remoteMovie.Movie?.Tags;
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteMovie.Release.DownloadProtocol, remoteMovie.Release.IndexerId, filterBlockedClients, tags);
var downloadClient = downloadClientId.HasValue
? _downloadClientProvider.Get(downloadClientId.Value)
: _downloadClientProvider.GetDownloadClient(remoteMovie.Release.DownloadProtocol, remoteMovie.Release.IndexerId, filterBlockedClients, tags);
await DownloadReport(remoteMovie, downloadClient);
}

View File

@@ -74,7 +74,7 @@ namespace NzbDrone.Core.Download
try
{
_logger.Trace("Grabbing from Indexer {0} at priority {1}.", remoteMovie.Release.Indexer, remoteMovie.Release.IndexerPriority);
await _downloadService.DownloadReport(remoteMovie);
await _downloadService.DownloadReport(remoteMovie, null);
grabbed.Add(report);
}
catch (ReleaseUnavailableException)

View File

@@ -216,6 +216,7 @@
"Day": "Day",
"Days": "Days",
"Debug": "Debug",
"Default": "Default",
"DefaultCase": "Default Case",
"DefaultDelayProfile": "This is the default profile. It applies to all movies that don't have an explicit profile.",
"DelayProfile": "Delay Profile",
@@ -304,6 +305,7 @@
"DownloadClientTagHelpText": "Only use this download client for movies with at least one matching tag. Leave blank to use with all movies.",
"DownloadClientUnavailable": "Download client is unavailable",
"DownloadClients": "Download Clients",
"DownloadClientsLoadError": "Unable to load download clients",
"DownloadClientsSettingsSummary": "Download clients, download handling and remote path mappings",
"DownloadFailed": "Download failed",
"DownloadFailedCheckDownloadClientForMoreDetails": "Download failed: check download client for more details",
@@ -568,6 +570,7 @@
"ManageIndexers": "Manage Indexers",
"ManageLists": "Manage Lists",
"Manual": "Manual",
"ManualGrab": "Manual Grab",
"ManualImport": "Manual Import",
"ManualImportSelectLanguage": "Manual Import - Select Language",
"ManualImportSelectMovie": "Manual Import - Select Movie",
@@ -756,6 +759,11 @@
"OriginalLanguage": "Original Language",
"OriginalTitle": "Original Title",
"OutputPath": "Output Path",
"OverrideAndAddToDownloadQueue": "Override and add to download queue",
"OverrideGrabModalTitle": "Override and Grab - {title}",
"OverrideGrabNoLanguage": "At least one language must be selected",
"OverrideGrabNoMovie": "Movie must be selected",
"OverrideGrabNoQuality": "Quality must be selected",
"Overview": "Overview",
"OverviewOptions": "Overview Options",
"PackageVersion": "Package Version",
@@ -863,6 +871,7 @@
"RefreshMovie": "Refresh movie",
"RegularExpressionsCanBeTested": "Regular expressions can be tested ",
"RejectionCount": "Rejection Count",
"Rejections": "Rejections",
"RelativePath": "Relative Path",
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid Radarr release branch, you will not receive updates",
"ReleaseDates": "Release Dates",
@@ -1002,6 +1011,7 @@
"Seeders": "Seeders",
"SelectAll": "Select All",
"SelectDotDot": "Select...",
"SelectDownloadClientModalTitle": "{modalTitle} - Select Download Client",
"SelectFolder": "Select Folder",
"SelectLanguage": "Select Language",
"SelectLanguages": "Select Languages",
@@ -1181,7 +1191,6 @@
"UnableToLoadCustomFormats": "Unable to load Custom Formats",
"UnableToLoadDelayProfiles": "Unable to load Delay Profiles",
"UnableToLoadDownloadClientOptions": "Unable to load download client options",
"UnableToLoadDownloadClients": "Unable to load download clients",
"UnableToLoadGeneralSettings": "Unable to load General settings",
"UnableToLoadHistory": "Unable to load history",
"UnableToLoadIndexerOptions": "Unable to load indexer options",

View File

@@ -5,6 +5,8 @@ using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Exceptions;
@@ -58,7 +60,8 @@ namespace Radarr.Api.V3.Indexers
}
[HttpPost]
public object DownloadRelease(ReleaseResource release)
[Consumes("application/json")]
public async Task<object> DownloadRelease(ReleaseResource release)
{
var remoteMovie = _remoteMovieCache.Find(GetCacheKey(release));
@@ -71,6 +74,30 @@ namespace Radarr.Api.V3.Indexers
try
{
if (release.ShouldOverride == true)
{
Ensure.That(release.MovieId, () => release.MovieId).IsNotNull();
Ensure.That(release.Quality, () => release.Quality).IsNotNull();
Ensure.That(release.Languages, () => release.Languages).IsNotNull();
// Clone the remote episode so we don't overwrite anything on the original
remoteMovie = new RemoteMovie
{
Release = remoteMovie.Release,
ParsedMovieInfo = remoteMovie.ParsedMovieInfo.JsonClone(),
DownloadAllowed = remoteMovie.DownloadAllowed,
SeedConfiguration = remoteMovie.SeedConfiguration,
CustomFormats = remoteMovie.CustomFormats,
CustomFormatScore = remoteMovie.CustomFormatScore,
MovieMatchType = remoteMovie.MovieMatchType,
ReleaseSource = remoteMovie.ReleaseSource
};
remoteMovie.Movie = _movieService.GetMovie(release.MovieId!.Value);
remoteMovie.ParsedMovieInfo.Quality = release.Quality;
remoteMovie.Languages = release.Languages;
}
if (remoteMovie.Movie == null)
{
if (release.MovieId.HasValue)
@@ -85,7 +112,7 @@ namespace Radarr.Api.V3.Indexers
}
}
_downloadService.DownloadReport(remoteMovie);
await _downloadService.DownloadReport(remoteMovie, release.DownloadClientId);
}
catch (ReleaseDownloadException ex)
{
@@ -97,6 +124,7 @@ namespace Radarr.Api.V3.Indexers
}
[HttpGet]
[Produces("application/json")]
public async Task<List<ReleaseResource>> GetReleases(int? movieId)
{
if (movieId.HasValue)

View File

@@ -32,6 +32,7 @@ namespace Radarr.Api.V3.Indexers
public bool SceneSource { get; set; }
public List<string> MovieTitles { get; set; }
public List<Language> Languages { get; set; }
public int? MappedMovieId { get; set; }
public bool Approved { get; set; }
public bool TemporarilyRejected { get; set; }
public bool Rejected { get; set; }
@@ -56,6 +57,12 @@ namespace Radarr.Api.V3.Indexers
// Sent when queuing an unknown release
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int? MovieId { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int? DownloadClientId { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool? ShouldOverride { get; set; }
}
public static class ReleaseResourceMapper
@@ -88,6 +95,7 @@ namespace Radarr.Api.V3.Indexers
Title = releaseInfo.Title,
MovieTitles = parsedMovieInfo.MovieTitles,
Languages = remoteMovie.Languages,
MappedMovieId = remoteMovie.Movie?.Id,
Approved = model.Approved,
TemporarilyRejected = model.TemporarilyRejected,
Rejected = model.Rejected,

View File

@@ -30,7 +30,7 @@ namespace Radarr.Api.V3.Queue
throw new NotFoundException();
}
await _downloadService.DownloadReport(pendingRelease.RemoteMovie);
await _downloadService.DownloadReport(pendingRelease.RemoteMovie, null);
return new { };
}
@@ -48,7 +48,7 @@ namespace Radarr.Api.V3.Queue
throw new NotFoundException();
}
await _downloadService.DownloadReport(pendingRelease.RemoteMovie);
await _downloadService.DownloadReport(pendingRelease.RemoteMovie, null);
}
return new { };