Use DownloadClientIds to find matching series/episodes instead of relying solely on release name

Fixed: Show a warning in Queue when drone is unable to import due to a name mismatch
Fixed: Better UI messages for Queue when there is an error or warning
pull/4/head
Mark McDowall 10 years ago
parent 3f11fcf2e1
commit caa6a3858b

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Download;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Api.Episodes;
@ -17,6 +19,7 @@ namespace NzbDrone.Api.Queue
public TimeSpan? Timeleft { get; set; }
public DateTime? EstimatedCompletionTime { get; set; }
public String Status { get; set; }
public String ErrorMessage { get; set; }
public String TrackedDownloadStatus { get; set; }
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
}
}

@ -69,10 +69,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
queue.Add(new TrackedDownload
{
State = state,
DownloadItem = new DownloadClientItem
{
RemoteEpisode = remoteEpisode
}
RemoteEpisode = remoteEpisode
});
}

@ -9,11 +9,13 @@ using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Test.Common;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.Download
@ -27,15 +29,18 @@ namespace NzbDrone.Core.Test.Download
public void Setup()
{
_completed = Builder<DownloadClientItem>.CreateListOfSize(1)
.All()
.With(h => h.Status = DownloadItemStatus.Completed)
.With(h => h.OutputPath = @"C:\DropFolder\MyDownload".AsOsAgnostic())
.With(h => h.RemoteEpisode = new RemoteEpisode
{
Episodes = new List<Episode> { new Episode { Id = 1 } }
})
.Build()
.ToList();
.All()
.With(h => h.Status = DownloadItemStatus.Completed)
.With(h => h.OutputPath = @"C:\DropFolder\MyDownload".AsOsAgnostic())
.With(h => h.Title = "Drone.S01E01.HDTV")
.Build()
.ToList();
var remoteEpisode = new RemoteEpisode
{
Series = new Series(),
Episodes = new List<Episode> {new Episode {Id = 1}}
};
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClients())
@ -43,7 +48,7 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock<IDownloadClient>()
.SetupGet(c => c.Definition)
.Returns(new Core.Download.DownloadClientDefinition { Id = 1, Name = "testClient" });
.Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
@ -56,6 +61,14 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock<IHistoryService>()
.Setup(s => s.Failed())
.Returns(new List<History.History>());
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Int32>(), It.IsAny<IEnumerable<Int32>>()))
.Returns(remoteEpisode);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Int32>(), (SearchCriteriaBase)null))
.Returns(remoteEpisode);
Mocker.SetConstant<ICompletedDownloadService>(Mocker.Resolve<CompletedDownloadService>());
}
@ -311,10 +324,7 @@ namespace NzbDrone.Core.Test.Download
.All()
.With(h => h.Status = DownloadItemStatus.Completed)
.With(h => h.OutputPath = @"C:\DropFolder\MyDownload".AsOsAgnostic())
.With(h => h.RemoteEpisode = new RemoteEpisode
{
Episodes = new List<Episode> { new Episode { Id = 1 } }
})
.With(h => h.Title = "Drone.S01E01.HDTV")
.Build());
var grabbedHistory = Builder<History.History>.CreateListOfSize(2)

@ -5,6 +5,7 @@ using Moq;
using NUnit.Framework;
using FluentAssertions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Parser;
@ -30,7 +31,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
.Returns(30);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), null))
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), (SearchCriteriaBase)null))
.Returns(() => CreateRemoteEpisode());
Mocker.GetMock<IHttpClient>()
@ -64,11 +65,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
{
downloadClientItem.DownloadClient.Should().Be(Subject.Definition.Name);
downloadClientItem.DownloadClientId.Should().NotBeNullOrEmpty();
downloadClientItem.Title.Should().NotBeNullOrEmpty();
downloadClientItem.RemoteEpisode.Should().NotBeNull();
}
protected void VerifyQueued(DownloadClientItem downloadClientItem)

