New: Drone now uses the Download Client API to determine if a download is ready for import. (User configuration is required to replace the drone factory with this feature)

pull/2/head
Taloth Saldono 11 years ago
parent dcb586b937
commit 2035fe8578

@ -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;

@ -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)

@ -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);

@ -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,380 @@
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;
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())
.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);
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);
VerifyNoImports();
}
[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,106 @@
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;
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<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,207 @@
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"
};
_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",
ScriptStatus = "NONE"
};
}
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);
}
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.FileSizeLo;
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);
}
}
}

@ -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.ToList());
}
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" />

@ -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); }

@ -11,15 +11,20 @@ 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; }
//Failed Download Handling (Download client)
//Completed/Failed Download Handling (Download client)
Boolean EnableCompletedDownloadHandling { get; set; }
Boolean RemoveCompletedDownloads { get; set; }
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; }

@ -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);
}
}
}

@ -5,15 +5,18 @@ 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 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 ActiveDownloads { get; set; }
public List<NzbgetParameter> Parameters { get; set; }
}
}

@ -4,6 +4,7 @@ using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -14,22 +15,27 @@ 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,
IParsingService parsingService,
IHttpProvider httpProvider,
Logger logger)
: base(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,7 +54,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
}
}
public override IEnumerable<QueueItem> GetQueue()
private IEnumerable<DownloadClientItem> GetQueue()
{
List<NzbgetQueueItem> queue;
@ -59,36 +65,42 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
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>();
foreach (var item in queue)
{
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;
queueItem.TotalSize = MakeInt64(item.FileSizeHi, item.FileSizeLo);
queueItem.RemainingSize = MakeInt64(item.RemainingSizeHi, item.RemainingSizeLo);
queueItem.Category = item.Category;
if (queueItem.TotalSize == MakeInt64(item.PausedSizeHi, item.PausedSizeLo))
{
queueItem.Status = DownloadItemStatus.Paused;
}
else if (item.ActiveDownloads == 0 && queueItem.RemainingSize != 0)
{
queueItem.Status = DownloadItemStatus.Queued;
}
else
{
queueItem.Status = DownloadItemStatus.Downloading;
}
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
if (remoteEpisode.Series == null) continue;
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;
@ -99,10 +111,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
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>();
var successStatues = new[] {"SUCCESS", "NONE"};
foreach (var item in history)
@ -110,15 +122,15 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
var droneParameter = item.Parameters.SingleOrDefault(p => p.Name == "drone");
var status = successStatues.Contains(item.ParStatus) &&
successStatues.Contains(item.ScriptStatus)
? HistoryStatus.Completed
: HistoryStatus.Failed;
? DownloadItemStatus.Completed
: DownloadItemStatus.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;
@ -129,12 +141,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);
}
@ -161,5 +181,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;
}
}
}

@ -5,11 +5,11 @@ 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 ScriptStatus { get; set; }
public String DestDir { 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; }
}
}

@ -7,8 +7,10 @@ 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
@ -21,22 +23,34 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
private static readonly Logger logger = NzbDroneLogger.GetLogger();
public Pneumatic(IConfigService configService, IHttpProvider httpProvider,
IDiskProvider diskProvider)
public Pneumatic(IConfigService configService,
IHttpProvider httpProvider,
IDiskProvider diskProvider,
IParsingService parsingService,
Logger logger)
: base(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);
@ -63,27 +77,19 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
}
}
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()

@ -6,7 +6,7 @@ using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients
namespace NzbDrone.Core.Download.Clients.Pneumatic
{
public class FolderSettingsValidator : AbstractValidator<FolderSettings>
{

@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -15,25 +15,27 @@ 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,
Logger logger)
: base(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 +56,104 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
}
}
public override IEnumerable<QueueItem> GetQueue()
private IEnumerable<DownloadClientItem> GetQueue()
{
return _queueCache.Get("queue", () =>
SabnzbdQueue sabQueue;
try
{
SabnzbdQueue sabQueue;
sabQueue = _proxy.GetQueue(0, 0, Settings);
}
catch (DownloadClientException ex)
{
_logger.ErrorException(ex.Message, ex);
return Enumerable.Empty<DownloadClientItem>();
}
var queueItems = new List<DownloadClientItem>();
try
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;
}
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, 0, 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 +161,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,154 @@
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.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,
IParsingService parsingService,
IHttpProvider httpProvider,
Logger logger)
: base(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.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(Settings.NzbFolder);
PerformTest(Settings.WatchFolder);
}
}
}

