New: The History->Queue UI now show some elementary error information for failed imports.

pull/92/head
Taloth Saldono 11 years ago
parent 981ffb09fc
commit 47089d360d

@ -37,6 +37,7 @@ module.exports = function (grunt) {
'Content/theme.less', 'Content/theme.less',
'Content/overrides.less', 'Content/overrides.less',
'Series/series.less', 'Series/series.less',
'History/history.less',
'AddSeries/addSeries.less', 'AddSeries/addSeries.less',
'Calendar/calendar.less', 'Calendar/calendar.less',
'Cells/cells.less', 'Cells/cells.less',

@ -17,5 +17,6 @@ namespace NzbDrone.Api.Queue
public Decimal Sizeleft { get; set; } public Decimal Sizeleft { get; set; }
public TimeSpan? Timeleft { get; set; } public TimeSpan? Timeleft { get; set; }
public String Status { get; set; } public String Status { get; set; }
public String ErrorMessage { get; set; }
} }
} }

@ -12,6 +12,7 @@ using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -103,14 +104,20 @@ namespace NzbDrone.Core.Test.Download
{ {
Mocker.GetMock<IDownloadedEpisodesImportService>() Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>())) .Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<Core.MediaFiles.EpisodeImport.ImportDecision>() { new Core.MediaFiles.EpisodeImport.ImportDecision(null) }); .Returns(new List<ImportDecision>()
{
new ImportDecision(null)
});
} }
private void GivenFailedImport() private void GivenFailedImport()
{ {
Mocker.GetMock<IDownloadedEpisodesImportService>() Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>())) .Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<Core.MediaFiles.EpisodeImport.ImportDecision>()); .Returns(new List<ImportDecision>()
{
new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure")
});
} }
private void VerifyNoImports() private void VerifyNoImports()
@ -265,6 +272,8 @@ namespace NzbDrone.Core.Test.Download
Subject.Execute(new CheckForFinishedDownloadCommand()); Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports(); VerifyNoImports();
ExceptionVerification.IgnoreErrors();
} }
[Test] [Test]
@ -289,6 +298,8 @@ namespace NzbDrone.Core.Test.Download
Subject.Execute(new CheckForFinishedDownloadCommand()); Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports(); VerifyNoImports();
ExceptionVerification.IgnoreWarns();
} }
[Test] [Test]
@ -412,6 +423,8 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never()); .Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
ExceptionVerification.IgnoreErrors();
} }
[Test] [Test]

