From e4d033d0a74c5f00f8901a6ed64e88451eed671a Mon Sep 17 00:00:00 2001 From: Qstick Date: Wed, 19 Aug 2020 22:34:26 -0400 Subject: [PATCH] New: Reprocess Language in Manual Import after Movie Selection --- .../Interactive/InteractiveImportRow.css | 7 ++ .../Interactive/InteractiveImportRow.js | 18 ++++- .../Movie/SelectMovieModalContentConnector.js | 7 +- .../Store/Actions/interactiveImportActions.js | 74 ++++++++++++++++++- .../MovieImport/Manual/ManualImportService.cs | 17 +++-- .../ManualImport/ManualImportModule.cs | 18 +++++ .../ManualImportReprocessResource.cs | 19 +++++ .../Movies/DiscoverMoviesModule.cs | 2 +- .../Movies/DiscoverMoviesResource.cs | 2 +- 9 files changed, 149 insertions(+), 15 deletions(-) create mode 100644 src/Radarr.Api.V3/ManualImport/ManualImportReprocessResource.cs diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css index 4cafa144a..f1b7b44e7 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.css @@ -14,3 +14,10 @@ cursor: pointer; } + +.reprocessing { + composes: loading from '~Components/Loading/LoadingIndicator.css'; + + margin-top: 0; + text-align: start; +} diff --git a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js index a74c67a60..c6d43921c 100644 --- a/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js +++ b/frontend/src/InteractiveImport/Interactive/InteractiveImportRow.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Icon from 'Components/Icon'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCellButton from 'Components/Table/Cells/TableRowCellButton'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; @@ -137,6 +138,7 @@ class InteractiveImportRow extends Component { languages, size, rejections, + isReprocessing, isSelected, onSelectedChange } = this.props; @@ -151,7 +153,7 @@ class InteractiveImportRow extends Component { const showMoviePlaceholder = isSelected && !movie; const showQualityPlaceholder = isSelected && !quality; - const showLanguagePlaceholder = isSelected && !languages; + const showLanguagePlaceholder = isSelected && !languages && !isReprocessing; return ( @@ -208,11 +210,20 @@ class InteractiveImportRow extends Component { } { - !showLanguagePlaceholder && !!languages && + !showLanguagePlaceholder && !!languages && !isReprocessing ? + /> : + null + } + + { + isReprocessing ? + : null } @@ -286,6 +297,7 @@ InteractiveImportRow.propTypes = { languages: PropTypes.arrayOf(PropTypes.object), size: PropTypes.number.isRequired, rejections: PropTypes.arrayOf(PropTypes.object).isRequired, + isReprocessing: PropTypes.bool, isSelected: PropTypes.bool, onSelectedChange: PropTypes.func.isRequired, onValidRowChange: PropTypes.func.isRequired diff --git a/frontend/src/InteractiveImport/Movie/SelectMovieModalContentConnector.js b/frontend/src/InteractiveImport/Movie/SelectMovieModalContentConnector.js index b728d9422..4115a59da 100644 --- a/frontend/src/InteractiveImport/Movie/SelectMovieModalContentConnector.js +++ b/frontend/src/InteractiveImport/Movie/SelectMovieModalContentConnector.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; +import { reprocessInteractiveImportItems, updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector'; import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector'; import SelectMovieModalContent from './SelectMovieModalContent'; @@ -59,6 +59,7 @@ function createMapStateToProps() { } const mapDispatchToProps = { + dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems, dispatchUpdateInteractiveImportItem: updateInteractiveImportItem }; @@ -72,6 +73,7 @@ class SelectMovieModalContentConnector extends Component { ids, items, dispatchUpdateInteractiveImportItem, + dispatchReprocessInteractiveImportItems, onModalClose } = this.props; @@ -84,6 +86,8 @@ class SelectMovieModalContentConnector extends Component { }); }); + dispatchReprocessInteractiveImportItems({ ids }); + onModalClose(true); } @@ -103,6 +107,7 @@ class SelectMovieModalContentConnector extends Component { SelectMovieModalContentConnector.propTypes = { ids: PropTypes.arrayOf(PropTypes.number).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, + dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired, dispatchUpdateInteractiveImportItem: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; diff --git a/frontend/src/Store/Actions/interactiveImportActions.js b/frontend/src/Store/Actions/interactiveImportActions.js index b8d8aa52d..da87e72c2 100644 --- a/frontend/src/Store/Actions/interactiveImportActions.js +++ b/frontend/src/Store/Actions/interactiveImportActions.js @@ -4,7 +4,7 @@ import { batchActions } from 'redux-batched-actions'; import { sortDirections } from 'Helpers/Props'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; -import { set, update } from './baseActions'; +import { set, update, updateItem } from './baseActions'; import createHandleActions from './Creators/createHandleActions'; import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer'; @@ -15,12 +15,16 @@ export const section = 'interactiveImport'; const MAXIMUM_RECENT_FOLDERS = 10; +let abortCurrentRequest = null; +let currentIds = []; + // // State export const defaultState = { isFetching: false, isPopulated: false, + isReprocessing: false, error: null, items: [], sortKey: 'quality', @@ -55,6 +59,7 @@ export const persistState = [ // Actions Types export const FETCH_INTERACTIVE_IMPORT_ITEMS = 'interactiveImport/fetchInteractiveImportItems'; +export const REPROCESS_INTERACTIVE_IMPORT_ITEMS = 'interactiveImport/reprocessInteractiveImportItems'; export const SET_INTERACTIVE_IMPORT_SORT = 'interactiveImport/setInteractiveImportSort'; export const UPDATE_INTERACTIVE_IMPORT_ITEM = 'interactiveImport/updateInteractiveImportItem'; export const UPDATE_INTERACTIVE_IMPORT_ITEMS = 'interactiveImport/updateInteractiveImportItems'; @@ -67,6 +72,7 @@ export const SET_INTERACTIVE_IMPORT_MODE = 'interactiveImport/setInteractiveImpo // Action Creators export const fetchInteractiveImportItems = createThunk(FETCH_INTERACTIVE_IMPORT_ITEMS); +export const reprocessInteractiveImportItems = createThunk(REPROCESS_INTERACTIVE_IMPORT_ITEMS); export const setInteractiveImportSort = createAction(SET_INTERACTIVE_IMPORT_SORT); export const updateInteractiveImportItem = createAction(UPDATE_INTERACTIVE_IMPORT_ITEM); export const updateInteractiveImportItems = createAction(UPDATE_INTERACTIVE_IMPORT_ITEMS); @@ -112,6 +118,72 @@ export const actionHandlers = handleThunks({ error: xhr })); }); + }, + + [REPROCESS_INTERACTIVE_IMPORT_ITEMS]: function(getState, payload, dispatch) { + if (abortCurrentRequest) { + abortCurrentRequest(); + } + + dispatch(batchActions([ + ...currentIds.map((id) => updateItem({ + section, + id, + isReprocessing: false + })), + ...payload.ids.map((id) => updateItem({ + section, + id, + isReprocessing: true + })) + ])); + + const items = getState()[section].items; + + const requestPayload = payload.ids.map((id) => { + const item = items.find((i) => i.id === id); + + return { + id, + path: item.path, + movieId: item.movie.id, + downloadId: item.downloadId + }; + }); + + const { request, abortRequest } = createAjaxRequest({ + method: 'POST', + url: '/manualimport', + contentType: 'application/json', + data: JSON.stringify(requestPayload) + }); + + abortCurrentRequest = abortRequest; + currentIds = payload.ids; + + request.done((data) => { + dispatch(batchActions( + data.map((item) => updateItem({ + section, + ...item, + isReprocessing: false + })) + )); + }); + + request.fail((xhr) => { + if (xhr.aborted) { + return; + } + + dispatch(batchActions( + payload.ids.map((id) => updateItem({ + section, + id, + isReprocessing: false + })) + )); + }); } }); diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs index e388b9874..1517e097f 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Manual/ManualImportService.cs @@ -5,11 +5,9 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation.Extensions; -using NzbDrone.Core.Configuration; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; -using NzbDrone.Core.History; using NzbDrone.Core.MediaFiles.MovieImport.Aggregation; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; @@ -22,6 +20,7 @@ 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); } public class ManualImportService : IExecute, IManualImportService @@ -36,8 +35,6 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual private readonly ITrackedDownloadService _trackedDownloadService; private readonly IDownloadedMovieImportService _downloadedMovieImportService; private readonly IEventAggregator _eventAggregator; - private readonly IConfigService _config; - private readonly IHistoryService _historyService; private readonly Logger _logger; public ManualImportService(IDiskProvider diskProvider, @@ -50,8 +47,6 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual ITrackedDownloadService trackedDownloadService, IDownloadedMovieImportService downloadedMovieImportService, IEventAggregator eventAggregator, - IConfigService config, - IHistoryService historyService, Logger logger) { _diskProvider = diskProvider; @@ -64,8 +59,6 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual _trackedDownloadService = trackedDownloadService; _downloadedMovieImportService = downloadedMovieImportService; _eventAggregator = eventAggregator; - _config = config; - _historyService = historyService; _logger = logger; } @@ -97,6 +90,14 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual return ProcessFolder(path, path, downloadId, movieId, filterExistingFiles); } + public ManualImportItem ReprocessItem(string path, string downloadId, int movieId) + { + var rootFolder = Path.GetDirectoryName(path); + var movie = _movieService.GetMovie(movieId); + + return ProcessFile(rootFolder, rootFolder, path, downloadId, movie); + } + private List ProcessFolder(string rootFolder, string baseFolder, string downloadId, int? movieId, bool filterExistingFiles) { DownloadClientItem downloadClientItem = null; diff --git a/src/Radarr.Api.V3/ManualImport/ManualImportModule.cs b/src/Radarr.Api.V3/ManualImport/ManualImportModule.cs index 8fea5498e..28525345f 100644 --- a/src/Radarr.Api.V3/ManualImport/ManualImportModule.cs +++ b/src/Radarr.Api.V3/ManualImport/ManualImportModule.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Core.MediaFiles.MovieImport.Manual; using NzbDrone.Core.Qualities; +using Radarr.Api.V3.Movies; using Radarr.Http; using Radarr.Http.Extensions; @@ -17,6 +18,7 @@ namespace Radarr.Api.V3.ManualImport _manualImportService = manualImportService; GetResourceAll = GetMediaFiles; + Post("/", x => ReprocessItems()); } private List GetMediaFiles() @@ -29,6 +31,22 @@ namespace Radarr.Api.V3.ManualImport return _manualImportService.GetMediaFiles(folder, downloadId, movieId, filterExistingFiles).ToResource().Select(AddQualityWeight).ToList(); } + private object ReprocessItems() + { + var items = Request.Body.FromJson>(); + + foreach (var item in items) + { + var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.MovieId); + + item.Movie = processedItem.Movie.ToResource(); + item.Rejections = processedItem.Rejections; + item.Languages = processedItem.Languages; + } + + return items; + } + private ManualImportResource AddQualityWeight(ManualImportResource item) { if (item.Quality != null) diff --git a/src/Radarr.Api.V3/ManualImport/ManualImportReprocessResource.cs b/src/Radarr.Api.V3/ManualImport/ManualImportReprocessResource.cs new file mode 100644 index 000000000..9ee4819a8 --- /dev/null +++ b/src/Radarr.Api.V3/ManualImport/ManualImportReprocessResource.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Languages; +using Radarr.Api.V3.Movies; +using Radarr.Http.REST; + +namespace Radarr.Api.V3.ManualImport +{ + public class ManualImportReprocessResource : RestResource + { + public string Path { get; set; } + public int MovieId { get; set; } + public MovieResource Movie { get; set; } + public string DownloadId { get; set; } + + public IEnumerable Rejections { get; set; } + public List Languages { get; set; } + } +} diff --git a/src/Radarr.Api.V3/Movies/DiscoverMoviesModule.cs b/src/Radarr.Api.V3/Movies/DiscoverMoviesModule.cs index 2390ada8c..f3d22cb12 100644 --- a/src/Radarr.Api.V3/Movies/DiscoverMoviesModule.cs +++ b/src/Radarr.Api.V3/Movies/DiscoverMoviesModule.cs @@ -7,7 +7,7 @@ using NzbDrone.Core.NetImport.ImportExclusions; using NzbDrone.Core.Organizer; using Radarr.Http; -namespace NzbDrone.Api.V3.Movies +namespace Radarr.Api.V3.Movies { public class DiscoverMoviesModule : RadarrRestModule { diff --git a/src/Radarr.Api.V3/Movies/DiscoverMoviesResource.cs b/src/Radarr.Api.V3/Movies/DiscoverMoviesResource.cs index 8d3c2edbc..96b5080fa 100644 --- a/src/Radarr.Api.V3/Movies/DiscoverMoviesResource.cs +++ b/src/Radarr.Api.V3/Movies/DiscoverMoviesResource.cs @@ -5,7 +5,7 @@ using NzbDrone.Core.Movies; using NzbDrone.Core.NetImport.ImportExclusions; using Radarr.Http.REST; -namespace NzbDrone.Api.V3.Movies +namespace Radarr.Api.V3.Movies { public class DiscoverMoviesResource : RestResource {