Download clients now use thingy provider

pull/4/head
Mark McDowall 11 years ago
parent ba22600412
commit 606d78f5e1

@ -0,0 +1,18 @@
using NzbDrone.Core.Download;
namespace NzbDrone.Api.DownloadClient
{
public class DownloadClientModule : ProviderModuleBase<DownloadClientResource, IDownloadClient, DownloadClientDefinition>
{
public DownloadClientModule(IDownloadClientFactory downloadClientFactory)
: base(downloadClientFactory, "downloadclient")
{
}
protected override void Validate(DownloadClientDefinition definition)
{
if (!definition.Enable) return;
base.Validate(definition);
}
}
}

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

@ -0,0 +1,37 @@
using System.Collections.Generic;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Core.Download;
using Omu.ValueInjecter;
namespace NzbDrone.Api.DownloadClient
{
public class DownloadClientSchemaModule : NzbDroneRestModule<DownloadClientResource>
{
private readonly IDownloadClientFactory _notificationFactory;
public DownloadClientSchemaModule(IDownloadClientFactory notificationFactory)
: base("downloadclient/schema")
{
_notificationFactory = notificationFactory;
GetResourceAll = GetSchema;
}
private List<DownloadClientResource> GetSchema()
{
var notifications = _notificationFactory.Templates();
var result = new List<DownloadClientResource>(notifications.Count);
foreach (var notification in notifications)
{
var notificationResource = new DownloadClientResource();
notificationResource.InjectFrom(notification);
notificationResource.Fields = SchemaBuilder.ToSchema(notification.Settings);
result.Add(notificationResource);
}
return result;
}
}
}

@ -94,6 +94,8 @@
<Compile Include="Commands\CommandResource.cs" />
<Compile Include="Config\NamingConfigResource.cs" />
<Compile Include="Config\NamingModule.cs" />
<Compile Include="DownloadClient\DownloadClientModule.cs" />
<Compile Include="DownloadClient\DownloadClientResource.cs" />
<Compile Include="DiskSpace\DiskSpaceModule.cs" />
<Compile Include="DiskSpace\DiskSpaceResource.cs" />
<Compile Include="EpisodeFiles\EpisodeFileModule.cs" />
@ -122,6 +124,7 @@
<Compile Include="History\HistoryModule.cs" />
<Compile Include="Metadata\MetadataResource.cs" />
<Compile Include="Metadata\MetadataModule.cs" />
<Compile Include="Notifications\NotificationSchemaModule.cs" />
<Compile Include="ProviderResource.cs" />
<Compile Include="ProviderModuleBase.cs" />
<Compile Include="Indexers\IndexerSchemaModule.cs" />
@ -145,7 +148,7 @@
<Compile Include="Queue\QueueModule.cs" />
<Compile Include="Queue\QueueResource.cs" />
<Compile Include="ResourceChangeMessage.cs" />
<Compile Include="Notifications\NotificationSchemaModule.cs" />
<Compile Include="DownloadClient\DownloadClientSchemaModule.cs" />
<Compile Include="Notifications\NotificationModule.cs" />
<Compile Include="Notifications\NotificationResource.cs" />
<Compile Include="NzbDroneRestModule.cs" />

@ -105,16 +105,6 @@ namespace NzbDrone.Core.Test.Configuration
Subject.GetValue(key, value2).Should().Be(value2);
}
[Test]
public void updating_a_vakye_should_update_its_value()
{
Subject.SabHost = "Test";
Subject.SabHost.Should().Be("Test");
Subject.SabHost = "Test2";
Subject.SabHost.Should().Be("Test2");
}
[Test]
[Description("This test will use reflection to ensure each config property read/writes to a unique key")]
public void config_properties_should_write_and_read_using_same_key()

@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenSabnzbdDownloadClient()
{
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClient()).Returns(Mocker.Resolve<SabnzbdClient>());
.Setup(c => c.GetDownloadClient()).Returns(Mocker.Resolve<Sabnzbd>());
}
private void GivenMostRecentForEpisode(HistoryEventType eventType)

@ -56,9 +56,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.GetMock<IProvideDownloadClient>()
.Setup(s => s.GetDownloadClient())
.Returns(_downloadClient.Object);
_downloadClient.SetupGet(s => s.IsConfigured)
.Returns(true);
}
private void GivenEmptyQueue()

@ -4,8 +4,9 @@ using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
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;
@ -13,7 +14,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.DownloadClientTests
{
[TestFixture]
public class BlackholeProviderFixture : CoreTest<BlackholeProvider>
public class BlackholeProviderFixture : CoreTest<Blackhole>
{
private const string _nzbUrl = "http://www.nzbs.com/url";
private const string _title = "some_nzb_title";
@ -27,13 +28,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
_blackHoleFolder = @"c:\nzb\blackhole\".AsOsAgnostic();
_nzbPath = @"c:\nzb\blackhole\some_nzb_title.nzb".AsOsAgnostic();
Mocker.GetMock<IConfigService>().SetupGet(c => c.BlackholeFolder).Returns(_blackHoleFolder);
_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()

@ -3,17 +3,15 @@ using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
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.NzbgetProviderTests
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
public class DownloadNzbFixture : CoreTest
public class DownloadNzbFixture : CoreTest<Nzbget>
{
private const string _url = "http://www.nzbdrone.com";
private const string _title = "30.Rock.S01E01.Pilot.720p.hdtv";
@ -32,6 +30,17 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetProviderTests
.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]
@ -39,14 +48,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetProviderTests
{
var p = new object[] {"30.Rock.S01E01.Pilot.720p.hdtv.nzb", "TV", 50, false, "http://www.nzbdrone.com"};
Mocker.GetMock<INzbGetCommunicationProxy>()
.Setup(s => s.AddNzb(p))
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.AddNzb(It.IsAny<NzbgetSettings>(), p))
.Returns(true);
Mocker.Resolve<NzbgetClient>().DownloadNzb(_remoteEpisode);
Subject.DownloadNzb(_remoteEpisode);
Mocker.GetMock<INzbGetCommunicationProxy>()
.Verify(v => v.AddNzb(It.IsAny<object []>()), Times.Once());
Mocker.GetMock<INzbgetProxy>()
.Verify(v => v.AddNzb(It.IsAny<NzbgetSettings>(), It.IsAny<object []>()), Times.Once());
}
}
}

@ -5,40 +5,52 @@ 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.NzbgetProviderTests
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
public class QueueFixture : CoreTest<NzbgetClient>
public class QueueFixture : CoreTest<Nzbget>
{
private List<NzbGetQueueItem> _queue;
private List<NzbgetQueueItem> _queue;
[SetUp]
public void Setup()
{
_queue = Builder<NzbGetQueueItem>.CreateListOfSize(5)
_queue = Builder<NzbgetQueueItem>.CreateListOfSize(5)
.All()
.With(q => q.NzbName = "30.Rock.S01E01.Pilot.720p.hdtv.nzb")
.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<INzbGetCommunicationProxy>()
.Setup(s => s.GetQueue())
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetQueue(It.IsAny<NzbgetSettings>()))
.Returns(_queue);
}
private void WithEmptyQueue()
{
Mocker.GetMock<INzbGetCommunicationProxy>()
.Setup(s => s.GetQueue())
.Returns(new List<NzbGetQueueItem>());
Mocker.GetMock<INzbgetProxy>()
.Setup(s => s.GetQueue(It.IsAny<NzbgetSettings>()))
.Returns(new List<NzbgetQueueItem>());
}
[Test]

@ -6,7 +6,9 @@ using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.Clients.Pneumatic;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
@ -14,7 +16,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Download.DownloadClientTests
{
[TestFixture]
public class PneumaticProviderFixture : CoreTest<PneumaticClient>
public class PneumaticProviderFixture : CoreTest<Pneumatic>
{
private const string _nzbUrl = "http://www.nzbs.com/url";
private const string _title = "30.Rock.S01E05.hdtv.xvid-LoL";
@ -31,7 +33,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
_nzbPath = Path.Combine(_pneumaticFolder, _title + ".nzb").AsOsAgnostic();
_sabDrop = @"d:\unsorted tv\".AsOsAgnostic();
Mocker.GetMock<IConfigService>().SetupGet(c => c.PneumaticFolder).Returns(_pneumaticFolder);
Mocker.GetMock<IConfigService>().SetupGet(c => c.DownloadedEpisodesFolder).Returns(_sabDrop);
_remoteEpisode = new RemoteEpisode();
@ -41,6 +42,12 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests
_remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo();
_remoteEpisode.ParsedEpisodeInfo.FullSeason = false;
Subject.Definition = new DownloadClientDefinition();
Subject.Definition.Settings = new FolderSettings
{
Folder = _pneumaticFolder
};
}
private void WithExistingFile()

@ -1,191 +0,0 @@
using System;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Sabnzbd;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
{
[TestFixture]
public class SabProviderFixture : CoreTest<SabnzbdClient>
{
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;
[SetUp]
public void Setup()
{
var fakeConfig = Mocker.GetMock<IConfigService>();
fakeConfig.SetupGet(c => c.SabHost).Returns("192.168.5.55");
fakeConfig.SetupGet(c => c.SabPort).Returns(2222);
fakeConfig.SetupGet(c => c.SabApiKey).Returns("5c770e3197e4fe763423ee7c392c25d1");
fakeConfig.SetupGet(c => c.SabUsername).Returns("admin");
fakeConfig.SetupGet(c => c.SabPassword).Returns("pass");
fakeConfig.SetupGet(c => c.SabTvCategory).Returns("tv");
_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();
}
[Test]
public void should_be_able_to_get_categories_when_config_is_passed_in()
{
const string host = "192.168.5.22";
const int port = 1111;
const string apikey = "5c770e3197e4fe763423ee7c392c25d2";
const string username = "admin2";
const string password = "pass2";
Mocker.GetMock<IHttpProvider>(MockBehavior.Strict)
.Setup(s => s.DownloadString("http://192.168.5.22:1111/api?mode=get_cats&output=json&apikey=5c770e3197e4fe763423ee7c392c25d2&ma_username=admin2&ma_password=pass2"))
.Returns(ReadAllText("Files", "Categories_json.txt"));
var result = Subject.GetCategories(host, port, apikey, username, password);
result.Should().NotBeNull();
result.categories.Should().NotBeEmpty();
}
[Test]
public void should_be_able_to_get_categories_using_config()
{
Mocker.GetMock<IHttpProvider>(MockBehavior.Strict)
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=get_cats&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(ReadAllText("Files", "Categories_json.txt"));
var result = Subject.GetCategories();
result.Should().NotBeNull();
result.categories.Should().NotBeEmpty();
}
[Test]
public void GetHistory_should_return_a_list_with_items_when_the_history_has_items()
{
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(ReadAllText("Files", "History.txt"));
var result = Subject.GetHistory();
result.Should().HaveCount(1);
}
[Test]
public void GetHistory_should_return_an_empty_list_when_the_queue_is_empty()
{
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(ReadAllText("Files", "HistoryEmpty.txt"));
var result = Subject.GetHistory();
result.Should().BeEmpty();
}
[Test]
public void GetHistory_should_return_an_empty_list_when_there_is_an_error_getting_the_queue()
{
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(ReadAllText("Files", "JsonError.txt"));
Assert.Throws<ApplicationException>(() => Subject.GetHistory(), "API Key Incorrect");
}
[Test]
public void GetVersion_should_return_the_version_using_passed_in_values()
{
var response = "{ \"version\": \"0.6.9\" }";
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=version&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(response);
var result = Subject.GetVersion("192.168.5.55", 2222, "5c770e3197e4fe763423ee7c392c25d1", "admin", "pass");
result.Should().NotBeNull();
result.Version.Should().Be("0.6.9");
}
[Test]
public void GetVersion_should_return_the_version_using_saved_values()
{
var response = "{ \"version\": \"0.6.9\" }";
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=version&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(response);
var result = Subject.GetVersion();
result.Should().NotBeNull();
result.Version.Should().Be("0.6.9");
}
[Test]
public void Test_should_return_version_as_a_string()
{
const string response = "{ \"version\": \"0.6.9\" }";
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=version&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(response);
var result = Subject.Test("192.168.5.55", 2222, "5c770e3197e4fe763423ee7c392c25d1", "admin", "pass");
result.Should().Be("0.6.9");
}
[Test]
public void downloadNzb_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
{
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.SabRecentTvPriority)
.Returns(SabPriorityType.High);
Mocker.GetMock<ISabCommunicationProxy>()
.Setup(s => s.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), (int)SabPriorityType.High))
.Returns(new SabAddResponse());
Subject.DownloadNzb(_remoteEpisode);
Mocker.GetMock<ISabCommunicationProxy>()
.Verify(v => v.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), (int)SabPriorityType.High), Times.Once());
}
}
}

