using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NitroxModel.Helper;
namespace NitroxServer_Subnautica.Communication;
///
/// Exposes an IPC channel for other local processes to communicate with the server.
///
public class IpcHost : IDisposable
{
private readonly CancellationTokenSource commandReadCancellation;
private readonly NamedPipeServerStream server = new($"Nitrox Server {NitroxEnvironment.CurrentProcessId}", PipeDirection.In, 1);
private IpcHost(CancellationTokenSource commandReadCancellation)
{
this.commandReadCancellation = commandReadCancellation;
}
public static IpcHost StartReadingCommands(Action onCommandReceived, CancellationToken cancellationToken = default)
{
Log.Info("Starting IPC host for command input");
ArgumentNullException.ThrowIfNull(onCommandReceived);
IpcHost host = new(CancellationTokenSource.CreateLinkedTokenSource(cancellationToken));
Thread thread = new(async () =>
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
string command = await host.ReadStringAsync(cancellationToken);
onCommandReceived(command);
}
catch (OperationCanceledException)
{
// ignored
}
}
});
thread.IsBackground = true;
thread.Start();
return host;
}
public async Task ReadStringAsync(CancellationToken cancellationToken = default)
{
if (!await WaitForConnection())
{
return "";
}
try
{
byte[] sizeBytes = new byte[4];
await server.ReadExactlyAsync(sizeBytes, cancellationToken);
byte[] stringBytes = new byte[BitConverter.ToUInt32(sizeBytes)];
await server.ReadExactlyAsync(stringBytes, cancellationToken);
return Encoding.UTF8.GetString(stringBytes);
}
catch (Exception)
{
return "";
}
}
public void Dispose()
{
commandReadCancellation?.Cancel();
server.Dispose();
}
private async Task WaitForConnection()
{
if (server.IsConnected)
{
return true;
}
try
{
await server.WaitForConnectionAsync();
return true;
}
catch (IOException)
{
try
{
server.Disconnect();
}
catch (Exception)
{
// ignored
}
}
return false;
}
}