Files
codex 626640e26b
PR and Push Build/Test / build-and-test (push) Failing after 10m56s
Add past project meeting summary tools
2026-05-29 10:30:48 +02:00

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);