@ -7,8 +7,12 @@ using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download
@ -23,16 +27,24 @@ namespace NzbDrone.Core.Test.Download
public void Setup()
{
_completed = Builder<DownloadClientItem>.CreateListOfSize(5)
.All()
.With(h => h.Status = DownloadItemStatus.Completed)
.Build()
.ToList();
.All()
.With(h => h.Status = DownloadItemStatus.Completed)
.With(h => h.Title = "Drone.S01E01.HDTV")
.Build()
.ToList();
_failed = Builder<DownloadClientItem>.CreateListOfSize(1)
.All()
.With(h => h.Status = DownloadItemStatus.Failed)
.Build()
.ToList();
.All()
.With(h => h.Status = DownloadItemStatus.Failed)
.With(h => h.Title = "Drone.S01E01.HDTV")
.Build()
.ToList();
var remoteEpisode = new RemoteEpisode
{
Series = new Series(),
Episodes = new List<Episode> { new Episode { Id = 1 } }
};
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClients())
@ -40,7 +52,7 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock<IDownloadClient>()
.SetupGet(c => c.Definition)
.Returns(new Core.Download.DownloadClientDefinition { Id = 1, Name = "testClient" });
.Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" });
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableFailedDownloadHandling)
@ -50,6 +62,14 @@ namespace NzbDrone.Core.Test.Download
.Setup(s => s.Imported())
.Returns(new List<History.History>());
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Int32>(), It.IsAny<IEnumerable<Int32>>()))
.Returns(remoteEpisode);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Int32>(), (SearchCriteriaBase)null))
.Returns(remoteEpisode);
Mocker.SetConstant<IFailedDownloadService>(Mocker.Resolve<FailedDownloadService>());
}

@ -34,7 +34,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
var queue = _downloadTrackingService.GetQueuedDownloads()
.Where(v => v.State == TrackedDownloadState.Downloading)
.Select(q => q.DownloadItem.RemoteEpisode).ToList();
.Select(q => q.RemoteEpisode).ToList();
if (IsInQueue(subject, queue))
{

@ -201,9 +201,6 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
if (downloadClientItem.Category == Settings.TvCategory)
{
downloadClientItem.RemoteEpisode = GetRemoteEpisode(downloadClientItem.Title);
if (downloadClientItem.RemoteEpisode == null) continue;
yield return downloadClientItem;
}
}

@ -103,9 +103,6 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
historyItem.Status = DownloadItemStatus.Completed;
}
historyItem.RemoteEpisode = GetRemoteEpisode(historyItem.Title);
if (historyItem.RemoteEpisode == null) continue;
yield return historyItem;
}
}

@ -188,9 +188,6 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
if (downloadClientItem.Category == Settings.TvCategory)
{
downloadClientItem.RemoteEpisode = GetRemoteEpisode(downloadClientItem.Title);
if (downloadClientItem.RemoteEpisode == null) continue;
yield return downloadClientItem;
}
}

@ -90,9 +90,6 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
historyItem.RemainingTime = TimeSpan.Zero;
}
historyItem.RemoteEpisode = GetRemoteEpisode(historyItem.Title);
if (historyItem.RemoteEpisode == null) continue;
yield return historyItem;
}
@ -121,9 +118,6 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
historyItem.RemainingTime = TimeSpan.Zero;
}
historyItem.RemoteEpisode = GetRemoteEpisode(historyItem.Title);
if (historyItem.RemoteEpisode == null) continue;
yield return historyItem;
}
}

