Merge branch 'downloadclient-import' of https://github.com/Taloth/NzbDrone into download-client-import

pull/4/head
Mark McDowall 10 years ago
commit 1446e1e0e7

@ -9,9 +9,12 @@ namespace NzbDrone.Api.Config
public String DownloadClientWorkingFolders { get; set; }
public Int32 DownloadedEpisodesScanInterval { get; set; }
public Boolean EnableCompletedDownloadHandling { get; set; }
public Boolean RemoveCompletedDownloads { get; set; }
public Boolean EnableFailedDownloadHandling { get; set; }
public Boolean AutoRedownloadFailed { get; set; }
public Boolean RemoveFailedDownloads { get; set; }
public Boolean EnableFailedDownloadHandling { get; set; }
public Int32 BlacklistGracePeriod { get; set; }
public Int32 BlacklistRetryInterval { get; set; }
public Int32 BlacklistRetryLimit { get; set; }

@ -1,10 +1,11 @@
using System;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.DownloadClient
{
public class DownloadClientResource : ProviderResource
{
public Boolean Enable { get; set; }
public Int32 Protocol { get; set; }
public DownloadProtocol Protocol { get; set; }
}
}

@ -7,28 +7,28 @@ namespace NzbDrone.Api.DownloadClient
{
public class DownloadClientSchemaModule : NzbDroneRestModule<DownloadClientResource>
{
private readonly IDownloadClientFactory _notificationFactory;
private readonly IDownloadClientFactory _downloadClientFactory;
public DownloadClientSchemaModule(IDownloadClientFactory notificationFactory)
public DownloadClientSchemaModule(IDownloadClientFactory downloadClientFactory)
: base("downloadclient/schema")
{
_notificationFactory = notificationFactory;
_downloadClientFactory = downloadClientFactory;
GetResourceAll = GetSchema;
}
private List<DownloadClientResource> GetSchema()
{
var notifications = _notificationFactory.Templates();
var downloadClients = _downloadClientFactory.Templates();
var result = new List<DownloadClientResource>(notifications.Count);
var result = new List<DownloadClientResource>(downloadClients.Count);
foreach (var notification in notifications)
foreach (var downloadClient in downloadClients)
{
var notificationResource = new DownloadClientResource();
notificationResource.InjectFrom(notification);
notificationResource.Fields = SchemaBuilder.ToSchema(notification.Settings);
var downloadClientResource = new DownloadClientResource();
downloadClientResource.InjectFrom(downloadClient);
downloadClientResource.Fields = SchemaBuilder.ToSchema(downloadClient.Settings);
result.Add(notificationResource);
result.Add(downloadClientResource);
}
return result;

@ -8,5 +8,6 @@ namespace NzbDrone.Api.Health
{
public HealthCheckResult Type { get; set; }
public String Message { get; set; }
public Uri WikiUrl { get; set; }
}
}

@ -1,9 +1,11 @@
using System;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Indexers
{
public class IndexerResource : ProviderResource
{
public Boolean Enable { get; set; }
public DownloadProtocol Protocol { get; set; }
}
}

@ -19,7 +19,7 @@ namespace NzbDrone.Api.Indexers
private List<IndexerResource> GetSchema()
{
var indexers = _indexerFactory.Templates().Where(c => c.Implementation =="Newznab");
var indexers = _indexerFactory.Templates();
var result = new List<IndexerResource>(indexers.Count());

@ -3,6 +3,7 @@ using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Indexers
{
@ -30,5 +31,6 @@ namespace NzbDrone.Api.Indexers
public String DownloadUrl { get; set; }
public String InfoUrl { get; set; }
public Boolean DownloadAllowed { get; set; }
public DownloadProtocol DownloadProtocol { get; set; }
}
}

@ -30,10 +30,11 @@ namespace NzbDrone.Api
DeleteResource = DeleteProvider;
SharedValidator.RuleFor(c => c.Name).NotEmpty();
SharedValidator.RuleFor(c => c.Name).Must((v,c) => !_providerFactory.All().Any(p => p.Name == c && p.Id != v.Id)).WithMessage("Should be unique");
SharedValidator.RuleFor(c => c.Implementation).NotEmpty();
SharedValidator.RuleFor(c => c.ConfigContract).NotEmpty();
PostValidator.RuleFor(c => c.Fields).NotEmpty();
PostValidator.RuleFor(c => c.Fields).NotNull();
}
private TProviderResource GetProviderById(int id)

@ -2,18 +2,20 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Api.Series;
using NzbDrone.Api.Episodes;
namespace NzbDrone.Api.Queue
{
public class QueueResource : RestResource
{
public Core.Tv.Series Series { get; set; }
public Episode Episode { get; set; }
public SeriesResource Series { get; set; }
public EpisodeResource Episode { get; set; }
public QualityModel Quality { get; set; }
public Decimal Size { get; set; }
public String Title { get; set; }
public Decimal Sizeleft { get; set; }
public TimeSpan Timeleft { get; set; }
public TimeSpan? Timeleft { get; set; }
public String Status { get; set; }
}
}

@ -137,7 +137,7 @@ namespace NzbDrone.Common.Test.DiskProviderTests
}
[Test]
public void move_read_only_file()
public void should_be_able_to_move_read_only_file()
{
var source = GetTempFilePath();
var destination = GetTempFilePath();
@ -151,6 +151,23 @@ namespace NzbDrone.Common.Test.DiskProviderTests
Subject.MoveFile(source, destination);
}
[Test]
public void should_be_able_to_delete_directory_with_read_only_file()
{
var sourceDir = GetTempFilePath();
var source = Path.Combine(sourceDir, "test.txt");
Directory.CreateDirectory(sourceDir);
Subject.WriteAllText(source, "SourceFile");
File.SetAttributes(source, FileAttributes.ReadOnly);
Subject.DeleteFolder(sourceDir, true);
Directory.Exists(sourceDir).Should().BeFalse();
}
[Test]
public void empty_folder_should_return_folder_modified_date()
{

@ -5,32 +5,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Common.Test.DiskProviderTests
{
public class IsParentFixture : TestBase
public class IsParentPathFixture : TestBase
{
private string _parent = @"C:\Test".AsOsAgnostic();
[Test]
public void should_return_false_when_not_a_child()
{
var path = @"C:\Another Folder".AsOsAgnostic();
DiskProviderBase.IsParent(_parent, path).Should().BeFalse();
}
[Test]
public void should_return_true_when_folder_is_parent_of_another_folder()
{
var path = @"C:\Test\TV".AsOsAgnostic();
DiskProviderBase.IsParent(_parent, path).Should().BeTrue();
}
[Test]
public void should_return_true_when_folder_is_parent_of_a_file()
{
var path = @"C:\Test\30.Rock.S01E01.Pilot.avi".AsOsAgnostic();
DiskProviderBase.IsParent(_parent, path).Should().BeTrue();
}
}
}

@ -85,6 +85,57 @@ namespace NzbDrone.Common.Test
{
first.AsOsAgnostic().PathEquals(second.AsOsAgnostic()).Should().BeFalse();
}
private string _parent = @"C:\Test".AsOsAgnostic();
[Test]
public void should_return_false_when_not_a_child()
{
var path = @"C:\Another Folder".AsOsAgnostic();
_parent.IsParentPath(path).Should().BeFalse();
}
[Test]
public void should_return_true_when_folder_is_parent_of_another_folder()
{
var path = @"C:\Test\TV".AsOsAgnostic();
_parent.IsParentPath(path).Should().BeTrue();
}
[Test]
public void should_return_true_when_folder_is_parent_of_a_file()
{
var path = @"C:\Test\30.Rock.S01E01.Pilot.avi".AsOsAgnostic();
_parent.IsParentPath(path).Should().BeTrue();
}
[TestCase(@"C:\Test\", @"C:\Test\mydir")]
[TestCase(@"C:\Test\", @"C:\Test\mydir\")]
[TestCase(@"C:\Test", @"C:\Test\30.Rock.S01E01.Pilot.avi")]
public void path_should_be_parent(string parentPath, string childPath)
{
parentPath.AsOsAgnostic().IsParentPath(childPath.AsOsAgnostic()).Should().BeTrue();
}
[TestCase(@"C:\Test2\", @"C:\Test")]
[TestCase(@"C:\Test\Test\", @"C:\Test\")]
[TestCase(@"C:\Test\", @"C:\Test")]
[TestCase(@"C:\Test\", @"C:\Test\")]
public void path_should_not_be_parent(string parentPath, string childPath)
{
parentPath.AsOsAgnostic().IsParentPath(childPath.AsOsAgnostic()).Should().BeFalse();
}
[TestCase(@"C:\test\", @"C:\Test\mydir")]
[TestCase(@"C:\test", @"C:\Test\mydir\")]
public void path_should_be_parent_on_windows_only(string parentPath, string childPath)
{
var expectedResult = OsInfo.IsWindows;
parentPath.IsParentPath(childPath).Should().Be(expectedResult);
}
[Test]
public void normalize_path_exception_empty()

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Common
{
public static class ConvertBase32
{
private static string ValidChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
public static byte[] FromBase32String(string str)
{
int numBytes = str.Length * 5 / 8;
byte[] bytes = new Byte[numBytes];
// all UPPERCASE chars
str = str.ToUpper();
int bitBuffer = 0;
int bitBufferCount = 0;
int index = 0;
for (int i = 0; i < str.Length;i++ )
{
bitBuffer = (bitBuffer << 5) | ValidChars.IndexOf(str[i]);
bitBufferCount += 5;
if (bitBufferCount >= 8)
{
bitBufferCount -= 8;
bytes[index++] = (byte)(bitBuffer >> bitBufferCount);
}
}
return bytes;
}
}
}

@ -25,45 +25,17 @@ namespace NzbDrone.Common.Disk
public abstract void SetPermissions(string path, string mask, string user, string group);
public abstract long? GetTotalSize(string path);
public static string GetRelativePath(string parentPath, string childPath)
{
if (!IsParent(parentPath, childPath))
{
throw new NotParentException("{0} is not a child of {1}", childPath, parentPath);
}
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
}
public static bool IsParent(string parentPath, string childPath)
public DateTime FolderGetCreationTimeUtc(string path)
{
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar);
var parent = new DirectoryInfo(parentPath);
var child = new DirectoryInfo(childPath);
while (child.Parent != null)
{
if (child.Parent.FullName == parent.FullName)
{
return true;
}
child = child.Parent;
}
CheckFolderExists(path);
return false;
return new DirectoryInfo(path).CreationTimeUtc;
}
public DateTime FolderGetLastWrite(string path)
{
Ensure.That(path, () => path).IsValidPath();
if (!FolderExists(path))
{
throw new DirectoryNotFoundException("Directory doesn't exist. " + path);
}
CheckFolderExists(path);
var dirFiles = GetFiles(path, SearchOption.AllDirectories).ToList();
@ -76,21 +48,38 @@ namespace NzbDrone.Common.Disk
.Max(c => c.LastWriteTimeUtc);
}
public DateTime FileGetCreationTimeUtc(string path)
{
CheckFileExists(path);
return new FileInfo(path).CreationTimeUtc;
}
public DateTime FileGetLastWrite(string path)
{
PathEnsureFileExists(path);
CheckFileExists(path);
return new FileInfo(path).LastWriteTime;
}
public DateTime FileGetLastWriteUtc(string path)
{
PathEnsureFileExists(path);
CheckFileExists(path);
return new FileInfo(path).LastWriteTimeUtc;
}
private void PathEnsureFileExists(string path)
private void CheckFolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
if (!FolderExists(path))
{
throw new DirectoryNotFoundException("Directory doesn't exist. " + path);
}
}
private void CheckFileExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
@ -286,6 +275,9 @@ namespace NzbDrone.Common.Disk
{
Ensure.That(path, () => path).IsValidPath();
var files = Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
Array.ForEach(files, RemoveReadOnly);
Directory.Delete(path, recursive);
}

@ -11,7 +11,9 @@ namespace NzbDrone.Common.Disk
void InheritFolderPermissions(string filename);
void SetPermissions(string path, string mask, string user, string group);
long? GetTotalSize(string path);
DateTime FolderGetCreationTimeUtc(string path);
DateTime FolderGetLastWrite(string path);
DateTime FileGetCreationTimeUtc(string path);
DateTime FileGetLastWrite(string path);
DateTime FileGetLastWriteUtc(string path);
void EnsureFolder(string path);

@ -24,11 +24,14 @@ namespace NzbDrone.Common.EnvironmentInfo
if (!IsMono)
{
Os = Os.Windows;
}
PathStringComparison = StringComparison.OrdinalIgnoreCase;
}
else
{
Os = IsOsx ? Os.Osx : Os.Linux;
PathStringComparison = StringComparison.Ordinal;
}
}
@ -40,6 +43,7 @@ namespace NzbDrone.Common.EnvironmentInfo
public static bool IsWindows { get; private set; }
public static Os Os { get; private set; }
public static DayOfWeek FirstDayOfWeek { get; private set; }
public static StringComparison PathStringComparison { get; private set; }
//Borrowed from: https://github.com/jpobst/Pinta/blob/master/Pinta.Core/Managers/SystemManager.cs
//From Managed.Windows.Forms/XplatUI

@ -84,6 +84,7 @@ namespace NzbDrone.Common.Http
public Stream DownloadStream(string url, NetworkCredential credential = null)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
request.UserAgent = _userAgent;
request.Timeout = 20 * 1000;

@ -60,6 +60,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ArchiveProvider.cs" />
<Compile Include="ConvertBase32.cs" />
<Compile Include="Cache\Cached.cs" />
<Compile Include="Cache\CacheManager.cs" />
<Compile Include="Cache\ICached.cs" />

@ -39,14 +39,39 @@ namespace NzbDrone.Common
public static bool PathEquals(this string firstPath, string secondPath)
{
if (OsInfo.IsMono)
if (firstPath.Equals(secondPath, OsInfo.PathStringComparison)) return true;
return String.Equals(firstPath.CleanFilePath(), secondPath.CleanFilePath(), OsInfo.PathStringComparison);
}
public static string GetRelativePath(this string parentPath, string childPath)
{
if (!parentPath.IsParentPath(childPath))
{
throw new NzbDrone.Common.Exceptions.NotParentException("{0} is not a child of {1}", childPath, parentPath);
}
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
}
public static bool IsParentPath(this string parentPath, string childPath)
{
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar);
var parent = new DirectoryInfo(parentPath);
var child = new DirectoryInfo(childPath);
while (child.Parent != null)
{
if (firstPath.Equals(secondPath)) return true;
return String.Equals(firstPath.CleanFilePath(), secondPath.CleanFilePath());
if (child.Parent.FullName.Equals(parent.FullName, OsInfo.PathStringComparison))
{
return true;
}
child = child.Parent;
}
if (firstPath.Equals(secondPath, StringComparison.OrdinalIgnoreCase)) return true;
return String.Equals(firstPath.CleanFilePath(), secondPath.CleanFilePath(), StringComparison.OrdinalIgnoreCase);
return false;
}
private static readonly Regex WindowsPathWithDriveRegex = new Regex(@"^[a-zA-Z]:\\", RegexOptions.Compiled);

@ -14,6 +14,8 @@ namespace NzbDrone.Core.Test.Configuration
public void SetUp()
{
Mocker.SetConstant<IConfigRepository>(Mocker.Resolve<ConfigRepository>());
Db.All<Config>().ForEach(Db.Delete);
}
[Test]

@ -67,7 +67,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(It.IsAny<QualityProfile>(), 3)).Returns<QualityModel>(null);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClient()).Returns(Mocker.GetMock<IDownloadClient>().Object);
.Setup(c => c.GetDownloadClients())
.Returns(new IDownloadClient[] { Mocker.GetMock<IDownloadClient>().Object });
}
private void WithFirstReportUpgradable()
@ -83,7 +84,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenSabnzbdDownloadClient()
{
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClient()).Returns(Mocker.Resolve<Sabnzbd>());
.Setup(c => c.GetDownloadClients())
.Returns(new IDownloadClient[] { Mocker.Resolve<Sabnzbd>() });
}
private void GivenMostRecentForEpisode(HistoryEventType eventType)

@ -9,6 +9,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Queue;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
@ -18,7 +19,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private Series _series;
private Episode _episode;
private RemoteEpisode _remoteEpisode;
private Mock<IDownloadClient> _downloadClient;
private Series _otherSeries;
private Episode _otherEpisode;
@ -50,34 +50,30 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD)})
.Build();
_downloadClient = Mocker.GetMock<IDownloadClient>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClient())
.Returns(_downloadClient.Object);
}
private void GivenEmptyQueue()
{
_downloadClient.Setup(s => s.GetQueue())
.Returns(new List<QueueItem>());
Mocker.GetMock<IQueueService>()
.Setup(s => s.GetQueue())
.Returns(new List<Queue.Queue>());
}
private void GivenQueue(IEnumerable<RemoteEpisode> remoteEpisodes)
{
var queue = new List<QueueItem>();
var queue = new List<Queue.Queue>();
foreach (var remoteEpisode in remoteEpisodes)
{
queue.Add(new QueueItem
{
RemoteEpisode = remoteEpisode
queue.Add(new Queue.Queue
{
RemoteEpisode = remoteEpisode
});
}
_downloadClient.Setup(s => s.GetQueue())
.Returns(queue);
Mocker.GetMock<IQueueService>()
.Setup(s => s.GetQueue())
.Returns(queue);
}
[Test]

@ -0,0 +1,439 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Test.Common;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Test.Download
{
[TestFixture]
public class CompletedDownloadServiceFixture : CoreTest<DownloadTrackingService>
{
private List<DownloadClientItem> _completed;
[SetUp]
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();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClients())
.Returns( new IDownloadClient[] { Mocker.GetMock<IDownloadClient>().Object });
Mocker.GetMock<IDownloadClient>()
.SetupGet(c => c.Definition)
.Returns(new Core.Download.DownloadClientDefinition { Id = 1, Name = "testClient" });
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(true);
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.RemoveCompletedDownloads)
.Returns(true);
Mocker.GetMock<IHistoryService>()
.Setup(s => s.Failed())
.Returns(new List<History.History>());
Mocker.SetConstant<ICompletedDownloadService>(Mocker.Resolve<CompletedDownloadService>());
}
private void GivenNoGrabbedHistory()
{
Mocker.GetMock<IHistoryService>()
.Setup(s => s.Grabbed())
.Returns(new List<History.History>());
}
private void GivenGrabbedHistory(List<History.History> history)
{
Mocker.GetMock<IHistoryService>()
.Setup(s => s.Grabbed())
.Returns(history);
}
private void GivenNoImportedHistory()
{
Mocker.GetMock<IHistoryService>()
.Setup(s => s.Imported())
.Returns(new List<History.History>());
}
private void GivenImportedHistory(List<History.History> importedHistory)
{
Mocker.GetMock<IHistoryService>()
.Setup(s => s.Imported())
.Returns(importedHistory);
}
private void GivenCompletedDownloadClientHistory(bool hasStorage = true)
{
Mocker.GetMock<IDownloadClient>()
.Setup(s => s.GetItems())
.Returns(_completed);
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(hasStorage);
}
private void GivenCompletedImport()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<Core.MediaFiles.EpisodeImport.ImportDecision>() { new Core.MediaFiles.EpisodeImport.ImportDecision(null) });
}
private void GivenFailedImport()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<Core.MediaFiles.EpisodeImport.ImportDecision>());
}
private void VerifyNoImports()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()), Times.Never());
}
private void VerifyImports()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Verify(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()), Times.Once());
}
[Test]
public void should_process_if_matching_history_is_not_found_but_category_specified()
{
_completed.First().Category = "tv";
GivenCompletedDownloadClientHistory();
GivenNoGrabbedHistory();
GivenNoImportedHistory();
GivenCompletedImport();
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyImports();
}
[Test]
public void should_not_process_if_matching_history_is_not_found_and_no_category_specified()
{
_completed.First().Category = null;
GivenCompletedDownloadClientHistory();
GivenNoGrabbedHistory();
GivenNoImportedHistory();
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports();
}
[Test]
public void should_not_process_if_grabbed_history_contains_null_downloadclient_id()
{
_completed.First().Category = null;
GivenCompletedDownloadClientHistory();
var historyGrabbed = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
historyGrabbed.First().Data.Add("downloadClientId", null);
GivenGrabbedHistory(historyGrabbed);
GivenNoImportedHistory();
GivenFailedImport();
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports();
}
[Test]
public void should_process_if_failed_history_contains_null_downloadclient_id()
{
GivenCompletedDownloadClientHistory();
var historyGrabbed = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
historyGrabbed.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
GivenGrabbedHistory(historyGrabbed);
var historyImported = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
historyImported.First().Data.Add("downloadClient", "SabnzbdClient");
historyImported.First().Data.Add("downloadClientId", null);
GivenImportedHistory(historyImported);
GivenCompletedImport();
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyImports();
}
[Test]
public void should_not_process_if_already_added_to_history_as_imported()
{
GivenCompletedDownloadClientHistory();
var history = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
GivenGrabbedHistory(history);
GivenImportedHistory(history);
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports();
}
[Test]
public void should_process_if_not_already_in_imported_history()
{
GivenCompletedDownloadClientHistory();
var history = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
GivenGrabbedHistory(history);
GivenNoImportedHistory();
GivenCompletedImport();
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyImports();
}
[Test]
public void should_not_process_if_storage_directory_does_not_exist()
{
GivenCompletedDownloadClientHistory(false);
var history = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
GivenGrabbedHistory(history);
GivenNoImportedHistory();
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports();
}
[Test]
public void should_not_process_if_storage_directory_in_drone_factory()
{
GivenCompletedDownloadClientHistory(true);
var history = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
GivenGrabbedHistory(history);
GivenNoImportedHistory();
Mocker.GetMock<IConfigService>()
.SetupGet(v => v.DownloadedEpisodesFolder)
.Returns(@"C:\DropFolder".AsOsAgnostic());
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports();
}
[Test]
public void should_process_as_already_imported_if_drone_factory_import_history_exists()
{
GivenCompletedDownloadClientHistory(false);
_completed.Clear();
_completed.AddRange(Builder<DownloadClientItem>.CreateListOfSize(2)
.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());
var grabbedHistory = Builder<History.History>.CreateListOfSize(2)
.All()
.With(d => d.Data["downloadClient"] = "SabnzbdClient")
.TheFirst(1)
.With(d => d.Data["downloadClientId"] = _completed.First().DownloadClientId)
.With(d => d.SourceTitle = "Droned.S01E01.720p-LAZY")
.TheLast(1)
.With(d => d.Data["downloadClientId"] = _completed.Last().DownloadClientId)
.With(d => d.SourceTitle = "Droned.S01E01.Proper.720p-LAZY")
.Build()
.ToList();
var importedHistory = Builder<History.History>.CreateListOfSize(2)
.All()
.With(d => d.EpisodeId = 1)
.TheFirst(1)
.With(d => d.Data["droppedPath"] = @"C:\mydownload\Droned.S01E01.720p-LAZY\lzy-dr101.mkv".AsOsAgnostic())
.TheLast(1)
.With(d => d.Data["droppedPath"] = @"C:\mydownload\Droned.S01E01.Proper.720p-LAZY\lzy-dr101.mkv".AsOsAgnostic())
.Build()
.ToList();
GivenGrabbedHistory(grabbedHistory);
GivenImportedHistory(importedHistory);
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoImports();
Mocker.GetMock<IHistoryService>()
.Verify(v => v.UpdateHistoryData(It.IsAny<int>(), It.IsAny<Dictionary<String, String>>()), Times.Exactly(2));
}
[Test]
public void should_not_remove_if_config_disabled()
{
GivenCompletedDownloadClientHistory();
var history = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
GivenGrabbedHistory(history);
GivenNoImportedHistory();
GivenCompletedImport();
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.RemoveCompletedDownloads)
.Returns(false);
Subject.Execute(new CheckForFinishedDownloadCommand());
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
}
[Test]
public void should_not_remove_while_readonly()
{
GivenCompletedDownloadClientHistory();
var history = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
GivenGrabbedHistory(history);
GivenNoImportedHistory();
GivenCompletedImport();
_completed.First().IsReadOnly = true;
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
Subject.Execute(new CheckForFinishedDownloadCommand());
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
}
[Test]
public void should_not_remove_if_imported_failed()
{
GivenCompletedDownloadClientHistory();
var history = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
GivenGrabbedHistory(history);
GivenNoImportedHistory();
GivenFailedImport();
_completed.First().IsReadOnly = true;
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
Subject.Execute(new CheckForFinishedDownloadCommand());
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
}
[Test]
public void should_remove_if_imported()
{
GivenCompletedDownloadClientHistory();
var history = Builder<History.History>.CreateListOfSize(1)
.Build()
.ToList();
GivenGrabbedHistory(history);
GivenNoImportedHistory();
GivenCompletedImport();
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
Subject.Execute(new CheckForFinishedDownloadCommand());
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Once());
}
}
}

