feat: add audited one-shot prepare gate
This commit is contained in:
@@ -5,6 +5,7 @@ using MBN_STOCK_WEBVIEW.LegacyApplication;
|
||||
using MBN_STOCK_WEBVIEW.LegacyBridge;
|
||||
using MBN_STOCK_WEBVIEW.Playout;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Configuration;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Safety;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Storage.Pickers;
|
||||
using WinRT.Interop;
|
||||
@@ -45,7 +46,9 @@ public sealed partial class MainWindow
|
||||
: null;
|
||||
_compositionOptions = new MutableLegacySceneCueCompositionOptionsSource(
|
||||
initialComposition);
|
||||
_playoutEngine = PlayoutEngineFactory.Create(_playoutOptions);
|
||||
_playoutEngine = PlayoutEngineFactory.Create(
|
||||
_playoutOptions,
|
||||
_playoutLaunchAuthorization);
|
||||
_playoutEngine.StatusChanged += OnPlayoutStatusChanged;
|
||||
if (_databaseRuntime is not null)
|
||||
{
|
||||
@@ -118,8 +121,19 @@ public sealed partial class MainWindow
|
||||
|
||||
private async Task<LegacyOperatorSnapshot> ExecuteOperatorPlayoutAsync(
|
||||
LegacyOperatorPlayoutCommand command,
|
||||
CancellationToken cancellationToken)
|
||||
CancellationToken cancellationToken,
|
||||
bool gateAPrepareClaimed = false,
|
||||
LegacyOperatorPlayoutRequest? gateAPrepareRequest = null)
|
||||
{
|
||||
if (_playoutLaunchAuthorization.IsGateA &&
|
||||
(!gateAPrepareClaimed ||
|
||||
command != LegacyOperatorPlayoutCommand.Prepare ||
|
||||
gateAPrepareRequest is null))
|
||||
{
|
||||
return _controller.ReportError(
|
||||
"Gate A 검증 회차에서는 승인된 5001 PREPARE 한 번만 실행할 수 있습니다.");
|
||||
}
|
||||
|
||||
var engine = _playoutEngine;
|
||||
var workflow = _playoutWorkflow;
|
||||
if (engine is null || workflow is null)
|
||||
@@ -163,8 +177,9 @@ public sealed partial class MainWindow
|
||||
switch (command)
|
||||
{
|
||||
case LegacyOperatorPlayoutCommand.Prepare:
|
||||
var request = _controller.CreatePlayoutRequest(
|
||||
_compositionOptions!.Current.FadeDuration);
|
||||
var request = gateAPrepareRequest ??
|
||||
_controller.CreatePlayoutRequest(
|
||||
_compositionOptions!.Current.FadeDuration);
|
||||
result = await workflow.PrepareAsync(
|
||||
request.Playlist,
|
||||
request.SelectedIndexZeroBased,
|
||||
@@ -265,10 +280,112 @@ public sealed partial class MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<LegacyOperatorSnapshot> ExecuteGateAPrepareAsync(
|
||||
string capability,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_playoutLaunchAuthorization.IsGateA ||
|
||||
!_playoutLaunchAuthorization.TryClaimGateAPrepare(capability))
|
||||
{
|
||||
return _controller.ReportError(
|
||||
"Gate A PREPARE 승인이 없거나 이미 사용되었습니다.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!TryCreateExactGateAPrepareRequest(out var request))
|
||||
{
|
||||
return _controller.ReportError(
|
||||
"Gate A PREPARE 직전 상태가 승인된 삼성전자 5001 page 1 조건과 일치하지 않습니다.");
|
||||
}
|
||||
|
||||
return await ExecuteOperatorPlayoutAsync(
|
||||
LegacyOperatorPlayoutCommand.Prepare,
|
||||
cancellationToken,
|
||||
gateAPrepareClaimed: true,
|
||||
gateAPrepareRequest: request).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Success, rejection, cancellation and unknown outcome all consume the
|
||||
// one process-lifetime capability. There is deliberately no retry path.
|
||||
_playoutLaunchAuthorization.CompleteGateAPrepare();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryCreateExactGateAPrepareRequest(
|
||||
out LegacyOperatorPlayoutRequest? request)
|
||||
{
|
||||
request = null;
|
||||
var snapshot = _controller.Current;
|
||||
if (snapshot.Playlist.Count != 1 ||
|
||||
_compositionOptions?.Current is not { } composition ||
|
||||
composition.FadeDuration != 6 ||
|
||||
composition.BackgroundKind != LegacySceneBackgroundKind.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var row = snapshot.Playlist[0];
|
||||
if (!row.IsEnabled || !row.IsActive ||
|
||||
!string.Equals(row.MarketText, "코스피", StringComparison.Ordinal) ||
|
||||
!string.Equals(row.StockName, "삼성전자", StringComparison.Ordinal) ||
|
||||
!string.Equals(row.GraphicType, "1열판기본", StringComparison.Ordinal) ||
|
||||
!string.Equals(row.Subtype, "현재가", StringComparison.Ordinal) ||
|
||||
!string.Equals(row.PageText, "1/1", StringComparison.Ordinal) ||
|
||||
!string.Equals(row.StockCode, "005930", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LegacyOperatorPlayoutRequest candidate;
|
||||
try
|
||||
{
|
||||
candidate = _controller.CreatePlayoutRequest(6);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (candidate.SelectedIndexZeroBased != 0 ||
|
||||
candidate.Playlist.Count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var entry = candidate.Playlist[0];
|
||||
var selection = entry.Selection;
|
||||
if (!entry.IsEnabled ||
|
||||
!string.Equals(entry.EntryId, row.RowId, StringComparison.Ordinal) ||
|
||||
!string.Equals(entry.CutCode, "5001", StringComparison.Ordinal) ||
|
||||
entry.FadeDuration != 6 ||
|
||||
entry.PageNavigation?.IsEnabled == true ||
|
||||
entry.MovingAverageSelectionSource is not null ||
|
||||
selection is null ||
|
||||
!string.Equals(selection.GroupCode, "코스피", StringComparison.Ordinal) ||
|
||||
!string.Equals(selection.Subject, "삼성전자", StringComparison.Ordinal) ||
|
||||
!string.Equals(selection.GraphicType, "1열판기본", StringComparison.Ordinal) ||
|
||||
!string.Equals(selection.Subtype, "현재가", StringComparison.Ordinal) ||
|
||||
!string.Equals(selection.DataCode, "005930", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
request = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<LegacyOperatorSnapshot> SetFadeDurationAsync(
|
||||
int fadeDuration,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_playoutLaunchAuthorization.AllowsCompositionMutation)
|
||||
{
|
||||
return _controller.ReportError(
|
||||
"Gate A 검증 회차에서는 Fade 설정을 변경할 수 없습니다.");
|
||||
}
|
||||
|
||||
if (fadeDuration is < 0 or > 19)
|
||||
{
|
||||
return _controller.ReportError("DissolveTime 값이 올바르지 않습니다.");
|
||||
@@ -308,6 +425,12 @@ public sealed partial class MainWindow
|
||||
private async Task<LegacyOperatorSnapshot> ToggleBackgroundAsync(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_playoutLaunchAuthorization.AllowsCompositionMutation)
|
||||
{
|
||||
return _controller.ReportError(
|
||||
"Gate A 검증 회차에서는 배경 설정을 변경할 수 없습니다.");
|
||||
}
|
||||
|
||||
if (!await _playoutCommandGate.WaitAsync(0, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return _controller.ReportError("송출 명령 처리 중에는 배경을 변경할 수 없습니다.");
|
||||
@@ -349,6 +472,12 @@ public sealed partial class MainWindow
|
||||
private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_playoutLaunchAuthorization.AllowsCompositionMutation)
|
||||
{
|
||||
return _controller.ReportError(
|
||||
"Gate A 검증 회차에서는 배경 파일을 선택할 수 없습니다.");
|
||||
}
|
||||
|
||||
if (!await _playoutCommandGate.WaitAsync(0, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return _controller.ReportError("송출 명령 처리 중에는 배경을 변경할 수 없습니다.");
|
||||
@@ -534,6 +663,27 @@ public sealed partial class MainWindow
|
||||
LegacyConfirmManualListIntent or
|
||||
LegacyImportManualListsIntent;
|
||||
|
||||
private static bool IsPersistentWriteIntent(LegacyUiIntent intent) => intent is
|
||||
LegacyDeleteUc4SelectedThemeIntent or
|
||||
LegacyDeleteUc6SelectedExpertIntent or
|
||||
LegacySaveOperatorCatalogIntent or
|
||||
LegacyConfirmNativeDialogIntent or
|
||||
LegacyAddComparisonPairIntent or
|
||||
LegacyMoveComparisonPairIntent or
|
||||
LegacyDeleteSelectedComparisonPairIntent or
|
||||
LegacyDeleteAllComparisonPairsIntent or
|
||||
LegacySaveManualFinancialIntent or
|
||||
LegacyDeleteManualFinancialIntent or
|
||||
LegacyDeleteAllManualFinancialIntent or
|
||||
LegacySaveManualNetSellIntent or
|
||||
LegacySaveManualViIntent or
|
||||
LegacyConfirmManualListIntent or
|
||||
LegacyImportManualListsIntent or
|
||||
LegacyCreateNamedPlaylistIntent or
|
||||
LegacySaveCurrentNamedPlaylistIntent or
|
||||
LegacySaveCurrentNamedPlaylistToIntent or
|
||||
LegacyDeleteSelectedNamedPlaylistIntent;
|
||||
|
||||
private static bool IsFixedSectionBatchMutationIntent(LegacyUiIntent intent) => intent is
|
||||
LegacySetManualNetSellCellIntent or
|
||||
LegacyAddManualViResultIntent or
|
||||
|
||||
Reference in New Issue
Block a user