@ -8,7 +8,6 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events;
using System.IO;
namespace NzbDrone.Core.Download
@ -20,21 +19,18 @@ namespace NzbDrone.Core.Download
public class CompletedDownloadService : ICompletedDownloadService
{
private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService;
private readonly IDiskProvider _diskProvider;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IHistoryService _historyService;
private readonly Logger _logger;
public CompletedDownloadService(IEventAggregator eventAggregator,
IConfigService configService,
IDiskProvider diskProvider,
IDownloadedEpisodesImportService downloadedEpisodesImportService,
IHistoryService historyService,
Logger logger)
public CompletedDownloadService(IConfigService configService,
IDiskProvider diskProvider,
IDownloadedEpisodesImportService downloadedEpisodesImportService,
IHistoryService historyService,
Logger logger)
{
_eventAggregator = eventAggregator;
_configService = configService;
_diskProvider = diskProvider;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
@ -61,7 +57,7 @@ namespace NzbDrone.Core.Download
if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
{
UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Download wasn't grabbed by drone or not in a category, ignoring download.");
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download wasn't grabbed by drone or not in a category, ignoring download.");
return;
}
@ -73,10 +69,16 @@ namespace NzbDrone.Core.Download
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.", trackedDownload.Status);
return;
}
else
{
string downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
string downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
if (downloadItemOutputPath.IsNullOrWhiteSpace())
{
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download doesn't contain intermediate path, ignoring download.");
@ -105,7 +107,7 @@ namespace NzbDrone.Core.Download
{
if (grabbedItems.Any())
{
var episodeIds = trackedDownload.DownloadItem.RemoteEpisode.Episodes.Select(v => v.Id).ToList();
var episodeIds = trackedDownload.RemoteEpisode.Episodes.Select(v => v.Id).ToList();
// Check if we can associate it with a previous drone factory import.
importedItems = importedHistory.Where(v => v.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID) == null &&
@ -171,7 +173,7 @@ namespace NzbDrone.Core.Download
if (trackedDownload.StatusMessage != statusMessage)
{
trackedDownload.HasError = logLevel >= LogLevel.Warn;
trackedDownload.SetStatusLevel(logLevel);
trackedDownload.StatusMessage = statusMessage;
_logger.Log(logLevel, logMessage);
}
@ -200,6 +202,9 @@ namespace NzbDrone.Core.Download
.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();
UpdateStatusMessage(trackedDownload, LogLevel.Error, errors);
}
}

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
@ -21,7 +20,6 @@ namespace NzbDrone.Core.Download
{
protected readonly IConfigService _configService;
protected readonly IDiskProvider _diskProvider;
protected readonly IParsingService _parsingService;
protected readonly IRemotePathMappingService _remotePathMappingService;
protected readonly Logger _logger;
@ -55,7 +53,6 @@ namespace NzbDrone.Core.Download
{
_configService = configService;
_diskProvider = diskProvider;
_parsingService = parsingService;
_remotePathMappingService = remotePathMappingService;
_logger = logger;
}
@ -76,17 +73,6 @@ namespace NzbDrone.Core.Download
public abstract String RetryDownload(string id);
public abstract DownloadClientStatus GetStatus();
protected RemoteEpisode GetRemoteEpisode(String title)
{
var parsedEpisodeInfo = Parser.Parser.ParseTitle(title);
if (parsedEpisodeInfo == null) return null;
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
if (remoteEpisode.Series == null) return null;
return remoteEpisode;
}
public ValidationResult Test()
{
var failures = new List<ValidationFailure>();

@ -1,5 +1,4 @@
using NzbDrone.Core.Parser.Model;
using System;
using System;
namespace NzbDrone.Core.Download
{
@ -21,7 +20,5 @@ namespace NzbDrone.Core.Download
public DownloadItemStatus Status { get; set; }
public Boolean IsEncrypted { get; set; }
public Boolean IsReadOnly { get; set; }
public RemoteEpisode RemoteEpisode { get; set; }
}
}

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Download
namespace NzbDrone.Core.Download
{
public enum DownloadItemStatus
{

@ -9,6 +9,7 @@ using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Queue;
namespace NzbDrone.Core.Download
@ -22,7 +23,10 @@ namespace NzbDrone.Core.Download
void MarkAsFailed(Int32 historyId);
}
public class DownloadTrackingService : IDownloadTrackingService, IExecute<CheckForFinishedDownloadCommand>, IHandleAsync<ApplicationStartedEvent>, IHandle<EpisodeGrabbedEvent>
public class DownloadTrackingService : IDownloadTrackingService,
IExecute<CheckForFinishedDownloadCommand>,
IHandleAsync<ApplicationStartedEvent>,
IHandle<EpisodeGrabbedEvent>
{
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IHistoryService _historyService;
@ -30,6 +34,7 @@ namespace NzbDrone.Core.Download
private readonly IConfigService _configService;
private readonly IFailedDownloadService _failedDownloadService;
private readonly ICompletedDownloadService _completedDownloadService;
private readonly IParsingService _parsingService;
private readonly Logger _logger;
private readonly ICached<TrackedDownload[]> _trackedDownloadCache;
@ -44,6 +49,7 @@ namespace NzbDrone.Core.Download
ICacheManager cacheManager,
IFailedDownloadService failedDownloadService,
ICompletedDownloadService completedDownloadService,
IParsingService parsingService,
Logger logger)
{
_downloadClientProvider = downloadClientProvider;
@ -52,6 +58,7 @@ namespace NzbDrone.Core.Download
_configService = configService;
_failedDownloadService = failedDownloadService;
_completedDownloadService = completedDownloadService;
_parsingService = parsingService;
_logger = logger;
_trackedDownloadCache = cacheManager.GetCache<TrackedDownload[]>(GetType());
@ -73,7 +80,7 @@ namespace NzbDrone.Core.Download
{
return _trackedDownloadCache.Get("queued", () =>
{
UpdateTrackedDownloads();
UpdateTrackedDownloads(_historyService.Grabbed());
return FilterQueuedDownloads(GetTrackedDownloads());
@ -119,7 +126,7 @@ namespace NzbDrone.Core.Download
.ToList();
}
private Boolean UpdateTrackedDownloads()
private Boolean UpdateTrackedDownloads(List<History.History> grabbedHistory)
{
var downloadClients = _downloadClientProvider.GetDownloadClients();
@ -140,13 +147,9 @@ namespace NzbDrone.Core.Download
if (!oldTrackedDownloads.TryGetValue(trackingId, out trackedDownload))
{
trackedDownload = new TrackedDownload
{
TrackingId = trackingId,
DownloadClient = downloadClient.Definition.Id,
StartedTracking = DateTime.UtcNow,
State = TrackedDownloadState.Unknown
};
trackedDownload = GetTrackedDownload(trackingId, downloadClient.Definition.Id, downloadItem, grabbedHistory);
if (trackedDownload == null) continue;
_logger.Debug("[{0}] Started tracking download with id {1}.", downloadItem.Title, trackingId);
stateChanged = true;
@ -182,9 +185,9 @@ namespace NzbDrone.Core.Download
var failedHistory = _historyService.Failed();
var importedHistory = _historyService.Imported();
var stateChanged = UpdateTrackedDownloads();
var stateChanged = UpdateTrackedDownloads(grabbedHistory);
var downloadClients = _downloadClientProvider.GetDownloadClients();
var downloadClients = _downloadClientProvider.GetDownloadClients().ToList();
var trackedDownloads = GetTrackedDownloads();
foreach (var trackedDownload in trackedDownloads)
@ -215,6 +218,50 @@ namespace NzbDrone.Core.Download
}
}
private TrackedDownload GetTrackedDownload(String trackingId, Int32 downloadClient, DownloadClientItem downloadItem, List<History.History> grabbedHistory)
{
var trackedDownload = new TrackedDownload
{
TrackingId = trackingId,
DownloadClient = downloadClient,
DownloadItem = downloadItem,
StartedTracking = DateTime.UtcNow,
State = TrackedDownloadState.Unknown,
Status = TrackedDownloadStatus.Ok,
};
var historyItems = grabbedHistory.Where(h =>
{
var downloadClientId = h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID);
if (downloadClientId == null) return false;
return downloadClientId.Equals(trackedDownload.DownloadItem.DownloadClientId);
}).ToList();
var parsedEpisodeInfo = Parser.Parser.ParseTitle(trackedDownload.DownloadItem.Title);
if (parsedEpisodeInfo == null) return null;
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
if (remoteEpisode.Series == null)
{
if (historyItems.Empty()) return null;
trackedDownload.Status = TrackedDownloadStatus.Warning;
trackedDownload.StatusMessages.Add(new TrackedDownloadStatusMessage(
trackedDownload.DownloadItem.Title,
"Series title mismatch, automatic import is not possible")
);
remoteEpisode = _parsingService.Map(parsedEpisodeInfo, historyItems.First().SeriesId, historyItems.Select(h => h.EpisodeId));
}
trackedDownload.RemoteEpisode = remoteEpisode;
return trackedDownload;
}
public void Execute(CheckForFinishedDownloadCommand message)
{
ProcessTrackedDownloads();

@ -3,10 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Download
@ -68,7 +66,7 @@ namespace NzbDrone.Core.Download
if (!grabbedItems.Any())
{
UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Download was not grabbed by drone, ignoring download");
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download was not grabbed by drone, ignoring download");
return;
}
@ -92,7 +90,7 @@ namespace NzbDrone.Core.Download
if (!grabbedItems.Any())
{
UpdateStatusMessage(trackedDownload, LogLevel.Debug, "Download wasn't grabbed by drone or not in a category, ignoring download.");
UpdateStatusMessage(trackedDownload, LogLevel.Warn, "Download wasn't grabbed by drone or not in a category, ignoring download.");
return;
}
@ -244,8 +242,12 @@ namespace NzbDrone.Core.Download
if (trackedDownload.StatusMessage != statusMessage)
{
trackedDownload.HasError = logLevel >= LogLevel.Warn;
trackedDownload.SetStatusLevel(logLevel);
trackedDownload.StatusMessage = statusMessage;
trackedDownload.StatusMessages = new List<TrackedDownloadStatusMessage>
{
new TrackedDownloadStatusMessage(trackedDownload.DownloadItem.Title, statusMessage)
};
_logger.Log(logLevel, logMessage);
}
else

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
@ -9,12 +11,33 @@ namespace NzbDrone.Core.Download
public Int32 DownloadClient { get; set; }
public DownloadClientItem DownloadItem { get; set; }
public TrackedDownloadState State { get; set; }
public TrackedDownloadStatus Status { get; set; }
public DateTime StartedTracking { get; set; }
public DateTime LastRetry { get; set; }
public Int32 RetryCount { get; set; }
public Boolean HasError { get; set; }
public String StatusMessage { get; set; }
public List<String> StatusMessages { get; set; }
public RemoteEpisode RemoteEpisode { get; set; }
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
public TrackedDownload()
{
StatusMessages = new List<TrackedDownloadStatusMessage>();
}
public void SetStatusLevel(LogLevel logLevel)
{
if (logLevel == LogLevel.Warn)
{
Status = TrackedDownloadStatus.Warning;
}
if (logLevel >= LogLevel.Error)
{
Status = TrackedDownloadStatus.Error;
}
else Status = TrackedDownloadStatus.Ok;
}
}
public enum TrackedDownloadState
@ -25,4 +48,11 @@ namespace NzbDrone.Core.Download
DownloadFailed,
Removed
}
public enum TrackedDownloadStatus
{
Ok,
Warning,
Error
}
}

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Download
{
public class TrackedDownloadStatusMessage
{
public String Title { get; set; }
public List<String> Messages { get; set; }
public TrackedDownloadStatusMessage(String title, List<String> messages)
{
Title = title;
Messages = messages;
}
public TrackedDownloadStatusMessage(String title, String message)
{
Title = title;
Messages = new List<String>{ message };
}
}
}

@ -317,6 +317,7 @@
<Compile Include="Download\Clients\UsenetBlackhole\UsenetBlackhole.cs" />
<Compile Include="Download\Clients\UsenetBlackhole\UsenetBlackholeSettings.cs" />
<Compile Include="Download\CompletedDownloadService.cs" />
<Compile Include="Download\TrackedDownloadStatusMessage.cs" />
<Compile Include="Download\UsenetClientBase.cs" />
<Compile Include="Download\DownloadClientBase.cs" />
<Compile Include="Download\DownloadClientDefinition.cs" />

@ -16,7 +16,8 @@ namespace NzbDrone.Core.Parser
{
LocalEpisode GetLocalEpisode(string filename, Series series, bool sceneSource);
Series GetSeries(string title);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 seriesId, IEnumerable<Int32> episodeIds);
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvRageId, SearchCriteriaBase searchCriteria = null);
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, Series series);
@ -99,7 +100,7 @@ namespace NzbDrone.Core.Parser
return series;
}
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvRageId, SearchCriteriaBase searchCriteria = null)
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId, SearchCriteriaBase searchCriteria = null)
{
var remoteEpisode = new RemoteEpisode
{
@ -120,6 +121,18 @@ namespace NzbDrone.Core.Parser
return remoteEpisode;
}
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 seriesId, IEnumerable<Int32> episodeIds)
{
var remoteEpisode = new RemoteEpisode
{
ParsedEpisodeInfo = parsedEpisodeInfo,
Series = _seriesService.GetSeries(seriesId),
Episodes = _episodeService.GetEpisodes(episodeIds)
};
return remoteEpisode;
}
public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null)
{
var result = new List<Episode>();

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Parser.Model;
@ -17,7 +19,8 @@ namespace NzbDrone.Core.Queue
public TimeSpan? Timeleft { get; set; }
public DateTime? EstimatedCompletionTime { get; set; }
public String Status { get; set; }
public String ErrorMessage { get; set; }
public String TrackedDownloadStatus { get; set; }
public List<TrackedDownloadStatusMessage> StatusMessages { get; set; }
public RemoteEpisode RemoteEpisode { get; set; }
}
}

