using System.IO;
using System.IO.Pipes;
using System.Runtime.Versioning;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using WebhookServer.Core.Ipc;
using WebhookServer.Core.Models;
namespace WebhookServer.Gui.Services;
///
/// Thin client around the admin named pipe. Each call connects, sends one request,
/// reads one response, and disconnects — keeps lifecycle simple at the cost of
/// connect-per-call overhead. The service single-instance pipe queues requests so
/// concurrent calls from the GUI serialize automatically.
///
[SupportedOSPlatform("windows")]
public sealed class AdminPipeClient
{
public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(5);
public async Task InvokeAsync(string op, object? data = null, CancellationToken ct = default)
{
var request = new AdminRequest
{
Op = op,
Data = data is null
? null
: JsonSerializer.SerializeToDocument(data, AdminProtocol.JsonOptions).RootElement.Clone(),
};
await using var pipe = new NamedPipeClientStream(
".",
PipeSecurityFactory.PipeName,
PipeDirection.InOut,
PipeOptions.Asynchronous);
await pipe.ConnectAsync((int)ConnectTimeout.TotalMilliseconds, ct).ConfigureAwait(false);
await PipeFraming.WriteAsync(pipe, request, ct).ConfigureAwait(false);
using var reader = PipeFraming.CreateReader(pipe);
var response = await PipeFraming.ReadAsync(reader, ct).ConfigureAwait(false);
return response ?? AdminResponse.Failure("empty response from service");
}
public async Task InvokeAsync(string op, object? data = null, CancellationToken ct = default) where T : class
{
var resp = await InvokeAsync(op, data, ct).ConfigureAwait(false);
if (!resp.Ok || resp.Data is null) return null;
return resp.Data.Value.Deserialize(AdminProtocol.JsonOptions);
}
public Task PingAsync(CancellationToken ct = default) =>
InvokeAsync(AdminOps.Ping, null, ct);
public Task GetStatusAsync(CancellationToken ct = default) =>
InvokeAsync(AdminOps.GetStatus, null, ct);
public Task GetConfigAsync(CancellationToken ct = default) =>
InvokeAsync(AdminOps.GetConfig, null, ct);
public Task CreateEndpointAsync(EndpointConfig endpoint, CancellationToken ct = default) =>
InvokeAsync(AdminOps.CreateEndpoint, endpoint, ct);
public Task UpdateEndpointAsync(EndpointConfig endpoint, CancellationToken ct = default) =>
InvokeAsync(AdminOps.UpdateEndpoint, endpoint, ct);
public Task DeleteEndpointAsync(Guid id, CancellationToken ct = default) =>
InvokeAsync(AdminOps.DeleteEndpoint, new DeleteEndpointArgs { Id = id }, ct);
public Task SetEndpointEnabledAsync(Guid id, bool enabled, CancellationToken ct = default) =>
InvokeAsync(enabled ? AdminOps.EnableEndpoint : AdminOps.DisableEndpoint, new EndpointToggle { Id = id }, ct);
public Task BindHttpsAsync(HttpsBinding? binding, CancellationToken ct = default) =>
InvokeAsync(AdminOps.BindHttps, binding, ct);
public Task RestartListenerAsync(CancellationToken ct = default) =>
InvokeAsync(AdminOps.RestartListener, null, ct);
public async Task> TailLogsAsync(int lines, CancellationToken ct = default)
{
var resp = await InvokeAsync(AdminOps.TailLogs, new TailLogsArgs { LinesToBacklog = lines, Follow = false }, ct).ConfigureAwait(false);
if (!resp.Ok || resp.Data is null) return new List();
var lst = resp.Data.Value.GetProperty("lines").Deserialize>(AdminProtocol.JsonOptions);
return lst ?? new List();
}
public async Task> ListBackupsAsync(CancellationToken ct = default)
{
var resp = await InvokeAsync(AdminOps.ListBackups, null, ct).ConfigureAwait(false);
if (!resp.Ok || resp.Data is null) return new List();
var lst = resp.Data.Value.GetProperty("backups").Deserialize>(AdminProtocol.JsonOptions);
return lst ?? new List();
}
public Task RestoreBackupAsync(string fileName, CancellationToken ct = default) =>
InvokeAsync(AdminOps.RestoreBackup, new RestoreBackupArgs { FileName = fileName }, ct);
public Task ImportConfigAsync(ServerConfig config, CancellationToken ct = default) =>
InvokeAsync(AdminOps.ImportConfig, config, ct);
public Task CreateCheckpointAsync(string? description, CancellationToken ct = default) =>
InvokeAsync(AdminOps.CreateCheckpoint, new CreateCheckpointArgs { Description = description }, ct);
}