Migrate remaining legacy operator workflows
This commit is contained in:
307
MainWindow.LegacyComparisonImport.cs
Normal file
307
MainWindow.LegacyComparisonImport.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
using System.Text.Json;
|
||||
using MBN_STOCK_WEBVIEW.Infrastructure;
|
||||
using MMoneyCoderSharp.Data;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW;
|
||||
|
||||
public sealed partial class MainWindow
|
||||
{
|
||||
private LegacyComparisonPairImportService? _legacyComparisonImportService;
|
||||
private CancellationTokenSource? _legacyComparisonImportCancellation;
|
||||
|
||||
private bool TryHandleLegacyComparisonImportRequest(
|
||||
string? requestType,
|
||||
JsonElement payload)
|
||||
{
|
||||
if (!string.Equals(
|
||||
requestType,
|
||||
"import-legacy-comparison-pairs",
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var requestId = string.Empty;
|
||||
var hasSafeRequestId = payload.ValueKind == JsonValueKind.Object &&
|
||||
TryGetSafeToken(
|
||||
payload,
|
||||
"requestId",
|
||||
MaximumPlayoutRequestIdLength,
|
||||
out requestId);
|
||||
if (!hasSafeRequestId || !HasOnlyProperties(payload, "requestId"))
|
||||
{
|
||||
PostLegacyComparisonImportError(
|
||||
hasSafeRequestId ? requestId : string.Empty,
|
||||
"INVALID_REQUEST",
|
||||
"원본 비교쌍 가져오기 요청 형식이 올바르지 않습니다.");
|
||||
return true;
|
||||
}
|
||||
|
||||
_ = HandleLegacyComparisonImportAsync(requestId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task HandleLegacyComparisonImportAsync(string requestId)
|
||||
{
|
||||
var runtime = _databaseRuntime;
|
||||
if (runtime is null)
|
||||
{
|
||||
PostLegacyComparisonImportError(
|
||||
requestId,
|
||||
"DATABASE_UNAVAILABLE",
|
||||
"현재 종목 마스터 DB를 사용할 수 없어 원본 비교쌍을 검증할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
LegacyComparisonPairImportService service;
|
||||
try
|
||||
{
|
||||
service = _legacyComparisonImportService ??=
|
||||
new LegacyComparisonPairImportService(
|
||||
runtime.Executor,
|
||||
TrustedLegacyComparisonFileSource.CreateDefault());
|
||||
}
|
||||
catch (LegacyComparisonImportException exception)
|
||||
{
|
||||
PostLegacyComparisonImportFailure(requestId, exception.Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
var cancellation = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
_lifetimeCancellation.Token);
|
||||
var previous = Interlocked.Exchange(
|
||||
ref _legacyComparisonImportCancellation,
|
||||
cancellation);
|
||||
CancelRequest(previous);
|
||||
|
||||
var gateEntered = false;
|
||||
try
|
||||
{
|
||||
if (Volatile.Read(ref _playoutCommandInFlight) != 0)
|
||||
{
|
||||
PostLegacyComparisonImportErrorIfCurrent(
|
||||
requestId,
|
||||
cancellation,
|
||||
"PLAYOUT_PRIORITY",
|
||||
"송출 명령이 우선 처리 중이므로 가져오기를 실행하지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
await _databaseActivityGate.WaitAsync(cancellation.Token);
|
||||
gateEntered = true;
|
||||
cancellation.Token.ThrowIfCancellationRequested();
|
||||
if (Volatile.Read(ref _playoutCommandInFlight) != 0)
|
||||
{
|
||||
PostLegacyComparisonImportErrorIfCurrent(
|
||||
requestId,
|
||||
cancellation,
|
||||
"PLAYOUT_PRIORITY",
|
||||
"송출 명령이 우선 처리 중이므로 가져오기를 실행하지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await service.ImportAsync(cancellation.Token);
|
||||
cancellation.Token.ThrowIfCancellationRequested();
|
||||
if (!IsCurrentLegacyComparisonImport(cancellation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PostMessage("legacy-comparison-import-results", new
|
||||
{
|
||||
requestId,
|
||||
result.SourceSha256,
|
||||
result.SourceRowCount,
|
||||
result.RetrievedAt,
|
||||
pairs = result.Pairs.Select(pair => new
|
||||
{
|
||||
pair.RowNumber,
|
||||
first = ToLegacyComparisonImportWireTarget(pair.First),
|
||||
second = ToLegacyComparisonImportWireTarget(pair.Second)
|
||||
})
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
|
||||
{
|
||||
// A newer explicit import, navigation, playout priority, or shutdown
|
||||
// owns the DB slot. Never retry or publish a stale result.
|
||||
PostPlayoutPriorityLegacyComparisonImportCancellationIfCurrent(
|
||||
requestId,
|
||||
cancellation);
|
||||
}
|
||||
catch (DatabaseOperationException) when (cancellation.IsCancellationRequested)
|
||||
{
|
||||
// Providers may surface a native exception after cancellation.
|
||||
PostPlayoutPriorityLegacyComparisonImportCancellationIfCurrent(
|
||||
requestId,
|
||||
cancellation);
|
||||
}
|
||||
catch (Exception) when (cancellation.IsCancellationRequested)
|
||||
{
|
||||
// Preserve correlation loss and shutdown semantics.
|
||||
PostPlayoutPriorityLegacyComparisonImportCancellationIfCurrent(
|
||||
requestId,
|
||||
cancellation);
|
||||
}
|
||||
catch (LegacyComparisonImportException exception)
|
||||
{
|
||||
if (IsCurrentLegacyComparisonImport(cancellation))
|
||||
{
|
||||
PostLegacyComparisonImportFailure(requestId, exception.Failure);
|
||||
}
|
||||
}
|
||||
catch (DatabaseOperationException)
|
||||
{
|
||||
if (IsCurrentLegacyComparisonImport(cancellation))
|
||||
{
|
||||
PostLegacyComparisonImportError(
|
||||
requestId,
|
||||
"DATABASE_UNAVAILABLE",
|
||||
"현재 종목 마스터 DB에서 원본 비교쌍을 검증하지 못했습니다.");
|
||||
_ = PostDatabaseStatusAsync(_lifetimeCancellation.Token);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (IsCurrentLegacyComparisonImport(cancellation))
|
||||
{
|
||||
PostLegacyComparisonImportError(
|
||||
requestId,
|
||||
"IMPORT_FAILED",
|
||||
"원본 비교쌍 가져오기를 안전하게 완료하지 못했습니다.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (gateEntered)
|
||||
{
|
||||
_databaseActivityGate.Release();
|
||||
}
|
||||
|
||||
Interlocked.CompareExchange(
|
||||
ref _legacyComparisonImportCancellation,
|
||||
null,
|
||||
cancellation);
|
||||
cancellation.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCurrentLegacyComparisonImport(CancellationTokenSource cancellation) =>
|
||||
!_lifetimeCancellation.IsCancellationRequested &&
|
||||
ReferenceEquals(
|
||||
Volatile.Read(ref _legacyComparisonImportCancellation),
|
||||
cancellation);
|
||||
|
||||
private void PostLegacyComparisonImportErrorIfCurrent(
|
||||
string requestId,
|
||||
CancellationTokenSource cancellation,
|
||||
string code,
|
||||
string message)
|
||||
{
|
||||
if (IsCurrentLegacyComparisonImport(cancellation))
|
||||
{
|
||||
PostLegacyComparisonImportError(requestId, code, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void PostPlayoutPriorityLegacyComparisonImportCancellationIfCurrent(
|
||||
string requestId,
|
||||
CancellationTokenSource cancellation)
|
||||
{
|
||||
if (Volatile.Read(ref _playoutCommandInFlight) != 0 &&
|
||||
IsCurrentLegacyComparisonImport(cancellation))
|
||||
{
|
||||
PostLegacyComparisonImportError(
|
||||
requestId,
|
||||
"PLAYOUT_PRIORITY",
|
||||
"송출 명령을 우선 처리하기 위해 원본 비교쌍 가져오기를 취소했습니다. 자동 재시도하지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
private void PostLegacyComparisonImportFailure(
|
||||
string requestId,
|
||||
LegacyComparisonImportFailure failure)
|
||||
{
|
||||
var (code, message) = failure switch
|
||||
{
|
||||
LegacyComparisonImportFailure.SourceUnavailable => (
|
||||
"SOURCE_UNAVAILABLE",
|
||||
"고정된 원본 위치의 종목비교.dat를 안전하게 읽을 수 없습니다."),
|
||||
LegacyComparisonImportFailure.SourceInvalid => (
|
||||
"SOURCE_INVALID",
|
||||
"원본 종목비교.dat가 정확한 CP949 9필드 형식과 일치하지 않습니다."),
|
||||
LegacyComparisonImportFailure.IdentityNotFound => (
|
||||
"IDENTITY_NOT_FOUND",
|
||||
"원본 비교쌍의 종목 또는 시장 식별자를 현재 DB에서 정확히 확인하지 못했습니다."),
|
||||
LegacyComparisonImportFailure.IdentityAmbiguous => (
|
||||
"IDENTITY_AMBIGUOUS",
|
||||
"원본 비교쌍의 종목 식별자가 현재 DB에서 둘 이상 확인되어 가져오기를 중단했습니다."),
|
||||
LegacyComparisonImportFailure.DatabaseDataInvalid => (
|
||||
"DATABASE_DATA_INVALID",
|
||||
"현재 종목 마스터 조회 결과가 엄격한 식별자 형식과 일치하지 않습니다."),
|
||||
_ => (
|
||||
"IMPORT_FAILED",
|
||||
"원본 비교쌍 가져오기를 안전하게 완료하지 못했습니다.")
|
||||
};
|
||||
PostLegacyComparisonImportError(requestId, code, message);
|
||||
}
|
||||
|
||||
private void PostLegacyComparisonImportError(
|
||||
string requestId,
|
||||
string code,
|
||||
string message)
|
||||
{
|
||||
PostMessage("legacy-comparison-import-error", new
|
||||
{
|
||||
requestId,
|
||||
code,
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
private static object ToLegacyComparisonImportWireTarget(
|
||||
LegacyComparisonImportTarget target) => target.Kind switch
|
||||
{
|
||||
LegacyComparisonImportTargetKind.MarketTarget => new
|
||||
{
|
||||
kind = "market-target",
|
||||
target = target.MarketTarget
|
||||
},
|
||||
LegacyComparisonImportTargetKind.KrxStock => new
|
||||
{
|
||||
kind = "krx-stock",
|
||||
market = target.Market,
|
||||
stockName = target.StockName,
|
||||
stockCode = target.StockCode
|
||||
},
|
||||
LegacyComparisonImportTargetKind.NxtStock => new
|
||||
{
|
||||
kind = "nxt-stock",
|
||||
market = target.Market,
|
||||
stockName = target.StockName,
|
||||
stockCode = target.StockCode
|
||||
},
|
||||
LegacyComparisonImportTargetKind.WorldStock => new
|
||||
{
|
||||
kind = "world-stock",
|
||||
inputName = target.InputName,
|
||||
symbol = target.Symbol,
|
||||
nation = target.Nation
|
||||
},
|
||||
_ => throw new InvalidOperationException(
|
||||
"The comparison import target kind is invalid.")
|
||||
};
|
||||
|
||||
private void InvalidateLegacyComparisonImportRequest()
|
||||
{
|
||||
CancelRequest(Interlocked.Exchange(
|
||||
ref _legacyComparisonImportCancellation,
|
||||
null));
|
||||
}
|
||||
|
||||
private void ShutdownLegacyComparisonImportRuntime()
|
||||
{
|
||||
InvalidateLegacyComparisonImportRequest();
|
||||
_legacyComparisonImportService = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user