@ -67,7 +67,7 @@ namespace NzbDrone.Core.Download
if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace()) if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
{ {
_logger.Trace("Ignoring download that wasn't grabbed by drone: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Download wasn't grabbed by drone or not in a category, ignoring download.");
return; return;
} }
@ -77,7 +77,7 @@ namespace NzbDrone.Core.Download
{ {
trackedDownload.State = TrackedDownloadState.Imported; trackedDownload.State = TrackedDownloadState.Imported;
_logger.Trace("Already added to history as imported: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Already added to history as imported.");
} }
else else
{ {
@ -85,13 +85,13 @@ namespace NzbDrone.Core.Download
string downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath; string downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
if (downloadItemOutputPath.IsNullOrWhiteSpace()) if (downloadItemOutputPath.IsNullOrWhiteSpace())
{ {
_logger.Trace("Storage path not specified: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download doesn't contain intermediate path, ignoring download.");
return; return;
} }
if (!downloadedEpisodesFolder.IsNullOrWhiteSpace() && (downloadedEpisodesFolder.PathEquals(downloadItemOutputPath) || downloadedEpisodesFolder.IsParentPath(downloadItemOutputPath))) if (!downloadedEpisodesFolder.IsNullOrWhiteSpace() && (downloadedEpisodesFolder.PathEquals(downloadItemOutputPath) || downloadedEpisodesFolder.IsParentPath(downloadItemOutputPath)))
{ {
_logger.Trace("Storage path inside drone factory, ignoring download: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Intermediate Download path inside drone factory, ignoring download.");
return; return;
} }
@ -99,19 +99,49 @@ namespace NzbDrone.Core.Download
{ {
var decisions = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem); var decisions = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);
if (decisions.Any()) if (!decisions.Any())
{ {
UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found eligible for import in {0}", trackedDownload.DownloadItem.OutputPath);
}
else if (decisions.Any(v => v.Approved))
{
UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", decisions.Count(v => v.Approved));
trackedDownload.State = TrackedDownloadState.Imported; trackedDownload.State = TrackedDownloadState.Imported;
} }
else
{
var rejections = decisions
.Where(v => !v.Approved)
.Select(v => v.Rejections.Aggregate(Path.GetFileName(v.LocalEpisode.Path), (a, r) => a + "\r\n- " + r))
.Aggregate("Failed to import:", (a, r) => a + "\r\n" + r);
UpdateStatusMessage(trackedDownload, LogLevel.Error, rejections);
}
} }
else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath)) else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
{ {
var decisions = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem); var decisions = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);
if (decisions.Any()) if (!decisions.Any())
{ {
UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found eligible for import in {0}", trackedDownload.DownloadItem.OutputPath);
}
else if (decisions.Any(v => v.Approved))
{
UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", decisions.Count(v => v.Approved));
trackedDownload.State = TrackedDownloadState.Imported; trackedDownload.State = TrackedDownloadState.Imported;
} }
else
{
var rejections = decisions
.Where(v => !v.Approved)
.Select(v => v.Rejections.Aggregate(Path.GetFileName(v.LocalEpisode.Path), (a, r) => a + "\r\n- " + r))
.Aggregate("Failed to import:", (a, r) => a + "\r\n" + r);
UpdateStatusMessage(trackedDownload, LogLevel.Error, rejections);
}
} }
else else
{ {
@ -137,13 +167,13 @@ namespace NzbDrone.Core.Download
importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID]; importedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID] = grabbedItems.First().Data[DownloadTrackingService.DOWNLOAD_CLIENT_ID];
_historyService.UpdateHistoryData(importedItems.First().Id, importedItems.First().Data); _historyService.UpdateHistoryData(importedItems.First().Id, importedItems.First().Data);
_logger.Trace("Storage path does not exist, but found probable drone factory ImportEvent: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Intermediate Download path does not exist, but found probable drone factory ImportEvent.");
return; return;
} }
} }
} }
_logger.Trace("Storage path does not exist: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(trackedDownload, LogLevel.Error, "Intermediate Download path does not exist: {0}", trackedDownload.DownloadItem.OutputPath);
return; return;
} }
} }
@ -153,17 +183,17 @@ namespace NzbDrone.Core.Download
{ {
try try
{ {
_logger.Info("Removing completed download from history: {0}", trackedDownload.DownloadItem.Title); _logger.Debug("[{0}] Removing completed download from history.", trackedDownload.DownloadItem.Title);
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId); downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath)) if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
{ {
_logger.Info("Removing completed download directory: {0}", trackedDownload.DownloadItem.OutputPath); _logger.Debug("Removing completed download directory: {0}", trackedDownload.DownloadItem.OutputPath);
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath, true); _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath, true);
} }
else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath)) else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
{ {
_logger.Info("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath); _logger.Debug("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath);
_diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath); _diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath);
} }
@ -171,9 +201,26 @@ namespace NzbDrone.Core.Download
} }
catch (NotSupportedException) catch (NotSupportedException)
{ {
_logger.Debug("Removing item not supported by your download client"); UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Removing item not supported by your download client.");
} }
} }
} }
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.HasError = logLevel >= LogLevel.Warn;
trackedDownload.StatusMessage = statusMessage;
_logger.Log(logLevel, logMessage);
}
else
{
_logger.Debug(logMessage);
}
}
} }
} }

@ -20,7 +20,7 @@ namespace NzbDrone.Core.Download
TrackedDownload[] GetQueuedDownloads(); TrackedDownload[] GetQueuedDownloads();
} }
public class DownloadTrackingService : IDownloadTrackingService, IExecute<CheckForFinishedDownloadCommand>, IHandle<ApplicationStartedEvent>, IHandle<EpisodeGrabbedEvent> public class DownloadTrackingService : IDownloadTrackingService, IExecute<CheckForFinishedDownloadCommand>, IHandleAsync<ApplicationStartedEvent>, IHandle<EpisodeGrabbedEvent>
{ {
private readonly IProvideDownloadClient _downloadClientProvider; private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IHistoryService _historyService; private readonly IHistoryService _historyService;
@ -201,7 +201,7 @@ namespace NzbDrone.Core.Download
ProcessTrackedDownloads(); ProcessTrackedDownloads();
} }
public void Handle(ApplicationStartedEvent message) public void HandleAsync(ApplicationStartedEvent message)
{ {
ProcessTrackedDownloads(); ProcessTrackedDownloads();
} }

