feat(opcua): surgical RemoveEquipmentSubtree with notifier demotion (R2-07 T10)
This commit is contained in:
@@ -87,7 +87,7 @@
|
|||||||
{
|
{
|
||||||
"id": "T10",
|
"id": "T10",
|
||||||
"subject": "Phase 2: OtOpcUaNodeManager.RemoveEquipmentSubtree (descendants + notifier demotion) + SdkAddressSpaceSink forwards (high-risk)",
|
"subject": "Phase 2: OtOpcUaNodeManager.RemoveEquipmentSubtree (descendants + notifier demotion) + SdkAddressSpaceSink forwards (high-risk)",
|
||||||
"status": "pending",
|
"status": "completed",
|
||||||
"blockedBy": [
|
"blockedBy": [
|
||||||
"T8",
|
"T8",
|
||||||
"T9"
|
"T9"
|
||||||
|
|||||||
@@ -1847,7 +1847,73 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ISurgicalAddressSpaceSink.RemoveEquipmentSubtree"/>
|
/// <inheritdoc cref="ISurgicalAddressSpaceSink.RemoveEquipmentSubtree"/>
|
||||||
public bool RemoveEquipmentSubtree(string equipmentNodeId) => false;
|
public bool RemoveEquipmentSubtree(string equipmentNodeId)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(equipmentNodeId);
|
||||||
|
EnsureAddressSpaceCreated();
|
||||||
|
|
||||||
|
GeneralModelChangeEventState e;
|
||||||
|
lock (Lock)
|
||||||
|
{
|
||||||
|
// Unknown equipment id ⇒ map drift ⇒ false (caller rebuilds).
|
||||||
|
if (!_folders.ContainsKey(equipmentNodeId)) return false;
|
||||||
|
|
||||||
|
// Folder-scoped NodeIds mean every descendant's id is the equipment id itself or begins with
|
||||||
|
// "<equipmentId>/" (sub-folders, variables, condition nodes). Snapshot each map's in-scope keys
|
||||||
|
// first (ToList) so we don't mutate while enumerating.
|
||||||
|
var prefix = equipmentNodeId + "/";
|
||||||
|
bool InScope(string id) => id == equipmentNodeId || id.StartsWith(prefix, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
// Value variables (+ their historized-tagname registrations).
|
||||||
|
foreach (var id in _variables.Keys.Where(InScope).ToList())
|
||||||
|
{
|
||||||
|
if (_variables.TryRemove(id, out var v))
|
||||||
|
{
|
||||||
|
v.Parent?.RemoveChild(v);
|
||||||
|
PredefinedNodes?.Remove(v.NodeId);
|
||||||
|
}
|
||||||
|
_historizedTagnames.TryRemove(id, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part 9 condition nodes (+ their native flags).
|
||||||
|
foreach (var id in _alarmConditions.Keys.Where(InScope).ToList())
|
||||||
|
{
|
||||||
|
if (_alarmConditions.TryRemove(id, out var c))
|
||||||
|
{
|
||||||
|
c.Parent?.RemoveChild(c);
|
||||||
|
PredefinedNodes?.Remove(c.NodeId);
|
||||||
|
}
|
||||||
|
_nativeAlarmNodeIds.Remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifier demotion BEFORE dropping the folders: sever the Server↔folder HasNotifier ref for
|
||||||
|
// every promoted folder in the subtree (an equipment folder, or a sub-folder that hosted an
|
||||||
|
// alarm), else the removal leaks an orphaned root-notifier reference on the Server object.
|
||||||
|
// _notifierFolders is keyed by the folder's NodeId; match on its string identifier.
|
||||||
|
foreach (var kvp in _notifierFolders.Where(kv => InScope(kv.Key.Identifier?.ToString() ?? string.Empty)).ToList())
|
||||||
|
{
|
||||||
|
RemoveRootNotifier(kvp.Value);
|
||||||
|
_notifierFolders.Remove(kvp.Key);
|
||||||
|
}
|
||||||
|
// Drop the event-history source registrations for the subtree (keyed by the folder id string).
|
||||||
|
foreach (var src in _eventNotifierSources.Keys.Where(InScope).ToList())
|
||||||
|
_eventNotifierSources.TryRemove(src, out _);
|
||||||
|
|
||||||
|
// Sub-folders + the equipment folder itself (children already detached above).
|
||||||
|
foreach (var id in _folders.Keys.Where(InScope).ToList())
|
||||||
|
{
|
||||||
|
if (_folders.TryRemove(id, out var f))
|
||||||
|
{
|
||||||
|
f.Parent?.RemoveChild(f);
|
||||||
|
PredefinedNodes?.Remove(f.NodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e = BuildNodesRemovedModelChange(equipmentNodeId);
|
||||||
|
}
|
||||||
|
ReportModelChangeOutsideLock(e, equipmentNodeId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private FolderState ResolveParentFolder(string? parentNodeId)
|
private FolderState ResolveParentFolder(string? parentNodeId)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user