Files
TuneRipper/TuneRipper.Api/Health/ExternalToolsHealthCheck.cs
T

69 lines
2.3 KiB
C#

using System.Diagnostics;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using TuneRipper.Api.Configuration;
namespace TuneRipper.Api.Health;
public sealed class ExternalToolsHealthCheck(IOptions<YtDlpOptions> options) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
var ytDlp = await ProbeAsync(options.Value.ExecutablePath, "--version", cancellationToken);
if (!ytDlp.Success)
{
return HealthCheckResult.Unhealthy($"yt-dlp is unavailable: {ytDlp.Message}");
}
var ffmpeg = await ProbeAsync(options.Value.FfmpegExecutablePath, "-version", cancellationToken);
if (!ffmpeg.Success)
{
return HealthCheckResult.Unhealthy($"ffmpeg is unavailable: {ffmpeg.Message}");
}
return HealthCheckResult.Healthy($"yt-dlp {ytDlp.Message}; ffmpeg available");
}
private static async Task<(bool Success, string Message)> ProbeAsync(
string executable,
string argument,
CancellationToken cancellationToken)
{
try
{
var startInfo = new ProcessStartInfo
{
FileName = executable,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
startInfo.ArgumentList.Add(argument);
using var process = Process.Start(startInfo);
if (process is null)
{
return (false, "process could not be started");
}
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeout.CancelAfter(TimeSpan.FromSeconds(10));
var outputTask = process.StandardOutput.ReadLineAsync(timeout.Token).AsTask();
await process.WaitForExitAsync(timeout.Token);
var output = (await outputTask)?.Trim();
return process.ExitCode == 0
? (true, string.IsNullOrWhiteSpace(output) ? "available" : output)
: (false, $"exit code {process.ExitCode}");
}
catch (Exception ex)
{
return (false, ex.Message);
}
}
}