parent
c871b3f948
commit
f30ae69c10
|
@ -6,7 +6,8 @@ import {
|
|||
updateInteractiveImportItem,
|
||||
fetchInteractiveImportEpisodes,
|
||||
setInteractiveImportEpisodesSort,
|
||||
clearInteractiveImportEpisodes
|
||||
clearInteractiveImportEpisodes,
|
||||
reprocessInteractiveImportItems
|
||||
} from 'Store/Actions/interactiveImportActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import SelectEpisodeModalContent from './SelectEpisodeModalContent';
|
||||
|
@ -21,10 +22,11 @@ function createMapStateToProps() {
|
|||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchInteractiveImportEpisodes,
|
||||
setInteractiveImportEpisodesSort,
|
||||
clearInteractiveImportEpisodes,
|
||||
updateInteractiveImportItem
|
||||
dispatchFetchInteractiveImportEpisodes: fetchInteractiveImportEpisodes,
|
||||
dispatchSetInteractiveImportEpisodesSort: setInteractiveImportEpisodesSort,
|
||||
dispatchClearInteractiveImportEpisodes: clearInteractiveImportEpisodes,
|
||||
dispatchUpdateInteractiveImportItem: updateInteractiveImportItem,
|
||||
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems
|
||||
};
|
||||
|
||||
class SelectEpisodeModalContentConnector extends Component {
|
||||
|
@ -38,26 +40,28 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||
seasonNumber
|
||||
} = this.props;
|
||||
|
||||
this.props.fetchInteractiveImportEpisodes({ seriesId, seasonNumber });
|
||||
this.props.dispatchFetchInteractiveImportEpisodes({ seriesId, seasonNumber });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// This clears the episodes for the queue and hides the queue
|
||||
// We'll need another place to store episodes for manual import
|
||||
this.props.clearInteractiveImportEpisodes();
|
||||
this.props.dispatchClearInteractiveImportEpisodes();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSortPress = (sortKey, sortDirection) => {
|
||||
this.props.setInteractiveImportEpisodesSort({ sortKey, sortDirection });
|
||||
this.props.dispatchSetInteractiveImportEpisodesSort({ sortKey, sortDirection });
|
||||
}
|
||||
|
||||
onEpisodesSelect = (episodeIds) => {
|
||||
const {
|
||||
ids,
|
||||
items,
|
||||
dispatchUpdateInteractiveImportItem,
|
||||
dispatchReprocessInteractiveImportItems,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
|
@ -78,12 +82,14 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||
const startingIndex = index * episodesPerFile;
|
||||
const episodes = sortedEpisodes.slice(startingIndex, startingIndex + episodesPerFile);
|
||||
|
||||
this.props.updateInteractiveImportItem({
|
||||
dispatchUpdateInteractiveImportItem({
|
||||
id,
|
||||
episodes
|
||||
});
|
||||
});
|
||||
|
||||
dispatchReprocessInteractiveImportItems({ ids });
|
||||
|
||||
onModalClose(true);
|
||||
}
|
||||
|
||||
|
@ -106,10 +112,11 @@ SelectEpisodeModalContentConnector.propTypes = {
|
|||
seriesId: PropTypes.number.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchInteractiveImportEpisodes: PropTypes.func.isRequired,
|
||||
setInteractiveImportEpisodesSort: PropTypes.func.isRequired,
|
||||
clearInteractiveImportEpisodes: PropTypes.func.isRequired,
|
||||
updateInteractiveImportItem: PropTypes.func.isRequired,
|
||||
dispatchFetchInteractiveImportEpisodes: PropTypes.func.isRequired,
|
||||
dispatchSetInteractiveImportEpisodesSort: PropTypes.func.isRequired,
|
||||
dispatchClearInteractiveImportEpisodes: PropTypes.func.isRequired,
|
||||
dispatchUpdateInteractiveImportItem: PropTypes.func.isRequired,
|
||||
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchLanguageProfileSchema } from 'Store/Actions/settingsActions';
|
||||
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import { updateInteractiveImportItems, reprocessInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import SelectLanguageModalContent from './SelectLanguageModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
@ -30,7 +30,8 @@ function createMapStateToProps() {
|
|||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchLanguageProfileSchema: fetchLanguageProfileSchema,
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
|
||||
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems
|
||||
};
|
||||
|
||||
class SelectLanguageModalContentConnector extends Component {
|
||||
|
@ -48,15 +49,23 @@ class SelectLanguageModalContentConnector extends Component {
|
|||
// Listeners
|
||||
|
||||
onLanguageSelect = ({ value }) => {
|
||||
const {
|
||||
ids,
|
||||
dispatchUpdateInteractiveImportItems,
|
||||
dispatchReprocessInteractiveImportItems
|
||||
} = this.props;
|
||||
|
||||
const languageId = parseInt(value);
|
||||
const language = _.find(this.props.items,
|
||||
(item) => item.language.id === languageId).language;
|
||||
|
||||
this.props.dispatchUpdateInteractiveImportItems({
|
||||
ids: this.props.ids,
|
||||
dispatchUpdateInteractiveImportItems({
|
||||
ids,
|
||||
language
|
||||
});
|
||||
|
||||
dispatchReprocessInteractiveImportItems({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
}
|
||||
|
||||
|
@ -81,6 +90,7 @@ SelectLanguageModalContentConnector.propTypes = {
|
|||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
dispatchFetchLanguageProfileSchema: PropTypes.func.isRequired,
|
||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
|||
import { createSelector } from 'reselect';
|
||||
import getQualities from 'Utilities/Quality/getQualities';
|
||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import { updateInteractiveImportItems, reprocessInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import SelectQualityModalContent from './SelectQualityModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
@ -31,7 +31,8 @@ function createMapStateToProps() {
|
|||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
|
||||
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems
|
||||
};
|
||||
|
||||
class SelectQualityModalContentConnector extends Component {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -89,6 +98,7 @@ SelectQualityModalContentConnector.propTypes = {
|
|||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
|
||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -169,6 +169,10 @@ export const actionHandlers = handleThunks({
|
|||
id,
|
||||
path: item.path,
|
||||
seriesId: item.series.id,
|
||||
season: item.season,
|
||||
episodeIds: item.episodes.map((e) => e.id),
|
||||
quality: item.quality,
|
||||
language: item.language,
|
||||
downloadId: item.downloadId
|
||||
};
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series);
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, DownloadClientItem downloadClientItem, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
||||
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, DownloadClientItem downloadClientItem, ParsedEpisodeInfo folderInfo, bool sceneSource, bool filterExistingFiles);
|
||||
ImportDecision GetDecision(LocalEpisode localEpisode, DownloadClientItem downloadClientItem);
|
||||
}
|
||||
|
||||
public class ImportDecisionMaker : IMakeImportDecision
|
||||
|
@ -90,6 +91,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
return decisions;
|
||||
}
|
||||
|
||||
public ImportDecision GetDecision(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var reasons = _specifications.Select(c => EvaluateSpec(c, localEpisode, downloadClientItem))
|
||||
.Where(c => c != null);
|
||||
|
||||
return new ImportDecision(localEpisode, reasons.ToArray());
|
||||
}
|
||||
|
||||
private ImportDecision GetDecision(LocalEpisode localEpisode, DownloadClientItem downloadClientItem, bool otherFiles)
|
||||
{
|
||||
ImportDecision decision = null;
|
||||
|
@ -150,14 +159,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
return decision;
|
||||
}
|
||||
|
||||
private ImportDecision GetDecision(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var reasons = _specifications.Select(c => EvaluateSpec(c, localEpisode, downloadClientItem))
|
||||
.Where(c => c != null);
|
||||
|
||||
return new ImportDecision(localEpisode, reasons.ToArray());
|
||||
}
|
||||
|
||||
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -9,11 +9,13 @@ using NzbDrone.Common.Instrumentation.Extensions;
|
|||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport.Aggregation;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
|
@ -21,7 +23,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||
public interface IManualImportService
|
||||
{
|
||||
List<ManualImportItem> GetMediaFiles(string path, string downloadId, int? seriesId, bool filterExistingFiles);
|
||||
ManualImportItem ReprocessItem(string path, string downloadId, int seriesId);
|
||||
ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, List<int> episodeIds, QualityModel quality, Language language);
|
||||
}
|
||||
|
||||
public class ManualImportService : IExecute<ManualImportCommand>, IManualImportService
|
||||
|
@ -94,11 +96,32 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||
return ProcessFolder(path, path, downloadId, seriesId, filterExistingFiles);
|
||||
}
|
||||
|
||||
public ManualImportItem ReprocessItem(string path, string downloadId, int seriesId)
|
||||
public ManualImportItem ReprocessItem(string path, string downloadId, int seriesId, List<int> episodeIds, QualityModel quality, Language language)
|
||||
{
|
||||
var rootFolder = Path.GetDirectoryName(path);
|
||||
var series = _seriesService.GetSeries(seriesId);
|
||||
|
||||
if (episodeIds.Any())
|
||||
{
|
||||
var downloadClientItem = GetTrackedDownload(downloadId)?.DownloadItem;
|
||||
|
||||
var localEpisode = new LocalEpisode
|
||||
{
|
||||
Series = series,
|
||||
Episodes = _episodeService.GetEpisodes(episodeIds),
|
||||
FileEpisodeInfo = Parser.Parser.ParsePath(path),
|
||||
DownloadClientEpisodeInfo = downloadClientItem == null ? null : Parser.Parser.ParseTitle(downloadClientItem.Title),
|
||||
Path = path,
|
||||
SceneSource = SceneSource(series, rootFolder),
|
||||
ExistingFile = series.Path.IsParentPath(path),
|
||||
Size = _diskProvider.GetFileSize(path),
|
||||
Language = language,
|
||||
Quality = quality
|
||||
};
|
||||
|
||||
return MapItem(_importDecisionMaker.GetDecision(localEpisode, downloadClientItem), rootFolder, downloadId, null);
|
||||
}
|
||||
|
||||
return ProcessFile(rootFolder, rootFolder, path, downloadId, series);
|
||||
}
|
||||
|
||||
|
@ -162,7 +185,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||
{
|
||||
try
|
||||
{
|
||||
DownloadClientItem downloadClientItem = null;
|
||||
var trackedDownload = GetTrackedDownload(downloadId);
|
||||
var relativeFile = baseFolder.GetRelativePath(file);
|
||||
|
||||
if (series == null)
|
||||
|
@ -175,15 +198,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||
series = _parsingService.GetSeries(relativeFile);
|
||||
}
|
||||
|
||||
if (downloadId.IsNotNullOrWhiteSpace())
|
||||
if (trackedDownload != null && series == null)
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
downloadClientItem = trackedDownload?.DownloadItem;
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
series = trackedDownload?.RemoteEpisode?.Series;
|
||||
}
|
||||
series = trackedDownload?.RemoteEpisode?.Series;
|
||||
}
|
||||
|
||||
if (series == null)
|
||||
|
@ -209,7 +226,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||
}
|
||||
|
||||
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file}, series,
|
||||
downloadClientItem, null, SceneSource(series, baseFolder));
|
||||
trackedDownload?.DownloadItem, null, SceneSource(series, baseFolder));
|
||||
|
||||
if (importDecisions.Any())
|
||||
{
|
||||
|
@ -236,6 +253,18 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||
return !(series.Path.PathEquals(folder) || series.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();
|
||||
|
|
|
@ -38,11 +38,14 @@ namespace Sonarr.Api.V3.ManualImport
|
|||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.SeriesId);
|
||||
var processedItem = _manualImportService.ReprocessItem(item.Path, item.DownloadId, item.SeriesId, item.EpisodeIds ?? new List<int>(), item.Quality, item.Language);
|
||||
|
||||
item.SeasonNumber = processedItem.SeasonNumber;
|
||||
item.Episodes = processedItem.Episodes.ToResource();
|
||||
item.Rejections = processedItem.Rejections;
|
||||
|
||||
// Clear episode IDs in favour of the full episode
|
||||
item.EpisodeIds = null;
|
||||
}
|
||||
|
||||
return items;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using Sonarr.Api.V3.Episodes;
|
||||
using Sonarr.Http.REST;
|
||||
|
||||
|
@ -11,6 +13,9 @@ namespace Sonarr.Api.V3.ManualImport
|
|||
public int SeriesId { get; set; }
|
||||
public int? SeasonNumber { get; set; }
|
||||
public List<EpisodeResource> Episodes { get; set; }
|
||||
public List<int> EpisodeIds { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public Language Language { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
|
|
Loading…
Reference in New Issue