Initial commit: Nix WSL Wrapper project with DDD, Clean Code, and tests
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace NixWslWrapper.Infrastructure.Executors
|
||||
{
|
||||
public class ProcessBuilder
|
||||
{
|
||||
private readonly ProcessStartInfo _startInfo = new();
|
||||
|
||||
public ProcessBuilder SetFileName(string fileName)
|
||||
{
|
||||
_startInfo.FileName = fileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProcessBuilder SetArguments(string arguments)
|
||||
{
|
||||
_startInfo.Arguments = arguments;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProcessBuilder RedirectInput(bool redirect = true)
|
||||
{
|
||||
_startInfo.RedirectStandardInput = redirect;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProcessBuilder RedirectOutput(bool redirect = true)
|
||||
{
|
||||
_startInfo.RedirectStandardOutput = redirect;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProcessBuilder RedirectError(bool redirect = true)
|
||||
{
|
||||
_startInfo.RedirectStandardError = redirect;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProcessBuilder UseShellExecute(bool useShell = false)
|
||||
{
|
||||
_startInfo.UseShellExecute = useShell;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProcessBuilder CreateNoWindow(bool noWindow = true)
|
||||
{
|
||||
_startInfo.CreateNoWindow = noWindow;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProcessStartInfo Build()
|
||||
{
|
||||
return _startInfo;
|
||||
}
|
||||
|
||||
public Process CreateProcess()
|
||||
{
|
||||
return new Process { StartInfo = _startInfo };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using NixWslWrapper.Core.Domain;
|
||||
using NixWslWrapper.Core.Interfaces;
|
||||
|
||||
namespace NixWslWrapper.Infrastructure.Executors
|
||||
{
|
||||
public class WslCommandExecutor : ICommandExecutor
|
||||
{
|
||||
private readonly IStreamCopier _streamCopier;
|
||||
private readonly Stream _hostIn;
|
||||
private readonly Stream _hostOut;
|
||||
private readonly Stream _hostErr;
|
||||
|
||||
public WslCommandExecutor(
|
||||
IStreamCopier streamCopier,
|
||||
Stream? hostIn = null,
|
||||
Stream? hostOut = null,
|
||||
Stream? hostErr = null)
|
||||
{
|
||||
_streamCopier = streamCopier ?? throw new ArgumentNullException(nameof(streamCopier));
|
||||
_hostIn = hostIn ?? Console.OpenStandardInput();
|
||||
_hostOut = hostOut ?? Console.OpenStandardOutput();
|
||||
_hostErr = hostErr ?? Console.OpenStandardError();
|
||||
}
|
||||
|
||||
public ExecutionResult Execute(NixArguments arguments, WslTarget target)
|
||||
{
|
||||
if (arguments == null) throw new ArgumentNullException(nameof(arguments));
|
||||
if (target == null) throw new ArgumentNullException(nameof(target));
|
||||
|
||||
string wslCmd = $". {target.NixProfilePath} && nix {arguments.ToEscapedString()}";
|
||||
string wslArgs = $"-d {target.Distribution} -u {target.User} -- sh -c \"{wslCmd.Replace("\"", "\\\"")}\"";
|
||||
|
||||
var processBuilder = new ProcessBuilder()
|
||||
.SetFileName("wsl.exe")
|
||||
.SetArguments(wslArgs)
|
||||
.RedirectInput(true)
|
||||
.RedirectOutput(true)
|
||||
.RedirectError(true)
|
||||
.UseShellExecute(false)
|
||||
.CreateNoWindow(true);
|
||||
|
||||
using (Process process = processBuilder.CreateProcess())
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
|
||||
// Standard streams copy tasks
|
||||
Thread tOut = new(() => _streamCopier.Copy(process.StandardOutput.BaseStream, _hostOut))
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
Thread tErr = new(() => _streamCopier.Copy(process.StandardError.BaseStream, _hostErr))
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
Thread tIn = new(() => _streamCopier.Copy(_hostIn, process.StandardInput.BaseStream))
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
|
||||
tOut.Start();
|
||||
tErr.Start();
|
||||
tIn.Start();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
// Wait up to 1 second for stream copier threads to finalize flushing buffer content
|
||||
tOut.Join(1000);
|
||||
tErr.Join(1000);
|
||||
|
||||
return new ExecutionResult(process.ExitCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ExecutionResult.Failure(1, $"Error executing Nix wrapper: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NixWslWrapper.Core.Interfaces;
|
||||
|
||||
namespace NixWslWrapper.Infrastructure.IO
|
||||
{
|
||||
public class ThreadedStreamCopier : IStreamCopier
|
||||
{
|
||||
private readonly int _bufferSize;
|
||||
|
||||
public ThreadedStreamCopier(int bufferSize = 8192)
|
||||
{
|
||||
if (bufferSize <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be positive.");
|
||||
_bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public void Copy(Stream input, Stream output)
|
||||
{
|
||||
byte[] buffer = new byte[_bufferSize];
|
||||
int read;
|
||||
try
|
||||
{
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
output.Write(buffer, 0, read);
|
||||
output.Flush();
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Clean exit when stream is closed or process terminates
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Clean exit when streams are disposed
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// General catch to ensure thread doesn't crash the host process
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NixWslWrapper.Core\NixWslWrapper.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user