Add XML documentation across gateway, worker, and .NET client
This commit is contained in:
@@ -10,6 +10,9 @@ namespace MxGateway.Server.Galaxy;
|
||||
/// other subscribers or the publisher. When a subscriber's channel is full the oldest
|
||||
/// event is dropped — clients use the sequence field to detect gaps.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Publishes Galaxy deploy events to streaming gRPC subscribers via private bounded channels.
|
||||
/// </summary>
|
||||
public sealed class GalaxyDeployNotifier : IGalaxyDeployNotifier
|
||||
{
|
||||
private const int SubscriberQueueCapacity = 16;
|
||||
@@ -17,8 +20,12 @@ public sealed class GalaxyDeployNotifier : IGalaxyDeployNotifier
|
||||
private readonly ConcurrentDictionary<Guid, Channel<GalaxyDeployEventInfo>> _subscribers = new();
|
||||
private GalaxyDeployEventInfo? _latest;
|
||||
|
||||
/// <summary>
|
||||
/// The most recent deploy event, or null if none has been published.
|
||||
/// </summary>
|
||||
public GalaxyDeployEventInfo? Latest => Volatile.Read(ref _latest);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Publish(GalaxyDeployEventInfo info)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
@@ -33,6 +40,7 @@ public sealed class GalaxyDeployNotifier : IGalaxyDeployNotifier
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<GalaxyDeployEventInfo> SubscribeAsync(
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,11 @@ public sealed class GalaxyHierarchyCache : IGalaxyHierarchyCache
|
||||
private readonly SemaphoreSlim _refreshGate = new(1, 1);
|
||||
private GalaxyHierarchyCacheEntry _current = GalaxyHierarchyCacheEntry.Empty;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="GalaxyHierarchyCache"/> class.</summary>
|
||||
/// <param name="repository">Galaxy Repository client for SQL queries.</param>
|
||||
/// <param name="notifier">Galaxy deploy event notifier.</param>
|
||||
/// <param name="timeProvider">Provider for current time; defaults to system time.</param>
|
||||
/// <param name="logger">Optional logger for diagnostic output.</param>
|
||||
public GalaxyHierarchyCache(
|
||||
GalaxyRepository repository,
|
||||
IGalaxyDeployNotifier notifier,
|
||||
@@ -37,6 +42,7 @@ public sealed class GalaxyHierarchyCache : IGalaxyHierarchyCache
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>Gets the current Galaxy hierarchy cache entry with projected status.</summary>
|
||||
public GalaxyHierarchyCacheEntry Current
|
||||
{
|
||||
get
|
||||
@@ -47,6 +53,9 @@ public sealed class GalaxyHierarchyCache : IGalaxyHierarchyCache
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Refreshes the Galaxy hierarchy cache if the deploy time has advanced.</summary>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
/// <returns>Asynchronous task representing the refresh operation.</returns>
|
||||
public async Task RefreshAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _refreshGate.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -60,6 +69,9 @@ public sealed class GalaxyHierarchyCache : IGalaxyHierarchyCache
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Waits for the Galaxy hierarchy cache to complete its first load.</summary>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
/// <returns>Asynchronous task representing the wait operation.</returns>
|
||||
public Task WaitForFirstLoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return _firstLoad.Task.WaitAsync(cancellationToken);
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed record GalaxyHierarchyCacheEntry(
|
||||
int HistorizedAttributeCount,
|
||||
int AlarmAttributeCount)
|
||||
{
|
||||
/// <summary>Gets an empty Galaxy hierarchy cache entry.</summary>
|
||||
public static GalaxyHierarchyCacheEntry Empty { get; } = new(
|
||||
Status: GalaxyCacheStatus.Unknown,
|
||||
Sequence: 0,
|
||||
@@ -39,5 +40,6 @@ public sealed record GalaxyHierarchyCacheEntry(
|
||||
HistorizedAttributeCount: 0,
|
||||
AlarmAttributeCount: 0);
|
||||
|
||||
/// <summary>Gets a value indicating whether the cache entry contains usable data.</summary>
|
||||
public bool HasData => Status is GalaxyCacheStatus.Healthy or GalaxyCacheStatus.Stale;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,7 @@ using Microsoft.Extensions.Options;
|
||||
|
||||
namespace MxGateway.Server.Galaxy;
|
||||
|
||||
/// <summary>
|
||||
/// Periodically refreshes <see cref="IGalaxyHierarchyCache"/> off the request path. The
|
||||
/// interval comes from <see cref="GalaxyRepositoryOptions.DashboardRefreshIntervalSeconds"/>;
|
||||
/// each tick is cheap when the deploy timestamp is unchanged.
|
||||
/// </summary>
|
||||
/// <summary>Background service that periodically refreshes the Galaxy Repository hierarchy cache off the request path.</summary>
|
||||
public sealed class GalaxyHierarchyRefreshService(
|
||||
IGalaxyHierarchyCache cache,
|
||||
IOptions<GalaxyRepositoryOptions> options,
|
||||
@@ -17,6 +13,7 @@ public sealed class GalaxyHierarchyRefreshService(
|
||||
{
|
||||
private readonly TimeProvider _timeProvider = timeProvider ?? TimeProvider.System;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
TimeSpan interval = TimeSpan.FromSeconds(Math.Max(1, options.Value.DashboardRefreshIntervalSeconds));
|
||||
|
||||
@@ -6,30 +6,70 @@ namespace MxGateway.Server.Galaxy;
|
||||
/// </summary>
|
||||
public sealed class GalaxyHierarchyRow
|
||||
{
|
||||
/// <summary>Gets the Galaxy object identifier.</summary>
|
||||
public int GobjectId { get; init; }
|
||||
|
||||
/// <summary>Gets the tag name.</summary>
|
||||
public string TagName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the contained name.</summary>
|
||||
public string ContainedName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the browse name.</summary>
|
||||
public string BrowseName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the parent Galaxy object identifier.</summary>
|
||||
public int ParentGobjectId { get; init; }
|
||||
|
||||
/// <summary>Gets a value indicating whether this is an area.</summary>
|
||||
public bool IsArea { get; init; }
|
||||
|
||||
/// <summary>Gets the category identifier.</summary>
|
||||
public int CategoryId { get; init; }
|
||||
|
||||
/// <summary>Gets the Galaxy object identifier of the host.</summary>
|
||||
public int HostedByGobjectId { get; init; }
|
||||
|
||||
/// <summary>Gets the template derivation chain.</summary>
|
||||
public IReadOnlyList<string> TemplateChain { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>One row from <see cref="GalaxyRepository.GetAttributesAsync"/>.</summary>
|
||||
public sealed class GalaxyAttributeRow
|
||||
{
|
||||
/// <summary>Gets the Galaxy object identifier.</summary>
|
||||
public int GobjectId { get; init; }
|
||||
|
||||
/// <summary>Gets the tag name.</summary>
|
||||
public string TagName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the attribute name.</summary>
|
||||
public string AttributeName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the full tag reference.</summary>
|
||||
public string FullTagReference { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the MXAccess data type code.</summary>
|
||||
public int MxDataType { get; init; }
|
||||
|
||||
/// <summary>Gets the data type name.</summary>
|
||||
public string? DataTypeName { get; init; }
|
||||
|
||||
/// <summary>Gets a value indicating whether this is an array.</summary>
|
||||
public bool IsArray { get; init; }
|
||||
|
||||
/// <summary>Gets the array dimension, if applicable.</summary>
|
||||
public int? ArrayDimension { get; init; }
|
||||
|
||||
/// <summary>Gets the MXAccess attribute category code.</summary>
|
||||
public int MxAttributeCategory { get; init; }
|
||||
|
||||
/// <summary>Gets the security classification code.</summary>
|
||||
public int SecurityClassification { get; init; }
|
||||
|
||||
/// <summary>Gets a value indicating whether this is historized.</summary>
|
||||
public bool IsHistorized { get; init; }
|
||||
|
||||
/// <summary>Gets a value indicating whether this is an alarm.</summary>
|
||||
public bool IsAlarm { get; init; }
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace MxGateway.Server.Galaxy;
|
||||
/// </summary>
|
||||
public sealed class GalaxyRepository(GalaxyRepositoryOptions options)
|
||||
{
|
||||
/// <summary>Tests the connection to the Galaxy Repository database.</summary>
|
||||
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||
public async Task<bool> TestConnectionAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
@@ -24,6 +26,8 @@ public sealed class GalaxyRepository(GalaxyRepositoryOptions options)
|
||||
catch (InvalidOperationException) { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Retrieves the last deployment time from the Galaxy Repository.</summary>
|
||||
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||
public async Task<DateTime?> GetLastDeployTimeAsync(CancellationToken ct = default)
|
||||
{
|
||||
using SqlConnection conn = new(options.ConnectionString);
|
||||
@@ -34,6 +38,8 @@ public sealed class GalaxyRepository(GalaxyRepositoryOptions options)
|
||||
return result is DateTime dt ? dt : null;
|
||||
}
|
||||
|
||||
/// <summary>Retrieves the complete hierarchy of Galaxy objects from the repository.</summary>
|
||||
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||
public async Task<List<GalaxyHierarchyRow>> GetHierarchyAsync(CancellationToken ct = default)
|
||||
{
|
||||
List<GalaxyHierarchyRow> rows = new();
|
||||
@@ -70,6 +76,8 @@ public sealed class GalaxyRepository(GalaxyRepositoryOptions options)
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>Retrieves all attributes for Galaxy objects from the repository.</summary>
|
||||
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||
public async Task<List<GalaxyAttributeRow>> GetAttributesAsync(CancellationToken ct = default)
|
||||
{
|
||||
List<GalaxyAttributeRow> rows = new();
|
||||
|
||||
@@ -8,9 +8,11 @@ public sealed class GalaxyRepositoryOptions
|
||||
{
|
||||
public const string SectionName = "MxGateway:Galaxy";
|
||||
|
||||
/// <summary>The SQL Server connection string for the Galaxy Repository database.</summary>
|
||||
public string ConnectionString { get; init; } =
|
||||
"Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;";
|
||||
|
||||
/// <summary>The timeout in seconds for SQL commands executed against the Galaxy Repository.</summary>
|
||||
public int CommandTimeoutSeconds { get; init; } = 60;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace MxGateway.Server.Galaxy;
|
||||
|
||||
public static class GalaxyRepositoryServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>Registers Galaxy Repository services in the dependency injection container.</summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
public static IServiceCollection AddGalaxyRepository(this IServiceCollection services)
|
||||
{
|
||||
services
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
namespace MxGateway.Server.Galaxy;
|
||||
|
||||
/// <summary>Publishes Galaxy repository deploy events to subscribers.</summary>
|
||||
public interface IGalaxyDeployNotifier
|
||||
{
|
||||
/// <summary>The most recently published event, or <c>null</c> if no event has fired yet.</summary>
|
||||
/// <summary>The most recently published event, or null if no event has fired yet.</summary>
|
||||
GalaxyDeployEventInfo? Latest { get; }
|
||||
|
||||
/// <summary>Publishes a deploy event to all current subscribers and stores it as <see cref="Latest"/>.</summary>
|
||||
/// <summary>Publishes a deploy event to all current subscribers and stores it as Latest.</summary>
|
||||
/// <param name="info">The deploy event to publish.</param>
|
||||
void Publish(GalaxyDeployEventInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to deploy events. The async sequence yields events as they fire. If
|
||||
/// <see cref="Latest"/> is set, it is yielded first so subscribers can bootstrap their
|
||||
/// local cache without waiting for the next deploy. Pass a cancellation token to
|
||||
/// unsubscribe.
|
||||
/// </summary>
|
||||
/// <summary>Subscribes to deploy events. The sequence yields the latest event first (if available) then streams new events as they fire.</summary>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
/// <returns>Async enumerable of deploy events.</returns>
|
||||
IAsyncEnumerable<GalaxyDeployEventInfo> SubscribeAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace MxGateway.Server.Galaxy;
|
||||
|
||||
/// <summary>Cache for Galaxy Repository hierarchy data.</summary>
|
||||
public interface IGalaxyHierarchyCache
|
||||
{
|
||||
/// <summary>The latest cache entry. Status freshness is recomputed against the clock.</summary>
|
||||
@@ -11,6 +12,7 @@ public interface IGalaxyHierarchyCache
|
||||
/// attributes rowsets when the deploy time has changed since the last successful
|
||||
/// refresh.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
Task RefreshAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -18,5 +20,6 @@ public interface IGalaxyHierarchyCache
|
||||
/// gRPC handlers that want to serve from cache without returning Unavailable on the
|
||||
/// very first request after gateway start.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
Task WaitForFirstLoadAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user