namespace MoShiro.Backend.Application; /// 某一时刻的「线路」开关:自动连接的总开关,加上每一对(拨号方,监听方)的手动设置。 public sealed class LinkSnapshot { private readonly Dictionary<(string Dialer, string Listener), bool> _overrides; public LinkSnapshot(bool auto, Dictionary<(string Dialer, string Listener), bool> overrides) { Auto = auto; _overrides = overrides; } /// 自动连接:开着时,所有节点自动拨其他监听节点;关着时,只有手动开启的才连。两个监听节点默认双向互连。 public bool Auto { get; } /// 手动设置:true 强制连、false 强制不连、null 没设置(跟随自动连接)。 public bool? OverrideOf(string dialerId, string listenerId) => _overrides.TryGetValue((dialerId, listenerId), out bool value) ? value : null; /// 这一对最终连不连:手动设置优先,没有就看自动连接。 public bool IsLinked(AgentRecord dialer, AgentRecord listener) => dialer.Id != listener.Id && (OverrideOf(dialer.Id.Value, listener.Id.Value) ?? Auto); } /// /// 哪些(拨号方,监听方)要连成线路。由面板决定、拨号方主动拨出:自动连接开着时,所有节点拨其他监听节点(默认), /// 关着时只连手动选中的;每一对还可以手动强制开或关,每个方向的线路独立设置。手动设置存在设置表里(link.<拨号方>.<监听方>,值 1 或 0,空表示没设置)。 /// 判断结果分别用在拨号方的拨号目标、监听方允许的拨号方(peers)和线路目录里;改动之后要调用 重新下发。 /// public sealed class LinkService { private const string Prefix = "link."; private readonly ISettingsStore _settings; public LinkService(ISettingsStore settings) { _settings = settings; } public async ValueTask SnapshotAsync(CancellationToken ct) { IReadOnlyDictionary all = await _settings.GetAllAsync(ct); var overrides = new Dictionary<(string, string), bool>(); foreach ((string key, string value) in all) { string[] parts = key.Split('.'); if (parts.Length == 3 && parts[0] + "." == Prefix && value is "1" or "0") { overrides[(parts[1], parts[2])] = value == "1"; } } return new LinkSnapshot(all.GetValueOrDefault(SettingsService.AutoLink) != "false", overrides); } /// 手动设置一对线路:true 强制连、false 强制不连、null 取消手动设置。 public ValueTask SetAsync(string dialerId, string listenerId, bool? enabled, CancellationToken ct) => _settings.SetAsync($"{Prefix}{dialerId}.{listenerId}", enabled is null ? "" : enabled.Value ? "1" : "0", ct); /// 节点被删除:清掉所有涉及它的手动设置。 public async ValueTask ForgetNodeAsync(string nodeId, CancellationToken ct) { foreach (string key in (await _settings.GetAllAsync(ct)).Keys.Where(k => k.StartsWith(Prefix, StringComparison.Ordinal) && k.Split('.').Skip(1).Contains(nodeId))) { await _settings.SetAsync(key, "", ct); } } }