From 8aecec507ec53372a9dacf1d8a980473023fc770 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 18 Jan 2019 11:46:21 -0800 Subject: [PATCH] New: Ability to forcibly grab a release from Interactive Search Closes #395 --- .../InteractiveSearch/InteractiveSearch.js | 3 + .../InteractiveSearchConnector.js | 4 +- .../InteractiveSearch/InteractiveSearchRow.js | 72 +++++++++++++++---- src/Sonarr.Api.V3/Indexers/ReleaseModule.cs | 40 +++++++++++ src/Sonarr.Api.V3/Indexers/ReleaseResource.cs | 11 +++ 5 files changed, 116 insertions(+), 14 deletions(-) diff --git a/frontend/src/InteractiveSearch/InteractiveSearch.js b/frontend/src/InteractiveSearch/InteractiveSearch.js index 862bc0c55..74d2fc219 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearch.js +++ b/frontend/src/InteractiveSearch/InteractiveSearch.js @@ -90,6 +90,7 @@ const columns = [ function InteractiveSearch(props) { const { + searchPayload, isFetching, isPopulated, error, @@ -164,6 +165,7 @@ function InteractiveSearch(props) { { + this.setState({ isConfirmGrabModalOpen: true }); + } + + onGrabConfirm = () => { + this.setState({ isConfirmGrabModalOpen: false }); + + const { + guid, + indexerId, + searchPayload, + onGrabPress + } = this.props; + + onGrabPress({ + guid, + indexerId, + ...searchPayload + }); + } + + onGrabCancel = () => { + this.setState({ isConfirmGrabModalOpen: false }); } // @@ -165,17 +205,24 @@ class InteractiveSearchRow extends Component { - { - downloadAllowed && - - } + + + ); } @@ -205,6 +252,7 @@ InteractiveSearchRow.propTypes = { grabError: PropTypes.string, longDateFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired, + searchPayload: PropTypes.object.isRequired, onGrabPress: PropTypes.func.isRequired }; diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs b/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs index d1a2dcbac..78c7e6590 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseModule.cs @@ -5,12 +5,15 @@ using Nancy; using Nancy.ModelBinding; using NLog; using NzbDrone.Common.Cache; +using NzbDrone.Common.Extensions; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; using NzbDrone.Core.IndexerSearch; +using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Tv; using NzbDrone.Core.Validation; using Sonarr.Http.Extensions; using HttpStatusCode = System.Net.HttpStatusCode; @@ -24,6 +27,9 @@ namespace Sonarr.Api.V3.Indexers private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; private readonly IDownloadService _downloadService; + private readonly ISeriesService _seriesService; + private readonly IEpisodeService _episodeService; + private readonly IParsingService _parsingService; private readonly Logger _logger; private readonly ICached _remoteEpisodeCache; @@ -33,6 +39,9 @@ namespace Sonarr.Api.V3.Indexers IMakeDownloadDecision downloadDecisionMaker, IPrioritizeDownloadDecision prioritizeDownloadDecision, IDownloadService downloadService, + ISeriesService seriesService, + IEpisodeService episodeService, + IParsingService parsingService, ICacheManager cacheManager, Logger logger) { @@ -41,6 +50,9 @@ namespace Sonarr.Api.V3.Indexers _downloadDecisionMaker = downloadDecisionMaker; _prioritizeDownloadDecision = prioritizeDownloadDecision; _downloadService = downloadService; + _seriesService = seriesService; + _episodeService = episodeService; + _parsingService = parsingService; _logger = logger; GetResourceAll = GetReleases; @@ -66,6 +78,34 @@ namespace Sonarr.Api.V3.Indexers try { + if (remoteEpisode.Series == null) + { + if (release.EpisodeId.HasValue) + { + var episode = _episodeService.GetEpisode(release.EpisodeId.Value); + + remoteEpisode.Series = _seriesService.GetSeries(episode.SeriesId); + remoteEpisode.Episodes = new List { episode }; + } + else if (release.SeriesId.HasValue) + { + var series = _seriesService.GetSeries(release.SeriesId.Value); + var episodes = _parsingService.GetEpisodes(remoteEpisode.ParsedEpisodeInfo, series, true); + + if (episodes.Empty()) + { + throw new NzbDroneClientException(HttpStatusCode.NotFound, "Unable to parse episodes in the release"); + } + + remoteEpisode.Series = series; + remoteEpisode.Episodes = episodes; + } + else + { + throw new NzbDroneClientException(HttpStatusCode.NotFound, "Unable to find matching series and episodes"); + } + } + _downloadService.DownloadReport(remoteEpisode); } catch (ReleaseDownloadException ex) diff --git a/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs b/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs index 006e78285..213a7a944 100644 --- a/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs +++ b/src/Sonarr.Api.V3/Indexers/ReleaseResource.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Indexers; using NzbDrone.Core.Languages; @@ -61,6 +62,16 @@ namespace Sonarr.Api.V3.Indexers public bool IsAbsoluteNumbering { get; set; } public bool IsPossibleSpecialEpisode { get; set; } public bool Special { get; set; } + + // Sent when queuing an unknown release + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] +// [JsonIgnore] + public int? SeriesId { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] +// [JsonIgnore] + public int? EpisodeId { get; set; } } public static class ReleaseResourceMapper