@ -0,0 +1,118 @@
using System.IO;
using System.Net;
using System.Linq;
using Moq;
using NUnit.Framework;
using FluentAssertions;
using NzbDrone.Test.Common;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.Clients.UsenetBlackhole;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
{
[TestFixture]
public class UsenetBlackholeFixture : DownloadClientFixtureBase<UsenetBlackhole>
{
protected string _completedDownloadFolder;
protected string _blackholeFolder;
protected string _filePath;
[SetUp]
public void Setup()
{
_completedDownloadFolder = @"c:\blackhole\completed".AsOsAgnostic();
_blackholeFolder = @"c:\blackhole\nzb".AsOsAgnostic();
_filePath = (@"c:\blackhole\nzb\" + _title + ".nzb").AsOsAgnostic();
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new UsenetBlackholeSettings
{
NzbFolder = _blackholeFolder,
WatchFolder = _completedDownloadFolder
};
}
protected void WithSuccessfulDownload()
{
}
protected void WithFailedDownload()
{
Mocker.GetMock<IHttpProvider>()
.Setup(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()))
.Throws(new WebException());
}
protected void GivenCompletedItem()
{
var targetDir = Path.Combine(_completedDownloadFolder, _title);
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.GetDirectories(_completedDownloadFolder))
.Returns(new[] { targetDir });
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.GetFiles(targetDir, SearchOption.AllDirectories))
.Returns(new[] { Path.Combine(_completedDownloadFolder, "somefile.mkv") });
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.GetFileSize(It.IsAny<string>()))
.Returns(1000000);
}
[Test]
public void completed_download_should_have_required_properties()
{
GivenCompletedItem();
var result = Subject.GetItems().Single();
VerifyCompleted(result);
}
[Test]
public void Download_should_download_file_if_it_doesnt_exist()
{
var remoteEpisode = CreateRemoteEpisode();
Subject.Download(remoteEpisode);
Mocker.GetMock<IHttpProvider>().Verify(c => c.DownloadFile(_downloadUrl, _filePath), Times.Once());
}
[Test]
public void Download_should_replace_illegal_characters_in_title()
{
var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
var expectedFilename = Path.Combine(_blackholeFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV]" + Path.GetExtension(_filePath));
var remoteEpisode = CreateRemoteEpisode();
remoteEpisode.Release.Title = illegalTitle;
Subject.Download(remoteEpisode);
Mocker.GetMock<IHttpProvider>().Verify(c => c.DownloadFile(It.IsAny<string>(), expectedFilename), Times.Once());
}
[Test]
public void GetItems_should_considered_locked_files_downloading()
{
GivenCompletedItem();
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.IsFileLocked(It.IsAny<string>()))
.Returns(true);
var result = Subject.GetItems().Single();
result.Status.Should().Be(DownloadItemStatus.Downloading);
}
}
}

@ -1,74 +0,0 @@
using System.IO;
using System.Net;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.Clients.Blackhole;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.DownloadClientTests
{
[TestFixture]
public class BlackholeProviderFixture : CoreTest<Blackhole>
{
private const string _nzbUrl = "http://www.nzbs.com/url";
private const string _title = "some_nzb_title";
private string _blackHoleFolder;
private string _nzbPath;
private RemoteEpisode _remoteEpisode;
[SetUp]
public void Setup()
{
_blackHoleFolder = @"c:\nzb\blackhole\".AsOsAgnostic();
_nzbPath = @"c:\nzb\blackhole\some_nzb_title.nzb".AsOsAgnostic();
_remoteEpisode = new RemoteEpisode();
_remoteEpisode.Release = new ReleaseInfo();
_remoteEpisode.Release.Title = _title;
_remoteEpisode.Release.DownloadUrl = _nzbUrl;
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new FolderSettings
{
Folder = _blackHoleFolder
};
}
private void WithExistingFile()
{
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(_nzbPath)).Returns(true);
}
private void WithFailedDownload()
{
Mocker.GetMock<IHttpProvider>().Setup(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>())).Throws(new WebException());
}
[Test]
public void DownloadNzb_should_download_file_if_it_doesnt_exist()
{
Subject.DownloadNzb(_remoteEpisode);
Mocker.GetMock<IHttpProvider>().Verify(c => c.DownloadFile(_nzbUrl, _nzbPath), Times.Once());
}
[Test]
public void should_replace_illegal_characters_in_title()
{
var illegalTitle = "Saturday Night Live - S38E08 - Jeremy Renner/Maroon 5 [SDTV]";
var expectedFilename = Path.Combine(_blackHoleFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb");
_remoteEpisode.Release.Title = illegalTitle;
Subject.DownloadNzb(_remoteEpisode);
Mocker.GetMock<IHttpProvider>().Verify(c => c.DownloadFile(It.IsAny<string>(), expectedFilename), Times.Once());
}
}
}

@ -0,0 +1,111 @@
using System;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using FluentAssertions;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Test.Download.DownloadClientTests
{
public abstract class DownloadClientFixtureBase<TSubject> : CoreTest<TSubject>
where TSubject : class, IDownloadClient
{
protected readonly string _title = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE";
protected readonly string _downloadUrl = "http://somewhere.com/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.ext";
[SetUp]
public void SetupBase()
{
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.DownloadClientHistoryLimit)
.Returns(30);
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), null))
.Returns(CreateRemoteEpisode());
}
protected virtual RemoteEpisode CreateRemoteEpisode()
{
var remoteEpisode = new RemoteEpisode();
remoteEpisode.Release = new ReleaseInfo();
remoteEpisode.Release.Title = _title;
remoteEpisode.Release.DownloadUrl = _downloadUrl;
remoteEpisode.Release.DownloadProtocol = Subject.Protocol;
remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo();
remoteEpisode.ParsedEpisodeInfo.FullSeason = false;
remoteEpisode.Episodes = new List<Episode>();
remoteEpisode.Series = new Series();
return remoteEpisode;
}
protected void VerifyIdentifiable(DownloadClientItem downloadClientItem)
{
downloadClientItem.DownloadClient.Should().Be(Subject.Definition.Name);
downloadClientItem.DownloadClientId.Should().NotBeNullOrEmpty();
downloadClientItem.Title.Should().NotBeNullOrEmpty();
downloadClientItem.RemoteEpisode.Should().NotBeNull();
}
protected void VerifyQueued(DownloadClientItem downloadClientItem)
{
VerifyIdentifiable(downloadClientItem);
downloadClientItem.RemainingSize.Should().NotBe(0);
//downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero);
//downloadClientItem.OutputPath.Should().NotBeNullOrEmpty();
downloadClientItem.Status.Should().Be(DownloadItemStatus.Queued);
}
protected void VerifyPaused(DownloadClientItem downloadClientItem)
{
VerifyIdentifiable(downloadClientItem);
downloadClientItem.RemainingSize.Should().NotBe(0);
//downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero);
//downloadClientItem.OutputPath.Should().NotBeNullOrEmpty();
downloadClientItem.Status.Should().Be(DownloadItemStatus.Paused);
}
protected void VerifyDownloading(DownloadClientItem downloadClientItem)
{
VerifyIdentifiable(downloadClientItem);
downloadClientItem.RemainingSize.Should().NotBe(0);
//downloadClientItem.RemainingTime.Should().NotBe(TimeSpan.Zero);
//downloadClientItem.OutputPath.Should().NotBeNullOrEmpty();
downloadClientItem.Status.Should().Be(DownloadItemStatus.Downloading);
}
protected void VerifyCompleted(DownloadClientItem downloadClientItem)
{
VerifyIdentifiable(downloadClientItem);
downloadClientItem.Title.Should().NotBeNullOrEmpty();
downloadClientItem.RemainingSize.Should().Be(0);
downloadClientItem.RemainingTime.Should().Be(TimeSpan.Zero);
//downloadClientItem.OutputPath.Should().NotBeNullOrEmpty();
downloadClientItem.Status.Should().Be(DownloadItemStatus.Completed);
}
protected void VerifyFailed(DownloadClientItem downloadClientItem)
{
VerifyIdentifiable(downloadClientItem);
downloadClientItem.Status.Should().Be(DownloadItemStatus.Failed);
}
}
}

@ -1,61 +0,0 @@
using System;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
public class DownloadNzbFixture : CoreTest<Nzbget>
{
private const string _url = "http://www.nzbdrone.com";
private const string _title = "30.Rock.S01E01.Pilot.720p.hdtv";
private RemoteEpisode _remoteEpisode;
[SetUp]
public void Setup()
{
_remoteEpisode = new RemoteEpisode();
_remoteEpisode.Release = new ReleaseInfo();
_remoteEpisode.Release.Title = _title;
_remoteEpisode.Release.DownloadUrl = _url;
_remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
.All()
.With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
.Build()
.ToList();
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new NzbgetSettings
{
Host = "localhost",
Port = 6789,
Username = "nzbget",
Password = "pass",
TvCategory = "tv",
RecentTvPriority = (int)NzbgetPriority.High
};
}
[Test]
public void should_add_item_to_queue()
{
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), It.IsAny<Int32>(), It.IsAny<NzbgetSettings>()))
.Returns("id");
Subject.DownloadNzb(_remoteEpisode);
Mocker.GetMock<INzbgetProxy>()
.Verify(v => v.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), It.IsAny<Int32>(), It.IsAny<NzbgetSettings>()), Times.Once());
}
}
}

@ -0,0 +1,227 @@
using System;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
[TestFixture]
public class NzbgetFixture : DownloadClientFixtureBase<Nzbget>
{
private NzbgetQueueItem _queued;
private NzbgetHistoryItem _failed;
private NzbgetHistoryItem _completed;
[SetUp]
public void Setup()
{
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new NzbgetSettings
{
Host = "192.168.5.55",
Port = 2222,
Username = "admin",
Password = "pass",
TvCategory = "tv",
RecentTvPriority = (int)NzbgetPriority.High
};
_queued = new NzbgetQueueItem
{
FileSizeLo = 1000,
RemainingSizeLo = 10,
Category = "tv",
NzbName = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
Parameters = new List<NzbgetParameter> { new NzbgetParameter { Name = "drone", Value = "id" } }
};
_failed = new NzbgetHistoryItem
{
FileSizeLo = 1000,
Category = "tv",
Name = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
DestDir = "somedirectory",
Parameters = new List<NzbgetParameter> { new NzbgetParameter { Name = "drone", Value = "id" } },
ParStatus = "Some Error",
UnpackStatus = "NONE",
MoveStatus = "NONE",
ScriptStatus = "NONE",
DeleteStatus = "NONE",
MarkStatus = "NONE"
};
_completed = new NzbgetHistoryItem
{
FileSizeLo = 1000,
Category = "tv",
Name = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
DestDir = "somedirectory",
Parameters = new List<NzbgetParameter> { new NzbgetParameter { Name = "drone", Value = "id" } },
ParStatus = "SUCCESS",
UnpackStatus = "NONE",
MoveStatus = "SUCCESS",
ScriptStatus = "NONE",
DeleteStatus = "NONE",
MarkStatus = "NONE"
};
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetGlobalStatus(It.IsAny<NzbgetSettings>()))
.Returns(new NzbgetGlobalStatus
{
DownloadRate = 7000000
});
}
protected void WithFailedDownload()
{
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), It.IsAny<int>(), It.IsAny<NzbgetSettings>()))
.Returns((String)null);
}
protected void WithSuccessfulDownload()
{
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), It.IsAny<int>(), It.IsAny<NzbgetSettings>()))
.Returns(Guid.NewGuid().ToString().Replace("-", ""));
}
protected virtual void WithQueue(NzbgetQueueItem queue)
{
var list = new List<NzbgetQueueItem>();
if (queue != null)
{
list.Add(queue);
}
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetQueue(It.IsAny<NzbgetSettings>()))
.Returns(list);
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetPostQueue(It.IsAny<NzbgetSettings>()))
.Returns(new List<NzbgetPostQueueItem>());
}
protected virtual void WithHistory(NzbgetHistoryItem history)
{
var list = new List<NzbgetHistoryItem>();
if (history != null)
{
list.Add(history);
}
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetHistory(It.IsAny<NzbgetSettings>()))
.Returns(list);
}
[Test]
public void GetItems_should_return_no_items_when_queue_is_empty()
{
WithQueue(null);
WithHistory(null);
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void queued_item_should_have_required_properties()
{
_queued.ActiveDownloads = 0;
WithQueue(_queued);
WithHistory(null);
var result = Subject.GetItems().Single();
VerifyQueued(result);
}
[Test]
public void paused_item_should_have_required_properties()
{
_queued.PausedSizeLo = _queued.RemainingSizeLo;
WithQueue(_queued);
WithHistory(null);
var result = Subject.GetItems().Single();
VerifyPaused(result);
}
[Test]
public void downloading_item_should_have_required_properties()
{
_queued.ActiveDownloads = 1;
WithQueue(_queued);
WithHistory(null);
var result = Subject.GetItems().Single();
VerifyDownloading(result);
}
[Test]
public void completed_download_should_have_required_properties()
{
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
VerifyCompleted(result);
}
[Test]
public void failed_item_should_have_required_properties()
{
WithQueue(null);
WithHistory(_failed);
var result = Subject.GetItems().Single();
VerifyFailed(result);
}
[Test]
public void Download_should_return_unique_id()
{
WithSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
}
[Test]
public void GetItems_should_ignore_downloads_from_other_categories()
{
_completed.Category = "mycat";
WithQueue(null);
WithHistory(_completed);
var items = Subject.GetItems();
items.Should().BeEmpty();
}
}
}

