Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,30 @@ namespace Ark.Tools.FtpClient.FluentFtp;
public sealed class FluentFtpClientConnection : FtpClientConnectionBase
{
private readonly IAsyncFtpClient _client;
private readonly Action<string, FluentFTP.FtpConfig>? _configureClient;

private bool _isDisposed;

/// <summary>
/// Initializes a new instance of the <see cref="FluentFtpClientConnection"/> class.
/// </summary>
/// <param name="ftpConfig">The FTP connection settings.</param>
public FluentFtpClientConnection(FtpConfig ftpConfig)
: this(ftpConfig, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FluentFtpClientConnection"/> class.
/// </summary>
/// <param name="ftpConfig">The FTP connection settings.</param>
/// <param name="configureClient">
/// Callback used to customize the FluentFTP client configuration for the requested host before the client is created.
/// </param>
public FluentFtpClientConnection(FtpConfig ftpConfig, Action<string, FluentFTP.FtpConfig>? configureClient)
: base(ftpConfig)
{
_configureClient = configureClient;
_client = _getClient();
}

Expand Down Expand Up @@ -89,13 +107,13 @@ protected override void Dispose(bool disposing)

private IAsyncFtpClient _getClient()
{
AsyncFtpClient client;

client = new AsyncFtpClient(Uri.Host, Credentials, Uri.Port, new FluentFTP.FtpConfig
var clientConfig = new FluentFTP.FtpConfig
{
SocketKeepAlive = true,
});
};

_configureClient?.Invoke(Uri.Host, clientConfig);

return client;
return new AsyncFtpClient(Uri.Host, Credentials, Uri.Port, clientConfig);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,33 @@ namespace Ark.Tools.FtpClient.FluentFtp;

public class FluentFtpClientConnectionFactory : IFtpClientConnectionFactory
{
private readonly Action<string, FluentFTP.FtpConfig>? _configureClient;

/// <summary>
/// Initializes a new instance of the <see cref="FluentFtpClientConnectionFactory"/> class.
/// </summary>
public FluentFtpClientConnectionFactory()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FluentFtpClientConnectionFactory"/> class.
/// </summary>
/// <param name="configureClient">
/// Callback used to customize the FluentFTP client configuration for the requested host before the client is created.
/// </param>
public FluentFtpClientConnectionFactory(Action<string, FluentFTP.FtpConfig> configureClient)
{
_configureClient = configureClient ?? throw new ArgumentNullException(nameof(configureClient));
}

/// <inheritdoc />
public IFtpClientConnection Create(FtpConfig ftpConfig)
{
ArgumentNullException.ThrowIfNull(ftpConfig);
ArgumentNullException.ThrowIfNull(ftpConfig.Uri);
ArgumentNullException.ThrowIfNull(ftpConfig.Credentials);

return new FluentFtpClientConnection(ftpConfig);
return new FluentFtpClientConnection(ftpConfig, _configureClient);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@ namespace Ark.Tools.FtpClient.FluentFtp;

public sealed class FluentFtpClientFactory : DefaultFtpClientFactory
{
/// <summary>
/// Initializes a new instance of the <see cref="FluentFtpClientFactory"/> class.
/// </summary>
public FluentFtpClientFactory()
: base(new FluentFtpClientConnectionFactory())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FluentFtpClientFactory"/> class.
/// </summary>
/// <param name="configureClient">
/// Callback used to customize the FluentFTP client configuration for the requested host before the client is created.
/// </param>
public FluentFtpClientFactory(Action<string, FluentFTP.FtpConfig> configureClient)
: base(new FluentFtpClientConnectionFactory(configureClient))
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@ namespace Ark.Tools.FtpClient.FluentFtp;

public sealed class FluentFtpClientPoolFactory : DefaultFtpClientPoolFactory
{
/// <summary>
/// Initializes a new instance of the <see cref="FluentFtpClientPoolFactory"/> class.
/// </summary>
public FluentFtpClientPoolFactory()
: base(new FluentFtpClientConnectionFactory())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="FluentFtpClientPoolFactory"/> class.
/// </summary>
/// <param name="configureClient">
/// Callback used to customize the FluentFTP client configuration for the requested host before the client is created.
/// </param>
public FluentFtpClientPoolFactory(Action<string, FluentFTP.FtpConfig> configureClient)
: base(new FluentFtpClientConnectionFactory(configureClient))
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="../../src/common/Ark.Tools.FtpClient.FluentFtp/Ark.Tools.FtpClient.FluentFtp.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (C) 2024 Ark Energy S.r.l. All rights reserved.
// Licensed under the MIT License. See LICENSE file for license information.

using Ark.Tools.FtpClient.Core;

using AwesomeAssertions;

using System.Net;
using System.Reflection;

namespace Ark.Tools.FtpClient.FluentFtp.Tests;

[TestClass]
public class FluentFtpClientFactoryTests
{
[TestMethod]
public void ClientFactory_ShouldInvokeHostAwareConfigCallback()
{
var callbackHost = string.Empty;
var ftpClientFactory = new FluentFtpClientFactory((host, config) =>
{
callbackHost = host;
config.SocketKeepAlive = false;
});

using var setup = CreateConnectionFromFactory(ftpClientFactory, "ftp://client-factory.example.com");
var clientConfig = GetFluentFtpConfig(setup.Connection);

callbackHost.Should().Be("client-factory.example.com");
clientConfig.SocketKeepAlive.Should().BeFalse();
}

[TestMethod]
public void PoolFactory_ShouldInvokeHostAwareConfigCallback()
{
var callbackHost = string.Empty;
var ftpClientPoolFactory = new FluentFtpClientPoolFactory((host, config) =>
{
callbackHost = host;
config.SocketKeepAlive = false;
});

using var setup = CreateConnectionFromFactory(ftpClientPoolFactory, "ftp://pool-factory.example.com");
var clientConfig = GetFluentFtpConfig(setup.Connection);

callbackHost.Should().Be("pool-factory.example.com");
clientConfig.SocketKeepAlive.Should().BeFalse();
}

private static FluentFTP.FtpConfig GetFluentFtpConfig(FluentFtpClientConnection connection)
{
var clientField = typeof(FluentFtpClientConnection).GetField("_client", BindingFlags.Instance | BindingFlags.NonPublic);
clientField.Should().NotBeNull();

var client = clientField!.GetValue(connection);
client.Should().NotBeNull();

var configProperty = client!.GetType().GetProperty("Config", BindingFlags.Instance | BindingFlags.Public);
configProperty.Should().NotBeNull();

var config = configProperty!.GetValue(client);
config.Should().BeOfType<FluentFTP.FtpConfig>();
if (config is not FluentFTP.FtpConfig ftpConfig)
{
throw new InvalidOperationException("Expected Config to be a non-null FluentFTP.FtpConfig.");
}

return ftpConfig;
}

private static ConnectionSetup CreateConnectionFromFactory(FluentFtpClientFactory ftpClientFactory, string uri)
{
var connectionFactoryField = typeof(DefaultFtpClientFactory).GetField("_connectionFactory", BindingFlags.Instance | BindingFlags.NonPublic);
connectionFactoryField.Should().NotBeNull();

var connectionFactory = connectionFactoryField!.GetValue(ftpClientFactory) as IFtpClientConnectionFactory;
connectionFactory.Should().NotBeNull();

var ftpConfig = new FtpConfig(new Uri(uri), new NetworkCredential("user", "password"));
var connection = connectionFactory!.Create(ftpConfig);
return new ConnectionSetup(connection.Should().BeOfType<FluentFtpClientConnection>().Subject, ftpConfig);
}

private static ConnectionSetup CreateConnectionFromFactory(FluentFtpClientPoolFactory ftpClientPoolFactory, string uri)
{
var connectionFactoryField = typeof(DefaultFtpClientPoolFactory).GetField("_connectionFactory", BindingFlags.Instance | BindingFlags.NonPublic);
connectionFactoryField.Should().NotBeNull();

if (connectionFactoryField!.GetValue(ftpClientPoolFactory) is not IFtpClientConnectionFactory connectionFactory)
{
throw new InvalidOperationException("Expected _connectionFactory to be an IFtpClientConnectionFactory.");
}

var ftpConfig = new FtpConfig(new Uri(uri), new NetworkCredential("user", "password"));
var connection = connectionFactory.Create(ftpConfig);
return new ConnectionSetup(connection.Should().BeOfType<FluentFtpClientConnection>().Subject, ftpConfig);
}

private sealed class ConnectionSetup : IDisposable
{
public ConnectionSetup(FluentFtpClientConnection connection, FtpConfig ftpConfig)
{
Connection = connection;
_ftpConfig = ftpConfig;
}

public FluentFtpClientConnection Connection { get; }

private readonly FtpConfig _ftpConfig;

public void Dispose()
{
Connection.Dispose();
_ftpConfig.Dispose();
}
}
}
Loading
Loading