mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-23 06:45:19 -07:00
Merge pull request #133 from Radarr/fix-manual-import
Fix Manual Import & Drone Factory
This commit is contained in:
commit
3b5887bf09
18 changed files with 810 additions and 326 deletions
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
{
|
||||||
|
importResults = ProcessPath(message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
importResults = ProcessDroneFactoryFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImportResult FileIsLockedResult(string videoFile)
|
if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported))
|
||||||
{
|
{
|
||||||
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
|
// Atm we don't report it as a command failure, coz that would cause the download to be failed.
|
||||||
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
|
// Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later.
|
||||||
|
//message.SetMessage("Failed to import");
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
266
src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs
Normal file
266
src/NzbDrone.Core/MediaFiles/DownloadedMovieImportService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
43
src/UI/ManualImport/Cells/MovieCell.js
Normal file
43
src/UI/ManualImport/Cells/MovieCell.js
Normal 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()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -176,29 +183,36 @@ module.exports = Marionette.Layout.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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')
|
||||||
};
|
};
|
||||||
|
|
101
src/UI/ManualImport/Movie/SelectMovieLayout.js
Normal file
101
src/UI/ManualImport/Movie/SelectMovieLayout.js
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
30
src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs
Normal file
30
src/UI/ManualImport/Movie/SelectMovieLayoutTemplate.hbs
Normal 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">×</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>
|
||||||
|
|
||||||
|
|
13
src/UI/ManualImport/Movie/SelectMovieRow.js
Normal file
13
src/UI/ManualImport/Movie/SelectMovieRow.js
Normal 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 });
|
||||||
|
}
|
||||||
|
});
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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() {
|
||||||
|
|
|
@ -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 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue