You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
13 KiB
268 lines
13 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using NLog;
|
|
using NzbDrone.Common.Disk;
|
|
using NzbDrone.Common.Extensions;
|
|
using NzbDrone.Core.Download;
|
|
using NzbDrone.Core.Extras;
|
|
using NzbDrone.Core.History;
|
|
using NzbDrone.Core.MediaFiles.Commands;
|
|
using NzbDrone.Core.MediaFiles.Events;
|
|
using NzbDrone.Core.Messaging.Commands;
|
|
using NzbDrone.Core.Messaging.Events;
|
|
using NzbDrone.Core.Parser.Model;
|
|
using NzbDrone.Core.Qualities;
|
|
|
|
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|
{
|
|
public interface IImportApprovedEpisodes
|
|
{
|
|
List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto);
|
|
}
|
|
|
|
public class ImportApprovedEpisodes : IImportApprovedEpisodes
|
|
{
|
|
private readonly IUpgradeMediaFiles _episodeFileUpgrader;
|
|
private readonly IMediaFileService _mediaFileService;
|
|
private readonly IExtraService _extraService;
|
|
private readonly IExistingExtraFiles _existingExtraFiles;
|
|
private readonly IDiskProvider _diskProvider;
|
|
private readonly IHistoryService _historyService;
|
|
private readonly IEventAggregator _eventAggregator;
|
|
private readonly IManageCommandQueue _commandQueueManager;
|
|
private readonly Logger _logger;
|
|
|
|
public ImportApprovedEpisodes(IUpgradeMediaFiles episodeFileUpgrader,
|
|
IMediaFileService mediaFileService,
|
|
IExtraService extraService,
|
|
IExistingExtraFiles existingExtraFiles,
|
|
IDiskProvider diskProvider,
|
|
IHistoryService historyService,
|
|
IEventAggregator eventAggregator,
|
|
IManageCommandQueue commandQueueManager,
|
|
Logger logger)
|
|
{
|
|
_episodeFileUpgrader = episodeFileUpgrader;
|
|
_mediaFileService = mediaFileService;
|
|
_extraService = extraService;
|
|
_existingExtraFiles = existingExtraFiles;
|
|
_diskProvider = diskProvider;
|
|
_historyService = historyService;
|
|
_eventAggregator = eventAggregator;
|
|
_commandQueueManager = commandQueueManager;
|
|
_logger = logger;
|
|
}
|
|
|
|
public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto)
|
|
{
|
|
var qualifiedImports = decisions
|
|
.Where(decision => decision.Approved)
|
|
.GroupBy(decision => decision.LocalEpisode.Series.Id)
|
|
.SelectMany(group => group
|
|
.OrderByDescending(decision => decision.LocalEpisode.Quality, new QualityModelComparer(group.First().LocalEpisode.Series.QualityProfile))
|
|
.ThenByDescending(decision => decision.LocalEpisode.Size))
|
|
.ToList();
|
|
|
|
var importResults = new List<ImportResult>();
|
|
|
|
foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalEpisode.Episodes.Select(episode => episode.EpisodeNumber).MinOrDefault())
|
|
.ThenByDescending(e => e.LocalEpisode.Size))
|
|
{
|
|
var localEpisode = importDecision.LocalEpisode;
|
|
var oldFiles = new List<DeletedEpisodeFile>();
|
|
|
|
try
|
|
{
|
|
// check if already imported
|
|
if (importResults.SelectMany(r => r.ImportDecision.LocalEpisode.Episodes)
|
|
.Select(e => e.Id)
|
|
.Intersect(localEpisode.Episodes.Select(e => e.Id))
|
|
.Any())
|
|
{
|
|
importResults.Add(new ImportResult(importDecision, "Episode has already been imported"));
|
|
continue;
|
|
}
|
|
|
|
var episodeFile = new EpisodeFile();
|
|
episodeFile.DateAdded = DateTime.UtcNow;
|
|
episodeFile.SeriesId = localEpisode.Series.Id;
|
|
episodeFile.Path = localEpisode.Path.CleanFilePath();
|
|
episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path);
|
|
episodeFile.Quality = localEpisode.Quality;
|
|
episodeFile.MediaInfo = localEpisode.MediaInfo;
|
|
episodeFile.SeasonNumber = localEpisode.SeasonNumber;
|
|
episodeFile.Episodes = localEpisode.Episodes;
|
|
episodeFile.ReleaseGroup = localEpisode.ReleaseGroup;
|
|
episodeFile.ReleaseHash = localEpisode.ReleaseHash;
|
|
episodeFile.Languages = localEpisode.Languages;
|
|
|
|
// Prefer the release type from the download client, folder and finally the file so we have the most accurate information.
|
|
episodeFile.ReleaseType = localEpisode.DownloadClientEpisodeInfo?.ReleaseType ??
|
|
localEpisode.FolderEpisodeInfo?.ReleaseType ??
|
|
localEpisode.FileEpisodeInfo.ReleaseType;
|
|
|
|
if (downloadClientItem?.DownloadId.IsNotNullOrWhiteSpace() == true)
|
|
{
|
|
var grabHistory = _historyService.FindByDownloadId(downloadClientItem.DownloadId)
|
|
.OrderByDescending(h => h.Date)
|
|
.FirstOrDefault(h => h.EventType == EpisodeHistoryEventType.Grabbed);
|
|
|
|
if (Enum.TryParse(grabHistory?.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))
|
|
{
|
|
episodeFile.IndexerFlags = flags;
|
|
}
|
|
|
|
// Prefer the release type from the grabbed history
|
|
if (Enum.TryParse(grabHistory?.Data.GetValueOrDefault("releaseType"), true, out ReleaseType releaseType))
|
|
{
|
|
episodeFile.ReleaseType = releaseType;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
episodeFile.IndexerFlags = localEpisode.IndexerFlags;
|
|
}
|
|
|
|
// Fall back to parsed information if history is unavailable or missing
|
|
if (episodeFile.ReleaseType == ReleaseType.Unknown)
|
|
{
|
|
// Prefer the release type from the download client, folder and finally the file so we have the most accurate information.
|
|
episodeFile.ReleaseType = localEpisode.DownloadClientEpisodeInfo?.ReleaseType ??
|
|
localEpisode.FolderEpisodeInfo?.ReleaseType ??
|
|
localEpisode.FileEpisodeInfo.ReleaseType;
|
|
}
|
|
|
|
bool copyOnly;
|
|
switch (importMode)
|
|
{
|
|
default:
|
|
case ImportMode.Auto:
|
|
copyOnly = downloadClientItem != null && !downloadClientItem.CanMoveFiles;
|
|
break;
|
|
case ImportMode.Move:
|
|
copyOnly = false;
|
|
break;
|
|
case ImportMode.Copy:
|
|
copyOnly = true;
|
|
break;
|
|
}
|
|
|
|
if (newDownload)
|
|
{
|
|
episodeFile.SceneName = localEpisode.SceneName;
|
|
episodeFile.OriginalFilePath = GetOriginalFilePath(downloadClientItem, localEpisode);
|
|
|
|
oldFiles = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly).OldFiles;
|
|
}
|
|
else
|
|
{
|
|
episodeFile.RelativePath = localEpisode.Series.Path.GetRelativePath(episodeFile.Path);
|
|
|
|
// Delete existing files from the DB mapped to this path
|
|
var previousFiles = _mediaFileService.GetFilesWithRelativePath(localEpisode.Series.Id, episodeFile.RelativePath);
|
|
|
|
foreach (var previousFile in previousFiles)
|
|
{
|
|
_mediaFileService.Delete(previousFile, DeleteMediaFileReason.ManualOverride);
|
|
}
|
|
}
|
|
|
|
episodeFile = _mediaFileService.Add(episodeFile);
|
|
importResults.Add(new ImportResult(importDecision));
|
|
|
|
if (newDownload)
|
|
{
|
|
if (localEpisode.ScriptImported)
|
|
{
|
|
_existingExtraFiles.ImportExtraFiles(localEpisode.Series, localEpisode.PossibleExtraFiles, localEpisode.FileNameBeforeRename);
|
|
|
|
if (localEpisode.FileNameBeforeRename != episodeFile.RelativePath)
|
|
{
|
|
_extraService.MoveFilesAfterRename(localEpisode.Series, episodeFile);
|
|
}
|
|
}
|
|
|
|
if (!localEpisode.ScriptImported || localEpisode.ShouldImportExtras)
|
|
{
|
|
_extraService.ImportEpisode(localEpisode, episodeFile, copyOnly);
|
|
}
|
|
}
|
|
|
|
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, oldFiles, newDownload, downloadClientItem));
|
|
}
|
|
catch (RootFolderNotFoundException e)
|
|
{
|
|
_logger.Warn(e, "Couldn't import episode " + localEpisode);
|
|
_eventAggregator.PublishEvent(new EpisodeImportFailedEvent(e, localEpisode, newDownload, downloadClientItem));
|
|
|
|
importResults.Add(new ImportResult(importDecision, "Failed to import episode, Root folder missing."));
|
|
}
|
|
catch (DestinationAlreadyExistsException e)
|
|
{
|
|
_logger.Warn(e, "Couldn't import episode " + localEpisode);
|
|
importResults.Add(new ImportResult(importDecision, "Failed to import episode, Destination already exists."));
|
|
|
|
_commandQueueManager.Push(new RescanSeriesCommand(localEpisode.Series.Id));
|
|
}
|
|
catch (RecycleBinException e)
|
|
{
|
|
_logger.Warn(e, "Couldn't import episode " + localEpisode);
|
|
_eventAggregator.PublishEvent(new EpisodeImportFailedEvent(e, localEpisode, newDownload, downloadClientItem));
|
|
|
|
importResults.Add(new ImportResult(importDecision, "Failed to import episode, unable to move existing file to the Recycle Bin."));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Warn(e, "Couldn't import episode " + localEpisode);
|
|
importResults.Add(new ImportResult(importDecision, "Failed to import episode"));
|
|
}
|
|
}
|
|
|
|
// Adding all the rejected decisions
|
|
importResults.AddRange(decisions.Where(c => !c.Approved)
|
|
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
|
|
|
|
return importResults;
|
|
}
|
|
|
|
private string GetOriginalFilePath(DownloadClientItem downloadClientItem, LocalEpisode localEpisode)
|
|
{
|
|
var path = localEpisode.Path;
|
|
|
|
if (downloadClientItem != null && !downloadClientItem.OutputPath.IsEmpty)
|
|
{
|
|
var outputDirectory = downloadClientItem.OutputPath.Directory.ToString();
|
|
|
|
if (outputDirectory.IsParentPath(path))
|
|
{
|
|
return outputDirectory.GetRelativePath(path);
|
|
}
|
|
}
|
|
|
|
var folderEpisodeInfo = localEpisode.FolderEpisodeInfo;
|
|
|
|
if (folderEpisodeInfo != null)
|
|
{
|
|
var folderPath = path.GetAncestorPath(folderEpisodeInfo.ReleaseTitle);
|
|
|
|
if (folderPath != null)
|
|
{
|
|
return folderPath.GetParentPath().GetRelativePath(path);
|
|
}
|
|
}
|
|
|
|
var parentPath = path.GetParentPath();
|
|
var grandparentPath = parentPath.GetParentPath();
|
|
|
|
if (grandparentPath != null)
|
|
{
|
|
return grandparentPath.GetRelativePath(path);
|
|
}
|
|
|
|
return Path.GetFileName(path);
|
|
}
|
|
}
|
|
}
|