* Fix

* Build all contexts

* Notify validator result

* Fix
This commit is contained in:
DHR60 2026-03-01 05:47:48 +00:00 committed by GitHub
parent a3ff31088e
commit 6b4ae5a386
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 144 additions and 93 deletions

View file

@ -5,6 +5,39 @@ public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeVali
public bool Success => ValidatorResult.Success; public bool Success => ValidatorResult.Success;
} }
/// <summary>
/// Holds the results of a full context build, including the main context and an optional
/// pre-socks context (e.g. for TUN protection or pre-socks chaining).
/// </summary>
public record CoreConfigContextBuilderAllResult(
CoreConfigContextBuilderResult MainResult,
CoreConfigContextBuilderResult? PreSocksResult)
{
/// <summary>True only when both the main result and (if present) the pre-socks result succeeded.</summary>
public bool Success => MainResult.Success && (PreSocksResult?.Success ?? true);
/// <summary>
/// Merges all errors and warnings from the main result and the optional pre-socks result
/// into a single <see cref="NodeValidatorResult"/> for unified notification.
/// </summary>
public NodeValidatorResult CombinedValidatorResult => new(
[.. MainResult.ValidatorResult.Errors, .. PreSocksResult?.ValidatorResult.Errors ?? []],
[.. MainResult.ValidatorResult.Warnings, .. PreSocksResult?.ValidatorResult.Warnings ?? []]);
/// <summary>
/// The main context with TunProtectSsPort/ProxyRelaySsPort and ProtectDomainList merged in
/// from the pre-socks result (if any). Pass this to the core runner.
/// </summary>
public CoreConfigContext ResolvedMainContext => PreSocksResult is not null
? MainResult.Context with
{
TunProtectSsPort = PreSocksResult.Context.TunProtectSsPort,
ProxyRelaySsPort = PreSocksResult.Context.ProxyRelaySsPort,
ProtectDomainList = [.. MainResult.Context.ProtectDomainList ?? [], .. PreSocksResult.Context.ProtectDomainList ?? []],
}
: MainResult.Context;
}
public class CoreConfigContextBuilder public class CoreConfigContextBuilder
{ {
/// <summary> /// <summary>
@ -75,6 +108,79 @@ public class CoreConfigContextBuilder
return new CoreConfigContextBuilderResult(context, validatorResult); return new CoreConfigContextBuilderResult(context, validatorResult);
} }
/// <summary>
/// Builds the main <see cref="CoreConfigContext"/> for <paramref name="node"/> and, when
/// the main build succeeds, also builds the optional pre-socks context required for TUN
/// protection or pre-socks proxy chaining.
/// </summary>
public static async Task<CoreConfigContextBuilderAllResult> BuildAll(Config config, ProfileItem node)
{
var mainResult = await Build(config, node);
if (!mainResult.Success)
{
return new CoreConfigContextBuilderAllResult(mainResult, null);
}
var preResult = await BuildPreSocksIfNeeded(mainResult.Context);
return new CoreConfigContextBuilderAllResult(mainResult, preResult);
}
/// <summary>
/// Determines whether a pre-socks context is required for <paramref name="nodeContext"/>
/// and, if so, builds and returns it. Returns <c>null</c> when no pre-socks core is needed.
/// </summary>
private static async Task<CoreConfigContextBuilderResult?> BuildPreSocksIfNeeded(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var preSocksItem = ConfigHandler.GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
var preSocksResult = await Build(nodeContext.AppConfig, preSocksItem);
return preSocksResult with
{
Context = preSocksResult.Context with
{
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preSocksResult.Context.ProtectDomainList ?? []],
}
};
}
if (!nodeContext.IsTunEnabled
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}
var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preResult2 = await Build(nodeContext.AppConfig, preItem);
return preResult2 with
{
Context = preResult2.Context with
{
ProtectDomainList = [.. nodeContext.ProtectDomainList ?? [], .. preResult2.Context.ProtectDomainList ?? []],
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
}
};
}
/// <summary> /// <summary>
/// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain. /// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain.
/// Returns the effective (possibly replaced) node and the validation result. /// Returns the effective (possibly replaced) node and the validation result.

View file

@ -1321,47 +1321,6 @@ public static class ConfigHandler
return itemSocks; return itemSocks;
} }
public static CoreConfigContext? GetPreSocksCoreConfigContext(CoreConfigContext nodeContext)
{
var config = nodeContext.AppConfig;
var node = nodeContext.Node;
var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType);
var preSocksItem = GetPreSocksItem(config, node, coreType);
if (preSocksItem != null)
{
return nodeContext with { Node = preSocksItem, };
}
if ((!nodeContext.IsTunEnabled)
|| coreType != ECoreType.Xray
|| node.ConfigType == EConfigType.Custom)
{
return null;
}
var tunProtectSsPort = Utils.GetFreePort();
var proxyRelaySsPort = Utils.GetFreePort();
var preItem = new ProfileItem()
{
CoreType = ECoreType.sing_box,
ConfigType = EConfigType.Shadowsocks,
Address = Global.Loopback,
Port = proxyRelaySsPort,
Password = Global.None,
};
preItem.SetProtocolExtra(preItem.GetProtocolExtra() with
{
SsMethod = Global.None,
});
var preContext = nodeContext with
{
Node = preItem,
TunProtectSsPort = tunProtectSsPort,
ProxyRelaySsPort = proxyRelaySsPort,
};
return preContext;
}
/// <summary> /// <summary>
/// Remove servers with invalid test results (timeout) /// Remove servers with invalid test results (timeout)
/// Useful for cleaning up subscription lists /// Useful for cleaning up subscription lists

