Skip to content

Commit d8324ae

Browse files
committed
Simplified API around IFtpClient
1 parent e174264 commit d8324ae

File tree

10 files changed

+97
-57
lines changed

10 files changed

+97
-57
lines changed

samples/FtpServer/FtpServerService.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,27 @@ private async Task ExecuteClientAsync(
123123
{
124124
await using var registration = cancellationToken.Register(
125125
connectionContext.Abort);
126-
var client = await _clientFactory.CreateClientAsync(connectionContext, cancellationToken);
127-
var clientInfo = new FtpClientInformation(client, connectionContext);
128-
lock (_activeClientsLock)
126+
try
129127
{
130-
_activeClients = _activeClients.Add(clientInfo);
131-
}
128+
var client = await _clientFactory.CreateClientAsync(connectionContext, cancellationToken);
129+
var clientInfo = new FtpClientInformation(client, connectionContext);
130+
lock (_activeClientsLock)
131+
{
132+
_activeClients = _activeClients.Add(clientInfo);
133+
}
132134

133-
_logger.LogTrace("FTP client added");
134-
await client.WaitForExitAsync(connectionContext.ConnectionClosed);
135+
_logger.LogTrace("FTP client added");
136+
await client.RunAsync(connectionContext.ConnectionClosed);
135137

136-
lock (_activeClientsLock)
138+
lock (_activeClientsLock)
139+
{
140+
_activeClients = _activeClients.Remove(clientInfo);
141+
_logger.LogTrace("FTP client removed");
142+
}
143+
}
144+
finally
137145
{
138-
_activeClients = _activeClients.Remove(clientInfo);
139-
_logger.LogTrace("FTP client removed");
146+
await connectionContext.DisposeAsync();
140147
}
141148
}
142149
}

samples/FtpServer/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
.AddLogging()
1919
.AddOptions()
2020
.AddSingleton<IConnectionListenerFactory, SocketTransportFactory>()
21-
//.AddEmbeddedClient()
22-
.AddExternalClient()
21+
.AddEmbeddedClient()
22+
//.AddExternalClient()
2323
.AddSingleton<IHostedService, FtpServerService>();
2424
services
2525
.Configure<FtpServerOptions>(opt => { opt.ListenEndPoints.Add(new IPEndPoint(IPAddress.IPv6Any, 8021)); });

src/FubarDev.FtpServer.Abstractions/IFtpClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ namespace FubarDev.FtpServer.Abstractions;
66

77
public interface IFtpClient
88
{
9-
Task WaitForExitAsync(CancellationToken cancellationToken = default);
9+
ValueTask RunAsync(CancellationToken cancellationToken = default);
1010
}

src/FubarDev.FtpServer.Client.Common/FtpConnection.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66

77
using FubarDev.FtpServer.Abstractions;
88

9-
using Microsoft.Extensions.Hosting;
109
using Microsoft.Extensions.Logging;
1110

1211
namespace FubarDev.FtpServer.Client.Common;
1312