@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
public class QueueFixture : CoreTest<Nzbget>
{
private List<NzbgetQueueItem> _queue;
[SetUp]
public void Setup()
{
_queue = Builder<NzbgetQueueItem>.CreateListOfSize(5)
.All()
.With(q => q.NzbName = "30.Rock.S01E01.Pilot.720p.hdtv.nzb")
.With(q => q.Parameters = new List<NzbgetParameter>
{
new NzbgetParameter { Name = "drone", Value = "id" }
})
.Build()
.ToList();
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new NzbgetSettings
{
Host = "localhost",
Port = 6789,
Username = "nzbget",
Password = "pass",
TvCategory = "tv",
RecentTvPriority = (int)NzbgetPriority.High
};
}
private void WithFullQueue()
{
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetQueue(It.IsAny<NzbgetSettings>()))
.Returns(_queue);
}
private void WithEmptyQueue()
{
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetQueue(It.IsAny<NzbgetSettings>()))
.Returns(new List<NzbgetQueueItem>());
}
[Test]
public void should_return_no_items_when_queue_is_empty()
{
WithEmptyQueue();
Subject.GetQueue()
.Should()
.BeEmpty();
}
[Test]
public void should_return_item_when_queue_has_item()
{
WithFullQueue();
Mocker.GetMock<IParsingService>()
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), 0, null))
.Returns(new RemoteEpisode {Series = new Series()});
Subject.GetQueue()
.Should()
.HaveCount(_queue.Count);
}
}
}

@ -45,9 +45,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
_remoteEpisode.ParsedEpisodeInfo.FullSeason = false;
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new FolderSettings
Subject.Definition.Settings = new PneumaticSettings
{
Folder = _pneumaticFolder
NzbFolder = _pneumaticFolder
};
}
@ -64,7 +64,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
[Test]
public void should_download_file_if_it_doesnt_exist()
{
Subject.DownloadNzb(_remoteEpisode);
Subject.Download(_remoteEpisode);
Mocker.GetMock<IHttpProvider>().Verify(c => c.DownloadFile(_nzbUrl, _nzbPath), Times.Once());
}
@ -75,7 +75,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
{
WithFailedDownload();
Assert.Throws<WebException>(() => Subject.DownloadNzb(_remoteEpisode));
Assert.Throws<WebException>(() => Subject.Download(_remoteEpisode));
}
[Test]
@ -84,7 +84,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
_remoteEpisode.Release.Title = "30 Rock - Season 1";
_remoteEpisode.ParsedEpisodeInfo.FullSeason = true;
Assert.Throws<NotImplementedException>(() => Subject.DownloadNzb(_remoteEpisode));
Assert.Throws<NotSupportedException>(() => Subject.Download(_remoteEpisode));
}
[Test]
public void should_throw_item_is_removed()
{
Assert.Throws<NotSupportedException>(() => Subject.RemoveItem(""));
}
[Test]
@ -94,7 +100,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
var expectedFilename = Path.Combine(_pneumaticFolder, "Saturday Night Live - S38E08 - Jeremy Renner+Maroon 5 [SDTV].nzb");
_remoteEpisode.Release.Title = illegalTitle;
Subject.DownloadNzb(_remoteEpisode);
Subject.Download(_remoteEpisode);
Mocker.GetMock<IHttpProvider>().Verify(c => c.DownloadFile(It.IsAny<string>(), expectedFilename), Times.Once());
}

@ -12,30 +12,20 @@ using NzbDrone.Core.Download.Clients.Sabnzbd.Responses;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{
[TestFixture]
public class SabnzbdFixture : CoreTest<Sabnzbd>
public class SabnzbdFixture : DownloadClientFixtureBase<Sabnzbd>
{
private const string URL = "http://www.nzbclub.com/nzb_download.aspx?mid=1950232";
private const string TITLE = "My Series Name - 5x2-5x3 - My title [Bluray720p] [Proper]";
private RemoteEpisode _remoteEpisode;
private SabnzbdQueue _queued;
private SabnzbdHistory _failed;
private SabnzbdHistory _completed;
[SetUp]
public void Setup()
{
_remoteEpisode = new RemoteEpisode();
_remoteEpisode.Release = new ReleaseInfo();
_remoteEpisode.Release.Title = TITLE;
_remoteEpisode.Release.DownloadUrl = URL;
_remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
.All()
.With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
.Build()
.ToList();
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new SabnzbdSettings
{
@ -47,16 +37,219 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
TvCategory = "tv",
RecentTvPriority = (int)SabnzbdPriority.High
};
_queued = new SabnzbdQueue
{
Paused = false,
Items = new List<SabnzbdQueueItem>()
{
new SabnzbdQueueItem
{
Status = SabnzbdDownloadStatus.Downloading,
Size = 1000,
Sizeleft = 10,
Timeleft = TimeSpan.FromSeconds(10),
Category = "tv",
Id = "sabnzbd_nzb12345",
Title = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE"
}
}
};
_failed = new SabnzbdHistory
{
Items = new List<SabnzbdHistoryItem>()
{
new SabnzbdHistoryItem
{
Status = SabnzbdDownloadStatus.Failed,
Size = 1000,
Category = "tv",
Id = "sabnzbd_nzb12345",
Title = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE"
}
}
};
_completed = new SabnzbdHistory
{
Items = new List<SabnzbdHistoryItem>()
{
new SabnzbdHistoryItem
{
Status = SabnzbdDownloadStatus.Completed,
Size = 1000,
Category = "tv",
Id = "sabnzbd_nzb12345",
Title = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
Storage = "somedirectory"
}
}
};
}
protected void WithFailedDownload()
{
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()))
.Returns((SabnzbdAddResponse)null);
}
protected void WithSuccessfulDownload()
{
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()))
.Returns(new SabnzbdAddResponse()
{
Status = true,
Ids = new List<string> { "sabznbd_nzo12345" }
});
}
protected virtual void WithQueue(SabnzbdQueue queue)
{
if (queue == null)
{
queue = new SabnzbdQueue() { Items = new List<SabnzbdQueueItem>() };
}
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.GetQueue(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()))
.Returns(queue);
}
protected virtual void WithHistory(SabnzbdHistory history)
{
if (history == null)
history = new SabnzbdHistory() { Items = new List<SabnzbdHistoryItem>() };
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.GetHistory(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()))
.Returns(history);
}
[Test]
public void GetItems_should_return_no_items_when_queue_is_empty()
{
WithQueue(null);
WithHistory(null);
Subject.GetItems().Should().BeEmpty();
}
[TestCase(SabnzbdDownloadStatus.Grabbing)]
[TestCase(SabnzbdDownloadStatus.Queued)]
public void queued_item_should_have_required_properties(SabnzbdDownloadStatus status)
{
_queued.Items.First().Status = status;
WithQueue(_queued);
WithHistory(null);
var result = Subject.GetItems().Single();
VerifyQueued(result);
result.RemainingTime.Should().NotBe(TimeSpan.Zero);
}
[TestCase(SabnzbdDownloadStatus.Paused)]
public void paused_item_should_have_required_properties(SabnzbdDownloadStatus status)
{
_queued.Items.First().Status = status;
WithQueue(_queued);
WithHistory(null);
var result = Subject.GetItems().Single();
VerifyPaused(result);
}
[TestCase(SabnzbdDownloadStatus.Checking)]
[TestCase(SabnzbdDownloadStatus.Downloading)]
[TestCase(SabnzbdDownloadStatus.QuickCheck)]
[TestCase(SabnzbdDownloadStatus.Verifying)]
[TestCase(SabnzbdDownloadStatus.Repairing)]
[TestCase(SabnzbdDownloadStatus.Fetching)]
[TestCase(SabnzbdDownloadStatus.Extracting)]
[TestCase(SabnzbdDownloadStatus.Moving)]
[TestCase(SabnzbdDownloadStatus.Running)]
public void downloading_item_should_have_required_properties(SabnzbdDownloadStatus status)
{
_queued.Items.First().Status = status;
WithQueue(_queued);
WithHistory(null);
var result = Subject.GetItems().Single();
VerifyDownloading(result);
result.RemainingTime.Should().NotBe(TimeSpan.Zero);
}
[Test]
public void completed_download_should_have_required_properties()
{
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
VerifyCompleted(result);
}
[Test]
public void failed_item_should_have_required_properties()
{
_completed.Items.First().Status = SabnzbdDownloadStatus.Failed;
WithQueue(null);
WithHistory(_completed);
var result = Subject.GetItems().Single();
VerifyFailed(result);
}
[Test]
public void Download_should_return_unique_id()
{
WithSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
var id = Subject.Download(remoteEpisode);
id.Should().NotBeNullOrEmpty();
}
[Test]
public void GetItems_should_ignore_downloads_from_other_categories()
{
_completed.Items.First().Category = "myowncat";
WithQueue(null);
WithHistory(_completed);
var items = Subject.GetItems();
items.Should().BeEmpty();
}
[Test]
public void downloadNzb_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
public void Download_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
{
Mocker.GetMock<ISabnzbdProxy>()
.Setup(s => s.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()))
.Returns(new SabnzbdAddResponse());
Subject.DownloadNzb(_remoteEpisode);
var remoteEpisode = CreateRemoteEpisode();
remoteEpisode.Episodes = Builder<Episode>.CreateListOfSize(1)
.All()
.With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT))
.Build()
.ToList();
Subject.Download(remoteEpisode);
Mocker.GetMock<ISabnzbdProxy>()
.Verify(v => v.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()), Times.Once());

@ -9,6 +9,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using System.Collections.Generic;
namespace NzbDrone.Core.Test.Download
{
@ -16,12 +17,19 @@ namespace NzbDrone.Core.Test.Download
public class DownloadServiceFixture : CoreTest<DownloadService>
{
private RemoteEpisode _parseResult;
private List<IDownloadClient> _downloadClients;
[SetUp]
public void Setup()
{
_downloadClients = new List<IDownloadClient>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(v => v.GetDownloadClients())
.Returns(_downloadClients);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClient()).Returns(Mocker.GetMock<IDownloadClient>().Object);
.Setup(v => v.GetDownloadClient(It.IsAny<Indexers.DownloadProtocol>()))
.Returns<Indexers.DownloadProtocol>(v => _downloadClients.FirstOrDefault(d => d.Protocol == v));
var episodes = Builder<Episode>.CreateListOfSize(2)
.TheFirst(1).With(s => s.Id = 12)
@ -29,31 +37,43 @@ namespace NzbDrone.Core.Test.Download
.All().With(s => s.SeriesId = 5)
.Build().ToList();
var releaseInfo = Builder<ReleaseInfo>.CreateNew()
.With(v => v.DownloadProtocol = Indexers.DownloadProtocol.Usenet)
.Build();
_parseResult = Builder<RemoteEpisode>.CreateNew()
.With(c => c.Series = Builder<Series>.CreateNew().Build())
.With(c => c.Release = Builder<ReleaseInfo>.CreateNew().Build())
.With(c => c.Release = releaseInfo)
.With(c => c.Episodes = episodes)
.Build();
}
private void WithSuccessfulAdd()
private Mock<IDownloadClient> WithUsenetClient()
{
Mocker.GetMock<IDownloadClient>()
.Setup(s => s.DownloadNzb(It.IsAny<RemoteEpisode>()));
var mock = new Mock<IDownloadClient>(Moq.MockBehavior.Default);
_downloadClients.Add(mock.Object);
mock.SetupGet(v => v.Protocol).Returns(Indexers.DownloadProtocol.Usenet);
return mock;
}
private void WithFailedAdd()
private Mock<IDownloadClient> WithTorrentClient()
{
Mocker.GetMock<IDownloadClient>()
.Setup(s => s.DownloadNzb(It.IsAny<RemoteEpisode>()))
.Throws(new WebException());
var mock = new Mock<IDownloadClient>(Moq.MockBehavior.Default);
_downloadClients.Add(mock.Object);
mock.SetupGet(v => v.Protocol).Returns(Indexers.DownloadProtocol.Torrent);
return mock;
}
[Test]
public void Download_report_should_publish_on_grab_event()
{
WithSuccessfulAdd();
var mock = WithUsenetClient();
mock.Setup(s => s.Download(It.IsAny<RemoteEpisode>()));
Subject.DownloadReport(_parseResult);
VerifyEventPublished<EpisodeGrabbedEvent>();
@ -62,18 +82,20 @@ namespace NzbDrone.Core.Test.Download
[Test]
public void Download_report_should_grab_using_client()
{
WithSuccessfulAdd();
var mock = WithUsenetClient();
mock.Setup(s => s.Download(It.IsAny<RemoteEpisode>()));
Subject.DownloadReport(_parseResult);
Mocker.GetMock<IDownloadClient>()
.Verify(s => s.DownloadNzb(It.IsAny<RemoteEpisode>()), Times.Once());
mock.Verify(s => s.Download(It.IsAny<RemoteEpisode>()), Times.Once());
}
[Test]
public void Download_report_should_not_publish_on_failed_grab_event()
{
WithFailedAdd();
var mock = WithUsenetClient();
mock.Setup(s => s.Download(It.IsAny<RemoteEpisode>()))
.Throws(new WebException());
Assert.Throws<WebException>(() => Subject.DownloadReport(_parseResult));
@ -83,15 +105,38 @@ namespace NzbDrone.Core.Test.Download
[Test]
public void should_not_attempt_download_if_client_isnt_configure()
{
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClient()).Returns((IDownloadClient)null);
Subject.DownloadReport(_parseResult);
Mocker.GetMock<IDownloadClient>().Verify(c => c.DownloadNzb(It.IsAny<RemoteEpisode>()), Times.Never());
Mocker.GetMock<IDownloadClient>().Verify(c => c.Download(It.IsAny<RemoteEpisode>()), Times.Never());
VerifyEventNotPublished<EpisodeGrabbedEvent>();
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_send_download_to_correct_usenet_client()
{
var mockTorrent = WithTorrentClient();
var mockUsenet = WithUsenetClient();
Subject.DownloadReport(_parseResult);
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteEpisode>()), Times.Never());
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteEpisode>()), Times.Once());
}
[Test]
public void should_send_download_to_correct_torrent_client()
{
var mockTorrent = WithTorrentClient();
var mockUsenet = WithUsenetClient();
_parseResult.Release.DownloadProtocol = Indexers.DownloadProtocol.Torrent;
Subject.DownloadReport(_parseResult);
mockTorrent.Verify(c => c.Download(It.IsAny<RemoteEpisode>()), Times.Once());
mockUsenet.Verify(c => c.Download(It.IsAny<RemoteEpisode>()), Times.Never());
}
}
}

@ -13,32 +13,43 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Download
{
[TestFixture]
public class FailedDownloadServiceFixture : CoreTest<FailedDownloadService>
public class FailedDownloadServiceFixture : CoreTest<DownloadTrackingService>
{
private List<HistoryItem> _completed;
private List<HistoryItem> _failed;
private List<DownloadClientItem> _completed;
private List<DownloadClientItem> _failed;
[SetUp]
public void Setup()
{
_completed = Builder<HistoryItem>.CreateListOfSize(5)
_completed = Builder<DownloadClientItem>.CreateListOfSize(5)
.All()
.With(h => h.Status = HistoryStatus.Completed)
.With(h => h.Status = DownloadItemStatus.Completed)
.Build()
.ToList();
_failed = Builder<HistoryItem>.CreateListOfSize(1)
_failed = Builder<DownloadClientItem>.CreateListOfSize(1)
.All()
.With(h => h.Status = HistoryStatus.Failed)
.With(h => h.Status = DownloadItemStatus.Failed)
.Build()
.ToList();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClient()).Returns(Mocker.GetMock<IDownloadClient>().Object);
.Setup(c => c.GetDownloadClients())
.Returns( new IDownloadClient[] { Mocker.GetMock<IDownloadClient>().Object });
Mocker.GetMock<IDownloadClient>()
.SetupGet(c => c.Definition)
.Returns(new Core.Download.DownloadClientDefinition { Id = 1, Name = "testClient" });
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableFailedDownloadHandling)
.Returns(true);
Mocker.GetMock<IHistoryService>()
.Setup(s => s.Imported())
.Returns(new List<History.History>());
Mocker.SetConstant<IFailedDownloadService>(Mocker.Resolve<FailedDownloadService>());
}
private void GivenNoGrabbedHistory()
@ -72,7 +83,7 @@ namespace NzbDrone.Core.Test.Download
private void GivenFailedDownloadClientHistory()
{
Mocker.GetMock<IDownloadClient>()
.Setup(s => s.GetHistory(0, 20))
.Setup(s => s.GetItems())
.Returns(_failed);
}
@ -102,10 +113,10 @@ namespace NzbDrone.Core.Test.Download
public void should_not_process_if_no_download_client_history()
{
Mocker.GetMock<IDownloadClient>()
.Setup(s => s.GetHistory(0, 20))
.Returns(new List<HistoryItem>());
.Setup(s => s.GetItems())
.Returns(new List<DownloadClientItem>());
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
Mocker.GetMock<IHistoryService>()
.Verify(s => s.BetweenDates(It.IsAny<DateTime>(), It.IsAny<DateTime>(), HistoryEventType.Grabbed),
@ -117,11 +128,14 @@ namespace NzbDrone.Core.Test.Download
[Test]
public void should_not_process_if_no_failed_items_in_download_client_history()
{
GivenNoGrabbedHistory();
GivenNoFailedHistory();
Mocker.GetMock<IDownloadClient>()
.Setup(s => s.GetHistory(0, 20))
.Setup(s => s.GetItems())
.Returns(_completed);
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
Mocker.GetMock<IHistoryService>()
.Verify(s => s.BetweenDates(It.IsAny<DateTime>(), It.IsAny<DateTime>(), HistoryEventType.Grabbed),
@ -136,7 +150,7 @@ namespace NzbDrone.Core.Test.Download
GivenNoGrabbedHistory();
GivenFailedDownloadClientHistory();
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoFailedDownloads();
}
@ -156,7 +170,7 @@ namespace NzbDrone.Core.Test.Download
GivenGrabbedHistory(historyGrabbed);
GivenNoFailedHistory();
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoFailedDownloads();
}
@ -171,7 +185,7 @@ namespace NzbDrone.Core.Test.Download
.ToList();
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
GivenGrabbedHistory(historyGrabbed);
@ -184,7 +198,7 @@ namespace NzbDrone.Core.Test.Download
GivenFailedHistory(historyFailed);
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyFailedDownloads();
}
@ -202,9 +216,9 @@ namespace NzbDrone.Core.Test.Download
GivenFailedHistory(history);
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _failed.First().Id);
history.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoFailedDownloads();
}
@ -222,9 +236,9 @@ namespace NzbDrone.Core.Test.Download
GivenNoFailedHistory();
history.First().Data.Add("downloadClient", "SabnzbdClient");
history.First().Data.Add("downloadClientId", _failed.First().Id);
history.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyFailedDownloads();
}
@ -244,10 +258,10 @@ namespace NzbDrone.Core.Test.Download
history.ForEach(h =>
{
h.Data.Add("downloadClient", "SabnzbdClient");
h.Data.Add("downloadClientId", _failed.First().Id);
h.Data.Add("downloadClientId", _failed.First().DownloadClientId);
});
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyFailedDownloads(2);
}
@ -259,7 +273,7 @@ namespace NzbDrone.Core.Test.Download
.SetupGet(s => s.EnableFailedDownloadHandling)
.Returns(false);
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoFailedDownloads();
}
@ -276,7 +290,7 @@ namespace NzbDrone.Core.Test.Download
_failed.First().Message = "Unpacking failed, write error or disk is full?";
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoFailedDownloads();
}
@ -291,12 +305,12 @@ namespace NzbDrone.Core.Test.Download
.ToList();
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
GivenGrabbedHistory(historyGrabbed);
GivenNoFailedHistory();
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyFailedDownloads();
}
@ -311,13 +325,13 @@ namespace NzbDrone.Core.Test.Download
.ToList();
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
historyGrabbed.First().Data.Add("ageHours", "48");
GivenGrabbedHistory(historyGrabbed);
GivenNoFailedHistory();
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyFailedDownloads();
}
@ -332,14 +346,14 @@ namespace NzbDrone.Core.Test.Download
.ToList();
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
historyGrabbed.First().Data.Add("ageHours", "48");
GivenGrabbedHistory(historyGrabbed);
GivenNoFailedHistory();
GivenGracePeriod(6);
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyFailedDownloads();
}
@ -354,7 +368,7 @@ namespace NzbDrone.Core.Test.Download
.ToList();
historyGrabbed.First().Data.Add("downloadClient", "SabnzbdClient");
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().Id);
historyGrabbed.First().Data.Add("downloadClientId", _failed.First().DownloadClientId);
historyGrabbed.First().Data.Add("ageHours", "1");
GivenGrabbedHistory(historyGrabbed);
@ -362,7 +376,7 @@ namespace NzbDrone.Core.Test.Download
GivenGracePeriod(6);
GivenRetryLimit(1);
Subject.Execute(new CheckForFailedDownloadCommand());
Subject.Execute(new CheckForFinishedDownloadCommand());
VerifyNoFailedDownloads();
}

