308 lines
11 KiB
C#
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;
|
|
}
|
|
}
|