@ -0,0 +1,35 @@
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();
}
}
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,148 @@
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 Logger _logger;
public CompletedDownloadService(IEventAggregator eventAggregator,
IConfigService configService,
IDiskProvider diskProvider,
IDownloadedEpisodesImportService downloadedEpisodesImportService,
Logger logger)
{
_eventAggregator = eventAggregator;
_configService = configService;
_diskProvider = diskProvider;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
_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
{
_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,20 @@
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 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()
{
private readonly IParsingService _parsingService;
protected readonly Logger _logger;
public Type ConfigContract
{
get
@ -33,17 +41,39 @@ namespace NzbDrone.Core.Download
}
}
protected DownloadClientBase(IParsingService parsingService, Logger logger)
{
_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,29 @@
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 long TotalSize { get; set; }
public long 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 bool IsEncrypted { get; set; }
public bool 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,209 @@
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
{
List<TrackedDownload> GetTrackedDownloads();
List<TrackedDownload> GetCompletedDownloads();
List<TrackedDownload> GetQueuedDownloads();
}
public class DownloadTrackingService : IDownloadTrackingService, IExecute<CheckForFinishedDownloadCommand>, IHandle<ApplicationStartedEvent>
{
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> _trackedDownloads;
private readonly ICached<List<TrackedDownload>> _queuedDownloads;
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;
_trackedDownloads = cacheManager.GetCache<TrackedDownload>(GetType());
_queuedDownloads = cacheManager.GetCache<List<TrackedDownload>>(GetType(), "queued");
}
public List<TrackedDownload> GetTrackedDownloads()
{
return _trackedDownloads.Values.ToList();
}
public List<TrackedDownload> GetCompletedDownloads()
{
return _trackedDownloads.Values.Where(v => v.State == TrackedDownloadState.Downloading && v.DownloadItem.Status == DownloadItemStatus.Completed).ToList();
}
public List<TrackedDownload> GetQueuedDownloads()
{
return _queuedDownloads.Get("queued", () =>
{
UpdateTrackedDownloads();
var enabledFailedDownloadHandling = _configService.EnableFailedDownloadHandling;
var enabledCompletedDownloadHandling = _configService.EnableCompletedDownloadHandling;
return _trackedDownloads.Values
.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)
.ToList();
}, TimeSpan.FromSeconds(5.0));
}
private TrackedDownload GetTrackedDownload(IDownloadClient downloadClient, DownloadClientItem queueItem)
{
var id = String.Format("{0}-{1}", downloadClient.Definition.Id, queueItem.DownloadClientId);
var trackedDownload = _trackedDownloads.Get(id, () => new TrackedDownload
{
TrackingId = id,
DownloadClient = downloadClient.Definition.Id,
StartedTracking = DateTime.UtcNow,
State = TrackedDownloadState.Unknown
});
trackedDownload.DownloadItem = queueItem;
return trackedDownload;
}
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 = new HashSet<TrackedDownload>(_trackedDownloads.Values);
var newTrackedDownloads = new HashSet<TrackedDownload>();
var stateChanged = false;
foreach (var downloadClient in downloadClients)
{
var downloadClientHistory = downloadClient.GetItems().Select(v => GetTrackedDownload(downloadClient, v)).ToList();
foreach (var trackedDownload in downloadClientHistory)
{
if (!oldTrackedDownloads.Contains(trackedDownload))
{
_logger.Trace("Started tracking download from history: {0}", trackedDownload.TrackingId);
stateChanged = true;
}
newTrackedDownloads.Add(trackedDownload);
}
}
foreach (var item in oldTrackedDownloads.Except(newTrackedDownloads))
{
if (item.State != TrackedDownloadState.Removed)
{
item.State = TrackedDownloadState.Removed;
stateChanged = true;
_logger.Debug("Item removed from download client by user: {0}", item.TrackingId);
}
}
foreach (var item in newTrackedDownloads.Union(oldTrackedDownloads).Where(v => v.State == TrackedDownloadState.Removed))
{
_trackedDownloads.Remove(item.TrackingId);
_logger.Trace("Stopped tracking download: {0}", item.TrackingId);
}
_queuedDownloads.Clear();
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 = _trackedDownloads.Values.ToArray();
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;
}
}
if (stateChanged)
{
_eventAggregator.PublishEvent(new UpdateQueueEvent());
}
}
public void Execute(CheckForFinishedDownloadCommand message)
{
ProcessTrackedDownloads();
}
public void Handle(ApplicationStartedEvent 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());
}
}
}

