Fixed: Blacklist Retry logic will now properly handle Sabnzbd changing the unique id.

This commit is contained in:
Taloth Saldono 2014-07-19 19:37:06 +02:00
parent 47089d360d
commit 53e723a301
11 changed files with 116 additions and 47 deletions

View File

@ -10,12 +10,12 @@ namespace NzbDrone.Api.History
public class HistoryModule : NzbDroneRestModule<HistoryResource> public class HistoryModule : NzbDroneRestModule<HistoryResource>
{ {
private readonly IHistoryService _historyService; private readonly IHistoryService _historyService;
private readonly IFailedDownloadService _failedDownloadService; private readonly IDownloadTrackingService _downloadTrackingService;
public HistoryModule(IHistoryService historyService, IFailedDownloadService failedDownloadService) public HistoryModule(IHistoryService historyService, IDownloadTrackingService downloadTrackingService)
{ {
_historyService = historyService; _historyService = historyService;
_failedDownloadService = failedDownloadService; _downloadTrackingService = downloadTrackingService;
GetResourcePaged = GetHistory; GetResourcePaged = GetHistory;
Post["/failed"] = x => MarkAsFailed(); Post["/failed"] = x => MarkAsFailed();
@ -51,7 +51,7 @@ namespace NzbDrone.Api.History
private Response MarkAsFailed() private Response MarkAsFailed()
{ {
var id = (int)Request.Form.Id; var id = (int)Request.Form.Id;
_failedDownloadService.MarkAsFailed(id); _downloadTrackingService.MarkAsFailed(id);
return new Object().AsResponse(); return new Object().AsResponse();
} }
} }

View File

@ -11,12 +11,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public class NotInQueueSpecification : IDecisionEngineSpecification public class NotInQueueSpecification : IDecisionEngineSpecification
{ {
private readonly IQueueService _queueService; private readonly IDownloadTrackingService _downloadTrackingService;
private readonly Logger _logger; private readonly Logger _logger;
public NotInQueueSpecification(IQueueService queueService, Logger logger) public NotInQueueSpecification(IDownloadTrackingService downloadTrackingService, Logger logger)
{ {
_queueService = queueService; _downloadTrackingService = downloadTrackingService;
_logger = logger; _logger = logger;
} }
@ -30,7 +30,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{ {
var queue = _queueService.GetQueue().Select(q => q.RemoteEpisode); var queue = _downloadTrackingService.GetQueuedDownloads()
.Where(v => v.State == TrackedDownloadState.Downloading)
.Select(q => q.DownloadItem.RemoteEpisode).ToList();
if (IsInQueue(subject, queue)) if (IsInQueue(subject, queue))
{ {

View File

@ -223,14 +223,16 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
} }
} }
public override void RemoveItem(string id) public override void RemoveItem(String id)
{ {
_proxy.RemoveFromHistory(id, Settings); _proxy.RemoveFromHistory(id, Settings);
} }
public override void RetryDownload(string id) public override String RetryDownload(String id)
{ {
_proxy.RetryDownload(id, Settings); _proxy.RetryDownload(id, Settings);
return id;
} }
public override DownloadClientStatus GetStatus() public override DownloadClientStatus GetStatus()

View File

@ -74,13 +74,13 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
{ {
return new DownloadClientItem[0]; return new DownloadClientItem[0];
} }
public override void RemoveItem(string id) public override void RemoveItem(String id)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override void RetryDownload(string id) public override String RetryDownload(String id)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View File

@ -215,7 +215,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
} }
} }
public override void RemoveItem(string id) public override void RemoveItem(String id)
{ {
if (GetQueue().Any(v => v.DownloadClientId == id)) if (GetQueue().Any(v => v.DownloadClientId == id))
{ {
@ -227,9 +227,27 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
} }
} }
public override void RetryDownload(string id) public override String RetryDownload(String id)
{ {
// Sabnzbd changed the nzo_id for retried downloads without reporting it back to us. We need to try to determine the new ID.
var history = GetHistory().Where(v => v.DownloadClientId == id).ToList();
_proxy.RetryDownload(id, Settings); _proxy.RetryDownload(id, Settings);
if (history.Count() != 1)
{
return id;
}
var queue = GetQueue().Where(v => v.Category == history.First().Category && v.Title == history.First().Title).ToList();
if (queue.Count() != 1)
{
return id;
}
return queue.First().DownloadClientId;
} }
protected IEnumerable<SabnzbdCategory> GetCategories(SabnzbdConfig config) protected IEnumerable<SabnzbdCategory> GetCategories(SabnzbdConfig config)

