client/dotnet: LazyBrowseNode walker for lazy hierarchy browse
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
namespace ZB.MOM.WW.MxGateway.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Filters and shape options for <see cref="GalaxyRepositoryClient.BrowseAsync(BrowseChildrenOptions, System.Threading.CancellationToken)"/>.
|
||||
/// Mirror of <see cref="DiscoverHierarchyOptions"/> for the lazy-browse path.
|
||||
/// </summary>
|
||||
public sealed class BrowseChildrenOptions
|
||||
{
|
||||
/// <summary>Restrict to children whose Galaxy category is in this set.</summary>
|
||||
public IReadOnlyList<int> CategoryIds { get; init; } = [];
|
||||
|
||||
/// <summary>Restrict to children whose template chain contains any of these tokens.</summary>
|
||||
public IReadOnlyList<string> TemplateChainContains { get; init; } = [];
|
||||
|
||||
/// <summary>Optional glob-style filter on <c>tag_name</c>.</summary>
|
||||
public string? TagNameGlob { get; init; }
|
||||
|
||||
/// <summary>Whether to populate each <c>GalaxyObject.Attributes</c>. Null leaves the server default.</summary>
|
||||
public bool? IncludeAttributes { get; init; }
|
||||
|
||||
/// <summary>Restrict to children that bear at least one alarm attribute.</summary>
|
||||
public bool AlarmBearingOnly { get; init; }
|
||||
|
||||
/// <summary>Restrict to children that have at least one historized attribute.</summary>
|
||||
public bool HistorizedOnly { get; init; }
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace ZB.MOM.WW.MxGateway.Client;
|
||||
public sealed class GalaxyRepositoryClient : IAsyncDisposable
|
||||
{
|
||||
private const int DiscoverHierarchyPageSize = 5000;
|
||||
private const int BrowseChildrenPageSize = 500;
|
||||
|
||||
private readonly GrpcChannel? _channel;
|
||||
private readonly IGalaxyRepositoryClientTransport _transport;
|
||||
@@ -278,6 +279,89 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Returns root-level browse nodes (objects with no parent).</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The list of root <see cref="LazyBrowseNode"/> instances.</returns>
|
||||
public Task<IReadOnlyList<LazyBrowseNode>> BrowseAsync(CancellationToken cancellationToken = default)
|
||||
=> BrowseAsync(null, cancellationToken);
|
||||
|
||||
/// <summary>Returns root-level browse nodes filtered by the given options.</summary>
|
||||
/// <param name="options">Browse filter options. Null applies no filter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The list of root <see cref="LazyBrowseNode"/> instances.</returns>
|
||||
public async Task<IReadOnlyList<LazyBrowseNode>> BrowseAsync(
|
||||
BrowseChildrenOptions? options,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
BrowseChildrenOptions effective = options ?? new BrowseChildrenOptions();
|
||||
List<LazyBrowseNode> roots = [];
|
||||
string pageToken = string.Empty;
|
||||
HashSet<string> seenPageTokens = new(StringComparer.Ordinal);
|
||||
do
|
||||
{
|
||||
BrowseChildrenRequest request = BuildBrowseChildrenRequest(effective);
|
||||
request.PageToken = pageToken;
|
||||
BrowseChildrenReply reply = await BrowseChildrenRawAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
for (int i = 0; i < reply.Children.Count; i++)
|
||||
{
|
||||
bool hint = i < reply.ChildHasChildren.Count && reply.ChildHasChildren[i];
|
||||
roots.Add(new LazyBrowseNode(this, reply.Children[i], hint, effective));
|
||||
}
|
||||
|
||||
pageToken = reply.NextPageToken;
|
||||
if (!string.IsNullOrWhiteSpace(pageToken) && !seenPageTokens.Add(pageToken))
|
||||
{
|
||||
throw new MxGatewayException(
|
||||
$"Galaxy BrowseChildren returned a repeated page token '{pageToken}'.");
|
||||
}
|
||||
}
|
||||
while (!string.IsNullOrWhiteSpace(pageToken));
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
/// <summary>Issues a raw BrowseChildren RPC without result wrapping.</summary>
|
||||
/// <param name="request">The browse-children request.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The raw server reply.</returns>
|
||||
public Task<BrowseChildrenReply> BrowseChildrenRawAsync(
|
||||
BrowseChildrenRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
ThrowIfDisposed();
|
||||
|
||||
return ExecuteSafeUnaryAsync(
|
||||
token => _transport.BrowseChildrenAsync(request, CreateCallOptions(token)),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
internal static BrowseChildrenRequest BuildBrowseChildrenRequest(BrowseChildrenOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
BrowseChildrenRequest request = new()
|
||||
{
|
||||
PageSize = BrowseChildrenPageSize,
|
||||
AlarmBearingOnly = options.AlarmBearingOnly,
|
||||
HistorizedOnly = options.HistorizedOnly,
|
||||
};
|
||||
request.CategoryIds.Add(options.CategoryIds);
|
||||
request.TemplateChainContains.Add(options.TemplateChainContains);
|
||||
if (!string.IsNullOrWhiteSpace(options.TagNameGlob))
|
||||
{
|
||||
request.TagNameGlob = options.TagNameGlob;
|
||||
}
|
||||
|
||||
if (options.IncludeAttributes.HasValue)
|
||||
{
|
||||
request.IncludeAttributes = options.IncludeAttributes.Value;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to Galaxy deploy events. The server emits a bootstrap event with the
|
||||
/// current state on subscribe so callers can prime their cache, then emits one event
|
||||
|
||||
@@ -74,6 +74,23 @@ internal sealed class GrpcGalaxyRepositoryClientTransport(
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<BrowseChildrenReply> BrowseChildrenAsync(
|
||||
BrowseChildrenRequest request,
|
||||
CallOptions callOptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await RawClient.BrowseChildrenAsync(request, callOptions)
|
||||
.ResponseAsync
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (RpcException exception)
|
||||
{
|
||||
throw MapRpcException(exception, callOptions.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<DeployEvent> WatchDeployEventsAsync(
|
||||
WatchDeployEventsRequest request,
|
||||
|
||||
@@ -33,6 +33,13 @@ internal interface IGalaxyRepositoryClientTransport
|
||||
DiscoverHierarchyRequest request,
|
||||
CallOptions callOptions);
|
||||
|
||||
/// <summary>Returns direct children of a parent in the Galaxy hierarchy.</summary>
|
||||
/// <param name="request">The browse children request.</param>
|
||||
/// <param name="callOptions">gRPC call options (timeout, cancellation, etc.).</param>
|
||||
Task<BrowseChildrenReply> BrowseChildrenAsync(
|
||||
BrowseChildrenRequest request,
|
||||
CallOptions callOptions);
|
||||
|
||||
/// <summary>Watches for deployment events from the Galaxy Repository server.</summary>
|
||||
/// <param name="request">The watch deploy events request.</param>
|
||||
/// <param name="callOptions">gRPC call options (timeout, cancellation, etc.).</param>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Client;
|
||||
|
||||
/// <summary>
|
||||
/// One node in a lazy-loaded Galaxy browse tree. Holds the underlying
|
||||
/// <see cref="GalaxyObject"/> and exposes <see cref="ExpandAsync"/> to fetch
|
||||
/// its direct children on demand. Expansion is one-shot: a second call is a
|
||||
/// no-op. Pagination of large sibling sets is handled internally.
|
||||
/// </summary>
|
||||
public sealed class LazyBrowseNode
|
||||
{
|
||||
private readonly GalaxyRepositoryClient _client;
|
||||
private readonly BrowseChildrenOptions _options;
|
||||
private readonly List<LazyBrowseNode> _children = [];
|
||||
private bool _isExpanded;
|
||||
|
||||
internal LazyBrowseNode(
|
||||
GalaxyRepositoryClient client,
|
||||
GalaxyObject @object,
|
||||
bool hasChildrenHint,
|
||||
BrowseChildrenOptions options)
|
||||
{
|
||||
_client = client;
|
||||
Object = @object;
|
||||
HasChildrenHint = hasChildrenHint;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>The underlying Galaxy object for this node.</summary>
|
||||
public GalaxyObject Object { get; }
|
||||
|
||||
/// <summary>True when the server reports this node has at least one matching descendant.</summary>
|
||||
public bool HasChildrenHint { get; }
|
||||
|
||||
/// <summary>Direct children loaded by <see cref="ExpandAsync"/>; empty until then.</summary>
|
||||
public IReadOnlyList<LazyBrowseNode> Children => _children;
|
||||
|
||||
/// <summary>True after the first <see cref="ExpandAsync"/> call completes.</summary>
|
||||
public bool IsExpanded => _isExpanded;
|
||||
|
||||
/// <summary>
|
||||
/// Fetches direct children from the gateway and populates <see cref="Children"/>.
|
||||
/// Idempotent: subsequent calls are no-ops.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
public async Task ExpandAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_isExpanded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string pageToken = string.Empty;
|
||||
HashSet<string> seenPageTokens = new(StringComparer.Ordinal);
|
||||
do
|
||||
{
|
||||
BrowseChildrenRequest request = GalaxyRepositoryClient.BuildBrowseChildrenRequest(_options);
|
||||
request.ParentGobjectId = Object.GobjectId;
|
||||
request.PageToken = pageToken;
|
||||
|
||||
BrowseChildrenReply reply = await _client
|
||||
.BrowseChildrenRawAsync(request, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
for (int i = 0; i < reply.Children.Count; i++)
|
||||
{
|
||||
bool hint = i < reply.ChildHasChildren.Count && reply.ChildHasChildren[i];
|
||||
_children.Add(new LazyBrowseNode(_client, reply.Children[i], hint, _options));
|
||||
}
|
||||
|
||||
pageToken = reply.NextPageToken;
|
||||
if (!string.IsNullOrWhiteSpace(pageToken) && !seenPageTokens.Add(pageToken))
|
||||
{
|
||||
throw new MxGatewayException(
|
||||
$"Galaxy BrowseChildren returned a repeated page token '{pageToken}'.");
|
||||
}
|
||||
}
|
||||
while (!string.IsNullOrWhiteSpace(pageToken));
|
||||
|
||||
_isExpanded = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user