mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2025-05-24 13:34:05 +02:00
Extract reusable one-shot process runner from backup compressor implementation
This commit is contained in:
parent
bb7de48d24
commit
d93c93cbf7
@ -1,15 +1,17 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Phantom.Common.Logging;
|
using Phantom.Common.Logging;
|
||||||
|
using Phantom.Utils.Runtime;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Agent.Services.Backups;
|
namespace Phantom.Agent.Services.Backups;
|
||||||
|
|
||||||
static class BackupCompressor {
|
static class BackupCompressor {
|
||||||
private static ILogger Logger { get; } = PhantomLogger.Create(nameof(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 string Quality = "-10";
|
||||||
private const int Memory = 26;
|
private const string Memory = "--long=26";
|
||||||
private const int Threads = 3;
|
private const string Threads = "-T3";
|
||||||
|
|
||||||
public static async Task<string?> Compress(string sourceFilePath, CancellationToken cancellationToken) {
|
public static async Task<string?> Compress(string sourceFilePath, CancellationToken cancellationToken) {
|
||||||
if (sourceFilePath.Contains('"')) {
|
if (sourceFilePath.Contains('"')) {
|
||||||
@ -38,66 +40,31 @@ static class BackupCompressor {
|
|||||||
Logger.Error("Invalid destination path: {Path}", destinationFilePath);
|
Logger.Error("Invalid destination path: {Path}", destinationFilePath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo {
|
var startInfo = new ProcessStartInfo {
|
||||||
FileName = "zstd",
|
FileName = "zstd",
|
||||||
WorkingDirectory = workingDirectory,
|
WorkingDirectory = workingDirectory,
|
||||||
Arguments = $"-{Quality} --long={Memory} -T{Threads} -c --rm --no-progress -c -o \"{destinationFilePath}\" -- \"{sourceFilePath}\"",
|
ArgumentList = {
|
||||||
RedirectStandardOutput = true,
|
Quality,
|
||||||
RedirectStandardError = true
|
Memory,
|
||||||
|
Threads,
|
||||||
|
"-c",
|
||||||
|
"--rm",
|
||||||
|
"--no-progress",
|
||||||
|
"-c",
|
||||||
|
"-o", destinationFilePath,
|
||||||
|
"--", sourceFilePath
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using var process = new Process { StartInfo = startInfo };
|
static void OnZstdOutput(object? sender, DataReceivedEventArgs e) {
|
||||||
process.OutputDataReceived += OnZstdProcessOutput;
|
if (!string.IsNullOrWhiteSpace(e.Data)) {
|
||||||
process.ErrorDataReceived += OnZstdProcessOutput;
|
ZstdLogger.Verbose("[Output] {Line}", e.Data);
|
||||||
|
}
|
||||||
try {
|
|
||||||
process.Start();
|
|
||||||
process.BeginOutputReadLine();
|
|
||||||
process.BeginErrorReadLine();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.Error(e, "Caught exception launching zstd process.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
var process = new OneShotProcess(ZstdLogger, startInfo);
|
||||||
await process.WaitForExitAsync(cancellationToken);
|
process.Output += OnZstdOutput;
|
||||||
} catch (OperationCanceledException) {
|
return await process.Run(cancellationToken);
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
Utils/Phantom.Utils.Runtime/OneShotProcess.cs
Normal file
69
Utils/Phantom.Utils.Runtime/OneShotProcess.cs
Normal 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user