From d82aab7955d4e7cba3380a63f96b5d8263587402 Mon Sep 17 00:00:00 2001 From: masarray Date: Mon, 20 Jul 2026 16:48:50 +0700 Subject: [PATCH 1/3] Keep dedicated MMS file-transfer session alive across UI operations --- Services/FaultRecordTransferClient.cs | 38 ++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/Services/FaultRecordTransferClient.cs b/Services/FaultRecordTransferClient.cs index 6fa93a4..27e1e12 100644 --- a/Services/FaultRecordTransferClient.cs +++ b/Services/FaultRecordTransferClient.cs @@ -15,8 +15,9 @@ public sealed class FaultRecordTransferClient : IAsyncDisposable private string _host = string.Empty; private int _port = 102; - public bool IsConnected => _session.IsMmsInitiated; - public string ConnectionState => _session.State.ToString(); + public bool IsConnected => IsSessionHealthy(); + public string ConnectionState => + $"{_session.State}; transport={_session.IsTransportConnected}; pump={_session.IsReceivePumpRunning}"; public async Task ConnectAsync( string host, @@ -30,21 +31,35 @@ public async Task ConnectAsync( await _operationGate.WaitAsync(cancellationToken).ConfigureAwait(false); try { - if (_session.IsMmsInitiated && + var sameEndpoint = _host.Equals(normalizedHost, StringComparison.OrdinalIgnoreCase) && - _port == normalizedPort) - { + _port == normalizedPort; + if (sameEndpoint && IsSessionHealthy()) return; - } - if (_session.IsTransportConnected) + // The connection operation token must not own the lifetime of a reusable MMS + // receive pump. A completed scan token is replaced before download; without this + // rebind the old cancellation would stop confirmed-service response routing while + // the association still appeared to be MmsInitiated. + if (_session.IsTransportConnected || + _session.IsMmsInitiated || + _session.IsReceivePumpRunning) + { await _session.DisposeAsync().ConfigureAwait(false); + } await _session.ConnectAsync( normalizedHost, normalizedPort, TimeSpan.FromSeconds(8), cancellationToken).ConfigureAwait(false); + await _session.RebindReceivePumpToSessionLifetimeAsync(cancellationToken).ConfigureAwait(false); + + if (!IsSessionHealthy()) + { + throw new InvalidOperationException( + $"The dedicated fault-record association is not operational after connect. {ConnectionState}."); + } _host = normalizedHost; _port = normalizedPort; @@ -132,12 +147,17 @@ public async ValueTask DisposeAsync() } } + private bool IsSessionHealthy() + => _session.IsMmsInitiated && + _session.IsTransportConnected && + _session.IsReceivePumpRunning; + private void EnsureReady() { - if (!_session.IsMmsInitiated || _service == null) + if (!IsSessionHealthy() || _service == null) { throw new InvalidOperationException( - $"The dedicated fault-record association is not ready. Current MMS state: {_session.State}."); + $"The dedicated fault-record association is not ready. {ConnectionState}."); } } } From d9ae909ee21a4fd91c1ce0f52c700164b715ab41 Mon Sep 17 00:00:00 2001 From: masarray Date: Mon, 20 Jul 2026 16:50:09 +0700 Subject: [PATCH 2/3] Expose actionable session diagnostics for file-transfer failures --- Services/FaultRecordTransferClient.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Services/FaultRecordTransferClient.cs b/Services/FaultRecordTransferClient.cs index 27e1e12..967c146 100644 --- a/Services/FaultRecordTransferClient.cs +++ b/Services/FaultRecordTransferClient.cs @@ -110,7 +110,7 @@ public async Task DownloadAsync( try { EnsureReady(); - return await _service!.DownloadAsync( + var result = await _service!.DownloadAsync( record, destinationRoot, new Iec61850FaultRecordDownloadOptions @@ -125,6 +125,21 @@ public async Task DownloadAsync( }, progress, cancellationToken).ConfigureAwait(false); + + if (result.IsSuccess) + return result; + + return new Iec61850FaultRecordDownloadResult + { + IsSuccess = false, + RecordId = result.RecordId, + DestinationDirectory = result.DestinationDirectory, + Files = result.Files, + BytesTransferred = result.BytesTransferred, + Message = + $"{result.Message} Dedicated session: {ConnectionState}. " + + $"Receive routing: {ValueOrDash(_session.LastReceiveRoutingSummary)}" + }; } finally { @@ -160,4 +175,7 @@ private void EnsureReady() $"The dedicated fault-record association is not ready. {ConnectionState}."); } } + + private static string ValueOrDash(string? value) + => string.IsNullOrWhiteSpace(value) ? "-" : value.Trim(); } From ec998ce48d62e9ad7d3618b7c55f46e962921527 Mon Sep 17 00:00:00 2001 From: masarray Date: Mon, 20 Jul 2026 16:56:09 +0700 Subject: [PATCH 3/3] Show detailed file-transfer failure diagnostics on record hover --- FaultRecordWindow.xaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FaultRecordWindow.xaml b/FaultRecordWindow.xaml index d9a0afd..07e1777 100644 --- a/FaultRecordWindow.xaml +++ b/FaultRecordWindow.xaml @@ -60,6 +60,8 @@