View File

@ -124,12 +124,12 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
} }
} }
public override void RemoveItem(string id) public override void RemoveItem(String id)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override void RetryDownload(string id) public override String RetryDownload(String id)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View File

@ -205,7 +205,7 @@ namespace NzbDrone.Core.Download
} }
} }
} }
private void UpdateStatusMessage(TrackedDownload trackedDownload, LogLevel logLevel, String message, params object[] args) private void UpdateStatusMessage(TrackedDownload trackedDownload, LogLevel logLevel, String message, params object[] args)
{ {
var statusMessage = String.Format(message, args); var statusMessage = String.Format(message, args);

View File

@ -67,10 +67,10 @@ namespace NzbDrone.Core.Download
get; get;
} }
public abstract string Download(RemoteEpisode remoteEpisode); public abstract String Download(RemoteEpisode remoteEpisode);
public abstract IEnumerable<DownloadClientItem> GetItems(); public abstract IEnumerable<DownloadClientItem> GetItems();
public abstract void RemoveItem(string id); public abstract void RemoveItem(string id);
public abstract void RetryDownload(string id); public abstract String RetryDownload(string id);
public abstract DownloadClientStatus GetStatus(); public abstract DownloadClientStatus GetStatus();
protected RemoteEpisode GetRemoteEpisode(String title) protected RemoteEpisode GetRemoteEpisode(String title)

View File

