1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2025-05-04 09:34:05 +02:00

Extract reusable one-shot process runner from backup compressor implementation

This commit is contained in:
chylex 2023-02-26 18:04:55 +01:00
parent bb7de48d24
commit d93c93cbf7
Signed by: chylex
GPG Key ID: 4DE42C8F19A80548
2 changed files with 93 additions and 57 deletions
Agent/Phantom.Agent.Services/Backups
Utils/Phantom.Utils.Runtime

View File

@ -1,15 +1,17 @@
using System.Diagnostics;
using Phantom.Common.Logging;
using Phantom.Utils.Runtime;
using Serilog;
namespace Phantom.Agent.Services.Backups;
static class BackupCompressor {
private static ILogger Logger { get; } = PhantomLogger.Create(nameof(BackupCompressor));
private static ILogger ZstdLogger { get; } = PhantomLogger.Create(nameof(BackupCompressor), "Zstd");
private const int Quality = 10;
private const int Memory = 26;
private const int Threads = 3;
private const string Quality = "-10";
private const string Memory = "--long=26";
private const string Threads = "-T3";
public static async Task<string?> Compress(string sourceFilePath, CancellationToken cancellationToken) {
if (sourceFilePath.Contains('"')) {
@ -38,66 +40,31 @@ static class BackupCompressor {
Logger.Error("Invalid destination path: {Path}", destinationFilePath);
return false;
}
var startInfo = new ProcessStartInfo {
FileName = "zstd",
WorkingDirectory = workingDirectory,
Arguments = $"-{Quality} --long={Memory} -T{Threads} -c --rm --no-progress -c -o \"{destinationFilePath}\" -- \"{sourceFilePath}\"",
RedirectStandardOutput = true,
RedirectStandardError = true
ArgumentList = {
Quality,
Memory,
Threads,
"-c",
"--rm",
"--no-progress",
"-c",
"-o", destinationFilePath,
"--", sourceFilePath
}
};
using var process = new Process { StartInfo = startInfo };
process.OutputDataReceived += OnZstdProcessOutput;
process.ErrorDataReceived += OnZstdProcessOutput;
try {
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
} catch (Exception e) {
Logger.Error(e, "Caught exception launching zstd process.");
static void OnZstdOutput(object? sender, DataReceivedEventArgs e) {
if (!string.IsNullOrWhiteSpace(e.Data)) {
ZstdLogger.Verbose("[Output] {Line}", e.Data);
}
}
try {
await process.WaitForExitAsync(cancellationToken);
} catch (OperationCanceledException) {
await TryKillProcess(process);
return false;
} catch (Exception e) {
Logger.Error(e, "Caught exception waiting for zstd process to exit.");
return false;
}
if (!process.HasExited) {
await TryKillProcess(process);
return false;
}
if (process.ExitCode != 0) {
Logger.Error("Zstd process exited with code {ExitCode}.", process.ExitCode);
return false;
}
return true;
}
private static void OnZstdProcessOutput(object sender, DataReceivedEventArgs e) {
if (!string.IsNullOrWhiteSpace(e.Data)) {
Logger.Verbose("[Zstd] {Line}", e.Data);
}
}
private static async Task TryKillProcess(Process process) {
CancellationTokenSource timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1));
try {
process.Kill();
await process.WaitForExitAsync(timeout.Token);
} catch (OperationCanceledException) {
Logger.Error("Timed out waiting for killed zstd process to exit.");
} catch (Exception e) {
Logger.Error(e, "Caught exception killing zstd process.");
}
var process = new OneShotProcess(ZstdLogger, startInfo);
process.Output += OnZstdOutput;
return await process.Run(cancellationToken);
}
}

View File

@ -0,0 +1,69 @@
using System.Diagnostics;
using Serilog;
namespace Phantom.Utils.Runtime;
public sealed class OneShotProcess {
private readonly ILogger logger;
private readonly ProcessStartInfo startInfo;
public event DataReceivedEventHandler? Output;
public OneShotProcess(ILogger logger, ProcessStartInfo startInfo) {
this.logger = logger;
this.startInfo = startInfo;
this.startInfo.RedirectStandardOutput = true;
this.startInfo.RedirectStandardError = true;
}
public async Task<bool> Run(CancellationToken cancellationToken) {
using var process = new Process { StartInfo = startInfo };
process.OutputDataReceived += Output;
process.ErrorDataReceived += Output;
try {
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
} catch (Exception e) {
logger.Error(e, "Caught exception launching process.");
return false;
}
try {
await process.WaitForExitAsync(cancellationToken);
} catch (OperationCanceledException) {
await TryKillProcess(process);
return false;
} catch (Exception e) {
logger.Error(e, "Caught exception waiting for process to exit.");
return false;
}
if (!process.HasExited) {
await TryKillProcess(process);
return false;
}
if (process.ExitCode != 0) {
logger.Error("Process exited with code {ExitCode}.", process.ExitCode);
return false;
}
logger.Verbose("Process finished successfully.");
return true;
}
private async Task TryKillProcess(Process process) {
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2));
try {
process.Kill();
await process.WaitForExitAsync(timeout.Token);
} catch (OperationCanceledException) {
logger.Error("Timed out waiting for killed process to exit.");
} catch (Exception e) {
logger.Error(e, "Caught exception killing process.");
}
}
}