From 2a93686360b1addeab70f384d61daef1ce56db0c Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 31 Jan 2021 01:02:51 -0500 Subject: [PATCH] New: Reprocess items after selection in Manual Import Fixes #5199 Co-Authored-By: Mark McDowall --- .../SelectLanguageModalContentConnector.js | 16 ++++-- .../SelectQualityModalContentConnector.js | 16 ++++-- .../Store/Actions/interactiveImportActions.js | 2 + .../MovieImport/ImportDecisionMaker.cs | 17 ++++--- .../MovieImport/Manual/ManualImportService.cs | 49 ++++++++++++++----- .../ManualImport/ManualImportModule.cs | 2 +- .../ManualImportReprocessResource.cs | 4 +- 7 files changed, 77 insertions(+), 29 deletions(-) diff --git a/frontend/src/InteractiveImport/Language/SelectLanguageModalContentConnector.js b/frontend/src/InteractiveImport/Language/SelectLanguageModalContentConnector.js index e1b30f9ea..474930331 100644 --- a/frontend/src/InteractiveImport/Language/SelectLanguageModalContentConnector.js +++ b/frontend/src/InteractiveImport/Language/SelectLanguageModalContentConnector.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions'; +import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions'; import { fetchLanguages } from 'Store/Actions/settingsActions'; import SelectLanguageModalContent from './SelectLanguageModalContent'; @@ -33,6 +33,7 @@ function createMapStateToProps() { const mapDispatchToProps = { dispatchFetchLanguages: fetchLanguages, + dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems, dispatchUpdateInteractiveImportItems: updateInteractiveImportItems }; @@ -51,6 +52,12 @@ class SelectLanguageModalContentConnector extends Component { // Listeners onLanguageSelect = ({ languageIds }) => { + const { + ids, + dispatchUpdateInteractiveImportItems, + dispatchReprocessInteractiveImportItems + } = this.props; + const languages = []; languageIds.forEach((languageId) => { @@ -62,11 +69,13 @@ class SelectLanguageModalContentConnector extends Component { } }); - this.props.dispatchUpdateInteractiveImportItems({ - ids: this.props.ids, + dispatchUpdateInteractiveImportItems({ + ids, languages }); + dispatchReprocessInteractiveImportItems({ ids }); + this.props.onModalClose(true); } @@ -91,6 +100,7 @@ SelectLanguageModalContentConnector.propTypes = { items: PropTypes.arrayOf(PropTypes.object).isRequired, dispatchFetchLanguages: PropTypes.func.isRequired, dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired, + dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/InteractiveImport/Quality/SelectQualityModalContentConnector.js b/frontend/src/InteractiveImport/Quality/SelectQualityModalContentConnector.js index 149d2536a..800c9bfec 100644 --- a/frontend/src/InteractiveImport/Quality/SelectQualityModalContentConnector.js +++ b/frontend/src/InteractiveImport/Quality/SelectQualityModalContentConnector.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions'; +import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions'; import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions'; import getQualities from 'Utilities/Quality/getQualities'; import SelectQualityModalContent from './SelectQualityModalContent'; @@ -31,6 +31,7 @@ function createMapStateToProps() { const mapDispatchToProps = { dispatchFetchQualityProfileSchema: fetchQualityProfileSchema, + dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems, dispatchUpdateInteractiveImportItems: updateInteractiveImportItems }; @@ -49,6 +50,12 @@ class SelectQualityModalContentConnector extends Component { // Listeners onQualitySelect = ({ qualityId, proper, real }) => { + const { + ids, + dispatchUpdateInteractiveImportItems, + dispatchReprocessInteractiveImportItems + } = this.props; + const quality = _.find(this.props.items, (item) => item.id === qualityId); @@ -57,14 +64,16 @@ class SelectQualityModalContentConnector extends Component { real: real ? 1 : 0 }; - this.props.dispatchUpdateInteractiveImportItems({ - ids: this.props.ids, + dispatchUpdateInteractiveImportItems({ + ids, quality: { quality, revision } }); + dispatchReprocessInteractiveImportItems({ ids }); + this.props.onModalClose(true); } @@ -88,6 +97,7 @@ SelectQualityModalContentConnector.propTypes = { error: PropTypes.object, items: PropTypes.arrayOf(PropTypes.object).isRequired, dispatchFetchQualityProfileSchema: PropTypes.func.isRequired, + dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired, dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js index 6f5043555..491c77fd4 100644 --- a/frontend/src/Store/Actions/interactiveImportActions.js +++ b/frontend/src/Store/Actions/interactiveImportActions.js @@ -149,6 +149,8 @@ export const actionHandlers = handleThunks({ id, path: item.path, movieId: item.movie.id, + quality: item.quality, + languages: item.languages, downloadId: item.downloadId }; }); diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs index 0b0ebab1a..ed707c1da 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportDecisionMaker.cs @@ -18,6 +18,7 @@ public interface IMakeImportDecision List GetImportDecisions(List videoFiles, Movie movie); List GetImportDecisions(List videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource); List GetImportDecisions(List videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool filterExistingFiles); + ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem); } public class ImportDecisionMaker : IMakeImportDecision @@ -93,6 +94,14 @@ public List GetImportDecisions(List videoFiles, Movie mo return decisions; } + public ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem) + { + var reasons = _specifications.Select(c => EvaluateSpec(c, localMovie, downloadClientItem)) + .Where(c => c != null); + + return new ImportDecision(localMovie, reasons.ToArray()); + } + private ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles) { ImportDecision decision = null; @@ -147,14 +156,6 @@ private ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem dow return decision; } - private ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem) - { - var reasons = _specifications.Select(c => EvaluateSpec(c, localMovie, downloadClientItem)) - .Where(c => c != null); - - return new ImportDecision(localMovie, reasons.ToArray()); - } - private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalMovie localMovie, DownloadClientItem downloadClientItem) { try diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs index 96434fa98..9bae5bbfb 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs @@ -8,19 +8,21 @@ using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles.MovieImport.Aggregation; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.MediaFiles.MovieImport.Manual { public interface IManualImportService { List GetMediaFiles(string path, string downloadId, int? movieId, bool filterExistingFiles); - ManualImportItem ReprocessItem(string path, string downloadId, int movieId); + ManualImportItem ReprocessItem(string path, string downloadId, int movieId, QualityModel quality, List languages); } public class ManualImportService : IExecute, IManualImportService @@ -90,12 +92,27 @@ public List GetMediaFiles(string path, string downloadId, int? return ProcessFolder(path, path, downloadId, movieId, filterExistingFiles); } - public ManualImportItem ReprocessItem(string path, string downloadId, int movieId) + public ManualImportItem ReprocessItem(string path, string downloadId, int movieId, QualityModel quality, List languages) { var rootFolder = Path.GetDirectoryName(path); var movie = _movieService.GetMovie(movieId); - return ProcessFile(rootFolder, rootFolder, path, downloadId, movie); + var downloadClientItem = GetTrackedDownload(downloadId)?.DownloadItem; + + var localEpisode = new LocalMovie + { + Movie = movie, + FileMovieInfo = Parser.Parser.ParseMoviePath(path), + DownloadClientMovieInfo = downloadClientItem == null ? null : Parser.Parser.ParseMovieTitle(downloadClientItem.Title), + Path = path, + SceneSource = SceneSource(movie, rootFolder), + ExistingFile = movie.Path.IsParentPath(path), + Size = _diskProvider.GetFileSize(path), + Languages = languages, + Quality = quality + }; + + return MapItem(_importDecisionMaker.GetDecision(localEpisode, downloadClientItem), rootFolder, downloadId, null); } private List ProcessFolder(string rootFolder, string baseFolder, string downloadId, int? movieId, bool filterExistingFiles) @@ -141,7 +158,7 @@ private List ProcessFolder(string rootFolder, string baseFolde private ManualImportItem ProcessFile(string rootFolder, string baseFolder, string file, string downloadId, Movie movie = null) { - DownloadClientItem downloadClientItem = null; + var trackedDownload = GetTrackedDownload(downloadId); var relativeFile = baseFolder.GetRelativePath(file); if (movie == null) @@ -154,15 +171,9 @@ private ManualImportItem ProcessFile(string rootFolder, string baseFolder, strin movie = _parsingService.GetMovie(relativeFile); } - if (downloadId.IsNotNullOrWhiteSpace()) + if (trackedDownload != null && movie == null) { - var trackedDownload = _trackedDownloadService.Find(downloadId); - downloadClientItem = trackedDownload?.DownloadItem; - - if (movie == null) - { - movie = trackedDownload?.RemoteMovie?.Movie; - } + movie = trackedDownload?.RemoteMovie?.Movie; } if (movie == null) @@ -186,7 +197,7 @@ private ManualImportItem ProcessFile(string rootFolder, string baseFolder, strin return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), rootFolder, downloadId, null); } - var importDecisions = _importDecisionMaker.GetImportDecisions(new List { file }, movie, downloadClientItem, null, SceneSource(movie, baseFolder)); + var importDecisions = _importDecisionMaker.GetImportDecisions(new List { file }, movie, trackedDownload?.DownloadItem, null, SceneSource(movie, baseFolder)); if (importDecisions.Any()) { @@ -208,6 +219,18 @@ private bool SceneSource(Movie movie, string folder) return !(movie.Path.PathEquals(folder) || movie.Path.IsParentPath(folder)); } + private TrackedDownload GetTrackedDownload(string downloadId) + { + if (downloadId.IsNotNullOrWhiteSpace()) + { + var trackedDownload = _trackedDownloadService.Find(downloadId); + + return trackedDownload; + } + + return null; + } + private ManualImportItem MapItem(ImportDecision decision, string rootFolder, string downloadId, string folderName) { var item = new ManualImportItem(); diff --git a/src/Radarr.Api.V3/ManualImport/ManualImportModule.cs b/src/Radarr.Api.V3/ManualImport/ManualImportModule.cs index ba9fd1f2a..21d06f0fb 100644 --- a/src/Radarr.Api.V3/ManualImport/ManualImportModule.cs +++ b/src/Radarr.Api.V3/ManualImport/ManualImportModule.cs @@ -37,7 +37,7 @@ private object ReprocessItems() foreach (var item in items) { - var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.MovieId); + var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.MovieId, item.Quality, item.Languages); item.Movie = processedItem.Movie.ToResource(0); item.Rejections = processedItem.Rejections; diff --git a/src/Radarr.Api.V3/ManualImport/ManualImportReprocessResource.cs b/src/Radarr.Api.V3/ManualImport/ManualImportReprocessResource.cs index 9ee4819a8..f610efbc5 100644 --- a/src/Radarr.Api.V3/ManualImport/ManualImportReprocessResource.cs +++ b/src/Radarr.Api.V3/ManualImport/ManualImportReprocessResource.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Languages; +using NzbDrone.Core.Qualities; using Radarr.Api.V3.Movies; using Radarr.Http.REST; @@ -11,9 +12,10 @@ public class ManualImportReprocessResource : RestResource public string Path { get; set; } public int MovieId { get; set; } public MovieResource Movie { get; set; } + public QualityModel Quality { get; set; } + public List Languages { get; set; } public string DownloadId { get; set; } public IEnumerable Rejections { get; set; } - public List Languages { get; set; } } }