Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions NuGet.config

This file was deleted.

93 changes: 59 additions & 34 deletions TorrentCore.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,58 +6,75 @@
// LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.CommandLine;
using System.Diagnostics;
using System.CommandLine.Builder;
using System.CommandLine.Hosting;
using System.CommandLine.NamingConventionBinder;
using System.CommandLine.Parsing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using TorrentCore.Extensions.ExtensionProtocol;
using TorrentCore.Extensions.PeerExchange;
using TorrentCore.Extensions.SendMetadata;
using TorrentCore.Modularity;
using TorrentCore.Web;

namespace TorrentCore.Cli
{
public class Program
{
public static void Main(string[] args)
public static async Task<int> Main(string[] args)
{
int port = 5000;
int uiPort = 5001;
bool runWebUi = false;
string input = null;
string output = null;
bool verbose = false;
ArgumentSyntax.Parse(args, syntax =>
{
syntax.DefineOption("p|port", ref port, "Port to listen for incoming connections on.");
syntax.DefineOption("o|output", ref output, "Path to save downloaded files to.");
syntax.DefineOption("v|verbose", ref verbose, "Show detailed logging information.");
var uiPortArgument = syntax.DefineOption("ui", ref uiPort, false, "Run a web UI, optionally specifying the port to listen on (default: 5001).");
runWebUi = uiPortArgument.IsSpecified;
return await BuildCommandLine()
.UseHost(host => host.UseSerilog())
.UseDefaults()
.Build()
.InvokeAsync(args);
}

syntax.DefineParameter("input", ref input, "Path of torrent file to download.");
});
private static CommandLineBuilder BuildCommandLine()
{
var root = new RootCommand()
{
new Option<int>(new[] { "-p", "--port" }, "Port to listen for incoming connections on.")
{
IsRequired = true,
},
new Option<DirectoryInfo>(new[] { "-o", "--output" }, "Path to save downloaded files to.")
{
IsRequired = true,
},
new Option<bool>(new[] { "-u", "--upnp" }, "Use UPnP for port forwarding."),
new Option<bool>(new[] { "-v", "--verbose" }, "Show detailed logging information."),
new Argument<FileInfo>("--input", "Path of torrent file to download.")
{
Arity = ArgumentArity.ExactlyOne,
},
};
root.Handler = CommandHandler.Create<Options>(RunAsync);
return new CommandLineBuilder(root);
}

private static async Task<int> RunAsync(Options options)
{
var builder = TorrentClientBuilder.CreateDefaultBuilder();

// Configure logging
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Is(verbose ? Serilog.Events.LogEventLevel.Debug : Serilog.Events.LogEventLevel.Information)
.MinimumLevel.Is(options.Verbose ? Serilog.Events.LogEventLevel.Debug : Serilog.Events.LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();

builder.ConfigureServices(services => services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true)));

// Listen for incoming connections on the specified port
builder.UsePort(port);
builder.UsePort(options.Port);

// If upnp is selected use it.
if (options.UPnP)
builder.AddUPnP();

// Add extension protocol
builder.ConfigureServices(services =>
Expand All @@ -72,15 +89,9 @@ public static void Main(string[] args)
});
});

var client = builder.Build();

////if (runWebUi)
////{
//// var uri = client.EnableWebUI(uiPort);
//// Console.WriteLine($"Web UI started at {uri}");
////}
using var client = builder.Build();

var download = client.Add(input, output);
var download = client.Add(options.Input.FullName, options.Output.FullName);
download.Start();

Console.WriteLine("Downloading...");
Expand All @@ -90,14 +101,28 @@ public static void Main(string[] args)
timer.Elapsed += (o, e) => LogStatus(download);
timer.Start();

download.WaitForDownloadCompletionAsync().Wait();
await download.WaitForDownloadCompletionAsync();
}
Console.ReadKey();

return 0;
}

private static void LogStatus(TorrentDownload download)
{
Console.WriteLine($"{download.State} ({download.Progress:P})");
}

private class Options
{
public int Port { get; set; }

public DirectoryInfo Output { get; set; }

public bool UPnP { get; set; }

public bool Verbose { get; set; }

public FileInfo Input { get; set; }
}
}
}
10 changes: 7 additions & 3 deletions TorrentCore.Cli/TorrentCore.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<DocumentationFile>bin\Debug\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
Comment on lines +15 to +17
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks unnecessary, can you remove it?


<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Visible="false" />
</ItemGroup>
Expand All @@ -22,9 +26,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="System.CommandLine" Version="0.1.0-e170729-1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="System.CommandLine.Hosting" Version="0.4.0-alpha.22114.1" />
<PackageReference Include="System.Net.NetworkInformation" Version="4.3.0" />
</ItemGroup>

