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 + } + } + } +}