New: Rebuilt Completed/Failed download handling from scratch

This commit is contained in:
Keivan Beigi
2014-12-18 16:26:42 -08:00
parent 264bb66c16
commit a6d34caf2c
79 changed files with 1221 additions and 2389 deletions

View File

@@ -1,244 +1,98 @@
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.Configuration;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using System.IO;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Download
{
public interface ICompletedDownloadService
{
void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> importedHistory);
List<ImportResult> Import(TrackedDownload trackedDownload, String overrideOutputPath = null);
void Process(TrackedDownload trackedDownload);
}
public class CompletedDownloadService : ICompletedDownloadService
{
private readonly IConfigService _configService;
private readonly IDiskProvider _diskProvider;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IEventAggregator _eventAggregator;
private readonly IHistoryService _historyService;
private readonly Logger _logger;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
public CompletedDownloadService(IConfigService configService,
IDiskProvider diskProvider,
IDownloadedEpisodesImportService downloadedEpisodesImportService,
IEventAggregator eventAggregator,
IHistoryService historyService,
Logger logger)
IDownloadedEpisodesImportService downloadedEpisodesImportService)
{
_configService = configService;
_diskProvider = diskProvider;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
_eventAggregator = eventAggregator;
_historyService = historyService;
_logger = logger;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
}
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
public void Process(TrackedDownload trackedDownload)
{
return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID)))
.ToList();
}
public void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> importedHistory)
{
if (!_configService.EnableCompletedDownloadHandling)
if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
{
return;
}
if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Completed && trackedDownload.State == TrackedDownloadState.Downloading)
var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);
if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
{
var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);
if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
{
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download wasn't grabbed by drone or not in a category, ignoring download.");
return;
}
var importedItems = GetHistoryItems(importedHistory, trackedDownload.DownloadItem.DownloadClientId);
if (importedItems.Any())
{
trackedDownload.State = TrackedDownloadState.Imported;
UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as imported.");
}
else if (trackedDownload.Status != TrackedDownloadStatus.Ok)
{
_logger.Debug("Tracked download status is: {0}, skipping import. {1}", trackedDownload.Status,
String.Join(". ", trackedDownload.StatusMessages));
return;
}
else
{
var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder);
var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
if (downloadItemOutputPath.IsEmpty)
{
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download doesn't contain intermediate path, ignoring download.");
return;
}
if (!downloadedEpisodesFolder.IsEmpty && downloadedEpisodesFolder.Contains(downloadItemOutputPath))
{
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Intermediate Download path inside drone factory, ignoring download.");
return;
}
var importResults = Import(trackedDownload);
//Only attempt to associate it with a previous import if its still in the downloading state
if (trackedDownload.State == TrackedDownloadState.Downloading && importResults.Empty())
{
AssociateWithPreviouslyImported(trackedDownload, grabbedItems, importedHistory);
}
}
trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
return;
}
if (_configService.RemoveCompletedDownloads)
var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
if (downloadItemOutputPath.IsEmpty)
{
RemoveCompleted(trackedDownload, downloadClient);
trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
return;
}
var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder);
if (downloadedEpisodesFolder.Contains(downloadItemOutputPath))
{
trackedDownload.Warn("Intermediate Download path inside drone factory, Skipping.");
return;
}
Import(trackedDownload);
}
public List<ImportResult> Import(TrackedDownload trackedDownload, String overrideOutputPath = null)
private void Import(TrackedDownload trackedDownload)
{
var importResults = new List<ImportResult>();
var outputPath = overrideOutputPath ?? trackedDownload.DownloadItem.OutputPath.FullPath;
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, trackedDownload.DownloadItem);
if (_diskProvider.FolderExists(outputPath))
{
importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(outputPath), trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
ProcessImportResults(trackedDownload, outputPath, importResults);
}
else if (_diskProvider.FileExists(outputPath))
{
importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(outputPath), trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
ProcessImportResults(trackedDownload, outputPath, importResults);
}
return importResults;
}
private void UpdateStatusMessage(TrackedDownload trackedDownload, LogLevel logLevel, String message, params object[] args)
{
var statusMessage = String.Format(message, args);
var logMessage = String.Format("[{0}] {1}", trackedDownload.DownloadItem.Title, statusMessage);
if (trackedDownload.StatusMessage != statusMessage)
{
trackedDownload.SetStatusLevel(logLevel);
trackedDownload.StatusMessage = statusMessage;
_logger.Log(logLevel, logMessage);
}
else
{
_logger.Debug(logMessage);
}
}
private void ProcessImportResults(TrackedDownload trackedDownload, String outputPath, List<ImportResult> importResults)
{
if (importResults.Empty())
{
UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found are eligible for import in {0}", outputPath);
trackedDownload.Warn("No files found are eligible for import in {0}", outputPath);
return;
}
else if (importResults.Any(v => v.Result == ImportResultType.Imported) && importResults.All(v => v.Result == ImportResultType.Imported || v.Result == ImportResultType.Rejected))
if (importResults.Any(c => c.Result != ImportResultType.Imported))
{
UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", importResults.Count(v => v.Result == ImportResultType.Imported));
var statusMessages = importResults
.Where(v => v.Result != ImportResultType.Imported)
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors))
.ToArray();
trackedDownload.State = TrackedDownloadState.Imported;
trackedDownload.Warn(statusMessages);
return;
}
else
{
var errors = importResults
.Where(v => v.Result == ImportResultType.Skipped || v.Result == ImportResultType.Rejected)
.Select(v => v.Errors.Aggregate(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), (a, r) => a + "\r\n- " + r))
.Aggregate("Failed to import:", (a, r) => a + "\r\n" + r);
trackedDownload.StatusMessages = importResults.Where(v => v.Result == ImportResultType.Skipped || v.Result == ImportResultType.Rejected)
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors)).ToList();
trackedDownload.State = TrackedDownloadStage.Imported;
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
UpdateStatusMessage(trackedDownload, LogLevel.Error, errors);
}
}
private void AssociateWithPreviouslyImported(TrackedDownload trackedDownload, List<History.History> grabbedItems, List<History.History> importedHistory)
{
if (grabbedItems.Any())
{
var episodeIds = trackedDownload.RemoteEpisode.Episodes.Select(v => v.Id).ToList();
// Check if we can associate it with a previous drone factory import.
var importedItems = importedHistory.Where(v => v.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID) == null &&
episodeIds.Contains(v.EpisodeId) &&
v.Data.GetValueOrDefault("droppedPath") != null &&
new FileInfo(v.Data["droppedPath"]).Directory.Name == grabbedItems.First().SourceTitle
).ToList();
if (importedItems.Count == 1)
{
var importedFile = new FileInfo(importedItems.First().Data["droppedPath"]);
if (importedFile.Directory.Name == grabbedItems.First().SourceTitle)
{
trackedDownload.State = TrackedDownloadState.Imported;
importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT];
importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID];
_historyService.UpdateHistoryData(importedItems.First().Id, importedItems.First().Data);
UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Intermediate Download path does not exist, but found probable drone factory ImportEvent.");
return;
}
}
}
UpdateStatusMessage(trackedDownload, LogLevel.Error, "Intermediate Download path does not exist: {0}", trackedDownload.DownloadItem.OutputPath);
}
private void RemoveCompleted(TrackedDownload trackedDownload, IDownloadClient downloadClient)
{
if (trackedDownload.State == TrackedDownloadState.Imported && !trackedDownload.DownloadItem.IsReadOnly)
{
try
{
_logger.Debug("[{0}] Removing completed download from history.", trackedDownload.DownloadItem.Title);
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{
_logger.Debug("Removing completed download directory: {0}",
trackedDownload.DownloadItem.OutputPath);
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
}
else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{
_logger.Debug("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath);
_diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath.FullPath);
}
trackedDownload.State = TrackedDownloadState.Removed;
}
catch (NotSupportedException)
{
UpdateStatusMessage(trackedDownload, LogLevel.Debug,
"Removing item not supported by your download client.");
}
}
}
}
}