feat(dotnet): add galaxy-browse CLI (§4.6); chore: verify version subcommand (§4.4)
This commit is contained in:
@@ -244,6 +244,19 @@ foreach (LazyBrowseNode root in roots)
|
||||
and is safe under concurrent callers. To refresh after a Galaxy redeploy, call
|
||||
`BrowseAsync` again from the root.
|
||||
|
||||
The CLI counterpart is `galaxy-browse`. Without `--parent` it walks the root
|
||||
objects and eagerly expands `--depth` further levels into an indented tree; with
|
||||
`--parent <gobject-id>` it fetches exactly one level of children for that object
|
||||
(`--depth` is ignored there). Filter flags map onto `BrowseChildrenOptions`:
|
||||
`--category-ids` and `--template-contains` are comma-separated lists,
|
||||
`--tag-name-glob` / `--alarm-bearing-only` / `--historized-only` are scalar, and
|
||||
`--include-attributes` overrides the server default for attribute population.
|
||||
|
||||
```powershell
|
||||
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-browse --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --depth 1
|
||||
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- galaxy-browse --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --parent 42 --json
|
||||
```
|
||||
|
||||
### Watching deploy events
|
||||
|
||||
`WatchDeployEventsAsync` opens the `WatchDeployEvents` server-streaming RPC. The
|
||||
|
||||
@@ -105,4 +105,16 @@ public interface IMxGatewayCliClient : IAsyncDisposable
|
||||
IAsyncEnumerable<DeployEvent> GalaxyWatchDeployEventsAsync(
|
||||
WatchDeployEventsRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches one page of direct children of a Galaxy parent (or the root
|
||||
/// objects when the parent selector is unset), the primitive that backs the
|
||||
/// lazy-browse helper.
|
||||
/// </summary>
|
||||
/// <param name="request">The browse-children request.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>The browse-children reply.</returns>
|
||||
Task<BrowseChildrenReply> GalaxyBrowseChildrenAsync(
|
||||
BrowseChildrenRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,14 @@ internal sealed class MxGatewayCliClientAdapter : IMxGatewayCliClient
|
||||
return _galaxyClient.Value.WatchDeployEventsRawAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<BrowseChildrenReply> GalaxyBrowseChildrenAsync(
|
||||
BrowseChildrenRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return _galaxyClient.Value.BrowseChildrenRawAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
|
||||
@@ -144,6 +144,8 @@ public static class MxGatewayClientCli
|
||||
.ConfigureAwait(false),
|
||||
"galaxy-discover" => await GalaxyDiscoverAsync(arguments, client, standardOutput, cancellation.Token)
|
||||
.ConfigureAwait(false),
|
||||
"galaxy-browse" => await GalaxyBrowseAsync(arguments, client, standardOutput, standardError, cancellation.Token)
|
||||
.ConfigureAwait(false),
|
||||
"galaxy-watch" => await GalaxyWatchAsync(arguments, client, standardOutput, cancellation.Token)
|
||||
.ConfigureAwait(false),
|
||||
_ => WriteUnknownCommand(command, standardError),
|
||||
@@ -1607,6 +1609,270 @@ public static class MxGatewayClientCli
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-request page size for the galaxy-browse single-level walks. Mirrors
|
||||
/// the library's <c>BrowseChildrenPageSize</c> so the CLI and the
|
||||
/// lazy-browse helper page identically.
|
||||
/// </summary>
|
||||
private const int BrowseChildrenCliPageSize = 500;
|
||||
|
||||
/// <summary>
|
||||
/// Drives the lazy-browse Galaxy surface from the CLI. Without
|
||||
/// <c>--parent</c> it walks the root objects and eagerly expands
|
||||
/// <c>--depth</c> further levels (each level reuses the same
|
||||
/// <see cref="BrowseChildrenOptions"/>, like the library helper). With
|
||||
/// <c>--parent</c> it fetches exactly one level of children for that
|
||||
/// gobject id via a parent-scoped BrowseChildren request; <c>--depth</c>
|
||||
/// is not meaningful there and a warning is emitted if combined, mirroring
|
||||
/// the Go/Rust CLIs.
|
||||
/// </summary>
|
||||
private static async Task<int> GalaxyBrowseAsync(
|
||||
CliArguments arguments,
|
||||
IMxGatewayCliClient client,
|
||||
TextWriter output,
|
||||
TextWriter standardError,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
BrowseChildrenOptions options = ParseBrowseChildrenOptions(arguments);
|
||||
bool json = arguments.HasFlag("json");
|
||||
int parent = arguments.GetInt32("parent", -1);
|
||||
int depth = arguments.GetInt32("depth", 0);
|
||||
|
||||
// A specific parent → one level of children via the parent-scoped RPC.
|
||||
if (parent >= 0)
|
||||
{
|
||||
if (depth > 0)
|
||||
{
|
||||
standardError.WriteLine("warning: --depth is ignored when --parent is specified.");
|
||||
}
|
||||
|
||||
IReadOnlyList<GalaxyObject> children = await BrowseOneLevelAsync(
|
||||
client,
|
||||
options,
|
||||
parent,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
output.WriteLine(JsonSerializer.Serialize(
|
||||
new
|
||||
{
|
||||
command = "galaxy-browse",
|
||||
parentId = parent,
|
||||
children = children.Select(GalaxyObjectToJsonElement).ToArray(),
|
||||
},
|
||||
JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.WriteLine(children.Count.ToString(CultureInfo.InvariantCulture));
|
||||
foreach (GalaxyObject child in children)
|
||||
{
|
||||
output.WriteLine(FormatGalaxyObject(child, level: 0, hasChildrenHint: null));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// No parent → walk the root objects, eagerly expanding --depth levels.
|
||||
IReadOnlyList<BrowseTreeNode> roots = await BrowseTreeAsync(
|
||||
client,
|
||||
options,
|
||||
parentGobjectId: 0,
|
||||
remainingDepth: depth,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
output.WriteLine(JsonSerializer.Serialize(
|
||||
new
|
||||
{
|
||||
command = "galaxy-browse",
|
||||
nodes = roots.Select(BrowseTreeNodeToJson).ToArray(),
|
||||
},
|
||||
JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.WriteLine(roots.Count.ToString(CultureInfo.InvariantCulture));
|
||||
foreach (BrowseTreeNode node in roots)
|
||||
{
|
||||
WriteBrowseTreeNode(output, node, level: 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One node in the eagerly-expanded galaxy-browse tree: the Galaxy object,
|
||||
/// the server's has-children hint, and any children fetched up to the
|
||||
/// requested depth.
|
||||
/// </summary>
|
||||
private sealed record BrowseTreeNode(
|
||||
GalaxyObject Object,
|
||||
bool HasChildrenHint,
|
||||
IReadOnlyList<BrowseTreeNode> Children);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the direct children of <paramref name="parentGobjectId"/>
|
||||
/// (0 = root) and recursively expands <paramref name="remainingDepth"/>
|
||||
/// further levels. Paging is followed to completion at each level.
|
||||
/// </summary>
|
||||
private static async Task<IReadOnlyList<BrowseTreeNode>> BrowseTreeAsync(
|
||||
IMxGatewayCliClient client,
|
||||
BrowseChildrenOptions options,
|
||||
int parentGobjectId,
|
||||
int remainingDepth,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
List<BrowseTreeNode> nodes = [];
|
||||
string pageToken = string.Empty;
|
||||
HashSet<string> seenPageTokens = new(StringComparer.Ordinal);
|
||||
do
|
||||
{
|
||||
BrowseChildrenRequest request = BuildBrowseChildrenRequest(options, parentGobjectId, pageToken);
|
||||
BrowseChildrenReply reply = await client.GalaxyBrowseChildrenAsync(request, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
for (int i = 0; i < reply.Children.Count; i++)
|
||||
{
|
||||
GalaxyObject child = reply.Children[i];
|
||||
bool hint = i < reply.ChildHasChildren.Count && reply.ChildHasChildren[i];
|
||||
IReadOnlyList<BrowseTreeNode> grandChildren = remainingDepth > 0
|
||||
? await BrowseTreeAsync(client, options, child.GobjectId, remainingDepth - 1, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
: [];
|
||||
nodes.Add(new BrowseTreeNode(child, hint, grandChildren));
|
||||
}
|
||||
|
||||
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 nodes;
|
||||
}
|
||||
|
||||
/// <summary>Fetches exactly one level of children for a parent gobject id, paging to completion.</summary>
|
||||
private static async Task<IReadOnlyList<GalaxyObject>> BrowseOneLevelAsync(
|
||||
IMxGatewayCliClient client,
|
||||
BrowseChildrenOptions options,
|
||||
int parentGobjectId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
List<GalaxyObject> children = [];
|
||||
string pageToken = string.Empty;
|
||||
HashSet<string> seenPageTokens = new(StringComparer.Ordinal);
|
||||
do
|
||||
{
|
||||
BrowseChildrenRequest request = BuildBrowseChildrenRequest(options, parentGobjectId, pageToken);
|
||||
BrowseChildrenReply reply = await client.GalaxyBrowseChildrenAsync(request, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
children.AddRange(reply.Children);
|
||||
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 children;
|
||||
}
|
||||
|
||||
private static BrowseChildrenOptions ParseBrowseChildrenOptions(CliArguments arguments)
|
||||
{
|
||||
return new BrowseChildrenOptions
|
||||
{
|
||||
CategoryIds = ParseOptionalInt32List(arguments.GetOptional("category-ids")),
|
||||
TemplateChainContains = ParseOptionalStringList(arguments.GetOptional("template-contains")),
|
||||
TagNameGlob = arguments.GetOptional("tag-name-glob"),
|
||||
AlarmBearingOnly = arguments.HasFlag("alarm-bearing-only"),
|
||||
HistorizedOnly = arguments.HasFlag("historized-only"),
|
||||
// Tri-state: only override the server default when the flag is present.
|
||||
IncludeAttributes = arguments.HasFlag("include-attributes") ? true : null,
|
||||
};
|
||||
}
|
||||
|
||||
private static BrowseChildrenRequest BuildBrowseChildrenRequest(
|
||||
BrowseChildrenOptions options,
|
||||
int parentGobjectId,
|
||||
string pageToken)
|
||||
{
|
||||
BrowseChildrenRequest request = new()
|
||||
{
|
||||
PageSize = BrowseChildrenCliPageSize,
|
||||
PageToken = pageToken,
|
||||
ParentGobjectId = parentGobjectId,
|
||||
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;
|
||||
}
|
||||
|
||||
private static void WriteBrowseTreeNode(TextWriter output, BrowseTreeNode node, int level)
|
||||
{
|
||||
output.WriteLine(FormatGalaxyObject(node.Object, level, node.HasChildrenHint));
|
||||
foreach (BrowseTreeNode child in node.Children)
|
||||
{
|
||||
WriteBrowseTreeNode(output, child, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatGalaxyObject(GalaxyObject galaxyObject, int level, bool? hasChildrenHint)
|
||||
{
|
||||
string indent = new(' ', level * 2);
|
||||
string suffix = hasChildrenHint is null
|
||||
? $"(attrs={galaxyObject.Attributes.Count})"
|
||||
: $"(attrs={galaxyObject.Attributes.Count}, hasChildren={hasChildrenHint.Value})";
|
||||
return $"{indent}{galaxyObject.GobjectId}\t{galaxyObject.TagName}\t{galaxyObject.BrowseName}\t{suffix}";
|
||||
}
|
||||
|
||||
private static object BrowseTreeNodeToJson(BrowseTreeNode node)
|
||||
{
|
||||
return new
|
||||
{
|
||||
@object = GalaxyObjectToJsonElement(node.Object),
|
||||
hasChildren = node.HasChildrenHint,
|
||||
children = node.Children.Select(BrowseTreeNodeToJson).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
private static JsonElement GalaxyObjectToJsonElement(GalaxyObject galaxyObject)
|
||||
{
|
||||
return JsonDocument.Parse(ProtobufJsonFormatter.Format(galaxyObject)).RootElement.Clone();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<int> ParseOptionalInt32List(string? value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? [] : ParseInt32List(value);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> ParseOptionalStringList(string? value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? [] : ParseStringList(value);
|
||||
}
|
||||
|
||||
private static async Task<int> GalaxyWatchAsync(
|
||||
CliArguments arguments,
|
||||
IMxGatewayCliClient client,
|
||||
@@ -1736,6 +2002,7 @@ public static class MxGatewayClientCli
|
||||
or "galaxy-test-connection"
|
||||
or "galaxy-last-deploy"
|
||||
or "galaxy-discover"
|
||||
or "galaxy-browse"
|
||||
or "galaxy-watch";
|
||||
}
|
||||
|
||||
@@ -1797,6 +2064,7 @@ public static class MxGatewayClientCli
|
||||
writer.WriteLine("mxgw-dotnet galaxy-test-connection [--json]");
|
||||
writer.WriteLine("mxgw-dotnet galaxy-last-deploy [--json]");
|
||||
writer.WriteLine("mxgw-dotnet galaxy-discover [--json]");
|
||||
writer.WriteLine("mxgw-dotnet galaxy-browse [--parent <gobject-id>] [--depth <n>] [--category-ids <n,n>] [--template-contains <s,s>] [--tag-name-glob <glob>] [--alarm-bearing-only] [--historized-only] [--include-attributes] [--json]");
|
||||
writer.WriteLine("mxgw-dotnet galaxy-watch [--last-seen-deploy-time <iso8601>] [--max-events <n>] [--json]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +360,146 @@ public sealed class MxGatewayClientCliTests
|
||||
Assert.Equal(string.Empty, error.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies galaxy-browse walks root objects and eagerly expands one further
|
||||
/// level when --depth 1 is passed, printing an indented tree.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsync_GalaxyBrowse_TextTreeExpandsToDepth()
|
||||
{
|
||||
using var output = new StringWriter();
|
||||
using var error = new StringWriter();
|
||||
FakeCliClient fakeClient = new();
|
||||
// Root level (parent 0): one area with a child hint.
|
||||
fakeClient.GalaxyBrowseChildrenReplies[0] = new Queue<BrowseChildrenReply>(
|
||||
[
|
||||
new BrowseChildrenReply
|
||||
{
|
||||
Children = { new GalaxyObject { GobjectId = 10, TagName = "Area_001", BrowseName = "Area" } },
|
||||
ChildHasChildren = { true },
|
||||
},
|
||||
]);
|
||||
// Children of gobject 10.
|
||||
fakeClient.GalaxyBrowseChildrenReplies[10] = new Queue<BrowseChildrenReply>(
|
||||
[
|
||||
new BrowseChildrenReply
|
||||
{
|
||||
Children = { new GalaxyObject { GobjectId = 20, TagName = "Tank_001", BrowseName = "Tank" } },
|
||||
},
|
||||
]);
|
||||
|
||||
int exitCode = await MxGatewayClientCli.RunAsync(
|
||||
[
|
||||
"galaxy-browse",
|
||||
"--endpoint",
|
||||
"http://localhost:5000",
|
||||
"--api-key",
|
||||
"test-api-key",
|
||||
"--depth",
|
||||
"1",
|
||||
],
|
||||
output,
|
||||
error,
|
||||
_ => fakeClient);
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
string text = output.ToString();
|
||||
Assert.Contains("Area_001", text);
|
||||
Assert.Contains("Tank_001", text);
|
||||
// Children are indented beneath their parent (two-space indent per level).
|
||||
Assert.Matches(@"\n \d+\tTank_001", text);
|
||||
// Root fetched with the parent oneof unset; child fetch used parent 10.
|
||||
Assert.Contains(
|
||||
fakeClient.GalaxyBrowseChildrenRequests,
|
||||
request => request.ParentCase == BrowseChildrenRequest.ParentOneofCase.ParentGobjectId
|
||||
&& request.ParentGobjectId == 10);
|
||||
Assert.Equal(string.Empty, error.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies galaxy-browse --json emits a nested JSON document and forwards
|
||||
/// the filter flags onto the BrowseChildren request.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsync_GalaxyBrowse_JsonForwardsFilters()
|
||||
{
|
||||
using var output = new StringWriter();
|
||||
using var error = new StringWriter();
|
||||
FakeCliClient fakeClient = new();
|
||||
fakeClient.GalaxyBrowseChildrenReplies[0] = new Queue<BrowseChildrenReply>(
|
||||
[
|
||||
new BrowseChildrenReply
|
||||
{
|
||||
Children = { new GalaxyObject { GobjectId = 10, TagName = "Area_001", BrowseName = "Area" } },
|
||||
},
|
||||
]);
|
||||
|
||||
int exitCode = await MxGatewayClientCli.RunAsync(
|
||||
[
|
||||
"galaxy-browse",
|
||||
"--endpoint",
|
||||
"http://localhost:5000",
|
||||
"--api-key",
|
||||
"test-api-key",
|
||||
"--tag-name-glob",
|
||||
"Area*",
|
||||
"--alarm-bearing-only",
|
||||
"--json",
|
||||
],
|
||||
output,
|
||||
error,
|
||||
_ => fakeClient);
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
using System.Text.Json.JsonDocument document = System.Text.Json.JsonDocument.Parse(output.ToString());
|
||||
Assert.Equal("galaxy-browse", document.RootElement.GetProperty("command").GetString());
|
||||
Assert.True(document.RootElement.GetProperty("nodes").GetArrayLength() >= 1);
|
||||
BrowseChildrenRequest request = Assert.Single(fakeClient.GalaxyBrowseChildrenRequests);
|
||||
Assert.Equal("Area*", request.TagNameGlob);
|
||||
Assert.True(request.AlarmBearingOnly);
|
||||
Assert.Equal(string.Empty, error.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies galaxy-browse --parent fetches exactly one level of children for
|
||||
/// the supplied gobject id via a parent-scoped BrowseChildren request.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsync_GalaxyBrowse_ParentFetchesSingleLevel()
|
||||
{
|
||||
using var output = new StringWriter();
|
||||
using var error = new StringWriter();
|
||||
FakeCliClient fakeClient = new();
|
||||
fakeClient.GalaxyBrowseChildrenReplies[10] = new Queue<BrowseChildrenReply>(
|
||||
[
|
||||
new BrowseChildrenReply
|
||||
{
|
||||
Children = { new GalaxyObject { GobjectId = 20, TagName = "Tank_001", BrowseName = "Tank" } },
|
||||
},
|
||||
]);
|
||||
|
||||
int exitCode = await MxGatewayClientCli.RunAsync(
|
||||
[
|
||||
"galaxy-browse",
|
||||
"--endpoint",
|
||||
"http://localhost:5000",
|
||||
"--api-key",
|
||||
"test-api-key",
|
||||
"--parent",
|
||||
"10",
|
||||
],
|
||||
output,
|
||||
error,
|
||||
_ => fakeClient);
|
||||
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.Contains("Tank_001", output.ToString());
|
||||
BrowseChildrenRequest request = Assert.Single(fakeClient.GalaxyBrowseChildrenRequests);
|
||||
Assert.Equal(BrowseChildrenRequest.ParentOneofCase.ParentGobjectId, request.ParentCase);
|
||||
Assert.Equal(10, request.ParentGobjectId);
|
||||
Assert.Equal(string.Empty, error.ToString());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that galaxy-watch command prints text output for deploy events.</summary>
|
||||
[Fact]
|
||||
public async Task RunAsync_GalaxyWatch_PrintsTextOutputForEvents()
|
||||
@@ -1051,5 +1191,33 @@ public sealed class MxGatewayClientCliTests
|
||||
yield return deployEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of received galaxy browse-children requests, in call order.</summary>
|
||||
public List<BrowseChildrenRequest> GalaxyBrowseChildrenRequests { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Per-parent browse-children replies keyed by <c>parent_gobject_id</c>
|
||||
/// (0 = root). Each parent's queue is dequeued in page order; an absent
|
||||
/// or exhausted queue yields an empty reply.
|
||||
/// </summary>
|
||||
public Dictionary<int, Queue<BrowseChildrenReply>> GalaxyBrowseChildrenReplies { get; } = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<BrowseChildrenReply> GalaxyBrowseChildrenAsync(
|
||||
BrowseChildrenRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
GalaxyBrowseChildrenRequests.Add(request);
|
||||
int parentId = request.ParentCase == BrowseChildrenRequest.ParentOneofCase.ParentGobjectId
|
||||
? request.ParentGobjectId
|
||||
: 0;
|
||||
if (GalaxyBrowseChildrenReplies.TryGetValue(parentId, out Queue<BrowseChildrenReply>? queue)
|
||||
&& queue.TryDequeue(out BrowseChildrenReply? reply))
|
||||
{
|
||||
return Task.FromResult(reply);
|
||||
}
|
||||
|
||||
return Task.FromResult(new BrowseChildrenReply());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user