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
4 changes: 3 additions & 1 deletion FmiSrl.FtpServer.Server/Abstractions/FtpCommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ namespace FmiSrl.FtpServer.Server.Abstractions;
/// <param name="Authenticator">The authentication provider to use.</param>
/// <param name="Configuration">The server configuration options.</param>
/// <param name="Logger">The logger to use for recording command execution details.</param>
/// <param name="EventHandlers">The collection of registered FTP server event handlers.</param>
public record FtpCommandContext(
IFtpSession Session,
string Verb,
string Arguments,
IFileSystemProvider FileSystem,
IAuthenticationProvider Authenticator,
FtpServerConfigurationOptions Configuration,
ILogger Logger
ILogger Logger,
IEnumerable<IFtpServerEventHandler>? EventHandlers = null
)
{
/// <summary>
Expand Down
15 changes: 15 additions & 0 deletions FmiSrl.FtpServer.Server/Abstractions/IFtpServerEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace FmiSrl.FtpServer.Server.Abstractions;

/// <summary>
/// Defines event handlers for FTP server events.
/// </summary>
public interface IFtpServerEventHandler
{
/// <summary>
/// Invoked when a file is successfully uploaded to the server.
/// </summary>
/// <param name="session">The FTP session that uploaded the file.</param>
/// <param name="targetFile">The full path of the uploaded file.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task OnFileUploadedAsync(IFtpSession session, string targetFile);
}
15 changes: 15 additions & 0 deletions FmiSrl.FtpServer.Server/Commands/StorCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,20 @@ private static async Task ReceiveFileAsync(FtpCommandContext context, string tar
}

await context.Session.SendResponseAsync(226, "Transfer complete.");

if (context.EventHandlers != null)
{
foreach (var handler in context.EventHandlers)
{
try
{
await handler.OnFileUploadedAsync(context.Session, targetFile);
}
catch (Exception ex)
{
context.Logger.LogError(ex, "Error executing event handler {HandlerType} for file {TargetFile}", handler.GetType().Name, targetFile);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ public IFtpServerBuilder AddMiddleware<TMiddleware>() where TMiddleware : class,
Services.AddTransient<IFtpCommandMiddleware, TMiddleware>();
return this;
}

public IFtpServerBuilder AddEventHandler<TEventHandler>() where TEventHandler : class, IFtpServerEventHandler
{
Services.AddTransient<IFtpServerEventHandler, TEventHandler>();
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,11 @@ public interface IFtpServerBuilder
/// <typeparam name="TMiddleware">The type of the middleware to add.</typeparam>
/// <returns>The <see cref="IFtpServerBuilder"/> instance.</returns>
IFtpServerBuilder AddMiddleware<TMiddleware>() where TMiddleware : class, IFtpCommandMiddleware;

/// <summary>
/// Adds an event handler to the FTP server.
/// </summary>
/// <typeparam name="TEventHandler">The type of the event handler to add.</typeparam>
/// <returns>The <see cref="IFtpServerBuilder"/> instance.</returns>
IFtpServerBuilder AddEventHandler<TEventHandler>() where TEventHandler : class, IFtpServerEventHandler;
}
6 changes: 5 additions & 1 deletion FmiSrl.FtpServer.Server/FtpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class FtpServer(
IFileSystemProvider fileSystemProvider,
IAuthenticationProvider authenticationProvider,
IEnumerable<IFtpCommandMiddleware> middlewares,
IEnumerable<IFtpServerEventHandler> eventHandlers,
IOptions<FtpServerConfigurationOptions> configurationOptions,
ILogger<FtpServer>? logger = null
)
Expand All @@ -35,6 +36,8 @@ public class FtpServer(
private readonly IAuthenticationProvider _authenticationProvider =
authenticationProvider ?? throw new ArgumentNullException(nameof(authenticationProvider));

private readonly IEnumerable<IFtpServerEventHandler> _eventHandlers = eventHandlers;

private readonly ILogger<FtpServer> _logger = logger ?? NullLogger<FtpServer>.Instance;
private readonly FtpCommandHandler _commandHandler = new(middlewares);
private readonly Dictionary<int, IFtpSession> _sessions = [];
Expand Down Expand Up @@ -177,7 +180,8 @@ private async Task ExecuteCommandAsync(NetClient client, FtpSession session, str
_fileSystemProvider,
_authenticationProvider,
_configurationOptions,
_logger);
_logger,
_eventHandlers);

await _commandHandler.HandleCommandAsync(context);

Expand Down
8 changes: 4 additions & 4 deletions FmiSrl.FtpServer.Tests.Integration/FtpServerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public FtpServerTests()
ServerName = "TestServer"
});

_server = new FmiSrl.FtpServer.Server.FtpServer(fileSystem, authenticator, Enumerable.Empty<IFtpCommandMiddleware>(), serverOptions, NullLogger<FmiSrl.FtpServer.Server.FtpServer>.Instance);
_server = new FmiSrl.FtpServer.Server.FtpServer(fileSystem, authenticator, Enumerable.Empty<IFtpCommandMiddleware>(), Enumerable.Empty<IFtpServerEventHandler>(), serverOptions, NullLogger<FmiSrl.FtpServer.Server.FtpServer>.Instance);
}

public async ValueTask DisposeAsync()
Expand All @@ -67,14 +67,14 @@ public async Task When_server_starts_should_be_able_to_login_and_list_files()
{
// Arrange
await _server.StartAsync();

// Create a test file for the user
var userRoot = Path.Combine(_rootPath, "test");
Directory.CreateDirectory(userRoot);
File.WriteAllText(Path.Combine(userRoot, "test.txt"), "Hello World");

using var client = new AsyncFtpClient("127.0.0.1", "test", "password", _port);

// Act
await client.Connect();
var currentDir = await client.GetWorkingDirectory();
Expand Down Expand Up @@ -116,7 +116,7 @@ public async Task When_uploading_file_should_save_to_disk()

// Assert
Assert.Equal(FtpStatus.Success, status);

var filePath = Path.Combine(_rootPath, "test", "uploaded.txt");
Assert.True(File.Exists(filePath));
Assert.Equal(testContent, await File.ReadAllTextAsync(filePath));
Expand Down
2 changes: 1 addition & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
next-version: 1.2.0
next-version: 1.3.0
branches:
main:
regex: ^master$|^main$
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ var authOptions = Options.Create(new SimpleAuthenticationProviderOptions
var ftpServer = new FtpServer(
new PhysicalFileSystemProvider(fsOptions),
new SimpleAuthenticationProvider(authOptions),
Enumerable.Empty<IFtpCommandMiddleware>(),
Enumerable.Empty<IFtpServerEventHandler>(),
serverOptions,
NullLogger<FtpServer>.Instance
);
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.101",
"version": "10.0.203",
"rollForward": "latestMajor",
"allowPrerelease": false
}
Expand Down
Loading