A src/Core/NosSmooth.Comms.NamedPipes/Extensions/ServiceCollectionExtensions.cs => src/Core/NosSmooth.Comms.NamedPipes/Extensions/ServiceCollectionExtensions.cs +41 -0
@@ 0,0 1,41 @@
+//
+// ServiceCollectionExtensions.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.IO.Pipes;
+using Microsoft.Extensions.DependencyInjection;
+using NosSmooth.Comms.Data;
+
+namespace NosSmooth.Comms.NamedPipes.Extensions;
+
+/// <summary>
+/// Extension methods for <see cref="IServiceCollection"/>.
+/// </summary>
+public static class ServiceCollectionExtensions
+{
+ /// <summary>
+ /// Adds a named pipe client as a <see cref="IClient"/>.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <param name="pipeNameResolver">A function for resolving the name of the pipe using service provider.</param>
+ /// <returns>The same service collection.</returns>
+ public static IServiceCollection AddNamedPipeClient
+ (this IServiceCollection serviceCollection, Func<IServiceProvider, string> pipeNameResolver)
+ => serviceCollection
+ .AddSingleton<NamedPipeClient>(p => new NamedPipeClient(pipeNameResolver(p)))
+ .AddSingleton<IClient>(p => p.GetRequiredService<NamedPipeClient>());
+
+ /// <summary>
+ /// Adds a named pipe server as a <see cref="IServer"/>.
+ /// </summary>
+ /// <param name="serviceCollection">The service collection.</param>
+ /// <param name="pipeNameResolver">A function for resolving the name of the pipe using service provider.</param>
+ /// <returns>The same service collection.</returns>
+ public static IServiceCollection AddNamedPipeServer
+ (this IServiceCollection serviceCollection, Func<IServiceProvider, string> pipeNameResolver)
+ => serviceCollection
+ .AddSingleton<NamedPipeServer>(p => new NamedPipeServer(pipeNameResolver(p)))
+ .AddSingleton<IServer>(p => p.GetRequiredService<NamedPipeServer>());
+}<
\ No newline at end of file
A src/Core/NosSmooth.Comms.NamedPipes/NamedPipeClient.cs => src/Core/NosSmooth.Comms.NamedPipes/NamedPipeClient.cs +64 -0
@@ 0,0 1,64 @@
+//
+// NamedPipeClient.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Data;
+using System.IO.Pipes;
+using NosSmooth.Comms.Data;
+using Remora.Results;
+
+namespace NosSmooth.Comms.NamedPipes;
+
+/// <summary>
+/// A client using named pipes.
+/// </summary>
+public class NamedPipeClient : IClient
+{
+ private readonly NamedPipeClientStream _stream;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NamedPipeClient"/> class.
+ /// </summary>
+ /// <param name="pipeName">The name of the pipe.</param>
+ public NamedPipeClient(string pipeName)
+ {
+ _stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
+ }
+
+ /// <inheritdoc />
+ public ConnectionState State => _stream.IsConnected ? ConnectionState.Open : ConnectionState.Closed;
+
+ /// <inheritdoc />
+ public Stream ReadStream => _stream;
+
+ /// <inheritdoc />
+ public Stream WriteStream => _stream;
+
+ /// <inheritdoc />
+ public void Disconnect()
+ {
+ _stream.Close();
+ _stream.Dispose();
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> ConnectAsync(CancellationToken ct)
+ {
+ try
+ {
+ await _stream.ConnectAsync(ct);
+ return Result.FromSuccess();
+ }
+ catch (OperationCanceledException)
+ {
+ // ignored
+ return Result.FromSuccess();
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ }
+}<
\ No newline at end of file
A src/Core/NosSmooth.Comms.NamedPipes/NamedPipeServer.cs => src/Core/NosSmooth.Comms.NamedPipes/NamedPipeServer.cs +125 -0
@@ 0,0 1,125 @@
+//
+// NamedPipeServer.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Data;
+using System.IO.Pipes;
+using NosSmooth.Comms.Data;
+using Remora.Results;
+
+namespace NosSmooth.Comms.NamedPipes;
+
+/// <summary>
+/// A server using named pipes.
+/// </summary>
+public class NamedPipeServer : IServer
+{
+ private readonly List<IConnection> _connections;
+ private readonly ReaderWriterLockSlim _readerWriterLock;
+ private readonly string _pipeName;
+ private bool _listening;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NamedPipeServer"/> class.
+ /// </summary>
+ /// <param name="pipeName">The name of the pipe.</param>
+ public NamedPipeServer(string pipeName)
+ {
+ _readerWriterLock = new ReaderWriterLockSlim();
+ _pipeName = pipeName;
+ _connections = new List<IConnection>();
+ }
+
+ /// <inheritdoc />
+ public IReadOnlyList<IConnection> Clients
+ {
+ get
+ {
+ _readerWriterLock.EnterReadLock();
+ var connections = new List<IConnection>(_connections);
+ _readerWriterLock.ExitReadLock();
+ return connections.AsReadOnly();
+ }
+ }
+
+ /// <inheritdoc />
+ public async Task<Result<IConnection>> WaitForConnectionAsync(CancellationToken ct = default)
+ {
+ if (!_listening)
+ {
+ throw new InvalidOperationException("The server is not listening.");
+ }
+
+ var serverStream = new NamedPipeServerStream
+ (
+ _pipeName,
+ PipeDirection.InOut,
+ NamedPipeServerStream.MaxAllowedServerInstances,
+ PipeTransmissionMode.Byte,
+ PipeOptions.Asynchronous
+ );
+
+ await serverStream.WaitForConnectionAsync(ct);
+
+ var connection = new NamedPipeConnection(this, serverStream);
+ _readerWriterLock.EnterWriteLock();
+ _connections.Add(connection);
+ _readerWriterLock.ExitWriteLock();
+
+ return connection;
+ }
+
+ /// <inheritdoc />
+ public Task<Result> ListenAsync(CancellationToken stopToken = default)
+ {
+ _listening = true;
+ stopToken.Register(Close);
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ /// <inheritdoc />
+ public void Close()
+ {
+ _readerWriterLock.EnterReadLock();
+ var connections = new List<IConnection>(_connections);
+ _readerWriterLock.ExitReadLock();
+
+ foreach (var connection in connections)
+ {
+ connection.Disconnect();
+ }
+
+ _listening = false;
+ }
+
+ private class NamedPipeConnection : IConnection
+ {
+ private readonly NamedPipeServer _server;
+ private readonly NamedPipeServerStream _serverStream;
+
+ public NamedPipeConnection(NamedPipeServer server, NamedPipeServerStream serverStream)
+ {
+ _server = server;
+ _serverStream = serverStream;
+ }
+
+ public ConnectionState State { get; private set; } = ConnectionState.Open;
+
+ public Stream ReadStream => _serverStream;
+
+ public Stream WriteStream => _serverStream;
+
+ public void Disconnect()
+ {
+ _serverStream.Disconnect();
+ _serverStream.Close();
+ State = ConnectionState.Closed;
+
+ _server._readerWriterLock.EnterWriteLock();
+ _server._connections.Remove(this);
+ _server._readerWriterLock.ExitWriteLock();
+ }
+ }
+}<
\ No newline at end of file
A src/Core/NosSmooth.Comms.NamedPipes/NosSmooth.Comms.NamedPipes.csproj => src/Core/NosSmooth.Comms.NamedPipes/NosSmooth.Comms.NamedPipes.csproj +13 -0
@@ 0,0 1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net7.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\NosSmooth.Comms.Abstractions\NosSmooth.Comms.Abstractions.csproj" />
+ </ItemGroup>
+
+</Project>