mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-05 02:22:31 +01:00
Merge pull request #133 from Radarr/fix-manual-import
Fix Manual Import & Drone Factory
This commit is contained in:
commit
3b5887bf09
@ -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.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 System.Text;
|
||||
|
||||
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
|
||||
public class DownloadedMovieCommandService : IExecute<DownloadedMovieScanCommand>
|
||||
{
|
||||
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
|
||||
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IParsingService _parsingService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly IImportApprovedMovie _importApprovedMovie;
|
||||
private readonly IDetectSample _detectSample;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadedMovieImportService(IDiskProvider diskProvider,
|
||||
IDiskScanService diskScanService,
|
||||
IMovieService movieService,
|
||||
IParsingService parsingService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
IImportApprovedMovie importApprovedMovie,
|
||||
IDetectSample detectSample,
|
||||
Logger logger)
|
||||
public DownloadedMovieCommandService(IDownloadedMovieImportService downloadedMovieImportService,
|
||||
ITrackedDownloadService trackedDownloadService,
|
||||
IDiskProvider diskProvider,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
{
|
||||
_downloadedMovieImportService = downloadedMovieImportService;
|
||||
_trackedDownloadService = trackedDownloadService;
|
||||
_diskProvider = diskProvider;
|
||||
_diskScanService = diskScanService;
|
||||
_movieService = movieService;
|
||||
_parsingService = parsingService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_importApprovedMovie = importApprovedMovie;
|
||||
_detectSample = detectSample;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<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);
|
||||
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>();
|
||||
}
|
||||
|
||||
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");
|
||||
_logger.Trace("Drone Factory folder is not configured");
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
var folderInfo = Parser.Parser.ParseMovieTitle(directoryInfo.Name);
|
||||
|
||||
if (folderInfo != null)
|
||||
if (!_diskProvider.FolderExists(downloadedEpisodesFolder))
|
||||
{
|
||||
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
|
||||
_logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder);
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
return _downloadedMovieImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder));
|
||||
}
|
||||
|
||||
if (downloadClientItem == null)
|
||||
private List<ImportResult> ProcessPath(DownloadedMovieScanCommand message)
|
||||
{
|
||||
if (!_diskProvider.FolderExists(message.Path) && !_diskProvider.FileExists(message.Path))
|
||||
{
|
||||
foreach (var videoFile in videoFiles)
|
||||
_logger.Warn("Folder/File specified for import scan [{0}] doesn't exist.", message.Path);
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
|
||||
if (message.DownloadClientId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(message.DownloadClientId);
|
||||
|
||||
if (trackedDownload != null)
|
||||
{
|
||||
if (_diskProvider.IsFileLocked(videoFile))
|
||||
{
|
||||
return new List<ImportResult>
|
||||
{
|
||||
FileIsLockedResult(videoFile)
|
||||
};
|
||||
}
|
||||
_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 _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
|
||||
}
|
||||
}
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), movie, folderInfo, true);
|
||||
var importResults = _importApprovedMovie.Import(decisions, true, downloadClientItem, importMode);
|
||||
return _downloadedMovieImportService.ProcessPath(message.Path, message.ImportMode);
|
||||
}
|
||||
|
||||
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
|
||||
importResults.Any(i => i.Result == ImportResultType.Imported) &&
|
||||
ShouldDeleteFolder(directoryInfo, movie))
|
||||
public void Execute(DownloadedMovieScanCommand message)
|
||||
{
|
||||
List<ImportResult> importResults;
|
||||
|
||||
if (message.Path.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Debug("Deleting folder after importing valid files");
|
||||
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
|
||||
importResults = ProcessPath(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
importResults = ProcessDroneFactoryFolder();
|
||||
}
|
||||
|
||||
return importResults;
|
||||
}
|
||||
|
||||
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var movie = _parsingService.GetMovie(Path.GetFileNameWithoutExtension(fileInfo.Name));
|
||||
|
||||
if (movie == null)
|
||||
if (importResults == null || importResults.All(v => v.Result != ImportResultType.Imported))
|
||||
{
|
||||
_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)
|
||||
};
|
||||
// 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");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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 @@ public class ManualImportFile
|
||||
public List<int> EpisodeIds { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public int MovieId { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -17,5 +17,6 @@ public class ManualImportItem
|
||||
public QualityModel Quality { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
public Movie Movie { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -30,11 +30,14 @@ public class ManualImportService : IExecute<ManualImportCommand>, IManualImportS
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IMovieService _movieService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
||||
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||
private readonly IImportApprovedMovie _importApprovedMovie;
|
||||
private readonly ITrackedDownloadService _trackedDownloadService;
|
||||
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
|
||||
private readonly IDownloadedMovieImportService _downloadedMovieImportService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
@ -43,11 +46,14 @@ public ManualImportService(IDiskProvider diskProvider,
|
||||
IDiskScanService diskScanService,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
ISeriesService seriesService,
|
||||
IMovieService movieService,
|
||||
IEpisodeService episodeService,
|
||||
IVideoFileInfoReader videoFileInfoReader,
|
||||
IImportApprovedEpisodes importApprovedEpisodes,
|
||||
IImportApprovedMovie importApprovedMovie,
|
||||
ITrackedDownloadService trackedDownloadService,
|
||||
IDownloadedEpisodesImportService downloadedEpisodesImportService,
|
||||
IDownloadedMovieImportService downloadedMovieImportService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
@ -56,11 +62,14 @@ public ManualImportService(IDiskProvider diskProvider,
|
||||
_diskScanService = diskScanService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_seriesService = seriesService;
|
||||
_movieService = movieService;
|
||||
_episodeService = episodeService;
|
||||
_videoFileInfoReader = videoFileInfoReader;
|
||||
_importApprovedEpisodes = importApprovedEpisodes;
|
||||
_importApprovedMovie = importApprovedMovie;
|
||||
_trackedDownloadService = trackedDownloadService;
|
||||
_downloadedEpisodesImportService = downloadedEpisodesImportService;
|
||||
_downloadedMovieImportService = downloadedMovieImportService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
}
|
||||
@ -126,62 +135,128 @@ private ManualImportItem ProcessFile(string file, string downloadId, string fold
|
||||
|
||||
var relativeFile = folder.GetRelativePath(file);
|
||||
|
||||
var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
|
||||
var movie = _parsingService.GetMovie(relativeFile.Split('\\', '/')[0]);
|
||||
|
||||
if (series == null)
|
||||
if (movie == null)
|
||||
{
|
||||
series = _parsingService.GetSeries(relativeFile);
|
||||
movie = _parsingService.GetMovie(relativeFile);
|
||||
}
|
||||
|
||||
if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
if (movie == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
series = trackedDownload.RemoteEpisode.Series;
|
||||
movie = trackedDownload.RemoteMovie.Movie;
|
||||
}
|
||||
|
||||
if (series == null)
|
||||
if (movie == null)
|
||||
{
|
||||
var localEpisode = new LocalEpisode();
|
||||
localEpisode.Path = file;
|
||||
localEpisode.Quality = QualityParser.ParseQuality(file);
|
||||
localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
var localMovie = new LocalMovie()
|
||||
{
|
||||
Path = file,
|
||||
Quality = QualityParser.ParseQuality(file),
|
||||
Size = _diskProvider.GetFileSize(file)
|
||||
};
|
||||
|
||||
return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
|
||||
return MapItem(new ImportDecision(localMovie, new Rejection("Unknown Movie")), folder, downloadId);
|
||||
}
|
||||
|
||||
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
|
||||
series, null, SceneSource(series, folder));
|
||||
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> { file },
|
||||
movie, null, SceneSource(movie, folder));
|
||||
|
||||
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
|
||||
}
|
||||
|
||||
//private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
|
||||
//{
|
||||
// if (folder.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// folder = new FileInfo(file).Directory.FullName;
|
||||
// }
|
||||
|
||||
// var relativeFile = folder.GetRelativePath(file);
|
||||
|
||||
// var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
|
||||
|
||||
// if (series == null)
|
||||
// {
|
||||
// series = _parsingService.GetSeries(relativeFile);
|
||||
// }
|
||||
|
||||
// if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
// {
|
||||
// var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
// series = trackedDownload.RemoteEpisode.Series;
|
||||
// }
|
||||
|
||||
// if (series == null)
|
||||
// {
|
||||
// var localEpisode = new LocalEpisode();
|
||||
// localEpisode.Path = file;
|
||||
// localEpisode.Quality = QualityParser.ParseQuality(file);
|
||||
// localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
|
||||
// return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
|
||||
// }
|
||||
|
||||
// var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
|
||||
// series, null, SceneSource(series, folder));
|
||||
|
||||
// return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
|
||||
//}
|
||||
|
||||
private bool SceneSource(Series series, string folder)
|
||||
{
|
||||
return !(series.Path.PathEquals(folder) || series.Path.IsParentPath(folder));
|
||||
}
|
||||
|
||||
private bool SceneSource(Movie movie, string folder)
|
||||
{
|
||||
return !(movie.Path.PathEquals(folder) || movie.Path.IsParentPath(folder));
|
||||
}
|
||||
|
||||
//private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
|
||||
//{
|
||||
// var item = new ManualImportItem();
|
||||
|
||||
// item.Path = decision.LocalEpisode.Path;
|
||||
// item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
|
||||
// item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
|
||||
// item.DownloadId = downloadId;
|
||||
|
||||
// if (decision.LocalEpisode.Series != null)
|
||||
// {
|
||||
// item.Series = decision.LocalEpisode.Series;
|
||||
// }
|
||||
|
||||
// if (decision.LocalEpisode.Episodes.Any())
|
||||
// {
|
||||
// item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
|
||||
// item.Episodes = decision.LocalEpisode.Episodes;
|
||||
// }
|
||||
|
||||
// item.Quality = decision.LocalEpisode.Quality;
|
||||
// item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
|
||||
// item.Rejections = decision.Rejections;
|
||||
|
||||
// return item;
|
||||
//}
|
||||
|
||||
private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
|
||||
{
|
||||
var item = new ManualImportItem();
|
||||
|
||||
item.Path = decision.LocalEpisode.Path;
|
||||
item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
|
||||
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
|
||||
item.Path = decision.LocalMovie.Path;
|
||||
item.RelativePath = folder.GetRelativePath(decision.LocalMovie.Path);
|
||||
item.Name = Path.GetFileNameWithoutExtension(decision.LocalMovie.Path);
|
||||
item.DownloadId = downloadId;
|
||||
|
||||
if (decision.LocalEpisode.Series != null)
|
||||
if (decision.LocalMovie.Movie != null)
|
||||
{
|
||||
item.Series = decision.LocalEpisode.Series;
|
||||
item.Movie = decision.LocalMovie.Movie;
|
||||
}
|
||||
|
||||
if (decision.LocalEpisode.Episodes.Any())
|
||||
{
|
||||
item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
|
||||
item.Episodes = decision.LocalEpisode.Episodes;
|
||||
}
|
||||
|
||||
item.Quality = decision.LocalEpisode.Quality;
|
||||
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
|
||||
item.Quality = decision.LocalMovie.Quality;
|
||||
item.Size = _diskProvider.GetFileSize(decision.LocalMovie.Path);
|
||||
item.Rejections = decision.Rejections;
|
||||
|
||||
return item;
|
||||
@ -199,45 +274,43 @@ public void Execute(ManualImportCommand message)
|
||||
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
|
||||
|
||||
var file = message.Files[i];
|
||||
var series = _seriesService.GetSeries(file.SeriesId);
|
||||
var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
|
||||
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
|
||||
var movie = _movieService.GetMovie(file.MovieId);
|
||||
var parsedMovieInfo = Parser.Parser.ParseMoviePath(file.Path) ?? new ParsedMovieInfo();
|
||||
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
|
||||
var existingFile = series.Path.IsParentPath(file.Path);
|
||||
var existingFile = movie.Path.IsParentPath(file.Path);
|
||||
|
||||
var localEpisode = new LocalEpisode
|
||||
var localMovie = new LocalMovie
|
||||
{
|
||||
ExistingFile = false,
|
||||
Episodes = episodes,
|
||||
MediaInfo = mediaInfo,
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
ParsedMovieInfo = parsedMovieInfo,
|
||||
Path = file.Path,
|
||||
Quality = file.Quality,
|
||||
Series = series,
|
||||
Movie = movie,
|
||||
Size = 0
|
||||
};
|
||||
|
||||
//TODO: Cleanup non-tracked downloads
|
||||
|
||||
var importDecision = new ImportDecision(localEpisode);
|
||||
var importDecision = new ImportDecision(localMovie);
|
||||
|
||||
if (file.DownloadId.IsNullOrWhiteSpace())
|
||||
{
|
||||
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
||||
imported.AddRange(_importApprovedMovie.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();
|
||||
var importResult = _importApprovedMovie.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
|
||||
|
||||
imported.Add(importResult);
|
||||
|
||||
importedTrackedDownload.Add(new ManuallyImportedFile
|
||||
{
|
||||
TrackedDownload = trackedDownload,
|
||||
ImportResult = importResult
|
||||
});
|
||||
{
|
||||
TrackedDownload = trackedDownload,
|
||||
ImportResult = importResult
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,20 +322,98 @@ public void Execute(ManualImportCommand message)
|
||||
|
||||
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
|
||||
{
|
||||
if (_downloadedEpisodesImportService.ShouldDeleteFolder(
|
||||
if (_downloadedMovieImportService.ShouldDeleteFolder(
|
||||
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
|
||||
trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
trackedDownload.RemoteMovie.Movie) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
{
|
||||
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
|
||||
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, 1)) //TODO: trackedDownload.RemoteMovie.Movie.Count is always 1?
|
||||
{
|
||||
trackedDownload.State = TrackedDownloadStage.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//public void Execute(ManualImportCommand message)
|
||||
//{
|
||||
// _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode);
|
||||
|
||||
// var imported = new List<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\CleanUpRecycleBinCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\DownloadedMovieScanCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameMovieCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RenameMovieFilesCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RescanMovieCommand.cs" />
|
||||
<Compile Include="MediaFiles\DownloadedMovieCommandService.cs" />
|
||||
<Compile Include="MediaFiles\DownloadedMovieImportService.cs" />
|
||||
<Compile Include="MediaFiles\MovieFileMovingService.cs" />
|
||||
<Compile Include="MediaFiles\Events\MovieDownloadedEvent.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 ApprovalStatusCell = require('../Cells/ApprovalStatusCell');
|
||||
var ManualImportCollection = require('./ManualImportCollection');
|
||||
var MovieCell = require('./Cells/MovieCell');
|
||||
var Messenger = require('../Shared/Messenger');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
@ -49,23 +50,29 @@ module.exports = Marionette.Layout.extend({
|
||||
sortable : true
|
||||
},
|
||||
{
|
||||
name : 'series',
|
||||
label : 'Series',
|
||||
cell : SeriesCell,
|
||||
name : 'movie',
|
||||
label : 'Movie',
|
||||
cell : MovieCell,
|
||||
sortable : true
|
||||
},
|
||||
{
|
||||
name : 'seasonNumber',
|
||||
label : 'Season',
|
||||
cell : SeasonCell,
|
||||
sortable : true
|
||||
},
|
||||
{
|
||||
name : 'episodes',
|
||||
label : 'Episode(s)',
|
||||
cell : EpisodesCell,
|
||||
sortable : false
|
||||
},
|
||||
// {
|
||||
// name : 'series',
|
||||
// label : 'Series',
|
||||
// cell : SeriesCell,
|
||||
// sortable : true
|
||||
// },
|
||||
// {
|
||||
// name : 'seasonNumber',
|
||||
// label : 'Season',
|
||||
// cell : SeasonCell,
|
||||
// sortable : true
|
||||
// },
|
||||
// {
|
||||
// name : 'episodes',
|
||||
// label : 'Episode(s)',
|
||||
// cell : EpisodesCell,
|
||||
// sortable : false
|
||||
// },
|
||||
{
|
||||
name : 'quality',
|
||||
label : 'Quality',
|
||||
@ -161,8 +168,8 @@ module.exports = Marionette.Layout.extend({
|
||||
},
|
||||
|
||||
_automaticImport : function (e) {
|
||||
CommandController.Execute('downloadedEpisodesScan', {
|
||||
name : 'downloadedEpisodesScan',
|
||||
CommandController.Execute('downloadedMovieScan', {
|
||||
name : 'downloadedMovieScan',
|
||||
path : e.folder
|
||||
});
|
||||
|
||||
@ -176,29 +183,36 @@ module.exports = Marionette.Layout.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.any(selected, function (model) {
|
||||
return !model.has('series');
|
||||
})) {
|
||||
|
||||
this._showErrorMessage('Series must be chosen for each selected file');
|
||||
if(_.any(selected, function(model) {
|
||||
return !model.has('movie');
|
||||
})) {
|
||||
this._showErrorMessage('Movie must be chosen for each selected file');
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.any(selected, function (model) {
|
||||
return !model.has('seasonNumber');
|
||||
})) {
|
||||
// if (_.any(selected, function (model) {
|
||||
// return !model.has('series');
|
||||
// })) {
|
||||
|
||||
this._showErrorMessage('Season must be chosen for each selected file');
|
||||
return;
|
||||
}
|
||||
// this._showErrorMessage('Series must be chosen for each selected file');
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (_.any(selected, function (model) {
|
||||
return !model.has('episodes') || model.get('episodes').length === 0;
|
||||
})) {
|
||||
// if (_.any(selected, function (model) {
|
||||
// return !model.has('seasonNumber');
|
||||
// })) {
|
||||
|
||||
this._showErrorMessage('One or more episodes must be chosen for each selected file');
|
||||
return;
|
||||
}
|
||||
// this._showErrorMessage('Season must be chosen for each selected file');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (_.any(selected, function (model) {
|
||||
// return !model.has('episodes') || model.get('episodes').length === 0;
|
||||
// })) {
|
||||
|
||||
// this._showErrorMessage('One or more episodes must be chosen for each selected file');
|
||||
// return;
|
||||
// }
|
||||
|
||||
var importMode = this.ui.importMode.val();
|
||||
|
||||
@ -207,8 +221,9 @@ module.exports = Marionette.Layout.extend({
|
||||
files : _.map(selected, function (file) {
|
||||
return {
|
||||
path : file.get('path'),
|
||||
seriesId : file.get('series').id,
|
||||
episodeIds : _.map(file.get('episodes'), 'id'),
|
||||
movieId : file.get('movie').id,
|
||||
// seriesId : file.get('series').id,
|
||||
// episodeIds : _.map(file.get('episodes'), 'id'),
|
||||
quality : file.get('quality'),
|
||||
downloadId : file.get('downloadId')
|
||||
};
|
||||
|
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({
|
||||
template : 'ManualImport/Summary/ManualImportSummaryViewTemplate',
|
||||
|
||||
// initialize : function (options) {
|
||||
// var episodes = _.map(options.episodes, function (episode) {
|
||||
// return episode.toJSON();
|
||||
// });
|
||||
|
||||
// this.templateHelpers = {
|
||||
// file : options.file,
|
||||
// series : options.series,
|
||||
// season : options.season,
|
||||
// episodes : episodes,
|
||||
// quality : options.quality
|
||||
// };
|
||||
// }
|
||||
|
||||
initialize : function (options) {
|
||||
var episodes = _.map(options.episodes, function (episode) {
|
||||
return episode.toJSON();
|
||||
});
|
||||
|
||||
this.templateHelpers = {
|
||||
file : options.file,
|
||||
series : options.series,
|
||||
season : options.season,
|
||||
episodes : episodes,
|
||||
movie : options.movie,
|
||||
quality : options.quality
|
||||
};
|
||||
}
|
||||
|
@ -3,16 +3,8 @@
|
||||
<dt>Path:</dt>
|
||||
<dd>{{file}}</dd>
|
||||
|
||||
<dt>Series:</dt>
|
||||
<dd>{{series.title}}</dd>
|
||||
|
||||
<dt>Season:</dt>
|
||||
<dd>{{season.seasonNumber}}</dd>
|
||||
|
||||
{{#each episodes}}
|
||||
<dt>Episode:</dt>
|
||||
<dd>{{episodeNumber}} - {{title}}</dd>
|
||||
{{/each}}
|
||||
<dt>Movie:</dt>
|
||||
<dd>{{movie.title}} ({{movie.year}})</dd>
|
||||
|
||||
<dt>Quality:</dt>
|
||||
<dd>{{quality.name}}</dd>
|
||||
|
@ -78,7 +78,7 @@ module.exports = Marionette.Layout.extend({
|
||||
|
||||
CommandController.Execute('renameMovieFiles', {
|
||||
name : 'renameMovieFiles',
|
||||
movieId : this.model.id,
|
||||
movieId : this.model.id,
|
||||
files : files
|
||||
});
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</fieldset>
|
@ -10,6 +10,7 @@ var LogDetailsView = require('../../System/Logs/Table/Details/LogDetailsView');
|
||||
var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout');
|
||||
var ManualImportLayout = require('../../ManualImport/ManualImportLayout');
|
||||
var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout');
|
||||
var MoviesDetailsLayout = require('../../Movies/Details/MoviesDetailsLayout');
|
||||
|
||||
module.exports = Marionette.AppRouter.extend({
|
||||
initialize : function() {
|
||||
|
@ -133,7 +133,7 @@ module.exports = Marionette.Layout.extend({
|
||||
{
|
||||
title : 'Rescan Drone Factory Folder',
|
||||
icon : 'icon-sonarr-refresh',
|
||||
command : 'downloadedepisodesscan',
|
||||
command : 'downloadedMovieScan',
|
||||
properties : { sendUpdates : true }
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user