UI for key update

This commit is contained in:
EugeneTes
2026-02-09 16:46:48 +01:00
parent e8d20c77c7
commit b63bd4e84d
6 changed files with 560 additions and 98 deletions

View File

@@ -0,0 +1,55 @@
using Inspectron.Epson.PrintServer.ConfigurationSources;
namespace EpsonPrintService;
public record ConfigLoadResult(bool Success, EpsonPrintServiceConfiguration? Config, string? Error);
public static class ConfigurationLoader
{
public static ConfigLoadResult TryLoadFromBase64(string base64Content)
{
try
{
var decoded = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(base64Content.Trim()));
var parts = decoded.Split(';');
if (parts.Length != 3)
return new ConfigLoadResult(false, null, $"Invalid configuration format. Expected 'apiUrl;restaurantId;apiKey', got {parts.Length} parts.");
var config = new EpsonPrintServiceConfiguration
{
ApiUrl = parts[0],
RestaurantId = parts[1],
ApiKey = parts[2]
};
if (string.IsNullOrWhiteSpace(config.ApiUrl) || string.IsNullOrWhiteSpace(config.RestaurantId) || string.IsNullOrWhiteSpace(config.ApiKey))
return new ConfigLoadResult(false, null, "Configuration contains empty values.");
return new ConfigLoadResult(true, config, null);
}
catch (FormatException)
{
return new ConfigLoadResult(false, null, "Invalid base64 content.");
}
}
public static ConfigLoadResult TryLoadFromFile()
{
try
{
var configPath = ConfigurationPaths.GetConfigPath();
if (!File.Exists(configPath))
return new ConfigLoadResult(false, null, $"Configuration file not found: {configPath}");
var base64Line = File.ReadAllText(configPath).Trim();
if (string.IsNullOrWhiteSpace(base64Line))
return new ConfigLoadResult(false, null, "Configuration file is empty.");
return TryLoadFromBase64(base64Line);
}
catch (Exception ex)
{
return new ConfigLoadResult(false, null, $"Failed to read configuration: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,127 @@
using Inspectron.Epson.PrintServer.ConfigurationSources;
namespace EpsonPrintService;
public static class HtmlPageBuilder
{
public static string BuildConfigPage(
EpsonPrintServiceConfiguration? currentConfig,
bool printServerRunning,
UsbKeyResult? usbResult,
string? errorMessage = null)
{
var statusColor = printServerRunning ? "#2e7d32" : "#e65100";
var statusText = printServerRunning ? "Running" : "Not Configured";
return $@"<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""UTF-8"">
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
<title>Epson Print Server</title>
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; background: #f5f5f5; color: #333; }}
h1 {{ font-size: 1.4em; }}
.card {{ background: #fff; border-radius: 8px; padding: 20px; margin: 16px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.12); }}
.status {{ display: inline-block; padding: 4px 12px; border-radius: 12px; color: #fff; font-weight: bold; font-size: 0.9em; background: {statusColor}; }}
.info {{ color: #666; font-size: 0.9em; }}
.info strong {{ color: #333; }}
.error {{ background: #ffebee; border-left: 4px solid #c62828; padding: 12px 16px; border-radius: 4px; margin: 12px 0; color: #c62828; }}
.usb {{ background: #e3f2fd; border-left: 4px solid #1565c0; padding: 12px 16px; border-radius: 4px; margin: 12px 0; }}
.usb strong {{ color: #1565c0; }}
.btn {{ display: inline-block; padding: 10px 24px; border: none; border-radius: 6px; font-size: 1em; cursor: pointer; text-decoration: none; color: #fff; }}
.btn-primary {{ background: #1565c0; }}
.btn-primary:hover {{ background: #0d47a1; }}
.btn-secondary {{ background: #757575; }}
.btn-secondary:hover {{ background: #616161; }}
.actions {{ margin-top: 16px; display: flex; gap: 10px; align-items: center; }}
</style>
</head>
<body>
<h1>Epson Print Server</h1>
<div class=""card"">
<p>Status: <span class=""status"">{statusText}</span></p>
{(currentConfig != null ? $@"
<div class=""info"">
<p><strong>Restaurant ID:</strong> {Escape(currentConfig.RestaurantId)}</p>
<p><strong>API URL:</strong> {Escape(currentConfig.ApiUrl ?? "(not set)")}</p>
</div>" : "")}
</div>
{(errorMessage != null ? $@"<div class=""error"">{Escape(errorMessage)}</div>" : "")}
{BuildUsbSection(usbResult)}
<div class=""actions"">
<a href=""/"" class=""btn btn-secondary"">Refresh</a>
</div>
</body>
</html>";
}
private static string BuildUsbSection(UsbKeyResult? usbResult)
{
if (usbResult == null)
return "";
if (usbResult.Error != null && !usbResult.Found)
return $@"<div class=""card""><p class=""info"">{Escape(usbResult.Error)}</p></div>";
if (!usbResult.Found)
return @"<div class=""card""><p class=""info"">No USB drive with <strong>print_server_key.txt</strong> found. Insert a USB drive and press Refresh.</p></div>";
if (usbResult.Error != null)
return $@"<div class=""error"">USB key found but invalid: {Escape(usbResult.Error)}</div>";
return $@"
<div class=""card"">
<div class=""usb"">
<strong>USB Key Found</strong>
<p class=""info""><strong>Restaurant ID:</strong> {Escape(usbResult.DecodedRestaurantId ?? "?")}</p>
<p class=""info""><strong>API URL:</strong> {Escape(usbResult.DecodedApiUrl ?? "?")}</p>
</div>
<form method=""POST"" action=""/update-key"" style=""margin-top: 12px;"">
<button type=""submit"" class=""btn btn-primary"">Update Key</button>
</form>
</div>";
}
public static string BuildSuccessPage(EpsonPrintServiceConfiguration newConfig)
{
return $@"<!DOCTYPE html>
<html lang=""en"">
<head>
<meta charset=""UTF-8"">
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
<meta http-equiv=""refresh"" content=""5"">
<title>Configuration Updated</title>
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; background: #f5f5f5; color: #333; }}
h1 {{ font-size: 1.4em; color: #2e7d32; }}
.card {{ background: #fff; border-radius: 8px; padding: 20px; margin: 16px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.12); }}
.success {{ background: #e8f5e9; border-left: 4px solid #2e7d32; padding: 12px 16px; border-radius: 4px; }}
.info {{ color: #666; font-size: 0.9em; }}
.info strong {{ color: #333; }}
</style>
</head>
<body>
<h1>Configuration Updated!</h1>
<div class=""card"">
<div class=""success"">
<p>The configuration has been saved. The service will restart automatically.</p>
</div>
<div class=""info"" style=""margin-top: 12px;"">
<p><strong>Restaurant ID:</strong> {Escape(newConfig.RestaurantId)}</p>
<p><strong>API URL:</strong> {Escape(newConfig.ApiUrl ?? "(not set)")}</p>
</div>
<p class=""info"" style=""margin-top: 12px;"">This page will refresh in 5 seconds...</p>
</div>
</body>
</html>";
}
private static string Escape(string value) =>
System.Net.WebUtility.HtmlEncode(value);
}

View File

@@ -0,0 +1,78 @@
using Inspectron.Epson;
using Inspectron.Epson.PrintServer;
using Inspectron.Epson.PrintServer.ConfigurationSources;
using Inspectron.Epson.PrintServer.JobSources;
using Inspectron.Epson.PrintServer.JobStatusReporters;
using Inspectron.Epson.PrintServer.PrinterAssinment;
using Inspectron.Epson.PrintServer.Printers;
using Inspectron.Epson.PrintServer.Printers.Utils;
using Inspectron.Epson.PrintServer.PrintServices;
using Inspectron.Epson.Queue;
using Microsoft.Extensions.Logging;
using Ninject;
namespace EpsonPrintService;
public class PrintServerBootstrapper
{
private readonly EpsonPrintServiceConfiguration _config;
private readonly CancellationTokenSource _cts;
private StandardKernel? _kernel;
public bool IsRunning { get; private set; }
public PrintServerBootstrapper(EpsonPrintServiceConfiguration config, CancellationTokenSource cts)
{
_config = config;
_cts = cts;
}
public async Task StartAsync()
{
_kernel = new StandardKernel();
_kernel.Bind<IDiscoveryService>().To<DiscoveryService>().InSingletonScope();
_kernel.Bind<HttpClient>().ToConstant(new HttpClient { Timeout = TimeSpan.FromSeconds(30) }).InSingletonScope();
_kernel.Bind<EpsonPrintServiceConfiguration>().ToConstant(_config);
_kernel.Bind<IPrinterFactory>().To<EpsonPrinterFactory>();
_kernel.Bind<IPrintJobSource, SignalRPrintJobSource>().To<SignalRPrintJobSource>().InSingletonScope();
_kernel.Bind<IPrintService>().To<Inspectron.Epson.PrintServer.PrintServices.EpsonPrintService>();
_kernel.Bind<ILogger>().ToMethod(ctx =>
{
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Trace);
});
return loggerFactory.CreateLogger("EpsonPrintService");
});
_kernel.Bind<IWrapperPrinterFactory>().To<PrinterWrapperFactory>().InSingletonScope();
_kernel.Bind<IPrinterConfigurationSource>().To<FixedConfigurationSource>();
_kernel.Bind<IReceiptConverterFactory>().To<ReceiptConverterFactory>().InSingletonScope();
_kernel.Bind<IJobStatusReporter>().To<JamesStatusReporter>();
_kernel.Bind<PrintServer>().ToSelf().InSingletonScope();
_kernel.Bind<IDiscoveredPrintersReceiver>().To<JamesDiscoveredPrintersReceiver>().InSingletonScope();
_kernel.Bind<PrinterDiscoveryBackgroundTask>().ToSelf().InSingletonScope();
_kernel.Bind<HeartbeatBackgroundTask>().ToSelf().InSingletonScope();
var printLoop = _kernel.Get<PrintLoop>();
var discoveryTask = _kernel.Get<PrinterDiscoveryBackgroundTask>();
var heartbeatTask = _kernel.Get<HeartbeatBackgroundTask>();
var jobSourceTask = _kernel.Get<IPrintJobSource>();
// Start background tasks
_ = discoveryTask.StartAsync(_cts.Token);
_ = heartbeatTask.StartAsync(_cts.Token);
// Delay for a moment to allow discovery to find printers
Console.WriteLine("Waiting for printer discovery...");
await Task.Delay(3000);
_ = jobSourceTask.StartAsync();
await printLoop.StartAsync();
IsRunning = true;
Console.WriteLine("Print server started.");
}
}

View File

@@ -1,95 +1,22 @@
using Inspectron.Epson;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using Ninject;
using System.Threading.Channels;
using Inspectron.Epson.PrintServer;
using Inspectron.Epson.PrintServer.ConfigurationSources;
using Inspectron.Epson.PrintServer.JobSources;
using Inspectron.Epson.PrintServer.PrinterAssinment;
using Inspectron.Epson.PrintServer.Printers;
using Inspectron.Epson.PrintServer.Printers.Utils;
using Inspectron.Epson.PrintServer.PrintServices;
using EpsonPrintService; using EpsonPrintService;
using Inspectron.Epson.PrintServer.JobStatusReporters; using Inspectron.Epson.PrintServer.ConfigurationSources;
using Inspectron.Epson.Queue;
StandardKernel kernel = new StandardKernel(); // Load configuration (may be absent on first boot)
var configResult = ConfigurationLoader.TryLoadFromFile();
EpsonPrintServiceConfiguration? activeConfig = null;
if (configResult.Success)
var configPath = ConfigurationPaths.GetConfigPath();
Console.WriteLine($"Loading configuration from: {configPath}");
var base64Line = File.ReadAllText(configPath).Trim();
var decodedConfig = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(base64Line));
var configParts = decodedConfig.Split(';');
if (configParts.Length != 3)
throw new InvalidOperationException($"Invalid configuration format. Expected 'apiUrl;restaurantId;apiKey', got {configParts.Length} parts.");
var config = new EpsonPrintServiceConfiguration
{ {
ApiUrl = configParts[0], activeConfig = configResult.Config;
RestaurantId = configParts[1], Console.WriteLine($"Configuration loaded.");
ApiKey = configParts[2] Console.WriteLine($"Restaurant: {activeConfig!.RestaurantId}");
}; Console.WriteLine($"API URL: {activeConfig.ApiUrl}");
}
Console.WriteLine($"Restaurant: {config.RestaurantId}"); else
Console.WriteLine($"API URL: {config.ApiUrl}");
kernel.Bind<IDiscoveryService>().To<DiscoveryService>().InSingletonScope();
//var discovery = new PreconfiguredDiscoveryService();
//discovery.AddPrinter("192.168.50.176", "TM-T30III", port: 9100);
//discovery.AddPrinter("192.168.50.60", "TM-T30III", port: 9100);
//discovery.AddPrinter("192.168.50.61", "TM-T30III", port: 9100);
//discovery.AddPrinter("192.168.50.80", "TM-U220II", port: 9100);
//kernel.Bind<IDiscoveryService>().ToConstant(discovery);
kernel.Bind<HttpClient>().ToConstant(new HttpClient { Timeout = TimeSpan.FromSeconds(30) }).InSingletonScope();
kernel.Bind<EpsonPrintServiceConfiguration>().ToConstant(config);
//kernel.Bind<IPrinterFactory>().ToMethod(sp=>new HtmlPrinterFactory("TM-U220II.html", paperWidth: 258,type:0x0d, logger:sp.Kernel.Get<ILogger>()));
//kernel.Bind<IPrinterFactory>().ToMethod(sp=>new HtmlPrinterFactory("TM-T30III.html",logger:sp.Kernel.Get<ILogger>()));
kernel.Bind<IPrinterFactory>().To<EpsonPrinterFactory>();
kernel.Bind<IPrintJobSource, SignalRPrintJobSource>().To<SignalRPrintJobSource>().InSingletonScope();
//kernel.Bind<IPrintJobSource>().To<HttpPollingPrintJobSource>().InSingletonScope();
kernel.Bind<IPrintService>().To<Inspectron.Epson.PrintServer.PrintServices.EpsonPrintService>();
kernel.Bind<ILogger>().ToMethod(ctx =>
{ {
var loggerFactory = LoggerFactory.Create(builder => Console.WriteLine($"No valid configuration: {configResult.Error}");
{ Console.WriteLine("Running in configuration-only mode. Navigate to http://<this-ip> to configure.");
builder.AddConsole(); }
builder.SetMinimumLevel(LogLevel.Trace);
});
return loggerFactory.CreateLogger("EpsonPrintService");
});
kernel.Bind<IWrapperPrinterFactory>().To<PrinterWrapperFactory>().InSingletonScope();
kernel.Bind<IPrinterConfigurationSource>().To<FixedConfigurationSource>();
kernel.Bind<IReceiptConverterFactory>().To<ReceiptConverterFactory>().InSingletonScope();
kernel.Bind<IJobStatusReporter>().To<JamesStatusReporter>();
kernel.Bind<PrintServer>().ToSelf().InSingletonScope();
kernel.Bind<IDiscoveredPrintersReceiver>().To<JamesDiscoveredPrintersReceiver>().InSingletonScope();
kernel.Bind<PrinterDiscoveryBackgroundTask>().ToSelf().InSingletonScope();
kernel.Bind<HeartbeatBackgroundTask>().ToSelf().InSingletonScope();
var printLoop = kernel.Get<PrintLoop>();
var printServer = kernel.Get<PrintServer>();
var discoveryTask = kernel.Get<PrinterDiscoveryBackgroundTask>();
var heartbeatTask = kernel.Get<HeartbeatBackgroundTask>();
var jobSourceTask = kernel.Get<IPrintJobSource>();
//printServer.RegisterPrinter("10.0.20.12");
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
Console.CancelKeyPress += (sender, e) => Console.CancelKeyPress += (sender, e) =>
@@ -99,18 +26,21 @@ Console.CancelKeyPress += (sender, e) =>
cts.Cancel(); cts.Cancel();
}; };
// Start background tasks // Track print server state for the web UI
_ = discoveryTask.StartAsync(cts.Token); PrintServerBootstrapper? bootstrapper = null;
_ = heartbeatTask.StartAsync(cts.Token);
// Start web UI (always runs)
var httpServer = new SimpleHttpServer(
getConfig: () => activeConfig,
getIsRunning: () => bootstrapper?.IsRunning ?? false);
_ = Task.Run(() => httpServer.StartAsync(cts.Token));
// delay for a moment to allow discovery to find printers // Start print server if configured
Console.WriteLine("Waiting for printer discovery..."); if (activeConfig != null)
await Task.Delay(3000); {
bootstrapper = new PrintServerBootstrapper(activeConfig, cts);
_ = jobSourceTask.StartAsync(); await bootstrapper.StartAsync();
}
await printLoop.StartAsync();
try try
{ {
@@ -120,4 +50,3 @@ catch (TaskCanceledException)
{ {
// Expected when Ctrl+C is pressed // Expected when Ctrl+C is pressed
} }

View File

@@ -0,0 +1,136 @@
using System.Net;
using System.Text;
using Inspectron.Epson.PrintServer.ConfigurationSources;
namespace EpsonPrintService;
public class SimpleHttpServer
{
private readonly HttpListener _listener;
private readonly Func<EpsonPrintServiceConfiguration?> _getConfig;
private readonly Func<bool> _getIsRunning;
public SimpleHttpServer(Func<EpsonPrintServiceConfiguration?> getConfig, Func<bool> getIsRunning)
{
_listener = new HttpListener();
_listener.Prefixes.Add("http://+:80/");
_getConfig = getConfig;
_getIsRunning = getIsRunning;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_listener.Start();
Console.WriteLine("Web UI listening on http://+:80/");
try
{
while (!cancellationToken.IsCancellationRequested)
{
var context = await _listener.GetContextAsync();
_ = Task.Run(() => HandleRequestAsync(context), cancellationToken);
}
}
catch (HttpListenerException) when (cancellationToken.IsCancellationRequested)
{
// Expected on shutdown
}
finally
{
_listener.Stop();
}
}
private async Task HandleRequestAsync(HttpListenerContext context)
{
try
{
var request = context.Request;
var response = context.Response;
var path = request.Url?.AbsolutePath ?? "/";
var method = request.HttpMethod;
string html;
if (method == "GET" && path == "/")
{
html = await BuildIndexPageAsync();
}
else if (method == "POST" && path == "/update-key")
{
html = await HandleUpdateKeyAsync();
}
else
{
response.StatusCode = 404;
html = "<html><body><h1>Not Found</h1></body></html>";
}
var buffer = Encoding.UTF8.GetBytes(html);
response.ContentType = "text/html; charset=utf-8";
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer);
response.Close();
}
catch (Exception ex)
{
Console.WriteLine($"HTTP error: {ex.Message}");
try { context.Response.Close(); } catch { }
}
}
private async Task<string> BuildIndexPageAsync()
{
var usbResult = await UsbKeyScanner.ScanForKeyAsync();
return HtmlPageBuilder.BuildConfigPage(_getConfig(), _getIsRunning(), usbResult);
}
private async Task<string> HandleUpdateKeyAsync()
{
var usbResult = await UsbKeyScanner.ScanForKeyAsync();
if (!usbResult.Found || usbResult.Base64Content == null)
{
return HtmlPageBuilder.BuildConfigPage(
_getConfig(), _getIsRunning(), usbResult,
errorMessage: "No valid USB key found. Please insert a USB drive and try again.");
}
// Validate again
var validation = ConfigurationLoader.TryLoadFromBase64(usbResult.Base64Content);
if (!validation.Success)
{
return HtmlPageBuilder.BuildConfigPage(
_getConfig(), _getIsRunning(), usbResult,
errorMessage: $"Key validation failed: {validation.Error}");
}
// Write config file
try
{
var configPath = ConfigurationPaths.GetConfigPath();
var configDir = Path.GetDirectoryName(configPath);
if (!string.IsNullOrEmpty(configDir) && !Directory.Exists(configDir))
Directory.CreateDirectory(configDir);
await File.WriteAllTextAsync(configPath, usbResult.Base64Content);
Console.WriteLine($"Configuration updated at {configPath}");
}
catch (Exception ex)
{
return HtmlPageBuilder.BuildConfigPage(
_getConfig(), _getIsRunning(), usbResult,
errorMessage: $"Failed to write configuration: {ex.Message}");
}
// Schedule exit so systemd restarts us with new config
_ = Task.Run(async () =>
{
await Task.Delay(500);
Console.WriteLine("Exiting for restart with new configuration...");
Environment.Exit(0);
});
return HtmlPageBuilder.BuildSuccessPage(validation.Config!);
}
}

View File

@@ -0,0 +1,137 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.Json;
namespace EpsonPrintService;
public record UsbKeyResult(bool Found, string? Base64Content, string? DecodedApiUrl, string? DecodedRestaurantId, string? Error);
public static class UsbKeyScanner
{
private const string KeyFileName = "print_server_key.txt";
public static async Task<UsbKeyResult> ScanForKeyAsync()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return new UsbKeyResult(false, null, null, null, "USB scanning is only supported on Linux.");
try
{
var lsblkJson = await RunCommandAsync("lsblk", "-J -o NAME,RM,MOUNTPOINT,TYPE");
if (lsblkJson == null)
return new UsbKeyResult(false, null, null, null, "Failed to run lsblk.");
var doc = JsonDocument.Parse(lsblkJson);
var devices = doc.RootElement.GetProperty("blockdevices");
foreach (var device in devices.EnumerateArray())
{
var result = await ScanDeviceAsync(device);
if (result != null)
return result;
// Check children (partitions)
if (device.TryGetProperty("children", out var children))
{
foreach (var child in children.EnumerateArray())
{
result = await ScanDeviceAsync(child);
if (result != null)
return result;
}
}
}
return new UsbKeyResult(false, null, null, null, null);
}
catch (Exception ex)
{
return new UsbKeyResult(false, null, null, null, $"USB scan failed: {ex.Message}");
}
}
private static async Task<UsbKeyResult?> ScanDeviceAsync(JsonElement device)
{
var isRemovable = device.TryGetProperty("rm", out var rm) && (rm.ValueKind == JsonValueKind.True || (rm.ValueKind == JsonValueKind.Number && rm.GetInt32() == 1));
if (!isRemovable)
return null;
var type = device.TryGetProperty("type", out var t) ? t.GetString() : null;
if (type != "part")
return null;
var name = device.TryGetProperty("name", out var n) ? n.GetString() : null;
if (name == null)
return null;
var mountpoint = device.TryGetProperty("mountpoint", out var mp) && mp.ValueKind == JsonValueKind.String ? mp.GetString() : null;
if (!string.IsNullOrEmpty(mountpoint))
{
return CheckMountpoint(mountpoint);
}
// Try to mount and check
var tempMount = $"/tmp/usb_scan_{name}";
try
{
Directory.CreateDirectory(tempMount);
var mountResult = await RunCommandAsync("mount", $"-o ro /dev/{name} {tempMount}");
if (mountResult == null)
return null;
return CheckMountpoint(tempMount);
}
finally
{
try { await RunCommandAsync("umount", tempMount); } catch { }
try { Directory.Delete(tempMount); } catch { }
}
}
private static UsbKeyResult? CheckMountpoint(string mountpoint)
{
var keyPath = Path.Combine(mountpoint, KeyFileName);
if (!File.Exists(keyPath))
return null;
var content = File.ReadAllText(keyPath).Trim();
if (string.IsNullOrWhiteSpace(content))
return null;
var validation = ConfigurationLoader.TryLoadFromBase64(content);
if (!validation.Success)
return new UsbKeyResult(true, content, null, null, $"Key file found but invalid: {validation.Error}");
return new UsbKeyResult(true, content, validation.Config!.ApiUrl, validation.Config.RestaurantId, null);
}
private static async Task<string?> RunCommandAsync(string command, string arguments)
{
try
{
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = command,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
var output = await process.StandardOutput.ReadToEndAsync();
await process.WaitForExitAsync();
return process.ExitCode == 0 ? output : null;
}
catch
{
return null;
}
}
}