using MBN_STOCK_WEBVIEW.Core.Playout.Scenes; using MBN_STOCK_WEBVIEW.Infrastructure; using MBN_STOCK_WEBVIEW.LegacyApplication; namespace MBN_STOCK_WEBVIEW.LegacyParityApp; public sealed partial class MainWindow { private const string OperatorDataDirectoryEnvironmentVariable = "MBN_STOCK_OPERATOR_DATA_DIRECTORY"; private S5025TrustedManualFileStore? _manualNetSellStore; private ViTrustedManualFileStore? _manualViStore; private LegacyManualOperatorDataImporter? _manualDataImporter; private LegacyManualListsWorkflow CreateManualListsWorkflow( ILegacyStockLookup stockLookup) { ILegacyManualListStore store; try { if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable( OperatorDataDirectoryEnvironmentVariable))) { store = new UnavailableManualListStore( "UNSAFE_CONFIGURATION", "외부 경로 설정은 사용하지 않습니다. 앱 전용 로컬 저장소만 사용할 수 있습니다."); return new LegacyManualListsWorkflow(store, stockLookup); } var directory = TrustedManualDirectory.PreparePrivateWritableDirectory( Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MBN_STOCK_WEBVIEW", "OperatorData")); var inspection = LegacyManualOperatorDataRuntimeGuard.Inspect(directory); if (!inspection.IsReady) { var interrupted = inspection.State == LegacyManualOperatorDataRuntimeState.InterruptedImport; store = new UnavailableManualListStore( interrupted ? "IMPORT_INTERRUPTED" : "IMPORTED_STATE_INVALID", interrupted ? "이전 원본 데이터 가져오기가 중단되어 수동 파일을 열지 않았습니다." : "수동 데이터 가져오기 표식과 파일 상태가 일치하지 않습니다."); return new LegacyManualListsWorkflow(store, stockLookup); } _manualNetSellStore = new S5025TrustedManualFileStore(directory); _manualViStore = new ViTrustedManualFileStore(directory); _manualDataImporter = new LegacyManualOperatorDataImporter( directory, new FixedLegacyManualOperatorDataSource()); store = new TrustedManualListStore( _manualNetSellStore, _manualViStore, _manualDataImporter); } catch { _manualDataImporter?.Dispose(); _manualViStore?.Dispose(); _manualNetSellStore?.Dispose(); _manualDataImporter = null; _manualViStore = null; _manualNetSellStore = null; store = new UnavailableManualListStore( "INITIALIZATION_FAILED", "수동 순매도/VI 로컬 저장소를 초기화하지 못했습니다."); } return new LegacyManualListsWorkflow(store, stockLookup); } private void ShutdownManualListsRuntime() { var importer = Interlocked.Exchange(ref _manualDataImporter, null); var viStore = Interlocked.Exchange(ref _manualViStore, null); var netStore = Interlocked.Exchange(ref _manualNetSellStore, null); if (importer is null && viStore is null && netStore is null) { return; } _ = Task.Run(async () => { await _intentGate.WaitAsync().ConfigureAwait(false); try { importer?.Dispose(); viStore?.Dispose(); netStore?.Dispose(); } finally { _intentGate.Release(); } }); } private sealed class TrustedManualListStore : ILegacyManualListStore { private readonly S5025TrustedManualFileStore _netStore; private readonly ViTrustedManualFileStore _viStore; private readonly LegacyManualOperatorDataImporter _importer; public TrustedManualListStore( S5025TrustedManualFileStore netStore, ViTrustedManualFileStore viStore, LegacyManualOperatorDataImporter importer) { _netStore = netStore; _viStore = viStore; _importer = importer; } public LegacyManualListStoreAvailability Availability { get; } = new(true, "READY", null); public Task> ReadNetSellAsync( S5025ManualAudience audience, CancellationToken cancellationToken = default) => _netStore.ReadAsync(audience, cancellationToken); public Task WriteNetSellAsync( S5025ManualAudience audience, IReadOnlyList rows, CancellationToken cancellationToken = default) => _netStore.WriteAsync(audience, rows, cancellationToken); public async Task ReadViAsync( CancellationToken cancellationToken = default) { var snapshot = await _viStore.ReadSnapshotAsync(cancellationToken) .ConfigureAwait(false); return Map(snapshot); } public async Task WriteViAsync( IReadOnlyList items, CancellationToken cancellationToken = default) { var result = await _viStore.WriteAsync( items.Select(item => new ViTrustedManualItem(item.Code, item.Name)).ToArray(), cancellationToken).ConfigureAwait(false); return new LegacyManualViWriteReceipt(Map(result.Snapshot), result.Persisted); } public async Task ImportAsync( CancellationToken cancellationToken = default) { try { var result = await _importer.ImportAsync(cancellationToken) .ConfigureAwait(false); return new LegacyManualImportReceipt( result.MarkerVersion, result.SourceSha256, result.ImportedAt, result.NetSellRowCount, result.ViItemCount); } catch (LegacyManualOperatorImportException exception) { throw new LegacyManualImportStoreException( ImportFailureCode(exception.Failure), exception.OutcomeUnknown); } } private static LegacyManualViStoreSnapshot Map(ViTrustedManualSnapshot snapshot) => new( Array.AsReadOnly(snapshot.Items.Select(item => new LegacyManualViStoredItem(item.Code, item.Name)).ToArray()), snapshot.RowVersion); private static string ImportFailureCode(LegacyManualOperatorImportFailure failure) => failure switch { LegacyManualOperatorImportFailure.SourceUnavailable => "SOURCE_UNAVAILABLE", LegacyManualOperatorImportFailure.SourceInvalid => "SOURCE_INVALID", LegacyManualOperatorImportFailure.AlreadyImported => "ALREADY_IMPORTED", LegacyManualOperatorImportFailure.DestinationNotEmpty => "DESTINATION_NOT_EMPTY", LegacyManualOperatorImportFailure.InterruptedImport => "INTERRUPTED_IMPORT", LegacyManualOperatorImportFailure.ImportFailed => "IMPORT_FAILED", LegacyManualOperatorImportFailure.RollbackFailed => "ROLLBACK_FAILED", _ => "IMPORT_FAILED" }; } private sealed class UnavailableManualListStore : ILegacyManualListStore { private readonly string _message; public UnavailableManualListStore(string code, string message) { _message = message; Availability = new LegacyManualListStoreAvailability(false, code, message); } public LegacyManualListStoreAvailability Availability { get; } public Task> ReadNetSellAsync( S5025ManualAudience audience, CancellationToken cancellationToken = default) => Task.FromException>( new InvalidOperationException(_message)); public Task WriteNetSellAsync( S5025ManualAudience audience, IReadOnlyList rows, CancellationToken cancellationToken = default) => Task.FromException(new InvalidOperationException(_message)); public Task ReadViAsync( CancellationToken cancellationToken = default) => Task.FromException( new InvalidOperationException(_message)); public Task WriteViAsync( IReadOnlyList items, CancellationToken cancellationToken = default) => Task.FromException( new InvalidOperationException(_message)); public Task ImportAsync( CancellationToken cancellationToken = default) => Task.FromException( new LegacyManualImportStoreException("UNAVAILABLE", false)); } }