Merge pull request #133 from Radarr/fix-manual-import

Fix Manual Import & Drone Factory
This commit is contained in:
Leonardo Galli 2017-01-11 16:39:09 +01:00 committed by GitHub
commit 3b5887bf09
18 changed files with 810 additions and 326 deletions

View file

@ -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; }
}
}

View file

@ -1,265 +1,107 @@
using System.Collections.Generic; using NLog;
using NzbDrone.Common.Disk;
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.Messaging.Commands;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NLog; using System.Text;
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;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
public interface IDownloadedMovieImportService public class DownloadedMovieCommandService : IExecute<DownloadedMovieScanCommand>
{
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
List<ImportResult> 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 IDownloadedMovieImportService _downloadedMovieImportService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService; private readonly IConfigService _configService;
private readonly IMovieService _movieService;
private readonly IParsingService _parsingService;
private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedMovie _importApprovedMovie;
private readonly IDetectSample _detectSample;
private readonly Logger _logger; private readonly Logger _logger;
public DownloadedMovieImportService(IDiskProvider diskProvider, public DownloadedMovieCommandService(IDownloadedMovieImportService downloadedMovieImportService,
IDiskScanService diskScanService, ITrackedDownloadService trackedDownloadService,
IMovieService movieService, IDiskProvider diskProvider,
IParsingService parsingService, IConfigService configService,
IMakeImportDecision importDecisionMaker,
IImportApprovedMovie importApprovedMovie,
IDetectSample detectSample,
Logger logger) Logger logger)
{ {
_downloadedMovieImportService = downloadedMovieImportService;
_trackedDownloadService = trackedDownloadService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_diskScanService = diskScanService; _configService = configService;
_movieService = movieService;
_parsingService = parsingService;
_importDecisionMaker = importDecisionMaker;
_importApprovedMovie = importApprovedMovie;
_detectSample = detectSample;
_logger = logger; _logger = logger;
} }
public List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo) private List<ImportResult> ProcessDroneFactoryFolder()
{ {
var results = new List<ImportResult>(); 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); _logger.Trace("Drone Factory folder is not configured");
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<ImportResult> 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 Sonarr: {0}", path);
return new List<ImportResult>(); return new List<ImportResult>();
} }
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Movie movie) if (!_diskProvider.FolderExists(downloadedEpisodesFolder))
{ {
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); _logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder);
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<ImportResult> 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<ImportResult>
{
UnknownMovieResult("Unknown Movie")
};
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> 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<ImportResult>(); return new List<ImportResult>();
} }
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); return _downloadedMovieImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder));
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); private List<ImportResult> ProcessPath(DownloadedMovieScanCommand message)
if (downloadClientItem == null)
{ {
foreach (var videoFile in videoFiles) if (!_diskProvider.FolderExists(message.Path) && !_diskProvider.FileExists(message.Path))
{ {
if (_diskProvider.IsFileLocked(videoFile)) _logger.Warn("Folder/File specified for import scan [{0}] doesn't exist.", message.Path);
{ return new List<ImportResult>();
return new List<ImportResult>
{
FileIsLockedResult(videoFile)
};
}
}
} }
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true); if (message.DownloadClientId.IsNotNullOrWhiteSpace())
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"); var trackedDownload = _trackedDownloadService.Find(message.DownloadClientId);
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
if (trackedDownload != null)
{
_logger.Debug("External directory scan request for known download {0}. [{1}]", message.DownloadClientId, message.Path);
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode, trackedDownload.RemoteMovie.Movie, trackedDownload.DownloadItem);
} }
else
{
_logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path);
return importResults; return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
}
private List<ImportResult> 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<ImportResult>
{
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
};
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> 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<ImportResult>
{
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<ImportResult>
{
FileIsLockedResult(fileInfo.FullName)
};
} }
} }
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, movie, null, true); return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
return _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
} }
private string GetCleanedUpFolderName(string folder) public void Execute(DownloadedMovieScanCommand message)
{ {
folder = folder.Replace("_UNPACK_", "") List<ImportResult> importResults;
.Replace("_FAILED_", "");
return folder; if (message.Path.IsNotNullOrWhiteSpace())
}
private ImportResult FileIsLockedResult(string videoFile)
{ {
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); importResults = ProcessPath(message);
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
} }
else
private ImportResult UnknownMovieResult(string message, string videoFile = null)
{ {
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile }; importResults = ProcessDroneFactoryFolder();
}
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Movie")), message); if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported))
{
// 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");
}
} }
} }
} }

