Files
MBN_STOCK_WEBVIEW/MainWindow.LegacyComparisonImport.cs

308 lines
11 KiB
C#

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