Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
3.8 KiB
C#
99 lines
3.8 KiB
C#
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Health;
|
|
|
|
/// <summary>
|
|
/// Pure-logic merger for the per-host connectivity entries that
|
|
/// <see cref="IHostConnectivityProbe"/> surfaces. Holds the current set of host
|
|
/// statuses (one synthetic top-level transport entry plus one entry per
|
|
/// <c>$WinPlatform</c>/<c>$AppEngine</c> probe) and emits
|
|
/// <see cref="OnHostStatusChanged"/> only when an upsert actually changes a host's
|
|
/// <see cref="HostState"/> — re-asserting the same state is a no-op so a stable
|
|
/// <c>ScanState=Running</c> burst doesn't fan out duplicate transitions.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This class owns the de-dup + diff logic that lived in
|
|
/// <c>GalaxyProxyDriver.OnHostConnectivityUpdate</c> in v1. The watcher
|
|
/// (<see cref="PerPlatformProbeWatcher"/>) and the transport forwarder
|
|
/// (<see cref="HostConnectivityForwarder"/>) both feed this aggregator; the
|
|
/// <see cref="GalaxyDriver"/> consumes <see cref="Snapshot"/> from
|
|
/// <c>IHostConnectivityProbe.GetHostStatuses()</c> and re-raises
|
|
/// <see cref="OnHostStatusChanged"/> as the driver-level event (wired in PR 4.W).
|
|
/// </remarks>
|
|
public sealed class HostStatusAggregator
|
|
{
|
|
private readonly object _lock = new();
|
|
private readonly Dictionary<string, HostConnectivityStatus> _byHost =
|
|
new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Fires when an <see cref="Update"/> call either introduces a new host or
|
|
/// transitions an existing host's <see cref="HostState"/>. Handlers run
|
|
/// outside the internal lock so they can safely re-enter the aggregator
|
|
/// (e.g. the driver re-broadcasting through <c>IHostConnectivityProbe</c>).
|
|
/// </summary>
|
|
public event EventHandler<HostStatusChangedEventArgs>? OnHostStatusChanged;
|
|
|
|
/// <summary>
|
|
/// Snapshot the current host set. Suitable as the body of
|
|
/// <c>IHostConnectivityProbe.GetHostStatuses()</c>.
|
|
/// </summary>
|
|
public IReadOnlyList<HostConnectivityStatus> Snapshot()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return [.. _byHost.Values];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Upsert the supplied status by <see cref="HostConnectivityStatus.HostName"/>.
|
|
/// Raises <see cref="OnHostStatusChanged"/> when the host is newly tracked
|
|
/// (previous state reported as <see cref="HostState.Unknown"/>) or when its
|
|
/// state value differs from the last cached entry. Re-asserting the same
|
|
/// state is silent.
|
|
/// </summary>
|
|
public void Update(HostConnectivityStatus status)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(status);
|
|
|
|
HostState previous;
|
|
bool changed;
|
|
lock (_lock)
|
|
{
|
|
if (_byHost.TryGetValue(status.HostName, out var existing))
|
|
{
|
|
previous = existing.State;
|
|
changed = existing.State != status.State;
|
|
}
|
|
else
|
|
{
|
|
previous = HostState.Unknown;
|
|
changed = true;
|
|
}
|
|
|
|
_byHost[status.HostName] = status;
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
OnHostStatusChanged?.Invoke(this,
|
|
new HostStatusChangedEventArgs(status.HostName, previous, status.State));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Drop a host entirely (e.g. after a redeploy removes a Platform). No event
|
|
/// is fired — observers only react to live transitions, not topology
|
|
/// reductions. Returns <c>true</c> when the host was tracked.
|
|
/// </summary>
|
|
public bool Remove(string hostName)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(hostName);
|
|
lock (_lock)
|
|
{
|
|
return _byHost.Remove(hostName);
|
|
}
|
|
}
|
|
}
|