Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
62 lines
2.2 KiB
C#
62 lines
2.2 KiB
C#
using System.Collections.Concurrent;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests;
|
|
|
|
public sealed class FakeUpstream : ITagUpstreamSource
|
|
{
|
|
private readonly ConcurrentDictionary<string, DataValueSnapshot> _values = new(StringComparer.Ordinal);
|
|
private readonly ConcurrentDictionary<string, List<Action<string, DataValueSnapshot>>> _subs
|
|
= new(StringComparer.Ordinal);
|
|
public int ActiveSubscriptionCount { get; private set; }
|
|
|
|
public void Set(string path, object? value, uint statusCode = 0u)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
_values[path] = new DataValueSnapshot(value, statusCode, now, now);
|
|
}
|
|
|
|
public void Push(string path, object? value, uint statusCode = 0u)
|
|
{
|
|
Set(path, value, statusCode);
|
|
if (_subs.TryGetValue(path, out var list))
|
|
{
|
|
Action<string, DataValueSnapshot>[] snap;
|
|
lock (list) { snap = list.ToArray(); }
|
|
foreach (var obs in snap) obs(path, _values[path]);
|
|
}
|
|
}
|
|
|
|
public DataValueSnapshot ReadTag(string path)
|
|
=> _values.TryGetValue(path, out var v) ? v
|
|
: new DataValueSnapshot(null, 0x80340000u, null, DateTime.UtcNow);
|
|
|
|
public IDisposable SubscribeTag(string path, Action<string, DataValueSnapshot> observer)
|
|
{
|
|
var list = _subs.GetOrAdd(path, _ => []);
|
|
lock (list) { list.Add(observer); }
|
|
ActiveSubscriptionCount++;
|
|
return new Unsub(this, path, observer);
|
|
}
|
|
|
|
private sealed class Unsub : IDisposable
|
|
{
|
|
private readonly FakeUpstream _up;
|
|
private readonly string _path;
|
|
private readonly Action<string, DataValueSnapshot> _observer;
|
|
public Unsub(FakeUpstream up, string path, Action<string, DataValueSnapshot> observer)
|
|
{ _up = up; _path = path; _observer = observer; }
|
|
public void Dispose()
|
|
{
|
|
if (_up._subs.TryGetValue(_path, out var list))
|
|
{
|
|
lock (list)
|
|
{
|
|
if (list.Remove(_observer)) _up.ActiveSubscriptionCount--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|