using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; namespace IceImportSL.Hubs; // Group convention: "iceimport:run:{runId}" per the Phase 2 plan. Client connects once, then calls // JoinRunGroup(runId) for whichever run(s) it wants to watch — mirrors // AutomationSL.Hubs.AutomationRunHub / FrameworkSL.Hubs.DataSync.DataSyncHub exactly (both use the // same "OnConnectedAsync can't know the RunId yet, so the client calls JoinRunGroup explicitly" // pattern, rather than MRPRunHub's fixed-OU-group-on-connect shape, since a run's id isn't known // until CommitImport/RetryImportRun returns it). public class IceImportRunHub : Hub { private readonly ILogger _logger; public IceImportRunHub(ILogger logger) => _logger = logger; public override async Task OnConnectedAsync() { _logger.LogInformation("IceImportRunHub connected: {ConnectionId}", Context.ConnectionId); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception? exception) { // Per-run groups joined via JoinRunGroup are torn down automatically by SignalR's default // in-memory group manager when the connection is removed — no manual per-run bookkeeping // needed here (same reasoning as AutomationRunHub.OnDisconnectedAsync). if (exception is not null) _logger.LogWarning(exception, "IceImportRunHub disconnected with error: {ConnectionId}", Context.ConnectionId); await base.OnDisconnectedAsync(exception); } public async Task JoinRunGroup(long runId) => await Groups.AddToGroupAsync(Context.ConnectionId, RunGroup(runId)); public async Task LeaveRunGroup(long runId) => await Groups.RemoveFromGroupAsync(Context.ConnectionId, RunGroup(runId)); public static string RunGroup(long runId) => $"iceimport:run:{runId}"; }