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; } }