@ -54,7 +54,7 @@ namespace NzbDrone.Core.Download
if (!grabbedItems.Any()) if (!grabbedItems.Any())
{ {
_logger.Trace("Download was not grabbed by drone, ignoring download: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Download was not grabbed by drone, ignoring download");
return; return;
} }
@ -64,7 +64,7 @@ namespace NzbDrone.Core.Download
if (failedItems.Any()) if (failedItems.Any())
{ {
_logger.Trace("Already added to history as failed: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Already added to history as failed");
} }
else else
{ {
@ -78,7 +78,7 @@ namespace NzbDrone.Core.Download
if (!grabbedItems.Any()) if (!grabbedItems.Any())
{ {
_logger.Trace("Download was not grabbed by drone, ignoring download: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Download wasn't grabbed by drone or not in a category, ignoring download.");
return; return;
} }
@ -86,13 +86,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))
{ {
_logger.Trace("Failed due to lack of disk space, do not blacklist: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(LogLevel.Error, trackedDownload, "Download failed due to lack of disk space, not blacklisting.");
return; return;
} }
if (FailedDownloadForRecentRelease(downloadClient, trackedDownload, grabbedItems)) if (FailedDownloadForRecentRelease(downloadClient, trackedDownload, grabbedItems))
{ {
_logger.Trace("Recent release Failed, do not blacklist: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Recent release Failed, do not blacklist.");
return; return;
} }
@ -102,7 +102,7 @@ namespace NzbDrone.Core.Download
if (failedItems.Any()) if (failedItems.Any())
{ {
_logger.Trace("Already added to history as failed: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Already added to history as failed.");
} }
else else
{ {
@ -117,7 +117,7 @@ namespace NzbDrone.Core.Download
if (grabbedItems.Any() && failedItems.Any()) if (grabbedItems.Any() && failedItems.Any())
{ {
_logger.Trace("Already added to history as failed, updating tracked state: " + trackedDownload.DownloadItem.Title); UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Already added to history as failed, updating tracked state.");
trackedDownload.State = TrackedDownloadState.DownloadFailed; trackedDownload.State = TrackedDownloadState.DownloadFailed;
} }
} }
@ -126,14 +126,14 @@ namespace NzbDrone.Core.Download
{ {
try try
{ {
_logger.Info("Removing failed download from client: {0}", 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)
{ {
_logger.Trace("Removing item not supported by your download client"); UpdateStatusMessage(LogLevel.Debug, trackedDownload, "Removing item not supported by your download client.");
} }
} }
} }
@ -144,25 +144,25 @@ 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.Debug("Unable to determine age of failed download: " + trackedDownload.DownloadItem.Title); _logger.Info("[{0}] Unable to determine age of failed download.", trackedDownload.DownloadItem.Title);
return false; return false;
} }
if (ageHours > _configService.BlacklistGracePeriod) if (ageHours > _configService.BlacklistGracePeriod)
{ {
_logger.Debug("Failed download is older than the grace period: " + trackedDownload.DownloadItem.Title); _logger.Info("[{0}] Failed download is older than the grace period.", trackedDownload.DownloadItem.Title);
return false; return false;
} }
if (trackedDownload.RetryCount >= _configService.BlacklistRetryLimit) if (trackedDownload.RetryCount >= _configService.BlacklistRetryLimit)
{ {
_logger.Debug("Retry limit reached: " + trackedDownload.DownloadItem.Title); _logger.Info("[{0}] Retry limit reached.", trackedDownload.DownloadItem.Title);
return false; return false;
} }
if (trackedDownload.RetryCount == 0 || trackedDownload.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow) if (trackedDownload.RetryCount == 0 || trackedDownload.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow)
{ {
_logger.Debug("Retrying failed release: " + trackedDownload.DownloadItem.Title); _logger.Info("[{0}] Retrying failed release.", trackedDownload.DownloadItem.Title);
trackedDownload.LastRetry = DateTime.UtcNow; trackedDownload.LastRetry = DateTime.UtcNow;
trackedDownload.RetryCount++; trackedDownload.RetryCount++;
@ -205,5 +205,22 @@ namespace NzbDrone.Core.Download
_eventAggregator.PublishEvent(downloadFailedEvent); _eventAggregator.PublishEvent(downloadFailedEvent);
} }
private void UpdateStatusMessage(LogLevel logLevel, TrackedDownload trackedDownload, String message, params object[] args)
{
var statusMessage = String.Format(message, args);
var logMessage = String.Format("[{0}] {1}", trackedDownload.DownloadItem.Title, message);
if (trackedDownload.StatusMessage != statusMessage)
{
trackedDownload.HasError = logLevel >= LogLevel.Warn;
trackedDownload.StatusMessage = statusMessage;
_logger.Log(logLevel, statusMessage);
}
else
{
_logger.Debug(logMessage);
}
}
} }
} }