@ -0,0 +1,104 @@
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.Sabnzbd;
using NzbDrone.Core.Download.Clients.Sabnzbd.Responses;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
{
[TestFixture]
public class SabnzbdFixture : CoreTest<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;
[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
{
Host = "192.168.5.55",
Port = 2222,
ApiKey = "5c770e3197e4fe763423ee7c392c25d1",
Username = "admin",
Password = "pass",
TvCategory = "tv",
RecentTvPriority = (int)SabnzbdPriority.High
};
}
[Test]
public void GetHistory_should_return_a_list_with_items_when_the_history_has_items()
{
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(ReadAllText("Files", "History.txt"));
var result = Subject.GetHistory();
result.Should().HaveCount(1);
}
[Test]
public void GetHistory_should_return_an_empty_list_when_the_queue_is_empty()
{
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(ReadAllText("Files", "HistoryEmpty.txt"));
var result = Subject.GetHistory();
result.Should().BeEmpty();
}
[Test]
public void GetHistory_should_return_an_empty_list_when_there_is_an_error_getting_the_queue()
{
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(ReadAllText("Files", "JsonError.txt"));
Assert.Throws<ApplicationException>(() => Subject.GetHistory(), "API Key Incorrect");
}
[Test]
public void downloadNzb_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);
Mocker.GetMock<ISabnzbdProxy>()
.Verify(v => v.DownloadNzb(It.IsAny<Stream>(), It.IsAny<String>(), It.IsAny<String>(), (int)SabnzbdPriority.High, It.IsAny<SabnzbdSettings>()), Times.Once());
}
}
}

@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Net;
using FizzWare.NBuilder;
@ -33,9 +34,6 @@ namespace NzbDrone.Core.Test.Download
.With(c => c.Release = Builder<ReleaseInfo>.CreateNew().Build())
.With(c => c.Episodes = episodes)
.Build();
Mocker.GetMock<IDownloadClient>().Setup(c => c.IsConfigured).Returns(true);
}
private void WithSuccessfulAdd()
@ -85,7 +83,8 @@ namespace NzbDrone.Core.Test.Download
[Test]
public void should_not_attempt_download_if_client_isnt_configure()
{
Mocker.GetMock<IDownloadClient>().Setup(c => c.IsConfigured).Returns(false);
Mocker.GetMock<IProvideDownloadClient>()
.Setup(c => c.GetDownloadClient()).Returns((IDownloadClient)null);
Subject.DownloadReport(_parseResult);

@ -1,25 +0,0 @@
{
"categories":[
"*",
"anime",
"apps",
"books",
"consoles",
"ds-games",
"emulation",
"games",
"misc",
"movies",
"music",
"pda",
"resources",
"test",
"tv",
"tv-dvd",
"unknown",
"wii-games",
"xbox-dlc",
"xbox-xbla",
"xxx"
]
}

@ -122,10 +122,10 @@
<Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\GetQualifiedReportsFixture.cs" />
<Compile Include="Download\DownloadClientTests\BlackholeProviderFixture.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetProviderTests\DownloadNzbFixture.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetProviderTests\QueueFixture.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetTests\DownloadNzbFixture.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetTests\QueueFixture.cs" />
<Compile Include="Download\DownloadClientTests\PneumaticProviderFixture.cs" />
<Compile Include="Download\DownloadClientTests\SabProviderTests\SabProviderFixture.cs" />
<Compile Include="Download\DownloadClientTests\SabnzbdTests\SabnzbdFixture.cs" />
<Compile Include="Download\DownloadServiceFixture.cs" />
<Compile Include="Download\FailedDownloadServiceFixture.cs" />
<Compile Include="Framework\CoreTest.cs" />
@ -330,9 +330,6 @@
<Content Include="Files\RSS\newznab.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Files\Categories_json.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Files\RSS\SizeParsing\newznab.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

@ -23,6 +23,7 @@ namespace NzbDrone.Core.Annotations
Textbox,
Password,
Checkbox,
Select
Select,
Path
}
}

@ -73,69 +73,6 @@ namespace NzbDrone.Core.Configuration
_eventAggregator.PublishEvent(new ConfigSavedEvent());
}
public String SabHost
{
get { return GetValue("SabHost", "localhost"); }
set { SetValue("SabHost", value); }
}
public int SabPort
{
get { return GetValueInt("SabPort", 8080); }
set { SetValue("SabPort", value); }
}
public String SabApiKey
{
get { return GetValue("SabApiKey"); }
set { SetValue("SabApiKey", value); }
}
public String SabUsername
{
get { return GetValue("SabUsername"); }
set { SetValue("SabUsername", value); }
}
public String SabPassword
{
get { return GetValue("SabPassword"); }
set { SetValue("SabPassword", value); }
}
public String SabTvCategory
{
get { return GetValue("SabTvCategory", "tv"); }
set { SetValue("SabTvCategory", value); }
}
public SabPriorityType SabRecentTvPriority
{
get { return GetValueEnum("SabRecentTvPriority", SabPriorityType.Default); }
set { SetValue("SabRecentTvPriority", value); }
}
public SabPriorityType SabOlderTvPriority
{
get { return GetValueEnum("SabOlderTvPriority", SabPriorityType.Default); }
set { SetValue("SabOlderTvPriority", value); }
}
public bool SabUseSsl
{
get { return GetValueBoolean("SabUseSsl", false); }
set { SetValue("SabUseSsl", value); }
}
public String DownloadedEpisodesFolder
{
get { return GetValue(ConfigKey.DownloadedEpisodesFolder.ToString()); }
@ -155,80 +92,12 @@ namespace NzbDrone.Core.Configuration
set { SetValue("Retention", value); }
}
public DownloadClientType DownloadClient
{
get { return GetValueEnum("DownloadClient", DownloadClientType.Blackhole); }
set { SetValue("DownloadClient", value); }
}
public string BlackholeFolder
{
get { return GetValue("BlackholeFolder", String.Empty); }
set { SetValue("BlackholeFolder", value); }
}
public string PneumaticFolder
{
get { return GetValue("PneumaticFolder", String.Empty); }
set { SetValue("PneumaticFolder", value); }
}
public string RecycleBin
{
get { return GetValue("RecycleBin", String.Empty); }
set { SetValue("RecycleBin", value); }
}
public String NzbgetUsername
{
get { return GetValue("NzbgetUsername", "nzbget"); }
set { SetValue("NzbgetUsername", value); }
}
public String NzbgetPassword
{
get { return GetValue("NzbgetPassword", ""); }
set { SetValue("NzbgetPassword", value); }
}
public String NzbgetHost
{
get { return GetValue("NzbgetHost", "localhost"); }
set { SetValue("NzbgetHost", value); }
}
public Int32 NzbgetPort
{
get { return GetValueInt("NzbgetPort", 6789); }
set { SetValue("NzbgetPort", value); }
}
public String NzbgetTvCategory
{
get { return GetValue("NzbgetTvCategory", ""); }
set { SetValue("NzbgetTvCategory", value); }
}
public PriorityType NzbgetRecentTvPriority
{
get { return GetValueEnum("NzbgetRecentTvPriority", PriorityType.Normal); }
set { SetValue("NzbgetRecentTvPriority", value); }
}
public PriorityType NzbgetOlderTvPriority
{
get { return GetValueEnum("NzbgetOlderTvPriority", PriorityType.Normal); }
set { SetValue("NzbgetOlderTvPriority", value); }
}
public string ReleaseRestrictions
{
get { return GetValue("ReleaseRestrictions", String.Empty).Trim('\r', '\n'); }

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Core.Download.Clients.Sabnzbd;
namespace NzbDrone.Core.Configuration
{
@ -10,29 +7,10 @@ namespace NzbDrone.Core.Configuration
{
IEnumerable<Config> All();
Dictionary<String, Object> AllWithDefaults();
String SabHost { get; set; }
int SabPort { get; set; }
String SabApiKey { get; set; }
String SabUsername { get; set; }
String SabPassword { get; set; }
String SabTvCategory { get; set; }
SabPriorityType SabRecentTvPriority { get; set; }
SabPriorityType SabOlderTvPriority { get; set; }
Boolean SabUseSsl { get; set; }
String DownloadedEpisodesFolder { get; set; }
bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
int Retention { get; set; }
DownloadClientType DownloadClient { get; set; }
string BlackholeFolder { get; set; }
string PneumaticFolder { get; set; }
string RecycleBin { get; set; }
String NzbgetUsername { get; set; }
String NzbgetPassword { get; set; }
String NzbgetHost { get; set; }
Int32 NzbgetPort { get; set; }
String NzbgetTvCategory { get; set; }
PriorityType NzbgetRecentTvPriority { get; set; }
PriorityType NzbgetOlderTvPriority { get; set; }
string ReleaseRestrictions { get; set; }
Int32 RssSyncInterval { get; set; }
Boolean AutoDownloadPropers { get; set; }

@ -0,0 +1,20 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(41)]
public class add_download_clients_table : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Create.TableForModel("DownloadClients")
.WithColumn("Enable").AsBoolean().NotNullable()
.WithColumn("Name").AsString().NotNullable()
.WithColumn("Implementation").AsString().NotNullable()
.WithColumn("Settings").AsString().NotNullable()
.WithColumn("ConfigContract").AsString().NotNullable()
.WithColumn("Protocol").AsInt32().NotNullable();
}
}
}

