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.
Sonarr/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs

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);
if (localEpisode.FileRenamedAfterScriptImport)
{
_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);
}
}
}