@ -16,6 +16,7 @@ namespace NzbDrone.Core.History
List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType);
List<History> Failed();
List<History> Grabbed();
List<History> Imported();
History MostRecentForEpisode(int episodeId);
List<History> FindBySourceTitle(string sourceTitle);
}
@ -62,6 +63,11 @@ namespace NzbDrone.Core.History
return Query.Where(h => h.EventType == HistoryEventType.Grabbed);
}
public List<History> Imported()
{
return Query.Where(h => h.EventType == HistoryEventType.DownloadFolderImported);
}
public History MostRecentForEpisode(int episodeId)
{
return Query.Where(h => h.EpisodeId == episodeId)

@ -21,6 +21,7 @@ namespace NzbDrone.Core.History
List<History> BetweenDates(DateTime startDate, DateTime endDate, HistoryEventType eventType);
List<History> Failed();
List<History> Grabbed();
List<History> Imported();
History MostRecentForEpisode(int episodeId);
History Get(int id);
List<History> FindBySourceTitle(string sourceTitle);
@ -62,6 +63,11 @@ namespace NzbDrone.Core.History
return _historyRepository.Grabbed();
}
public List<History> Imported()
{
return _historyRepository.Imported();
}
public History MostRecentForEpisode(int episodeId)
{
return _historyRepository.MostRecentForEpisode(episodeId);
@ -149,6 +155,8 @@ namespace NzbDrone.Core.History
//history.Data.Add("FileId", message.ImportedEpisode.Id.ToString());
history.Data.Add("DroppedPath", message.EpisodeInfo.Path);
history.Data.Add("ImportedPath", message.ImportedEpisode.Path);
history.Data.Add("DownloadClient", message.DownloadClient);
history.Data.Add("DownloadClientId", message.DownloadClientId);
_historyRepository.Insert(history);
}

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Indexers
{
public enum DownloadProtocol
{
Usenet = 1,
Torrent = 2
}
}

@ -1,8 +0,0 @@
namespace NzbDrone.Core.Indexers
{
public enum DownloadProtocols
{
Nzb = 0,
Torrent =1
}
}

@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers.Eztv
{
public class Eztv : IndexerBase<NullConfig>
{
public override DownloadProtocol Protocol
{
get
{
return DownloadProtocol.Torrent;
}
}
public override bool SupportsPaging
{
get
{
return false;
}
}
public override IParseFeed Parser
{
get
{
return new BasicTorrentRssParser();
}
}
public override IEnumerable<string> RecentFeed
{
get
{
yield return "http://www.ezrss.it/feed/";
}
}
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber)
{
yield return string.Format("http://www.ezrss.it/search/index.php?show_name={0}&season={1}&episode={2}&mode=rss", seriesTitle, seasonNumber, episodeNumber);
}
public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset)
{
yield return string.Format("http://www.ezrss.it/search/index.php?show_name={0}&season={1}&mode=rss", seriesTitle, seasonNumber);
}
public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date)
{
//EZTV doesn't support searching based on actual episode airdate. they only support release date.
return new string[0];
}
public override IEnumerable<string> GetSearchUrls(string query, int offset)
{
return new List<string>();
}
}
}

@ -8,6 +8,7 @@ namespace NzbDrone.Core.Indexers
{
IParseFeed Parser { get; }
DownloadProtocol Protocol { get; }
Int32 SupportedPageSize { get; }
Boolean SupportsPaging { get; }
Boolean SupportsSearching { get; }

@ -34,8 +34,10 @@ namespace NzbDrone.Core.Indexers
public abstract DownloadProtocol Protocol { get; }
public abstract bool SupportsPaging { get; }
public virtual bool SupportsSearching { get { return true; } }
public virtual Boolean SupportsFeed { get { return true; } }
public virtual Int32 SupportedPageSize { get { return 0; } }
public bool SupportsPaging { get { return SupportedPageSize > 0; } }
public virtual Boolean SupportsSearching { get { return true; } }
protected TSettings Settings
{
@ -58,10 +60,4 @@ namespace NzbDrone.Core.Indexers
return Definition.Name;
}
}
public enum DownloadProtocol
{
Usenet = 1,
Torrent = 2
}
}