@ -28,33 +28,30 @@ namespace NzbDrone.Core.Queue
return MapQueue(queueItems);
}
private List<Queue> MapQueue(IEnumerable<TrackedDownload> queueItems)
private List<Queue> MapQueue(IEnumerable<TrackedDownload> trackedDownloads)
{
var queued = new List<Queue>();
foreach (var queueItem in queueItems)
foreach (var trackedDownload in trackedDownloads)
{
foreach (var episode in queueItem.DownloadItem.RemoteEpisode.Episodes)
foreach (var episode in trackedDownload.RemoteEpisode.Episodes)
{
var queue = new Queue
{
Id = episode.Id ^ (queueItem.DownloadItem.DownloadClientId.GetHashCode().GetHashCode() << 16),
Series = queueItem.DownloadItem.RemoteEpisode.Series,
Id = episode.Id ^ (trackedDownload.DownloadItem.DownloadClientId.GetHashCode().GetHashCode() << 16),
Series = trackedDownload.RemoteEpisode.Series,
Episode = episode,
Quality = queueItem.DownloadItem.RemoteEpisode.ParsedEpisodeInfo.Quality,
Title = queueItem.DownloadItem.Title,
Size = queueItem.DownloadItem.TotalSize,
Sizeleft = queueItem.DownloadItem.RemainingSize,
Timeleft = queueItem.DownloadItem.RemainingTime,
Status = queueItem.DownloadItem.Status.ToString(),
RemoteEpisode = queueItem.DownloadItem.RemoteEpisode
Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
Title = trackedDownload.DownloadItem.Title,
Size = trackedDownload.DownloadItem.TotalSize,
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
Timeleft = trackedDownload.DownloadItem.RemainingTime,
Status = trackedDownload.DownloadItem.Status.ToString(),
RemoteEpisode = trackedDownload.RemoteEpisode,
TrackedDownloadStatus = trackedDownload.Status.ToString(),
StatusMessages = trackedDownload.StatusMessages
};
if (queueItem.HasError)
{
queue.ErrorMessage = queueItem.StatusMessage;
}
if (queue.Timeleft.HasValue)
{
queue.EstimatedCompletionTime = DateTime.UtcNow.Add(queue.Timeleft.Value);

@ -14,6 +14,7 @@ namespace NzbDrone.Core.Tv
public interface IEpisodeService
{
Episode GetEpisode(int id);
List<Episode> GetEpisodes(IEnumerable<Int32> ids);
Episode FindEpisode(int seriesId, int seasonNumber, int episodeNumber);
Episode FindEpisode(int seriesId, int absoluteEpisodeNumber);
Episode FindEpisodeByName(int seriesId, int seasonNumber, string episodeTitle);
@ -37,9 +38,9 @@ namespace NzbDrone.Core.Tv
}
public class EpisodeService : IEpisodeService,
IHandle<EpisodeFileDeletedEvent>,
IHandle<EpisodeFileAddedEvent>,
IHandleAsync<SeriesDeletedEvent>
IHandle<EpisodeFileDeletedEvent>,
IHandle<EpisodeFileAddedEvent>,
IHandleAsync<SeriesDeletedEvent>
{
private readonly IEpisodeRepository _episodeRepository;
private readonly IConfigService _configService;
@ -57,6 +58,11 @@ namespace NzbDrone.Core.Tv
return _episodeRepository.Get(id);
}
public List<Episode> GetEpisodes(IEnumerable<int> ids)
{
return _episodeRepository.Get(ids).ToList();
}
public Episode FindEpisode(int seriesId, int seasonNumber, int episodeNumber)
{
return _episodeRepository.Find(seriesId, seasonNumber, episodeNumber);

@ -204,3 +204,7 @@
.icon-nd-deleted:before {
.icon(@trash);
}
.icon-nd-warning:before {
color: @brand-warning;
}

@ -2,20 +2,26 @@
define(
[
'marionette',
'Cells/NzbDroneCell'
], function (NzbDroneCell) {
], function (Marionette, NzbDroneCell) {
return NzbDroneCell.extend({
className: 'queue-status-cell',
className : 'queue-status-cell',
template : 'History/Queue/QueueStatusCellTemplate',
render: function () {
this.$el.empty();
if (this.cellValue) {
var status = this.cellValue.get('status').toLowerCase();
var errorMessage = (this.cellValue.get('errorMessage') || '');
var trackedDownloadStatus = this.cellValue.get('trackedDownloadStatus').toLowerCase();
var hasError = this.cellValue.get('hasError') || false;
var hasWarning = this.cellValue.get('hasWarning') || false;
var icon = 'icon-nd-downloading';
var title = 'Downloading';
var itemTitle = this.cellValue.get('title');
var content = itemTitle;
if (status === 'paused') {
icon = 'icon-pause';
@ -39,7 +45,7 @@ define(
if (status === 'failed') {
icon = 'icon-nd-download-failed';
title = 'Download failed: check download client for more details';
title = 'Download failed';
}
if (status === 'warning') {
@ -47,29 +53,37 @@ define(
title = 'Download warning: check download client for more details';
}
if (errorMessage !== '') {
if (trackedDownloadStatus === 'warning') {
icon += ' icon-nd-warning';
// title = 'Download failed';
this.templateFunction = Marionette.TemplateCache.get(this.template);
content = this.templateFunction(this.cellValue.toJSON());
}
if (trackedDownloadStatus === 'error') {
if (status === 'completed') {
icon = 'icon-nd-import-failed';
title = 'Import failed';
title = 'Import failed: ' + itemTitle;
}
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));
this.templateFunction = Marionette.TemplateCache.get(this.template);
content = this.templateFunction(this.cellValue.toJSON());
}
this.$el.html('<i class="{0}"></i>'.format(icon));
this.$el.popover({
content : content,
html : true,
trigger : 'hover',
title : title,
placement: 'right',
container: this.$el
});
}
return this;

@ -0,0 +1,8 @@
{{#each statusMessages}}
{{title}}
<ul>
{{#each messages}}
<li>{{this}}</li>
{{/each}}
</ul>
{{/each}}
Loading…
Cancel
Save