Merge pull request #960 from Sonarr/nzb-vortex
NZBVortex Download Client
This commit is contained in:
commit
536aa350f0
|
@ -92,5 +92,13 @@ namespace NzbDrone.Common.Extensions
|
|||
|
||||
return "\"" + text + "\"";
|
||||
}
|
||||
|
||||
public static byte[] HexToByteArray(this string input)
|
||||
{
|
||||
return Enumerable.Range(0, input.Length)
|
||||
.Where(x => x%2 == 0)
|
||||
.Select(x => Convert.ToByte(input.Substring(x, 2), 16))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients.Nzbget;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Download.Clients.NzbVortex;
|
||||
using NzbDrone.Core.Download.Clients.NzbVortex.Responses;
|
||||
|
||||
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbVortexTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class NzbVortexFixture : DownloadClientFixtureBase<NzbVortex>
|
||||
{
|
||||
private NzbVortexQueueItem _queued;
|
||||
private NzbVortexQueueItem _failed;
|
||||
private NzbVortexQueueItem _completed;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Subject.Definition = new DownloadClientDefinition();
|
||||
Subject.Definition.Settings = new NzbVortexSettings
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 2222,
|
||||
ApiKey = "1234-ABCD",
|
||||
TvCategory = "tv",
|
||||
RecentTvPriority = (int)NzbgetPriority.High
|
||||
};
|
||||
|
||||
_queued = new NzbVortexQueueItem
|
||||
{
|
||||
Id = RandomNumber,
|
||||
DownloadedSize = 1000,
|
||||
TotalDownloadSize = 10,
|
||||
GroupName = "tv",
|
||||
UiTitle = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE"
|
||||
};
|
||||
|
||||
_failed = new NzbVortexQueueItem
|
||||
{
|
||||
DownloadedSize = 1000,
|
||||
TotalDownloadSize = 1000,
|
||||
GroupName = "tv",
|
||||
UiTitle = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
|
||||
DestinationPath = "somedirectory",
|
||||
State = NzbVortexStateType.UncompressFailed,
|
||||
};
|
||||
|
||||
_completed = new NzbVortexQueueItem
|
||||
{
|
||||
DownloadedSize = 1000,
|
||||
TotalDownloadSize = 1000,
|
||||
GroupName = "tv",
|
||||
UiTitle = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
|
||||
DestinationPath = "/remote/mount/tv/Droned.S01E01.Pilot.1080p.WEB-DL-DRONE",
|
||||
State = NzbVortexStateType.Done
|
||||
};
|
||||
}
|
||||
|
||||
protected void GivenFailedDownload()
|
||||
{
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns((string)null);
|
||||
}
|
||||
|
||||
protected void GivenSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.DownloadNzb(It.IsAny<byte[]>(), It.IsAny<string>(), It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(Guid.NewGuid().ToString().Replace("-", ""));
|
||||
}
|
||||
|
||||
protected virtual void GivenQueue(NzbVortexQueueItem queue)
|
||||
{
|
||||
var list = new List<NzbVortexQueueItem>();
|
||||
|
||||
list.AddIfNotNull(queue);
|
||||
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.GetQueue(It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new NzbVortexQueue
|
||||
{
|
||||
Items = list
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_return_no_items_when_queue_is_empty()
|
||||
{
|
||||
GivenQueue(null);
|
||||
|
||||
Subject.GetItems().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void queued_item_should_have_required_properties()
|
||||
{
|
||||
GivenQueue(_queued);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyQueued(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void paused_item_should_have_required_properties()
|
||||
{
|
||||
_queued.IsPaused = true;
|
||||
GivenQueue(_queued);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyPaused(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void downloading_item_should_have_required_properties()
|
||||
{
|
||||
_queued.State = NzbVortexStateType.Downloading;
|
||||
GivenQueue(_queued);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyDownloading(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void completed_download_should_have_required_properties()
|
||||
{
|
||||
GivenQueue(_completed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyCompleted(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void failed_item_should_have_required_properties()
|
||||
{
|
||||
GivenQueue(_failed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
VerifyFailed(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_report_UncompressFailed_as_failed()
|
||||
{
|
||||
_queued.State = NzbVortexStateType.UncompressFailed;
|
||||
GivenQueue(_failed);
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.First().Status.Should().Be(DownloadItemStatus.Failed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_report_CheckFailedDataCorrupt_as_failed()
|
||||
{
|
||||
_queued.State = NzbVortexStateType.CheckFailedDataCorrupt;
|
||||
GivenQueue(_failed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
result.Status.Should().Be(DownloadItemStatus.Failed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_report_BadlyEncoded_as_failed()
|
||||
{
|
||||
_queued.State = NzbVortexStateType.BadlyEncoded;
|
||||
GivenQueue(_failed);
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.First().Status.Should().Be(DownloadItemStatus.Failed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_return_unique_id()
|
||||
{
|
||||
GivenSuccessfulDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
var id = Subject.Download(remoteEpisode);
|
||||
|
||||
id.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Download_should_throw_if_failed()
|
||||
{
|
||||
GivenFailedDownload();
|
||||
|
||||
var remoteEpisode = CreateRemoteEpisode();
|
||||
|
||||
Assert.Throws<DownloadClientException>(() => Subject.Download(remoteEpisode));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_should_ignore_downloads_from_other_categories()
|
||||
{
|
||||
_completed.GroupName = "mycat";
|
||||
|
||||
GivenQueue(null);
|
||||
|
||||
var items = Subject.GetItems();
|
||||
|
||||
items.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remap_storage_if_mounted()
|
||||
{
|
||||
Mocker.GetMock<IRemotePathMappingService>()
|
||||
.Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny<OsPath>()))
|
||||
.Returns(new OsPath(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic()));
|
||||
|
||||
GivenQueue(_completed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_files_if_completed_download_is_not_in_a_job_folder()
|
||||
{
|
||||
Mocker.GetMock<IRemotePathMappingService>()
|
||||
.Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny<OsPath>()))
|
||||
.Returns(new OsPath(@"O:\mymount\".AsOsAgnostic()));
|
||||
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.GetFiles(It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new NzbVortexFiles{ Files = new List<NzbVortexFile> { new NzbVortexFile { FileName = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.mkv" } } });
|
||||
|
||||
_completed.State = NzbVortexStateType.Done;
|
||||
GivenQueue(_completed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.mkv".AsOsAgnostic());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_be_warning_if_more_than_one_file_is_not_in_a_job_folder()
|
||||
{
|
||||
Mocker.GetMock<IRemotePathMappingService>()
|
||||
.Setup(v => v.RemapRemoteToLocal("127.0.0.1", It.IsAny<OsPath>()))
|
||||
.Returns(new OsPath(@"O:\mymount\".AsOsAgnostic()));
|
||||
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(s => s.GetFiles(It.IsAny<int>(), It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new NzbVortexFiles { Files = new List<NzbVortexFile>
|
||||
{
|
||||
new NzbVortexFile { FileName = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.mkv" },
|
||||
new NzbVortexFile { FileName = "Droned.S01E01.Pilot.1080p.WEB-DL-DRONE.nfo" }
|
||||
} });
|
||||
|
||||
_completed.State = NzbVortexStateType.Done;
|
||||
GivenQueue(_completed);
|
||||
|
||||
var result = Subject.GetItems().Single();
|
||||
|
||||
result.Status.Should().Be(DownloadItemStatus.Warning);
|
||||
}
|
||||
|
||||
[TestCase("1.0", false)]
|
||||
[TestCase("2.2", false)]
|
||||
[TestCase("2.3", true)]
|
||||
[TestCase("2.4", true)]
|
||||
[TestCase("3.0", true)]
|
||||
public void should_test_api_version(string version, bool expected)
|
||||
{
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(v => v.GetGroups(It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new List<NzbVortexGroup> { new NzbVortexGroup { GroupName = ((NzbVortexSettings)Subject.Definition.Settings).TvCategory } });
|
||||
|
||||
Mocker.GetMock<INzbVortexProxy>()
|
||||
.Setup(v => v.GetApiVersion(It.IsAny<NzbVortexSettings>()))
|
||||
.Returns(new NzbVortexApiVersionResponse { ApiLevel = version });
|
||||
|
||||
var error = Subject.Test();
|
||||
|
||||
error.IsValid.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -161,6 +161,7 @@
|
|||
<Compile Include="Download\DownloadClientTests\DelugeTests\DelugeFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\DownloadClientFixtureBase.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\NzbgetTests\NzbgetFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\NzbVortexTests\NzbVortexFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\PneumaticProviderFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\RTorrentTests\RTorrentFixture.cs" />
|
||||
<Compile Include="Download\DownloadClientTests\QBittorrentTests\QBittorrentFixture.cs" />
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.JsonConverters
|
||||
{
|
||||
public class NzbVortexLoginResultTypeConverter : JsonConverter
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var priorityType = (NzbVortexLoginResultType)value;
|
||||
writer.WriteValue(priorityType.ToString().ToLower());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var result = reader.Value.ToString().Replace("_", string.Empty);
|
||||
|
||||
NzbVortexLoginResultType output;
|
||||
Enum.TryParse(result, true, out output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(NzbVortexLoginResultType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.JsonConverters
|
||||
{
|
||||
public class NzbVortexResultTypeConverter : JsonConverter
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var priorityType = (NzbVortexResultType)value;
|
||||
writer.WriteValue(priorityType.ToString().ToLower());
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var result = reader.Value.ToString().Replace("_", string.Empty);
|
||||
|
||||
NzbVortexResultType output;
|
||||
Enum.TryParse(result, true, out output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(NzbVortexResultType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortex : UsenetClientBase<NzbVortexSettings>
|
||||
{
|
||||
private readonly INzbVortexProxy _proxy;
|
||||
|
||||
public NzbVortex(INzbVortexProxy proxy,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IRemotePathMappingService remotePathMappingService,
|
||||
Logger logger)
|
||||
: base(httpClient, configService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
protected override string AddFromNzbFile(RemoteEpisode remoteEpisode, string filename, byte[] fileContent)
|
||||
{
|
||||
var priority = remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority;
|
||||
|
||||
var response = _proxy.DownloadNzb(fileContent, filename, priority, Settings);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
throw new DownloadClientException("Failed to add nzb {0}", filename);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "NZBVortex";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
NzbVortexQueue vortexQueue;
|
||||
|
||||
try
|
||||
{
|
||||
vortexQueue = _proxy.GetQueue(30, Settings);
|
||||
}
|
||||
catch (DownloadClientException ex)
|
||||
{
|
||||
_logger.Warn("Couldn't get download queue. {0}", ex.Message);
|
||||
return Enumerable.Empty<DownloadClientItem>();
|
||||
}
|
||||
|
||||
var queueItems = new List<DownloadClientItem>();
|
||||
|
||||
foreach (var vortexQueueItem in vortexQueue.Items)
|
||||
{
|
||||
var queueItem = new DownloadClientItem();
|
||||
|
||||
queueItem.DownloadClient = Definition.Name;
|
||||
queueItem.DownloadId = vortexQueueItem.AddUUID ?? vortexQueueItem.Id.ToString();
|
||||
queueItem.Category = vortexQueueItem.GroupName;
|
||||
queueItem.Title = vortexQueueItem.UiTitle;
|
||||
queueItem.TotalSize = vortexQueueItem.TotalDownloadSize;
|
||||
queueItem.RemainingSize = vortexQueueItem.TotalDownloadSize - vortexQueueItem.DownloadedSize;
|
||||
queueItem.RemainingTime = null;
|
||||
|
||||
if (vortexQueueItem.IsPaused)
|
||||
{
|
||||
queueItem.Status = DownloadItemStatus.Paused;
|
||||
}
|
||||
else switch (vortexQueueItem.State)
|
||||
{
|
||||
case NzbVortexStateType.Waiting:
|
||||
queueItem.Status = DownloadItemStatus.Queued;
|
||||
break;
|
||||
case NzbVortexStateType.Done:
|
||||
queueItem.Status = DownloadItemStatus.Completed;
|
||||
break;
|
||||
case NzbVortexStateType.UncompressFailed:
|
||||
case NzbVortexStateType.CheckFailedDataCorrupt:
|
||||
case NzbVortexStateType.BadlyEncoded:
|
||||
queueItem.Status = DownloadItemStatus.Failed;
|
||||
break;
|
||||
default:
|
||||
queueItem.Status = DownloadItemStatus.Downloading;
|
||||
break;
|
||||
}
|
||||
|
||||
queueItem.OutputPath = GetOutputPath(vortexQueueItem, queueItem);
|
||||
|
||||
if (vortexQueueItem.State == NzbVortexStateType.PasswordRequest)
|
||||
{
|
||||
queueItem.IsEncrypted = true;
|
||||
}
|
||||
|
||||
if (queueItem.Status == DownloadItemStatus.Completed)
|
||||
{
|
||||
queueItem.RemainingTime = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
queueItems.Add(queueItem);
|
||||
}
|
||||
|
||||
return queueItems;
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
{
|
||||
// Try to find the download by numerical ID, otherwise try by AddUUID
|
||||
int id;
|
||||
|
||||
if (int.TryParse(downloadId, out id))
|
||||
{
|
||||
_proxy.Remove(id, deleteData, Settings);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var queue = _proxy.GetQueue(30, Settings);
|
||||
var queueItem = queue.Items.FirstOrDefault(c => c.AddUUID == downloadId);
|
||||
|
||||
if (queueItem != null)
|
||||
{
|
||||
_proxy.Remove(queueItem.Id, deleteData, Settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<NzbVortexGroup> GetGroups()
|
||||
{
|
||||
return _proxy.GetGroups(Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientStatus GetStatus()
|
||||
{
|
||||
var status = new DownloadClientStatus
|
||||
{
|
||||
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost"
|
||||
};
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
failures.AddIfNotNull(TestConnection());
|
||||
failures.AddIfNotNull(TestApiVersion());
|
||||
failures.AddIfNotNull(TestAuthentication());
|
||||
failures.AddIfNotNull(TestCategory());
|
||||
}
|
||||
|
||||
private ValidationFailure TestConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.GetVersion(Settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException(ex.Message, ex);
|
||||
return new ValidationFailure("Host", "Unable to connect to NZBVortex");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ValidationFailure TestApiVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = _proxy.GetApiVersion(Settings);
|
||||
var version = new Version(response.ApiLevel);
|
||||
|
||||
if (version.Major < 2 || (version.Major == 2 && version.Minor < 3))
|
||||
{
|
||||
return new ValidationFailure("Host", "NZBVortex needs to be updated");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException(ex.Message, ex);
|
||||
return new ValidationFailure("Host", "Unable to connect to NZBVortex");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ValidationFailure TestAuthentication()
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.GetQueue(1, Settings);
|
||||
}
|
||||
catch (NzbVortexAuthenticationException ex)
|
||||
{
|
||||
return new ValidationFailure("ApiKey", "API Key Incorrect");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ValidationFailure TestCategory()
|
||||
{
|
||||
var group = GetGroups().FirstOrDefault(c => c.GroupName == Settings.TvCategory);
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return new NzbDroneValidationFailure("TvCategory", "Group does not exist")
|
||||
{
|
||||
DetailedDescription = "The Group you entered doesn't exist in NzbVortex. Go to NzbVortex to create it."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private OsPath GetOutputPath(NzbVortexQueueItem vortexQueueItem, DownloadClientItem queueItem)
|
||||
{
|
||||
var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(vortexQueueItem.DestinationPath));
|
||||
|
||||
if (outputPath.FileName == vortexQueueItem.UiTitle)
|
||||
{
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
// If the release isn't done yet, skip the files check and return null
|
||||
if (vortexQueueItem.State != NzbVortexStateType.Done)
|
||||
{
|
||||
return new OsPath(null);
|
||||
}
|
||||
|
||||
var filesResponse = _proxy.GetFiles(vortexQueueItem.Id, Settings);
|
||||
|
||||
if (filesResponse.Files.Count > 1)
|
||||
{
|
||||
var message = string.Format("Download contains multiple files and is not in a job folder: {0}", outputPath);
|
||||
|
||||
queueItem.Status = DownloadItemStatus.Warning;
|
||||
queueItem.Message = message;
|
||||
|
||||
_logger.Debug(message);
|
||||
}
|
||||
|
||||
return new OsPath(Path.Combine(outputPath.FullPath, filesResponse.Files.First().FileName));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
class NzbVortexAuthenticationException : DownloadClientException
|
||||
{
|
||||
public NzbVortexAuthenticationException(string message, params object[] args) : base(message, args)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexAuthenticationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexAuthenticationException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexAuthenticationException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortexFile
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public NzbVortexStateType State { get; set; }
|
||||
public long DileSize { get; set; }
|
||||
public long DownloadedSize { get; set; }
|
||||
public long TotalDownloadedSize { get; set; }
|
||||
public bool ExtractPasswordRequired { get; set; }
|
||||
public string ExtractPassword { get; set; }
|
||||
public long PostDate { get; set; }
|
||||
public bool Crc32CheckFailed { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortexFiles
|
||||
{
|
||||
public List<NzbVortexFile> Files { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortexGroup
|
||||
{
|
||||
public string GroupName { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortexJsonError
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Error { get; set; }
|
||||
|
||||
public bool Failed
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(Status) &&
|
||||
Status.Equals("false", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public enum NzbVortexLoginResultType
|
||||
{
|
||||
Successful,
|
||||
Failed
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
class NzbVortexNotLoggedInException : DownloadClientException
|
||||
{
|
||||
public NzbVortexNotLoggedInException() : this("Authentication is required")
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexNotLoggedInException(string message, params object[] args) : base(message, args)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexNotLoggedInException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexNotLoggedInException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
|
||||
{
|
||||
}
|
||||
|
||||
public NzbVortexNotLoggedInException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public enum NzbVortexPriority
|
||||
{
|
||||
Low = -1,
|
||||
Normal = 0,
|
||||
High = 1,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.CodeDom;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Rest;
|
||||
using NzbDrone.Core.Download.Clients.NzbVortex.Responses;
|
||||
using RestSharp;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public interface INzbVortexProxy
|
||||
{
|
||||
string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings);
|
||||
void Remove(int id, bool deleteData, NzbVortexSettings settings);
|
||||
NzbVortexVersionResponse GetVersion(NzbVortexSettings settings);
|
||||
NzbVortexApiVersionResponse GetApiVersion(NzbVortexSettings settings);
|
||||
List<NzbVortexGroup> GetGroups(NzbVortexSettings settings);
|
||||
NzbVortexQueue GetQueue(int doneLimit, NzbVortexSettings settings);
|
||||
NzbVortexFiles GetFiles(int id, NzbVortexSettings settings);
|
||||
}
|
||||
|
||||
public class NzbVortexProxy : INzbVortexProxy
|
||||
{
|
||||
private readonly ICached<string> _authCache;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NzbVortexProxy(ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_authCache = cacheManager.GetCache<string>(GetType(), "authCache");
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string DownloadNzb(byte[] nzbData, string filename, int priority, NzbVortexSettings settings)
|
||||
{
|
||||
var request = BuildRequest("/nzb/add", Method.POST, true, settings);
|
||||
|
||||
request.AddFile("name", nzbData, filename, "application/x-nzb");
|
||||
request.AddQueryParameter("priority", priority.ToString());
|
||||
|
||||
if (settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddQueryParameter("groupname", settings.TvCategory);
|
||||
}
|
||||
|
||||
var response = ProcessRequest<NzbVortexAddResponse>(request, settings);
|
||||
|
||||
return response.Id;
|
||||
}
|
||||
|
||||
public void Remove(int id, bool deleteData, NzbVortexSettings settings)
|
||||
{
|
||||
var request = BuildRequest(string.Format("nzb/{0}/cancel", id), Method.GET, true, settings);
|
||||
|
||||
if (deleteData)
|
||||
{
|
||||
request.Resource += "Delete";
|
||||
}
|
||||
|
||||
ProcessRequest(request, settings);
|
||||
}
|
||||
|
||||
public NzbVortexVersionResponse GetVersion(NzbVortexSettings settings)
|
||||
{
|
||||
var request = BuildRequest("app/appversion", Method.GET, false, settings);
|
||||
var response = ProcessRequest<NzbVortexVersionResponse>(request, settings);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public NzbVortexApiVersionResponse GetApiVersion(NzbVortexSettings settings)
|
||||
{
|
||||
var request = BuildRequest("app/apilevel", Method.GET, false, settings);
|
||||
var response = ProcessRequest<NzbVortexApiVersionResponse>(request, settings);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public List<NzbVortexGroup> GetGroups(NzbVortexSettings settings)
|
||||
{
|
||||
var request = BuildRequest("group", Method.GET, true, settings);
|
||||
var response = ProcessRequest<NzbVortexGroupResponse>(request, settings);
|
||||
|
||||
return response.Groups;
|
||||
}
|
||||
|
||||
public NzbVortexQueue GetQueue(int doneLimit, NzbVortexSettings settings)
|
||||
{
|
||||
var request = BuildRequest("nzb", Method.GET, true, settings);
|
||||
|
||||
if (settings.TvCategory.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
request.AddQueryParameter("groupName", settings.TvCategory);
|
||||
}
|
||||
|
||||
request.AddQueryParameter("limitDone", doneLimit.ToString());
|
||||
|
||||
var response = ProcessRequest<NzbVortexQueue>(request, settings);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public NzbVortexFiles GetFiles(int id, NzbVortexSettings settings)
|
||||
{
|
||||
var request = BuildRequest(string.Format("file/{0}", id), Method.GET, true, settings);
|
||||
var response = ProcessRequest<NzbVortexFiles>(request, settings);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private string GetSessionId(bool force, NzbVortexSettings settings)
|
||||
{
|
||||
var authCacheKey = string.Format("{0}_{1}_{2}", settings.Host, settings.Port, settings.ApiKey);
|
||||
|
||||
if (force)
|
||||
{
|
||||
_authCache.Remove(authCacheKey);
|
||||
}
|
||||
|
||||
var sessionId = _authCache.Get(authCacheKey, () => Authenticate(settings));
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
private string Authenticate(NzbVortexSettings settings)
|
||||
{
|
||||
var nonce = GetNonce(settings);
|
||||
var cnonce = Guid.NewGuid().ToString();
|
||||
var hashString = string.Format("{0}:{1}:{2}", nonce, cnonce, settings.ApiKey);
|
||||
var sha256 = hashString.SHA256Hash();
|
||||
var base64 = Convert.ToBase64String(sha256.HexToByteArray());
|
||||
var request = BuildRequest("auth/login", Method.GET, false, settings);
|
||||
|
||||
request.AddQueryParameter("nonce", nonce);
|
||||
request.AddQueryParameter("cnonce", cnonce);
|
||||
request.AddQueryParameter("hash", base64);
|
||||
|
||||
var response = ProcessRequest(request, settings);
|
||||
var result = Json.Deserialize<NzbVortexAuthResponse>(response);
|
||||
|
||||
if (result.LoginResult == NzbVortexLoginResultType.Failed)
|
||||
{
|
||||
throw new NzbVortexAuthenticationException("Authentication failed, check your API Key");
|
||||
}
|
||||
|
||||
return result.SessionId;
|
||||
}
|
||||
|
||||
private string GetNonce(NzbVortexSettings settings)
|
||||
{
|
||||
var request = BuildRequest("auth/nonce", Method.GET, false, settings);
|
||||
|
||||
return ProcessRequest<NzbVortexAuthNonceResponse>(request, settings).AuthNonce;
|
||||
}
|
||||
|
||||
private IRestClient BuildClient(NzbVortexSettings settings)
|
||||
{
|
||||
var url = string.Format(@"https://{0}:{1}/api", settings.Host, settings.Port);
|
||||
|
||||
return RestClientFactory.BuildClient(url);
|
||||
}
|
||||
|
||||
private IRestRequest BuildRequest(string resource, Method method, bool requiresAuthentication, NzbVortexSettings settings)
|
||||
{
|
||||
var request = new RestRequest(resource, method);
|
||||
|
||||
if (requiresAuthentication)
|
||||
{
|
||||
request.AddQueryParameter("sessionid", GetSessionId(false, settings));
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private T ProcessRequest<T>(IRestRequest request, NzbVortexSettings settings) where T : new()
|
||||
{
|
||||
return Json.Deserialize<T>(ProcessRequest(request, settings));
|
||||
}
|
||||
|
||||
private string ProcessRequest(IRestRequest request, NzbVortexSettings settings)
|
||||
{
|
||||
var client = BuildClient(settings);
|
||||
|
||||
try
|
||||
{
|
||||
return ProcessRequest(client, request).Content;
|
||||
}
|
||||
catch (NzbVortexNotLoggedInException ex)
|
||||
{
|
||||
_logger.Warn("Not logged in response received, reauthenticating and retrying");
|
||||
request.AddQueryParameter("sessionid", GetSessionId(true, settings));
|
||||
|
||||
return ProcessRequest(client, request).Content;
|
||||
}
|
||||
}
|
||||
|
||||
private IRestResponse ProcessRequest(IRestClient client, IRestRequest request)
|
||||
{
|
||||
_logger.Debug("URL: {0}/{1}", client.BaseUrl, request.Resource);
|
||||
var response = client.Execute(request);
|
||||
|
||||
_logger.Trace("Response: {0}", response.Content);
|
||||
CheckForError(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void CheckForError(IRestResponse response)
|
||||
{
|
||||
if (response.ResponseStatus != ResponseStatus.Completed)
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to NZBVortex, please check your settings", response.ErrorException);
|
||||
}
|
||||
|
||||
NzbVortexResponseBase result;
|
||||
|
||||
if (Json.TryDeserialize<NzbVortexResponseBase>(response.Content, out result))
|
||||
{
|
||||
if (result.Result == NzbVortexResultType.NotLoggedIn)
|
||||
{
|
||||
throw new NzbVortexNotLoggedInException();
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
throw new DownloadClientException("Response could not be processed: {0}", response.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortexQueue
|
||||
{
|
||||
[JsonProperty(PropertyName = "nzbs")]
|
||||
public List<NzbVortexQueueItem> Items { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortexQueueItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string UiTitle { get; set; }
|
||||
public string DestinationPath { get; set; }
|
||||
public string NzbFilename { get; set; }
|
||||
public bool IsPaused { get; set; }
|
||||
public NzbVortexStateType State { get; set; }
|
||||
public string StatusText { get; set; }
|
||||
public int TransferedSpeed { get; set; }
|
||||
public double Progress { get; set; }
|
||||
public long DownloadedSize { get; set; }
|
||||
public long TotalDownloadSize { get; set; }
|
||||
public long PostDate { get; set; }
|
||||
public int TotalArticleCount { get; set; }
|
||||
public int FailedArticleCount { get; set; }
|
||||
public string GroupUUID { get; set; }
|
||||
public string AddUUID { get; set; }
|
||||
public string GroupName { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public enum NzbVortexResultType
|
||||
{
|
||||
Ok,
|
||||
NotLoggedIn,
|
||||
UnknownCommand
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public class NzbVortexSettingsValidator : AbstractValidator<NzbVortexSettings>
|
||||
{
|
||||
public NzbVortexSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Host).ValidHost();
|
||||
RuleFor(c => c.Port).GreaterThan(0);
|
||||
|
||||
RuleFor(c => c.ApiKey).NotEmpty()
|
||||
.WithMessage("API Key is required");
|
||||
|
||||
RuleFor(c => c.TvCategory).NotEmpty()
|
||||
.WithMessage("A category is recommended")
|
||||
.AsWarning();
|
||||
}
|
||||
}
|
||||
|
||||
public class NzbVortexSettings : IProviderConfig
|
||||
{
|
||||
private static readonly NzbVortexSettingsValidator Validator = new NzbVortexSettingsValidator();
|
||||
|
||||
public NzbVortexSettings()
|
||||
{
|
||||
Host = "localhost";
|
||||
Port = 4321;
|
||||
TvCategory = "TV Shows";
|
||||
RecentTvPriority = (int)NzbVortexPriority.Normal;
|
||||
OlderTvPriority = (int)NzbVortexPriority.Normal;
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||
public string Host { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "API Key", Type = FieldType.Textbox)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||
public string TvCategory { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
||||
public int RecentTvPriority { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
||||
public int OlderTvPriority { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||
{
|
||||
public enum NzbVortexStateType
|
||||
{
|
||||
Waiting = 0,
|
||||
Downloading = 1,
|
||||
WaitingForSave = 2,
|
||||
Saving = 3,
|
||||
Saved = 4,
|
||||
PasswordRequest = 5,
|
||||
QuaedForProcessing = 6,
|
||||
UserWaitForProcessing = 7,
|
||||
Checking = 8,
|
||||
Repairing = 9,
|
||||
Joining = 10,
|
||||
WaitForFurtherProcessing = 11,
|
||||
Joining2 = 12,
|
||||
WaitForUncompress = 13,
|
||||
Uncompressing = 14,
|
||||
WaitForCleanup = 15,
|
||||
CleaningUp = 16,
|
||||
CleanedUp = 17,
|
||||
MovingToCompleted = 18,
|
||||
MoveCompleted = 19,
|
||||
Done = 20,
|
||||
UncompressFailed = 21,
|
||||
CheckFailedDataCorrupt = 22,
|
||||
MoveFailed = 23,
|
||||
BadlyEncoded = 24
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.Responses
|
||||
{
|
||||
public class NzbVortexAddResponse : NzbVortexResponseBase
|
||||
{
|
||||
[JsonProperty(PropertyName = "add_uuid")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex.Responses
|
||||
{
|
||||
public class NzbVortexApiVersionResponse : NzbVortexResponseBase
|
||||
{
|
||||
public string ApiLevel { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex.Responses
|
||||
{
|
||||
public class NzbVortexAuthNonceResponse
|
||||
{
|
||||
public string AuthNonce { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using Newtonsoft.Json;
|
||||
using NzbDrone.Core.Download.Clients.NzbVortex.JsonConverters;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.Responses
|
||||
{
|
||||
public class NzbVortexAuthResponse : NzbVortexResponseBase
|
||||
{
|
||||
[JsonConverter(typeof(NzbVortexLoginResultTypeConverter))]
|
||||
public NzbVortexLoginResultType LoginResult { get; set; }
|
||||
|
||||
public string SessionId { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.Responses
|
||||
{
|
||||
public class NzbVortexGroupResponse : NzbVortexResponseBase
|
||||
{
|
||||
public List<NzbVortexGroup> Groups { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using Newtonsoft.Json;
|
||||
using NzbDrone.Core.Download.Clients.NzbVortex.JsonConverters;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.Responses
|
||||
{
|
||||
public class NzbVortexResponseBase
|
||||
{
|
||||
[JsonConverter(typeof(NzbVortexResultTypeConverter))]
|
||||
public NzbVortexResultType Result { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.NzbVortex.Responses
|
||||
{
|
||||
public class NzbVortexRetryResponse
|
||||
{
|
||||
public bool Status { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "nzo_id")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace NzbDrone.Core.Download.Clients.NzbVortex.Responses
|
||||
{
|
||||
public class NzbVortexVersionResponse : NzbVortexResponseBase
|
||||
{
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
|
@ -357,6 +357,33 @@
|
|||
<Compile Include="Download\Clients\Nzbget\NzbgetQueueItem.cs" />
|
||||
<Compile Include="Download\Clients\Nzbget\NzbgetResponse.cs" />
|
||||
<Compile Include="Download\Clients\Nzbget\NzbgetSettings.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\JsonConverters\NzbVortexLoginResultTypeConverter.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\JsonConverters\NzbVortexResultTypeConverter.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortex.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexGroup.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexNotLoggedInException.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexAuthenticationException.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexJsonError.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexPriority.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexProxy.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexFiles.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexQueue.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexFile.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexQueueItem.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexLoginResultType.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexStateType.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexResultType.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\NzbVortexSettings.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\Responses\NzbVortexAddResponse.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\Responses\NzbVortexAuthNonceResponse.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\Responses\NzbVortexAuthResponse.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\Responses\NzbVortexGroupResponse.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\Responses\NzbVortexResponseBase.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\Responses\NzbVortexRetryResponse.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\Responses\NzbVortexApiVersionResponse.cs" />
|
||||
<Compile Include="Download\Clients\NzbVortex\Responses\NzbVortexVersionResponse.cs" />
|
||||
<Compile Include="Download\Clients\Pneumatic\Pneumatic.cs" />
|
||||
<Compile Include="Download\Clients\Pneumatic\PneumaticSettings.cs" />
|
||||
<Compile Include="Download\Clients\qBittorrent\DigestAuthenticator.cs" />
|
||||
|
|
Loading…
Reference in New Issue