@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Runtime.Remoting.Messaging;
using FluentMigrator;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(42)]
public class convert_config_to_download_clients : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(ConvertToThingyProvder);
}
private void ConvertToThingyProvder(IDbConnection conn, IDbTransaction tran)
{
var config = new Dictionary<string, string>();
using (IDbCommand configCmd = conn.CreateCommand())
{
configCmd.Transaction = tran;
configCmd.CommandText = @"SELECT * FROM Config";
using (IDataReader configReader = configCmd.ExecuteReader())
{
var keyIndex = configReader.GetOrdinal("Key");
var valueIndex = configReader.GetOrdinal("Value");
while (configReader.Read())
{
var key = configReader.GetString(keyIndex);
var value = configReader.GetString(valueIndex);
config.Add(key.ToLowerInvariant(), value);
}
}
}
var client = GetConfigValue(config, "DownloadClient", "");
if (String.IsNullOrWhiteSpace(client))
{
return;
}
if (client.Equals("sabnzbd", StringComparison.InvariantCultureIgnoreCase))
{
var settings = new ClientSettingsForMigration
{
Host = GetConfigValue(config, "SabHost", "localhost"),
Port = GetConfigValue(config, "SabPort", 8080),
ApiKey = GetConfigValue(config, "SabApiKey", ""),
Username = GetConfigValue(config, "SabUsername", ""),
Password = GetConfigValue(config, "SabPassword", ""),
TvCategory = GetConfigValue(config, "SabTvCategory", "tv"),
RecentTvPriority = GetSabnzbdPriority(GetConfigValue(config, "NzbgetRecentTvPriority", "Default")),
OlderTvPriority = GetSabnzbdPriority(GetConfigValue(config, "NzbgetOlderTvPriority", "Default")),
UseSsl = GetConfigValue(config, "SabUseSsl", false)
};
AddDownloadClient(conn, tran, "Sabnzbd", "Sabnzbd", settings.ToJson(), "SabnzbdSettings", 1);
}
else if (client.Equals("nzbget", StringComparison.InvariantCultureIgnoreCase))
{
var settings = new ClientSettingsForMigration
{
Host = GetConfigValue(config, "NzbGetHost", "localhost"),
Port = GetConfigValue(config, "NzbgetPort", 6789),
Username = GetConfigValue(config, "NzbgetUsername", "nzbget"),
Password = GetConfigValue(config, "NzbgetPassword", ""),
TvCategory = GetConfigValue(config, "NzbgetTvCategory", "tv"),
RecentTvPriority = GetNzbgetPriority(GetConfigValue(config, "NzbgetRecentTvPriority", "Normal")),
OlderTvPriority = GetNzbgetPriority(GetConfigValue(config, "NzbgetOlderTvPriority", "Normal")),
};
AddDownloadClient(conn, tran, "Nzbget", "Nzbget", settings.ToJson(), "NzbgetSettings", 1);
}
else if (client.Equals("pneumatic", StringComparison.InvariantCultureIgnoreCase))
{
var settings = new FolderSettingsForMigration
{
Folder = GetConfigValue(config, "PneumaticFolder", "")
};
AddDownloadClient(conn, tran, "Pneumatic", "Pneumatic", settings.ToJson(), "FolderSettings", 1);
}
else if (client.Equals("blackhole", StringComparison.InvariantCultureIgnoreCase))
{
var settings = new FolderSettingsForMigration
{
Folder = GetConfigValue(config, "BlackholeFolder", "")
};
AddDownloadClient(conn, tran, "Blackhole", "Blackhole", settings.ToJson(), "FolderSettings", 1);
}
DeleteOldConfigValues(conn, tran);
}
private T GetConfigValue<T>(Dictionary<string, string> config, string key, T defaultValue)
{
key = key.ToLowerInvariant();
if (config.ContainsKey(key))
{
return (T) Convert.ChangeType(config[key], typeof (T));
}
return defaultValue;
}
private void AddDownloadClient(IDbConnection conn, IDbTransaction tran, string name, string implementation, string settings,
string configContract, int protocol)
{
using (IDbCommand updateCmd = conn.CreateCommand())
{
var text = String.Format("INSERT INTO DownloadClients (Enable, Name, Implementation, Settings, ConfigContract, Protocol) VALUES (1, ?, ?, ?, ?, ?)");
updateCmd.AddParameter(name);
updateCmd.AddParameter(implementation);
updateCmd.AddParameter(settings);
updateCmd.AddParameter(configContract);
updateCmd.AddParameter(protocol);
updateCmd.Transaction = tran;
updateCmd.CommandText = text;
updateCmd.ExecuteNonQuery();
}
}
private void DeleteOldConfigValues(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand updateCmd = conn.CreateCommand())
{
var text = "DELETE FROM Config WHERE [KEY] IN ('nzbgetusername', 'nzbgetpassword', 'nzbgethost', 'nzbgetport', " +
"'nzbgettvcategory', 'nzbgetrecenttvpriority', 'nzbgetoldertvpriority', 'sabhost', 'sabport', " +
"'sabapikey', 'sabusername', 'sabpassword', 'sabtvcategory', 'sabrecenttvpriority', " +
"'saboldertvpriority', 'sabusessl', 'downloadclient', 'blackholefolder', 'pneumaticfolder')";
updateCmd.Transaction = tran;
updateCmd.CommandText = text;
updateCmd.ExecuteNonQuery();
}
}
private int GetSabnzbdPriority(string priority)
{
return (int)Enum.Parse(typeof(SabnzbdPriorityForMigration), priority, true);
}
private int GetNzbgetPriority(string priority)
{
return (int)Enum.Parse(typeof(NzbGetPriorityForMigration), priority, true);
}
private class ClientSettingsForMigration
{
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; }
}
private class FolderSettingsForMigration
{
public String Folder { get; set; }
}
private enum SabnzbdPriorityForMigration
{
Default = -100,
Paused = -2,
Low = -1,
Normal = 0,
High = 1,
Force = 2
}
private enum NzbGetPriorityForMigration
{
VeryLow = -100,
Low = -50,
Normal = 0,
High = 50,
VeryHigh = 100
}
}
}

@ -7,6 +7,7 @@ using NzbDrone.Core.Blacklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Jobs;
@ -39,6 +40,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<ScheduledTask>().RegisterModel("ScheduledTasks");
Mapper.Entity<NotificationDefinition>().RegisterModel("Notifications");
Mapper.Entity<MetadataDefinition>().RegisterModel("Metadata");
Mapper.Entity<DownloadClientDefinition>().RegisterModel("DownloadClients");
Mapper.Entity<SceneMapping>().RegisterModel("SceneMappings");

@ -5,7 +5,6 @@ using NzbDrone.Core.Download;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
@ -32,9 +31,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
var downloadClient = _downloadClientProvider.GetDownloadClient();
if (!downloadClient.IsConfigured)
if (downloadClient == null)
{
_logger.Warn("Download client {0} isn't configured yet.", downloadClient.GetType().Name);
_logger.Warn("Download client isn't configured yet.");
return true;
}

@ -41,7 +41,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
return true;
}
if (_downloadClientProvider.GetDownloadClient().GetType() == typeof (SabnzbdClient))
if (_downloadClientProvider.GetDownloadClient().GetType() == typeof (Sabnzbd))
{
_logger.Trace("Performing history status check on report");
foreach (var episode in subject.Episodes)

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Configuration;
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.Trace("Downloading NZB from: {0} to: {1}", url, filename);
_httpProvider.DownloadFile(url, filename);
_logger.Trace("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 = 0)
{
return new HistoryItem[0];
}
public override void RemoveFromQueue(string id)
{
}
public override void RemoveFromHistory(string id)
{
}
public void Execute(TestBlackholeCommand message)
{
var testPath = Path.Combine(message.Folder, "drone_test.txt");
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
_diskProvider.DeleteFile(testPath);
}
}
}

@ -0,0 +1,18 @@
using System;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Download.Clients.Blackhole
{
public class TestBlackholeCommand : Command
{
public override bool SendUpdatesToClient
{
get
{
return true;
}
}
public String Folder { get; set; }
}
}

@ -1,68 +0,0 @@
using System.Collections.Generic;
using System.IO;
using NLog;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Clients
{
public class BlackholeProvider : IDownloadClient
{
private readonly IConfigService _configService;
private readonly IHttpProvider _httpProvider;
private readonly Logger _logger;
public BlackholeProvider(IConfigService configService, IHttpProvider httpProvider, Logger logger)
{
_configService = configService;
_httpProvider = httpProvider;
_logger = logger;
}
public string DownloadNzb(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
title = FileNameBuilder.CleanFilename(title);
var filename = Path.Combine(_configService.BlackholeFolder, title + ".nzb");
_logger.Trace("Downloading NZB from: {0} to: {1}", url, filename);
_httpProvider.DownloadFile(url, filename);
_logger.Trace("NZB Download succeeded, saved to: {0}", filename);
return null;
}
public bool IsConfigured
{
get
{
return !string.IsNullOrWhiteSpace(_configService.BlackholeFolder);
}
}
public IEnumerable<QueueItem> GetQueue()
{
return new QueueItem[0];
}
public IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
{
return new HistoryItem[0];
}
public void RemoveFromQueue(string id)
{
}
public void RemoveFromHistory(string id)
{
}
}
}

@ -0,0 +1,29 @@
using System;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Download.Clients
{
public class FolderSettingsValidator : AbstractValidator<FolderSettings>
{
public FolderSettingsValidator()
{
RuleFor(c => c.Folder).NotEmpty();
}
}
public class FolderSettings : IProviderConfig
{
private static readonly FolderSettingsValidator Validator = new FolderSettingsValidator();
[FieldDefinition(0, Label = "Folder", Type = FieldType.Path)]
public String Folder { get; set; }
public ValidationResult Validate()
{
return Validator.Validate(this);
}
}
}

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

@ -2,14 +2,11 @@
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbGetQueueItem
public class NzbgetQueueItem
{
private string _nzbName;
public Int32 NzbId { get; set; }
public string NzbName { get; set; }
public String Category { get; set; }
public Int32 FileSizeMb { get; set; }
public Int32 RemainingSizeMb { get; set; }

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using Omu.ValueInjecter;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class Nzbget : DownloadClientBase<NzbgetSettings>, IExecute<TestNzbgetCommand>
{
private readonly INzbgetProxy _proxy;
private readonly IParsingService _parsingService;
private readonly Logger _logger;
public Nzbget(INzbgetProxy proxy,
IParsingService parsingService,
Logger logger)
{
_proxy = proxy;
_parsingService = parsingService;
_logger = logger;
}
public override string DownloadNzb(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title + ".nzb";
string cat = Settings.TvCategory;
int priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
_logger.Info("Adding report [{0}] to the queue.", title);
var success = _proxy.AddNzb(Settings, title, cat, priority, false, url);
_logger.Debug("Queue Response: [{0}]", success);
return null;
}
public override IEnumerable<QueueItem> GetQueue()
{
var items = _proxy.GetQueue(Settings);
foreach (var nzbGetQueueItem in items)
{
var queueItem = new QueueItem();
queueItem.Id = nzbGetQueueItem.NzbId.ToString();
queueItem.Title = nzbGetQueueItem.NzbName;
queueItem.Size = nzbGetQueueItem.FileSizeMb;
queueItem.Sizeleft = nzbGetQueueItem.RemainingSizeMb;
queueItem.Status = nzbGetQueueItem.FileSizeMb == nzbGetQueueItem.PausedSizeMb ? "paused" : "queued";
var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title);
if (parsedEpisodeInfo == null) continue;
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
if (remoteEpisode.Series == null) continue;
queueItem.RemoteEpisode = remoteEpisode;
yield return queueItem;
}
}
public override IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
{
return new HistoryItem[0];
}
public override void RemoveFromQueue(string id)
{
throw new NotImplementedException();
}
public override void RemoveFromHistory(string id)
{
throw new NotImplementedException();
}
public VersionResponse GetVersion(string host = null, int port = 0, string username = null, string password = null)
{
return _proxy.GetVersion(Settings);
}
public void Execute(TestNzbgetCommand message)
{
var settings = new NzbgetSettings();
settings.InjectFrom(message);
_proxy.GetVersion(settings);
}
}
}

