From 2eb24d494b2681891533b4ae5c50b533d396b3c0 Mon Sep 17 00:00:00 2001 From: Michael Feinbier Date: Fri, 15 Sep 2023 08:20:45 +0200 Subject: [PATCH] Applied original .patch From original PR https://github.com/Sonarr/Sonarr/pull/1357 --- .../PutioTests/PutioFixture.cs | 338 ++++++++++++++++++ .../Download/Clients/Putio/Putio.cs | 191 ++++++++++ .../Download/Clients/Putio/PutioException.cs | 13 + .../Download/Clients/Putio/PutioFile.cs | 8 + .../Clients/Putio/PutioFileResponse.cs | 8 + .../Clients/Putio/PutioGenericResponse.cs | 12 + .../Download/Clients/Putio/PutioPriority.cs | 8 + .../Download/Clients/Putio/PutioProxy.cs | 107 ++++++ .../Download/Clients/Putio/PutioSettings.cs | 39 ++ .../Download/Clients/Putio/PutioTorrent.cs | 32 ++ .../Clients/Putio/PutioTorrentStatus.cs | 12 + .../Clients/Putio/PutioTransfersResponse.cs | 8 + 12 files changed, 776 insertions(+) create mode 100644 src/NzbDrone.Core.Test/Download/DownloadClientTests/PutioTests/PutioFixture.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/Putio.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioException.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioFile.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioFileResponse.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioGenericResponse.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioPriority.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioProxy.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioSettings.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioTorrent.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioTorrentStatus.cs create mode 100644 src/NzbDrone.Core/Download/Clients/Putio/PutioTransfersResponse.cs diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/PutioTests/PutioFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/PutioTests/PutioFixture.cs new file mode 100644 index 000000000..9292eb45a --- /dev/null +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/PutioTests/PutioFixture.cs @@ -0,0 +1,338 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Http; +using NzbDrone.Core.MediaFiles.TorrentInfo; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients.Putio; + +namespace NzbDrone.Core.Test.Download.DownloadClientTests.PutioTests +{ + [TestFixture] + public class PutioFixture : DownloadClientFixtureBase + { + protected PutioSettings _settings; + protected PutioTorrent _queued; + protected PutioTorrent _downloading; + protected PutioTorrent _failed; + protected PutioTorrent _completed; + protected PutioTorrent _magnet; + protected Dictionary _PutioAccountSettingsItems; + + [SetUp] + public void Setup() + { + _settings = new PutioSettings + { + }; + + Subject.Definition = new DownloadClientDefinition(); + Subject.Definition.Settings = _settings; + + _queued = new PutioTorrent + { + HashString = "HASH", + IsFinished = false, + Status = PutioTorrentStatus.InQueue, + Name = _title, + TotalSize = 1000, + LeftUntilDone = 1000, + DownloadDir = "somepath" + }; + + _downloading = new PutioTorrent + { + HashString = "HASH", + IsFinished = false, + Status = PutioTorrentStatus.Downloading, + Name = _title, + TotalSize = 1000, + LeftUntilDone = 100, + DownloadDir = "somepath" + }; + + _failed = new PutioTorrent + { + HashString = "HASH", + IsFinished = false, + Status = PutioTorrentStatus.Error, + Name = _title, + TotalSize = 1000, + LeftUntilDone = 100, + ErrorString = "Error", + DownloadDir = "somepath" + }; + + _completed = new PutioTorrent + { + HashString = "HASH", + IsFinished = true, + Status = PutioTorrentStatus.Completed, + Name = _title, + TotalSize = 1000, + LeftUntilDone = 0, + DownloadDir = "somepath" + }; + + _magnet = new PutioTorrent + { + HashString = "HASH", + IsFinished = false, + Status = PutioTorrentStatus.Downloading, + Name = _title, + TotalSize = 0, + LeftUntilDone = 100, + DownloadDir = "somepath" + }; + + Mocker.GetMock() + .Setup(s => s.GetHashFromTorrentFile(It.IsAny())) + .Returns("CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951"); + + Mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[0])); + + _PutioAccountSettingsItems = new Dictionary(); + + _PutioAccountSettingsItems.Add("download-dir", @"C:/Downloads/Finished/Putio"); + _PutioAccountSettingsItems.Add("incomplete-dir", null); + _PutioAccountSettingsItems.Add("incomplete-dir-enabled", false); + + Mocker.GetMock() + .Setup(v => v.GetAccountSettings(It.IsAny())) + .Returns(_PutioAccountSettingsItems); + + } + + protected void GivenFailedDownload() + { + Mocker.GetMock() + .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny())) + .Throws(); + } + + protected void GivenSuccessfulDownload() + { + Mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(r => new HttpResponse(r, new HttpHeader(), new byte[1000])); + + Mocker.GetMock() + .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny())) + .Callback(PrepareClientToReturnQueuedItem); + + Mocker.GetMock() + .Setup(s => s.AddTorrentFromData(It.IsAny(), It.IsAny())) + .Callback(PrepareClientToReturnQueuedItem); + } + + protected virtual void GivenTorrents(List torrents) + { + if (torrents == null) + { + torrents = new List(); + } + + Mocker.GetMock() + .Setup(s => s.GetTorrents(It.IsAny())) + .Returns(torrents); + } + + protected void PrepareClientToReturnQueuedItem() + { + GivenTorrents(new List + { + _queued + }); + } + + protected void PrepareClientToReturnDownloadingItem() + { + GivenTorrents(new List + { + _downloading + }); + } + + protected void PrepareClientToReturnFailedItem() + { + GivenTorrents(new List + { + _failed + }); + } + + protected void PrepareClientToReturnCompletedItem() + { + GivenTorrents(new List + { + _completed + }); + } + + protected void PrepareClientToReturnMagnetItem() + { + GivenTorrents(new List + { + _magnet + }); + } + + [Test] + public void queued_item_should_have_required_properties() + { + PrepareClientToReturnQueuedItem(); + var item = Subject.GetItems().Single(); + VerifyQueued(item); + } + + [Test] + public void downloading_item_should_have_required_properties() + { + PrepareClientToReturnDownloadingItem(); + var item = Subject.GetItems().Single(); + VerifyDownloading(item); + } + + [Test] + public void failed_item_should_have_required_properties() + { + PrepareClientToReturnFailedItem(); + var item = Subject.GetItems().Single(); + VerifyWarning(item); + } + + [Test] + public void completed_download_should_have_required_properties() + { + PrepareClientToReturnCompletedItem(); + var item = Subject.GetItems().Single(); + VerifyCompleted(item); + } + + [Test] + public void magnet_download_should_not_return_the_item() + { + PrepareClientToReturnMagnetItem(); + Subject.GetItems().Count().Should().Be(0); + } + + [Test] + public void Download_should_return_unique_id() + { + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + + var id = Subject.Download(remoteEpisode); + + id.Should().NotBeNullOrEmpty(); + } + + [TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")] + public void Download_should_get_hash_from_magnet_url(string magnetUrl, string expectedHash) + { + GivenSuccessfulDownload(); + + var remoteEpisode = CreateRemoteEpisode(); + remoteEpisode.Release.DownloadUrl = magnetUrl; + + var id = Subject.Download(remoteEpisode); + + id.Should().Be(expectedHash); + } + + [TestCase(PutioTorrentStatus.Stopped, DownloadItemStatus.Downloading)] + [TestCase(PutioTorrentStatus.CheckWait, DownloadItemStatus.Downloading)] + [TestCase(PutioTorrentStatus.Check, DownloadItemStatus.Downloading)] + [TestCase(PutioTorrentStatus.Queued, DownloadItemStatus.Queued)] + [TestCase(PutioTorrentStatus.Downloading, DownloadItemStatus.Downloading)] + [TestCase(PutioTorrentStatus.SeedingWait, DownloadItemStatus.Completed)] + [TestCase(PutioTorrentStatus.Seeding, DownloadItemStatus.Completed)] + public void GetItems_should_return_queued_item_as_downloadItemStatus(PutioTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) + { + _queued.Status = apiStatus; + + PrepareClientToReturnQueuedItem(); + + var item = Subject.GetItems().Single(); + + item.Status.Should().Be(expectedItemStatus); + } + + [TestCase(PutioTorrentStatus.Queued, DownloadItemStatus.Queued)] + [TestCase(PutioTorrentStatus.Downloading, DownloadItemStatus.Downloading)] + [TestCase(PutioTorrentStatus.Seeding, DownloadItemStatus.Completed)] + public void GetItems_should_return_downloading_item_as_downloadItemStatus(PutioTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus) + { + _downloading.Status = apiStatus; + + PrepareClientToReturnDownloadingItem(); + + var item = Subject.GetItems().Single(); + + item.Status.Should().Be(expectedItemStatus); + } + + [TestCase(PutioTorrentStatus.Stopped, DownloadItemStatus.Completed, false)] + [TestCase(PutioTorrentStatus.CheckWait, DownloadItemStatus.Downloading, true)] + [TestCase(PutioTorrentStatus.Check, DownloadItemStatus.Downloading, true)] + [TestCase(PutioTorrentStatus.Queued, DownloadItemStatus.Completed, true)] + [TestCase(PutioTorrentStatus.SeedingWait, DownloadItemStatus.Completed, true)] + [TestCase(PutioTorrentStatus.Seeding, DownloadItemStatus.Completed, true)] + public void GetItems_should_return_completed_item_as_downloadItemStatus(PutioTorrentStatus apiStatus, DownloadItemStatus expectedItemStatus, bool expectedReadOnly) + { + _completed.Status = apiStatus; + + PrepareClientToReturnCompletedItem(); + + var item = Subject.GetItems().Single(); + + item.Status.Should().Be(expectedItemStatus); + item.IsReadOnly.Should().Be(expectedReadOnly); + } + + [Test] + public void should_return_status_with_outputdirs() + { + var result = Subject.GetStatus(); + + result.IsLocalhost.Should().BeTrue(); + result.OutputRootFolders.Should().NotBeNull(); + result.OutputRootFolders.First().Should().Be(@"C:\Downloads\Finished\Putio"); + } + + [Test] + public void should_fix_forward_slashes() + { + WindowsOnly(); + + _downloading.DownloadDir = @"C:/Downloads/Finished/Putio"; + + GivenTorrents(new List + { + _downloading + }); + + var items = Subject.GetItems().ToList(); + + items.Should().HaveCount(1); + items.First().OutputPath.Should().Be(@"C:\Downloads\Finished\Putio\" + _title); + } + + [TestCase(-1)] // Infinite/Unknown + [TestCase(-2)] // Magnet Downloading + public void should_ignore_negative_eta(int eta) + { + _completed.Eta = eta; + + PrepareClientToReturnCompletedItem(); + var item = Subject.GetItems().Single(); + item.RemainingTime.Should().NotHaveValue(); + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/Putio.cs b/src/NzbDrone.Core/Download/Clients/Putio/Putio.cs new file mode 100644 index 000000000..4d086e732 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/Putio.cs @@ -0,0 +1,191 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; +using NzbDrone.Core.Configuration; +using NLog; +using FluentValidation.Results; +using NzbDrone.Core.MediaFiles.TorrentInfo; +using NzbDrone.Core.Validation; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.RemotePathMappings; + +namespace NzbDrone.Core.Download.Clients.Putio +{ + public class Putio : TorrentClientBase + { + private readonly IPutioProxy _proxy; + + public Putio(IPutioProxy proxy, + ITorrentFileInfoReader torrentFileInfoReader, + IHttpClient httpClient, + IConfigService configService, + IDiskProvider diskProvider, + IRemotePathMappingService remotePathMappingService, + Logger logger) + : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) + { + _proxy = proxy; + } + + public override string Name + { + get + { + return "put.io"; + } + } + protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink) + { + _proxy.AddTorrentFromUrl(magnetLink, Settings); + return hash; + } + + protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent) + { + _proxy.AddTorrentFromData(fileContent, Settings); + return hash; + } + + public override IEnumerable GetItems() + { + List torrents; + + try + { + torrents = _proxy.GetTorrents(Settings); + } + catch (DownloadClientException ex) + { + _logger.Error(ex, ex.Message); + return Enumerable.Empty(); + } + + var items = new List(); + + foreach (var torrent in torrents) + { + // If totalsize == 0 the torrent is a magnet downloading metadata + if (torrent.Size == 0) + continue; + + var item = new DownloadClientItem(); + item.DownloadId = "putio-" + torrent.Id; + item.Category = Settings.SaveParentId; + item.Title = torrent.Name; + + item.DownloadClient = Definition.Name; + + item.TotalSize = torrent.Size; + item.RemainingSize = torrent.Size - torrent.Downloaded; + + try + { + if (torrent.FileId != 0) + { + var file = _proxy.GetFile(torrent.FileId, Settings); + var torrentPath = "/completed/" + file.Name; + + var outputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Url, new OsPath(torrentPath)); + + if (Settings.SaveParentId.IsNotNullOrWhiteSpace()) + { + var directories = outputPath.FullPath.Split('\\', '/'); + if (!directories.Contains(string.Format("{0}", Settings.SaveParentId))) continue; + } + + item.OutputPath = outputPath; // + torrent.Name; + } + } + catch (DownloadClientException ex) + { + _logger.Error(ex, ex.Message); + } + + if (torrent.EstimatedTime >= 0) + { + item.RemainingTime = TimeSpan.FromSeconds(torrent.EstimatedTime); + } + + if (!torrent.ErrorMessage.IsNullOrWhiteSpace()) + { + item.Status = DownloadItemStatus.Warning; + item.Message = torrent.ErrorMessage; + } + else if (torrent.Status == PutioTorrentStatus.Completed) + { + item.Status = DownloadItemStatus.Completed; + } + else if (torrent.Status == PutioTorrentStatus.InQueue) + { + item.Status = DownloadItemStatus.Queued; + } + else + { + item.Status = DownloadItemStatus.Downloading; + } + + // item.IsReadOnly = torrent.Status != PutioTorrentStatus.Error; + + items.Add(item); + } + + return items; + } + + public override void RemoveItem(string downloadId, bool deleteData) + { + _proxy.RemoveTorrent(downloadId.ToLower(), Settings); + } + + public override DownloadClientStatus GetStatus() + { + var destDir = string.Format("{0}", Settings.SaveParentId); + + return new DownloadClientStatus + { + IsLocalhost = false, + OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Url, new OsPath(destDir)) } + }; + } + + protected override void Test(List failures) + { + failures.AddIfNotNull(TestConnection()); + if (failures.Any()) return; + failures.AddIfNotNull(TestGetTorrents()); + } + + private ValidationFailure TestConnection() + { + try + { + _proxy.GetAccountSettings(Settings); + } + catch (Exception ex) + { + _logger.Error(ex, ex.Message); + return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message); + } + + return null; + } + + private ValidationFailure TestGetTorrents() + { + try + { + _proxy.GetTorrents(Settings); + } + catch (Exception ex) + { + _logger.Error(ex, ex.Message); + return new NzbDroneValidationFailure(string.Empty, "Failed to get the list of torrents: " + ex.Message); + } + + return null; + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioException.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioException.cs new file mode 100644 index 000000000..ecb3002f6 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioException.cs @@ -0,0 +1,13 @@ +using System; + +namespace NzbDrone.Core.Download.Clients.Putio +{ + public class PutioException : DownloadClientException + { + public PutioException(string message) + : base(message) + { + + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioFile.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioFile.cs new file mode 100644 index 000000000..f6a49683c --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioFile.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.Download.Clients.Putio +{ + public class PutioFile + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioFileResponse.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioFileResponse.cs new file mode 100644 index 000000000..a03ffc152 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioFileResponse.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +namespace NzbDrone.Core.Download.Clients.Putio +{ + public class PutioFileResponse : PutioGenericResponse + { + public PutioFile File { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioGenericResponse.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioGenericResponse.cs new file mode 100644 index 000000000..4e0624fb9 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioGenericResponse.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace NzbDrone.Core.Download.Clients.Putio +{ + public class PutioGenericResponse + { + [JsonProperty(PropertyName = "error_message")] + public string ErrorMessage { get; set; } + + public string Status { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioPriority.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioPriority.cs new file mode 100644 index 000000000..1a93436c2 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioPriority.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.Download.Clients.Putio +{ + public enum PutioPriority + { + Last = 0, + First = 1 + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioProxy.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioProxy.cs new file mode 100644 index 000000000..91afd6f52 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioProxy.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Core.Rest; +using NLog; +using RestSharp; +using RestSharp.Deserializers; +using Newtonsoft.Json; + +namespace NzbDrone.Core.Download.Clients.Putio +{ + public interface IPutioProxy + { + List GetTorrents(PutioSettings settings); + PutioFile GetFile(long fileId, PutioSettings settings); + void AddTorrentFromUrl(string torrentUrl, PutioSettings settings); + void AddTorrentFromData(byte[] torrentData, PutioSettings settings); + void RemoveTorrent(string hash, PutioSettings settings); + void GetAccountSettings(PutioSettings settings); + } + + public class PutioProxy: IPutioProxy + { + private readonly Logger _logger; + + public PutioProxy(Logger logger) + { + _logger = logger; + } + + public List GetTorrents(PutioSettings settings) + { + var result = ProcessRequest(Method.GET, "transfers/list", null, settings); + return result.Transfers; + } + + public PutioFile GetFile(long fileId, PutioSettings settings) + { + var result = ProcessRequest(Method.GET, "files/" + fileId, null, settings); + return result.File; + } + + public void AddTorrentFromUrl(string torrentUrl, PutioSettings settings) + { + var arguments = new Dictionary(); + arguments.Add("url", torrentUrl); + ProcessRequest(Method.POST, "transfers/add", arguments, settings); + } + + public void AddTorrentFromData(byte[] torrentData, PutioSettings settings) + { + var arguments = new Dictionary(); + arguments.Add("metainfo", Convert.ToBase64String(torrentData)); + ProcessRequest(Method.POST, "transfers/add", arguments, settings); + } + + public void RemoveTorrent(string hashString, PutioSettings settings) + { + var arguments = new Dictionary(); + arguments.Add("transfer_ids", new string[] { hashString }); + ProcessRequest(Method.POST, "torrents/cancel", arguments, settings); + } + + public void GetAccountSettings(PutioSettings settings) + { + ProcessRequest(Method.GET, "account/settings", null, settings); + } + + public TResponseType ProcessRequest(Method method, string resource, Dictionary arguments, PutioSettings settings) where TResponseType : PutioGenericResponse + { + var client = BuildClient(settings); + + var request = new RestRequest(resource, method); + request.RequestFormat = DataFormat.Json; + request.AddQueryParameter("oauth_token", settings.OAuthToken); + + if (arguments != null) + { + foreach (KeyValuePair e in arguments) + { + request.AddParameter(e.Key, e.Value); + } + } + + _logger.Debug("Method: {0} Url: {1}", method, client.BuildUri(request)); + + var restResponse = client.Execute(request); + + var json = new JsonDeserializer(); + + TResponseType output = json.Deserialize(restResponse); + + if (output.Status != "OK") + { + throw new PutioException(output.ErrorMessage); + } + + return output; + } + + private IRestClient BuildClient(PutioSettings settings) + { + var restClient = RestClientFactory.BuildClient(settings.Url); + restClient.FollowRedirects = false; + return restClient; + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioSettings.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioSettings.cs new file mode 100644 index 000000000..fcff64d9a --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioSettings.cs @@ -0,0 +1,39 @@ +using System.Text.RegularExpressions; +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Download.Clients.Putio +{ + public class PutioSettingsValidator : AbstractValidator + { + public PutioSettingsValidator() + { + RuleFor(c => c.SaveParentId).Matches(@"^\.?[0-9]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters 0-9"); + } + } + + public class PutioSettings : IProviderConfig + { + private static readonly PutioSettingsValidator Validator = new PutioSettingsValidator(); + + public PutioSettings() + { + Url = "https://api.put.io/v2"; + } + + public string Url { get; } + + [FieldDefinition(0, Label = "OAuth Token", Type = FieldType.Textbox)] + public string OAuthToken { get; set; } + + [FieldDefinition(1, Label = "Save Parent ID", Type = FieldType.Textbox, HelpText = "Adding a save parent ID specific to Sonarr avoids conflicts with unrelated downloads, but it's optional. Creates a .[SaveParentId] subdirectory in the output directory.")] + public string SaveParentId { get; set; } + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioTorrent.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioTorrent.cs new file mode 100644 index 000000000..54c86339b --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioTorrent.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; + +namespace NzbDrone.Core.Download.Clients.Putio +{ + public class PutioTorrent + { + public long Downloaded { get; set; } + + [JsonProperty(PropertyName = "error_message")] + public string ErrorMessage { get; set; } + + [JsonProperty(PropertyName = "estimated_time")] + public long EstimatedTime { get; set; } + + [JsonProperty(PropertyName = "file_id")] + public long FileId { get; set; } + + public int Id { get; set; } + + public string Name { get; set; } + + [JsonProperty(PropertyName = "percent_done")] + public int PercentDone { get; set; } + + [JsonProperty(PropertyName = "seconds_seeding")] + public long SecondsSeeding { get; set; } + + public long Size { get; set; } + + public string Status { get; set; } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioTorrentStatus.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioTorrentStatus.cs new file mode 100644 index 000000000..10bd697b0 --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioTorrentStatus.cs @@ -0,0 +1,12 @@ +using System; + +namespace NzbDrone.Core.Download.Clients.Putio +{ + public sealed class PutioTorrentStatus + { + public static readonly String Completed = "COMPLETED"; + public static readonly String Downloading = "DOWNLOADING"; + public static readonly String Error = "ERROR"; + public static readonly String InQueue = "IN_QUEUE"; + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Putio/PutioTransfersResponse.cs b/src/NzbDrone.Core/Download/Clients/Putio/PutioTransfersResponse.cs new file mode 100644 index 000000000..d54d6a5ff --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/Putio/PutioTransfersResponse.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +namespace NzbDrone.Core.Download.Clients.Putio +{ + public class PutioTransfersResponse : PutioGenericResponse + { + public List Transfers { get; set; } + } +}