@ -15,8 +15,8 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
public void should_return_warning_when_download_client_has_not_been_configured()
{
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClient())
.Returns((IDownloadClient)null);
.Setup(s => s.GetDownloadClients())
.Returns(new IDownloadClient[0]);
Subject.Check().ShouldBeWarning();
}
@ -26,12 +26,12 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
{
var downloadClient = Mocker.GetMock<IDownloadClient>();
downloadClient.Setup(s => s.GetQueue())
downloadClient.Setup(s => s.GetItems())
.Throws<Exception>();
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClient())
.Returns(downloadClient.Object);
.Setup(s => s.GetDownloadClients())
.Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeError();
}
@ -41,12 +41,12 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
{
var downloadClient = Mocker.GetMock<IDownloadClient>();
downloadClient.Setup(s => s.GetQueue())
.Returns(new List<QueueItem>());
downloadClient.Setup(s => s.GetItems())
.Returns(new List<DownloadClientItem>());
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClient())
.Returns(downloadClient.Object);
.Setup(s => s.GetDownloadClients())
.Returns(new IDownloadClient[] { downloadClient.Object });
Subject.Check().ShouldBeOk();
}

@ -25,17 +25,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
.Setup(s => s.FolderExists(DRONE_FACTORY_FOLDER))
.Returns(exists);
}
[Test]
public void should_return_warning_when_drone_factory_folder_is_not_configured()
{
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.DownloadedEpisodesFolder)
.Returns("");
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_error_when_drone_factory_folder_does_not_exist()
{

@ -0,0 +1,95 @@
using System;
using System.Linq;
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using FizzWare.NBuilder;
using NUnit.Framework;
using NzbDrone.Test.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Download;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class ImportMechanismCheckFixture : CoreTest<ImportMechanismCheck>
{
private const string DRONE_FACTORY_FOLDER = @"C:\Test\Unsorted";
private IList<TrackedDownload> _completed;
private void GivenCompletedDownloadHandling(bool? enabled = null)
{
if (enabled.HasValue)
{
Mocker.GetMock<IConfigService>()
.Setup(s => s.IsDefined("EnableCompletedDownloadHandling"))
.Returns(true);
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(enabled.Value);
}
_completed = Builder<TrackedDownload>.CreateListOfSize(1)
.All()
.With(v => v.State == TrackedDownloadState.Downloading)
.With(v => v.DownloadItem = new DownloadClientItem())
.With(v => v.DownloadItem.Status = DownloadItemStatus.Completed)
.With(v => v.DownloadItem.OutputPath = @"C:\Test\DropFolder\myfile.mkv".AsOsAgnostic())
.Build();
Mocker.GetMock<IDownloadTrackingService>()
.Setup(v => v.GetCompletedDownloads())
.Returns(_completed.ToArray());
}
private void GivenDroneFactoryFolder(bool exists = false)
{
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.DownloadedEpisodesFolder)
.Returns(DRONE_FACTORY_FOLDER.AsOsAgnostic());
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(DRONE_FACTORY_FOLDER.AsOsAgnostic()))
.Returns(exists);
}
[Test]
public void should_return_warning_when_completed_download_handling_not_configured()
{
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_warning_when_both_completeddownloadhandling_and_dronefactory_are_not_configured()
{
GivenCompletedDownloadHandling(false);
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_warning_when_downloadclient_drops_in_dronefactory_folder()
{
GivenCompletedDownloadHandling(true);
GivenDroneFactoryFolder(true);
_completed.First().DownloadItem.OutputPath = (DRONE_FACTORY_FOLDER + @"\myfile.mkv").AsOsAgnostic();
Subject.Check().ShouldBeWarning();
}
[Test]
public void should_return_ok_when_no_issues_found()
{
GivenCompletedDownloadHandling(true);
GivenDroneFactoryFolder(true);
Subject.Check().ShouldBeOk();
}
}
}

@ -29,37 +29,6 @@ namespace NzbDrone.Core.Test.IndexerTests
Mocker.SetConstant<IEnumerable<IIndexer>>(_indexers);
}
[Test]
public void should_create_default_indexer_on_startup()
{
IList<IndexerDefinition> storedIndexers = null;
Mocker.GetMock<IIndexerRepository>()
.Setup(c => c.InsertMany(It.IsAny<IList<IndexerDefinition>>()))
.Callback<IList<IndexerDefinition>>(indexers => storedIndexers = indexers);
Subject.Handle(new ApplicationStartedEvent());
storedIndexers.Should().NotBeEmpty();
storedIndexers.Select(c => c.Name).Should().OnlyHaveUniqueItems();
storedIndexers.Select(c => c.Enable).Should().NotBeEmpty();
storedIndexers.Select(c => c.Implementation).Should().NotContainNulls();
}
[Test]
public void getting_list_of_indexers()
{
Mocker.SetConstant<IIndexerRepository>(Mocker.Resolve<IndexerRepository>());
Subject.Handle(new ApplicationStartedEvent());
var indexers = Subject.All().ToList();
indexers.Should().NotBeEmpty();
indexers.Should().NotContain(c => c.Settings == null);
indexers.Should().NotContain(c => c.Name == null);
indexers.Select(c => c.Name).Should().OnlyHaveUniqueItems();
}
[Test]
public void should_remove_missing_indexers_on_startup()
{

@ -1,7 +1,6 @@
using System.Collections.Generic;
using FluentAssertions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Eztv;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Indexers.Wombles;
using NzbDrone.Core.Parser.Model;
@ -37,39 +36,6 @@ namespace NzbDrone.Core.Test.IndexerTests.IntegrationTests
ValidateResult(result, skipSize: true, skipInfo: true);
}
[Test]
public void extv_rss()
{
var indexer = new Eztv();
indexer.Definition = new IndexerDefinition
{
Name = "Eztv",
Settings = NullConfig.Instance
};
var result = Subject.FetchRss(indexer);
ValidateTorrentResult(result, skipSize: false, skipInfo: true);
}
[Test]
public void nzbsorg_rss()
{
var indexer = new Newznab();
indexer.Definition = new IndexerDefinition();
indexer.Definition.Name = "nzbs.org";
indexer.Definition.Settings = new NewznabSettings
{
ApiKey = "64d61d3cfd4b75e51d01cbc7c6a78275",
Url = "http://nzbs.org"
};
var result = Subject.FetchRss(indexer);
ValidateResult(result);
}
private void ValidateResult(IList<ReleaseInfo> reports, bool skipSize = false, bool skipInfo = false)
{

@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.IndexerTests
indexer.Setup(s => s.GetSeasonSearchUrls(It.IsAny<String>(), It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>()))
.Returns(new List<string> { "http://www.nzbdrone.com" });
indexer.SetupGet(s => s.SupportsPaging).Returns(paging);
indexer.SetupGet(s => s.SupportedPageSize).Returns(paging ? 100 : 0);
var definition = new IndexerDefinition();
definition.Name = "Test";

@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.MediaFiles
.Returns("c:\\drop\\".AsOsAgnostic());
Mocker.GetMock<IImportApprovedEpisodes>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true))
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
.Returns(new List<ImportDecision>());
}
@ -77,6 +77,8 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_skip_if_file_is_in_use_by_another_process()
{
GivenValidSeries();
Mocker.GetMock<IDiskProvider>().Setup(c => c.IsFileLocked(It.IsAny<string>()))
.Returns(true);
@ -122,7 +124,7 @@ namespace NzbDrone.Core.Test.MediaFiles
public void should_not_delete_folder_if_no_files_were_imported()
{
Mocker.GetMock<IImportApprovedEpisodes>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), false))
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), false, null))
.Returns(new List<ImportDecision>());
Subject.Execute(new DownloadedEpisodesScanCommand());
@ -132,7 +134,7 @@ namespace NzbDrone.Core.Test.MediaFiles
}
[Test]
public void should_delete_folder_if_files_were_imported_and_video_files_remain()
public void should_not_delete_folder_if_files_were_imported_and_video_files_remain()
{
GivenValidSeries();
@ -146,7 +148,7 @@ namespace NzbDrone.Core.Test.MediaFiles
.Returns(imported);
Mocker.GetMock<IImportApprovedEpisodes>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true))
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
.Returns(imported);
Subject.Execute(new DownloadedEpisodesScanCommand());
@ -172,7 +174,7 @@ namespace NzbDrone.Core.Test.MediaFiles
.Returns(imported);
Mocker.GetMock<IImportApprovedEpisodes>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true))
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
.Returns(imported);
Mocker.GetMock<ISampleService>()
@ -211,13 +213,13 @@ namespace NzbDrone.Core.Test.MediaFiles
private void VerifyNoImport()
{
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true),
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null),
Times.Never());
}
private void VerifyImport()
{
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true),
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null),
Times.Once());
}
}

@ -1,75 +0,0 @@
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
{
[TestFixture]
public class NotInUseSpecificationFixture : CoreTest<NotInUseSpecification>
{
private LocalEpisode _localEpisode;
[SetUp]
public void Setup()
{
_localEpisode = new LocalEpisode
{
Path = @"C:\Test\30 Rock\30.rock.s01e01.avi".AsOsAgnostic(),
Size = 100,
Series = Builder<Series>.CreateNew().Build()
};
}
private void GivenChildOfSeries()
{
_localEpisode.ExistingFile = true;
}
[Test]
public void should_return_true_if_file_is_under_series_folder()
{
GivenChildOfSeries();
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
}
[Test]
public void should_not_check_for_file_in_use_if_child_of_series_folder()
{
GivenChildOfSeries();
Subject.IsSatisfiedBy(_localEpisode);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.IsFileLocked(It.IsAny<string>()), Times.Never());
}
[Test]
public void should_return_false_if_file_is_in_use()
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.IsFileLocked(It.IsAny<string>()))
.Returns(true);
Subject.IsSatisfiedBy(_localEpisode).Should().BeFalse();
}
[Test]
public void should_return_true_if_file_is_not_in_use()
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.IsFileLocked(It.IsAny<string>()))
.Returns(false);
Subject.IsSatisfiedBy(_localEpisode).Should().BeTrue();
}
}
}

@ -57,20 +57,20 @@ namespace NzbDrone.Core.Test.MediaFiles
}
Mocker.GetMock<IUpgradeMediaFiles>()
.Setup(s => s.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), It.IsAny<LocalEpisode>()))
.Setup(s => s.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), It.IsAny<LocalEpisode>(), false))
.Returns(new EpisodeFileMoveResult());
}
[Test]
public void should_return_empty_list_if_there_are_no_approved_decisions()
{
Subject.Import(_rejectedDecisions).Should().BeEmpty();
Subject.Import(_rejectedDecisions, false).Should().BeEmpty();
}
[Test]
public void should_import_each_approved()
{
Subject.Import(_approvedDecisions).Should().HaveCount(5);
Subject.Import(_approvedDecisions, false).Should().HaveCount(5);
}
[Test]
@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.MediaFiles
all.AddRange(_rejectedDecisions);
all.AddRange(_approvedDecisions);
Subject.Import(all).Should().HaveCount(5);
Subject.Import(all, false).Should().HaveCount(5);
}
[Test]
@ -90,7 +90,7 @@ namespace NzbDrone.Core.Test.MediaFiles
all.AddRange(_approvedDecisions);
all.Add(new ImportDecision(_approvedDecisions.First().LocalEpisode));
Subject.Import(all).Should().HaveCount(5);
Subject.Import(all, false).Should().HaveCount(5);
}
[Test]
@ -99,7 +99,7 @@ namespace NzbDrone.Core.Test.MediaFiles
Subject.Import(new List<ImportDecision> {_approvedDecisions.First()}, true);
Mocker.GetMock<IUpgradeMediaFiles>()
.Verify(v => v.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), _approvedDecisions.First().LocalEpisode),
.Verify(v => v.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), _approvedDecisions.First().LocalEpisode, false),
Times.Once());
}
@ -115,10 +115,10 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_not_move_existing_files()
{
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() });
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, false);
Mocker.GetMock<IUpgradeMediaFiles>()
.Verify(v => v.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), _approvedDecisions.First().LocalEpisode),
.Verify(v => v.UpgradeEpisodeFile(It.IsAny<EpisodeFile>(), _approvedDecisions.First().LocalEpisode, false),
Times.Never());
}
@ -143,7 +143,7 @@ namespace NzbDrone.Core.Test.MediaFiles
all.Add(fileDecision);
all.Add(sampleDecision);
var results = Subject.Import(all);
var results = Subject.Import(all, false);
results.Should().HaveCount(1);
results.Should().ContainSingle(d => d.LocalEpisode.Size == fileDecision.LocalEpisode.Size);

@ -115,18 +115,20 @@
<Compile Include="DecisionEngineTests\Search\SeriesSpecificationFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\GetQualifiedReportsFixture.cs" />
<Compile Include="Download\DownloadClientTests\BlackholeProviderFixture.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetTests\DownloadNzbFixture.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetTests\QueueFixture.cs" />
<Compile Include="Download\DownloadClientTests\Blackhole\UsenetBlackholeFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadClientFixtureBase.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetTests\NzbgetFixture.cs" />
<Compile Include="Download\DownloadClientTests\PneumaticProviderFixture.cs" />
<Compile Include="Download\DownloadClientTests\SabnzbdTests\SabnzbdFixture.cs" />
<Compile Include="Download\DownloadServiceFixture.cs" />
<Compile Include="Download\CompletedDownloadServiceFixture.cs" />
<Compile Include="Download\FailedDownloadServiceFixture.cs" />
<Compile Include="Framework\CoreTest.cs" />
<Compile Include="Framework\DbTest.cs" />
<Compile Include="Framework\NBuilderExtensions.cs" />
<Compile Include="HealthCheck\Checks\RootFolderCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\DownloadClientCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\ImportMechanismCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\UpdateCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\IndexerCheckFixture.cs" />
<Compile Include="HealthCheck\Checks\DroneFactoryCheckFixture.cs" />
@ -156,7 +158,6 @@
<Compile Include="MediaFiles\EpisodeImport\Specifications\FullSeasonSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\FreeSpaceSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMakerFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotInUseSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\SampleServiceFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotSampleSpecificationFixture.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\NotUnpackingSpecificationFixture.cs" />

