mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-10-29 05:33:45 +00:00
fix(ng2): review fixes for agent/system-proxy switch split
- setAgentEnabled(true) 在意图已开启且未达健康态(off/启动失败/服务 失败)时执行重收敛,成为失败态的显式重试入口;健康运行中不做无谓 的注销重拉 - StatusMenuModel.Summary 增加 systemProxyStateLabel 短标签,去掉 HomeView 对呈现文本的字符串剥离耦合 - 控制器头注释改为如实描述:launchd/契约动作序列在 ProxyRuntimePlan, 系统代理门禁收敛决策留在控制器(issue #60) - 新增重试路径测试
This commit is contained in:
@@ -76,7 +76,7 @@ private struct RuntimeControlCard: View {
|
||||
Toggle("设置系统代理", isOn: systemProxyBinding)
|
||||
.toggleStyle(.switch)
|
||||
Spacer(minLength: 0)
|
||||
Text(systemProxyShortStatus(summary))
|
||||
Text(summary.systemProxyStateLabel)
|
||||
.font(.callout.weight(.medium))
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
@@ -109,10 +109,6 @@ private struct RuntimeControlCard: View {
|
||||
set: { enabled in Task { await control.setSystemProxyEnabled(enabled) } })
|
||||
}
|
||||
|
||||
private func systemProxyShortStatus(_ summary: StatusMenuModel.Summary) -> String {
|
||||
summary.systemProxyStatus.replacingOccurrences(of: "系统代理:", with: "")
|
||||
}
|
||||
|
||||
private func systemProxyHint(_ summary: StatusMenuModel.Summary) -> String {
|
||||
switch control.snapshot.systemProxyApplication {
|
||||
case .idle:
|
||||
|
||||
@@ -13,9 +13,10 @@ struct SystemEndpointProbe: EndpointProbing {
|
||||
}
|
||||
|
||||
/// 代理运行时控制器(spec #21 D2/D5/D7/D9,issue #27/#28/#60):把激活状态机
|
||||
/// 的产出接到「GUI → LaunchAgent → wrapper → sslocal」链路。决策全部在纯域
|
||||
/// `ProxyRuntimePlan`,本类按序执行动作并负责健康呈现;GUI 退出不影响任何
|
||||
/// 一侧(agent 由 launchd 持有,构造上成立)。
|
||||
/// 的产出接到「GUI → LaunchAgent → wrapper → sslocal」链路。launchd/契约动作
|
||||
/// 序列在纯域 `ProxyRuntimePlan`,本类按序执行、负责健康呈现与系统代理门禁
|
||||
/// 收敛(issue #60:意图持久化先行,门禁与待应用策略是应用层决策);GUI 退出
|
||||
/// 不影响任何一侧(agent 由 launchd 持有,构造上成立)。
|
||||
///
|
||||
/// 两个用户意图相互独立(issue #60):agent 意图(`settings.agentEnabled`,
|
||||
/// 默认开启)驱动 LaunchAgent 注册与本地监听;系统代理意图
|
||||
@@ -230,9 +231,20 @@ final class ProxyRuntimeController: ObservableObject {
|
||||
}
|
||||
|
||||
/// Agent 开关(issue #60):先持久化意图(显式关闭在 GUI 重启后仍生效),
|
||||
/// 再收敛运行时。持久化失败保留现状并点名,不静默偏离持久化事实。
|
||||
/// 再收敛运行时。持久化失败保留现状并点名,不静默偏离持久化事实。意图已
|
||||
/// 开启时的开启命令仅在未达健康态时重收敛——它是启动失败/重置后的显式
|
||||
/// 重试入口;健康运行中不做无谓的注销重拉。
|
||||
func setAgentEnabled(_ enabled: Bool) async {
|
||||
guard enabled != settings.agentEnabled else { return }
|
||||
if enabled == settings.agentEnabled {
|
||||
guard enabled else { return }
|
||||
switch state {
|
||||
case .off, .launchFailed, .serviceFailed:
|
||||
await convergeAgent()
|
||||
case .starting, .running, .firewallBlocked, .requiresApproval:
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
var next = settings
|
||||
next.agentEnabled = enabled
|
||||
do {
|
||||
|
||||
@@ -20,6 +20,8 @@ enum StatusMenuModel {
|
||||
let systemProxyIntentEnabled: Bool
|
||||
/// 系统代理实际应用状态文本(与 agent 运行状态分开呈现)。
|
||||
let systemProxyStatus: String
|
||||
/// 同上但不带「系统代理:」前缀(首页等已有上下文的呈现面用)。
|
||||
let systemProxyStateLabel: String
|
||||
/// 系统代理写入/恢复失败的点名原因;非失败态为 nil。
|
||||
let systemProxyDetail: String?
|
||||
let modeLabel: String
|
||||
@@ -50,20 +52,20 @@ enum StatusMenuModel {
|
||||
let agentDetail =
|
||||
snapshot.runtime.failure.map { AppPresentation.message(for: $0) }
|
||||
?? snapshot.activationFailure.map { AppPresentation.message(for: $0) }
|
||||
let systemProxyStatus: String
|
||||
let stateLabel: String
|
||||
let systemProxyDetail: String?
|
||||
switch snapshot.systemProxyApplication {
|
||||
case .idle:
|
||||
systemProxyStatus = "系统代理:未接管"
|
||||
stateLabel = "未接管"
|
||||
systemProxyDetail = nil
|
||||
case .pending:
|
||||
systemProxyStatus = "系统代理:待应用"
|
||||
stateLabel = "待应用"
|
||||
systemProxyDetail = nil
|
||||
case .applied:
|
||||
systemProxyStatus = "系统代理:已应用"
|
||||
stateLabel = "已应用"
|
||||
systemProxyDetail = nil
|
||||
case .failed(let facts):
|
||||
systemProxyStatus = "系统代理:应用失败"
|
||||
stateLabel = "应用失败"
|
||||
systemProxyDetail = AppPresentation.message(for: RuntimeFailureFacts.systemProxy(facts))
|
||||
}
|
||||
return Summary(
|
||||
@@ -72,7 +74,8 @@ enum StatusMenuModel {
|
||||
status: status,
|
||||
detail: agentDetail,
|
||||
systemProxyIntentEnabled: snapshot.systemProxyIntentEnabled,
|
||||
systemProxyStatus: systemProxyStatus,
|
||||
systemProxyStatus: "系统代理:\(stateLabel)",
|
||||
systemProxyStateLabel: stateLabel,
|
||||
systemProxyDetail: systemProxyDetail,
|
||||
modeLabel: snapshot.proxyMode.label,
|
||||
targetPath: snapshot.activeTarget?.pathSummary)
|
||||
|
||||
@@ -692,3 +692,25 @@ extension ProxyRuntimeControllerTests {
|
||||
XCTAssertTrue(systemProxy.applied.isEmpty, "系统代理意图关闭,不写系统设置")
|
||||
}
|
||||
}
|
||||
|
||||
extension ProxyRuntimeControllerTests {
|
||||
/// 意图已开启时的开启命令是失败态的显式重试入口(issue #60):无需先关再开。
|
||||
func testAgentEnableCommandRetriesConvergenceWhileIntentAlreadyOn() async throws {
|
||||
let seeded = try makeSeededCatalog()
|
||||
let probe = ProxyRuntimeFixture.FakeProbe.refusing()
|
||||
let controller = makeController(probe: probe)
|
||||
|
||||
// 激活即按默认意图部署:端点不可达 → 启动失败(走满 15 秒健康窗)。
|
||||
try await controller.activate(seeded.server)
|
||||
if case .launchFailed = controller.state {
|
||||
} else {
|
||||
XCTFail("端点不可达应呈现启动失败,实际 \(controller.state)")
|
||||
}
|
||||
|
||||
probe.setOutcomes([.reachable])
|
||||
await controller.setAgentEnabled(true)
|
||||
|
||||
XCTAssertEqual(controller.state, .running, "意图已开启时开启命令重走收敛")
|
||||
XCTAssertEqual(controller.systemProxyState, .idle)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user