@ -1,136 +0,0 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbgetClient : IDownloadClient
{
private readonly IConfigService _configService;
private readonly IHttpProvider _httpProvider;
private readonly INzbGetCommunicationProxy _proxy;
private readonly IParsingService _parsingService;
private readonly Logger _logger;
public NzbgetClient(IConfigService configService,
IHttpProvider httpProvider,
INzbGetCommunicationProxy proxy,
IParsingService parsingService,
Logger logger)
{
_configService = configService;
_httpProvider = httpProvider;
_proxy = proxy;
_parsingService = parsingService;
_logger = logger;
}
public string DownloadNzb(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title + ".nzb";
string cat = _configService.NzbgetTvCategory;
int priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.NzbgetRecentTvPriority : (int)_configService.NzbgetOlderTvPriority;
_logger.Info("Adding report [{0}] to the queue.", title);
var success = _proxy.AddNzb(title, cat, priority, false, url);
_logger.Debug("Queue Response: [{0}]", success);
return null;
}
public bool IsConfigured
{
get
{
return !string.IsNullOrWhiteSpace(_configService.NzbgetHost) && _configService.NzbgetPort != 0;
}
}
public virtual IEnumerable<QueueItem> GetQueue()
{
var items = _proxy.GetQueue();
foreach (var nzbGetQueueItem in items)
{
var queueItem = new QueueItem();
queueItem.Id = nzbGetQueueItem.NzbId.ToString();
queueItem.Title = nzbGetQueueItem.NzbName;
queueItem.Size = nzbGetQueueItem.FileSizeMb;
queueItem.Sizeleft = nzbGetQueueItem.RemainingSizeMb;
queueItem.Status = nzbGetQueueItem.FileSizeMb == nzbGetQueueItem.PausedSizeMb ? "paused" : "queued";
var parsedEpisodeInfo = Parser.Parser.ParseTitle(queueItem.Title);
if (parsedEpisodeInfo == null) continue;
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0);
if (remoteEpisode.Series == null) continue;
queueItem.RemoteEpisode = remoteEpisode;
yield return queueItem;
}
}
public IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
{
return new HistoryItem[0];
}
public void RemoveFromQueue(string id)
{
throw new NotImplementedException();
}
public void RemoveFromHistory(string id)
{
throw new NotImplementedException();
}
public virtual VersionModel GetVersion(string host = null, int port = 0, string username = null, string password = null)
{
throw new NotImplementedException();
//Get saved values if any of these are defaults
if (host == null)
host = _configService.NzbgetHost;
if (port == 0)
port = _configService.NzbgetPort;
if (username == null)
username = _configService.NzbgetUsername;
if (password == null)
password = _configService.NzbgetPassword;
var response = _proxy.GetVersion();
return Json.Deserialize<VersionModel>(response);
}
public virtual string Test(string host, int port, string username, string password)
{
try
{
var version = GetVersion(host, port, username, password);
return version.Result;
}
catch (Exception ex)
{
_logger.DebugException("Failed to Test Nzbget", ex);
}
return String.Empty;
}
}
}

@ -1,6 +1,6 @@
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public enum PriorityType
public enum NzbgetPriority
{
VeryLow = -100,
Low = -50,

@ -1,57 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Rest;
using RestSharp;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public interface INzbGetCommunicationProxy
public interface INzbgetProxy
{
bool AddNzb(params object[] parameters);
List<NzbGetQueueItem> GetQueue();
string GetVersion();
bool AddNzb(NzbgetSettings settings, params object[] parameters);
List<NzbgetQueueItem> GetQueue(NzbgetSettings settings);
VersionResponse GetVersion(NzbgetSettings settings);
}
public class NzbGetCommunicationProxy : INzbGetCommunicationProxy
public class NzbgetProxy : INzbgetProxy
{
private readonly IConfigService _configService;
private readonly Logger _logger;
public NzbGetCommunicationProxy(IConfigService configService, Logger logger)
public NzbgetProxy(Logger logger)
{
_configService = configService;
_logger = logger;
}
public bool AddNzb(params object[] parameters)
public bool AddNzb(NzbgetSettings settings, params object[] parameters)
{
var request = BuildRequest(new JsonRequest("appendurl", parameters));
return Json.Deserialize<EnqueueResponse>(ProcessRequest(request)).Result;
return Json.Deserialize<EnqueueResponse>(ProcessRequest(request, settings)).Result;
}
public List<NzbGetQueueItem> GetQueue()
public List<NzbgetQueueItem> GetQueue(NzbgetSettings settings)
{
var request = BuildRequest(new JsonRequest("listgroups"));
return Json.Deserialize<NzbGetQueue>(ProcessRequest(request)).QueueItems;
return Json.Deserialize<NzbgetQueue>(ProcessRequest(request, settings)).QueueItems;
}
public string GetVersion()
public VersionResponse GetVersion(NzbgetSettings settings)
{
var request = BuildRequest(new JsonRequest("version"));
return ProcessRequest(request);
return Json.Deserialize<VersionResponse>(ProcessRequest(request, settings));
}
private string ProcessRequest(IRestRequest restRequest)
private string ProcessRequest(IRestRequest restRequest, NzbgetSettings settings)
{
var client = BuildClient();
var client = BuildClient(settings);
var response = client.Execute(restRequest);
_logger.Trace("Response: {0}", response.Content);
@ -60,14 +55,14 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
return response.Content;
}
private IRestClient BuildClient()
private IRestClient BuildClient(NzbgetSettings settings)
{
var url = String.Format("http://{0}:{1}/jsonrpc",
_configService.NzbgetHost,
_configService.NzbgetPort);
settings.Host,
settings.Port);
var client = new RestClient(url);
client.Authenticator = new HttpBasicAuthenticator(_configService.NzbgetUsername, _configService.NzbgetPassword);
client.Authenticator = new HttpBasicAuthenticator(settings.Username, settings.Password);
return client;
}

@ -0,0 +1,59 @@
using System;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class NzbgetSettingsValidator : AbstractValidator<NzbgetSettings>
{
public NzbgetSettingsValidator()
{
RuleFor(c => c.Host).NotEmpty();
RuleFor(c => c.Port).GreaterThan(0);
RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Password).NotEmpty();
}
}
public class NzbgetSettings : IProviderConfig
{
private static readonly NzbgetSettingsValidator Validator = new NzbgetSettingsValidator();
public NzbgetSettings()
{
Host = "localhost";
Port = 6789;
TvCategory = "tv";
RecentTvPriority = (int)NzbgetPriority.Normal;
OlderTvPriority = (int)NzbgetPriority.Normal;
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
public String Host { get; set; }
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public Int32 Port { get; set; }
[FieldDefinition(2, Label = "Username", Type = FieldType.Textbox)]
public String Username { get; set; }
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; }
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox)]
public String TvCategory { get; set; }
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority))]
public Int32 RecentTvPriority { get; set; }
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority))]
public Int32 OlderTvPriority { get; set; }
public ValidationResult Validate()
{
return Validator.Validate(this);
}
}
}

@ -0,0 +1,21 @@
using System;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class TestNzbgetCommand : Command
{
public override bool SendUpdatesToClient
{
get
{
return true;
}
}
public String Host { get; set; }
public Int32 Port { get; set; }
public String Username { get; set; }
public String Password { get; set; }
}
}

@ -2,7 +2,7 @@
namespace NzbDrone.Core.Download.Clients.Nzbget
{
public class VersionModel
public class VersionResponse
{
public String Version { get; set; }
public String Result { get; set; }

@ -6,12 +6,13 @@ using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Clients
namespace NzbDrone.Core.Download.Clients.Pneumatic
{
public class PneumaticClient : IDownloadClient
public class Pneumatic : DownloadClientBase<FolderSettings>, IExecute<TestPneumaticCommand>
{
private readonly IConfigService _configService;
private readonly IHttpProvider _httpProvider;
@ -19,7 +20,7 @@ namespace NzbDrone.Core.Download.Clients
private static readonly Logger logger = NzbDroneLogger.GetLogger();
public PneumaticClient(IConfigService configService, IHttpProvider httpProvider,
public Pneumatic(IConfigService configService, IHttpProvider httpProvider,
IDiskProvider diskProvider)
{
_configService = configService;
@ -27,20 +28,20 @@ namespace NzbDrone.Core.Download.Clients
_diskProvider = diskProvider;
}
public string DownloadNzb(RemoteEpisode remoteEpisode)
public override string DownloadNzb(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
if (remoteEpisode.ParsedEpisodeInfo.FullSeason)
{
throw new NotImplementedException("Full season Pneumatic releases are not supported.");
throw new NotImplementedException("Full season releases are not supported with Pneumatic.");
}
title = FileNameBuilder.CleanFilename(title);
//Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC)
var filename = Path.Combine(_configService.PneumaticFolder, title + ".nzb");
var filename = Path.Combine(Settings.Folder, title + ".nzb");
logger.Trace("Downloading NZB from: {0} to: {1}", url, filename);
_httpProvider.DownloadFile(url, filename);
@ -57,31 +58,33 @@ namespace NzbDrone.Core.Download.Clients
{
get
{
return !string.IsNullOrWhiteSpace(_configService.PneumaticFolder);
return !string.IsNullOrWhiteSpace(Settings.Folder);
}
}
public IEnumerable<QueueItem> GetQueue()
public override IEnumerable<QueueItem> GetQueue()
{
return new QueueItem[0];
}
public IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
public override IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
{
return new HistoryItem[0];
}
public void RemoveFromQueue(string id)
public override void RemoveFromQueue(string id)
{
}
public void RemoveFromHistory(string id)
public override void RemoveFromHistory(string id)
{
}
public virtual bool IsInQueue(RemoteEpisode newEpisode)
public void Execute(TestPneumaticCommand message)
{
return false;
var testPath = Path.Combine(message.Folder, "drone_test.txt");
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
_diskProvider.DeleteFile(testPath);
}
}
}

@ -0,0 +1,18 @@
using System;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Download.Clients.Pneumatic
{
public class TestPneumaticCommand : Command
{
public override bool SendUpdatesToClient
{
get
{
return true;
}
}
public String Folder { get; set; }
}
}

@ -1,8 +0,0 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class ConnectionInfoModel
{
public string Address { get; set; }
public int Port { get; set; }
}
}

@ -7,7 +7,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var priorityType = (SabPriorityType)value;
var priorityType = (SabnzbdPriority)value;
writer.WriteValue(priorityType.ToString());
}
@ -15,7 +15,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters
{
var queuePriority = reader.Value.ToString();
SabPriorityType output;
SabnzbdPriority output;
Enum.TryParse(queuePriority, out output);
return output;
@ -23,7 +23,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters
public override bool CanConvert(Type objectType)
{
return objectType == typeof(SabPriorityType);
return objectType == typeof(SabnzbdPriority);
}
}
}

@ -0,0 +1,18 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses
{
public class SabnzbdAddResponse
{
public SabnzbdAddResponse()
{
Ids = new List<string>();
}
public bool Status { get; set; }
[JsonProperty(PropertyName = "nzo_ids")]
public List<string> Ids { get; set; }
}
}

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses
{
public class SabnzbdCategoryResponse
{
public SabnzbdCategoryResponse()
{
Categories = new List<String>();
}
public List<String> Categories { get; set; }
}
}

@ -0,0 +1,7 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses
{
public class SabnzbdVersionResponse
{
public string Version { get; set; }
}
}

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabAddResponse
{
public SabAddResponse()
{
Ids = new List<String>();
}
public bool Status { get; set; }
[JsonProperty(PropertyName = "nzo_ids")]
public List<String> Ids { get; set; }
}
}

