refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)

Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
@@ -0,0 +1,177 @@
using Akka;
using Akka.Actor;
using Akka.Streams;
using Akka.Streams.Dsl;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Streaming;
/// <summary>
/// WP-23: Site-Wide Akka Stream — manages a broadcast stream for attribute value
/// and alarm state changes. Instance Actors publish events via fire-and-forget Tell.
/// A BroadcastHub fans events out to per-subscriber graphs, each filtered by
/// instance name and bounded by a drop-oldest buffer.
///
/// Filterable by instance name for debug view (WP-25).
/// Implements ISiteStreamSubscriber so the gRPC server can subscribe actors
/// to instance events without referencing SiteRuntime directly.
/// </summary>
public class SiteStreamManager : ISiteStreamSubscriber
{
private ActorSystem? _system;
private IMaterializer? _materializer;
private readonly int _bufferSize;
private readonly ILogger<SiteStreamManager> _logger;
private readonly object _lock = new();
private IActorRef? _sourceActor;
private Source<ISiteStreamEvent, NotUsed>? _hubSource;
private readonly Dictionary<string, SubscriptionInfo> _subscriptions = new();
/// <summary>Initializes the stream manager with configuration and logger; the Akka stream is not started until <see cref="Initialize"/> is called.</summary>
/// <param name="options">Site runtime options providing the stream buffer size.</param>
/// <param name="logger">Logger instance.</param>
public SiteStreamManager(
SiteRuntimeOptions options,
ILogger<SiteStreamManager> logger)
{
_bufferSize = options.StreamBufferSize;
_logger = logger;
}
/// <summary>
/// Initializes the broadcast stream. Must be called after ActorSystem is ready.
/// The ActorSystem is passed here rather than via the constructor so that
/// SiteStreamManager can be created by DI before the actor system exists.
/// </summary>
/// <param name="system">The running Akka <see cref="ActorSystem"/> used to materialize the broadcast stream.</param>
public void Initialize(ActorSystem system)
{
_system = system;
_materializer = _system.Materializer();
var (sourceActor, hubSource) = Source.ActorRef<ISiteStreamEvent>(
_bufferSize,
OverflowStrategy.DropHead)
.ToMaterialized(
BroadcastHub.Sink<ISiteStreamEvent>(bufferSize: 256),
Keep.Both)
.Run(_materializer);
_sourceActor = sourceActor;
_hubSource = hubSource;
_logger.LogInformation(
"SiteStreamManager initialized with publish buffer size {BufferSize}", _bufferSize);
}
/// <summary>
/// Publishes an attribute value change to the broadcast hub.
/// Fire-and-forget — never blocks the calling actor.
/// </summary>
/// <param name="changed">The attribute value change event to publish.</param>
public void PublishAttributeValueChanged(AttributeValueChanged changed)
{
_sourceActor?.Tell(changed);
}
/// <summary>
/// Publishes an alarm state change to the broadcast hub.
/// Fire-and-forget — never blocks the calling actor.
/// </summary>
/// <param name="changed">The alarm state change event to publish.</param>
public void PublishAlarmStateChanged(AlarmStateChanged changed)
{
_sourceActor?.Tell(changed);
}
/// <inheritdoc />
public string Subscribe(string instanceName, IActorRef subscriber)
{
if (_hubSource is null || _materializer is null)
throw new InvalidOperationException("SiteStreamManager.Initialize must be called before Subscribe");
var subscriptionId = Guid.NewGuid().ToString();
var capturedInstance = instanceName;
var capturedSubscriber = subscriber;
var killSwitch = _hubSource
.Where(ev => ev.InstanceUniqueName == capturedInstance)
.Buffer(_bufferSize, OverflowStrategy.DropHead)
.ViaMaterialized(KillSwitches.Single<ISiteStreamEvent>(), Keep.Right)
.To(Sink.ForEach<ISiteStreamEvent>(ev => capturedSubscriber.Tell(ev)))
.Run(_materializer);
lock (_lock)
{
_subscriptions[subscriptionId] = new SubscriptionInfo(
instanceName, subscriber, killSwitch, DateTimeOffset.UtcNow);
}
_logger.LogDebug(
"Subscriber {SubscriptionId} registered for instance {Instance}",
subscriptionId, instanceName);
return subscriptionId;
}
/// <summary>
/// WP-25: Unsubscribe from instance events. Shuts down the per-subscriber
/// stream graph via its KillSwitch.
/// </summary>
/// <param name="subscriptionId">The subscription ID returned by <see cref="Subscribe"/>.</param>
/// <returns><c>true</c> if the subscription was found and removed; <c>false</c> if it was already gone.</returns>
public bool Unsubscribe(string subscriptionId)
{
SubscriptionInfo? info;
lock (_lock)
{
if (!_subscriptions.Remove(subscriptionId, out info))
return false;
}
info.KillSwitch.Shutdown();
_logger.LogDebug("Subscriber {SubscriptionId} removed", subscriptionId);
return true;
}
/// <inheritdoc />
public void RemoveSubscriber(IActorRef subscriber)
{
List<SubscriptionInfo> toShutdown;
lock (_lock)
{
var matched = _subscriptions
.Where(kvp => kvp.Value.Subscriber.Equals(subscriber))
.ToList();
foreach (var kvp in matched)
_subscriptions.Remove(kvp.Key);
toShutdown = matched.Select(kvp => kvp.Value).ToList();
}
foreach (var info in toShutdown)
info.KillSwitch.Shutdown();
if (toShutdown.Count > 0)
{
_logger.LogDebug(
"Removed {Count} subscriptions for disconnected subscriber", toShutdown.Count);
}
}
/// <summary>
/// Returns the count of active subscriptions (for diagnostics/testing).
/// </summary>
public int SubscriptionCount
{
get { lock (_lock) { return _subscriptions.Count; } }
}
private record SubscriptionInfo(
string InstanceName,
IActorRef Subscriber,
IKillSwitch KillSwitch,
DateTimeOffset SubscribedAt);
}