using System; using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles.MovieImport.Aggregation; using NzbDrone.Core.Movies; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.MediaFiles.MovieImport { public interface IMakeImportDecision { List GetImportDecisions(List videoFiles, Movie movie); List GetImportDecisions(List videoFiles, Movie movie, bool filterExistingFiles); 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 { private readonly IEnumerable _specifications; private readonly IMediaFileService _mediaFileService; private readonly IAggregationService _aggregationService; private readonly IDiskProvider _diskProvider; private readonly IDetectSample _detectSample; private readonly IParsingService _parsingService; private readonly Logger _logger; public ImportDecisionMaker(IEnumerable specifications, IMediaFileService mediaFileService, IAggregationService aggregationService, IDiskProvider diskProvider, IDetectSample detectSample, IParsingService parsingService, Logger logger) { _specifications = specifications; _mediaFileService = mediaFileService; _aggregationService = aggregationService; _diskProvider = diskProvider; _detectSample = detectSample; _parsingService = parsingService; _logger = logger; } public List GetImportDecisions(List videoFiles, Movie movie) { return GetImportDecisions(videoFiles, movie, null, null, false); } public List GetImportDecisions(List videoFiles, Movie movie, bool filterExistingFiles) { return GetImportDecisions(videoFiles, movie, null, null, false, filterExistingFiles); } public List GetImportDecisions(List videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource) { return GetImportDecisions(videoFiles, movie, downloadClientItem, folderInfo, sceneSource, true); } public List GetImportDecisions(List videoFiles, Movie movie, DownloadClientItem downloadClientItem, ParsedMovieInfo folderInfo, bool sceneSource, bool filterExistingFiles) { var newFiles = filterExistingFiles ? _mediaFileService.FilterExistingFiles(videoFiles.ToList(), movie) : videoFiles.ToList(); _logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count); ParsedMovieInfo downloadClientItemInfo = null; if (downloadClientItem != null) { downloadClientItemInfo = Parser.Parser.ParseMovieTitle(downloadClientItem.Title); downloadClientItemInfo = _parsingService.EnhanceMovieInfo(downloadClientItemInfo); } var nonSampleVideoFileCount = GetNonSampleVideoFileCount(newFiles, movie); var decisions = new List(); foreach (var file in newFiles) { var localMovie = new LocalMovie { Movie = movie, DownloadClientMovieInfo = downloadClientItemInfo, FolderMovieInfo = folderInfo, Path = file, SceneSource = sceneSource, ExistingFile = movie.Path.IsParentPath(file) }; decisions.AddIfNotNull(GetDecision(localMovie, downloadClientItem, nonSampleVideoFileCount > 1)); } 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; var fileMovieInfo = Parser.Parser.ParseMoviePath(localMovie.Path); if (fileMovieInfo != null) { fileMovieInfo = _parsingService.EnhanceMovieInfo(fileMovieInfo); } localMovie.FileMovieInfo = fileMovieInfo; localMovie.Size = _diskProvider.GetFileSize(localMovie.Path); try { _aggregationService.Augment(localMovie, downloadClientItem, otherFiles); if (localMovie.Movie == null) { decision = new ImportDecision(localMovie, new Rejection("Invalid movie")); } else { decision = GetDecision(localMovie, downloadClientItem); } } catch (AugmentingFailedException) { decision = new ImportDecision(localMovie, new Rejection("Unable to parse file")); } catch (Exception ex) { _logger.Error(ex, "Couldn't import file. {0}", localMovie.Path); decision = new ImportDecision(localMovie, new Rejection("Unexpected error processing file")); } if (decision == null) { _logger.Error("Unable to make a decision on {0}", localMovie.Path); } else if (decision.Rejections.Any()) { _logger.Debug("File rejected for the following reasons: {0}", string.Join(", ", decision.Rejections)); } else { _logger.Debug("File accepted"); } return decision; } private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalMovie localMovie, DownloadClientItem downloadClientItem) { try { var result = spec.IsSatisfiedBy(localMovie, downloadClientItem); if (!result.Accepted) { return new Rejection(result.Reason); } } catch (NotImplementedException e) { _logger.Warn(e, "Spec " + spec.ToString() + " currently does not implement evaluation for movies."); return null; } catch (Exception ex) { _logger.Error(ex, "Couldn't evaluate decision on {0}", localMovie.Path); return new Rejection($"{spec.GetType().Name}: {ex.Message}"); } return null; } private int GetNonSampleVideoFileCount(List videoFiles, Movie movie) { return videoFiles.Count(file => { var sample = _detectSample.IsSample(movie, file); if (sample == DetectSampleResult.Sample) { return false; } return true; }); } } }