@ -1,100 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabAutoConfigureService
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger();
public SabModel AutoConfigureSab()
{
var info = GetConnectionList();
return FindApiKey(info);
}
private List<ConnectionInfoModel> GetConnectionList()
{
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
var info =
ipProperties.GetActiveTcpListeners().Select(
p =>
new ConnectionInfoModel { Address = p.Address.ToString().Replace("0.0.0.0", "127.0.0.1"), Port = p.Port }).Distinct().
ToList();
info.RemoveAll(i => i.Port == 135);
info.RemoveAll(i => i.Port == 139);
info.RemoveAll(i => i.Port == 445);
info.RemoveAll(i => i.Port == 3389);
info.RemoveAll(i => i.Port == 5900);
info.RemoveAll(i => i.Address.Contains("::"));
info.Reverse();
return info;
}
private SabModel FindApiKey(List<ConnectionInfoModel> info)
{
foreach (var connection in info)
{
var apiKey = GetApiKey(connection.Address, connection.Port);
if (!String.IsNullOrEmpty(apiKey))
return new SabModel
{
Host = connection.Address,
Port = connection.Port,
ApiKey = apiKey
};
}
return null;
}
private string GetApiKey(string ipAddress, int port)
{
var request = String.Format("http://{0}:{1}/config/general/", ipAddress, port);
var result = DownloadString(request);
Regex regex =
new Regex("\\<input\\Wtype\\=\\\"text\\\"\\Wid\\=\\\"apikey\\\"\\Wvalue\\=\\\"(?<apikey>\\w+)\\W",
RegexOptions.IgnoreCase
| RegexOptions.Compiled);
var match = regex.Match(result);
if (match.Success)
{
return match.Groups["apikey"].Value;
}
return String.Empty;
}
private string DownloadString(string url)
{
try
{
var request = WebRequest.Create(url);
request.Timeout = 2000;
var response = request.GetResponse();
var reader = new StreamReader(response.GetResponseStream());
return reader.ReadToEnd();
}
catch (Exception ex)
{
Logger.Trace("Failed to get response from: {0}", url);
Logger.Trace(ex.Message, ex);
}
return String.Empty;
}
}
}

@ -1,9 +0,0 @@
using System.Collections.Generic;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabCategoryModel
{
public List<string> categories { get; set; }
}
}

@ -1,130 +0,0 @@
using System;
using System.IO;
using NLog;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using RestSharp;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public interface ISabCommunicationProxy
{
SabAddResponse DownloadNzb(Stream nzb, string name, string category, int priority);
void RemoveFrom(string source, string id);
string ProcessRequest(IRestRequest restRequest, string action);
}
public class SabCommunicationProxy : ISabCommunicationProxy
{
private readonly IConfigService _configService;
private readonly Logger _logger;
public SabCommunicationProxy(IConfigService configService, Logger logger)
{
_configService = configService;
_logger = logger;
}
public SabAddResponse DownloadNzb(Stream nzb, string title, string category, int priority)
{
var request = new RestRequest(Method.POST);
var action = String.Format("mode=addfile&cat={0}&priority={1}", category, priority);
request.AddFile("name", ReadFully(nzb), title, "application/x-nzb");
SabAddResponse response;
if (!Json.TryDeserialize<SabAddResponse>(ProcessRequest(request, action), out response))
{
response = new SabAddResponse();
response.Status = true;
}
return response;
}
public void RemoveFrom(string source, string id)
{
var request = new RestRequest();
var action = String.Format("mode={0}&name=delete&del_files=1&value={1}", source, id);
ProcessRequest(request, action);
}
public string ProcessRequest(IRestRequest restRequest, string action)
{
var client = BuildClient(action);
var response = client.Execute(restRequest);
_logger.Trace("Response: {0}", response.Content);
CheckForError(response);
return response.Content;
}
private IRestClient BuildClient(string action)
{
var protocol = _configService.SabUseSsl ? "https" : "http";
var url = string.Format(@"{0}://{1}:{2}/api?{3}&apikey={4}&ma_username={5}&ma_password={6}&output=json",
protocol,
_configService.SabHost,
_configService.SabPort,
action,
_configService.SabApiKey,
_configService.SabUsername,
_configService.SabPassword);
_logger.Trace(url);
return new RestClient(url);
}
private void CheckForError(IRestResponse response)
{
if (response.ResponseStatus != ResponseStatus.Completed)
{
throw new ApplicationException("Unable to connect to SABnzbd, please check your settings");
}
SabJsonError result;
if (!Json.TryDeserialize<SabJsonError>(response.Content, out result))
{
//Handle plain text responses from SAB
result = new SabJsonError();
if (response.Content.StartsWith("error", StringComparison.InvariantCultureIgnoreCase))
{
result.Status = "false";
result.Error = response.Content.Replace("error: ", "");
}
else
{
result.Status = "true";
}
result.Error = response.Content.Replace("error: ", "");
}
if (result.Failed)
throw new ApplicationException(result.Error);
}
//TODO: Find a better home for this
private byte[] ReadFully(Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
}
}

@ -1,9 +0,0 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabModel
{
public string Host { get; set; }
public int Port { get; set; }
public string ApiKey { get; set; }
}
}

@ -1,7 +0,0 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabVersionModel
{
public string Version { get; set; }
}
}

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download.Clients.Sabnzbd.Responses;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using Omu.ValueInjecter;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class Sabnzbd : DownloadClientBase<SabnzbdSettings>, IExecute<TestSabnzbdCommand>
{
private readonly IHttpProvider _httpProvider;
private readonly IParsingService _parsingService;
private readonly ISabnzbdProxy _sabnzbdProxy;
private readonly ICached<IEnumerable<QueueItem>> _queueCache;
private readonly Logger _logger;
public Sabnzbd(IHttpProvider httpProvider,
ICacheManger cacheManger,
IParsingService parsingService,
ISabnzbdProxy sabnzbdProxy,
Logger logger)
{
_httpProvider = httpProvider;
_parsingService = parsingService;
_sabnzbdProxy = sabnzbdProxy;
_queueCache = cacheManger.GetCache<IEnumerable<QueueItem>>(GetType(), "queue");
_logger = logger;
}
public override string DownloadNzb(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
var category = Settings.TvCategory;
var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
using (var nzb = _httpProvider.DownloadStream(url))
{
_logger.Info("Adding report [{0}] to the queue.", title);
var response = _sabnzbdProxy.DownloadNzb(nzb, title, category, priority, Settings);
if (response != null && response.Ids.Any())
{
return response.Ids.First();
}
return null;
}
}
public override IEnumerable<QueueItem> GetQueue()
{
return _queueCache.Get("queue", () =>
{
var sabQueue = _sabnzbdProxy.GetQueue(0, 0, Settings).Items;
var queueItems = new List<QueueItem>();
foreach (var sabQueueItem in sabQueue)
{
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;
queueItems.Add(queueItem);
}
return queueItems;
}, TimeSpan.FromSeconds(10));
}
public override IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
{
var items = _sabnzbdProxy.GetHistory(start, limit, Settings).Items;
var historyItems = new List<HistoryItem>();
foreach (var sabHistoryItem in 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;
historyItems.Add(historyItem);
}
return historyItems;
}
public override void RemoveFromQueue(string id)
{
_sabnzbdProxy.RemoveFrom("queue", id, Settings);
}
public override void RemoveFromHistory(string id)
{
_sabnzbdProxy.RemoveFrom("history", id, Settings);
}
public void Execute(TestSabnzbdCommand message)
{
var settings = new SabnzbdSettings();
settings.InjectFrom(message);
_sabnzbdProxy.GetVersion(settings);
}
}
}

@ -1,250 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabnzbdClient : IDownloadClient
{
private readonly IConfigService _configService;
private readonly IHttpProvider _httpProvider;
private readonly IParsingService _parsingService;
private readonly ISabCommunicationProxy _sabCommunicationProxy;
private readonly ICached<IEnumerable<QueueItem>> _queueCache;
private readonly Logger _logger;
public SabnzbdClient(IConfigService configService,
IHttpProvider httpProvider,
ICacheManger cacheManger,
IParsingService parsingService,
ISabCommunicationProxy sabCommunicationProxy,
Logger logger)
{
_configService = configService;
_httpProvider = httpProvider;
_parsingService = parsingService;
_sabCommunicationProxy = sabCommunicationProxy;
_queueCache = cacheManger.GetCache<IEnumerable<QueueItem>>(GetType(), "queue");
_logger = logger;
}
public bool IsConfigured
{
get
{
return !string.IsNullOrWhiteSpace(_configService.SabHost)
&& _configService.SabPort != 0;
}
}
public string DownloadNzb(RemoteEpisode remoteEpisode)
{
var url = remoteEpisode.Release.DownloadUrl;
var title = remoteEpisode.Release.Title;
var category = _configService.SabTvCategory;
var priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.SabRecentTvPriority : (int)_configService.SabOlderTvPriority;
using (var nzb = _httpProvider.DownloadStream(url))
{
_logger.Info("Adding report [{0}] to the queue.", title);
var response = _sabCommunicationProxy.DownloadNzb(nzb, title, category, priority);
if (response != null && response.Ids.Any())
{
return response.Ids.First();
}
return null;
}
}
public IEnumerable<QueueItem> GetQueue()
{
return _queueCache.Get("queue", () =>
{
string action = String.Format("mode=queue&output=json&start={0}&limit={1}", 0, 0);
string request = GetSabRequest(action);
string response = _httpProvider.DownloadString(request);
CheckForError(response);
var sabQueue = Json.Deserialize<SabQueue>(JObject.Parse(response).SelectToken("queue").ToString()).Items;
var queueItems = new List<QueueItem>();
foreach (var sabQueueItem in sabQueue)
{
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;
queueItems.Add(queueItem);
}
return queueItems;
}, TimeSpan.FromSeconds(10));
}
public IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0)
{
string action = String.Format("mode=history&output=json&start={0}&limit={1}", start, limit);
string request = GetSabRequest(action);
string response = _httpProvider.DownloadString(request);
CheckForError(response);
var items = Json.Deserialize<SabHistory>(JObject.Parse(response).SelectToken("history").ToString()).Items;
var historyItems = new List<HistoryItem>();
foreach (var sabHistoryItem in 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;
historyItems.Add(historyItem);
}
return historyItems;
}
public void RemoveFromQueue(string id)
{
_sabCommunicationProxy.RemoveFrom("queue", id);
}
public void RemoveFromHistory(string id)
{
_sabCommunicationProxy.RemoveFrom("history", id);
}
public virtual SabCategoryModel GetCategories(string host = null, int port = 0, string apiKey = null, string username = null, string password = null)
{
//Get saved values if any of these are defaults
if (host == null)
host = _configService.SabHost;
if (port == 0)
port = _configService.SabPort;
if (apiKey == null)
apiKey = _configService.SabApiKey;
if (username == null)
username = _configService.SabUsername;
if (password == null)
password = _configService.SabPassword;
const string action = "mode=get_cats&output=json";
var command = string.Format(@"http://{0}:{1}/api?{2}&apikey={3}&ma_username={4}&ma_password={5}",
host, port, action, apiKey, username, password);
var response = _httpProvider.DownloadString(command);
if (String.IsNullOrWhiteSpace(response))
return new SabCategoryModel { categories = new List<string>() };
var categories = Json.Deserialize<SabCategoryModel>(response);
return categories;
}
public virtual SabVersionModel GetVersion(string host = null, int port = 0, string apiKey = null, string username = null, string password = null)
{
//Get saved values if any of these are defaults
if (host == null)
host = _configService.SabHost;
if (port == 0)
port = _configService.SabPort;
if (apiKey == null)
apiKey = _configService.SabApiKey;
if (username == null)
username = _configService.SabUsername;
if (password == null)
password = _configService.SabPassword;
const string action = "mode=version&output=json";
var command = string.Format(@"http://{0}:{1}/api?{2}&apikey={3}&ma_username={4}&ma_password={5}",
host, port, action, apiKey, username, password);
var response = _httpProvider.DownloadString(command);
if (String.IsNullOrWhiteSpace(response))
return null;
var version = Json.Deserialize<SabVersionModel>(response);
return version;
}
public virtual string Test(string host, int port, string apiKey, string username, string password)
{
try
{
var version = GetVersion(host, port, apiKey, username, password);
return version.Version;
}
catch (Exception ex)
{
_logger.DebugException("Failed to Test SABnzbd", ex);
}
return String.Empty;
}
private string GetSabRequest(string action)
{
var protocol = _configService.SabUseSsl ? "https" : "http";
return string.Format(@"{0}://{1}:{2}/api?{3}&apikey={4}&ma_username={5}&ma_password={6}",
protocol,
_configService.SabHost,
_configService.SabPort,
action,
_configService.SabApiKey,
_configService.SabUsername,
_configService.SabPassword);
}
private void CheckForError(string response)
{
var result = Json.Deserialize<SabJsonError>(response);
if (result.Failed)
throw new ApplicationException(result.Error);
}
}
}

