dragon-iso/src/TeamsISO.App/Services/ControlSurface/Endpoints/TopologyEndpoints.cs

92 lines
3.4 KiB
C#
Raw Normal View History

refactor(control-surface): split server into endpoint partials ControlSurfaceServer.cs was 1061 lines / 47KB — a single class hosting the HttpListener loop, the route dispatch, and every endpoint body in between. Splits the class via partial-class into a thin host file plus one partial per route group, all under Services/ControlSurface/. * Services/ControlSurfaceServer.cs (was 1061L → now 400L) — kept here: Start / Stop / DisposeAsync (the listener lifecycle), AcceptLoopAsync, HandleRequestAsync (the route table itself, with its CORS preflight + WebSocket upgrade + JSON dispatch), the response helpers (ReadBodyAsync / WriteJsonAsync / TryGetBool / TryGetString), the NotFound switch-arm, and the JsonSerializerOptions singleton. * Services/ControlSurface/Endpoints/HomeEndpoints.cs — GetServerInfo, TryRead helper. * Services/ControlSurface/Endpoints/ParticipantsEndpoints.cs (the biggest split) — GetParticipants, SetIsoOverrideByIdAsync, ClearIsoOverrideByIdAsync, TryParseEnum, ToggleIsoByIdAsync, ToggleIsoByNameAsync, ToggleByIdAsync. Together: every /participants/* handler. * Services/ControlSurface/Endpoints/PresetsEndpoints.cs — RefreshDiscovery, StopAllAsync, ApplyPresetAsync. * Services/ControlSurface/Endpoints/TeamsEndpoints.cs — InvokeTeams (the helper that maps a TeamsControlBridge result to the JSON body). * Services/ControlSurface/Endpoints/TopologyEndpoints.cs — GetTopology, ApplyTopologyAsync, RestoreTopologyAsync. * Services/ControlSurface/Endpoints/NotesEndpoints.cs — AppendNote. * Services/ControlSurface/Endpoints/ThumbnailEndpoint.cs — TryEncodeThumbnailJpeg (which is actually the BMP path now) + EncodeBmpDownscaled + the LE byte writers. The legacy TryEncodeThumbnailJpeg_WpfDeadCode helper that was dead-coded "for posterity" is gone — no call sites; we removed-comments-on-removed- code is the anti-pattern we wanted to fix. * Services/ControlSurface/WebSocketHub.cs — HandleWebSocketAsync, PushSnapshotIfChangedAsync, SendAsync, GetSnapshotJsonAsync. The push-timer wiring stays in the host's Start() so the lifetime is obvious where the connection is opened. No behavior change. The route table in HandleRequestAsync still dispatches by (HttpMethod, path) — only the handler bodies moved. Build clean; 56 + 104 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:48:03 -04:00
namespace TeamsISO.App.Services;
// /topology/* route handlers — read + apply / restore the machine NDI
// access-manager config so the operator can flip transcoder topology
// without leaving the web UI.
//
// GET /topology → GetTopology
// POST /topology/apply → ApplyTopologyAsync
// POST /topology/restore → RestoreTopologyAsync
public sealed partial class ControlSurfaceServer
{
/// <summary>
/// Report the current NDI machine topology. "mode" is "hidden" when
/// local senders are confined to the private group (raw Teams sources
/// invisible to the rest of the LAN), "public" otherwise. Reads the
/// machine NDI config file directly — no caching, so the result
/// reflects whatever state the file is in right now (including
/// manual edits).
/// </summary>
private object GetTopology()
{
try
{
var (mode, sends, recvs) = NdiAccessManagerConfig.ReadCurrent();
return new
{
mode,
senders = sends,
receivers = recvs,
configPath = NdiAccessManagerConfig.ConfigPath,
};
}
catch (Exception ex)
{
return new { ok = false, error = ex.Message };
}
}
/// <summary>
/// Apply the transcoder topology: machine senders → <c>teamsiso-input</c>,
/// receivers → <c>public + teamsiso-input</c>; engine groups updated to
/// match (discover from teamsiso-input, broadcast on public). Operator
/// MUST restart Teams afterward for it to read the new NDI config.
/// </summary>
private async Task<object> ApplyTopologyAsync()
{
var result = NdiAccessManagerConfig.ApplyTranscoderTopology();
if (!result.Success)
{
return new { ok = false, error = result.ErrorMessage, configPath = result.ConfigPath };
}
// Mirror what the WPF settings VM does so the engine groups +
// machine config stay in lockstep.
var ourGroups = new TeamsISO.Engine.Domain.NdiGroupSettings(
DiscoveryGroups: NdiAccessManagerConfig.TranscoderInputGroup,
OutputGroups: "public");
await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None);
return new
{
ok = true,
mode = "hidden",
backupPath = result.BackupPath,
note = "Restart Microsoft Teams for the new NDI config to take effect there.",
};
}
/// <summary>
/// Restore the machine NDI defaults: senders + receivers both on
/// <c>public</c>. Engine groups go back to null/defaults too. Operator
/// must restart Teams for it to broadcast on public again.
/// </summary>
private async Task<object> RestoreTopologyAsync()
{
var result = NdiAccessManagerConfig.RestoreDefaults();
if (!result.Success)
{
return new { ok = false, error = result.ErrorMessage, configPath = result.ConfigPath };
}
var ourGroups = new TeamsISO.Engine.Domain.NdiGroupSettings(
DiscoveryGroups: null,
OutputGroups: null);
await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None);
return new
{
ok = true,
mode = "public",
backupPath = result.BackupPath,
note = "Restart Microsoft Teams for the new NDI config to take effect there.",
};
}
}