Expand Down
5 changes: 2 additions & 3 deletions TorrentCore.sln
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.4
# Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5BD8F3F0-5E3F-40CB-9534-CA78C50E88EF}"
ProjectSection(SolutionItems) = preProject
.travis.yml = .travis.yml
Directory.build.props = Directory.build.props
LICENSE = LICENSE
NuGet.config = NuGet.config
README.md = README.md
stylecop.json = stylecop.json
StyleCopRules.ruleset = StyleCopRules.ruleset
Expand Down
10 changes: 10 additions & 0 deletions TorrentCore/TorrentClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ public TorrentClientBuilder UsePort(int port)
return this;
}

/// <summary>
/// Add UPnP port forwarding feature.
/// </summary>
/// <returns>TorrentClientBuilder.</returns>
public TorrentClientBuilder AddUPnP()
{
_services.Configure<LocalTcpConnectionOptions>(options => options.UseUPnP = true);
return this;
}

/// <summary>
/// Sets up a default Torrent Client using the TCP transport protocol and the BitTorrent application protocol.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions TorrentCore/TorrentClientSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@ public TorrentClientSettings()
PeerId = PeerId.CreateNew();
ListenPort = 6881;
AdapterAddress = IPAddress.Any;
UseUPnP = false;
}

/// <summary>
/// Gets or sets the Peer ID for the local client.
/// </summary>
public PeerId PeerId { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to use UPnP for port forwarding.
/// </summary>
public bool UseUPnP { get; set; }

/// <summary>
/// Gets or sets the port to listen for incoming connections on.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions TorrentCore/TorrentCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<PackageReference Include="System.Net.Sockets" Version="4.3.0" />
<PackageReference Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
<PackageReference Include="Open.NAT.Core" Version="2.1.0" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions TorrentCore/Transport/IUPnPClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// This file is part of TorrentCore.
// https://torrentcore.org
// Copyright (c) Samuel Fisher.
//
// Licensed under the GNU Lesser General Public License, version 3. See the
// LICENSE file in the project root for full license information.

using System.Threading.Tasks;

namespace TorrentCore.Transport
{
public interface IUPnPClient
{
Task TryAddPortMappingAsync(int port);
}
}
5 changes: 5 additions & 0 deletions TorrentCore/Transport/Tcp/LocalTcpConnectionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@ public class LocalTcpConnectionOptions
/// Gets or sets the address of the local adapter used for connections.
/// </summary>
public IPAddress BindAddress { get; set; } = IPAddress.Any;

/// <summary>
/// Gets or sets a value indicating whether to use UPnP for port forwarding.
/// </summary>
public bool UseUPnP { get; set; }
}
}
4 changes: 4 additions & 0 deletions TorrentCore/Transport/Tcp/TcpTransportProtocol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class TcpTransportProtocol : ITcpTransportProtocol
{
private readonly ILogger<TcpTransportProtocol> _logger;
private readonly ConcurrentBag<TcpTransportStream> _streams;
private readonly IUPnPClient _uPnPClient;

private TcpListener _listener;

Expand All @@ -40,6 +41,7 @@ public TcpTransportProtocol(ILogger<TcpTransportProtocol> logger, IOptions<Local
_logger = logger;
_streams = new ConcurrentBag<TcpTransportStream>();
Port = options.Value.Port;
_uPnPClient = new TcpTransportUPnPClient(options.Value.UseUPnP);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you register this in the DI container and inject it instead of creating it here? This will allow users more flexibility to change how UPnP is handled if required.

LocalBindAddress = options.Value.BindAddress ?? IPAddress.Any;
RateLimiter = new RateLimiter();
}
Expand Down Expand Up @@ -108,6 +110,8 @@ public void Start()
// If port=0 was supplied, set the actual port we are listening on.
Port = ((IPEndPoint)_listener.LocalEndpoint).Port;
ListenForIncomingConnections();

_uPnPClient.TryAddPortMappingAsync(Port);
}

/// <summary>
Expand Down
42 changes: 42 additions & 0 deletions TorrentCore/Transport/Tcp/TcpTransportUPnPClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// This file is part of TorrentCore.
// https://torrentcore.org
// Copyright (c) Samuel Fisher.
//
// Licensed under the GNU Lesser General Public License, version 3. See the
// LICENSE file in the project root for full license information.

using System.Threading;
using System.Threading.Tasks;
using Open.Nat;

namespace TorrentCore.Transport.Tcp
{
class TcpTransportUPnPClient : IUPnPClient
{
private bool _useUPnP;

public TcpTransportUPnPClient(bool useUPnP)
{
_useUPnP = useUPnP;
}

public async Task TryAddPortMappingAsync(int port)
{
if (!_useUPnP)
return;

try
{
var discoverer = new NatDiscoverer();
var cts = new CancellationTokenSource(10000);

var device = await discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cts);
await device.CreatePortMapAsync(new Mapping(Protocol.Tcp, port, port, "TorrentCore"));
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't take into consideration which IP the application is listening on (TorrentClientSettings.AdapterAddress).

For context, AdapterAddress is used for situations such as forcing TorrentCore to run over a VPN connection. In this situation it could end up adding the mapping onto a network the user specifically doesn't want TorrentCore to be using.

}
catch
{
// Do nothing
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What type of exceptions does this throw? It could do with some logging to indicate something has gone wrong.

}
}
}
}