@ -33,13 +33,14 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[ACX]Hack Sign 01 Role Play [Kosaka] [9C57891E].mkv", "Hack Sign", 1, 0, 0)]
[TestCase("[SFW-sage] Bakuman S3 - 12 [720p][D07C91FC]", "Bakuman S3", 12, 0, 0)]
[TestCase("ducktales_e66_time_is_money_part_one_marking_time", "DuckTales", 66, 0, 0)]
[TestCase("[Underwater-FFF] No Game No Life - 01 (720p) [27AAA0A0].mkv", "No Game No Life", 1, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
{
var result = Parser.Parser.ParseTitle(postTitle);
result.Should().NotBeNull();
result.AbsoluteEpisodeNumbers.First().Should().Be(absoluteEpisodeNumber);
result.AbsoluteEpisodeNumbers.Single().Should().Be(absoluteEpisodeNumber);
result.SeasonNumber.Should().Be(seasonNumber);
result.EpisodeNumbers.FirstOrDefault().Should().Be(episodeNumber);
result.EpisodeNumbers.SingleOrDefault().Should().Be(episodeNumber);
result.SeriesTitle.Should().Be(title.CleanSeriesTitle());
result.FullSeason.Should().BeFalse();
}

@ -40,6 +40,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Shield,.The.1x13.Tueurs.De.Flics.FR.DVDRip.XviD", Language.French)]
[TestCase("True.Detective.S01E01.1080p.WEB-DL.Rus.Eng.TVKlondike", Language.Russian)]
[TestCase("The.Trip.To.Italy.S02E01.720p.HDTV.x264-TLA", Language.English)]
[TestCase("Revolution S01E03 No Quarter 2012 WEB-DL 720p Nordic-philipo mkv", Language.Norwegian)]
public void should_parse_language(string postTitle, Language language)
{
var result = Parser.Parser.ParseTitle(postTitle);

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration />

@ -72,6 +72,11 @@ namespace NzbDrone.Core.Configuration
_eventAggregator.PublishEvent(new ConfigSavedEvent());
}
public Boolean IsDefined(String key)
{
return _repository.Get(key.ToLower()) != null;
}
public String DownloadedEpisodesFolder
{
get { return GetValue(ConfigKey.DownloadedEpisodesFolder.ToString()); }
@ -117,6 +122,27 @@ namespace NzbDrone.Core.Configuration
set { SetValue("AutoDownloadPropers", value); }
}
public Boolean EnableCompletedDownloadHandling
{
get { return GetValueBoolean("EnableCompletedDownloadHandling", false); }
set { SetValue("EnableCompletedDownloadHandling", value); }
}
public Boolean RemoveCompletedDownloads
{
get { return GetValueBoolean("RemoveCompletedDownloads", false); }
set { SetValue("RemoveCompletedDownloads", value); }
}
public Boolean EnableFailedDownloadHandling
{
get { return GetValueBoolean("EnableFailedDownloadHandling", true); }
set { SetValue("EnableFailedDownloadHandling", value); }
}
public Boolean AutoRedownloadFailed
{
get { return GetValueBoolean("AutoRedownloadFailed", true); }
@ -152,13 +178,6 @@ namespace NzbDrone.Core.Configuration
set { SetValue("BlacklistRetryLimit", value); }
}
public Boolean EnableFailedDownloadHandling
{
get { return GetValueBoolean("EnableFailedDownloadHandling", true); }
set { SetValue("EnableFailedDownloadHandling", value); }
}
public Boolean CreateEmptySeriesFolders
{
get { return GetValueBoolean("CreateEmptySeriesFolders", false); }
@ -186,6 +205,13 @@ namespace NzbDrone.Core.Configuration
set { SetValue("DownloadedEpisodesScanInterval", value); }
}
public Int32 DownloadClientHistoryLimit
{
get { return GetValueInt("DownloadClientHistoryLimit", 30); }
set { SetValue("DownloadClientHistoryLimit", value); }
}
public Boolean SkipFreeSpaceCheckWhenImporting
{
get { return GetValueBoolean("SkipFreeSpaceCheckWhenImporting", false); }

@ -11,15 +11,21 @@ namespace NzbDrone.Core.Configuration
Dictionary<String, Object> AllWithDefaults();
void SaveConfigDictionary(Dictionary<string, object> configValues);
Boolean IsDefined(String key);
//Download Client
String DownloadedEpisodesFolder { get; set; }
String DownloadClientWorkingFolders { get; set; }
Int32 DownloadedEpisodesScanInterval { get; set; }
Int32 DownloadClientHistoryLimit { get; set; }
//Completed/Failed Download Handling (Download client)
Boolean EnableCompletedDownloadHandling { get; set; }
Boolean RemoveCompletedDownloads { get; set; }
//Failed Download Handling (Download client)
Boolean EnableFailedDownloadHandling { get; set; }
Boolean AutoRedownloadFailed { get; set; }
Boolean RemoveFailedDownloads { get; set; }
Boolean EnableFailedDownloadHandling { get; set; }
Int32 BlacklistGracePeriod { get; set; }
Int32 BlacklistRetryInterval { get; set; }
Int32 BlacklistRetryLimit { get; set; }

@ -0,0 +1,243 @@
using System;
using System.Data;
using System.Linq;
using System.Collections.Generic;
using FluentMigrator;
using Newtonsoft.Json;
using NzbDrone.Common;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.IO;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(51)]
public class download_client_import : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(EnableCompletedDownloadHandlingForNewUsers);
Execute.WithConnection(ConvertFolderSettings);
Execute.WithConnection(AssociateImportedHistoryItems);
}
private void EnableCompletedDownloadHandlingForNewUsers(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = @"SELECT Value FROM Config WHERE Key = 'downloadedepisodesfolder'";
var result = cmd.ExecuteScalar();
if (result == null)
{
cmd.CommandText = @"INSERT INTO Config (Key, Value) VALUES ('enablecompleteddownloadhandling', 'True')";
cmd.ExecuteNonQuery();
}
}
}
private void ConvertFolderSettings(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand downloadClientsCmd = conn.CreateCommand())
{
downloadClientsCmd.Transaction = tran;
downloadClientsCmd.CommandText = @"SELECT Value FROM Config WHERE Key = 'downloadedepisodesfolder'";
var downloadedEpisodesFolder = downloadClientsCmd.ExecuteScalar() as String;
downloadClientsCmd.Transaction = tran;
downloadClientsCmd.CommandText = @"SELECT Id, Implementation, Settings, ConfigContract FROM DownloadClients WHERE ConfigContract = 'FolderSettings'";
using (IDataReader downloadClientReader = downloadClientsCmd.ExecuteReader())
{
while (downloadClientReader.Read())
{
var id = downloadClientReader.GetInt32(0);
var implementation = downloadClientReader.GetString(1);
var settings = downloadClientReader.GetString(2);
var configContract = downloadClientReader.GetString(3);
var settingsJson = JsonConvert.DeserializeObject(settings) as Newtonsoft.Json.Linq.JObject;
if (implementation == "Blackhole")
{
var newSettings = new
{
NzbFolder = settingsJson.Value<String>("folder"),
WatchFolder = downloadedEpisodesFolder
}.ToJson();
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE DownloadClients SET Implementation = ?, Settings = ?, ConfigContract = ? WHERE Id = ?";
updateCmd.AddParameter("UsenetBlackhole");
updateCmd.AddParameter(newSettings);
updateCmd.AddParameter("UsenetBlackholeSettings");
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
}
else if (implementation == "Pneumatic")
{
var newSettings = new
{
NzbFolder = settingsJson.Value<String>("folder")
}.ToJson();
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE DownloadClients SET Settings = ?, ConfigContract = ? WHERE Id = ?";
updateCmd.AddParameter(newSettings);
updateCmd.AddParameter("PneumaticSettings");
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
}
else
{
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "DELETE FROM DownloadClients WHERE Id = ?";
updateCmd.AddParameter(id);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
private sealed class MigrationHistoryItem
{
public Int32 Id { get; set; }
public Int32 EpisodeId { get; set; }
public Int32 SeriesId { get; set; }
public String SourceTitle { get; set; }
public DateTime Date { get; set; }
public Dictionary<String, String> Data { get; set; }
public MigrationHistoryEventType EventType { get; set; }
}
private enum MigrationHistoryEventType
{
Unknown = 0,
Grabbed = 1,
SeriesFolderImported = 2,
DownloadFolderImported = 3,
DownloadFailed = 4
}
private void AssociateImportedHistoryItems(IDbConnection conn, IDbTransaction tran)
{
var historyItems = new List<MigrationHistoryItem>();
using (IDbCommand historyCmd = conn.CreateCommand())
{
historyCmd.Transaction = tran;
historyCmd.CommandText = @"SELECT Id, EpisodeId, SeriesId, SourceTitle, Date, Data, EventType FROM History WHERE EventType NOT NULL";
using (IDataReader historyRead = historyCmd.ExecuteReader())
{
while (historyRead.Read())
{
historyItems.Add(new MigrationHistoryItem
{
Id = historyRead.GetInt32(0),
EpisodeId = historyRead.GetInt32(1),
SeriesId = historyRead.GetInt32(2),
SourceTitle = historyRead.GetString(3),
Date = historyRead.GetDateTime(4),
Data = Json.Deserialize<Dictionary<String, String>>(historyRead.GetString(5)),
EventType = (MigrationHistoryEventType)historyRead.GetInt32(6)
});
}
}
}
var numHistoryItemsNotAssociated = historyItems.Count(v => v.EventType == MigrationHistoryEventType.DownloadFolderImported &&
v.Data.GetValueOrDefault("downloadClientId") == null);
if (numHistoryItemsNotAssociated == 0)
{
return;
}
var historyItemsToAssociate = new Dictionary<MigrationHistoryItem, MigrationHistoryItem>();
var historyItemsLookup = historyItems.ToLookup(v => v.EpisodeId);
foreach (var historyItemGroup in historyItemsLookup)
{
var list = historyItemGroup.ToList();
for (int i = 0; i < list.Count - 1; i++)
{
var grabbedEvent = list[i];
if (grabbedEvent.EventType != MigrationHistoryEventType.Grabbed) continue;
if (grabbedEvent.Data.GetValueOrDefault("downloadClient") == null || grabbedEvent.Data.GetValueOrDefault("downloadClientId") == null) continue;
// Check if it is already associated with a failed/imported event.
int j;
for (j = i + 1; j < list.Count;j++)
{
if (list[j].EventType != MigrationHistoryEventType.DownloadFolderImported &&
list[j].EventType != MigrationHistoryEventType.DownloadFailed)
{
continue;
}
if (list[j].Data.ContainsKey("downloadClient") && list[j].Data["downloadClient"] == grabbedEvent.Data["downloadClient"] &&
list[j].Data.ContainsKey("downloadClientId") && list[j].Data["downloadClientId"] == grabbedEvent.Data["downloadClientId"])
{
break;
}
}
if (j != list.Count)
{
list.RemoveAt(j);
list.RemoveAt(i--);
continue;
}
var importedEvent = list[i + 1];
if (importedEvent.EventType != MigrationHistoryEventType.DownloadFolderImported) continue;
var droppedPath = importedEvent.Data.GetValueOrDefault("droppedPath");
if (droppedPath != null && new FileInfo(droppedPath).Directory.Name == grabbedEvent.SourceTitle)
{
historyItemsToAssociate[importedEvent] = grabbedEvent;
list.RemoveAt(i + 1);
list.RemoveAt(i--);
}
}
}
foreach (var pair in historyItemsToAssociate)
{
using (IDbCommand updateHistoryCmd = conn.CreateCommand())
{
pair.Key.Data["downloadClient"] = pair.Value.Data["downloadClient"];
pair.Key.Data["downloadClientId"] = pair.Value.Data["downloadClientId"];
updateHistoryCmd.Transaction = tran;
updateHistoryCmd.CommandText = "UPDATE History SET Data = ? WHERE Id = ?";
updateHistoryCmd.AddParameter(pair.Key.Data.ToJson());
updateHistoryCmd.AddParameter(pair.Key.Id);
updateHistoryCmd.ExecuteNonQuery();
}
}
_logger.Info("Updated old History items. {0}/{1} old ImportedEvents were associated with GrabbedEvents.", historyItemsToAssociate.Count, numHistoryItemsNotAssociated);
}
}
}

@ -6,11 +6,11 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
{
public abstract class NzbDroneMigrationBase : FluentMigrator.Migration
{
private Logger _logger;
protected readonly Logger _logger;
protected NzbDroneMigrationBase()
{
_logger = NzbDroneLogger.GetLogger();
_logger = NzbDroneLogger.GetLogger(this);
}
protected virtual void MainDbUpgrade()

@ -37,7 +37,8 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<Config>().RegisterModel("Config");
Mapper.Entity<RootFolder>().RegisterModel("RootFolders").Ignore(r => r.FreeSpace);
Mapper.Entity<IndexerDefinition>().RegisterModel("Indexers");
Mapper.Entity<IndexerDefinition>().RegisterModel("Indexers")
.Ignore(s => s.Protocol);
Mapper.Entity<ScheduledTask>().RegisterModel("ScheduledTasks");
Mapper.Entity<NotificationDefinition>().RegisterModel("Notifications");
Mapper.Entity<MetadataDefinition>().RegisterModel("Metadata");

@ -5,17 +5,18 @@ using NzbDrone.Core.Download;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Queue;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class NotInQueueSpecification : IDecisionEngineSpecification
{
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IQueueService _queueService;
private readonly Logger _logger;
public NotInQueueSpecification(IProvideDownloadClient downloadClientProvider, Logger logger)
public NotInQueueSpecification(IQueueService queueService, Logger logger)
{
_downloadClientProvider = downloadClientProvider;
_queueService = queueService;
_logger = logger;
}
@ -29,15 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public bool IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var downloadClient = _downloadClientProvider.GetDownloadClient();
if (downloadClient == null)
{
_logger.Warn("Download client isn't configured yet.");
return true;
}
var queue = downloadClient.GetQueue().Select(q => q.RemoteEpisode);
var queue = _queueService.GetQueue().Select(q => q.RemoteEpisode);
if (IsInQueue(subject, queue))
{

@ -1,3 +1,4 @@
using System.Linq;
using NLog;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Sabnzbd;
@ -41,9 +42,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
return true;
}
var downloadClient = _downloadClientProvider.GetDownloadClient();
var downloadClients = _downloadClientProvider.GetDownloadClients();
if (downloadClient != null && downloadClient.GetType() == typeof (Sabnzbd))
foreach (var downloadClient in downloadClients.OfType<Sabnzbd>())
{
_logger.Debug("Performing history status check on report");
foreach (var episode in subject.Episodes)

@ -2,7 +2,7 @@
namespace NzbDrone.Core.Download
{
public class CheckForFailedDownloadCommand : Command
public class CheckForFinishedDownloadCommand : Command
{
}

@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Clients.Blackhole
{
public class Blackhole : DownloadClientBase<FolderSettings>, IExecute<TestBlackholeCommand>
{
private readonly IDiskProvider _diskProvider;
private readonly IHttpProvider _httpProvider;
private readonly Logger _logger;
public Blackhole(IDiskProvider diskProvider, IHttpProvider httpProvider, Logger logger)
{
_diskProvider = diskProvider;
_httpProvider = httpProvider;
_logger = logger;
}
public override string DownloadNzb(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
title = FileNameBuilder.CleanFilename(title);
var filename = Path.Combine(Settings.Folder, title + ".nzb");
_logger.Debug("Downloading NZB from: {0} to: {1}", url, filename);
_httpProvider.DownloadFile(url, filename);
_logger.Debug("NZB Download succeeded, saved to: {0}", filename);
return null;
}
public override IEnumerable<QueueItem> GetQueue()
{
return new QueueItem[0];
}
public override IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 10)
{
return new HistoryItem[0];
}
public override void RemoveFromQueue(string id)
{
}
public override void RemoveFromHistory(string id)
{
}
public override void RetryDownload(string id)
{
throw new NotImplementedException();
}
public override void Test()
{
PerformTest(Settings.Folder);
}
private void PerformTest(string folder)
{
var testPath = Path.Combine(folder, "drone_test.txt");
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
_diskProvider.DeleteFile(testPath);
}
public void Execute(TestBlackholeCommand message)
{
PerformTest(message.Folder);
}
}
}

@ -1,32 +0,0 @@
using System;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients
{
public class FolderSettingsValidator : AbstractValidator<FolderSettings>
{
public FolderSettingsValidator()
{
//Todo: Validate that the path actually exists
RuleFor(c => c.Folder).IsValidPath();
}
}
public class FolderSettings : IProviderConfig
{
private static readonly FolderSettingsValidator Validator = new FolderSettingsValidator();
[FieldDefinition(0, Label = "Folder", Type = FieldType.Path)]
public String Folder { get; set; }
public ValidationResult Validate()
{
return Validator.Validate(this);
}
}
}

@ -5,15 +5,20 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbgetQueueItem
{
private string _nzbName;
public Int32 NzbId { get; set; }
public Int32 FirstId { get; set; }
public Int32 LastId { get; set; }
public string NzbName { get; set; }
public String NzbName { get; set; }
public String Category { get; set; }
public Int32 FileSizeMb { get; set; }
public Int32 RemainingSizeMb { get; set; }
public Int32 PausedSizeMb { get; set; }
public UInt32 FileSizeLo { get; set; }
public UInt32 FileSizeHi { get; set; }
public UInt32 RemainingSizeLo { get; set; }
public UInt32 RemainingSizeHi { get; set; }
public UInt32 PausedSizeLo { get; set; }
public UInt32 PausedSizeHi { get; set; }
public Int32 MinPriority { get; set; }
public Int32 MaxPriority { get; set; }
public Int32 ActiveDownloads { get; set; }
public List<NzbgetParameter> Parameters { get; set; }
}
}

@ -4,6 +4,8 @@ using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -14,22 +16,28 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
public class Nzbget : DownloadClientBase<NzbgetSettings>, IExecute<TestNzbgetCommand>
{
private readonly INzbgetProxy _proxy;
private readonly IParsingService _parsingService;
private readonly IHttpProvider _httpProvider;
private readonly Logger _logger;
public Nzbget(INzbgetProxy proxy,
IConfigService configService,
IParsingService parsingService,
IHttpProvider httpProvider,
Logger logger)
: base(configService, parsingService, logger)
{
_proxy = proxy;
_parsingService = parsingService;
_httpProvider = httpProvider;
_logger = logger;
}
public override string DownloadNzb(RemoteEpisode remoteEpisode)
public override DownloadProtocol Protocol
{
get
{
return DownloadProtocol.Usenet;
}
}
public override string Download(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title + ".nzb";
@ -48,80 +56,133 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
}
}
public override IEnumerable<QueueItem> GetQueue()
private IEnumerable<DownloadClientItem> GetQueue()
{
NzbgetGlobalStatus globalStatus;
List<NzbgetQueueItem> queue;
Dictionary<Int32, NzbgetPostQueueItem> postQueue;
try
{
globalStatus = _proxy.GetGlobalStatus(Settings);
queue = _proxy.GetQueue(Settings);
postQueue = _proxy.GetPostQueue(Settings).ToDictionary(v => v.NzbId);
}
catch (DownloadClientException ex)
{
_logger.ErrorException(ex.Message, ex);
return Enumerable.Empty<QueueItem>();
return Enumerable.Empty<DownloadClientItem>();
}
var queueItems = new List<QueueItem>();
var queueItems = new List<DownloadClientItem>();
Int64 totalRemainingSize = 0;
foreach (var item in queue)
{
var postQueueItem = postQueue.GetValueOrDefault(item.NzbId);
var totalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
var pausedSize = MakeInt64(item.PausedSizeHi, item.PausedSizeLo);
var remainingSize = MakeInt64(item.RemainingSizeHi, item.RemainingSizeLo);
var droneParameter = item.Parameters.SingleOrDefault(p => p.Name == "drone");
var queueItem = new QueueItem();
queueItem.Id = droneParameter == null ? item.NzbId.ToString() : droneParameter.Value.ToString();
var queueItem = new DownloadClientItem();
queueItem.DownloadClientId = droneParameter == null ? item.NzbId.ToString() : droneParameter.Value.ToString();
queueItem.Title = item.NzbName;
queueItem.Size = item.FileSizeMb;
queueItem.Sizeleft = item.RemainingSizeMb;
queueItem.Status = item.FileSizeMb == item.PausedSizeMb ? "paused" : "queued";
var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title);
if (parsedEpisodeInfo == null) continue;
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
if (remoteEpisode.Series == null) continue;
queueItem.TotalSize = totalSize;
queueItem.Category = item.Category;
if (postQueueItem != null)
{
queueItem.Status = DownloadItemStatus.Downloading;
queueItem.Message = postQueueItem.ProgressLabel;
if (postQueueItem.StageProgress != 0)
{
queueItem.RemainingTime = TimeSpan.FromSeconds(postQueueItem.StageTimeSec * 1000 / postQueueItem.StageProgress - postQueueItem.StageTimeSec);
}
}
else if (globalStatus.DownloadPaused || remainingSize == pausedSize)
{
queueItem.Status = DownloadItemStatus.Paused;
queueItem.RemainingSize = remainingSize;
}
else
{
if (item.ActiveDownloads == 0 && remainingSize != 0)
{
queueItem.Status = DownloadItemStatus.Queued;
}
else
{
queueItem.Status = DownloadItemStatus.Downloading;
}
queueItem.RemainingSize = remainingSize - pausedSize;
if (globalStatus.DownloadRate != 0)
{
queueItem.RemainingTime = TimeSpan.FromSeconds((totalRemainingSize + queueItem.RemainingSize) / globalStatus.DownloadRate);
totalRemainingSize += queueItem.RemainingSize;
}
}
queueItem.RemoteEpisode = remoteEpisode;
queueItems.Add(queueItem);
}
return queueItems;
}
public override IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 10)
private IEnumerable<DownloadClientItem> GetHistory()
{
List<NzbgetHistoryItem> history;
try
{
history = _proxy.GetHistory(Settings);
history = _proxy.GetHistory(Settings).Take(_configService.DownloadClientHistoryLimit).ToList();
}
catch (DownloadClientException ex)
{
_logger.ErrorException(ex.Message, ex);
return Enumerable.Empty<HistoryItem>();
return Enumerable.Empty<DownloadClientItem>();
}
var historyItems = new List<HistoryItem>();
var successStatues = new[] {"SUCCESS", "NONE"};
var historyItems = new List<DownloadClientItem>();
var successStatus = new[] {"SUCCESS", "NONE"};
foreach (var item in history)
{
var droneParameter = item.Parameters.SingleOrDefault(p => p.Name == "drone");
var status = successStatues.Contains(item.ParStatus) &&
successStatues.Contains(item.ScriptStatus)
? HistoryStatus.Completed
: HistoryStatus.Failed;
var historyItem = new HistoryItem();
historyItem.Id = droneParameter == null ? item.Id.ToString() : droneParameter.Value.ToString();
var historyItem = new DownloadClientItem();
historyItem.DownloadClient = Definition.Name;
historyItem.DownloadClientId = droneParameter == null ? item.Id.ToString() : droneParameter.Value.ToString();
historyItem.Title = item.Name;
historyItem.Size = item.FileSizeMb.ToString(); //Why is this a string?
historyItem.DownloadTime = 0;
historyItem.Storage = item.DestDir;
historyItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
historyItem.OutputPath = item.DestDir;
historyItem.Category = item.Category;
historyItem.Message = String.Format("PAR Status: {0} - Script Status: {1}", item.ParStatus, item.ScriptStatus);
historyItem.Status = status;
historyItem.Message = String.Format("PAR Status: {0} - Unpack Status: {1} - Move Status: {2} - Script Status: {3} - Delete Status: {4} - Mark Status: {5}", item.ParStatus, item.UnpackStatus, item.MoveStatus, item.ScriptStatus, item.DeleteStatus, item.MarkStatus);
historyItem.Status = DownloadItemStatus.Completed;
historyItem.RemainingTime = TimeSpan.Zero;
if (item.DeleteStatus == "MANUAL")
{
continue;
}
if (!successStatus.Contains(item.ParStatus) ||
!successStatus.Contains(item.UnpackStatus) ||
!successStatus.Contains(item.MoveStatus) ||
!successStatus.Contains(item.ScriptStatus))
{
historyItem.Status = DownloadItemStatus.Failed;
}
else if (item.MoveStatus != "SUCCESS")
{
historyItem.Status = DownloadItemStatus.Queued;
}
historyItems.Add(historyItem);
}
@ -129,12 +190,20 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
return historyItems;
}
public override void RemoveFromQueue(string id)
public override IEnumerable<DownloadClientItem> GetItems()
{
throw new NotImplementedException();
foreach (var downloadClientItem in GetQueue().Concat(GetHistory()))
{
if (downloadClientItem.Category != Settings.TvCategory) continue;
downloadClientItem.RemoteEpisode = GetRemoteEpisode(downloadClientItem.Title);
if (downloadClientItem.RemoteEpisode == null) continue;
yield return downloadClientItem;
}
}
public override void RemoveFromHistory(string id)
public override void RemoveItem(string id)
{
_proxy.RemoveFromHistory(id, Settings);
}
@ -149,7 +218,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
_proxy.GetVersion(Settings);
}
private VersionResponse GetVersion(string host = null, int port = 0, string username = null, string password = null)
private String GetVersion(string host = null, int port = 0, string username = null, string password = null)
{
return _proxy.GetVersion(Settings);
}
@ -161,5 +230,17 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
_proxy.GetVersion(settings);
}
// Javascript doesn't support 64 bit integers natively so json officially doesn't either.
// NzbGet api thus sends it in two 32 bit chunks. Here we join the two chunks back together.
// Simplified decimal example: "42" splits into "4" and "2". To join them I shift (<<) the "4" 1 digit to the left = "40". combine it with "2". which becomes "42" again.
private Int64 MakeInt64(UInt32 high, UInt32 low)
{
Int64 result = high;
result = (result << 32) | (Int64)low;
return result;
}
}
}

