Public Access
114 lines
3.5 KiB
C#
114 lines
3.5 KiB
C#
using System.Diagnostics;
|
|
using System.Text;
|
|
|
|
namespace MeetingAssistant.Transcription;
|
|
|
|
public interface ICommandRunner
|
|
{
|
|
Task<CommandResult> RunAsync(
|
|
string fileName,
|
|
IReadOnlyList<string> arguments,
|
|
CancellationToken cancellationToken,
|
|
IReadOnlyDictionary<string, string>? environment = null);
|
|
}
|
|
|
|
public sealed class ProcessCommandRunner : ICommandRunner
|
|
{
|
|
private readonly ILogger<ProcessCommandRunner> logger;
|
|
|
|
public ProcessCommandRunner(ILogger<ProcessCommandRunner> logger)
|
|
{
|
|
this.logger = logger;
|
|
}
|
|
|
|
public async Task<CommandResult> RunAsync(
|
|
string fileName,
|
|
IReadOnlyList<string> arguments,
|
|
CancellationToken cancellationToken,
|
|
IReadOnlyDictionary<string, string>? environment = null)
|
|
{
|
|
var startInfo = CreateStartInfo(fileName, arguments, environment);
|
|
|
|
logger.LogInformation("Running command {Command} {Arguments}", fileName, string.Join(' ', arguments));
|
|
using var process = Process.Start(startInfo)
|
|
?? throw new InvalidOperationException($"Failed to start command '{fileName}'.");
|
|
var outputTask = ReadLinesAsync(process.StandardOutput, line => logger.LogInformation("{Command}: {Line}", fileName, line), cancellationToken);
|
|
var errorTask = ReadLinesAsync(process.StandardError, line => logger.LogWarning("{Command}: {Line}", fileName, line), cancellationToken);
|
|
|
|
try
|
|
{
|
|
await process.WaitForExitAsync(cancellationToken);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
if (!process.HasExited)
|
|
{
|
|
logger.LogWarning("Cancelling command {Command}; killing process tree.", fileName);
|
|
process.Kill(entireProcessTree: true);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
|
|
var result = new CommandResult(process.ExitCode, await outputTask, await errorTask);
|
|
if (result.ExitCode != 0)
|
|
{
|
|
logger.LogWarning(
|
|
"Command {Command} exited with {ExitCode}: {Error}",
|
|
fileName,
|
|
result.ExitCode,
|
|
result.StandardError);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
internal static ProcessStartInfo CreateStartInfo(
|
|
string fileName,
|
|
IReadOnlyList<string> arguments,
|
|
IReadOnlyDictionary<string, string>? environment = null)
|
|
{
|
|
var startInfo = new ProcessStartInfo
|
|
{
|
|
FileName = fileName,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
WindowStyle = ProcessWindowStyle.Hidden
|
|
};
|
|
|
|
foreach (var argument in arguments)
|
|
{
|
|
startInfo.ArgumentList.Add(argument);
|
|
}
|
|
|
|
if (environment is not null)
|
|
{
|
|
foreach (var (key, value) in environment)
|
|
{
|
|
startInfo.Environment[key] = value;
|
|
}
|
|
}
|
|
|
|
return startInfo;
|
|
}
|
|
|
|
private static async Task<string> ReadLinesAsync(
|
|
StreamReader reader,
|
|
Action<string> log,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var output = new StringBuilder();
|
|
while (await reader.ReadLineAsync(cancellationToken) is { } line)
|
|
{
|
|
output.AppendLine(line);
|
|
log(line);
|
|
}
|
|
|
|
return output.ToString();
|
|
}
|
|
}
|
|
|
|
public sealed record CommandResult(int ExitCode, string StandardOutput, string StandardError);
|