diff --git a/src/NzbDrone.Core/Download/Clients/Tribler/Models/TriblerDownloadClientApi.cs b/src/NzbDrone.Core/Download/Clients/Tribler/Models/TriblerDownloadClientApi.cs
new file mode 100644
index 000000000..9f075eef4
--- /dev/null
+++ b/src/NzbDrone.Core/Download/Clients/Tribler/Models/TriblerDownloadClientApi.cs
@@ -0,0 +1,257 @@
+namespace NzbDrone.Core.Download.Clients.Tribler
+{
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public enum DownloadStatus
+ {
+ [System.Runtime.Serialization.EnumMember(Value = @"WAITING4HASHCHECK")]
+ WAITING4HASHCHECK = 0,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"HASHCHECKING")]
+ HASHCHECKING = 1,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"METADATA")]
+ METADATA = 2,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"DOWNLOADING")]
+ DOWNLOADING = 3,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"SEEDING")]
+ SEEDING = 4,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"STOPPED")]
+ STOPPED = 5,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"ALLOCATING_DISKSPACE")]
+ ALLOCATING_DISKSPACE = 6,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"EXIT_NODES")]
+ EXIT_NODES = 7,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"CIRCUITS")]
+ CIRCUITS = 8,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"STOPPED_ON_ERROR")]
+ STOPPED_ON_ERROR = 9,
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class Trackers
+ {
+ /// url of tracker
+ [Newtonsoft.Json.JsonProperty("url", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Url { get; set; }
+
+ /// number of peers
+ [Newtonsoft.Json.JsonProperty("peers", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public object Peers { get; set; }
+
+ /// If the tracker is working or not.
+ [Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Status { get; set; }
+
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class Download
+ {
+ [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Name { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("progress", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public float? Progress { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("anon_download", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Anon_download { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("availability", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public float? Availability { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("eta", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double? Eta { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("total_pieces", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Total_pieces { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("num_seeds", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Num_seeds { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("total_up", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Total_up { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
+ public DownloadStatus? Status { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("infohash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Infohash { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("ratio", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public float? Ratio { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("vod_mode", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Vod_mode { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("time_added", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Time_added { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("max_upload_speed", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Max_upload_speed { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("max_download_speed", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Max_download_speed { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("hops", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Hops { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("safe_seeding", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Safe_seeding { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("error", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Error { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("total_down", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Total_down { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("vod_prebuffering_progress", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public float? Vod_prebuffering_progress { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("trackers", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Collections.Generic.ICollection Trackers { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Size { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("peers", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Collections.Generic.ICollection Peers { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("destination", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Destination { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("speed_down", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public float? Speed_down { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("speed_up", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public float? Speed_up { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("vod_prebuffering_progress_consec", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public float? Vod_prebuffering_progress_consec { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("files", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Collections.Generic.ICollection Files { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("num_peers", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Num_peers { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("channel_download", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? ChannelDownload { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class DownloadsResponse
+ {
+ [Newtonsoft.Json.JsonProperty("downloads", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Collections.Generic.ICollection Downloads { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class AddDownloadRequest
+ {
+ /// Number of hops for the anonymous download. No hops is equivalent to a plain download
+ [Newtonsoft.Json.JsonProperty("anon_hops", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Anon_hops { get; set; }
+
+ /// Whether the seeding of the download should be anonymous or not
+ [Newtonsoft.Json.JsonProperty("safe_seeding", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Safe_seeding { get; set; }
+
+ /// the download destination path of the torrent
+ [Newtonsoft.Json.JsonProperty("destination", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Destination { get; set; }
+
+ /// The URI of the torrent file that should be downloaded. This URI can either represent a file location, a magnet link or a HTTP(S) url.
+ [Newtonsoft.Json.JsonProperty("uri", Required = Newtonsoft.Json.Required.Always)]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string Uri { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class AddDownloadResponse
+ {
+ [Newtonsoft.Json.JsonProperty("infohash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Infohash { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("started", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Started { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class RemoveDownloadRequest
+ {
+ /// Whether or not to remove the associated data
+ [Newtonsoft.Json.JsonProperty("remove_data", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Remove_data { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class DeleteDownloadResponse
+ {
+ [Newtonsoft.Json.JsonProperty("removed", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Removed { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("infohash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Infohash { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class UpdateDownloadRequest
+ {
+ /// The anonymity of a download can be changed at runtime by passing the anon_hops parameter, however, this must be the only parameter in this request.
+ [Newtonsoft.Json.JsonProperty("anon_hops", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Anon_hops { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("selected_files", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Collections.Generic.ICollection Selected_files { get; set; }
+
+ /// State parameter to be passed to modify the state of the download (resume/stop/recheck)
+ [Newtonsoft.Json.JsonProperty("state", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string State { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class UpdateDownloadResponse
+ {
+ [Newtonsoft.Json.JsonProperty("modified", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Modified { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("infohash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Infohash { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class File
+ {
+ [Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Size { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("index", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public long? Index { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Name { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("progress", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public float? Progress { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("included", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Included { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class GetFilesResponse
+ {
+ [Newtonsoft.Json.JsonProperty("files", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public System.Collections.Generic.ICollection Files { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/Clients/Tribler/Models/TriblerSettingsApi.cs b/src/NzbDrone.Core/Download/Clients/Tribler/Models/TriblerSettingsApi.cs
new file mode 100644
index 000000000..74ae37c74
--- /dev/null
+++ b/src/NzbDrone.Core/Download/Clients/Tribler/Models/TriblerSettingsApi.cs
@@ -0,0 +1,129 @@
+namespace NzbDrone.Core.Indexers.Tribler
+{
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public class GetTriblerSettingsResponse
+ {
+ [Newtonsoft.Json.JsonProperty("settings", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public Settings Settings { get; set; }
+ }
+
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class Settings
+ {
+ [Newtonsoft.Json.JsonProperty("general", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public General General { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("tunnel_community", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public Tunnel_community Tunnel_community { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("dht", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public Dht Dht { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("download_defaults", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public Download_defaults Download_defaults { get; set; }
+
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class General
+ {
+ [Newtonsoft.Json.JsonProperty("log_dir", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Log_dir { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Version { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("version_checker_enabled", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Version_checker_enabled { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("testnet", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Testnet { get; set; }
+ }
+
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class Tunnel_community
+ {
+ [Newtonsoft.Json.JsonProperty("exitnode_enabled", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Exitnode_enabled { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("enabled", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Enabled { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("random_slots", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public int? Random_slots { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("competing_slots", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public int? Competing_slots { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("min_circuits", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public int? Min_Circuits { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("max_circuits", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public int? Max_Circuits { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("testnet", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Testnet { get; set; }
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class Dht
+ {
+ [Newtonsoft.Json.JsonProperty("enabled", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Enabled { get; set; }
+
+
+ }
+
+
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public partial class Download_defaults
+ {
+ [Newtonsoft.Json.JsonProperty("anonymity_enabled", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Anonymity_enabled { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("number_hops", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public int? Number_hops { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("safeseeding_enabled", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public bool? Safeseeding_enabled { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("saveas", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public string Saveas { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("seeding_mode", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
+ public Download_defaultsSeeding_mode? Seeding_mode { get; set; }
+
+ /// Seeding ratio download/upload
+ [Newtonsoft.Json.JsonProperty("seeding_ratio", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double? Seeding_ratio { get; set; }
+
+ /// Seeding time in seconds
+ [Newtonsoft.Json.JsonProperty("seeding_time", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public double? Seeding_time { get; set; }
+
+
+ }
+
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
+ public enum Download_defaultsSeeding_mode
+ {
+ [System.Runtime.Serialization.EnumMember(Value = @"ratio")]
+ Ratio = 0,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"forever")]
+ Forever = 1,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"time")]
+ Time = 2,
+
+ [System.Runtime.Serialization.EnumMember(Value = @"never")]
+ Never = 3,
+
+ }
+}
diff --git a/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadClient.cs b/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadClient.cs
new file mode 100644
index 000000000..df3608639
--- /dev/null
+++ b/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadClient.cs
@@ -0,0 +1,327 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using FluentValidation.Results;
+using MonoTorrent;
+using NLog;
+using NzbDrone.Common.Disk;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Common.Http;
+
+using NzbDrone.Core.Blocklisting;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.Indexers.Tribler;
+using NzbDrone.Core.Localization;
+using NzbDrone.Core.MediaFiles.TorrentInfo;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.RemotePathMappings;
+using NzbDrone.Core.Validation;
+
+namespace NzbDrone.Core.Download.Clients.Tribler
+{
+ public class TriblerDownloadClient : TorrentClientBase
+ {
+ private readonly ITriblerDownloadClientProxy _proxy;
+
+ public TriblerDownloadClient(
+ ITriblerDownloadClientProxy triblerDownloadClientProxy,
+ ITorrentFileInfoReader torrentFileInfoReader,
+ IHttpClient httpClient,
+ IConfigService configService,
+ IDiskProvider diskProvider,
+ IRemotePathMappingService remotePathMappingService,
+ ILocalizationService localizationService,
+ IBlocklistService blocklistService,
+ Logger logger)
+ : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
+ {
+ _proxy = triblerDownloadClientProxy;
+ }
+
+ public override string Name => "Tribler";
+
+ public override bool PreferTorrentFile => false;
+
+ public override IEnumerable GetItems()
+ {
+ var configAsync = _proxy.GetConfig(Settings);
+
+ var items = new List();
+
+ var downloads = _proxy.GetDownloads(Settings);
+
+ foreach (var download in downloads)
+ {
+ // If totalsize == 0 the torrent is a magnet downloading metadata
+ if (download.Size == null || download.Size == 0)
+ {
+ continue;
+ }
+
+ // skip channel downloads
+ if (download.ChannelDownload == true)
+ {
+ continue;
+ }
+
+ var item = new DownloadClientItem
+ {
+ DownloadId = InfoHash.FromHex(download.Infohash).ToHex(),
+ Title = download.Name,
+
+ DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this, false) // TODO: WHAT IS POST-IMPORT
+ };
+
+ // some concurrency could make this faster.
+ var files = _proxy.GetDownloadFiles(Settings, download);
+
+ item.OutputPath = new OsPath(download.Destination);
+
+ if (files.Count == 1)
+ {
+ item.OutputPath += files.First().Name;
+ }
+ else
+ {
+ item.OutputPath += item.Title;
+ }
+
+ item.TotalSize = (long)download.Size;
+ item.RemainingSize = (long)(download.Size * (1 - download.Progress)); // TODO: i expect progress to be between 0 and 1
+ item.SeedRatio = download.Ratio;
+
+ if (download.Eta.HasValue)
+ {
+ if (download.Eta.Value >= TimeSpan.FromDays(365).TotalSeconds)
+ {
+ item.RemainingTime = TimeSpan.FromDays(365);
+ }
+ else if (download.Eta.Value < 0)
+ {
+ item.RemainingTime = TimeSpan.FromSeconds(0);
+ }
+ else
+ {
+ item.RemainingTime = TimeSpan.FromSeconds(download.Eta.Value);
+ }
+ }
+
+ // TODO: the item's message should not be equal to Error
+ item.Message = download.Error;
+
+ // tribler always saves files unencrypted to disk.
+ item.IsEncrypted = false;
+
+ // state handling
+
+ // TODO: impossible states?
+ // Failed = 4,
+ // Warning = 5
+
+ // Queued = 0,
+ // Completed = 3,
+ // Downloading = 2,
+
+ // Paused is guesstimated
+ // Paused = 1,
+
+ switch (download.Status)
+ {
+ case DownloadStatus.HASHCHECKING:
+ case DownloadStatus.WAITING4HASHCHECK:
+ case DownloadStatus.CIRCUITS:
+ case DownloadStatus.EXIT_NODES:
+ case DownloadStatus.DOWNLOADING:
+ item.Status = DownloadItemStatus.Downloading;
+ break;
+ case DownloadStatus.METADATA:
+ case DownloadStatus.ALLOCATING_DISKSPACE:
+ item.Status = DownloadItemStatus.Queued;
+ break;
+ case DownloadStatus.SEEDING:
+ case DownloadStatus.STOPPED:
+ item.Status = DownloadItemStatus.Completed;
+ break;
+ case DownloadStatus.STOPPED_ON_ERROR:
+ item.Status = DownloadItemStatus.Failed;
+ break;
+ default: // new status in API? default to downloading
+ item.Message = "Unknown download state: " + download.Status;
+ _logger.Info(item.Message);
+ item.Status = DownloadItemStatus.Downloading;
+ break;
+ }
+
+ // override status' if completed but progress is not finished
+ if (download.Status == DownloadStatus.STOPPED && download.Progress < 1)
+ {
+ item.Status = DownloadItemStatus.Paused;
+ }
+
+ // override status if error is set
+ if (download.Error != null && download.Error.Length > 0)
+ {
+ item.Status = DownloadItemStatus.Warning; // maybe this should be an error?
+ }
+
+ // done (finished seeding & stopped, guessed)
+ item.CanBeRemoved = HasReachedSeedLimit(download, configAsync);
+
+ // seeding or done, or stopped
+ item.CanMoveFiles = download.Progress == 1.0;
+
+ items.Add(item);
+ }
+
+ return items;
+ }
+
+ public override void RemoveItem(DownloadClientItem item, bool deleteData)
+ {
+ _proxy.RemoveDownload(Settings, item, deleteData);
+ }
+
+ public override DownloadClientInfo GetStatus()
+ {
+ var config = _proxy.GetConfig(Settings);
+ var destDir = config.Settings.Download_defaults.Saveas;
+
+ if (Settings.TvCategory.IsNotNullOrWhiteSpace())
+ {
+ destDir = string.Format("{0}/.{1}", destDir, Settings.TvCategory);
+ }
+
+ return new DownloadClientInfo
+ {
+ IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "localhost",
+ OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) }
+ };
+ }
+
+ /**
+ * this basically checks if torrent is stopped because of seeding has finished
+ */
+ protected static bool HasReachedSeedLimit(Download torrent, GetTriblerSettingsResponse config)
+ {
+ if (config == null)
+ {
+ throw new ArgumentNullException(nameof(config));
+ }
+
+ if (torrent == null)
+ {
+ throw new ArgumentNullException(nameof(torrent));
+ }
+
+ // if download is still running then it's not finished.
+ if (torrent.Status != DownloadStatus.STOPPED)
+ {
+ return false;
+ }
+
+ switch (config.Settings.Download_defaults.Seeding_mode)
+ {
+ // if in ratio mode, wait for ratio to become larger than expeced. Tribler's DownloadStatus will switch from SEEDING to STOPPED
+ case Download_defaultsSeeding_mode.Ratio:
+
+ return torrent.Ratio.HasValue
+ && torrent.Ratio >= config.Settings.Download_defaults.Seeding_ratio;
+
+ case Download_defaultsSeeding_mode.Time:
+ var downloadStarted = DateTimeOffset.FromUnixTimeSeconds(torrent.Time_added.Value);
+ var maxSeedingTime = TimeSpan.FromSeconds(config.Settings.Download_defaults.Seeding_time ?? 0);
+
+ return torrent.Time_added.HasValue
+ && downloadStarted.Add(maxSeedingTime) < DateTimeOffset.Now;
+
+ case Download_defaultsSeeding_mode.Never:
+ return true;
+
+ case Download_defaultsSeeding_mode.Forever:
+ default:
+ return false;
+ }
+ }
+
+ protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
+ {
+ var addDownloadRequestObject = new AddDownloadRequest
+ {
+ Destination = GetDownloadDirectory(),
+ Uri = magnetLink,
+ Safe_seeding = Settings.SafeSeeding,
+ Anon_hops = Settings.AnonymityLevel
+ };
+
+ return _proxy.AddFromMagnetLink(Settings, addDownloadRequestObject);
+ }
+
+ protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent)
+ {
+ // tribler api currently do not support recieving a torrent file direcly.
+ throw new NotSupportedException("Tribler download client only support magnet links currently");
+ }
+
+ protected override void Test(List failures)
+ {
+ failures.AddIfNotNull(TestConnection());
+ if (failures.HasErrors())
+ {
+ return;
+ }
+
+ // failures.AddIfNotNull(TestGetTorrents());
+ }
+
+ protected string GetDownloadDirectory()
+ {
+ if (Settings.TvDirectory.IsNotNullOrWhiteSpace())
+ {
+ return Settings.TvDirectory;
+ }
+
+ if (!Settings.TvCategory.IsNotNullOrWhiteSpace())
+ {
+ return null;
+ }
+
+ var config = _proxy.GetConfig(Settings);
+ var destDir = config.Settings.Download_defaults.Saveas;
+
+ return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
+ }
+
+ protected ValidationFailure TestConnection()
+ {
+ try
+ {
+ var downloads = GetItems();
+ return null;
+ }
+ catch (DownloadClientAuthenticationException ex)
+ {
+ _logger.Error(ex, ex.Message);
+
+ return new NzbDroneValidationFailure("ApiKey", "Authentication failure")
+ {
+ DetailedDescription = string.Format("Please verify your ApiKey is correct. Also verify if the host running Sonarr isn't blocked from accessing {0} by WhiteList limitations in the {0} configuration.", Name)
+ };
+ }
+ catch (DownloadClientUnavailableException ex)
+ {
+ _logger.Error(ex, ex.Message);
+
+ return new NzbDroneValidationFailure("Url", "Unable to connect to Tribler")
+ {
+ DetailedDescription = ex.Message
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex, "Failed to test");
+
+ return new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message);
+ }
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadClientProxy.cs b/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadClientProxy.cs
new file mode 100644
index 000000000..c77c4da70
--- /dev/null
+++ b/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadClientProxy.cs
@@ -0,0 +1,137 @@
+using System.Collections.Generic;
+using NLog;
+using NzbDrone.Common.Http;
+using NzbDrone.Common.Serializer;
+using NzbDrone.Core.Indexers.Tribler;
+
+namespace NzbDrone.Core.Download.Clients.Tribler
+{
+ public interface ITriblerDownloadClientProxy
+ {
+ ICollection GetDownloads(TriblerDownloadSettings settings);
+
+ ICollection GetDownloadFiles(TriblerDownloadSettings settings, Download downloadItem);
+
+ GetTriblerSettingsResponse GetConfig(TriblerDownloadSettings settings);
+
+ void RemoveDownload(TriblerDownloadSettings settings, DownloadClientItem item, bool deleteData);
+
+ string AddFromMagnetLink(TriblerDownloadSettings settings, AddDownloadRequest downloadRequest);
+ }
+
+ public class TriblerDownloadClientProxy : ITriblerDownloadClientProxy
+ {
+ protected readonly IHttpClient _httpClient;
+ private readonly Logger _logger;
+
+ public TriblerDownloadClientProxy(IHttpClient httpClient, Logger logger)
+ {
+ _httpClient = httpClient;
+ _logger = logger;
+ }
+
+ private HttpRequestBuilder getRequestBuilder(TriblerDownloadSettings settings, string relativePath = null)
+ {
+ var requestBuilder = new HttpRequestBuilder(GetBaseUrl(settings, relativePath))
+ .Accept(HttpAccept.Json);
+
+ requestBuilder.Headers.Add("X-Api-Key", settings.ApiKey);
+
+ requestBuilder.LogResponseContent = true;
+
+ return requestBuilder;
+ }
+
+ public string GetBaseUrl(TriblerDownloadSettings settings, string relativePath = null)
+ {
+ var baseUrl = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase);
+ baseUrl = HttpUri.CombinePath(baseUrl, relativePath);
+
+ return baseUrl;
+ }
+
+ private T ProcessRequest(HttpRequestBuilder requestBuilder)
+ where T : new()
+ {
+ return ProcessRequest(requestBuilder.Build());
+ }
+
+ private T ProcessRequest(HttpRequest requestBuilder)
+ where T : new()
+ {
+ var httpRequest = requestBuilder;
+
+ HttpResponse response;
+
+ _logger.Debug("Url: {0}", httpRequest.Url);
+
+ try
+ {
+ response = _httpClient.Execute(httpRequest);
+ }
+ catch (HttpException ex)
+ {
+ if (ex.Response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
+ {
+ throw new DownloadClientAuthenticationException("Unauthorized - AuthToken is invalid", ex);
+ }
+
+ throw new DownloadClientUnavailableException("Unable to connect to Tribler. Status Code: {0}", ex.Response.StatusCode, ex);
+ }
+
+ return Json.Deserialize(response.Content);
+ }
+
+ public GetTriblerSettingsResponse GetConfig(TriblerDownloadSettings settings)
+ {
+ var configRequest = getRequestBuilder(settings, "settings");
+ return ProcessRequest(configRequest);
+ }
+
+ public ICollection GetDownloadFiles(TriblerDownloadSettings settings, Download downloadItem)
+ {
+ var filesRequest = getRequestBuilder(settings, "downloads/" + downloadItem.Infohash + "/files");
+ return ProcessRequest(filesRequest).Files;
+ }
+
+ public ICollection GetDownloads(TriblerDownloadSettings settings)
+ {
+ var downloadRequest = getRequestBuilder(settings, "downloads");
+ var downloads = ProcessRequest(downloadRequest);
+ return downloads.Downloads;
+ }
+
+ public void RemoveDownload(TriblerDownloadSettings settings, DownloadClientItem item, bool deleteData)
+ {
+ var deleteDownloadRequestObject = new RemoveDownloadRequest
+ {
+ Remove_data = deleteData
+ };
+
+ var deleteRequestBuilder = getRequestBuilder(settings, "downloads/" + item.DownloadId.ToLower());
+ deleteRequestBuilder.Method = System.Net.Http.HttpMethod.Delete;
+
+ // manually set content of delete request.
+ var deleteRequest = deleteRequestBuilder.Build();
+ deleteRequest.SetContent(Json.ToJson(deleteDownloadRequestObject));
+
+ ProcessRequest(deleteRequest);
+ }
+
+ public string AddFromMagnetLink(TriblerDownloadSettings settings, AddDownloadRequest downloadRequest)
+ {
+ // run hash through InfoHash class to ensure the correct casing.
+ var addDownloadRequestBuilder = getRequestBuilder(settings, "downloads");
+ addDownloadRequestBuilder.Method = System.Net.Http.HttpMethod.Put;
+
+ // manually set content of delete request.
+ var addDownloadRequest = addDownloadRequestBuilder.Build();
+ addDownloadRequest.SetContent(Json.ToJson(downloadRequest));
+
+ var infoHashAsString = ProcessRequest(addDownloadRequest).Infohash;
+
+ var infoHash = MonoTorrent.InfoHash.FromHex(infoHashAsString);
+ return infoHash.ToHex();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadSettings.cs b/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadSettings.cs
new file mode 100644
index 000000000..fc12d9f9c
--- /dev/null
+++ b/src/NzbDrone.Core/Download/Clients/Tribler/TriblerDownloadSettings.cs
@@ -0,0 +1,77 @@
+using System.Text.RegularExpressions;
+using FluentValidation;
+using NzbDrone.Common.Extensions;
+using NzbDrone.Core.Annotations;
+using NzbDrone.Core.ThingiProvider;
+using NzbDrone.Core.Validation;
+
+namespace NzbDrone.Core.Download.Clients.Tribler
+{
+ public class TriblerSettingsValidator : AbstractValidator
+ {
+ public TriblerSettingsValidator()
+ {
+ RuleFor(c => c.Host).ValidHost();
+ RuleFor(c => c.Port).InclusiveBetween(1, 65535);
+
+ RuleFor(c => c.UrlBase).ValidUrlBase();
+
+ RuleFor(c => c.ApiKey).NotEmpty();
+
+ RuleFor(c => c.TvCategory).Matches(@"^\.?[-a-z]*$", RegexOptions.IgnoreCase).WithMessage("Allowed characters a-z and -");
+
+ RuleFor(c => c.TvCategory).Empty()
+ .When(c => c.TvDirectory.IsNotNullOrWhiteSpace())
+ .WithMessage("Cannot use Category and Directory");
+
+ RuleFor(c => c.AnonymityLevel).GreaterThanOrEqualTo(0).WithMessage("Should be greater than or equal to zero");
+ }
+ }
+
+ public class TriblerDownloadSettings : IProviderConfig
+ {
+ private static readonly TriblerSettingsValidator Validator = new TriblerSettingsValidator();
+
+ public TriblerDownloadSettings()
+ {
+ Host = "localhost";
+ Port = 20100;
+ UrlBase = "";
+
+ AnonymityLevel = 1;
+ SafeSeeding = true;
+ }
+
+ [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 = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to Tribler")]
+ public bool UseSsl { get; set; }
+
+ [FieldDefinition(4, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the tribler url, eg http://[host]:[port]/[urlBase], defaults to ''")]
+ public string UrlBase { get; set; }
+
+ [FieldDefinition(5, Label = "ApiKey", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, HelpText = "Api key, found in %APPDATA%\\Roaming\\.Tribler\\7.10\\triblerd.conf, the api key is [api].key, NOT [http_api].key")]
+ public string ApiKey { get; set; }
+
+ [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional. Creates a [category] subdirectory in the output directory.")]
+ public string TvCategory { get; set; }
+
+ [FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
+ public string TvDirectory { get; set; }
+
+ [FieldDefinition(8, Label = "AnonymityLevel", Type = FieldType.Number, HelpText = "Number of proxies to use when downloading content. To disable set to 0. Proxies reduce download/upload speed. See https://www.tribler.org/anonymity.html")]
+ public int AnonymityLevel { get; set; }
+
+ [FieldDefinition(9, Label = "SafeSeeding", Type = FieldType.Checkbox, HelpText = "If seeding only should be done through proxies. The Anonymity level defines the number of proxies used. See https://www.tribler.org/anonymity.html")]
+ public bool SafeSeeding { get; set; }
+
+ public NzbDroneValidationResult Validate()
+ {
+ return new NzbDroneValidationResult(Validator.Validate(this));
+ }
+ }
+}