@ -1,10 +0,0 @@
using System;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbgetBooleanResponse
{
public String Version { get; set; }
public Boolean Result { get; set; }
}
}

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbgetGlobalStatus
{
public UInt32 RemainingSizeLo { get; set; }
public UInt32 RemainingSizeHi { get; set; }
public UInt32 DownloadedSizeLo { get; set; }
public UInt32 DownloadedSizeHi { get; set; }
public UInt32 DownloadRate { get; set; }
public UInt32 AverageDownloadRate { get; set; }
public UInt32 DownloadLimit { get; set; }
public Boolean DownloadPaused { get; set; }
}
}

@ -5,13 +5,17 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbgetHistoryItem
{
private string _nzbName;
public Int32 Id { get; set; }
public String Name { get; set; }
public String Category { get; set; }
public Int32 FileSizeMb { get; set; }
public UInt32 FileSizeLo { get; set; }
public UInt32 FileSizeHi { get; set; }
public String ParStatus { get; set; }
public String UnpackStatus { get; set; }
public String MoveStatus { get; set; }
public String ScriptStatus { get; set; }
public String DeleteStatus { get; set; }
public String MarkStatus { get; set; }
public String DestDir { get; set; }
public List<NzbgetParameter> Parameters { get; set; }
}

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbgetPostQueueItem
{
public Int32 NzbId { get; set; }
public String NzbName { get; set; }
public String Stage { get; set; }
public String ProgressLabel { get; set; }
public Int32 FileProgress { get; set; }
public Int32 StageProgress { get; set; }
public Int32 TotalTimeSec { get; set; }
public Int32 StageTimeSec { get; set; }
}
}

@ -13,9 +13,11 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
public interface INzbgetProxy
{
string DownloadNzb(Stream nzb, string title, string category, int priority, NzbgetSettings settings);
NzbgetGlobalStatus GetGlobalStatus(NzbgetSettings settings);
List<NzbgetQueueItem> GetQueue(NzbgetSettings settings);
List<NzbgetPostQueueItem> GetPostQueue(NzbgetSettings settings);
List<NzbgetHistoryItem> GetHistory(NzbgetSettings settings);
VersionResponse GetVersion(NzbgetSettings settings);
String GetVersion(NzbgetSettings settings);
void RemoveFromHistory(string id, NzbgetSettings settings);
void RetryDownload(string id, NzbgetSettings settings);
}
@ -34,7 +36,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
var parameters = new object[] { title, category, priority, false, Convert.ToBase64String(nzb.ToBytes()) };
var request = BuildRequest(new JsonRequest("append", parameters));
var response = Json.Deserialize<NzbgetBooleanResponse>(ProcessRequest(request, settings));
var response = Json.Deserialize<NzbgetResponse<Boolean>>(ProcessRequest(request, settings));
_logger.Debug("Queue Response: [{0}]", response.Result);
if (!response.Result)
@ -61,31 +63,45 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
return droneId;
}
public NzbgetGlobalStatus GetGlobalStatus(NzbgetSettings settings)
{
var request = BuildRequest(new JsonRequest("status"));
return Json.Deserialize<NzbgetResponse<NzbgetGlobalStatus>>(ProcessRequest(request, settings)).Result;
}
public List<NzbgetQueueItem> GetQueue(NzbgetSettings settings)
{
var request = BuildRequest(new JsonRequest("listgroups"));
return Json.Deserialize<NzbgetListResponse<NzbgetQueueItem>>(ProcessRequest(request, settings)).QueueItems;
return Json.Deserialize<NzbgetResponse<List<NzbgetQueueItem>>>(ProcessRequest(request, settings)).Result;
}
public List<NzbgetPostQueueItem> GetPostQueue(NzbgetSettings settings)
{
var request = BuildRequest(new JsonRequest("postqueue"));
return Json.Deserialize<NzbgetResponse<List<NzbgetPostQueueItem>>>(ProcessRequest(request, settings)).Result;
}
public List<NzbgetHistoryItem> GetHistory(NzbgetSettings settings)
{
var request = BuildRequest(new JsonRequest("history"));
return Json.Deserialize<NzbgetListResponse<NzbgetHistoryItem>>(ProcessRequest(request, settings)).QueueItems;
return Json.Deserialize<NzbgetResponse<List<NzbgetHistoryItem>>>(ProcessRequest(request, settings)).Result;
}
public VersionResponse GetVersion(NzbgetSettings settings)
public String GetVersion(NzbgetSettings settings)
{
var request = BuildRequest(new JsonRequest("version"));
return Json.Deserialize<VersionResponse>(ProcessRequest(request, settings));
return Json.Deserialize<NzbgetResponse<String>>(ProcessRequest(request, settings)).Version;
}
public void RemoveFromHistory(string id, NzbgetSettings settings)
{
var history = GetHistory(settings);
var item = history.SingleOrDefault(h => h.Parameters.SingleOrDefault(p => p.Name == "drone") != null);
var item = history.SingleOrDefault(h => h.Parameters.Any(p => p.Name == "drone" && id == (p.Value as string)));
if (item == null)
{
@ -120,7 +136,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
var parameters = new object[] { command, offset, editText, id };
var request = BuildRequest(new JsonRequest("editqueue", parameters));
var response = Json.Deserialize<NzbgetBooleanResponse>(ProcessRequest(request, settings));
var response = Json.Deserialize<NzbgetResponse<Boolean>>(ProcessRequest(request, settings));
return response.Result;
}

@ -4,11 +4,11 @@ using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbgetListResponse<T>
public class NzbgetResponse<T>
{
public String Version { get; set; }
[JsonProperty(PropertyName = "result")]
public List<T> QueueItems { get; set; }
public T Result { get; set; }
}
}

@ -13,9 +13,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
}
}
public String Host { get; set; }
public Int32 Port { get; set; }
public String Username { get; set; }
public String Password { get; set; }
public String TvCategory { get; set; }
public Int32 RecentTvPriority { get; set; }
public Int32 OlderTvPriority { get; set; }
public Boolean UseSsl { get; set; }
}
}

@ -1,10 +0,0 @@
using System;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class VersionResponse
{
public String Version { get; set; }
public String Result { get; set; }
}
}

@ -7,42 +7,54 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Clients.Pneumatic
{
public class Pneumatic : DownloadClientBase<FolderSettings>, IExecute<TestPneumaticCommand>
public class Pneumatic : DownloadClientBase<PneumaticSettings>, IExecute<TestPneumaticCommand>
{
private readonly IConfigService _configService;
private readonly IHttpProvider _httpProvider;
private readonly IDiskProvider _diskProvider;
private static readonly Logger logger = NzbDroneLogger.GetLogger();
public Pneumatic(IConfigService configService, IHttpProvider httpProvider,
IDiskProvider diskProvider)
public Pneumatic(IHttpProvider httpProvider,
IDiskProvider diskProvider,
IConfigService configService,
IParsingService parsingService,
Logger logger)
: base(configService, parsingService, logger)
{
_configService = configService;
_httpProvider = httpProvider;
_diskProvider = diskProvider;
}
public override string DownloadNzb(RemoteEpisode remoteEpisode)
public override DownloadProtocol Protocol
{
get
{
return DownloadProtocol.Usenet;
}
}
public override string Download(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
if (remoteEpisode.ParsedEpisodeInfo.FullSeason)
{
throw new NotImplementedException("Full season releases are not supported with Pneumatic.");
throw new NotSupportedException("Full season releases are not supported with Pneumatic.");
}
title = FileNameBuilder.CleanFilename(title);
//Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC)
var filename = Path.Combine(Settings.Folder, title + ".nzb");
var filename = Path.Combine(Settings.NzbFolder, title + ".nzb");
logger.Debug("Downloading NZB from: {0} to: {1}", url, filename);
_httpProvider.DownloadFile(url, filename);
@ -59,36 +71,28 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
{
get
{
return !string.IsNullOrWhiteSpace(Settings.Folder);
return !string.IsNullOrWhiteSpace(Settings.NzbFolder);
}
}
public override IEnumerable<QueueItem> GetQueue()
public override IEnumerable<DownloadClientItem> GetItems()
{
return new QueueItem[0];
return new DownloadClientItem[0];
}
public override IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 10)
{
return new HistoryItem[0];
}
public override void RemoveFromQueue(string id)
{
}
public override void RemoveFromHistory(string id)
public override void RemoveItem(string id)
{
throw new NotSupportedException();
}
public override void RetryDownload(string id)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
public override void Test()
{
PerformTest(Settings.Folder);
PerformTest(Settings.NzbFolder);
}
private void PerformTest(string folder)

@ -0,0 +1,32 @@
using System;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients.Pneumatic
{
public class PneumaticSettingsValidator : AbstractValidator<PneumaticSettings>
{
public PneumaticSettingsValidator()
{
//Todo: Validate that the path actually exists
RuleFor(c => c.NzbFolder).IsValidPath();
}
}
public class PneumaticSettings : IProviderConfig
{
private static readonly PneumaticSettingsValidator Validator = new PneumaticSettingsValidator();
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path)]
public String NzbFolder { get; set; }
public ValidationResult Validate()
{
return Validator.Validate(this);
}
}
}

@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -15,25 +16,28 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public class Sabnzbd : DownloadClientBase<SabnzbdSettings>, IExecute<TestSabnzbdCommand>
{
private readonly IHttpProvider _httpProvider;
private readonly IParsingService _parsingService;
private readonly ISabnzbdProxy _proxy;
private readonly ICached<IEnumerable<QueueItem>> _queueCache;
private readonly Logger _logger;
public Sabnzbd(IHttpProvider httpProvider,
ICacheManager cacheManager,
IParsingService parsingService,
ISabnzbdProxy proxy,
IConfigService configService,
IParsingService parsingService,
Logger logger)
: base(configService, parsingService, logger)
{
_httpProvider = httpProvider;
_parsingService = parsingService;
_proxy = proxy;
_queueCache = cacheManager.GetCache<IEnumerable<QueueItem>>(GetType(), "queue");
_logger = logger;
}
public override string DownloadNzb(RemoteEpisode remoteEpisode)
public override DownloadProtocol Protocol
{
get
{
return DownloadProtocol.Usenet;
}
}
public override string Download(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
@ -54,76 +58,106 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
}
}
public override IEnumerable<QueueItem> GetQueue()
private IEnumerable<DownloadClientItem> GetQueue()
{
return _queueCache.Get("queue", () =>
SabnzbdQueue sabQueue;
try
{
sabQueue = _proxy.GetQueue(0, 0, Settings);
}
catch (DownloadClientException ex)
{
SabnzbdQueue sabQueue;
_logger.ErrorException(ex.Message, ex);
return Enumerable.Empty<DownloadClientItem>();
}
try
var queueItems = new List<DownloadClientItem>();
foreach (var sabQueueItem in sabQueue.Items)
{
var queueItem = new DownloadClientItem();
queueItem.DownloadClient = Definition.Name;
queueItem.DownloadClientId = sabQueueItem.Id;
queueItem.Category = sabQueueItem.Category;
queueItem.Title = sabQueueItem.Title;
queueItem.TotalSize = (long)(sabQueueItem.Size * 1024 * 1024);
queueItem.RemainingSize = (long)(sabQueueItem.Sizeleft * 1024 * 1024);
queueItem.RemainingTime = sabQueueItem.Timeleft;
if (sabQueue.Paused || sabQueueItem.Status == SabnzbdDownloadStatus.Paused)
{
sabQueue = _proxy.GetQueue(0, 0, Settings);
queueItem.Status = DownloadItemStatus.Paused;
queueItem.RemainingTime = null;
}
catch (DownloadClientException ex)
else if (sabQueueItem.Status == SabnzbdDownloadStatus.Queued || sabQueueItem.Status == SabnzbdDownloadStatus.Grabbing)
{
_logger.ErrorException(ex.Message, ex);
return Enumerable.Empty<QueueItem>();
queueItem.Status = DownloadItemStatus.Queued;
}
var queueItems = new List<QueueItem>();
foreach (var sabQueueItem in sabQueue.Items)
else
{
var queueItem = new QueueItem();
queueItem.Id = sabQueueItem.Id;
queueItem.Title = sabQueueItem.Title;
queueItem.Size = sabQueueItem.Size;
queueItem.Sizeleft = sabQueueItem.Sizeleft;
queueItem.Timeleft = sabQueueItem.Timeleft;
queueItem.Status = sabQueueItem.Status;
var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title.Replace("ENCRYPTED / ", ""));
if (parsedEpisodeInfo == null) continue;
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
if (remoteEpisode.Series == null) continue;
queueItem.RemoteEpisode = remoteEpisode;
queueItem.Status = DownloadItemStatus.Downloading;
}
queueItems.Add(queueItem);
if (queueItem.Title.StartsWith("ENCRYPTED /"))
{
queueItem.Title = queueItem.Title.Substring(11);
queueItem.IsEncrypted = true;
}
return queueItems;
}, TimeSpan.FromSeconds(10));
queueItems.Add(queueItem);
}
return queueItems;
}
public override IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 10)
private IEnumerable<DownloadClientItem> GetHistory()
{
SabnzbdHistory sabHistory;
try
{
sabHistory = _proxy.GetHistory(start, limit, Settings);
sabHistory = _proxy.GetHistory(0, _configService.DownloadClientHistoryLimit, Settings);
}
catch (DownloadClientException ex)
{
_logger.ErrorException(ex.Message, ex);
return Enumerable.Empty<HistoryItem>();
return Enumerable.Empty<DownloadClientItem>();
}
var historyItems = new List<HistoryItem>();
var historyItems = new List<DownloadClientItem>();
foreach (var sabHistoryItem in sabHistory.Items)
{
var historyItem = new HistoryItem();
historyItem.Id = sabHistoryItem.Id;
historyItem.Title = sabHistoryItem.Title;
historyItem.Size = sabHistoryItem.Size;
historyItem.DownloadTime = sabHistoryItem.DownloadTime;
historyItem.Storage = sabHistoryItem.Storage;
historyItem.Category = sabHistoryItem.Category;
historyItem.Message = sabHistoryItem.FailMessage;
historyItem.Status = sabHistoryItem.Status == "Failed" ? HistoryStatus.Failed : HistoryStatus.Completed;
var historyItem = new DownloadClientItem
{
DownloadClient = Definition.Name,
DownloadClientId = sabHistoryItem.Id,
Category = sabHistoryItem.Category,
Title = sabHistoryItem.Title,
TotalSize = sabHistoryItem.Size,
RemainingSize = 0,
DownloadTime = TimeSpan.FromSeconds(sabHistoryItem.DownloadTime),
RemainingTime = TimeSpan.Zero,
OutputPath = sabHistoryItem.Storage,
Message = sabHistoryItem.FailMessage
};
if (sabHistoryItem.Status == SabnzbdDownloadStatus.Failed)
{
historyItem.Status = DownloadItemStatus.Failed;
}
else if (sabHistoryItem.Status == SabnzbdDownloadStatus.Completed)
{
historyItem.Status = DownloadItemStatus.Completed;
}
else // Verifying/Moving etc
{
historyItem.Status = DownloadItemStatus.Downloading;
}
historyItems.Add(historyItem);
}
@ -131,14 +165,29 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
return historyItems;
}
public override void RemoveFromQueue(string id)
public override IEnumerable<DownloadClientItem> GetItems()
{
_proxy.RemoveFrom("queue", id, Settings);
foreach (var downloadClientItem in GetQueue().Concat(GetHistory()))
{
if (downloadClientItem.Category != Settings.TvCategory) continue;
downloadClientItem.RemoteEpisode = GetRemoteEpisode(downloadClientItem.Title);
if (downloadClientItem.RemoteEpisode == null) continue;
yield return downloadClientItem;
}
}
public override void RemoveFromHistory(string id)
public override void RemoveItem(string id)
{
_proxy.RemoveFrom("history", id, Settings);
if (GetQueue().Any(v => v.DownloadClientId == id))
{
_proxy.RemoveFrom("queue", id, Settings);
}
else
{
_proxy.RemoveFrom("history", id, Settings);
}
}
public override void RetryDownload(string id)

