161 lines
5.5 KiB
C#
161 lines
5.5 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Tornado3_2026Election.Domain;
|
|
|
|
namespace Tornado3_2026Election.Services;
|
|
|
|
public sealed class MockTornado3Adapter : ITornado3Adapter
|
|
{
|
|
private readonly LogService _logService;
|
|
private TornadoConnectionState _state = TornadoConnectionState.Idle;
|
|
|
|
public MockTornado3Adapter(LogService logService)
|
|
{
|
|
_logService = logService;
|
|
}
|
|
|
|
public string BackendName => "Mock Adapter";
|
|
|
|
public bool IsLiveCg => false;
|
|
|
|
public bool IsConnected => false;
|
|
|
|
public string ConnectionTarget => "실CG 미연동";
|
|
|
|
public TornadoConnectionState State
|
|
{
|
|
get => _state;
|
|
private set
|
|
{
|
|
if (_state == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_state = value;
|
|
StateChanged?.Invoke(this, value);
|
|
}
|
|
}
|
|
|
|
public event EventHandler<TornadoConnectionState>? StateChanged;
|
|
|
|
public event EventHandler? ConnectionChanged;
|
|
|
|
public async Task EnsureConnectedAsync(CancellationToken cancellationToken)
|
|
{
|
|
await ExecuteWithTimeoutAsync(async () =>
|
|
{
|
|
await Task.Delay(120, cancellationToken).ConfigureAwait(false);
|
|
State = TornadoConnectionState.Ready;
|
|
_logService.Info("Tornado3 mock adapter connected.");
|
|
}, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task ApplyCutAsync(
|
|
BroadcastChannel channel,
|
|
FormatTemplateDefinition template,
|
|
FormatCutDefinition cut,
|
|
ElectionDataSnapshot snapshot,
|
|
BroadcastStationProfile station,
|
|
string imageRootPath,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
await ExecuteWithTimeoutAsync(async () =>
|
|
{
|
|
State = TornadoConnectionState.Sending;
|
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
|
var summary = snapshot.BroadcastPhase == BroadcastPhase.PreElection
|
|
? $"turnout={snapshot.TurnoutRate:0.0}% voters={snapshot.TurnoutVotes:N0}"
|
|
: $"leader={snapshot.Candidates.OrderByDescending(candidate => candidate.VoteCount).FirstOrDefault()?.Name ?? "없음"} counted={snapshot.CountedVotes:N0}";
|
|
_logService.Info($"[{channel}] Apply {template.Name}/{cut.Name} station={station.Name} {summary} imageRoot={imageRootPath}");
|
|
State = TornadoConnectionState.Ready;
|
|
}, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public Task<bool> TryCapturePendingCutPreviewAsync(
|
|
BroadcastChannel channel,
|
|
string fileName,
|
|
int width,
|
|
int height,
|
|
int frame,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
return Task.FromResult(false);
|
|
}
|
|
|
|
public Task<bool> TryCaptureCutPreviewAsync(
|
|
BroadcastChannel channel,
|
|
FormatTemplateDefinition template,
|
|
FormatCutDefinition cut,
|
|
ElectionDataSnapshot snapshot,
|
|
BroadcastStationProfile station,
|
|
string imageRootPath,
|
|
string fileName,
|
|
int width,
|
|
int height,
|
|
int frame,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
return Task.FromResult(false);
|
|
}
|
|
|
|
public async Task PrepareAsync(BroadcastChannel channel, CancellationToken cancellationToken)
|
|
{
|
|
await ExecuteWithTimeoutAsync(async () =>
|
|
{
|
|
State = TornadoConnectionState.Ready;
|
|
await Task.Delay(60, cancellationToken).ConfigureAwait(false);
|
|
_logService.Info($"[{channel}] Prepare");
|
|
}, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task ShowPreparedFirstFrameAsync(BroadcastChannel channel, CancellationToken cancellationToken)
|
|
{
|
|
await ExecuteWithTimeoutAsync(async () =>
|
|
{
|
|
State = TornadoConnectionState.Ready;
|
|
await Task.Delay(40, cancellationToken).ConfigureAwait(false);
|
|
_logService.Info($"[{channel}] Show prepared first frame on PGM");
|
|
}, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task TakeAsync(BroadcastChannel channel, CancellationToken cancellationToken)
|
|
{
|
|
await ExecuteWithTimeoutAsync(async () =>
|
|
{
|
|
State = TornadoConnectionState.OnAir;
|
|
await Task.Delay(60, cancellationToken).ConfigureAwait(false);
|
|
_logService.Info($"[{channel}] Take");
|
|
}, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task ClearOutputAsync(BroadcastChannel channel, CancellationToken cancellationToken)
|
|
{
|
|
await ExecuteWithTimeoutAsync(async () =>
|
|
{
|
|
State = TornadoConnectionState.Idle;
|
|
await Task.Delay(30, cancellationToken).ConfigureAwait(false);
|
|
_logService.Info($"[{channel}] Clear output layer");
|
|
}, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task OutAsync(BroadcastChannel channel, CancellationToken cancellationToken)
|
|
{
|
|
await ExecuteWithTimeoutAsync(async () =>
|
|
{
|
|
State = TornadoConnectionState.Idle;
|
|
await Task.Delay(60, cancellationToken).ConfigureAwait(false);
|
|
_logService.Info($"[{channel}] Layer Out");
|
|
}, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
private static async Task ExecuteWithTimeoutAsync(Func<Task> action, CancellationToken cancellationToken)
|
|
{
|
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
timeoutCts.CancelAfter(TimeSpan.FromSeconds(5));
|
|
await action().WaitAsync(timeoutCts.Token).ConfigureAwait(false);
|
|
}
|
|
}
|