@ -3,11 +3,11 @@ using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabQueue
public class SabnzbdHistory
{
public bool Paused { get; set; }
[JsonProperty(PropertyName = "slots")]
public List<SabQueueItem> Items { get; set; }
public List<SabnzbdHistoryItem> Items { get; set; }
}
}

@ -2,7 +2,7 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabHistoryItem
public class SabnzbdHistoryItem
{
[JsonProperty(PropertyName = "fail_message")]
public string FailMessage { get; set; }

@ -2,7 +2,7 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabJsonError
public class SabnzbdJsonError
{
public string Status { get; set; }
public string Error { get; set; }

@ -1,6 +1,6 @@
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public enum SabPriorityType
public enum SabnzbdPriority
{
Default = -100,
Paused = -2,

@ -0,0 +1,182 @@
using System;
using System.IO;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download.Clients.Sabnzbd.Responses;
using RestSharp;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public interface ISabnzbdProxy
{
SabnzbdAddResponse DownloadNzb(Stream nzb, string name, string category, int priority, SabnzbdSettings settings);
void RemoveFrom(string source, string id, SabnzbdSettings settings);
string ProcessRequest(IRestRequest restRequest, string action, SabnzbdSettings settings);
SabnzbdVersionResponse GetVersion(SabnzbdSettings settings);
SabnzbdCategoryResponse GetCategories(SabnzbdSettings settings);
SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings);
SabnzbdHistory GetHistory(int start, int limit, SabnzbdSettings settings);
}
public class SabnzbdProxy : ISabnzbdProxy
{
private readonly Logger _logger;
public SabnzbdProxy(Logger logger)
{
_logger = logger;
}
public SabnzbdAddResponse DownloadNzb(Stream nzb, string title, string category, int priority, SabnzbdSettings settings)
{
var request = new RestRequest(Method.POST);
var action = String.Format("mode=addfile&cat={0}&priority={1}", category, priority);
request.AddFile("name", ReadFully(nzb), title, "application/x-nzb");
SabnzbdAddResponse response;
if (!Json.TryDeserialize<SabnzbdAddResponse>(ProcessRequest(request, action, settings), out response))
{
response = new SabnzbdAddResponse();
response.Status = true;
}
return response;
}
public void RemoveFrom(string source, string id, SabnzbdSettings settings)
{
var request = new RestRequest();
var action = String.Format("mode={0}&name=delete&del_files=1&value={1}", source, id);
ProcessRequest(request, action, settings);
}
public string ProcessRequest(IRestRequest restRequest, string action, SabnzbdSettings settings)
{
var client = BuildClient(action, settings);
var response = client.Execute(restRequest);
_logger.Trace("Response: {0}", response.Content);
CheckForError(response);
return response.Content;
}
public SabnzbdVersionResponse GetVersion(SabnzbdSettings settings)
{
var request = new RestRequest();
var action = "mode=version";
SabnzbdVersionResponse response;
if (!Json.TryDeserialize<SabnzbdVersionResponse>(ProcessRequest(request, action, settings), out response))
{
response = new SabnzbdVersionResponse();
}
return response;
}
public SabnzbdCategoryResponse GetCategories(SabnzbdSettings settings)
{
var request = new RestRequest();
var action = "mode=get_cats";
SabnzbdCategoryResponse response;
if (!Json.TryDeserialize<SabnzbdCategoryResponse>(ProcessRequest(request, action, settings), out response))
{
response = new SabnzbdCategoryResponse();
}
return response;
}
public SabnzbdQueue GetQueue(int start, int limit, SabnzbdSettings settings)
{
var request = new RestRequest();
var action = String.Format("mode=queue&start={0}&limit={1}", start, limit);
var response = ProcessRequest(request, action, settings);
return Json.Deserialize<SabnzbdQueue>(JObject.Parse(response).SelectToken("queue").ToString());
}
public SabnzbdHistory GetHistory(int start, int limit, SabnzbdSettings settings)
{
var request = new RestRequest();
var action = String.Format("mode=queue&start={0}&limit={1}", start, limit);
var response = ProcessRequest(request, action, settings);
return Json.Deserialize<SabnzbdHistory>(JObject.Parse(response).SelectToken("history").ToString());
}
private IRestClient BuildClient(string action, SabnzbdSettings settings)
{
var protocol = settings.UseSsl ? "https" : "http";
var url = string.Format(@"{0}://{1}:{2}/api?{3}&apikey={4}&ma_username={5}&ma_password={6}&output=json",
protocol,
settings.Host,
settings.Port,
action,
settings.ApiKey,
settings.Username,
settings.Password);
_logger.Trace(url);
return new RestClient(url);
}
private void CheckForError(IRestResponse response)
{
if (response.ResponseStatus != ResponseStatus.Completed)
{
throw new ApplicationException("Unable to connect to SABnzbd, please check your settings");
}
SabnzbdJsonError result;
if (!Json.TryDeserialize<SabnzbdJsonError>(response.Content, out result))
{
//Handle plain text responses from SAB
result = new SabnzbdJsonError();
if (response.Content.StartsWith("error", StringComparison.InvariantCultureIgnoreCase))
{
result.Status = "false";
result.Error = response.Content.Replace("error: ", "");
}
else
{
result.Status = "true";
}
result.Error = response.Content.Replace("error: ", "");
}
if (result.Failed)
throw new ApplicationException(result.Error);
}
//TODO: Find a better home for this
private byte[] ReadFully(Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
}
}

@ -3,11 +3,11 @@ using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabHistory
public class SabnzbdQueue
{
public bool Paused { get; set; }
[JsonProperty(PropertyName = "slots")]
public List<SabHistoryItem> Items { get; set; }
public List<SabnzbdQueueItem> Items { get; set; }
}
}

@ -4,7 +4,7 @@ using NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabQueueItem
public class SabnzbdQueueItem
{
public string Status { get; set; }
public int Index { get; set; }
@ -21,7 +21,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public string Title { get; set; }
[JsonConverter(typeof(SabnzbdPriorityTypeConverter))]
public SabPriorityType Priority { get; set; }
public SabnzbdPriority Priority { get; set; }
[JsonProperty(PropertyName = "cat")]
public string Category { get; set; }

@ -0,0 +1,66 @@
using System;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabnzbdSettingsValidator : AbstractValidator<SabnzbdSettings>
{
public SabnzbdSettingsValidator()
{
RuleFor(c => c.Host).NotEmpty();
RuleFor(c => c.Port).GreaterThan(0);
//Todo: either API key or Username/Password needs to be valid
}
}
public class SabnzbdSettings : IProviderConfig
{
private static readonly SabnzbdSettingsValidator Validator = new SabnzbdSettingsValidator();
public SabnzbdSettings()
{
Host = "localhost";
Port = 8080;
TvCategory = "tv";
RecentTvPriority = (int)SabnzbdPriority.Default;
OlderTvPriority = (int)SabnzbdPriority.Default;
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
public String Host { get; set; }
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
public Int32 Port { get; set; }
[FieldDefinition(2, Label = "API Key", Type = FieldType.Textbox)]
public String ApiKey { get; set; }
[FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)]
public String Username { get; set; }
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox)]
public String TvCategory { get; set; }
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority))]
public Int32 RecentTvPriority { get; set; }
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority))]
public Int32 OlderTvPriority { get; set; }
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
public Boolean UseSsl { get; set; }
public ValidationResult Validate()
{
return Validator.Validate(this);
}
}
}

@ -0,0 +1,23 @@
using System;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class TestSabnzbdCommand : Command
{
public override bool SendUpdatesToClient
{
get
{
return true;
}
}
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 Boolean UseSsl { get; set; }
}
}

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Download
{
public abstract class DownloadClientBase<TSettings> : IDownloadClient where TSettings : IProviderConfig, new()
{
public Type ConfigContract
{
get
{
return typeof(TSettings);
}
}
public IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
return new List<ProviderDefinition>();
}
}
public ProviderDefinition Definition { get; set; }
protected TSettings Settings
{
get
{
return (TSettings)Definition.Settings;
}
}
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 = 0);
public abstract void RemoveFromQueue(string id);
public abstract void RemoveFromHistory(string id);
}
}

@ -0,0 +1,12 @@
using System;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Download
{
public class DownloadClientDefinition : ProviderDefinition
{
public Boolean Enable { get; set; }
public DownloadProtocol Protocol { get; set; }
}
}

@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Composition;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Download
{
public interface IDownloadClientFactory : IProviderFactory<IDownloadClient, DownloadClientDefinition>
{
List<IDownloadClient> Enabled();
}
public class DownloadClientFactory : ProviderFactory<IDownloadClient, DownloadClientDefinition>, IDownloadClientFactory
{
private readonly IDownloadClientRepository _providerRepository;
public DownloadClientFactory(IDownloadClientRepository providerRepository, IEnumerable<IDownloadClient> providers, IContainer container, Logger logger)
: base(providerRepository, providers, container, logger)
{
_providerRepository = providerRepository;
}
public List<IDownloadClient> Enabled()
{
return GetAvailableProviders().Where(n => ((DownloadClientDefinition)n.Definition).Enable).ToList();
}
}
}

@ -1,4 +1,6 @@
using NzbDrone.Core.Configuration;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Core.Download.Clients.Sabnzbd;
@ -12,42 +14,16 @@ namespace NzbDrone.Core.Download
public class DownloadClientProvider : IProvideDownloadClient
{
private readonly IDownloadClientFactory _downloadClientFactory;
private readonly SabnzbdClient _sabnzbdClient;
private readonly IConfigService _configService;
private readonly BlackholeProvider _blackholeProvider;
private readonly PneumaticClient _pneumaticClient;
private readonly NzbgetClient _nzbgetClient;
public DownloadClientProvider(SabnzbdClient sabnzbdClient, IConfigService configService,
BlackholeProvider blackholeProvider,
PneumaticClient pneumaticClient,
NzbgetClient nzbgetClient)
public DownloadClientProvider(IDownloadClientFactory downloadClientFactory)
{
_sabnzbdClient = sabnzbdClient;
_configService = configService;
_blackholeProvider = blackholeProvider;
_pneumaticClient = pneumaticClient;
_nzbgetClient = nzbgetClient;
_downloadClientFactory = downloadClientFactory;
}
public IDownloadClient GetDownloadClient()
{
switch (_configService.DownloadClient)
{
case DownloadClientType.Blackhole:
return _blackholeProvider;
case DownloadClientType.Pneumatic:
return _pneumaticClient;
case DownloadClientType.Nzbget:
return _nzbgetClient;
default:
return _sabnzbdClient;
}
return _downloadClientFactory.Enabled().FirstOrDefault();
}
}
}