14-
public class FtpConnection : BackgroundService
13+
public class FtpConnection
1514
{
1615
private readonly IFtpConnectionContextAccessor _connectionContextAccessor;
1716
private readonly ILogger<FtpConnection> _logger;
@@ -24,7 +23,7 @@ public FtpConnection(
2423
_logger = logger;
2524
}
2625

27-
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
26+
public async Task RunAsync(CancellationToken stoppingToken = default)
2827
{
2928
var connectionContext = _connectionContextAccessor.Context ?? throw new InvalidOperationException();
3029
var transport = connectionContext.ControlConnection;

src/FubarDev.FtpServer.Client.Embedded/EmbeddedFtpClient.cs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,57 @@
55
using FubarDev.FtpServer.Abstractions;
66
using FubarDev.FtpServer.Client.Common;
77

8+
using Microsoft.AspNetCore.Connections;
9+
using Microsoft.Extensions.DependencyInjection;
810
using Microsoft.Extensions.Logging;
911

12+
using Nerdbank.Streams;
13+
1014
namespace FubarDev.FtpServer.Client.Embedded;
1115

12-
internal class EmbeddedFtpClient : IFtpClient, IDisposable
16+
internal class EmbeddedFtpClient : IFtpClient
1317
{
14-
private readonly FtpConnection _connection;
15-
private readonly CancellationTokenSource _connectionTokenSource;
18+
private readonly ConnectionContext _connectionContext;
19+
private readonly IFtpConnectionContextAccessor _connectionContextAccessor;
20+
private readonly IServiceProvider _serviceProvider;
1621
private readonly ILogger<EmbeddedFtpClient> _logger;
1722

1823
public EmbeddedFtpClient(
19-
FtpConnection connection,
20-
CancellationTokenSource connectionTokenSource,
24+
ConnectionContext connectionContext,
25+
IFtpConnectionContextAccessor connectionContextAccessor,
26+
IServiceProvider serviceProvider,
2127
ILogger<EmbeddedFtpClient> logger)
2228
{
23-
_connection = connection;
24-
_connectionTokenSource = connectionTokenSource;
29+
_connectionContext = connectionContext;
30+
_connectionContextAccessor = connectionContextAccessor;
31+
_serviceProvider = serviceProvider;
2532
_logger = logger;
2633
}
2734

28-
public async Task WaitForExitAsync(CancellationToken cancellationToken = default)
35+
public async ValueTask RunAsync(CancellationToken cancellationToken = default)
2936
{
37+
var cts = new CancellationTokenSource();
38+
var transport = new DuplexPipe(
39+
_connectionContext.Transport.Input,
40+
_connectionContext.Transport.Output
41+
.OnCompleted((_, _) => cts.Cancel()));
42+
_connectionContextAccessor.Context = new FtpConnectionContext(transport);
43+
var connection = ActivatorUtilities.CreateInstance<FtpConnection>(_serviceProvider);
44+
3045
using var stoppingTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
3146
cancellationToken,
32-
_connectionTokenSource.Token);
47+
_connectionContext.ConnectionClosed);
3348
try
3449
{
35-
await Task.Delay(Timeout.Infinite, stoppingTokenSource.Token);
50+
using var loggerScope = _logger.BeginScope(
51+
new Dictionary<string, object?>()
52+
{
53+
["ConnectionId"] = _connectionContext.ConnectionId,
54+
["LocalEndPoint"] = _connectionContext.LocalEndPoint,
55+
["RemoteEndPoint"] = _connectionContext.RemoteEndPoint,
56+
});
57+
_logger.LogTrace("FTP connection starting");
58+
await connection.RunAsync(stoppingTokenSource.Token);
3659
}
3760
catch (OperationCanceledException)
3861
{
@@ -43,12 +66,6 @@ public async Task WaitForExitAsync(CancellationToken cancellationToken = default
4366
_logger.LogError(exception, "Delay aborted with error {ErrorMessage}", exception.Message);
4467
}
4568

46-
_logger.LogTrace("Stopping FTP connection");
47-
await _connection.StopAsync(cancellationToken);
48-
}
49-
50-
public void Dispose()
51-
{
52-
_connectionTokenSource.Dispose();
69+
_logger.LogTrace("FTP connection stopped");
5370
}
5471
}

src/FubarDev.FtpServer.Client.Embedded/EmbeddedFtpClientFactory.cs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,9 @@ public async ValueTask<IFtpClient> CreateClientAsync(
3333
ConnectionContext connectionContext,
3434
CancellationToken cancellationToken = default)
3535
{
36-
var cts = new CancellationTokenSource();
37-
var transport = new DuplexPipe(
38-
connectionContext.Transport.Input,
39-
connectionContext.Transport.Output
40-
.OnCompleted((_, _) => cts.Cancel()));
41-
_connectionContextAccessor.Context = new FtpConnectionContext(transport);
42-
var connection = ActivatorUtilities.CreateInstance<FtpConnection>(_serviceProvider);
43-
44-
await connection.StartAsync(cancellationToken);
45-
if (connection.ExecuteTask.IsCompleted)
46-
{
47-
_logger.LogWarning("FTP connection stopped too early");
48-
await connection.ExecuteTask;
49-
}
50-
5136
var ftpClient = ActivatorUtilities.CreateInstance<EmbeddedFtpClient>(
5237
_serviceProvider,
53-
connection,
54-
cts);
38+
connectionContext);
5539
return ftpClient;
5640
}
5741
}

src/FubarDev.FtpServer.Client.External/ExternalFtpClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public ExternalFtpClient(
1818
_executionTask = executionTask;
1919
}
2020