@ -18,6 +18,8 @@ namespace NzbDrone.Core.Download
TrackedDownload[] GetTrackedDownloads(); TrackedDownload[] GetTrackedDownloads();
TrackedDownload[] GetCompletedDownloads(); TrackedDownload[] GetCompletedDownloads();
TrackedDownload[] GetQueuedDownloads(); TrackedDownload[] GetQueuedDownloads();
void MarkAsFailed(Int32 historyId);
} }
public class DownloadTrackingService : IDownloadTrackingService, IExecute<CheckForFinishedDownloadCommand>, IHandleAsync<ApplicationStartedEvent>, IHandle<EpisodeGrabbedEvent> public class DownloadTrackingService : IDownloadTrackingService, IExecute<CheckForFinishedDownloadCommand>, IHandleAsync<ApplicationStartedEvent>, IHandle<EpisodeGrabbedEvent>
@ -78,6 +80,22 @@ namespace NzbDrone.Core.Download
}, TimeSpan.FromSeconds(5.0)); }, TimeSpan.FromSeconds(5.0));
} }
public void MarkAsFailed(Int32 historyId)
{
var item = _historyService.Get(historyId);
var trackedDownload = GetTrackedDownloads()
.Where(h => h.DownloadItem.DownloadClientId.Equals(item.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID)))
.FirstOrDefault();
if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Unknown)
{
ProcessTrackedDownloads();
}
_failedDownloadService.MarkAsFailed(trackedDownload, item);
}
private TrackedDownload[] FilterQueuedDownloads(IEnumerable<TrackedDownload> trackedDownloads) private TrackedDownload[] FilterQueuedDownloads(IEnumerable<TrackedDownload> trackedDownloads)
{ {
var enabledFailedDownloadHandling = _configService.EnableFailedDownloadHandling; var enabledFailedDownloadHandling = _configService.EnableFailedDownloadHandling;

View File

@ -13,7 +13,7 @@ namespace NzbDrone.Core.Download
{ {
public interface IFailedDownloadService public interface IFailedDownloadService
{ {
void MarkAsFailed(int historyId); void MarkAsFailed(TrackedDownload trackedDownload, History.History grabbedHistory);
void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> failedHistory); void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> failedHistory);
} }
@ -35,10 +35,14 @@ namespace NzbDrone.Core.Download
_logger = logger; _logger = logger;
} }
public void MarkAsFailed(int historyId) public void MarkAsFailed(TrackedDownload trackedDownload, History.History grabbedHistory)
{ {
var item = _historyService.Get(historyId); if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Downloading)
PublishDownloadFailedEvent(new List<History.History> { item }, "Manually marked as failed"); {
trackedDownload.State = TrackedDownloadState.DownloadFailed;
}
PublishDownloadFailedEvent(new List<History.History> { grabbedHistory }, "Manually marked as failed");
} }
public void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> failedHistory) public void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> failedHistory)
@ -54,7 +58,7 @@ namespace NzbDrone.Core.Download
if (!grabbedItems.Any()) if (!grabbedItems.Any())
{ {
UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Download was not grabbed by drone, ignoring download"); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Download was not grabbed by drone, ignoring download");
return; return;
} }
@ -64,7 +68,7 @@ namespace NzbDrone.Core.Download
if (failedItems.Any()) if (failedItems.Any())
{ {
UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Already added to history as failed"); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as failed.");
} }
else else
{ {
@ -78,7 +82,7 @@ namespace NzbDrone.Core.Download
if (!grabbedItems.Any()) if (!grabbedItems.Any())
{ {
UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Download wasn't grabbed by drone or not in a category, ignoring download."); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Download wasn't grabbed by drone or not in a category, ignoring download.");
return; return;
} }
@ -86,13 +90,13 @@ namespace NzbDrone.Core.Download
if (trackedDownload.DownloadItem.Message.Equals("Unpacking failed, write error or disk is full?", if (trackedDownload.DownloadItem.Message.Equals("Unpacking failed, write error or disk is full?",
StringComparison.InvariantCultureIgnoreCase)) StringComparison.InvariantCultureIgnoreCase))
{ {
UpdateStatusMessage(LogLevel.Error, trackedDownload, "Download failed due to lack of disk space, not blacklisting."); UpdateStatusMessage(trackedDownload, LogLevel.Error, "Download failed due to lack of disk space, not blacklisting.");
return; return;
} }
if (FailedDownloadForRecentRelease(downloadClient, trackedDownload, grabbedItems)) if (FailedDownloadForRecentRelease(downloadClient, trackedDownload, grabbedItems))
{ {
UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Recent release Failed, do not blacklist."); _logger.Debug("[{0}] Recent release Failed, do not blacklist.", trackedDownload.DownloadItem.Title);
return; return;
} }
@ -102,7 +106,7 @@ namespace NzbDrone.Core.Download
if (failedItems.Any()) if (failedItems.Any())
{ {
UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Already added to history as failed."); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as failed.");
} }
else else
{ {
@ -117,7 +121,7 @@ namespace NzbDrone.Core.Download
if (grabbedItems.Any() && failedItems.Any()) if (grabbedItems.Any() && failedItems.Any())
{ {
UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Already added to history as failed, updating tracked state."); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as failed, updating tracked state.");
trackedDownload.State = TrackedDownloadState.DownloadFailed; trackedDownload.State = TrackedDownloadState.DownloadFailed;
} }
} }
@ -126,14 +130,14 @@ namespace NzbDrone.Core.Download
{ {
try try
{ {
_logger.Debug("[{0}] Removing failed download from client", trackedDownload.DownloadItem.Title); _logger.Debug("[{0}] Removing failed download from client.", trackedDownload.DownloadItem.Title);
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId); downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
trackedDownload.State = TrackedDownloadState.Removed; trackedDownload.State = TrackedDownloadState.Removed;
} }
catch (NotSupportedException) catch (NotSupportedException)
{ {
UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Removing item not supported by your download client."); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Removing item not supported by your download client.");
} }
} }
} }
@ -144,38 +148,62 @@ namespace NzbDrone.Core.Download
if (!Double.TryParse(matchingHistoryItems.First().Data.GetValueOrDefault("ageHours"), out ageHours)) if (!Double.TryParse(matchingHistoryItems.First().Data.GetValueOrDefault("ageHours"), out ageHours))
{ {
_logger.Info("[{0}] Unable to determine age of failed download.", trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Info, "Unable to determine age of failed download.");
return false; return false;
} }
if (ageHours > _configService.BlacklistGracePeriod) if (ageHours > _configService.BlacklistGracePeriod)
{ {
_logger.Info("[{0}] Failed download is older than the grace period.", trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Info, "Download Failed, Failed download is older than the grace period.");
return false; return false;
} }
if (trackedDownload.RetryCount >= _configService.BlacklistRetryLimit) if (trackedDownload.RetryCount >= _configService.BlacklistRetryLimit)
{ {
_logger.Info("[{0}] Retry limit reached.", trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Info, "Download Failed, Retry limit reached.");
return false; return false;
} }
if (trackedDownload.RetryCount == 0 || trackedDownload.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow) if (trackedDownload.LastRetry == new DateTime())
{
trackedDownload.LastRetry = DateTime.UtcNow;
}
if (trackedDownload.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow)
{ {
_logger.Info("[{0}] Retrying failed release.", trackedDownload.DownloadItem.Title);
trackedDownload.LastRetry = DateTime.UtcNow; trackedDownload.LastRetry = DateTime.UtcNow;
trackedDownload.RetryCount++; trackedDownload.RetryCount++;
UpdateStatusMessage(trackedDownload, LogLevel.Info, "Download Failed, initiating retry attempt {0}/{1}.", trackedDownload.RetryCount, _configService.BlacklistRetryLimit);
try try
{ {
downloadClient.RetryDownload(trackedDownload.DownloadItem.DownloadClientId); var newDownloadClientId = downloadClient.RetryDownload(trackedDownload.DownloadItem.DownloadClientId);
if (newDownloadClientId != trackedDownload.DownloadItem.DownloadClientId)
{
var oldTrackingId = trackedDownload.TrackingId;
var newTrackingId = String.Format("{0}-{1}", downloadClient.Definition.Id, newDownloadClientId);
trackedDownload.TrackingId = newTrackingId;
trackedDownload.DownloadItem.DownloadClientId = newDownloadClientId;
_logger.Debug("[{0}] Changed id from {1} to {2}.", trackedDownload.DownloadItem.Title, oldTrackingId, newTrackingId);
var newHistoryData = new Dictionary<String, String>(matchingHistoryItems.First().Data);
newHistoryData[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = newDownloadClientId;
_historyService.UpdateHistoryData(matchingHistoryItems.First().Id, newHistoryData);
}
} }
catch (NotSupportedException ex) catch (NotSupportedException ex)
{ {
_logger.Debug("Retrying failed downloads is not supported by your download client"); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Retrying failed downloads is not supported by your download client.");
return false; return false;
} }
} }
else
{
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download Failed, waiting for retry interval to expire.");
}
return true; return true;
} }
@ -206,16 +234,16 @@ namespace NzbDrone.Core.Download
_eventAggregator.PublishEvent(downloadFailedEvent); _eventAggregator.PublishEvent(downloadFailedEvent);
} }
private void UpdateStatusMessage(LogLevel logLevel, TrackedDownload trackedDownload, String message, params object[] args) private void UpdateStatusMessage(TrackedDownload trackedDownload, LogLevel logLevel, String message, params object[] args)
{ {
var statusMessage = String.Format(message, args); var statusMessage = String.Format(message, args);
var logMessage = String.Format("[{0}] {1}", trackedDownload.DownloadItem.Title, message); var logMessage = String.Format("[{0}] {1}", trackedDownload.DownloadItem.Title, statusMessage);
if (trackedDownload.StatusMessage != statusMessage) if (trackedDownload.StatusMessage != statusMessage)
{ {
trackedDownload.HasError = logLevel >= LogLevel.Warn; trackedDownload.HasError = logLevel >= LogLevel.Warn;
trackedDownload.StatusMessage = statusMessage; trackedDownload.StatusMessage = statusMessage;
_logger.Log(logLevel, statusMessage); _logger.Log(logLevel, logMessage);
} }
else else
{ {

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
@ -9,10 +10,10 @@ namespace NzbDrone.Core.Download
{ {
DownloadProtocol Protocol { get; } DownloadProtocol Protocol { get; }
string Download(RemoteEpisode remoteEpisode); String Download(RemoteEpisode remoteEpisode);
IEnumerable<DownloadClientItem> GetItems(); IEnumerable<DownloadClientItem> GetItems();
void RemoveItem(string id); void RemoveItem(String id);
void RetryDownload(string id); String RetryDownload(String id);
DownloadClientStatus GetStatus(); DownloadClientStatus GetStatus();
} }