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
113 changes: 62 additions & 51 deletions src/EntglDb.Services.FileTransfer/FileTransferExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,62 @@
using EntglDb.Network;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace EntglDb.Services.FileTransfer;

/// <summary>
/// Extension methods for registering EntglDb file transfer services.
/// </summary>
public static class FileTransferExtensions
{
/// <summary>
/// Adds the EntglDb file transfer service to the DI container.
/// </summary>
/// <remarks>
/// <para>
/// Registers:
/// <list type="bullet">
/// <item><see cref="FileQueryHandler"/> as <see cref="INetworkMessageHandler"/> (server-side, wire type 1100)</item>
/// <item><see cref="FileDownloadHandler"/> as <see cref="INetworkMessageHandler"/> (server-side streaming, wire type 1102)</item>
/// <item><see cref="IFileTransferService"/> / <see cref="FileTransferClient"/> (client-side download API)</item>
/// </list>
/// </para>
/// <para>
/// Call <c>AddEntglDbNetwork&lt;T&gt;()</c> before this method so that
/// <see cref="IPeerMessenger"/> and <see cref="IPeerConnectionPool"/> are already registered.
/// </para>
/// <para>
/// To make files available for remote download, also register an <see cref="IFileProvider"/>:
/// <code>
/// services.AddEntglDbFileTransfer();
/// services.AddSingleton&lt;IFileProvider, MyFileProvider&gt;();
/// </code>
/// Without an <see cref="IFileProvider"/> the server-side handlers are registered but will
/// return not-found for every request.
/// </para>
/// </remarks>
public static IServiceCollection AddEntglDbFileTransfer(this IServiceCollection services)
{
// Server side — file query (single response)
services.AddSingleton<INetworkMessageHandler, FileQueryHandler>();

// Server side — file download (streaming response)
services.AddSingleton<INetworkMessageHandler, FileDownloadHandler>();

// Client side
services.TryAddSingleton<IFileTransferService, FileTransferClient>();

return services;
}
}
using EntglDb.Network;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace EntglDb.Services.FileTransfer;

/// <summary>
/// Extension methods for registering EntglDb file transfer services.
/// </summary>
public static class FileTransferExtensions
{
/// <summary>
/// Adds the EntglDb file transfer service to the DI container.
/// </summary>
/// <remarks>
/// <para>
/// Registers:
/// <list type="bullet">
/// <item><see cref="FileQueryHandler"/> as <see cref="INetworkMessageHandler"/> (server-side, wire type 1100)</item>
/// <item><see cref="FileDownloadHandler"/> as <see cref="INetworkMessageHandler"/> (server-side streaming, wire type 1102)</item>
/// <item><see cref="IFileTransferService"/> / <see cref="FileTransferClient"/> (client-side download API)</item>
/// <item><see cref="IFileProvider"/> / <see cref="NullFileProvider"/> (server-side default - see below)</item>
/// </list>
/// </para>
/// <para>
/// Call <c>AddEntglDbNetwork&lt;T&gt;()</c> before this method so that
/// <see cref="IPeerMessenger"/> and <see cref="IPeerConnectionPool"/> are already registered.
/// </para>
/// <para>
/// A node that only ever downloads files (never serves any) needs nothing further - it uses the
/// registered <see cref="NullFileProvider"/> default, reporting every file as not found. To make files
/// available for remote download instead, register a real provider <em>after</em> this call:
/// <code>
/// services.AddEntglDbFileTransfer();
/// services.AddSingleton&lt;IFileProvider, MyFileProvider&gt;();
/// </code>
/// <see cref="FileQueryHandler"/>/<see cref="FileDownloadHandler"/> take a single (non-enumerable)
/// <see cref="IFileProvider"/>, so the last registration wins - the call above's own provider overrides
/// the default even though both end up registered.
Comment on lines +30 to +39
/// </para>
/// </remarks>
public static IServiceCollection AddEntglDbFileTransfer(this IServiceCollection services)
{
// Server side — file query (single response)
services.AddSingleton<INetworkMessageHandler, FileQueryHandler>();

// Server side — file download (streaming response)
services.AddSingleton<INetworkMessageHandler, FileDownloadHandler>();

// Client side
services.TryAddSingleton<IFileTransferService, FileTransferClient>();

// Server-side default so a download-only consumer doesn't have to implement IFileProvider itself
// just to satisfy FileQueryHandler/FileDownloadHandler's constructor dependency - see remarks
// above. Must be TryAddSingleton (not AddSingleton) so a real provider the caller registers
// afterward via plain AddSingleton becomes the winning "last registration" for the single-instance
// resolution FileQueryHandler/FileDownloadHandler actually use.
services.TryAddSingleton<IFileProvider, NullFileProvider>();

return services;
}
}
22 changes: 22 additions & 0 deletions src/EntglDb.Services.FileTransfer/NullFileProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace EntglDb.Services.FileTransfer;

/// <summary>
/// Default <see cref="IFileProvider"/> registered by <see cref="FileTransferExtensions.AddEntglDbFileTransfer"/> -
/// reports every file as not found. Lets a node that only ever downloads files (never serves any) use
/// <c>AddEntglDbFileTransfer()</c> without also having to implement and register an <see cref="IFileProvider"/>
/// of its own just to satisfy <see cref="FileQueryHandler"/>/<see cref="FileDownloadHandler"/>'s constructor
/// dependency. A node that does want to serve files registers its own provider after this call; DI resolves
/// a single (non-enumerable) service to the last registration, so the real provider wins.
/// </summary>
public sealed class NullFileProvider : IFileProvider
{
public Task<FileTransferInfo?> GetInfoAsync(string fileId, CancellationToken ct = default) =>
Task.FromResult<FileTransferInfo?>(null);

public Task<Stream?> OpenReadAsync(string fileId, CancellationToken ct = default) =>
Task.FromResult<Stream?>(null);
Comment on lines +17 to +21
}