@ -0,0 +1,22 @@
using System;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public enum SabnzbdDownloadStatus
{
Grabbing,
Queued,
Paused,
Checking,
Downloading,
QuickCheck,
Verifying,
Repairing,
Fetching, // Fetching additional blocks
Extracting,
Moving,
Running, // Running PP Script
Completed,
Failed
}
}

@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
@ -7,7 +8,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[JsonProperty(PropertyName = "fail_message")]
public string FailMessage { get; set; }
public string Size { get; set; }
[JsonProperty(PropertyName = "bytes")]
public Int64 Size { get; set; }
public string Category { get; set; }
[JsonProperty(PropertyName = "nzb_name")]
@ -17,7 +19,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public int DownloadTime { get; set; }
public string Storage { get; set; }
public string Status { get; set; }
public SabnzbdDownloadStatus Status { get; set; }
[JsonProperty(PropertyName = "nzo_id")]
public string Id { get; set; }

@ -167,7 +167,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
result.Error = response.Content.Replace("error: ", "");
}
if (result.Failed)
throw new DownloadClientException("Error response received from SABnzbd: {0}", result.Error);
}

@ -6,7 +6,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabnzbdQueueItem
{
public string Status { get; set; }
public SabnzbdDownloadStatus Status { get; set; }
public int Index { get; set; }
[JsonConverter(typeof(SabnzbdQueueTimeConverter))]
@ -15,8 +15,6 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[JsonProperty(PropertyName = "mb")]
public decimal Size { get; set; }
private string _title;
[JsonProperty(PropertyName = "filename")]
public string Title { get; set; }

@ -13,11 +13,15 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
}
}
public String Host { get; set; }
public Int32 Port { get; set; }
public String ApiKey { get; set; }
public String Username { get; set; }
public String Password { get; set; }
public String TvCategory { get; set; }
public Int32 RecentTvPriority { get; set; }
public Int32 OlderTvPriority { get; set; }
public Boolean UseSsl { get; set; }
}
}

@ -1,9 +1,9 @@
using System;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Download.Clients.Blackhole
namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
{
public class TestBlackholeCommand : Command
public class TestUsenetBlackholeCommand : Command
{
public override bool SendUpdatesToClient
{
@ -13,6 +13,7 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
}
}
public String Folder { get; set; }
public String NzbFolder { get; set; }
public String WatchFolder { get; set; }
}
}

@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
{
public class UsenetBlackhole : DownloadClientBase<UsenetBlackholeSettings>, IExecute<TestUsenetBlackholeCommand>
{
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
private readonly IHttpProvider _httpProvider;
public UsenetBlackhole(IDiskProvider diskProvider,
IDiskScanService diskScanService,
IHttpProvider httpProvider,
IConfigService configService,
IParsingService parsingService,
Logger logger)
: base(configService, parsingService, logger)
{
_diskProvider = diskProvider;
_diskScanService = diskScanService;
_httpProvider = httpProvider;
}
public override DownloadProtocol Protocol
{
get
{
return DownloadProtocol.Usenet;
}
}
public override string Download(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
title = FileNameBuilder.CleanFilename(title);
var filename = Path.Combine(Settings.NzbFolder, title + ".nzb");
_logger.Debug("Downloading NZB from: {0} to: {1}", url, filename);
_httpProvider.DownloadFile(url, filename);
_logger.Debug("NZB Download succeeded, saved to: {0}", filename);
return null;
}
public override IEnumerable<DownloadClientItem> GetItems()
{
foreach (var folder in _diskProvider.GetDirectories(Settings.WatchFolder))
{
var title = FileNameBuilder.CleanFilename(Path.GetFileName(folder));
var files = _diskProvider.GetFiles(folder, SearchOption.AllDirectories);
var historyItem = new DownloadClientItem
{
DownloadClient = Definition.Name,
DownloadClientId = Definition.Name + "_" + Path.GetFileName(folder) + "_" + _diskProvider.FolderGetCreationTimeUtc(folder).Ticks,
Title = title,
TotalSize = files.Select(_diskProvider.GetFileSize).Sum(),
OutputPath = folder
};
if (files.Any(_diskProvider.IsFileLocked))
{
historyItem.Status = DownloadItemStatus.Downloading;
}
else
{
historyItem.Status = DownloadItemStatus.Completed;
historyItem.RemainingTime = TimeSpan.Zero;
}
historyItem.RemoteEpisode = GetRemoteEpisode(historyItem.Title);
if (historyItem.RemoteEpisode == null) continue;
yield return historyItem;
}
foreach (var videoFile in _diskScanService.GetVideoFiles(Settings.WatchFolder, false))
{
var title = FileNameBuilder.CleanFilename(Path.GetFileName(videoFile));
var historyItem = new DownloadClientItem
{
DownloadClient = Definition.Name,
DownloadClientId = Definition.Name + "_" + Path.GetFileName(videoFile) + "_" + _diskProvider.FileGetLastWriteUtc(videoFile).Ticks,
Title = title,
TotalSize = _diskProvider.GetFileSize(videoFile),
OutputPath = videoFile
};
if (_diskProvider.IsFileLocked(videoFile))
{
historyItem.Status = DownloadItemStatus.Downloading;
}
else
{
historyItem.Status = DownloadItemStatus.Completed;
}
historyItem.RemoteEpisode = GetRemoteEpisode(historyItem.Title);
if (historyItem.RemoteEpisode == null) continue;
yield return historyItem;
}
}
public override void RemoveItem(string id)
{
throw new NotSupportedException();
}
public override void RetryDownload(string id)
{
throw new NotSupportedException();
}
public override void Test()
{
PerformTest(Settings.NzbFolder);
PerformTest(Settings.WatchFolder);
}
private void PerformTest(string folder)
{
var testPath = Path.Combine(folder, "drone_test.txt");
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
_diskProvider.DeleteFile(testPath);
}
public void Execute(TestUsenetBlackholeCommand message)
{
PerformTest(message.NzbFolder);
PerformTest(message.WatchFolder);
}
}
}

@ -0,0 +1,36 @@
using System;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients.UsenetBlackhole
{
public class UsenetBlackholeSettingsValidator : AbstractValidator<UsenetBlackholeSettings>
{
public UsenetBlackholeSettingsValidator()
{
//Todo: Validate that the path actually exists
RuleFor(c => c.NzbFolder).IsValidPath();
RuleFor(c => c.WatchFolder).IsValidPath();
}
}
public class UsenetBlackholeSettings : IProviderConfig
{
private static readonly UsenetBlackholeSettingsValidator Validator = new UsenetBlackholeSettingsValidator();
[FieldDefinition(0, Label = "Nzb Folder", Type = FieldType.Path)]
public String NzbFolder { get; set; }
[FieldDefinition(1, Label = "Watch Folder", Type = FieldType.Path)]
public String WatchFolder { get; set; }
public ValidationResult Validate()
{
return Validator.Validate(this);
}
}
}

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using System.IO;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Download
{
public interface ICompletedDownloadService
{
void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> importedHistory);
}
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)
{
_eventAggregator = eventAggregator;
_configService = configService;
_diskProvider = diskProvider;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
_historyService = historyService;
_logger = logger;
}
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
{
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)
{
return;
}
if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Completed && trackedDownload.State == TrackedDownloadState.Downloading)
{
var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);
if (!grabbedItems.Any() && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
{
_logger.Trace("Ignoring download that wasn't grabbed by drone: " + trackedDownload.DownloadItem.Title);
return;
}
var importedItems = GetHistoryItems(importedHistory, trackedDownload.DownloadItem.DownloadClientId);
if (importedItems.Any())
{
trackedDownload.State = TrackedDownloadState.Imported;
_logger.Debug("Already added to history as imported: " + trackedDownload.DownloadItem.Title);
}
else
{
string downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
string downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
if (downloadItemOutputPath.IsNullOrWhiteSpace())
{
_logger.Trace("Storage path not specified: " + trackedDownload.DownloadItem.Title);
return;
}
if (!downloadedEpisodesFolder.IsNullOrWhiteSpace() && (downloadedEpisodesFolder.PathEquals(downloadItemOutputPath) || downloadedEpisodesFolder.IsParentPath(downloadItemOutputPath)))
{
_logger.Trace("Storage path inside drone factory, ignoring download: " + trackedDownload.DownloadItem.Title);
return;
}
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
{
var decisions = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);
if (decisions.Any())
{
trackedDownload.State = TrackedDownloadState.Imported;
}
}
else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
{
var decisions = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem);
if (decisions.Any())
{
trackedDownload.State = TrackedDownloadState.Imported;
}
}
else
{
if (grabbedItems.Any())
{
var episodeIds = trackedDownload.DownloadItem.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 &&
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);
_logger.Debug("Storage path does not exist, but found probable drone factory ImportEvent: " + trackedDownload.DownloadItem.Title);
return;
}
}
}
_logger.Debug("Storage path does not exist: " + trackedDownload.DownloadItem.Title);
return;
}
}
}
if (_configService.RemoveCompletedDownloads && trackedDownload.State == TrackedDownloadState.Imported && !trackedDownload.DownloadItem.IsReadOnly)
{
try
{
_logger.Info("Removing completed download from history: {0}", trackedDownload.DownloadItem.Title);
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath))
{
_logger.Info("Removing completed download directory: {0}", trackedDownload.DownloadItem.OutputPath);
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath, true);
}
else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath))
{
_logger.Info("Removing completed download file: {0}", trackedDownload.DownloadItem.OutputPath);
_diskProvider.DeleteFile(trackedDownload.DownloadItem.OutputPath);
}
trackedDownload.State = TrackedDownloadState.Removed;
}
catch (NotSupportedException)
{
_logger.Debug("Removing item not supported by your download client");
}
}
}
}
}

@ -1,12 +1,22 @@
using System;
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Configuration;
using NLog;
namespace NzbDrone.Core.Download
{
public abstract class DownloadClientBase<TSettings> : IDownloadClient where TSettings : IProviderConfig, new()
public abstract class DownloadClientBase<TSettings> : IDownloadClient
where TSettings : IProviderConfig, new()
{
protected readonly IConfigService _configService;
private readonly IParsingService _parsingService;
protected readonly Logger _logger;
public Type ConfigContract
{
get
@ -33,17 +43,40 @@ namespace NzbDrone.Core.Download
}
}
protected DownloadClientBase(IConfigService configService, IParsingService parsingService, Logger logger)
{
_configService = configService;
_parsingService = parsingService;
_logger = logger;
}
public override string ToString()
{
return GetType().Name;
}
public abstract string DownloadNzb(RemoteEpisode remoteEpisode);
public abstract IEnumerable<QueueItem> GetQueue();
public abstract IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 10);
public abstract void RemoveFromQueue(string id);
public abstract void RemoveFromHistory(string id);
public abstract DownloadProtocol Protocol
{
get;
}
public abstract string Download(RemoteEpisode remoteEpisode);
public abstract IEnumerable<DownloadClientItem> GetItems();
public abstract void RemoveItem(string id);
public abstract void RetryDownload(string id);
public abstract void Test();
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;
}
}
}

@ -9,7 +9,7 @@ namespace NzbDrone.Core.Download
{
public interface IDownloadClientFactory : IProviderFactory<IDownloadClient, DownloadClientDefinition>
{
List<IDownloadClient> Enabled();
}
public class DownloadClientFactory : ProviderFactory<IDownloadClient, DownloadClientDefinition>, IDownloadClientFactory
@ -22,9 +22,18 @@ namespace NzbDrone.Core.Download
_providerRepository = providerRepository;
}
public List<IDownloadClient> Enabled()
protected override List<DownloadClientDefinition> Active()
{
return base.Active().Where(c => c.Enable).ToList();
}
protected override DownloadClientDefinition GetTemplate(IDownloadClient provider)
{
return GetAvailableProviders().Where(n => ((DownloadClientDefinition)n.Definition).Enable).ToList();
var definition = base.GetTemplate(provider);
definition.Protocol = provider.Protocol;
return definition;
}
}
}

@ -0,0 +1,30 @@
using NzbDrone.Core.Parser.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Download
{
public class DownloadClientItem
{
public String DownloadClient { get; set; }
public String DownloadClientId { get; set; }
public String Category { get; set; }
public String Title { get; set; }
public Int64 TotalSize { get; set; }
public Int64 RemainingSize { get; set; }
public TimeSpan? DownloadTime { get; set; }
public TimeSpan? RemainingTime { get; set; }
public String OutputPath { get; set; }
public String Message { get; set; }
public DownloadItemStatus Status { get; set; }
public Boolean IsEncrypted { get; set; }
public Boolean IsReadOnly { get; set; }
public RemoteEpisode RemoteEpisode { get; set; }
}
}

@ -1,10 +1,14 @@
using System.Linq;
using System;
using System.Linq;
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Core.Download
{
public interface IProvideDownloadClient
{
IDownloadClient GetDownloadClient();
IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol);
IEnumerable<IDownloadClient> GetDownloadClients();
}
public class DownloadClientProvider : IProvideDownloadClient
@ -16,9 +20,14 @@ namespace NzbDrone.Core.Download
_downloadClientFactory = downloadClientFactory;
}
public IDownloadClient GetDownloadClient()
public IDownloadClient GetDownloadClient(DownloadProtocol downloadProtocol)
{
return _downloadClientFactory.Enabled().FirstOrDefault();
return _downloadClientFactory.GetAvailableProviders().FirstOrDefault(v => v.Protocol == downloadProtocol);
}
public IEnumerable<IDownloadClient> GetDownloadClients()
{
return _downloadClientFactory.GetAvailableProviders();
}
}
}

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Download
{
public enum DownloadItemStatus
{
Queued = 0,
Paused = 1,
Downloading = 2,
Completed = 3,
Failed = 4
}
}