@ -0,0 +1,20 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Download
{
public interface IDownloadClientRepository : IProviderRepository<DownloadClientDefinition>
{
}
public class DownloadClientRepository : ProviderRepository<DownloadClientDefinition>, IDownloadClientRepository
{
public DownloadClientRepository(IDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
}
}

@ -36,9 +36,9 @@ namespace NzbDrone.Core.Download
var downloadTitle = remoteEpisode.Release.Title;
var downloadClient = _downloadClientProvider.GetDownloadClient();
if (!downloadClient.IsConfigured)
if (downloadClient == null)
{
_logger.Warn("Download client {0} isn't configured yet.", downloadClient.GetType().Name);
_logger.Warn("Download client isn't configured yet.");
return;
}

@ -0,0 +1,18 @@
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; }
}
}

@ -0,0 +1,18 @@
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,12 +1,12 @@
using System.Collections.Generic;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Download
{
public interface IDownloadClient
public interface IDownloadClient : IProvider
{
string DownloadNzb(RemoteEpisode remoteEpisode);
bool IsConfigured { get; }
IEnumerable<QueueItem> GetQueue();
IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0);
void RemoveFromQueue(string id);

@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers
public enum DownloadProtocol
{
Usenet,
Torrent
Usenet = 1,
Torrent = 2
}
}