View file

@ -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<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
List<ImportResult> 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<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo)
{
var results = new List<ImportResult>();
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<ImportResult> 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<ImportResult>();
}
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<ImportResult> 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<ImportResult>
{
UnknownMovieResult("Unknown Movie")
};
}
return ProcessFolder(directoryInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> 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<ImportResult>();
}
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<ImportResult>
{
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<ImportResult> 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<ImportResult>
{
UnknownMovieResult(string.Format("Unknown Movie for file: {0}", fileInfo.Name), fileInfo.FullName)
};
}
return ProcessFile(fileInfo, importMode, movie, downloadClientItem);
}
private List<ImportResult> 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<ImportResult>
{
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<ImportResult>
{
FileIsLockedResult(fileInfo.FullName)
};
}
}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { 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);
}
}
}

View file

@ -10,5 +10,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public List<int> EpisodeIds { get; set; } public List<int> EpisodeIds { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
public int MovieId { get; set; }
} }
} }

View file

@ -17,5 +17,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
public QualityModel Quality { get; set; } public QualityModel Quality { 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 Movie Movie { get; set; }
} }
} }

View file

@ -30,11 +30,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
private readonly IDiskScanService _diskScanService; private readonly IDiskScanService _diskScanService;
private readonly IMakeImportDecision _importDecisionMaker; private readonly IMakeImportDecision _importDecisionMaker;
private readonly ISeriesService _seriesService; private readonly ISeriesService _seriesService;
private readonly IMovieService _movieService;
private readonly IEpisodeService _episodeService; private readonly IEpisodeService _episodeService;
private readonly IVideoFileInfoReader _videoFileInfoReader; private readonly IVideoFileInfoReader _videoFileInfoReader;
private readonly IImportApprovedEpisodes _importApprovedEpisodes; private readonly IImportApprovedEpisodes _importApprovedEpisodes;
private readonly IImportApprovedMovie _importApprovedMovie;
private readonly ITrackedDownloadService _trackedDownloadService; private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
@ -43,11 +46,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
IDiskScanService diskScanService, IDiskScanService diskScanService,
IMakeImportDecision importDecisionMaker, IMakeImportDecision importDecisionMaker,
ISeriesService seriesService, ISeriesService seriesService,
IMovieService movieService,
IEpisodeService episodeService, IEpisodeService episodeService,
IVideoFileInfoReader videoFileInfoReader, IVideoFileInfoReader videoFileInfoReader,
IImportApprovedEpisodes importApprovedEpisodes, IImportApprovedEpisodes importApprovedEpisodes,
IImportApprovedMovie importApprovedMovie,
ITrackedDownloadService trackedDownloadService, ITrackedDownloadService trackedDownloadService,
IDownloadedEpisodesImportService downloadedEpisodesImportService, IDownloadedEpisodesImportService downloadedEpisodesImportService,
IDownloadedMovieImportService downloadedMovieImportService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
@ -56,11 +62,14 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
_diskScanService = diskScanService; _diskScanService = diskScanService;
_importDecisionMaker = importDecisionMaker; _importDecisionMaker = importDecisionMaker;
_seriesService = seriesService; _seriesService = seriesService;
_movieService = movieService;
_episodeService = episodeService; _episodeService = episodeService;
_videoFileInfoReader = videoFileInfoReader; _videoFileInfoReader = videoFileInfoReader;
_importApprovedEpisodes = importApprovedEpisodes; _importApprovedEpisodes = importApprovedEpisodes;
_importApprovedMovie = importApprovedMovie;
_trackedDownloadService = trackedDownloadService; _trackedDownloadService = trackedDownloadService;
_downloadedEpisodesImportService = downloadedEpisodesImportService; _downloadedEpisodesImportService = downloadedEpisodesImportService;
_downloadedMovieImportService = downloadedMovieImportService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
} }
@ -126,62 +135,128 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
var relativeFile = folder.GetRelativePath(file); 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); var trackedDownload = _trackedDownloadService.Find(downloadId);
series = trackedDownload.RemoteEpisode.Series; movie = trackedDownload.RemoteMovie.Movie;
} }
if (series == null) if (movie == null)
{ {
var localEpisode = new LocalEpisode(); var localMovie = new LocalMovie()
localEpisode.Path = file; {
localEpisode.Quality = QualityParser.ParseQuality(file); Path = file,
localEpisode.Size = _diskProvider.GetFileSize(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<string> { file }, var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file },
series, null, SceneSource(series, folder)); movie, null, SceneSource(movie, folder));
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; 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<string> {file},
// series, null, SceneSource(series, folder));
// return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
//}
private bool SceneSource(Series series, string folder) private bool SceneSource(Series series, string folder)
{ {
return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(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) private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
{ {
var item = new ManualImportItem(); var item = new ManualImportItem();
item.Path = decision.LocalEpisode.Path; item.Path = decision.LocalMovie.Path;
item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); item.RelativePath = folder.GetRelativePath(decision.LocalMovie.Path);
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); item.Name = Path.GetFileNameWithoutExtension(decision.LocalMovie.Path);
item.DownloadId = downloadId; 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.Quality = decision.LocalMovie.Quality;
{ item.Size = _diskProvider.GetFileSize(decision.LocalMovie.Path);
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; item.Rejections = decision.Rejections;
return item; return item;
@ -199,37 +274,35 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
var file = message.Files[i]; var file = message.Files[i];
var series = _seriesService.GetSeries(file.SeriesId); var movie = _movieService.GetMovie(file.MovieId);
var episodes = _episodeService.GetEpisodes(file.EpisodeIds); var parsedMovieInfo = Parser.Parser.ParseMoviePath(file.Path) ?? new ParsedMovieInfo();
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); 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, ExistingFile = false,
Episodes = episodes,
MediaInfo = mediaInfo, MediaInfo = mediaInfo,
ParsedEpisodeInfo = parsedEpisodeInfo, ParsedMovieInfo = parsedMovieInfo,
Path = file.Path, Path = file.Path,
Quality = file.Quality, Quality = file.Quality,
Series = series, Movie = movie,
Size = 0 Size = 0
}; };
//TODO: Cleanup non-tracked downloads //TODO: Cleanup non-tracked downloads
var importDecision = new ImportDecision(localEpisode); var importDecision = new ImportDecision(localMovie);
if (file.DownloadId.IsNullOrWhiteSpace()) if (file.DownloadId.IsNullOrWhiteSpace())
{ {
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode)); imported.AddRange(_importApprovedMovie.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
} }
else else
{ {
var trackedDownload = _trackedDownloadService.Find(file.DownloadId); var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); var importResult = _importApprovedMovie.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
imported.Add(importResult); imported.Add(importResult);
@ -249,20 +322,98 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{ {
if (_downloadedEpisodesImportService.ShouldDeleteFolder( if (_downloadedMovieImportService.ShouldDeleteFolder(
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), 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); _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; trackedDownload.State = TrackedDownloadStage.Imported;
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); _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<ImportResult>();
// var importedTrackedDownload = new List<ManuallyImportedFile>();
// 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> { importDecision }, !existingFile, null, message.ImportMode));
// }
// else
// {
// var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
// var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { 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));
// }
// }
//}
} }
} }

