New: Ability to forcibly grab a release from Interactive Search

Closes #395
pull/2900/head
Mark McDowall 6 years ago
parent 70fb1551af
commit 8aecec507e

@ -90,6 +90,7 @@ const columns = [
function InteractiveSearch(props) { function InteractiveSearch(props) {
const { const {
searchPayload,
isFetching, isFetching,
isPopulated, isPopulated,
error, error,
@ -164,6 +165,7 @@ function InteractiveSearch(props) {
<InteractiveSearchRow <InteractiveSearchRow
key={item.guid} key={item.guid}
{...item} {...item}
searchPayload={searchPayload}
longDateFormat={longDateFormat} longDateFormat={longDateFormat}
timeFormat={timeFormat} timeFormat={timeFormat}
onGrabPress={onGrabPress} onGrabPress={onGrabPress}
@ -186,6 +188,7 @@ function InteractiveSearch(props) {
} }
InteractiveSearch.propTypes = { InteractiveSearch.propTypes = {
searchPayload: PropTypes.object.isRequired,
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,

@ -41,8 +41,8 @@ function createMapDispatchToProps(dispatch, props) {
dispatch(action({ selectedFilterKey })); dispatch(action({ selectedFilterKey }));
}, },
onGrabPress(guid, indexerId) { onGrabPress(payload) {
dispatch(releaseActions.grabRelease({ guid, indexerId })); dispatch(releaseActions.grabRelease(payload));
} }
}; };
} }

@ -7,6 +7,7 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
@ -42,6 +43,17 @@ function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
class InteractiveSearchRow extends Component { class InteractiveSearchRow extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isConfirmGrabModalOpen: false
};
}
// //
// Listeners // Listeners
@ -50,9 +62,37 @@ class InteractiveSearchRow extends Component {
guid, guid,
indexerId, indexerId,
onGrabPress onGrabPress
}= this.props; } = this.props;
onGrabPress({
guid,
indexerId
});
}
onGrabPress(guid, indexerId); onConfirmGrabPress = () => {
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 {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.download}> <TableRowCell className={styles.download}>
{ <SpinnerIconButton
downloadAllowed && name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
<SpinnerIconButton kind={grabError ? kinds.DANGER : kinds.DEFAULT}
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)} title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
kind={grabError ? kinds.DANGER : kinds.DEFAULT} isSpinning={isGrabbing}
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)} onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
isSpinning={isGrabbing} />
onPress={this.onGrabPress}
/>
}
</TableRowCell> </TableRowCell>
<ConfirmModal
isOpen={this.state.isConfirmGrabModalOpen}
kind={kinds.WARNING}
title="Grab Release"
message={`Sonarr was unable to determine which series and episode this release was for. Sonarr may be unable to automatically import this release. Do you want to grab '${title}'?`}
confirmLabel="Grab"
onConfirm={this.onGrabConfirm}
onCancel={this.onGrabCancel}
/>
</TableRow> </TableRow>
); );
} }
@ -205,6 +252,7 @@ InteractiveSearchRow.propTypes = {
grabError: PropTypes.string, grabError: PropTypes.string,
longDateFormat: PropTypes.string.isRequired, longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
searchPayload: PropTypes.object.isRequired,
onGrabPress: PropTypes.func.isRequired onGrabPress: PropTypes.func.isRequired
}; };

@ -5,12 +5,15 @@ using Nancy;
using Nancy.ModelBinding; using Nancy.ModelBinding;
using NLog; using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
using Sonarr.Http.Extensions; using Sonarr.Http.Extensions;
using HttpStatusCode = System.Net.HttpStatusCode; using HttpStatusCode = System.Net.HttpStatusCode;
@ -24,6 +27,9 @@ namespace Sonarr.Api.V3.Indexers
private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision;
private readonly IDownloadService _downloadService; private readonly IDownloadService _downloadService;
private readonly ISeriesService _seriesService;
private readonly IEpisodeService _episodeService;
private readonly IParsingService _parsingService;
private readonly Logger _logger; private readonly Logger _logger;
private readonly ICached<RemoteEpisode> _remoteEpisodeCache; private readonly ICached<RemoteEpisode> _remoteEpisodeCache;
@ -33,6 +39,9 @@ namespace Sonarr.Api.V3.Indexers
IMakeDownloadDecision downloadDecisionMaker, IMakeDownloadDecision downloadDecisionMaker,
IPrioritizeDownloadDecision prioritizeDownloadDecision, IPrioritizeDownloadDecision prioritizeDownloadDecision,
IDownloadService downloadService, IDownloadService downloadService,
ISeriesService seriesService,
IEpisodeService episodeService,
IParsingService parsingService,
ICacheManager cacheManager, ICacheManager cacheManager,
Logger logger) Logger logger)
{ {
@ -41,6 +50,9 @@ namespace Sonarr.Api.V3.Indexers
_downloadDecisionMaker = downloadDecisionMaker; _downloadDecisionMaker = downloadDecisionMaker;
_prioritizeDownloadDecision = prioritizeDownloadDecision; _prioritizeDownloadDecision = prioritizeDownloadDecision;
_downloadService = downloadService; _downloadService = downloadService;
_seriesService = seriesService;
_episodeService = episodeService;
_parsingService = parsingService;
_logger = logger; _logger = logger;
GetResourceAll = GetReleases; GetResourceAll = GetReleases;
@ -66,6 +78,34 @@ namespace Sonarr.Api.V3.Indexers
try 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> { 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); _downloadService.DownloadReport(remoteEpisode);
} }
catch (ReleaseDownloadException ex) catch (ReleaseDownloadException ex)

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
@ -61,6 +62,16 @@ namespace Sonarr.Api.V3.Indexers
public bool IsAbsoluteNumbering { get; set; } public bool IsAbsoluteNumbering { get; set; }
public bool IsPossibleSpecialEpisode { get; set; } public bool IsPossibleSpecialEpisode { get; set; }
public bool Special { 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 public static class ReleaseResourceMapper

Loading…
Cancel
Save