diff --git a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedMovieScanCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedMovieScanCommand.cs new file mode 100644 index 000000000..69e1bb34d --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedMovieScanCommand.cs @@ -0,0 +1,17 @@ +using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.MediaFiles.Commands +{ + public class DownloadedMovieScanCommand : Command + { + public override bool SendUpdatesToClient => SendUpdates; + + public bool SendUpdates { get; set; } + + // Properties used by third-party apps, do not modify. + public string Path { get; set; } + public string DownloadClientId { get; set; } + public ImportMode ImportMode { get; set; } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedMovieCommandService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedMovieCommandService.cs index 211f6f13c..5f9c5362f 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedMovieCommandService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedMovieCommandService.cs @@ -1,265 +1,107 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using NLog; +using NLog; using NzbDrone.Common.Disk; -using NzbDrone.Core.DecisionEngine; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.EpisodeImport; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Download; -using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Messaging.Commands; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; namespace NzbDrone.Core.MediaFiles { - public interface IDownloadedMovieImportService - { - List ProcessRootFolder(DirectoryInfo directoryInfo); - List ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null); - bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie); - } - - public class DownloadedMovieImportService : IDownloadedMovieImportService + public class DownloadedMovieCommandService : IExecute { + private readonly IDownloadedMovieImportService _downloadedMovieImportService; + private readonly ITrackedDownloadService _trackedDownloadService; private readonly IDiskProvider _diskProvider; - private readonly IDiskScanService _diskScanService; - private readonly IMovieService _movieService; - private readonly IParsingService _parsingService; - private readonly IMakeImportDecision _importDecisionMaker; - private readonly IImportApprovedMovie _importApprovedMovie; - private readonly IDetectSample _detectSample; + private readonly IConfigService _configService; private readonly Logger _logger; - public DownloadedMovieImportService(IDiskProvider diskProvider, - IDiskScanService diskScanService, - IMovieService movieService, - IParsingService parsingService, - IMakeImportDecision importDecisionMaker, - IImportApprovedMovie importApprovedMovie, - IDetectSample detectSample, - Logger logger) + public DownloadedMovieCommandService(IDownloadedMovieImportService downloadedMovieImportService, + ITrackedDownloadService trackedDownloadService, + IDiskProvider diskProvider, + IConfigService configService, + Logger logger) { + _downloadedMovieImportService = downloadedMovieImportService; + _trackedDownloadService = trackedDownloadService; _diskProvider = diskProvider; - _diskScanService = diskScanService; - _movieService = movieService; - _parsingService = parsingService; - _importDecisionMaker = importDecisionMaker; - _importApprovedMovie = importApprovedMovie; - _detectSample = detectSample; + _configService = configService; _logger = logger; } - public List ProcessRootFolder(DirectoryInfo directoryInfo) + private List ProcessDroneFactoryFolder() { - var results = new List(); + var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder; - foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName)) + if (string.IsNullOrEmpty(downloadedEpisodesFolder)) { - var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null); - results.AddRange(folderResults); + _logger.Trace("Drone Factory folder is not configured"); + return new List(); } - foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false)) + if (!_diskProvider.FolderExists(downloadedEpisodesFolder)) { - var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null); - results.AddRange(fileResults); + _logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder); + return new List(); } - return results; + return _downloadedMovieImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder)); } - public List ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null) + private List ProcessPath(DownloadedMovieScanCommand message) { - if (_diskProvider.FolderExists(path)) + if (!_diskProvider.FolderExists(message.Path) && !_diskProvider.FileExists(message.Path)) { - var directoryInfo = new DirectoryInfo(path); - - if (movie == null) - { - return ProcessFolder(directoryInfo, importMode, downloadClientItem); - } - - return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem); + _logger.Warn("Folder/File specified for import scan [{0}] doesn't exist.", message.Path); + return new List(); } - if (_diskProvider.FileExists(path)) + if (message.DownloadClientId.IsNotNullOrWhiteSpace()) { - var fileInfo = new FileInfo(path); + var trackedDownload = _trackedDownloadService.Find(message.DownloadClientId); - if (movie == null) + if (trackedDownload != null) { - return ProcessFile(fileInfo, importMode, downloadClientItem); - } + _logger.Debug("External directory scan request for known download {0}. [{1}]", message.DownloadClientId, message.Path); - return ProcessFile(fileInfo, importMode, movie, downloadClientItem); - } - - _logger.Error("Import failed, path does not exist or is not accessible by Sonarr: {0}", path); - return new List(); - } - - public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie) - { - var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); - var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar"); - - foreach (var videoFile in videoFiles) - { - var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile)); - - if (episodeParseResult == null) - { - _logger.Warn("Unable to parse file on import: [{0}]", videoFile); - return false; + return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode, trackedDownload.RemoteMovie.Movie, trackedDownload.DownloadItem); } - - var size = _diskProvider.GetFileSize(videoFile); - var quality = QualityParser.ParseQuality(videoFile); - - if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode)) + else { - _logger.Warn("Non-sample file detected: [{0}]", videoFile); - return false; - } - } - - if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes())) - { - _logger.Warn("RAR file detected, will require manual cleanup"); - return false; - } - - return true; - } - - private List ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem) - { - var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); - var movie = _parsingService.GetMovie(cleanedUpName); - - if (movie == null) - { - _logger.Debug("Unknown Movie {0}", cleanedUpName); - - return new List - { - UnknownMovieResult("Unknown Movie") - }; - } + _logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path); - return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem); - } - - private List ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem) - { - if (_movieService.MoviePathExists(directoryInfo.FullName)) - { - _logger.Warn("Unable to process folder that is mapped to an existing show"); - return new List(); - } - - var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); - var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name); - - if (folderInfo != null) - { - _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality); - } - - var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); - - if (downloadClientItem == null) - { - foreach (var videoFile in videoFiles) - { - if (_diskProvider.IsFileLocked(videoFile)) - { - return new List - { - FileIsLockedResult(videoFile) - }; - } + return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode); } } - var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true); - var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode); - - if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && - importResults.Any(i => i.Result == ImportResultType.Imported) && - ShouldDeleteFolder(directoryInfo, movie)) - { - _logger.Debug("Deleting folder after importing valid files"); - _diskProvider.DeleteFolder(directoryInfo.FullName, true); - } - - return importResults; + return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode); } - private List ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem) + public void Execute(DownloadedMovieScanCommand message) { - var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name)); + List importResults; - if (movie == null) + if (message.Path.IsNotNullOrWhiteSpace()) { - _logger.Debug("Unknown Movie for file: {0}", fileInfo.Name); - - return new List - { - UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName) - }; + importResults = ProcessPath(message); } - - return ProcessFile(fileInfo, importMode, movie, downloadClientItem); - } - - private List ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem) - { - if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._")) + else { - _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName); - - return new List - { - new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'") - }; + importResults = ProcessDroneFactoryFolder(); } - if (downloadClientItem == null) + if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported)) { - if (_diskProvider.IsFileLocked(fileInfo.FullName)) - { - return new List - { - FileIsLockedResult(fileInfo.FullName) - }; - } + // Atm we don't report it as a command failure, coz that would cause the download to be failed. + // Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later. + //message.SetMessage("Failed to import"); } - - var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, movie, null, true); - - return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode); - } - - private string GetCleanedUpFolderName(string folder) - { - folder = folder.Replace("_UNPACK_", "") - .Replace("_FAILED_", ""); - - return folder; - } - - private ImportResult FileIsLockedResult(string videoFile) - { - _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); - return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later"); - } - - private ImportResult UnknownMovieResult(string message, string videoFile = null) - { - var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile }; - - return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message); } } } diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs new file mode 100644 index 000000000..05617dffe --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs @@ -0,0 +1,266 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.MediaFiles.Commands; + +namespace NzbDrone.Core.MediaFiles +{ + public interface IDownloadedMovieImportService + { + List ProcessRootFolder(DirectoryInfo directoryInfo); + List ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null); + bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie); + } + + public class DownloadedMovieImportService : IDownloadedMovieImportService + { + private readonly IDiskProvider _diskProvider; + private readonly IDiskScanService _diskScanService; + private readonly IMovieService _movieService; + private readonly IParsingService _parsingService; + private readonly IMakeImportDecision _importDecisionMaker; + private readonly IImportApprovedMovie _importApprovedMovie; + private readonly IDetectSample _detectSample; + private readonly Logger _logger; + + public DownloadedMovieImportService(IDiskProvider diskProvider, + IDiskScanService diskScanService, + IMovieService movieService, + IParsingService parsingService, + IMakeImportDecision importDecisionMaker, + IImportApprovedMovie importApprovedMovie, + IDetectSample detectSample, + Logger logger) + { + _diskProvider = diskProvider; + _diskScanService = diskScanService; + _movieService = movieService; + _parsingService = parsingService; + _importDecisionMaker = importDecisionMaker; + _importApprovedMovie = importApprovedMovie; + _detectSample = detectSample; + _logger = logger; + } + + public List ProcessRootFolder(DirectoryInfo directoryInfo) + { + var results = new List(); + + foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName)) + { + var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null); + results.AddRange(folderResults); + } + + foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false)) + { + var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null); + results.AddRange(fileResults); + } + + return results; + } + + public List ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Movie movie = null, DownloadClientItem downloadClientItem = null) + { + if (_diskProvider.FolderExists(path)) + { + var directoryInfo = new DirectoryInfo(path); + + if (movie == null) + { + return ProcessFolder(directoryInfo, importMode, downloadClientItem); + } + + return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem); + } + + if (_diskProvider.FileExists(path)) + { + var fileInfo = new FileInfo(path); + + if (movie == null) + { + return ProcessFile(fileInfo, importMode, downloadClientItem); + } + + return ProcessFile(fileInfo, importMode, movie, downloadClientItem); + } + + _logger.Error("Import failed, path does not exist or is not accessible by Radarr: {0}", path); + return new List(); + } + + public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie) + { + var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); + var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar"); + + foreach (var videoFile in videoFiles) + { + var episodeParseResult = Parser.Parser.ParseTitle(Path.GetFileName(videoFile)); + + if (episodeParseResult == null) + { + _logger.Warn("Unable to parse file on import: [{0}]", videoFile); + return false; + } + + var size = _diskProvider.GetFileSize(videoFile); + var quality = QualityParser.ParseQuality(videoFile); + + if (!_detectSample.IsSample(movie, quality, videoFile, size, episodeParseResult.IsPossibleSpecialEpisode)) + { + _logger.Warn("Non-sample file detected: [{0}]", videoFile); + return false; + } + } + + if (rarFiles.Any(f => _diskProvider.GetFileSize(f) > 10.Megabytes())) + { + _logger.Warn("RAR file detected, will require manual cleanup"); + return false; + } + + return true; + } + + private List ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem) + { + var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); + var movie = _parsingService.GetMovie(cleanedUpName); + + if (movie == null) + { + _logger.Debug("Unknown Movie {0}", cleanedUpName); + + return new List + { + UnknownMovieResult("Unknown Movie") + }; + } + + return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem); + } + + private List ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem) + { + if (_movieService.MoviePathExists(directoryInfo.FullName)) + { + _logger.Warn("Unable to process folder that is mapped to an existing show"); + return new List(); + } + + var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); + var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name); + + if (folderInfo != null) + { + _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality); + } + + var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); + + if (downloadClientItem == null) + { + foreach (var videoFile in videoFiles) + { + if (_diskProvider.IsFileLocked(videoFile)) + { + return new List + { + FileIsLockedResult(videoFile) + }; + } + } + } + + var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true); + var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode); + + if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && + importResults.Any(i => i.Result == ImportResultType.Imported) && + ShouldDeleteFolder(directoryInfo, movie)) + { + _logger.Debug("Deleting folder after importing valid files"); + _diskProvider.DeleteFolder(directoryInfo.FullName, true); + } + + return importResults; + } + + private List ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem) + { + var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name)); + + if (movie == null) + { + _logger.Debug("Unknown Movie for file: {0}", fileInfo.Name); + + return new List + { + UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName) + }; + } + + return ProcessFile(fileInfo, importMode, movie, downloadClientItem); + } + + private List ProcessFile(FileInfo fileInfo, ImportMode importMode, Movie movie, DownloadClientItem downloadClientItem) + { + if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._")) + { + _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName); + + return new List + { + new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'") + }; + } + + if (downloadClientItem == null) + { + if (_diskProvider.IsFileLocked(fileInfo.FullName)) + { + return new List + { + FileIsLockedResult(fileInfo.FullName) + }; + } + } + + var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, movie, null, true); + + return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode); + } + + private string GetCleanedUpFolderName(string folder) + { + folder = folder.Replace("_UNPACK_", "") + .Replace("_FAILED_", ""); + + return folder; + } + + private ImportResult FileIsLockedResult(string videoFile) + { + _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); + return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later"); + } + + private ImportResult UnknownMovieResult(string message, string videoFile = null) + { + var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile }; + + return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs index 4c9fecc7c..d9f089179 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportFile.cs @@ -10,5 +10,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual public List EpisodeIds { get; set; } public QualityModel Quality { get; set; } public string DownloadId { get; set; } + public int MovieId { get; set; } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs index bd3954816..ecca2739c 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportItem.cs @@ -17,5 +17,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual public QualityModel Quality { get; set; } public string DownloadId { get; set; } public IEnumerable Rejections { get; set; } + public Movie Movie { get; set; } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index d85a2e119..e8f228327 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -30,11 +30,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual private readonly IDiskScanService _diskScanService; private readonly IMakeImportDecision _importDecisionMaker; private readonly ISeriesService _seriesService; + private readonly IMovieService _movieService; private readonly IEpisodeService _episodeService; private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IImportApprovedEpisodes _importApprovedEpisodes; + private readonly IImportApprovedMovie _importApprovedMovie; private readonly ITrackedDownloadService _trackedDownloadService; private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; + private readonly IDownloadedMovieImportService _downloadedMovieImportService; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; @@ -43,11 +46,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual IDiskScanService diskScanService, IMakeImportDecision importDecisionMaker, ISeriesService seriesService, + IMovieService movieService, IEpisodeService episodeService, IVideoFileInfoReader videoFileInfoReader, IImportApprovedEpisodes importApprovedEpisodes, + IImportApprovedMovie importApprovedMovie, ITrackedDownloadService trackedDownloadService, IDownloadedEpisodesImportService downloadedEpisodesImportService, + IDownloadedMovieImportService downloadedMovieImportService, IEventAggregator eventAggregator, Logger logger) { @@ -56,11 +62,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual _diskScanService = diskScanService; _importDecisionMaker = importDecisionMaker; _seriesService = seriesService; + _movieService = movieService; _episodeService = episodeService; _videoFileInfoReader = videoFileInfoReader; _importApprovedEpisodes = importApprovedEpisodes; + _importApprovedMovie = importApprovedMovie; _trackedDownloadService = trackedDownloadService; _downloadedEpisodesImportService = downloadedEpisodesImportService; + _downloadedMovieImportService = downloadedMovieImportService; _eventAggregator = eventAggregator; _logger = logger; } @@ -126,62 +135,128 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual var relativeFile = folder.GetRelativePath(file); - var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]); + var movie = _parsingService.GetMovie(relativeFile.Split('\\', '/')[0]); - if (series == null) + if (movie == null) { - series = _parsingService.GetSeries(relativeFile); + movie = _parsingService.GetMovie(relativeFile); } - if (series == null && downloadId.IsNotNullOrWhiteSpace()) + if (movie == null && downloadId.IsNotNullOrWhiteSpace()) { var trackedDownload = _trackedDownloadService.Find(downloadId); - series = trackedDownload.RemoteEpisode.Series; + movie = trackedDownload.RemoteMovie.Movie; } - if (series == null) + if (movie == null) { - var localEpisode = new LocalEpisode(); - localEpisode.Path = file; - localEpisode.Quality = QualityParser.ParseQuality(file); - localEpisode.Size = _diskProvider.GetFileSize(file); + var localMovie = new LocalMovie() + { + Path = file, + Quality = QualityParser.ParseQuality(file), + Size = _diskProvider.GetFileSize(file) + }; - return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId); + return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), folder, downloadId); } - var importDecisions = _importDecisionMaker.GetImportDecisions(new List {file}, - series, null, SceneSource(series, folder)); + var importDecisions = _importDecisionMaker.GetImportDecisions(new List { file }, + movie, null, SceneSource(movie, folder)); return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; } + //private ManualImportItem ProcessFile(string file, string downloadId, string folder = null) + //{ + // if (folder.IsNullOrWhiteSpace()) + // { + // folder = new FileInfo(file).Directory.FullName; + // } + + // var relativeFile = folder.GetRelativePath(file); + + // var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]); + + // if (series == null) + // { + // series = _parsingService.GetSeries(relativeFile); + // } + + // if (series == null && downloadId.IsNotNullOrWhiteSpace()) + // { + // var trackedDownload = _trackedDownloadService.Find(downloadId); + // series = trackedDownload.RemoteEpisode.Series; + // } + + // if (series == null) + // { + // var localEpisode = new LocalEpisode(); + // localEpisode.Path = file; + // localEpisode.Quality = QualityParser.ParseQuality(file); + // localEpisode.Size = _diskProvider.GetFileSize(file); + + // return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId); + // } + + // var importDecisions = _importDecisionMaker.GetImportDecisions(new List {file}, + // series, null, SceneSource(series, folder)); + + // return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; + //} + private bool SceneSource(Series series, string folder) { return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder)); } + private bool SceneSource(Movie movie, string folder) + { + return !(movie.Path.PathEquals(folder) || movie.Path.IsParentPath(folder)); + } + + //private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId) + //{ + // var item = new ManualImportItem(); + + // item.Path = decision.LocalEpisode.Path; + // item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); + // item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); + // item.DownloadId = downloadId; + + // if (decision.LocalEpisode.Series != null) + // { + // item.Series = decision.LocalEpisode.Series; + // } + + // if (decision.LocalEpisode.Episodes.Any()) + // { + // item.SeasonNumber = decision.LocalEpisode.SeasonNumber; + // item.Episodes = decision.LocalEpisode.Episodes; + // } + + // item.Quality = decision.LocalEpisode.Quality; + // item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path); + // item.Rejections = decision.Rejections; + + // return item; + //} + private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId) { var item = new ManualImportItem(); - item.Path = decision.LocalEpisode.Path; - item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); - item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); + item.Path = decision.LocalMovie.Path; + item.RelativePath = folder.GetRelativePath(decision.LocalMovie.Path); + item.Name = Path.GetFileNameWithoutExtension(decision.LocalMovie.Path); item.DownloadId = downloadId; - if (decision.LocalEpisode.Series != null) + if (decision.LocalMovie.Movie != null) { - item.Series = decision.LocalEpisode.Series; + item.Movie = decision.LocalMovie.Movie; } - if (decision.LocalEpisode.Episodes.Any()) - { - item.SeasonNumber = decision.LocalEpisode.SeasonNumber; - item.Episodes = decision.LocalEpisode.Episodes; - } - - item.Quality = decision.LocalEpisode.Quality; - item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path); + item.Quality = decision.LocalMovie.Quality; + item.Size = _diskProvider.GetFileSize(decision.LocalMovie.Path); item.Rejections = decision.Rejections; return item; @@ -199,45 +274,43 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); var file = message.Files[i]; - var series = _seriesService.GetSeries(file.SeriesId); - var episodes = _episodeService.GetEpisodes(file.EpisodeIds); - var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo(); + var movie = _movieService.GetMovie(file.MovieId); + var parsedMovieInfo = Parser.Parser.ParseMoviePath(file.Path) ?? new ParsedMovieInfo(); var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); - var existingFile = series.Path.IsParentPath(file.Path); + var existingFile = movie.Path.IsParentPath(file.Path); - var localEpisode = new LocalEpisode + var localMovie = new LocalMovie { ExistingFile = false, - Episodes = episodes, MediaInfo = mediaInfo, - ParsedEpisodeInfo = parsedEpisodeInfo, + ParsedMovieInfo = parsedMovieInfo, Path = file.Path, Quality = file.Quality, - Series = series, + Movie = movie, Size = 0 }; //TODO: Cleanup non-tracked downloads - var importDecision = new ImportDecision(localEpisode); + var importDecision = new ImportDecision(localMovie); if (file.DownloadId.IsNullOrWhiteSpace()) { - imported.AddRange(_importApprovedEpisodes.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); + imported.AddRange(_importApprovedMovie.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); } else { var trackedDownload = _trackedDownloadService.Find(file.DownloadId); - var importResult = _importApprovedEpisodes.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); + var importResult = _importApprovedMovie.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); imported.Add(importResult); importedTrackedDownload.Add(new ManuallyImportedFile - { - TrackedDownload = trackedDownload, - ImportResult = importResult - }); + { + TrackedDownload = trackedDownload, + ImportResult = importResult + }); } } @@ -249,20 +322,98 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) { - if (_downloadedEpisodesImportService.ShouldDeleteFolder( + if (_downloadedMovieImportService.ShouldDeleteFolder( new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), - trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) + trackedDownload.RemoteMovie.Movie) && !trackedDownload.DownloadItem.IsReadOnly) { _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); } } - if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count)) + if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, 1)) //TODO: trackedDownload.RemoteMovie.Movie.Count is always 1? { trackedDownload.State = TrackedDownloadStage.Imported; _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); } } } + + //public void Execute(ManualImportCommand message) + //{ + // _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode); + + // var imported = new List(); + // var importedTrackedDownload = new List(); + + // for (int i = 0; i < message.Files.Count; i++) + // { + // _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); + + // var file = message.Files[i]; + // var series = _seriesService.GetSeries(file.SeriesId); + // var episodes = _episodeService.GetEpisodes(file.EpisodeIds); + // var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo(); + // var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); + // var existingFile = series.Path.IsParentPath(file.Path); + + // var localEpisode = new LocalEpisode + // { + // ExistingFile = false, + // Episodes = episodes, + // MediaInfo = mediaInfo, + // ParsedEpisodeInfo = parsedEpisodeInfo, + // Path = file.Path, + // Quality = file.Quality, + // Series = series, + // Size = 0 + // }; + + // //TODO: Cleanup non-tracked downloads + + // var importDecision = new ImportDecision(localEpisode); + + // if (file.DownloadId.IsNullOrWhiteSpace()) + // { + // imported.AddRange(_importApprovedEpisodes.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); + // } + + // else + // { + // var trackedDownload = _trackedDownloadService.Find(file.DownloadId); + // var importResult = _importApprovedEpisodes.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); + + // imported.Add(importResult); + + // importedTrackedDownload.Add(new ManuallyImportedFile + // { + // TrackedDownload = trackedDownload, + // ImportResult = importResult + // }); + // } + // } + + // _logger.ProgressTrace("Manually imported {0} files", imported.Count); + + // foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList()) + // { + // var trackedDownload = groupedTrackedDownload.First().TrackedDownload; + + // if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) + // { + // if (_downloadedEpisodesImportService.ShouldDeleteFolder( + // new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), + // trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) + // { + // _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); + // } + // } + + // if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count)) + // { + // trackedDownload.State = TrackedDownloadStage.Imported; + // _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); + // } + // } + //} } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 6dbc3ebc5..3ea6a0a76 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -705,10 +705,12 @@ + + diff --git a/src/UI/ManualImport/Cells/MovieCell.js b/src/UI/ManualImport/Cells/MovieCell.js new file mode 100644 index 000000000..8849f8ea3 --- /dev/null +++ b/src/UI/ManualImport/Cells/MovieCell.js @@ -0,0 +1,43 @@ +var vent = require('../../vent'); +var NzbDroneCell = require('../../Cells/NzbDroneCell'); +var SelectMovieLayout = require('../Movie/SelectMovieLayout'); + +module.exports = NzbDroneCell.extend({ + className : 'series-title-cell editable', + + events : { + 'click' : '_onClick' + }, + + render : function() { + this.$el.empty(); + + var movie = this.model.get('movie'); + + if (movie) + { + this.$el.html(movie.title + " (" + movie.year + ")" ); + } + + this.delegateEvents(); + return this; + }, + + _onClick : function () { + var view = new SelectMovieLayout(); + + this.listenTo(view, 'manualimport:selected:movie', this._setMovie); + + vent.trigger(vent.Commands.OpenModal2Command, view); + }, + + _setMovie : function (e) { + if (this.model.has('movie') && e.model.id === this.model.get('movie').id) { + return; + } + + this.model.set({ + movie : e.model.toJSON() + }); + } +}); \ No newline at end of file diff --git a/src/UI/ManualImport/ManualImportLayout.js b/src/UI/ManualImport/ManualImportLayout.js index ba5a139fc..97c20e75d 100644 --- a/src/UI/ManualImport/ManualImportLayout.js +++ b/src/UI/ManualImport/ManualImportLayout.js @@ -16,6 +16,7 @@ var QualityCell = require('./Cells/QualityCell'); var FileSizeCell = require('../Cells/FileSizeCell'); var ApprovalStatusCell = require('../Cells/ApprovalStatusCell'); var ManualImportCollection = require('./ManualImportCollection'); +var MovieCell = require('./Cells/MovieCell'); var Messenger = require('../Shared/Messenger'); module.exports = Marionette.Layout.extend({ @@ -49,23 +50,29 @@ module.exports = Marionette.Layout.extend({ sortable : true }, { - name : 'series', - label : 'Series', - cell : SeriesCell, + name : 'movie', + label : 'Movie', + cell : MovieCell, sortable : true }, - { - name : 'seasonNumber', - label : 'Season', - cell : SeasonCell, - sortable : true - }, - { - name : 'episodes', - label : 'Episode(s)', - cell : EpisodesCell, - sortable : false - }, + // { + // name : 'series', + // label : 'Series', + // cell : SeriesCell, + // sortable : true + // }, + // { + // name : 'seasonNumber', + // label : 'Season', + // cell : SeasonCell, + // sortable : true + // }, + // { + // name : 'episodes', + // label : 'Episode(s)', + // cell : EpisodesCell, + // sortable : false + // }, { name : 'quality', label : 'Quality', @@ -161,8 +168,8 @@ module.exports = Marionette.Layout.extend({ }, _automaticImport : function (e) { - CommandController.Execute('downloadedEpisodesScan', { - name : 'downloadedEpisodesScan', + CommandController.Execute('downloadedMovieScan', { + name : 'downloadedMovieScan', path : e.folder }); @@ -176,29 +183,36 @@ module.exports = Marionette.Layout.extend({ return; } - if (_.any(selected, function (model) { - return !model.has('series'); - })) { - - this._showErrorMessage('Series must be chosen for each selected file'); + if(_.any(selected, function(model) { + return !model.has('movie'); + })) { + this._showErrorMessage('Movie must be chosen for each selected file'); return; } - if (_.any(selected, function (model) { - return !model.has('seasonNumber'); - })) { + // if (_.any(selected, function (model) { + // return !model.has('series'); + // })) { - this._showErrorMessage('Season must be chosen for each selected file'); - return; - } + // this._showErrorMessage('Series must be chosen for each selected file'); + // return; + // } - if (_.any(selected, function (model) { - return !model.has('episodes') || model.get('episodes').length === 0; - })) { + // if (_.any(selected, function (model) { + // return !model.has('seasonNumber'); + // })) { - this._showErrorMessage('One or more episodes must be chosen for each selected file'); - return; - } + // this._showErrorMessage('Season must be chosen for each selected file'); + // return; + // } + + // if (_.any(selected, function (model) { + // return !model.has('episodes') || model.get('episodes').length === 0; + // })) { + + // this._showErrorMessage('One or more episodes must be chosen for each selected file'); + // return; + // } var importMode = this.ui.importMode.val(); @@ -207,8 +221,9 @@ module.exports = Marionette.Layout.extend({ files : _.map(selected, function (file) { return { path : file.get('path'), - seriesId : file.get('series').id, - episodeIds : _.map(file.get('episodes'), 'id'), + movieId : file.get('movie').id, + // seriesId : file.get('series').id, + // episodeIds : _.map(file.get('episodes'), 'id'), quality : file.get('quality'), downloadId : file.get('downloadId') }; diff --git a/src/UI/ManualImport/Movie/SelectMovieLayout.js b/src/UI/ManualImport/Movie/SelectMovieLayout.js new file mode 100644 index 000000000..55799a2c5 --- /dev/null +++ b/src/UI/ManualImport/Movie/SelectMovieLayout.js @@ -0,0 +1,101 @@ +var _ = require('underscore'); +var vent = require('vent'); +var Marionette = require('marionette'); +var Backgrid = require('backgrid'); +var MoviesCollection = require('../../Movies/MoviesCollection'); +var SelectRow = require('./SelectMovieRow'); + +module.exports = Marionette.Layout.extend({ + template : 'ManualImport/Movie/SelectMovieLayoutTemplate', + + regions : { + movie : '.x-movie' + }, + + ui : { + filter : '.x-filter' + }, + + columns : [ + { + name : 'title', + label : 'Title', + cell : 'String', + sortValue : 'sortTitle' + } + ], + + initialize : function() { + this.movieCollection = MoviesCollection.clone(); + this._setModelCollection(); + + this.listenTo(this.movieCollection, 'row:selected', this._onSelected); + this.listenTo(this, 'modal:afterShow', this._setFocus); + }, + + onRender : function() { + this.movieView = new Backgrid.Grid({ + columns : this.columns, + collection : this.movieCollection, + className : 'table table-hover season-grid', + row : SelectRow + }); + + this.movie.show(this.movieView); + this._setupFilter(); + }, + + _setupFilter : function () { + var self = this; + + //TODO: This should be a mixin (same as Add Series searching) + this.ui.filter.keyup(function(e) { + if (_.contains([ + 9, + 16, + 17, + 18, + 19, + 20, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 91, + 92, + 93 + ], e.keyCode)) { + return; + } + + self._filter(self.ui.filter.val()); + }); + }, + + _filter : function (term) { + this.movieCollection.setFilter(['title', term, 'contains']); + this._setModelCollection(); + }, + + _onSelected : function (e) { + this.trigger('manualimport:selected:movie', { model: e.model }); + + vent.trigger(vent.Commands.CloseModal2Command); + }, + + _setFocus : function () { + this.ui.filter.focus(); + }, + + _setModelCollection: function () { + var self = this; + + _.each(this.movieCollection.models, function (model) { + model.collection = self.movieCollection; + }); + } +}); diff --git a/src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs b/src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs new file mode 100644 index 000000000..25b3c39d4 --- /dev/null +++ b/src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs @@ -0,0 +1,30 @@ + + + diff --git a/src/UI/ManualImport/Movie/SelectMovieRow.js b/src/UI/ManualImport/Movie/SelectMovieRow.js new file mode 100644 index 000000000..38a2d5ca6 --- /dev/null +++ b/src/UI/ManualImport/Movie/SelectMovieRow.js @@ -0,0 +1,13 @@ +var Backgrid = require('backgrid'); + +module.exports = Backgrid.Row.extend({ + className : 'select-row select-series-row', + + events : { + 'click' : '_onClick' + }, + + _onClick : function() { + this.model.collection.trigger('row:selected', { model: this.model }); + } +}); \ No newline at end of file diff --git a/src/UI/ManualImport/Summary/ManualImportSummaryView.js b/src/UI/ManualImport/Summary/ManualImportSummaryView.js index a4ab847c2..141f2ca26 100644 --- a/src/UI/ManualImport/Summary/ManualImportSummaryView.js +++ b/src/UI/ManualImport/Summary/ManualImportSummaryView.js @@ -4,16 +4,25 @@ var Marionette = require('marionette'); module.exports = Marionette.ItemView.extend({ template : 'ManualImport/Summary/ManualImportSummaryViewTemplate', + // initialize : function (options) { + // var episodes = _.map(options.episodes, function (episode) { + // return episode.toJSON(); + // }); + + // this.templateHelpers = { + // file : options.file, + // series : options.series, + // season : options.season, + // episodes : episodes, + // quality : options.quality + // }; + // } + initialize : function (options) { - var episodes = _.map(options.episodes, function (episode) { - return episode.toJSON(); - }); this.templateHelpers = { file : options.file, - series : options.series, - season : options.season, - episodes : episodes, + movie : options.movie, quality : options.quality }; } diff --git a/src/UI/ManualImport/Summary/ManualImportSummaryViewTemplate.hbs b/src/UI/ManualImport/Summary/ManualImportSummaryViewTemplate.hbs index d65ff52f1..36497083e 100644 --- a/src/UI/ManualImport/Summary/ManualImportSummaryViewTemplate.hbs +++ b/src/UI/ManualImport/Summary/ManualImportSummaryViewTemplate.hbs @@ -3,16 +3,8 @@
Path:
{{file}}
-
Series:
-
{{series.title}}
- -
Season:
-
{{season.seasonNumber}}
- - {{#each episodes}} -
Episode:
-
{{episodeNumber}} - {{title}}
- {{/each}} +
Movie:
+
{{movie.title}} ({{movie.year}})
Quality:
{{quality.name}}
diff --git a/src/UI/Rename/RenamePreviewLayout.js b/src/UI/Rename/RenamePreviewLayout.js index a37ffeb37..f8b26658e 100644 --- a/src/UI/Rename/RenamePreviewLayout.js +++ b/src/UI/Rename/RenamePreviewLayout.js @@ -78,7 +78,7 @@ module.exports = Marionette.Layout.extend({ CommandController.Execute('renameMovieFiles', { name : 'renameMovieFiles', - movieId : this.model.id, + movieId : this.model.id, files : files }); diff --git a/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs b/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs index 9043ad2f5..a10f5d234 100644 --- a/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs +++ b/src/UI/Settings/DownloadClient/DroneFactory/DroneFactoryViewTemplate.hbs @@ -23,7 +23,7 @@
- +
\ No newline at end of file diff --git a/src/UI/Shared/Modal/ModalController.js b/src/UI/Shared/Modal/ModalController.js index 9aaaeeeb1..4392967df 100644 --- a/src/UI/Shared/Modal/ModalController.js +++ b/src/UI/Shared/Modal/ModalController.js @@ -10,6 +10,7 @@ var LogDetailsView = require('../../System/Logs/Table/Details/LogDetailsView'); var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout'); var ManualImportLayout = require('../../ManualImport/ManualImportLayout'); var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout'); +var MoviesDetailsLayout = require('../../Movies/Details/MoviesDetailsLayout'); module.exports = Marionette.AppRouter.extend({ initialize : function() { diff --git a/src/UI/Wanted/Missing/MissingLayout.js b/src/UI/Wanted/Missing/MissingLayout.js index 3adb4876b..e6a8ce33d 100644 --- a/src/UI/Wanted/Missing/MissingLayout.js +++ b/src/UI/Wanted/Missing/MissingLayout.js @@ -133,7 +133,7 @@ module.exports = Marionette.Layout.extend({ { title : 'Rescan Drone Factory Folder', icon : 'icon-sonarr-refresh', - command : 'downloadedepisodesscan', + command : 'downloadedMovieScan', properties : { sendUpdates : true } }, {