View file

@ -705,10 +705,12 @@
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" /> <Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" /> <Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" /> <Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedMovieScanCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieCommand.cs" /> <Compile Include="MediaFiles\Commands\RenameMovieCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameMovieFilesCommand.cs" /> <Compile Include="MediaFiles\Commands\RenameMovieFilesCommand.cs" />
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" /> <Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
<Compile Include="MediaFiles\DownloadedMovieCommandService.cs" /> <Compile Include="MediaFiles\DownloadedMovieCommandService.cs" />
<Compile Include="MediaFiles\DownloadedMovieImportService.cs" />
<Compile Include="MediaFiles\MovieFileMovingService.cs" /> <Compile Include="MediaFiles\MovieFileMovingService.cs" />
<Compile Include="MediaFiles\Events\MovieDownloadedEvent.cs" /> <Compile Include="MediaFiles\Events\MovieDownloadedEvent.cs" />
<Compile Include="MediaFiles\Events\MovieFileAddedEvent.cs" /> <Compile Include="MediaFiles\Events\MovieFileAddedEvent.cs" />

View file

@ -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()
});
}
});

View file

@ -16,6 +16,7 @@ var QualityCell = require('./Cells/QualityCell');
var FileSizeCell = require('../Cells/FileSizeCell'); var FileSizeCell = require('../Cells/FileSizeCell');
var ApprovalStatusCell = require('../Cells/ApprovalStatusCell'); var ApprovalStatusCell = require('../Cells/ApprovalStatusCell');
var ManualImportCollection = require('./ManualImportCollection'); var ManualImportCollection = require('./ManualImportCollection');
var MovieCell = require('./Cells/MovieCell');
var Messenger = require('../Shared/Messenger'); var Messenger = require('../Shared/Messenger');
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
@ -49,23 +50,29 @@ module.exports = Marionette.Layout.extend({
sortable : true sortable : true
}, },
{ {
name : 'series', name : 'movie',
label : 'Series', label : 'Movie',
cell : SeriesCell, cell : MovieCell,
sortable : true sortable : true
}, },
{ // {
name : 'seasonNumber', // name : 'series',
label : 'Season', // label : 'Series',
cell : SeasonCell, // cell : SeriesCell,
sortable : true // sortable : true
}, // },
{ // {
name : 'episodes', // name : 'seasonNumber',
label : 'Episode(s)', // label : 'Season',
cell : EpisodesCell, // cell : SeasonCell,
sortable : false // sortable : true
}, // },
// {
// name : 'episodes',
// label : 'Episode(s)',
// cell : EpisodesCell,
// sortable : false
// },
{ {
name : 'quality', name : 'quality',
label : 'Quality', label : 'Quality',
@ -161,8 +168,8 @@ module.exports = Marionette.Layout.extend({
}, },
_automaticImport : function (e) { _automaticImport : function (e) {
CommandController.Execute('downloadedEpisodesScan', { CommandController.Execute('downloadedMovieScan', {
name : 'downloadedEpisodesScan', name : 'downloadedMovieScan',
path : e.folder path : e.folder
}); });
@ -177,28 +184,35 @@ module.exports = Marionette.Layout.extend({
} }
if(_.any(selected, function(model) { if(_.any(selected, function(model) {
return !model.has('series'); return !model.has('movie');
})) { })) {
this._showErrorMessage('Movie must be chosen for each selected file');
this._showErrorMessage('Series must be chosen for each selected file');
return; return;
} }
if (_.any(selected, function (model) { // if (_.any(selected, function (model) {
return !model.has('seasonNumber'); // return !model.has('series');
})) { // })) {
this._showErrorMessage('Season must be chosen for each selected file'); // this._showErrorMessage('Series must be chosen for each selected file');
return; // return;
} // }
if (_.any(selected, function (model) { // if (_.any(selected, function (model) {
return !model.has('episodes') || model.get('episodes').length === 0; // return !model.has('seasonNumber');
})) { // })) {
this._showErrorMessage('One or more episodes must be chosen for each selected file'); // this._showErrorMessage('Season must be chosen for each selected file');
return; // 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(); var importMode = this.ui.importMode.val();
@ -207,8 +221,9 @@ module.exports = Marionette.Layout.extend({
files : _.map(selected, function (file) { files : _.map(selected, function (file) {
return { return {
path : file.get('path'), path : file.get('path'),
seriesId : file.get('series').id, movieId : file.get('movie').id,
episodeIds : _.map(file.get('episodes'), 'id'), // seriesId : file.get('series').id,
// episodeIds : _.map(file.get('episodes'), 'id'),
quality : file.get('quality'), quality : file.get('quality'),
downloadId : file.get('downloadId') downloadId : file.get('downloadId')
}; };

View file

@ -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;
});
}
});

View file

@ -0,0 +1,30 @@
<div class="modal-content">
<div class="manual-import-modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>
Manual Import - Select Movie
</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<input type="text" class="form-control x-filter" placeholder="Filter movies" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 x-movie"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>

View file

@ -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 });
}
});

View file

@ -4,16 +4,25 @@ var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({ module.exports = Marionette.ItemView.extend({
template : 'ManualImport/Summary/ManualImportSummaryViewTemplate', 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) { initialize : function (options) {
var episodes = _.map(options.episodes, function (episode) {
return episode.toJSON();
});
this.templateHelpers = { this.templateHelpers = {
file : options.file, file : options.file,
series : options.series, movie : options.movie,
season : options.season,
episodes : episodes,
quality : options.quality quality : options.quality
}; };
} }

View file

@ -3,16 +3,8 @@
<dt>Path:</dt> <dt>Path:</dt>
<dd>{{file}}</dd> <dd>{{file}}</dd>
<dt>Series:</dt> <dt>Movie:</dt>
<dd>{{series.title}}</dd> <dd>{{movie.title}} ({{movie.year}})</dd>
<dt>Season:</dt>
<dd>{{season.seasonNumber}}</dd>
{{#each episodes}}
<dt>Episode:</dt>
<dd>{{episodeNumber}} - {{title}}</dd>
{{/each}}
<dt>Quality:</dt> <dt>Quality:</dt>
<dd>{{quality.name}}</dd> <dd>{{quality.name}}</dd>

View file

@ -23,7 +23,7 @@
</div> </div>
<div class="col-sm-2 col-sm-pull-1"> <div class="col-sm-2 col-sm-pull-1">
<input type="number" name="downloadedEpisodesScanInterval" class="form-control" /> <input type="number" name="downloadedMovieScanInterval" class="form-control" />
</div> </div>
</div> </div>
</fieldset> </fieldset>

View file

@ -10,6 +10,7 @@ var LogDetailsView = require('../../System/Logs/Table/Details/LogDetailsView');
var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout'); var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout');
var ManualImportLayout = require('../../ManualImport/ManualImportLayout'); var ManualImportLayout = require('../../ManualImport/ManualImportLayout');
var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout'); var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout');
var MoviesDetailsLayout = require('../../Movies/Details/MoviesDetailsLayout');
module.exports = Marionette.AppRouter.extend({ module.exports = Marionette.AppRouter.extend({
initialize : function() { initialize : function() {

View file

@ -133,7 +133,7 @@ module.exports = Marionette.Layout.extend({
{ {
title : 'Rescan Drone Factory Folder', title : 'Rescan Drone Factory Folder',
icon : 'icon-sonarr-refresh', icon : 'icon-sonarr-refresh',
command : 'downloadedepisodesscan', command : 'downloadedMovieScan',
properties : { sendUpdates : true } properties : { sendUpdates : true }
}, },
{ {