Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 48 additions & 10 deletions Services/FaultRecordTransferClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -95,7 +110,7 @@ public async Task<Iec61850FaultRecordDownloadResult> DownloadAsync(
try
{
EnsureReady();
return await _service!.DownloadAsync(
var result = await _service!.DownloadAsync(
record,
destinationRoot,
new Iec61850FaultRecordDownloadOptions
Expand All @@ -110,6 +125,21 @@ public async Task<Iec61850FaultRecordDownloadResult> 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
{
Expand All @@ -132,12 +162,20 @@ 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}.");
}
}

private static string ValueOrDash(string? value)
=> string.IsNullOrWhiteSpace(value) ? "-" : value.Trim();
}
Loading