@ -5,5 +5,7 @@ namespace NzbDrone.Core.Indexers
public class IndexerDefinition : ProviderDefinition
{
public bool Enable { get; set; }
public DownloadProtocol Protocol { get; set; }
}
}

@ -31,17 +31,7 @@ namespace NzbDrone.Core.Indexers
protected override void InitializeProviders()
{
var definitions = _providers.Where(c => c.Protocol == DownloadProtocol.Usenet)
.SelectMany(indexer => indexer.DefaultDefinitions);
var currentProviders = All();
var newProviders = definitions.Where(def => currentProviders.All(c => c.Implementation != def.Implementation)).ToList();
if (newProviders.Any())
{
_providerRepository.InsertMany(newProviders.Cast<IndexerDefinition>().ToList());
}
}
protected override List<IndexerDefinition> Active()
@ -59,5 +49,14 @@ namespace NzbDrone.Core.Indexers
return base.Create(definition);
}
protected override IndexerDefinition GetTemplate(IIndexer provider)
{
var definition = base.GetTemplate(provider);
definition.Protocol = provider.Protocol;
return definition;
}
}
}

@ -63,11 +63,9 @@ namespace NzbDrone.Core.Indexers
_logger.Info("{0} offset {1}. Found {2}", indexer, searchCriteria, result.Count);
if (result.Count > 90 &&
offset < 900 &&
indexer.SupportsPaging)
if (indexer.SupportsPaging && result.Count >= indexer.SupportedPageSize && offset < 900)
{
result.AddRange(Fetch(indexer, searchCriteria, offset + 100));
result.AddRange(Fetch(indexer, searchCriteria, offset + indexer.SupportedPageSize));
}
return result;
@ -152,7 +150,11 @@ namespace NzbDrone.Core.Indexers
}
}
result.ForEach(c => c.Indexer = indexer.Definition.Name);
result.ForEach(c =>
{
c.Indexer = indexer.Definition.Name;
c.DownloadProtocol = indexer.Protocol;
});
return result;
}

@ -7,6 +7,9 @@ namespace NzbDrone.Core.Indexers.Newznab
{
public class Newznab : IndexerBase<NewznabSettings>
{
public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } }
public override Int32 SupportedPageSize { get { return 100; } }
public override IParseFeed Parser
{
get
@ -72,14 +75,6 @@ namespace NzbDrone.Core.Indexers.Newznab
return settings;
}
public override bool SupportsPaging
{
get
{
return true;
}
}
public override IEnumerable<string> RecentFeed
{
get
@ -140,14 +135,6 @@ namespace NzbDrone.Core.Indexers.Newznab
return RecentFeed.Select(url => String.Format("{0}&limit=100&q={1}&season={2}&offset={3}", url, NewsnabifyTitle(seriesTitle), seasonNumber, offset));
}
public override DownloadProtocol Protocol
{
get
{
return DownloadProtocol.Usenet;
}
}
private static string NewsnabifyTitle(string title)
{
return title.Replace("+", "%20");

@ -5,13 +5,7 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
public class Omgwtfnzbs : IndexerBase<OmgwtfnzbsSettings>
{
public override DownloadProtocol Protocol
{
get
{
return DownloadProtocol.Usenet;
}
}
public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } }
public override IParseFeed Parser
{
@ -25,7 +19,6 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
get
{
yield return String.Format("http://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user={0}&api={1}&eng=1",
Settings.Username, Settings.ApiKey);
}
@ -71,13 +64,5 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
return new List<string>();
}
public override bool SupportsPaging
{
get
{
return false;
}
}
}
}