@ -12,6 +12,8 @@ namespace NzbDrone.Core.Download
public DateTime StartedTracking { get; set; } public DateTime StartedTracking { get; set; }
public DateTime LastRetry { get; set; } public DateTime LastRetry { get; set; }
public Int32 RetryCount { get; set; } public Int32 RetryCount { get; set; }
public Boolean HasError { get; set; }
public String StatusMessage { get; set; }
} }
public enum TrackedDownloadState public enum TrackedDownloadState

@ -82,7 +82,7 @@ namespace NzbDrone.Core.MediaFiles
_diskProvider.DeleteFolder(directoryInfo.FullName, true); _diskProvider.DeleteFolder(directoryInfo.FullName, true);
} }
return importedDecisions; return importedDecisions.Union(decisions).ToList();
} }
public List<ImportDecision> ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem) public List<ImportDecision> ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem)
@ -99,7 +99,7 @@ namespace NzbDrone.Core.MediaFiles
var importedDecisions = _importApprovedEpisodes.Import(decisions, true, downloadClientItem); var importedDecisions = _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
return importedDecisions; return importedDecisions.Union(decisions).ToList();
} }
private void ProcessDownloadedEpisodesFolder() private void ProcessDownloadedEpisodesFolder()

@ -16,6 +16,7 @@ namespace NzbDrone.Core.Queue
public Decimal Sizeleft { get; set; } public Decimal Sizeleft { get; set; }
public TimeSpan? Timeleft { get; set; } public TimeSpan? Timeleft { get; set; }
public String Status { get; set; } public String Status { get; set; }
public String ErrorMessage { get; set; }
public RemoteEpisode RemoteEpisode { get; set; } public RemoteEpisode RemoteEpisode { get; set; }
} }
} }

