mirror of
https://github.com/Radarr/Radarr.git
synced 2024-09-11 20:12:41 +02:00
New: Reprocess items after selection in Manual Import
Fixes #5199 Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
This commit is contained in:
parent
f33f004aa9
commit
2a93686360
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||||
import { fetchLanguages } from 'Store/Actions/settingsActions';
|
import { fetchLanguages } from 'Store/Actions/settingsActions';
|
||||||
import SelectLanguageModalContent from './SelectLanguageModalContent';
|
import SelectLanguageModalContent from './SelectLanguageModalContent';
|
||||||
|
|
||||||
@ -33,6 +33,7 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
dispatchFetchLanguages: fetchLanguages,
|
dispatchFetchLanguages: fetchLanguages,
|
||||||
|
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems,
|
||||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,6 +52,12 @@ class SelectLanguageModalContentConnector extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onLanguageSelect = ({ languageIds }) => {
|
onLanguageSelect = ({ languageIds }) => {
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
dispatchUpdateInteractiveImportItems,
|
||||||
|
dispatchReprocessInteractiveImportItems
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
const languages = [];
|
const languages = [];
|
||||||
|
|
||||||
languageIds.forEach((languageId) => {
|
languageIds.forEach((languageId) => {
|
||||||
@ -62,11 +69,13 @@ class SelectLanguageModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.dispatchUpdateInteractiveImportItems({
|
dispatchUpdateInteractiveImportItems({
|
||||||
ids: this.props.ids,
|
ids,
|
||||||
languages
|
languages
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dispatchReprocessInteractiveImportItems({ ids });
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
this.props.onModalClose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +100,7 @@ SelectLanguageModalContentConnector.propTypes = {
|
|||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
dispatchFetchLanguages: PropTypes.func.isRequired,
|
dispatchFetchLanguages: PropTypes.func.isRequired,
|
||||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
|
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||||
import getQualities from 'Utilities/Quality/getQualities';
|
import getQualities from 'Utilities/Quality/getQualities';
|
||||||
import SelectQualityModalContent from './SelectQualityModalContent';
|
import SelectQualityModalContent from './SelectQualityModalContent';
|
||||||
@ -31,6 +31,7 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
|
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
|
||||||
|
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems,
|
||||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,6 +50,12 @@ class SelectQualityModalContentConnector extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onQualitySelect = ({ qualityId, proper, real }) => {
|
onQualitySelect = ({ qualityId, proper, real }) => {
|
||||||
|
const {
|
||||||
|
ids,
|
||||||
|
dispatchUpdateInteractiveImportItems,
|
||||||
|
dispatchReprocessInteractiveImportItems
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
const quality = _.find(this.props.items,
|
const quality = _.find(this.props.items,
|
||||||
(item) => item.id === qualityId);
|
(item) => item.id === qualityId);
|
||||||
|
|
||||||
@ -57,14 +64,16 @@ class SelectQualityModalContentConnector extends Component {
|
|||||||
real: real ? 1 : 0
|
real: real ? 1 : 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.dispatchUpdateInteractiveImportItems({
|
dispatchUpdateInteractiveImportItems({
|
||||||
ids: this.props.ids,
|
ids,
|
||||||
quality: {
|
quality: {
|
||||||
quality,
|
quality,
|
||||||
revision
|
revision
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dispatchReprocessInteractiveImportItems({ ids });
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
this.props.onModalClose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +97,7 @@ SelectQualityModalContentConnector.propTypes = {
|
|||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
||||||
|
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
@ -149,6 +149,8 @@ export const actionHandlers = handleThunks({
|
|||||||
id,
|
id,
|
||||||
path: item.path,
|
path: item.path,
|
||||||
movieId: item.movie.id,
|
movieId: item.movie.id,
|
||||||
|
quality: item.quality,
|
||||||
|
languages: item.languages,
|
||||||
downloadId: item.downloadId
|
downloadId: item.downloadId
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -18,6 +18,7 @@ public interface IMakeImportDecision
|
|||||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie);
|
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie);
|
||||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource);
|
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource);
|
||||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool filterExistingFiles);
|
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool filterExistingFiles);
|
||||||
|
ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ImportDecisionMaker : IMakeImportDecision
|
public class ImportDecisionMaker : IMakeImportDecision
|
||||||
@ -93,6 +94,14 @@ public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Movie mo
|
|||||||
return decisions;
|
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)
|
private ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles)
|
||||||
{
|
{
|
||||||
ImportDecision decision = null;
|
ImportDecision decision = null;
|
||||||
@ -147,14 +156,6 @@ private ImportDecision GetDecision(LocalMovie localMovie, DownloadClientItem dow
|
|||||||
return decision;
|
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)
|
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalMovie localMovie, DownloadClientItem downloadClientItem)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -8,19 +8,21 @@
|
|||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation;
|
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Movies;
|
using NzbDrone.Core.Movies;
|
||||||
using NzbDrone.Core.Parser;
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
|
namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
|
||||||
{
|
{
|
||||||
public interface IManualImportService
|
public interface IManualImportService
|
||||||
{
|
{
|
||||||
List<ManualImportItem> GetMediaFiles(string path, string downloadId, int? movieId, bool filterExistingFiles);
|
List<ManualImportItem> 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<Language> languages);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ManualImportService : IExecute<ManualImportCommand>, IManualImportService
|
public class ManualImportService : IExecute<ManualImportCommand>, IManualImportService
|
||||||
@ -90,12 +92,27 @@ public List<ManualImportItem> GetMediaFiles(string path, string downloadId, int?
|
|||||||
return ProcessFolder(path, path, downloadId, movieId, filterExistingFiles);
|
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<Language> languages)
|
||||||
{
|
{
|
||||||
var rootFolder = Path.GetDirectoryName(path);
|
var rootFolder = Path.GetDirectoryName(path);
|
||||||
var movie = _movieService.GetMovie(movieId);
|
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<ManualImportItem> ProcessFolder(string rootFolder, string baseFolder, string downloadId, int? movieId, bool filterExistingFiles)
|
private List<ManualImportItem> ProcessFolder(string rootFolder, string baseFolder, string downloadId, int? movieId, bool filterExistingFiles)
|
||||||
@ -141,7 +158,7 @@ private List<ManualImportItem> ProcessFolder(string rootFolder, string baseFolde
|
|||||||
|
|
||||||
private ManualImportItem ProcessFile(string rootFolder, string baseFolder, string file, string downloadId, Movie movie = null)
|
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);
|
var relativeFile = baseFolder.GetRelativePath(file);
|
||||||
|
|
||||||
if (movie == null)
|
if (movie == null)
|
||||||
@ -154,15 +171,9 @@ private ManualImportItem ProcessFile(string rootFolder, string baseFolder, strin
|
|||||||
movie = _parsingService.GetMovie(relativeFile);
|
movie = _parsingService.GetMovie(relativeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadId.IsNotNullOrWhiteSpace())
|
if (trackedDownload != null && movie == null)
|
||||||
{
|
{
|
||||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
movie = trackedDownload?.RemoteMovie?.Movie;
|
||||||
downloadClientItem = trackedDownload?.DownloadItem;
|
|
||||||
|
|
||||||
if (movie == null)
|
|
||||||
{
|
|
||||||
movie = trackedDownload?.RemoteMovie?.Movie;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movie == null)
|
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);
|
return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), rootFolder, downloadId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file }, movie, downloadClientItem, null, SceneSource(movie, baseFolder));
|
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file }, movie, trackedDownload?.DownloadItem, null, SceneSource(movie, baseFolder));
|
||||||
|
|
||||||
if (importDecisions.Any())
|
if (importDecisions.Any())
|
||||||
{
|
{
|
||||||
@ -208,6 +219,18 @@ private bool SceneSource(Movie movie, string folder)
|
|||||||
return !(movie.Path.PathEquals(folder) || movie.Path.IsParentPath(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)
|
private ManualImportItem MapItem(ImportDecision decision, string rootFolder, string downloadId, string folderName)
|
||||||
{
|
{
|
||||||
var item = new ManualImportItem();
|
var item = new ManualImportItem();
|
||||||
|
@ -37,7 +37,7 @@ private object ReprocessItems()
|
|||||||
|
|
||||||
foreach (var item in items)
|
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.Movie = processedItem.Movie.ToResource(0);
|
||||||
item.Rejections = processedItem.Rejections;
|
item.Rejections = processedItem.Rejections;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
using Radarr.Api.V3.Movies;
|
using Radarr.Api.V3.Movies;
|
||||||
using Radarr.Http.REST;
|
using Radarr.Http.REST;
|
||||||
|
|
||||||
@ -11,9 +12,10 @@ public class ManualImportReprocessResource : RestResource
|
|||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public int MovieId { get; set; }
|
public int MovieId { get; set; }
|
||||||
public MovieResource Movie { get; set; }
|
public MovieResource Movie { get; set; }
|
||||||
|
public QualityModel Quality { get; set; }
|
||||||
|
public List<Language> Languages { get; set; }
|
||||||
public string DownloadId { get; set; }
|
public string DownloadId { get; set; }
|
||||||
|
|
||||||
public IEnumerable<Rejection> Rejections { get; set; }
|
public IEnumerable<Rejection> Rejections { get; set; }
|
||||||
public List<Language> Languages { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user