21-
public async Task WaitForExitAsync(CancellationToken cancellationToken = default)
21+
public async ValueTask RunAsync(CancellationToken cancellationToken = default)
2222
{
2323
await _executionTask;
2424
}

src/FubarDev.FtpServer.Client.External/ExternalFtpClientFactory.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,23 @@
99
using FubarDev.FtpServer.Abstractions;
1010

1111
using Microsoft.AspNetCore.Connections;
12+
using Microsoft.Extensions.DependencyInjection;
1213

1314
using Nerdbank.Streams;
1415

1516
namespace FubarDev.FtpServer.Client.External;
1617

1718
public class ExternalFtpClientFactory : IFtpClientFactory
1819
{
20+
private readonly IServiceProvider _serviceProvider;
1921
private readonly Assembly _clientAssembly;
2022
private readonly string _clientExecutable;
2123
private readonly bool _startedWithDotnetTool;
2224

23-
public ExternalFtpClientFactory()
25+
public ExternalFtpClientFactory(
26+
IServiceProvider serviceProvider)
2427
{
28+
_serviceProvider = serviceProvider;
2529
var startExecutable = Path.GetFileName(Environment.ProcessPath)
2630
?? throw new InvalidOperationException();
2731
_startedWithDotnetTool = startExecutable.ToLowerInvariant() switch
@@ -42,7 +46,7 @@ public ExternalFtpClientFactory()
4246
};
4347
}
4448

45-
public ValueTask<IFtpClient> CreateClientAsync(
49+
public async ValueTask<IFtpClient> CreateClientAsync(
4650
ConnectionContext connectionContext,
4751
CancellationToken cancellationToken = default)
4852
{
@@ -62,10 +66,13 @@ public ValueTask<IFtpClient> CreateClientAsync(
6266
.WithStandardOutputPipe(PipeTarget.ToStream(stream))
6367
.WithStandardInputPipe(PipeSource.FromStream(stream))
6468
.WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError()))
65-
.ExecuteAsync();
69+
.ExecuteAsync(CancellationToken.None);
6670

67-
var ftpClient = new ExternalFtpClient(cliTask);
68-
return ValueTask.FromResult<IFtpClient>(ftpClient);
71+
// var multiplexor = await MultiplexingStream.CreateAsync(stream, cancellationToken);
72+
var ftpClient = ActivatorUtilities.CreateInstance<ExternalFtpClient>(
73+
_serviceProvider,
74+
cliTask);
75+
return ftpClient;
6976
}
7077

7178
private static string? ProbeExecutableFor(Assembly assembly)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// <copyright file="HostedFtpConnection.cs" company="Fubar Development Junker">
2+
// Copyright (c) Fubar Development Junker. All rights reserved.
3+
// </copyright>
4+
5+
using FubarDev.FtpServer.Client.Common;
6+
7+
using Microsoft.Extensions.Hosting;
8+
9+
namespace FubarDev.FtpServer.Client.External;
10+
11+
public class HostedFtpConnection : BackgroundService
12+
{
13+
private readonly FtpConnection _connection;
14+
15+
public HostedFtpConnection(FtpConnection connection)
16+
{
17+
_connection = connection;
18+
}
19+
20+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
21+
{
22+
await _connection.RunAsync(stoppingToken);
23+
}
24+
}

src/FubarDev.FtpServer.Client.External/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
using FubarDev.FtpServer.Abstractions;
88
using FubarDev.FtpServer.Client.Common;
9+
using FubarDev.FtpServer.Client.External;
910

1011
using Microsoft.Extensions.DependencyInjection;
1112
using Microsoft.Extensions.Hosting;
@@ -31,7 +32,8 @@
3132
services.Configure<ConsoleLoggerOptions>(
3233
opt => opt.LogToStandardErrorThreshold = LogLevel.Trace);
3334
services.AddSingleton<IFtpConnectionContextAccessor, FtpConnectionContextAccessor>();
34-
services.AddSingleton<IHostedService, FtpConnection>();
35+
services.AddSingleton<FtpConnection>();
36+
services.AddSingleton<IHostedService, HostedFtpConnection>();
3537
})
3638
.Build();
3739

0 commit comments

Comments
 (0)