@ -25,32 +25,37 @@ namespace NzbDrone.Core.Queue
public List<Queue> GetQueue() public List<Queue> GetQueue()
{ {
var queueItems = _downloadTrackingService.GetQueuedDownloads() var queueItems = _downloadTrackingService.GetQueuedDownloads()
.Select(v => v.DownloadItem) .OrderBy(v => v.DownloadItem.RemainingTime)
.OrderBy(v => v.RemainingTime)
.ToList(); .ToList();
return MapQueue(queueItems); return MapQueue(queueItems);
} }
private List<Queue> MapQueue(IEnumerable<DownloadClientItem> queueItems) private List<Queue> MapQueue(IEnumerable<TrackedDownload> queueItems)
{ {
var queued = new List<Queue>(); var queued = new List<Queue>();
foreach (var queueItem in queueItems) foreach (var queueItem in queueItems)
{ {
foreach (var episode in queueItem.RemoteEpisode.Episodes) foreach (var episode in queueItem.DownloadItem.RemoteEpisode.Episodes)
{ {
var queue = new Queue(); var queue = new Queue();
queue.Id = queueItem.DownloadClientId.GetHashCode() + episode.Id; queue.Id = queueItem.DownloadItem.DownloadClientId.GetHashCode() + episode.Id;
queue.Series = queueItem.RemoteEpisode.Series; queue.Series = queueItem.DownloadItem.RemoteEpisode.Series;
queue.Episode = episode; queue.Episode = episode;
queue.Quality = queueItem.RemoteEpisode.ParsedEpisodeInfo.Quality; queue.Quality = queueItem.DownloadItem.RemoteEpisode.ParsedEpisodeInfo.Quality;
queue.Title = queueItem.Title; queue.Title = queueItem.DownloadItem.Title;
queue.Size = queueItem.TotalSize; queue.Size = queueItem.DownloadItem.TotalSize;
queue.Sizeleft = queueItem.RemainingSize; queue.Sizeleft = queueItem.DownloadItem.RemainingSize;
queue.Timeleft = queueItem.RemainingTime; queue.Timeleft = queueItem.DownloadItem.RemainingTime;
queue.Status = queueItem.Status.ToString(); queue.Status = queueItem.DownloadItem.Status.ToString();
queue.RemoteEpisode = queueItem.RemoteEpisode; queue.RemoteEpisode = queueItem.DownloadItem.RemoteEpisode;
if (queueItem.HasError)
{
queue.ErrorMessage = queueItem.StatusMessage;
}
queued.Add(queue); queued.Add(queue);
} }
} }

@ -163,6 +163,11 @@
color : purple; color : purple;
} }
.icon-nd-import-failed:before {
.icon(@download-alt);
color: @brand-danger;
}
.icon-nd-download-failed:before { .icon-nd-download-failed:before {
.icon(@cloud-download); .icon(@cloud-download);
color: @brand-danger; color: @brand-danger;

@ -13,6 +13,7 @@ define(
if (this.cellValue) { if (this.cellValue) {
var status = this.cellValue.get('status').toLowerCase(); var status = this.cellValue.get('status').toLowerCase();
var errorMessage = (this.cellValue.get('errorMessage') || '');
var icon = 'icon-nd-downloading'; var icon = 'icon-nd-downloading';
var title = 'Downloading'; var title = 'Downloading';
@ -31,7 +32,29 @@ define(
title = 'Downloaded'; title = 'Downloaded';
} }
this.$el.html('<i class="{0}" title="{1}"></i>'.format(icon, title)); if (errorMessage !== '') {
if (status === 'completed') {
icon = 'icon-nd-import-failed';
title = "Import failed";
}
else {
icon = 'icon-nd-download-failed';
title = "Download failed";
}
this.$el.html('<i class="{0}"></i>'.format(icon));
this.$el.popover({
content : errorMessage.replace(new RegExp('\r\n', 'g'), '<br/>'),
html : true,
trigger : 'hover',
title : title,
placement: 'right',
container: this.$el
});
}
else {
this.$el.html('<i class="{0}" title="{1}"></i>'.format(icon, title));
}
} }
return this; return this;

@ -22,9 +22,8 @@ define(
this.$el.html("-"); this.$el.html("-");
} }
else { else {
this.$el.html(timeleft); this.$el.html('<span title="{1} / {2}">{0}</span>'.format(timeleft, remainingSize, totalSize));
} }
this.$el.attr('title', '{0} / {1}'.format(remainingSize, totalSize));
} }
return this; return this;

@ -0,0 +1,4 @@
.queue-status-cell .popover {
max-width: 800px;
}

@ -11,8 +11,7 @@ define(
render: function () { render: function () {
var date = Moment(this._getValue()); var date = Moment(this._getValue());
this.$el.html(date.format('LT')); this.$el.html('<span title="{1}">{0}</span>'.format(date.format('LT'), date.format('LLLL')));
this.$el.attr('title', date.format('LLLL'));
return this; return this;
} }

@ -12,6 +12,7 @@
<link href="/Content/theme.css" rel='stylesheet' type='text/css'/> <link href="/Content/theme.css" rel='stylesheet' type='text/css'/>
<link href="/Cells/cells.css" rel='stylesheet' type='text/css'> <link href="/Cells/cells.css" rel='stylesheet' type='text/css'>
<link href="/Series/series.css" rel='stylesheet' type='text/css'/> <link href="/Series/series.css" rel='stylesheet' type='text/css'/>
<link href="/History/history.css" rel='stylesheet' type='text/css'/>
<link href="/System/Logs/logs.css" rel='stylesheet' type='text/css'/> <link href="/System/Logs/logs.css" rel='stylesheet' type='text/css'/>
<link href="/Settings/settings.css" rel='stylesheet' type='text/css'/> <link href="/Settings/settings.css" rel='stylesheet' type='text/css'/>
<link href="/AddSeries/addSeries.css" rel='stylesheet' type='text/css'/> <link href="/AddSeries/addSeries.css" rel='stylesheet' type='text/css'/>

Loading…
Cancel
Save