Introduced tribler as a download client.

The client is based on the structure of Rargb downloader.

Tribler Api calls are in TriblerDownloadClientProxy
This commit is contained in:
Jesper Utoft 2021-10-21 21:09:03 +02:00 committed by Jesper Utoft
parent 2f04b037a1
commit fdb990b1ad
5 changed files with 927 additions and 0 deletions

View File

@ -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
{
/// <summary>url of tracker</summary>
[Newtonsoft.Json.JsonProperty("url", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Url { get; set; }
/// <summary>number of peers</summary>
[Newtonsoft.Json.JsonProperty("peers", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public object Peers { get; set; }
/// <summary>If the tracker is working or not.</summary>
[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> 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<string> 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<string> 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<Download> Downloads { get; set; }
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
public partial class AddDownloadRequest
{
/// <summary>Number of hops for the anonymous download. No hops is equivalent to a plain download</summary>
[Newtonsoft.Json.JsonProperty("anon_hops", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public long? Anon_hops { get; set; }
/// <summary>Whether the seeding of the download should be anonymous or not</summary>
[Newtonsoft.Json.JsonProperty("safe_seeding", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public bool? Safe_seeding { get; set; }
/// <summary>the download destination path of the torrent</summary>
[Newtonsoft.Json.JsonProperty("destination", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Destination { get; set; }
/// <summary>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.</summary>
[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
{
/// <summary>Whether or not to remove the associated data</summary>
[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
{
/// <summary>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.</summary>
[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<int> Selected_files { get; set; }
/// <summary>State parameter to be passed to modify the state of the download (resume/stop/recheck)</summary>
[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<File> Files { get; set; }
}
}

View File

@ -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; }
/// <summary>Seeding ratio download/upload</summary>
[Newtonsoft.Json.JsonProperty("seeding_ratio", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public double? Seeding_ratio { get; set; }
/// <summary>Seeding time in seconds</summary>
[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,
}
}

View File

@ -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<TriblerDownloadSettings>
{
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<DownloadClientItem> GetItems()
{
var configAsync = _proxy.GetConfig(Settings);
var items = new List<DownloadClientItem>();
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<OsPath> { _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<ValidationFailure> 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);
}
}
}
}

View File

@ -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<Download> GetDownloads(TriblerDownloadSettings settings);
ICollection<File> 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<T>(HttpRequestBuilder requestBuilder)
where T : new()
{
return ProcessRequest<T>(requestBuilder.Build());
}
private T ProcessRequest<T>(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<T>(response.Content);
}
public GetTriblerSettingsResponse GetConfig(TriblerDownloadSettings settings)
{
var configRequest = getRequestBuilder(settings, "settings");
return ProcessRequest<GetTriblerSettingsResponse>(configRequest);
}
public ICollection<File> GetDownloadFiles(TriblerDownloadSettings settings, Download downloadItem)
{
var filesRequest = getRequestBuilder(settings, "downloads/" + downloadItem.Infohash + "/files");
return ProcessRequest<GetFilesResponse>(filesRequest).Files;
}
public ICollection<Download> GetDownloads(TriblerDownloadSettings settings)
{
var downloadRequest = getRequestBuilder(settings, "downloads");
var downloads = ProcessRequest<DownloadsResponse>(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<DeleteDownloadResponse>(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<AddDownloadResponse>(addDownloadRequest).Infohash;
var infoHash = MonoTorrent.InfoHash.FromHex(infoHashAsString);
return infoHash.ToHex();
}
}
}

View File

@ -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<TriblerDownloadSettings>
{
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));
}
}
}