feat(browse): Wave-0 Batch C — CapturingAddressSpaceBuilder + CapturedTreeBrowseSession
Task 6: CapturedTree/CapturedNode model + CapturingAddressSpaceBuilder — an in-memory
IAddressSpaceBuilder that records a driver's DiscoverAsync stream (folder nesting,
leaf FullName as node id, alarm mark, node-cap truncation, no-op alarm sink).
Task 7: CapturedTreeBrowseSession — serves the captured snapshot through IBrowseSession
(Root/Expand/Attributes from memory, truncation marker, shared-immutable-tree dispose).
13 unit tests green.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Commons.Browsing;
|
||||
|
||||
/// <summary>
|
||||
/// Serves a captured discovery snapshot through the standard picker session interface.
|
||||
/// No I/O after open; the tree is point-in-time (picker offers Refresh = close + re-open).
|
||||
/// The tree may be shared by coalesced sessions — dispose drops only this session's
|
||||
/// reference. See design §4.3.
|
||||
/// </summary>
|
||||
public sealed class CapturedTreeBrowseSession(CapturedTree tree) : IBrowseSession
|
||||
{
|
||||
private CapturedTree? _tree = tree;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Token { get; } = Guid.NewGuid();
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime LastUsedUtc { get; private set; } = DateTime.UtcNow;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<BrowseNode>> RootAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var t = Tree();
|
||||
var nodes = t.Root.Children.Select(Map).ToList();
|
||||
if (t.Truncated)
|
||||
nodes.Add(new BrowseNode("__truncated__",
|
||||
"⚠ Results truncated — narrow the driver config or use manual entry",
|
||||
BrowseNodeKind.Folder, HasChildrenHint: false));
|
||||
LastUsedUtc = DateTime.UtcNow;
|
||||
return Task.FromResult<IReadOnlyList<BrowseNode>>(nodes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<BrowseNode>> ExpandAsync(string nodeId, CancellationToken cancellationToken)
|
||||
{
|
||||
var t = Tree();
|
||||
LastUsedUtc = DateTime.UtcNow;
|
||||
return Task.FromResult<IReadOnlyList<BrowseNode>>(
|
||||
t.ById.TryGetValue(nodeId, out var n) ? n.Children.Select(Map).ToList() : []);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<AttributeInfo>> AttributesAsync(string nodeId, CancellationToken cancellationToken)
|
||||
{
|
||||
var t = Tree();
|
||||
LastUsedUtc = DateTime.UtcNow;
|
||||
if (!t.ById.TryGetValue(nodeId, out var n))
|
||||
return Task.FromResult<IReadOnlyList<AttributeInfo>>([]);
|
||||
var attrs = new List<AttributeInfo>();
|
||||
if (n.Attribute is { } a)
|
||||
attrs.Add(new AttributeInfo(n.Name, a.DriverDataType.ToString(), a.IsArray,
|
||||
a.SecurityClass.ToString(), a.IsAlarm || n.IsAlarmMarked));
|
||||
attrs.AddRange(n.Properties.Select(p =>
|
||||
new AttributeInfo(p.Name, p.Type.ToString(), false, "", false)));
|
||||
return Task.FromResult<IReadOnlyList<AttributeInfo>>(attrs);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
_tree = null; // drop this session's reference only — tree may be shared (coalescing)
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private CapturedTree Tree() => _tree ?? throw new ObjectDisposedException(nameof(CapturedTreeBrowseSession));
|
||||
|
||||
private static BrowseNode Map(CapturedNode n) => n.IsLeaf
|
||||
? new BrowseNode(n.Id, n.DisplayName, BrowseNodeKind.Leaf, false)
|
||||
: new BrowseNode(n.Id, n.DisplayName, BrowseNodeKind.Folder, n.Children.Count > 0);
|
||||
}
|
||||
Reference in New Issue
Block a user