mirror of https://github.com/Radarr/Radarr
Initial support for Flood (#5552)
* Initial support for Flood * Flip StartOnAdd to AddPaused
This commit is contained in:
parent
39d11b4669
commit
2237624333
|
@ -0,0 +1,234 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.Flood.Models;
|
||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Flood
|
||||
{
|
||||
public class Flood : TorrentClientBase<FloodSettings>
|
||||
{
|
||||
private readonly IFloodProxy _proxy;
|
||||
|
||||
public Flood(IFloodProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
IConfigService configService,
|
||||
INamingConfigService namingConfigService,
|
||||
IDiskProvider diskProvider,
|
||||
IRemotePathMappingService remotePathMappingService,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> HandleTags(RemoteMovie remoteMovie, FloodSettings settings)
|
||||
{
|
||||
var result = new HashSet<string>();
|
||||
|
||||
if (settings.Tags.Any())
|
||||
{
|
||||
result.UnionWith(settings.Tags);
|
||||
}
|
||||
|
||||
if (settings.AdditionalTags.Any())
|
||||
{
|
||||
foreach (var additionalTag in settings.AdditionalTags)
|
||||
{
|
||||
switch (additionalTag)
|
||||
{
|
||||
case (int)AdditionalTags.Collection:
|
||||
result.Add(remoteMovie.Movie.Collection.Name);
|
||||
break;
|
||||
case (int)AdditionalTags.Quality:
|
||||
result.Add(remoteMovie.ParsedMovieInfo.Quality.Quality.ToString());
|
||||
break;
|
||||
case (int)AdditionalTags.Languages:
|
||||
result.UnionWith(remoteMovie.ParsedMovieInfo.Languages.ConvertAll(language => language.ToString()));
|
||||
break;
|
||||
case (int)AdditionalTags.ReleaseGroup:
|
||||
result.Add(remoteMovie.ParsedMovieInfo.ReleaseGroup);
|
||||
break;
|
||||
case (int)AdditionalTags.Year:
|
||||
result.Add(remoteMovie.Movie.Year.ToString());
|
||||
break;
|
||||
case (int)AdditionalTags.Indexer:
|
||||
result.Add(remoteMovie.Release.Indexer);
|
||||
break;
|
||||
case (int)AdditionalTags.Studio:
|
||||
result.Add(remoteMovie.Movie.Studio);
|
||||
break;
|
||||
default:
|
||||
throw new DownloadClientException("Unexpected additional tag ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string Name => "Flood";
|
||||
public override ProviderMessage Message => new ProviderMessage("Radarr is unable to remove torrents that have finished seeding when using Flood", ProviderMessageType.Warning);
|
||||
|
||||
protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string hash, string filename, byte[] fileContent)
|
||||
{
|
||||
_proxy.AddTorrentByFile(Convert.ToBase64String(fileContent), HandleTags(remoteMovie, Settings), Settings);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
|
||||
{
|
||||
_proxy.AddTorrentByUrl(magnetLink, HandleTags(remoteMovie, Settings), Settings);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
var items = new List<DownloadClientItem>();
|
||||
|
||||
var list = _proxy.GetTorrents(Settings);
|
||||
|
||||
foreach (var torrent in list)
|
||||
{
|
||||
var properties = torrent.Value;
|
||||
|
||||
if (!Settings.Tags.All(tag => properties.Tags.Contains(tag)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = new DownloadClientItem
|
||||
{
|
||||
DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this),
|
||||
DownloadId = torrent.Key,
|
||||
Title = properties.Name,
|
||||
OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(properties.Directory)),
|
||||
Category = properties.Tags.Count > 0 ? properties.Tags[0] : null,
|
||||
RemainingSize = properties.SizeBytes - properties.BytesDone,
|
||||
TotalSize = properties.SizeBytes,
|
||||
SeedRatio = properties.Ratio,
|
||||
Message = properties.Message,
|
||||
};
|
||||
|
||||
if (properties.Eta > 0)
|
||||
{
|
||||
item.RemainingTime = TimeSpan.FromSeconds(properties.Eta);
|
||||
}
|
||||
|
||||
if (properties.Status.Contains("error"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Warning;
|
||||
}
|
||||
else if (properties.Status.Contains("seeding") || properties.Status.Contains("complete"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Completed;
|
||||
}
|
||||
else if (properties.Status.Contains("downloading"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Downloading;
|
||||
}
|
||||
else if (properties.Status.Contains("stopped"))
|
||||
{
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
}
|
||||
|
||||
item.CanMoveFiles = item.CanBeRemoved = false;
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public override DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt)
|
||||
{
|
||||
var result = item.Clone();
|
||||
|
||||
var contentPaths = _proxy.GetTorrentContentPaths(item.DownloadId, Settings);
|
||||
|
||||
if (contentPaths.Count < 1)
|
||||
{
|
||||
throw new DownloadClientUnavailableException($"Failed to fetch list of contents of torrent: {item.DownloadId}");
|
||||
}
|
||||
|
||||
if (contentPaths.Count == 1)
|
||||
{
|
||||
// For single-file torrent, OutputPath should be the path of file.
|
||||
result.OutputPath = item.OutputPath + new OsPath(contentPaths[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For multi-file torrent, OutputPath should be the path of base directory of torrent.
|
||||
var baseDirectoryPaths = contentPaths.ConvertAll(path =>
|
||||
path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries)[0]);
|
||||
|
||||
// Check first segment (directory) of paths of contents. If all contents share the same directory, use that directory.
|
||||
if (baseDirectoryPaths.TrueForAll(path => path == baseDirectoryPaths[0]))
|
||||
{
|
||||
result.OutputPath = item.OutputPath + new OsPath(baseDirectoryPaths[0]);
|
||||
}
|
||||
|
||||
// Otherwise, OutputPath is already the base directory.
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void MarkItemAsImported(DownloadClientItem downloadClientItem)
|
||||
{
|
||||
if (Settings.PostImportTags.Any())
|
||||
{
|
||||
var list = _proxy.GetTorrents(Settings);
|
||||
|
||||
if (list.ContainsKey(downloadClientItem.DownloadId))
|
||||
{
|
||||
_proxy.SetTorrentsTags(downloadClientItem.DownloadId,
|
||||
list[downloadClientItem.DownloadId].Tags.Concat(Settings.PostImportTags).ToHashSet(),
|
||||
Settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveItem(string downloadId, bool deleteData)
|
||||
{
|
||||
_proxy.DeleteTorrent(downloadId, deleteData, Settings);
|
||||
}
|
||||
|
||||
public override DownloadClientInfo GetStatus()
|
||||
{
|
||||
return new DownloadClientInfo
|
||||
{
|
||||
IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "::1" || Settings.Host == "localhost",
|
||||
OutputRootFolders = new List<OsPath> { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(Settings.Destination)) }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Test(List<ValidationFailure> failures)
|
||||
{
|
||||
try
|
||||
{
|
||||
_proxy.AuthVerify(Settings);
|
||||
}
|
||||
catch (DownloadClientAuthenticationException ex)
|
||||
{
|
||||
failures.Add(new ValidationFailure("Password", ex.Message));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failures.Add(new ValidationFailure("Host", ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Download.Clients.Flood.Types;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Flood
|
||||
{
|
||||
public interface IFloodProxy
|
||||
{
|
||||
void AuthVerify(FloodSettings settings);
|
||||
void AddTorrentByUrl(string url, IEnumerable<string> tags, FloodSettings settings);
|
||||
void AddTorrentByFile(string file, IEnumerable<string> tags, FloodSettings settings);
|
||||
void DeleteTorrent(string hash, bool deleteData, FloodSettings settings);
|
||||
Dictionary<string, Torrent> GetTorrents(FloodSettings settings);
|
||||
List<string> GetTorrentContentPaths(string hash, FloodSettings settings);
|
||||
void SetTorrentsTags(string hash, IEnumerable<string> tags, FloodSettings settings);
|
||||
}
|
||||
|
||||
public class FloodProxy : IFloodProxy
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly Logger _logger;
|
||||
private readonly ICached<Dictionary<string, string>> _authCookieCache;
|
||||
|
||||
public FloodProxy(IHttpClient httpClient, ICacheManager cacheManager, Logger logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_authCookieCache = cacheManager.GetCache<Dictionary<string, string>>(GetType(), "authCookies");
|
||||
}
|
||||
|
||||
private string BuildUrl(FloodSettings settings)
|
||||
{
|
||||
return $"{(settings.UseSsl ? "https://" : "http://")}{settings.Host}:{settings.Port}/{settings.UrlBase}";
|
||||
}
|
||||
|
||||
private string BuildCachedCookieKey(FloodSettings settings)
|
||||
{
|
||||
return $"{BuildUrl(settings)}:{settings.Username}";
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(FloodSettings settings)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder(HttpUri.CombinePath(BuildUrl(settings), "/api"))
|
||||
{
|
||||
LogResponseContent = true,
|
||||
NetworkCredential = new NetworkCredential(settings.Username, settings.Password)
|
||||
};
|
||||
|
||||
requestBuilder.Headers.ContentType = "application/json";
|
||||
requestBuilder.SetCookies(AuthAuthenticate(requestBuilder, settings));
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private HttpResponse HandleRequest(HttpRequest request, FloodSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _httpClient.Execute(request);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.Response.StatusCode == HttpStatusCode.Forbidden ||
|
||||
ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_authCookieCache.Remove(BuildCachedCookieKey(settings));
|
||||
throw new DownloadClientAuthenticationException("Failed to authenticate with Flood.");
|
||||
}
|
||||
|
||||
throw new DownloadClientException("Unable to connect to Flood, please check your settings");
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new DownloadClientException("Unable to connect to Flood, please check your settings");
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, string> AuthAuthenticate(HttpRequestBuilder requestBuilder, FloodSettings settings, bool force = false)
|
||||
{
|
||||
var cachedCookies = _authCookieCache.Find(BuildCachedCookieKey(settings));
|
||||
|
||||
if (cachedCookies == null || force)
|
||||
{
|
||||
var authenticateRequest = requestBuilder.Resource("/auth/authenticate").Post().Build();
|
||||
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
{ "username", settings.Username },
|
||||
{ "password", settings.Password }
|
||||
};
|
||||
authenticateRequest.SetContent(body.ToJson());
|
||||
|
||||
var response = HandleRequest(authenticateRequest, settings);
|
||||
cachedCookies = response.GetCookies();
|
||||
_authCookieCache.Set(BuildCachedCookieKey(settings), cachedCookies);
|
||||
}
|
||||
|
||||
return cachedCookies;
|
||||
}
|
||||
|
||||
public void AuthVerify(FloodSettings settings)
|
||||
{
|
||||
var verifyRequest = BuildRequest(settings).Resource("/auth/verify").Build();
|
||||
|
||||
verifyRequest.Method = HttpMethod.GET;
|
||||
|
||||
HandleRequest(verifyRequest, settings);
|
||||
}
|
||||
|
||||
public void AddTorrentByFile(string file, IEnumerable<string> tags, FloodSettings settings)
|
||||
{
|
||||
var addRequest = BuildRequest(settings).Resource("/torrents/add-files").Post().Build();
|
||||
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
{ "files", new List<string> { file } },
|
||||
{ "tags", tags.ToList() }
|
||||
};
|
||||
|
||||
if (settings.Destination != null)
|
||||
{
|
||||
body.Add("destination", settings.Destination);
|
||||
}
|
||||
|
||||
if (!settings.AddPaused)
|
||||
{
|
||||
body.Add("start", true);
|
||||
}
|
||||
|
||||
addRequest.SetContent(body.ToJson());
|
||||
|
||||
HandleRequest(addRequest, settings);
|
||||
}
|
||||
|
||||
public void AddTorrentByUrl(string url, IEnumerable<string> tags, FloodSettings settings)
|
||||
{
|
||||
var addRequest = BuildRequest(settings).Resource("/torrents/add-urls").Post().Build();
|
||||
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
{ "urls", new List<string> { url } },
|
||||
{ "tags", tags.ToList() }
|
||||
};
|
||||
|
||||
if (settings.Destination != null)
|
||||
{
|
||||
body.Add("destination", settings.Destination);
|
||||
}
|
||||
|
||||
if (!settings.AddPaused)
|
||||
{
|
||||
body.Add("start", true);
|
||||
}
|
||||
|
||||
addRequest.SetContent(body.ToJson());
|
||||
|
||||
HandleRequest(addRequest, settings);
|
||||
}
|
||||
|
||||
public void DeleteTorrent(string hash, bool deleteData, FloodSettings settings)
|
||||
{
|
||||
var deleteRequest = BuildRequest(settings).Resource("/torrents/delete").Post().Build();
|
||||
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
{ "hashes", new List<string> { hash } },
|
||||
{ "deleteData", deleteData }
|
||||
};
|
||||
deleteRequest.SetContent(body.ToJson());
|
||||
|
||||
HandleRequest(deleteRequest, settings);
|
||||
}
|
||||
|
||||
public Dictionary<string, Torrent> GetTorrents(FloodSettings settings)
|
||||
{
|
||||
var getTorrentsRequest = BuildRequest(settings).Resource("/torrents").Build();
|
||||
|
||||
getTorrentsRequest.Method = HttpMethod.GET;
|
||||
|
||||
return Json.Deserialize<TorrentListSummary>(HandleRequest(getTorrentsRequest, settings).Content).Torrents;
|
||||
}
|
||||
|
||||
public List<string> GetTorrentContentPaths(string hash, FloodSettings settings)
|
||||
{
|
||||
var contentsRequest = BuildRequest(settings).Resource($"/torrents/{hash}/contents").Build();
|
||||
|
||||
contentsRequest.Method = HttpMethod.GET;
|
||||
|
||||
return Json.Deserialize<List<TorrentContent>>(HandleRequest(contentsRequest, settings).Content).ConvertAll(content => content.Path);
|
||||
}
|
||||
|
||||
public void SetTorrentsTags(string hash, IEnumerable<string> tags, FloodSettings settings)
|
||||
{
|
||||
var tagsRequest = BuildRequest(settings).Resource("/torrents/tags").Build();
|
||||
|
||||
tagsRequest.Method = HttpMethod.PATCH;
|
||||
|
||||
var body = new Dictionary<string, object>
|
||||
{
|
||||
{ "hashes", new List<string> { hash } },
|
||||
{ "tags", tags.ToList() }
|
||||
};
|
||||
tagsRequest.SetContent(body.ToJson());
|
||||
|
||||
HandleRequest(tagsRequest, settings);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Download.Clients.Flood.Models;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Flood
|
||||
{
|
||||
public class FloodSettingsValidator : AbstractValidator<FloodSettings>
|
||||
{
|
||||
public FloodSettingsValidator()
|
||||
{
|
||||
RuleFor(c => c.Host).ValidHost();
|
||||
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
|
||||
}
|
||||
}
|
||||
|
||||
public class FloodSettings : IProviderConfig
|
||||
{
|
||||
private static readonly FloodSettingsValidator Validator = new FloodSettingsValidator();
|
||||
|
||||
public FloodSettings()
|
||||
{
|
||||
UseSsl = false;
|
||||
Host = "localhost";
|
||||
Port = 3000;
|
||||
Tags = new string[]
|
||||
{
|
||||
"radarr"
|
||||
};
|
||||
AdditionalTags = Enumerable.Empty<int>();
|
||||
AddPaused = false;
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Host", Type = FieldType.Textbox)]
|
||||
public string Host { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Port", Type = FieldType.Textbox)]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Url Base", Type = FieldType.Textbox, HelpText = "Optionally adds a prefix to Flood API, such as [protocol]://[host]:[port]/[urlBase]api")]
|
||||
public string UrlBase { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "Password", Type = FieldType.Password, Privacy = PrivacyLevel.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "Destination", Type = FieldType.Textbox, HelpText = "Manually specifies download destination")]
|
||||
public string Destination { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "Tags", Type = FieldType.Tag, HelpText = "Initial tags of a download. To be recognized, a download must have all initial tags. This avoids conflicts with unrelated downloads.")]
|
||||
public IEnumerable<string> Tags { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "Post-Import Tags", Type = FieldType.Tag, HelpText = "Appends tags after a download is imported.", Advanced = true)]
|
||||
public IEnumerable<string> PostImportTags { get; set; }
|
||||
|
||||
[FieldDefinition(9, Label = "Additional Tags", Type = FieldType.Select, SelectOptions = typeof(AdditionalTags), HelpText = "Adds properties of media as tags. Hints are examples.", Advanced = true)]
|
||||
public IEnumerable<int> AdditionalTags { get; set; }
|
||||
|
||||
[FieldDefinition(10, Label = "Add Paused", Type = FieldType.Checkbox)]
|
||||
public bool AddPaused { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
{
|
||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using NzbDrone.Core.Annotations;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Flood.Models
|
||||
{
|
||||
public enum AdditionalTags
|
||||
{
|
||||
[FieldOption(Hint = "Big Buck Bunny Series")]
|
||||
Collection = 0,
|
||||
|
||||
[FieldOption(Hint = "Bluray-2160p")]
|
||||
Quality = 1,
|
||||
|
||||
[FieldOption(Hint = "English")]
|
||||
Languages = 2,
|
||||
|
||||
[FieldOption(Hint = "Example-Raws")]
|
||||
ReleaseGroup = 3,
|
||||
|
||||
[FieldOption(Hint = "2020")]
|
||||
Year = 4,
|
||||
|
||||
[FieldOption(Hint = "Torznab")]
|
||||
Indexer = 5,
|
||||
|
||||
[FieldOption(Hint = "C-SPAN")]
|
||||
Studio = 6
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Flood.Types
|
||||
{
|
||||
public sealed class Torrent
|
||||
{
|
||||
[JsonProperty(PropertyName = "bytesDone")]
|
||||
public long BytesDone { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "directory")]
|
||||
public string Directory { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "eta")]
|
||||
public long Eta { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "ratio")]
|
||||
public float Ratio { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "sizeBytes")]
|
||||
public long SizeBytes { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "status")]
|
||||
public List<string> Status { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "tags")]
|
||||
public List<string> Tags { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Flood.Types
|
||||
{
|
||||
public sealed class TorrentContent
|
||||
{
|
||||
[JsonProperty(PropertyName = "path")]
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Flood.Types
|
||||
{
|
||||
public sealed class TorrentListSummary
|
||||
{
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "torrents")]
|
||||
public Dictionary<string, Torrent> Torrents { get; set; }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue