diff --git a/NuGet.config b/NuGet.config
deleted file mode 100644
index 65d701d..0000000
--- a/NuGet.config
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/TorrentCore.Cli/Program.cs b/TorrentCore.Cli/Program.cs
index b210dc3..10ebebb 100644
--- a/TorrentCore.Cli/Program.cs
+++ b/TorrentCore.Cli/Program.cs
@@ -6,50 +6,63 @@
// 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 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(new[] { "-p", "--port" }, "Port to listen for incoming connections on.")
+ {
+ IsRequired = true,
+ },
+ new Option(new[] { "-o", "--output" }, "Path to save downloaded files to.")
+ {
+ IsRequired = true,
+ },
+ new Option(new[] { "-u", "--upnp" }, "Use UPnP for port forwarding."),
+ new Option(new[] { "-v", "--verbose" }, "Show detailed logging information."),
+ new Argument("--input", "Path of torrent file to download.")
+ {
+ Arity = ArgumentArity.ExactlyOne,
+ },
+ };
+ root.Handler = CommandHandler.Create(RunAsync);
+ return new CommandLineBuilder(root);
+ }
+ private static async Task 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();
@@ -57,7 +70,11 @@ public static void Main(string[] args)
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 =>
@@ -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...");
@@ -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; }
+ }
}
}
diff --git a/TorrentCore.Cli/TorrentCore.Cli.csproj b/TorrentCore.Cli/TorrentCore.Cli.csproj
index 915ee78..1c02daf 100644
--- a/TorrentCore.Cli/TorrentCore.Cli.csproj
+++ b/TorrentCore.Cli/TorrentCore.Cli.csproj
@@ -12,6 +12,10 @@
bin\Debug\$(AssemblyName).xml
+
+ AnyCPU
+
+
@@ -22,9 +26,9 @@
-
-
-
+
+
+
diff --git a/TorrentCore.sln b/TorrentCore.sln
index b06187e..3611dd0 100644
--- a/TorrentCore.sln
+++ b/TorrentCore.sln
@@ -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
diff --git a/TorrentCore/TorrentClientBuilder.cs b/TorrentCore/TorrentClientBuilder.cs
index d76d693..17d9c80 100644
--- a/TorrentCore/TorrentClientBuilder.cs
+++ b/TorrentCore/TorrentClientBuilder.cs
@@ -88,6 +88,16 @@ public TorrentClientBuilder UsePort(int port)
return this;
}
+ ///
+ /// Add UPnP port forwarding feature.
+ ///
+ /// TorrentClientBuilder.
+ public TorrentClientBuilder AddUPnP()
+ {
+ _services.Configure(options => options.UseUPnP = true);
+ return this;
+ }
+
///
/// Sets up a default Torrent Client using the TCP transport protocol and the BitTorrent application protocol.
///
diff --git a/TorrentCore/TorrentClientSettings.cs b/TorrentCore/TorrentClientSettings.cs
index 616d4b7..3c50fae 100644
--- a/TorrentCore/TorrentClientSettings.cs
+++ b/TorrentCore/TorrentClientSettings.cs
@@ -21,6 +21,7 @@ public TorrentClientSettings()
PeerId = PeerId.CreateNew();
ListenPort = 6881;
AdapterAddress = IPAddress.Any;
+ UseUPnP = false;
}
///
@@ -28,6 +29,11 @@ public TorrentClientSettings()
///
public PeerId PeerId { get; set; }
+ ///
+ /// Gets or sets a value indicating whether to use UPnP for port forwarding.
+ ///
+ public bool UseUPnP { get; set; }
+
///
/// Gets or sets the port to listen for incoming connections on.
///
diff --git a/TorrentCore/TorrentCore.csproj b/TorrentCore/TorrentCore.csproj
index eb97155..02360d8 100644
--- a/TorrentCore/TorrentCore.csproj
+++ b/TorrentCore/TorrentCore.csproj
@@ -31,6 +31,7 @@
+
diff --git a/TorrentCore/Transport/IUPnPClient.cs b/TorrentCore/Transport/IUPnPClient.cs
new file mode 100644
index 0000000..0e090e2
--- /dev/null
+++ b/TorrentCore/Transport/IUPnPClient.cs
@@ -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);
+ }
+}
diff --git a/TorrentCore/Transport/Tcp/LocalTcpConnectionOptions.cs b/TorrentCore/Transport/Tcp/LocalTcpConnectionOptions.cs
index ff95fd5..b33ca21 100644
--- a/TorrentCore/Transport/Tcp/LocalTcpConnectionOptions.cs
+++ b/TorrentCore/Transport/Tcp/LocalTcpConnectionOptions.cs
@@ -28,5 +28,10 @@ public class LocalTcpConnectionOptions
/// Gets or sets the address of the local adapter used for connections.
///
public IPAddress BindAddress { get; set; } = IPAddress.Any;
+
+ ///
+ /// Gets or sets a value indicating whether to use UPnP for port forwarding.
+ ///
+ public bool UseUPnP { get; set; }
}
}
diff --git a/TorrentCore/Transport/Tcp/TcpTransportProtocol.cs b/TorrentCore/Transport/Tcp/TcpTransportProtocol.cs
index 0b9ca20..38d2d73 100644
--- a/TorrentCore/Transport/Tcp/TcpTransportProtocol.cs
+++ b/TorrentCore/Transport/Tcp/TcpTransportProtocol.cs
@@ -27,6 +27,7 @@ class TcpTransportProtocol : ITcpTransportProtocol
{
private readonly ILogger _logger;
private readonly ConcurrentBag _streams;
+ private readonly IUPnPClient _uPnPClient;
private TcpListener _listener;
@@ -40,6 +41,7 @@ public TcpTransportProtocol(ILogger logger, IOptions();
Port = options.Value.Port;
+ _uPnPClient = new TcpTransportUPnPClient(options.Value.UseUPnP);
LocalBindAddress = options.Value.BindAddress ?? IPAddress.Any;
RateLimiter = new RateLimiter();
}
@@ -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);
}
///
diff --git a/TorrentCore/Transport/Tcp/TcpTransportUPnPClient.cs b/TorrentCore/Transport/Tcp/TcpTransportUPnPClient.cs
new file mode 100644
index 0000000..ac90e7c
--- /dev/null
+++ b/TorrentCore/Transport/Tcp/TcpTransportUPnPClient.cs
@@ -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"));
+ }
+ catch
+ {
+ // Do nothing
+ }
+ }
+ }
+}