View file

@ -57,27 +57,19 @@ public class CoreManager
} }
} }
public async Task LoadCore(CoreConfigContext? context) /// <param name="mainContext">Resolved main context (with pre-socks ports already merged if applicable).</param>
/// <param name="preContext">Optional pre-socks context passed to <see cref="CoreStartPreService"/>.</param>
public async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{ {
if (context == null) if (mainContext == null)
{ {
await UpdateFunc(false, ResUI.CheckServerSettings); await UpdateFunc(false, ResUI.CheckServerSettings);
return; return;
} }
var contextMod = context; var node = mainContext.Node;
var node = contextMod.Node;
var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName);
var preContext = ConfigHandler.GetPreSocksCoreConfigContext(contextMod); var result = await CoreConfigHandler.GenerateClientConfig(mainContext, fileName);
if (preContext is not null)
{
contextMod = contextMod with
{
TunProtectSsPort = preContext.TunProtectSsPort,
ProxyRelaySsPort = preContext.ProxyRelaySsPort,
};
}
var result = await CoreConfigHandler.GenerateClientConfig(contextMod, fileName);
if (result.Success != true) if (result.Success != true)
{ {
await UpdateFunc(true, result.Msg); await UpdateFunc(true, result.Msg);
@ -96,7 +88,7 @@ public class CoreManager
await WindowsUtils.RemoveTunDevice(); await WindowsUtils.RemoveTunDevice();
} }
await CoreStart(contextMod); await CoreStart(mainContext);
await CoreStartPreService(preContext); await CoreStartPreService(preContext);
if (_processService != null) if (_processService != null)
{ {

View file

@ -38,4 +38,25 @@ public class NoticeManager
Enqueue(msg); Enqueue(msg);
SendMessage(msg); SendMessage(msg);
} }
/// <summary>
/// Sends each error and warning in <paramref name="validatorResult"/> to the message panel
/// and enqueues a summary snack notification (capped at 10 messages).
/// Returns <c>true</c> when there were any messages so the caller can decide on early-return
/// based on <see cref="NodeValidatorResult.Success"/>.
/// </summary>
public bool NotifyValidatorResult(NodeValidatorResult validatorResult)
{
var msgs = new List<string>([.. validatorResult.Errors, .. validatorResult.Warnings]);
if (msgs.Count == 0)
{
return false;
}
foreach (var msg in msgs)
{
SendMessage(msg);
}
Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
return true;
}
} }

View file

@ -546,24 +546,15 @@ public class MainWindowViewModel : MyReactiveObject
NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings); NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings);
return; return;
} }
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, profileItem); var allResult = await CoreConfigContextBuilder.BuildAll(_config, profileItem);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]); if (NoticeManager.Instance.NotifyValidatorResult(allResult.CombinedValidatorResult) && !allResult.Success)
if (msgs.Count > 0)
{ {
foreach (var msg in msgs) return;
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
} }
await Task.Run(async () => await Task.Run(async () =>
{ {
await LoadCore(context); await LoadCore(allResult.ResolvedMainContext, allResult.PreSocksResult?.Context);
await SysProxyHandler.UpdateSysProxy(_config, false); await SysProxyHandler.UpdateSysProxy(_config, false);
await Task.Delay(1000); await Task.Delay(1000);
}); });
@ -604,9 +595,9 @@ public class MainWindowViewModel : MyReactiveObject
RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled); RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled);
} }
private async Task LoadCore(CoreConfigContext? context) private async Task LoadCore(CoreConfigContext? mainContext, CoreConfigContext? preContext)
{ {
await CoreManager.Instance.LoadCore(context); await CoreManager.Instance.LoadCore(mainContext, preContext);
} }
#endregion core job #endregion core job

View file

@ -764,18 +764,9 @@ public class ProfilesViewModel : MyReactiveObject
} }
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]); if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
if (msgs.Count > 0)
{ {
foreach (var msg in msgs) return;
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
} }
if (blClipboard) if (blClipboard)
@ -804,18 +795,9 @@ public class ProfilesViewModel : MyReactiveObject
return; return;
} }
var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item);
var msgs = new List<string>([..validatorResult.Errors, ..validatorResult.Warnings]); if (NoticeManager.Instance.NotifyValidatorResult(validatorResult) && !validatorResult.Success)
if (msgs.Count > 0)
{ {
foreach (var msg in msgs) return;
{
NoticeManager.Instance.SendMessage(msg);
}
NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true));
if (!validatorResult.Success)
{
return;
}
} }
var result = await CoreConfigHandler.GenerateClientConfig(context, fileName); var result = await CoreConfigHandler.GenerateClientConfig(context, fileName);
if (result.Success != true) if (result.Success != true)