@ -44,6 +44,7 @@ namespace NzbDrone.Core.Indexers
try
{
var reportInfo = ParseFeedItem(item.StripNameSpace(), url);
if (reportInfo != null)
{
reportInfo.DownloadUrl = GetNzbUrl(item);
@ -69,7 +70,7 @@ namespace NzbDrone.Core.Indexers
var reportInfo = CreateNewReleaseInfo();
reportInfo.Title = title;
reportInfo.PublishDate = item.PublishDate();
reportInfo.PublishDate = GetPublishDate(item);
reportInfo.DownloadUrl = GetNzbUrl(item);
reportInfo.InfoUrl = GetNzbInfoUrl(item);
@ -92,6 +93,11 @@ namespace NzbDrone.Core.Indexers
return item.Title();
}
protected virtual DateTime GetPublishDate(XElement item)
{
return item.PublishDate();
}
protected virtual string GetNzbUrl(XElement item)
{
return item.Links().First();

@ -6,29 +6,8 @@ namespace NzbDrone.Core.Indexers.Wombles
{
public class Wombles : IndexerBase<NullConfig>
{
public override DownloadProtocol Protocol
{
get
{
return DownloadProtocol.Usenet;
}
}
public override bool SupportsPaging
{
get
{
return false;
}
}
public override bool SupportsSearching
{
get
{
return false;
}
}
public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } }
public override bool SupportsSearching { get { return false; } }
public override IParseFeed Parser
{

@ -13,7 +13,7 @@ namespace NzbDrone.Core.Indexers
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger();
private static readonly Regex RemoveTimeZoneRegex = new Regex(@"\s[A-Z]{2,4}$", RegexOptions.Compiled);
public static readonly Regex RemoveTimeZoneRegex = new Regex(@"\s[A-Z]{2,4}$", RegexOptions.Compiled);
public static string Title(this XElement item)
{
@ -78,7 +78,7 @@ namespace NzbDrone.Core.Indexers
return long.Parse(item.TryGetValue("length"));
}
private static string TryGetValue(this XElement item, string elementName, string defaultValue = "")
public static string TryGetValue(this XElement item, string elementName, string defaultValue = "")
{
var element = item.Element(elementName);

@ -49,7 +49,7 @@ namespace NzbDrone.Core.Jobs
var defaultTasks = new[]
{
new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName},
new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFailedDownloadCommand).FullName},
new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName},
new ScheduledTask{ Interval = 1*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},

@ -95,7 +95,7 @@ namespace NzbDrone.Core.MediaFiles
decisionsStopwatch.Stop();
_logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed);
_importApprovedEpisodes.Import(decisions);
_importApprovedEpisodes.Import(decisions, false);
_logger.Info("Completed scanning disk for {0}", series.Title);
_eventAggregator.PublishEvent(new SeriesScannedEvent(series));

@ -14,10 +14,17 @@ using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Download;
namespace NzbDrone.Core.MediaFiles
{
public class DownloadedEpisodesImportService : IExecute<DownloadedEpisodesScanCommand>
public interface IDownloadedEpisodesImportService
{
List<ImportDecision> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem);
List<ImportDecision> ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem);
}
public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService, IExecute<DownloadedEpisodesScanCommand>
{
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
@ -50,9 +57,53 @@ namespace NzbDrone.Core.MediaFiles
_logger = logger;
}
public List<ImportDecision> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem)
{
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var series = _parsingService.GetSeries(cleanedUpName);
var quality = QualityParser.ParseQuality(cleanedUpName);
_logger.Debug("{0} folder quality: {1}", cleanedUpName, quality);
if (series == null)
{
_logger.Debug("Unknown Series {0}", cleanedUpName);
return new List<ImportDecision>();
}
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, true, quality);
var importedDecisions = _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
if (!downloadClientItem.IsReadOnly && importedDecisions.Any() && ShouldDeleteFolder(directoryInfo))
{
_logger.Debug("Deleting folder after importing valid files");
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
}
return importedDecisions;
}
public List<ImportDecision> ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem)
{
var series = _parsingService.GetSeries(Path.GetFileNameWithoutExtension(fileInfo.Name));
if (series == null)
{
_logger.Debug("Unknown Series for file: {0}", fileInfo.Name);
return new List<ImportDecision>();
}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, series, true, null);
var importedDecisions = _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
return importedDecisions;
}
private void ProcessDownloadedEpisodesFolder()
{
//TODO: We should also process the download client's category folder
var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder;
if (String.IsNullOrEmpty(downloadedEpisodesFolder))
@ -100,6 +151,15 @@ namespace NzbDrone.Core.MediaFiles
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
foreach (var videoFile in videoFiles)
{
if (_diskProvider.IsFileLocked(videoFile))
{
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
return new List<ImportDecision>();
}
}
return ProcessFiles(series, quality, videoFiles);
}

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

Loading…
Cancel
Save