@ -1,12 +1,9 @@
using System.IO;
using System.Linq;
using System.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Metadata.Files;
using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.Metadata
{
@ -16,10 +13,10 @@ namespace NzbDrone.Core.Metadata
IHandle<SeriesRenamedEvent>
{
private readonly IMetadataFactory _metadataFactory;
private readonly MetadataFileService _metadataFileService;
private readonly IMetadataFileService _metadataFileService;
private readonly Logger _logger;
public NotificationService(IMetadataFactory metadataFactory, MetadataFileService metadataFileService, Logger logger)
public NotificationService(IMetadataFactory metadataFactory, IMetadataFileService metadataFileService, Logger logger)
{
_metadataFactory = metadataFactory;
_metadataFileService = metadataFileService;

@ -198,6 +198,8 @@
<Compile Include="Datastore\Migration\040_add_metadata_to_episodes_and_series.cs" />
<Compile Include="Datastore\Migration\039_add_metadata_tables.cs" />
<Compile Include="Datastore\Migration\041_fix_xbmc_season_images_metadata.cs" />
<Compile Include="Datastore\Migration\041_add_download_clients_table.cs" />
<Compile Include="Datastore\Migration\042_convert_config_to_download_clients.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
@ -240,13 +242,27 @@
<Compile Include="DecisionEngine\Specifications\RssSync\HistorySpecification.cs" />
<Compile Include="DiskSpace\DiskSpace.cs" />
<Compile Include="DiskSpace\DiskSpaceService.cs" />
<Compile Include="Download\Clients\Blackhole\Blackhole.cs" />
<Compile Include="Download\Clients\Blackhole\TestBlackholeCommand.cs" />
<Compile Include="Download\Clients\FolderSettings.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetSettings.cs" />
<Compile Include="Download\Clients\Nzbget\TestNzbgetCommand.cs" />
<Compile Include="Download\Clients\Pneumatic\Pneumatic.cs" />
<Compile Include="Download\Clients\Pneumatic\TestPneumaticCommand.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdAddResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdCategoryResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdVersionResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdSettings.cs" />
<Compile Include="Download\Clients\Sabnzbd\TestSabnzbdCommand.cs" />
<Compile Include="Download\DownloadClientBase.cs" />
<Compile Include="Download\DownloadClientDefinition.cs" />
<Compile Include="Download\DownloadClientFactory.cs" />
<Compile Include="Download\DownloadClientRepository.cs" />
<Compile Include="Download\Clients\Nzbget\JsonRequest.cs" />
<Compile Include="Download\Clients\Nzbget\NzbGetCommunicationProxy.cs" />
<Compile Include="Download\Clients\Sabnzbd\ConnectionInfoModel.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetProxy.cs" />
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdPriorityTypeConverter.cs" />
<Compile Include="Download\Clients\Sabnzbd\JsonConverters\SabnzbdQueueTimeConverter.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabAutoConfigureService.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabCommunicationProxy.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdProxy.cs" />
<Compile Include="Download\CheckForFailedDownloadCommand.cs" />
<Compile Include="Download\HistoryItem.cs" />
<Compile Include="Download\DownloadFailedEvent.cs" />
@ -476,10 +492,10 @@
<Compile Include="Download\Clients\Nzbget\EnqueueResponse.cs" />
<Compile Include="Download\Clients\Nzbget\ErrorModel.cs" />
<Compile Include="Download\Clients\Nzbget\JsonError.cs" />
<Compile Include="Download\Clients\Nzbget\NzbGetQueue.cs" />
<Compile Include="Download\Clients\Nzbget\NzbGetQueueItem.cs" />
<Compile Include="Download\Clients\Nzbget\PriorityType.cs" />
<Compile Include="Download\Clients\Nzbget\VersionModel.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetQueue.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetQueueItem.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetPriority.cs" />
<Compile Include="Download\Clients\Nzbget\VersionResponse.cs" />
<Compile Include="Organizer\NamingConfig.cs" />
<Compile Include="Parser\Language.cs" />
<Compile Include="Parser\Model\LocalEpisode.cs" />
@ -529,18 +545,13 @@
<Compile Include="Tv\RefreshEpisodeService.cs" />
<Compile Include="Tv\SeriesRepository.cs" />
<Compile Include="Qualities\QualityModel.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabAddResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabHistoryItem.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabHistory.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabJsonError.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabQueue.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabCategoryModel.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabModel.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabQueueItem.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabVersionModel.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdHistoryItem.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdHistory.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdJsonError.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdQueue.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdQueueItem.cs" />
<Compile Include="MediaCover\MediaCoverService.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetClient.cs" />
<Compile Include="Download\Clients\PneumaticClient.cs" />
<Compile Include="Download\Clients\Nzbget\Nzbget.cs" />
<Compile Include="MediaFiles\RecycleBinProvider.cs" />
<Compile Include="SeriesStats\SeriesStatistics.cs" />
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
@ -556,13 +567,10 @@
<Compile Include="MediaFiles\DiskScanService.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Download\Clients\BlackholeProvider.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Download\IDownloadClient.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Download\Clients\Sabnzbd\SabnzbdClient.cs">
<Compile Include="Download\Clients\Sabnzbd\Sabnzbd.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Download\DownloadService.cs">
@ -631,7 +639,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="Notifications\NotificationDefinition.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabPriorityType.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdPriority.cs" />
<Compile Include="MediaFiles\EpisodeFile.cs" />
<Compile Include="Tv\Episode.cs" />
<Compile Include="Instrumentation\Log.cs" />

@ -23,6 +23,13 @@ namespace NzbDrone.Core.Queue
public List<Queue> GetQueue()
{
var downloadClient = _downloadClientProvider.GetDownloadClient();
if (downloadClient == null)
{
_logger.Trace("Download client is not configured.");
return new List<Queue>();
}
var queueItems = downloadClient.GetQueue();
return MapQueue(queueItems);

@ -97,6 +97,8 @@ namespace NzbDrone.Core.Tv
return FindByTvdbId(tvdbId.Value);
}
var clean = Parser.Parser.CleanSeriesTitle(title);
return _seriesRepository.FindByTitle(Parser.Parser.CleanSeriesTitle(title));
}

@ -8,16 +8,16 @@
<option es3="false" />
<option forin="true" />
<option immed="true" />
<option latedef="true" />
<option newcap="true" />
<option noarg="true" />
<option noempty="false" />
<option nonew="true" />
<option plusplus="false" />
<option undef="true" />
<option unused="true" />
<option strict="true" />
<option trailing="false" />
<option latedef="true" />
<option unused="true" />
<option quotmark="single" />
<option maxdepth="3" />
<option asi="false" />

@ -35,6 +35,13 @@ define(
]);
}
if (field.type === 'path') {
return _templateRenderer.apply(field,
[
'Form/PathTemplate'
]);
}
return _templateRenderer.apply(field,
[
'Form/TextboxTemplate'

@ -0,0 +1,12 @@
<div class="control-group">
<label class="control-label">{{label}}</label>
<div class="controls">
<input type="text" name="fields.{{order}}.value" validation-name="{{name}}" class="x-path"/>
{{#if helpText}}
<span class="help-inline">
<i class="icon-nd-form-info" title="{{helpText}}"/>
</span>
{{/if}}
</div>
</div>

@ -0,0 +1,23 @@
'use strict';
define([
'marionette',
'Settings/DownloadClient/Add/DownloadClientAddItemView'
], function (Marionette, AddItemView) {
return Marionette.CompositeView.extend({
itemView : AddItemView,
itemViewContainer: '.add-download-client .items',
template : 'Settings/DownloadClient/Add/DownloadClientAddCollectionViewTemplate',
itemViewOptions: function () {
return {
downloadClientCollection: this.downloadClientCollection
};
},
initialize: function (options) {
this.downloadClientCollection = options.downloadClientCollection;
}
});
});

@ -0,0 +1,12 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Add Download Client</h3>
</div>
<div class="modal-body">
<div class="add-download-client add-thingies">
<ul class="items"></ul>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">close</button>
</div>

@ -0,0 +1,38 @@
'use strict';
define([
'AppLayout',
'marionette',
'Settings/DownloadClient/Edit/DownloadClientEditView'
], function (AppLayout, Marionette, EditView) {
return Marionette.ItemView.extend({
template: 'Settings/DownloadClient/Add/DownloadClientAddItemViewTemplate',
tagName : 'li',
events: {
'click': '_add'
},
initialize: function (options) {
this.downloadClientCollection = options.downloadClientCollection;
},
_add: function (e) {
if (this.$(e.target).hasClass('icon-info-sign')) {
return;
}
this.model.set({
id : undefined,
name : this.model.get('implementationName'),
onGrab : true,
onDownload : true,
onUpgrade : true
});
var editView = new EditView({ model: this.model, downloadClientCollection: this.downloadClientCollection });
AppLayout.modalRegion.show(editView);
}
});
});

@ -0,0 +1,10 @@
<div class="add-thingy span3">
<div class="row">
<div class="span3">
{{implementation}}
{{#if link}}
<a href="{{link}}"><i class="icon-info-sign"/></a>
{{/if}}
</div>
</div>
</div>

@ -0,0 +1,20 @@
'use strict';
define([
'AppLayout',
'Settings/DownloadClient/DownloadClientCollection',
'Settings/DownloadClient/Add/DownloadClientAddCollectionView'
], function (AppLayout, DownloadClientCollection, DownloadClientAddCollectionView) {
return ({
open: function (collection) {
var schemaCollection = new DownloadClientCollection();
var originalUrl = schemaCollection.url;
schemaCollection.url = schemaCollection.url + '/schema';
schemaCollection.fetch();
schemaCollection.url = originalUrl;
var view = new DownloadClientAddCollectionView({ collection: schemaCollection, downloadClientCollection: collection});
AppLayout.modalRegion.show(view);
}
});
});

@ -1,24 +0,0 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AutoComplete',
'bootstrap'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template : 'Settings/DownloadClient/BlackholeViewTemplate',
ui: {
'blackholeFolder': '.x-path'
},
onShow: function () {
this.ui.blackholeFolder.autoComplete('/directories');
}
});
return AsModelBoundView.call(view);
});

@ -1,13 +0,0 @@
<fieldset>
<legend>Blackhole</legend>
<div class="control-group">
<label class="control-label">Blackhole Folder</label>
<div class="controls">
<input type="text" name="blackholeFolder" class="x-path"/>
<span class="help-inline">
<i class="icon-nd-form-info" title="The folder where your download client will pickup .nzb files"/>
</span>
</div>
</div>
</fieldset>

@ -0,0 +1,23 @@
'use strict';
define(
[
'vent',
'marionette'
], function (vent, Marionette) {
return Marionette.ItemView.extend({
template: 'Settings/DownloadClient/Delete/DownloadClientDeleteViewTemplate',
events: {
'click .x-confirm-delete': '_delete'
},
_delete: function () {
this.model.destroy({
wait : true,
success: function () {
vent.trigger(vent.Commands.CloseModalCommand);
}
});
}
});
});

@ -0,0 +1,11 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Delete Download Client</h3>
</div>
<div class="modal-body">
<p>Are you sure you want to delete '{{name}}'?</p>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">cancel</button>
<button class="btn btn-danger x-confirm-delete">delete</button>
</div>

@ -0,0 +1,12 @@
'use strict';
define(
[
'backbone',
'Settings/DownloadClient/DownloadClientModel'
], function (Backbone, DownloadClientModel) {
return Backbone.Collection.extend({
model: DownloadClientModel,
url : window.NzbDrone.ApiRoot + '/downloadclient'
});
});

@ -0,0 +1,31 @@
'use strict';
define(
[
'underscore',
'AppLayout',
'marionette',
'Settings/DownloadClient/DownloadClientItemView',
'Settings/DownloadClient/Add/SchemaModal'
], function (_, AppLayout, Marionette, DownloadClientItemView, SchemaModal) {
return Marionette.CompositeView.extend({
itemView : DownloadClientItemView,
itemViewContainer: '#x-download-clients',
template : 'Settings/DownloadClient/DownloadClientCollectionViewTemplate',
ui: {
'addCard': '.x-add-card'
},
events: {
'click .x-add-card': '_openSchemaModal'
},
appendHtml: function (collectionView, itemView, index) {
collectionView.ui.addCard.parent('li').before(itemView.el);
},
_openSchemaModal: function () {
SchemaModal.open(this.collection);
}
});
});

@ -0,0 +1,16 @@
<fieldset>
<legend>Download Clients</legend>
<div class="row">
<div class="span12">
<ul id="x-download-clients" class="download-client-list">
<li>
<div class="download-client-item thingy add-card x-add-card">
<span class="center well">
<i class="icon-plus" title="Add Download Client"/>
</span>
</div>
</li>
</ul>
</div>
</div>
</fieldset>

@ -0,0 +1,34 @@
'use strict';
define(
[
'AppLayout',
'marionette',
'Settings/DownloadClient/Edit/DownloadClientEditView',
'Settings/DownloadClient/Delete/DownloadClientDeleteView'
], function (AppLayout, Marionette, EditView, DeleteView) {
return Marionette.ItemView.extend({
template: 'Settings/DownloadClient/DownloadClientItemViewTemplate',
tagName : 'li',
events: {
'click .x-edit' : '_edit',
'click .x-delete' : '_delete'
},
initialize: function () {
this.listenTo(this.model, 'sync', this.render);
},
_edit: function () {
var view = new EditView({ model: this.model});
AppLayout.modalRegion.show(view);
},
_delete: function () {
var view = new DeleteView({ model: this.model});
AppLayout.modalRegion.show(view);
}
});
});

@ -0,0 +1,17 @@
<div class="download-client-item thingy">
<div>
<h3>{{name}}</h3>
<span class="btn-group pull-right">
<button class="btn btn-mini btn-icon-only x-edit"><i class="icon-nd-edit"/></button>
<button class="btn btn-mini btn-icon-only x-delete"><i class="icon-nd-delete"/></button>
</span>
</div>
<div class="settings">
{{#if enable}}
<span class="label label-success">Enabled</span>
{{else}}
<span class="label">Not Enabled</span>
{{/if}}
</div>
</div>

@ -0,0 +1,40 @@
'use strict';
define(
[
'marionette',
'Settings/DownloadClient/DownloadClientCollection',
'Settings/DownloadClient/DownloadClientCollectionView',
'Mixins/AsModelBoundView',
'Mixins/AutoComplete',
'bootstrap'
], function (Marionette, DownloadClientCollection, DownloadClientCollectionView, AsModelBoundView) {
var view = Marionette.Layout.extend({
template : 'Settings/DownloadClient/DownloadClientLayoutTemplate',
regions: {
downloadClients: '#x-download-clients-region'
},
ui: {
droneFactory: '.x-path'
},
events: {
'change .x-download-client': 'downloadClientChanged'
},
initialize: function () {
this.downloadClientCollection = new DownloadClientCollection();
this.downloadClientCollection.fetch();
},
onShow: function () {
this.downloadClients.show(new DownloadClientCollectionView({ collection: this.downloadClientCollection }));
this.ui.droneFactory.autoComplete('/directories');
}
});
return AsModelBoundView.call(view);
});

@ -0,0 +1,16 @@
<div id="x-download-clients-region"></div>
<fieldset class="form-horizontal">
<legend>Options</legend>
<div class="control-group">
<label class="control-label">Drone Factory</label>
<div class="controls">
<input type="text" name="downloadedEpisodesFolder" class="x-path"/>
<span class="help-inline">
<i class="icon-nd-form-info" title="The folder where your download client downloads TV shows to (Completed Download Directory)"/>
<i class="icon-nd-form-warning" title="Do not use the folder that contains some or all of your sorted and named TV shows - doing so could cause data loss"></i>
</span>
</div>
</div>
</fieldset>

@ -0,0 +1,10 @@
'use strict';
define(
[
'backbone.deepmodel'
], function (DeepModel) {
return DeepModel.DeepModel.extend({
});
});

@ -0,0 +1,93 @@
'use strict';
define(
[
'vent',
'AppLayout',
'marionette',
'Settings/DownloadClient/Delete/DownloadClientDeleteView',
'Commands/CommandController',
'Mixins/AsModelBoundView',
'underscore',
'Form/FormBuilder',
'Mixins/AutoComplete',
'bootstrap'
], function (vent, AppLayout, Marionette, DeleteView, CommandController, AsModelBoundView, _) {
var model = Marionette.ItemView.extend({
template: 'Settings/DownloadClient/Edit/DownloadClientEditViewTemplate',
ui: {
path : '.x-path',
modalBody : '.modal-body'
},
events: {
'click .x-save' : '_save',
'click .x-save-and-add': '_saveAndAdd',
'click .x-delete' : '_delete',
'click .x-back' : '_back',
'click .x-test' : '_test'
},
initialize: function (options) {
this.downloadClientCollection = options.downloadClientCollection;
},
onShow: function () {
//Hack to deal with modals not overflowing
if (this.ui.path.length > 0) {
this.ui.modalBody.addClass('modal-overflow');
}
this.ui.path.autoComplete('/directories');
},
_save: function () {
var self = this;
var promise = this.model.save();
if (promise) {
promise.done(function () {
self.downloadClientCollection.add(self.model, { merge: true });
vent.trigger(vent.Commands.CloseModalCommand);
});
}
},
_saveAndAdd: function () {
var self = this;
var promise = this.model.save();
if (promise) {
promise.done(function () {
self.notificationCollection.add(self.model, { merge: true });
require('Settings/DownloadClient/Add/SchemaModal').open(self.downloadClientCollection);
});
}
},
_delete: function () {
var view = new DeleteView({ model: this.model });
AppLayout.modalRegion.show(view);
},
_back: function () {
require('Settings/DownloadClient/Add/SchemaModal').open(this.downloadClientCollection);
},
_test: function () {
var testCommand = 'test{0}'.format(this.model.get('implementation'));
var properties = {};
_.each(this.model.get('fields'), function (field) {
properties[field.name] = field.value;
});
CommandController.Execute(testCommand, properties);
}
});
return AsModelBoundView.call(model);
});

@ -0,0 +1,59 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
{{#if id}}
<h3>Edit - {{implementation}}</h3>
{{else}}
<h3>Add - {{implementation}}</h3>
{{/if}}
</div>
<div class="modal-body download-client-modal">
<div class="form-horizontal">
<div class="control-group">
<label class="control-label">Name</label>
<div class="controls">
<input type="text" name="name"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Enable</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="enable"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
{{formBuilder}}
</div>
</div>
<div class="modal-footer">
{{#if id}}
<button class="btn btn-danger pull-left x-delete">delete</button>
{{else}}
<button class="btn pull-left x-back">back</button>
{{/if}}
<button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button>
<button class="btn" data-dismiss="modal">cancel</button>
<div class="btn-group">
<button class="btn btn-primary x-save">save</button>
<button class="btn btn-icon-only btn-primary dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="save-and-add x-save-and-add">
save and add
</li>
</ul>
</div>
</div>

@ -1,78 +0,0 @@
'use strict';
define(
[
'marionette',
'Settings/DownloadClient/SabView',
'Settings/DownloadClient/BlackholeView',
'Settings/DownloadClient/PneumaticView',
'Settings/DownloadClient/NzbgetView',
'Mixins/AsModelBoundView',
'Mixins/AutoComplete',
'bootstrap'
], function (Marionette, SabView, BlackholeView, PneumaticView, NzbgetView, AsModelBoundView) {
var view = Marionette.Layout.extend({
template : 'Settings/DownloadClient/LayoutTemplate',
regions: {
downloadClient: '#download-client-settings-region'
},
ui: {
downloadClientSelect: '.x-download-client',
downloadedEpisodesFolder: '.x-path'
},
events: {
'change .x-download-client': 'downloadClientChanged'
},
onShow: function () {
this.sabView = new SabView({ model: this.model});
this.blackholeView = new BlackholeView({ model: this.model});
this.pneumaticView = new PneumaticView({ model: this.model});
this.nzbgetView = new NzbgetView({ model: this.model});
this.ui.downloadedEpisodesFolder.autoComplete('/directories');
var client = this.model.get('downloadClient');
this.refreshUIVisibility(client);
},
downloadClientChanged: function () {
var clientId = this.ui.downloadClientSelect.val();
this.refreshUIVisibility(clientId);
},
refreshUIVisibility: function (clientId) {
if (!clientId) {
clientId = 'sabnzbd';
}
switch (clientId.toString()) {
case 'sabnzbd':
this.downloadClient.show(this.sabView);
break;
case 'blackhole':
this.downloadClient.show(this.blackholeView);
break;
case 'pneumatic':
this.downloadClient.show(this.pneumaticView);
break;
case 'nzbget':
this.downloadClient.show(this.nzbgetView);
break;
default :
throw 'unknown download client id' + clientId;
}
}
});
return AsModelBoundView.call(view);
});

@ -1,29 +0,0 @@
<fieldset class="form-horizontal">
<legend>General</legend>
<div class="control-group">
<label class="control-label">Download Client</label>
<div class="controls">
<select class="inputClass x-download-client" name="downloadClient">
<option value="sabnzbd">SABnzbd</option>
<option value="blackhole">Blackhole</option>
<option value="pneumatic">Pneumatic</option>
<option value="nzbget">NZBGet</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label">Drone Factory</label>
<div class="controls">
<input type="text" name="downloadedEpisodesFolder" class="x-path"/>
<span class="help-inline">
<i class="icon-nd-form-info" title="The folder where your download client downloads TV shows to (Completed Download Directory)"/>
<i class="icon-nd-form-warning" title="Do not use the folder that contains some or all of your sorted and named TV shows - doing so could cause data loss"></i>
</span>
</div>
</div>
</fieldset>
<div id="download-client-settings-region" class="form-horizontal"></div>

@ -1,15 +0,0 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'bootstrap'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template : 'Settings/DownloadClient/NzbgetViewTemplate'
});
return AsModelBoundView.call(view);
});

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

Loading…
Cancel
Save