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);
}
}
}