using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy; namespace ZB.MOM.WW.MxGateway.Client; /// /// One node in a lazy-loaded Galaxy browse tree. Holds the underlying /// and exposes 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. /// public sealed class LazyBrowseNode { private readonly GalaxyRepositoryClient _client; private readonly BrowseChildrenOptions _options; private readonly List _children = []; private readonly SemaphoreSlim _expandLock = new(1, 1); private bool _isExpanded; /// Initializes a new instance of . /// The repository client used to fetch children. /// The underlying Galaxy object for this node. /// True when the server reports the node has at least one matching descendant. /// Options controlling child browse behavior. internal LazyBrowseNode( GalaxyRepositoryClient client, GalaxyObject @object, bool hasChildrenHint, BrowseChildrenOptions options) { _client = client; Object = @object; HasChildrenHint = hasChildrenHint; _options = options; } /// The underlying Galaxy object for this node. public GalaxyObject Object { get; } /// True when the server reports this node has at least one matching descendant. public bool HasChildrenHint { get; } /// Direct children loaded by ; empty until then. public IReadOnlyList Children => _children; /// True after the first call completes. public bool IsExpanded => _isExpanded; /// /// Fetches direct children from the gateway and populates . /// Idempotent: subsequent calls are no-ops. /// /// /// Thread-safe: concurrent callers see exactly one fetch; subsequent callers /// (after the first completes) return immediately. /// /// Token to observe for cancellation. /// A task that represents the asynchronous operation. public async Task ExpandAsync(CancellationToken cancellationToken = default) { if (_isExpanded) { return; } await _expandLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { if (_isExpanded) { return; } string pageToken = string.Empty; HashSet 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; } finally { _expandLock.Release(); } } }