@ -19,7 +19,6 @@ namespace NzbDrone.Core.Download
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public DownloadService(IProvideDownloadClient downloadClientProvider,
IEventAggregator eventAggregator, Logger logger)
{
@ -34,15 +33,15 @@ namespace NzbDrone.Core.Download
Ensure.That(remoteEpisode.Episodes, () => remoteEpisode.Episodes).HasItems();
var downloadTitle = remoteEpisode.Release.Title;
var downloadClient = _downloadClientProvider.GetDownloadClient();
var downloadClient = _downloadClientProvider.GetDownloadClient(remoteEpisode.Release.DownloadProtocol);
if (downloadClient == null)
{
_logger.Warn("Download client isn't configured yet.");
_logger.Warn("{0} Download client isn't configured yet.", remoteEpisode.Release.DownloadProtocol);
return;
}
var downloadClientId = downloadClient.DownloadNzb(remoteEpisode);
var downloadClientId = downloadClient.Download(remoteEpisode);
var episodeGrabbedEvent = new EpisodeGrabbedEvent(remoteEpisode);
episodeGrabbedEvent.DownloadClient = downloadClient.GetType().Name;

@ -0,0 +1,212 @@
using System;
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;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Queue;
namespace NzbDrone.Core.Download
{
public interface IDownloadTrackingService
{
TrackedDownload[] GetTrackedDownloads();
TrackedDownload[] GetCompletedDownloads();
TrackedDownload[] GetQueuedDownloads();
}
public class DownloadTrackingService : IDownloadTrackingService, IExecute<CheckForFinishedDownloadCommand>, IHandle<ApplicationStartedEvent>, IHandle<EpisodeGrabbedEvent>
{
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService;
private readonly IFailedDownloadService _failedDownloadService;
private readonly ICompletedDownloadService _completedDownloadService;
private readonly Logger _logger;
private readonly ICached<TrackedDownload[]> _trackedDownloadCache;
public static string DOWNLOAD_CLIENT = "downloadClient";
public static string DOWNLOAD_CLIENT_ID = "downloadClientId";
public DownloadTrackingService(IProvideDownloadClient downloadClientProvider,
IHistoryService historyService,
IEventAggregator eventAggregator,
IConfigService configService,
ICacheManager cacheManager,
IFailedDownloadService failedDownloadService,
ICompletedDownloadService completedDownloadService,
Logger logger)
{
_downloadClientProvider = downloadClientProvider;
_historyService = historyService;
_eventAggregator = eventAggregator;
_configService = configService;
_failedDownloadService = failedDownloadService;
_completedDownloadService = completedDownloadService;
_logger = logger;
_trackedDownloadCache = cacheManager.GetCache<TrackedDownload[]>(GetType());
}
public TrackedDownload[] GetTrackedDownloads()
{
return _trackedDownloadCache.Get("tracked", () => new TrackedDownload[0]);
}
public TrackedDownload[] GetCompletedDownloads()
{
return GetTrackedDownloads()
.Where(v => v.State == TrackedDownloadState.Downloading && v.DownloadItem.Status == DownloadItemStatus.Completed)
.ToArray();
}
public TrackedDownload[] GetQueuedDownloads()
{
return _trackedDownloadCache.Get("queued", () =>
{
UpdateTrackedDownloads();
return FilterQueuedDownloads(GetTrackedDownloads());
}, TimeSpan.FromSeconds(5.0));
}
private TrackedDownload[] FilterQueuedDownloads(IEnumerable<TrackedDownload> trackedDownloads)
{
var enabledFailedDownloadHandling = _configService.EnableFailedDownloadHandling;
var enabledCompletedDownloadHandling = _configService.EnableCompletedDownloadHandling;
return trackedDownloads
.Where(v => v.State == TrackedDownloadState.Downloading)
.Where(v =>
v.DownloadItem.Status == DownloadItemStatus.Queued ||
v.DownloadItem.Status == DownloadItemStatus.Paused ||
v.DownloadItem.Status == DownloadItemStatus.Downloading ||
v.DownloadItem.Status == DownloadItemStatus.Failed && enabledFailedDownloadHandling ||
v.DownloadItem.Status == DownloadItemStatus.Completed && enabledCompletedDownloadHandling)
.ToArray();
}
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
{
return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID)))
.ToList();
}
private Boolean UpdateTrackedDownloads()
{
var downloadClients = _downloadClientProvider.GetDownloadClients();
var oldTrackedDownloads = GetTrackedDownloads().ToDictionary(v => v.TrackingId);
var newTrackedDownloads = new List<TrackedDownload>();
var stateChanged = false;
foreach (var downloadClient in downloadClients)
{
var downloadClientHistory = downloadClient.GetItems().ToList();
foreach (var downloadItem in downloadClientHistory)
{
var trackingId = String.Format("{0}-{1}", downloadClient.Definition.Id, downloadItem.DownloadClientId);
TrackedDownload trackedDownload;
if (!oldTrackedDownloads.TryGetValue(trackingId, out trackedDownload))
{
trackedDownload = new TrackedDownload
{
TrackingId = trackingId,
DownloadClient = downloadClient.Definition.Id,
StartedTracking = DateTime.UtcNow,
State = TrackedDownloadState.Unknown
};
_logger.Trace("Started tracking download from history: {0}: {1}", trackedDownload.TrackingId, downloadItem.Title);
stateChanged = true;
}
trackedDownload.DownloadItem = downloadItem;
newTrackedDownloads.Add(trackedDownload);
}
}
foreach (var downloadItem in oldTrackedDownloads.Values.Except(newTrackedDownloads))
{
if (downloadItem.State != TrackedDownloadState.Removed)
{
downloadItem.State = TrackedDownloadState.Removed;
stateChanged = true;
_logger.Debug("Item removed from download client by user: {0}: {1}", downloadItem.TrackingId, downloadItem.DownloadItem.Title);
}
_logger.Trace("Stopped tracking download: {0}: {1}", downloadItem.TrackingId, downloadItem.DownloadItem.Title);
}
_trackedDownloadCache.Set("tracked", newTrackedDownloads.ToArray());
return stateChanged;
}
private void ProcessTrackedDownloads()
{
var grabbedHistory = _historyService.Grabbed();
var failedHistory = _historyService.Failed();
var importedHistory = _historyService.Imported();
var stateChanged = UpdateTrackedDownloads();
var downloadClients = _downloadClientProvider.GetDownloadClients();
var trackedDownloads = GetTrackedDownloads();
foreach (var trackedDownload in trackedDownloads)
{
var downloadClient = downloadClients.Single(v => v.Definition.Id == trackedDownload.DownloadClient);
var state = trackedDownload.State;
if (trackedDownload.State == TrackedDownloadState.Unknown)
{
trackedDownload.State = TrackedDownloadState.Downloading;
}
_failedDownloadService.CheckForFailedItem(downloadClient, trackedDownload, grabbedHistory, failedHistory);
_completedDownloadService.CheckForCompletedItem(downloadClient, trackedDownload, grabbedHistory, importedHistory);
if (state != trackedDownload.State)
{
stateChanged = true;
}
}
_trackedDownloadCache.Set("queued", FilterQueuedDownloads(trackedDownloads), TimeSpan.FromSeconds(5.0));
if (stateChanged)
{
_eventAggregator.PublishEvent(new UpdateQueueEvent());
}
}
public void Execute(CheckForFinishedDownloadCommand message)
{
ProcessTrackedDownloads();
}
public void Handle(ApplicationStartedEvent message)
{
ProcessTrackedDownloads();
}
public void Handle(EpisodeGrabbedEvent message)
{
ProcessTrackedDownloads();
}
}
}

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Download.Events
{
public class DownloadFailedEvent : IEvent
{
public Int32 SeriesId { get; set; }
public List<Int32> EpisodeIds { get; set; }
public QualityModel Quality { get; set; }
public String SourceTitle { get; set; }
public String DownloadClient { get; set; }
public String DownloadClientId { get; set; }
public String Message { get; set; }
}
}

@ -1,18 +0,0 @@
using System;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Events
{
public class EpisodeGrabbedEvent : IEvent
{
public RemoteEpisode Episode { get; private set; }
public String DownloadClient { get; set; }
public String DownloadClientId { get; set; }
public EpisodeGrabbedEvent(RemoteEpisode episode)
{
Episode = episode;
}
}
}

@ -1,11 +0,0 @@
using System;
namespace NzbDrone.Core.Download
{
public class FailedDownload
{
public HistoryItem DownloadClientHistoryItem { get; set; }
public DateTime LastRetry { get; set; }
public Int32 RetryCount { get; set; }
}
}

@ -14,35 +14,25 @@ namespace NzbDrone.Core.Download
public interface IFailedDownloadService
{
void MarkAsFailed(int historyId);
void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> failedHistory);
}
public class FailedDownloadService : IFailedDownloadService, IExecute<CheckForFailedDownloadCommand>
public class FailedDownloadService : IFailedDownloadService
{
private readonly IProvideDownloadClient _downloadClientProvider;
private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService;
private readonly Logger _logger;
private readonly ICached<FailedDownload> _failedDownloads;
private static string DOWNLOAD_CLIENT = "downloadClient";
private static string DOWNLOAD_CLIENT_ID = "downloadClientId";
public FailedDownloadService(IProvideDownloadClient downloadClientProvider,
IHistoryService historyService,
public FailedDownloadService(IHistoryService historyService,
IEventAggregator eventAggregator,
IConfigService configService,
ICacheManager cacheManager,
Logger logger)
{
_downloadClientProvider = downloadClientProvider;
_historyService = historyService;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
_failedDownloads = cacheManager.GetCache<FailedDownload>(GetType());
}
public void MarkAsFailed(int historyId)
@ -51,149 +41,92 @@ namespace NzbDrone.Core.Download
PublishDownloadFailedEvent(new List<History.History> { item }, "Manually marked as failed");
}
private void CheckQueue(List<History.History> grabbedHistory, List<History.History> failedHistory)
public void CheckForFailedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> grabbedHistory, List<History.History> failedHistory)
{
var downloadClient = GetDownloadClient();
if (downloadClient == null)
{
return;
}
var downloadClientQueue = downloadClient.GetQueue().ToList();
var failedItems = downloadClientQueue.Where(q => q.Title.StartsWith("ENCRYPTED / ")).ToList();
if (!failedItems.Any())
if (!_configService.EnableFailedDownloadHandling)
{
_logger.Debug("Yay! No encrypted downloads");
return;
}
foreach (var failedItem in failedItems)
if (trackedDownload.DownloadItem.IsEncrypted && trackedDownload.State == TrackedDownloadState.Downloading)
{
var failedLocal = failedItem;
var historyItems = GetHistoryItems(grabbedHistory, failedLocal.Id);
var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);
if (!historyItems.Any())
if (!grabbedItems.Any())
{
_logger.Debug("Unable to find matching history item");
continue;
_logger.Debug("Download was not grabbed by drone, ignoring.");
return;
}
if (failedHistory.Any(h => failedLocal.Id.Equals(h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID))))
trackedDownload.State = TrackedDownloadState.DownloadFailed;
var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId);
if (failedItems.Any())
{
_logger.Debug("Already added to history as failed");
continue;
}
PublishDownloadFailedEvent(historyItems, "Encrypted download detected");
if (_configService.RemoveFailedDownloads)
else
{
_logger.Info("Removing encrypted download from queue: {0}", failedItem.Title.Replace("ENCRYPTED / ", ""));
downloadClient.RemoveFromQueue(failedItem.Id);
PublishDownloadFailedEvent(grabbedItems, "Encrypted download detected");
}
}
}
private void CheckHistory(List<History.History> grabbedHistory, List<History.History> failedHistory)
{
var downloadClient = GetDownloadClient();
if (downloadClient == null)
if (trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed && trackedDownload.State == TrackedDownloadState.Downloading)
{
return;
}
var grabbedItems = GetHistoryItems(grabbedHistory, trackedDownload.DownloadItem.DownloadClientId);
var downloadClientHistory = downloadClient.GetHistory(0, 20).ToList();
var failedItems = downloadClientHistory.Where(h => h.Status == HistoryStatus.Failed).ToList();
if (!failedItems.Any())
{
_logger.Debug("Yay! No failed downloads");
return;
}
foreach (var failedItem in failedItems)
{
var failedLocal = failedItem;
var historyItems = GetHistoryItems(grabbedHistory, failedLocal.Id);
if (!historyItems.Any())
if (!grabbedItems.Any())
{
_logger.Debug("Unable to find matching history item");
continue;
_logger.Debug("Download was not grabbed by drone, ignoring.");
return;
}
//TODO: Make this more configurable (ignore failure reasons) to support changes and other failures that should be ignored
if (failedLocal.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))
{
_logger.Debug("Failed due to lack of disk space, do not blacklist");
continue;
return;
}
if (FailedDownloadForRecentRelease(failedItem, historyItems))
if (FailedDownloadForRecentRelease(downloadClient, trackedDownload, grabbedItems))
{
_logger.Debug("Recent release Failed, do not blacklist");
continue;
return;
}
if (failedHistory.Any(h => failedLocal.Id.Equals(h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID))))
trackedDownload.State = TrackedDownloadState.DownloadFailed;
var failedItems = GetHistoryItems(failedHistory, trackedDownload.DownloadItem.DownloadClientId);
if (failedItems.Any())
{
_logger.Debug("Already added to history as failed");
continue;
}
PublishDownloadFailedEvent(historyItems, failedItem.Message);
if (_configService.RemoveFailedDownloads)
else
{
_logger.Info("Removing failed download from history: {0}", failedItem.Title);
downloadClient.RemoveFromHistory(failedItem.Id);
PublishDownloadFailedEvent(grabbedItems, trackedDownload.DownloadItem.Message);
}
}
}
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
{
return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID)))
.ToList();
}
private void PublishDownloadFailedEvent(List<History.History> historyItems, string message)
{
var historyItem = historyItems.First();
var downloadFailedEvent = new DownloadFailedEvent
{
SeriesId = historyItem.SeriesId,
EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
Quality = historyItem.Quality,
SourceTitle = historyItem.SourceTitle,
DownloadClient = historyItem.Data.GetValueOrDefault(DOWNLOAD_CLIENT),
DownloadClientId = historyItem.Data.GetValueOrDefault(DOWNLOAD_CLIENT_ID),
Message = message
};
downloadFailedEvent.Data = downloadFailedEvent.Data.Merge(historyItem.Data);
_eventAggregator.PublishEvent(downloadFailedEvent);
}
private IDownloadClient GetDownloadClient()
{
var downloadClient = _downloadClientProvider.GetDownloadClient();
if (downloadClient == null)
if (_configService.RemoveFailedDownloads && trackedDownload.State == TrackedDownloadState.DownloadFailed)
{
_logger.Debug("No download client is configured");
}
try
{
_logger.Info("Removing failed download from client: {0}", trackedDownload.DownloadItem.Title);
downloadClient.RemoveItem(trackedDownload.DownloadItem.DownloadClientId);
return downloadClient;
trackedDownload.State = TrackedDownloadState.Removed;
}
catch (NotSupportedException)
{
_logger.Debug("Removing item not supported by your download client");
}
}
}
private bool FailedDownloadForRecentRelease(HistoryItem failedDownloadHistoryItem, List<History.History> matchingHistoryItems)
private bool FailedDownloadForRecentRelease(IDownloadClient downloadClient, TrackedDownload trackedDownload, List<History.History> matchingHistoryItems)
{
double ageHours;
@ -209,31 +142,23 @@ namespace NzbDrone.Core.Download
return false;
}
var tracked = _failedDownloads.Get(failedDownloadHistoryItem.Id, () => new FailedDownload
{
DownloadClientHistoryItem = failedDownloadHistoryItem,
LastRetry = DateTime.UtcNow
}
);
if (tracked.RetryCount >= _configService.BlacklistRetryLimit)
if (trackedDownload.RetryCount >= _configService.BlacklistRetryLimit)
{
_logger.Debug("Retry limit reached");
return false;
}
if (tracked.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow)
if (trackedDownload.RetryCount == 0 || trackedDownload.LastRetry.AddMinutes(_configService.BlacklistRetryInterval) < DateTime.UtcNow)
{
_logger.Debug("Retrying failed release");
tracked.LastRetry = DateTime.UtcNow;
tracked.RetryCount++;
trackedDownload.LastRetry = DateTime.UtcNow;
trackedDownload.RetryCount++;
try
{
GetDownloadClient().RetryDownload(failedDownloadHistoryItem.Id);
downloadClient.RetryDownload(trackedDownload.DownloadItem.DownloadClientId);
}
catch (NotImplementedException ex)
catch (NotSupportedException ex)
{
_logger.Debug("Retrying failed downloads is not supported by your download client");
return false;
@ -243,19 +168,30 @@ namespace NzbDrone.Core.Download
return true;
}
public void Execute(CheckForFailedDownloadCommand message)
private List<History.History> GetHistoryItems(List<History.History> grabbedHistory, string downloadClientId)
{
if (!_configService.EnableFailedDownloadHandling)
return grabbedHistory.Where(h => downloadClientId.Equals(h.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID)))
.ToList();
}
private void PublishDownloadFailedEvent(List<History.History> historyItems, string message)
{
var historyItem = historyItems.First();
var downloadFailedEvent = new DownloadFailedEvent
{
_logger.Debug("Failed Download Handling is not enabled");
return;
}
SeriesId = historyItem.SeriesId,
EpisodeIds = historyItems.Select(h => h.EpisodeId).ToList(),
Quality = historyItem.Quality,
SourceTitle = historyItem.SourceTitle,
DownloadClient = historyItem.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT),
DownloadClientId = historyItem.Data.GetValueOrDefault(DownloadTrackingService.DOWNLOAD_CLIENT_ID),
Message = message
};
var grabbedHistory = _historyService.Grabbed();
var failedHistory = _historyService.Failed();
downloadFailedEvent.Data = downloadFailedEvent.Data.Merge(historyItem.Data);
CheckQueue(grabbedHistory, failedHistory);
CheckHistory(grabbedHistory, failedHistory);
_eventAggregator.PublishEvent(downloadFailedEvent);
}
}
}

@ -1,22 +0,0 @@
using System;
namespace NzbDrone.Core.Download
{
public class HistoryItem
{
public String Id { get; set; }
public String Title { get; set; }
public String Size { get; set; }
public String Category { get; set; }
public Int32 DownloadTime { get; set; }
public String Storage { get; set; }
public String Message { get; set; }
public HistoryStatus Status { get; set; }
}
public enum HistoryStatus
{
Completed = 0,
Failed = 1
}
}

@ -1,4 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
@ -6,11 +7,11 @@ namespace NzbDrone.Core.Download
{
public interface IDownloadClient : IProvider
{
string DownloadNzb(RemoteEpisode remoteEpisode);
IEnumerable<QueueItem> GetQueue();
IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0);
void RemoveFromQueue(string id);
void RemoveFromHistory(string id);
DownloadProtocol Protocol { get; }
string Download(RemoteEpisode remoteEpisode);
IEnumerable<DownloadClientItem> GetItems();
void RemoveItem(string id);
void RetryDownload(string id);
void Test();
}

@ -1,16 +0,0 @@
using System;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
public class QueueItem
{
public string Id { get; set; }
public decimal Size { get; set; }
public string Title { get; set; }
public decimal Sizeleft { get; set; }
public TimeSpan Timeleft { get; set; }
public String Status { get; set; }
public RemoteEpisode RemoteEpisode { get; set; }
}
}

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Download
{
public class TrackedDownload
{
public String TrackingId { get; set; }
public Int32 DownloadClient { get; set; }
public DownloadClientItem DownloadItem { get; set; }
public TrackedDownloadState State { get; set; }
public DateTime StartedTracking { get; set; }
public DateTime LastRetry { get; set; }
public Int32 RetryCount { get; set; }
}
public enum TrackedDownloadState
{
Unknown,
Downloading,
Imported,
DownloadFailed,
Removed
}
}

@ -1,4 +1,5 @@
using System;
using System.Linq;
using NzbDrone.Core.Download;
namespace NzbDrone.Core.HealthCheck.Checks
@ -14,16 +15,19 @@ namespace NzbDrone.Core.HealthCheck.Checks
public override HealthCheck Check()
{
var downloadClient = _downloadClientProvider.GetDownloadClient();
var downloadClients = _downloadClientProvider.GetDownloadClients();
if (downloadClient == null)
if (downloadClients.Count() == 0)
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, "No download client is available");
}
try
{
downloadClient.GetQueue();
foreach (var downloadClient in downloadClients)
{
downloadClient.GetItems();
}
}
catch (Exception)
{

@ -23,7 +23,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
if (droneFactoryFolder.IsNullOrWhiteSpace())
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Drone factory folder is not configured");
return new HealthCheck(GetType());
}
if (!_diskProvider.FolderExists(droneFactoryFolder))

@ -0,0 +1,44 @@
using System;
using System.IO;
using System.Linq;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class ImportMechanismCheck : HealthCheckBase
{
private readonly IConfigService _configService;
private readonly IDownloadTrackingService _downloadTrackingService;
public ImportMechanismCheck(IConfigService configService, IDownloadTrackingService downloadTrackingService)
{
_configService = configService;
_downloadTrackingService = downloadTrackingService;
}
public override HealthCheck Check()
{
if (!_configService.IsDefined("EnableCompletedDownloadHandling"))
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Completed Download Handling is disabled");
}
var droneFactoryFolder = _configService.DownloadedEpisodesFolder;
if (!_configService.EnableCompletedDownloadHandling && droneFactoryFolder.IsNullOrWhiteSpace())
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Enable Completed Download Handling or configure Drone factory");
}
if (_configService.EnableCompletedDownloadHandling && !droneFactoryFolder.IsNullOrWhiteSpace() && _downloadTrackingService.GetCompletedDownloads().Any(v => droneFactoryFolder.PathEquals(v.DownloadItem.OutputPath) || droneFactoryFolder.IsParentPath(v.DownloadItem.OutputPath)))
{
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Download Client has history items in Drone Factory conflicting with Completed Download Handling");
}
return new HealthCheck(GetType());
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save