mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 11:14:51 +00:00
113 lines
4.8 KiB
Swift
113 lines
4.8 KiB
Swift
import Foundation
|
|
|
|
// MARK: - 设置变更与目录提交同步
|
|
|
|
extension ProxyRuntimeController {
|
|
/// A published mapping invalidates prepared documents before asynchronous convergence.
|
|
func pluginMappingsDidPublish() {
|
|
runtimePreparationGeneration += 1
|
|
}
|
|
|
|
/// 目录提交协调器的生产适配入口:以刚提交的内存快照重展开,
|
|
/// 不回读磁盘(磁盘仍是重启与跨进程恢复的权威来源)。agent 意图开启时
|
|
/// 原子更新运行时;目标失效 → 清除目标但 agent 继续监听。返回结构化收敛
|
|
/// 结果,健康检查耗时属于本调用的异步收敛阶段,不改变「目录已提交」的事实。
|
|
/// 「有效值未变的提交不做运行时收敛」由收敛脊柱 apply 层的 plan 空判定
|
|
/// 承担(`convergencePlanIsEmpty`);派生被更新的收敛取代时按重校验呈现。
|
|
func catalogDidCommit(snapshot catalog: ConfigurationCatalog) async -> RuntimeSyncOutcome {
|
|
guard catalogSnapshotReader.isCatalogAvailable else { return .failed(failure: nil) }
|
|
switch reexpand(in: catalog) {
|
|
case .deployed(let configuration):
|
|
guard settings.agentEnabled else {
|
|
return .revalidated
|
|
}
|
|
let result = await deploy(configuration.document)
|
|
if case .superseded = result {
|
|
return .revalidated
|
|
}
|
|
return Self.syncOutcome(
|
|
agentState: state, systemProxyState: systemProxyState,
|
|
skippedServers: configuration.skippedServers)
|
|
case .clearedAndStopped(let failure):
|
|
await handleCleared(failure)
|
|
return .clearedAndStopped(failure)
|
|
case nil:
|
|
guard settings.agentEnabled else {
|
|
return .revalidated
|
|
}
|
|
// 无活动目标:已在以空列表监听则不重走健康门(目录提交不该让状态闪回
|
|
// starting);否则补齐空监听。
|
|
if state != .off, runtimeObservation?.hasServers == false {
|
|
return Self.syncOutcome(
|
|
agentState: state, systemProxyState: systemProxyState, skippedServers: [])
|
|
}
|
|
await deployListeningWithoutTarget()
|
|
return Self.syncOutcome(
|
|
agentState: state, systemProxyState: systemProxyState, skippedServers: [])
|
|
}
|
|
}
|
|
|
|
/// 部署后的控制器状态 → 结构化收敛结果:健康通过(含防火墙受阻的运行态)
|
|
/// 视为已收敛;系统代理写入失败携带点名细节,待应用不算目录收敛失败。
|
|
private static func syncOutcome(
|
|
agentState: AgentRunState, systemProxyState: SystemProxyControlState,
|
|
skippedServers: [SkippedServer]
|
|
) -> RuntimeSyncOutcome {
|
|
if case .failed(let facts) = systemProxyState {
|
|
return .failed(failure: .systemProxy(facts))
|
|
}
|
|
switch agentState {
|
|
case .running, .firewallBlocked:
|
|
return .converged(skippedServers: skippedServers)
|
|
case .launchFailed(let facts):
|
|
return .failed(failure: .launch(facts))
|
|
case .serviceFailed(let facts):
|
|
return .failed(failure: .service(facts))
|
|
case .requiresApproval, .off, .starting:
|
|
return .failed(failure: nil)
|
|
}
|
|
}
|
|
|
|
/// 保存监听配置后更新内存并收敛;文件失败不发布新值。
|
|
func updateListening(_ proposed: ProxyListeningConfiguration) async throws {
|
|
guard !settingsUnreadable else { throw ProxyListeningStoreError.encryptedFile(.notLoaded) }
|
|
try listeningStore.save(proposed)
|
|
settings.listen = proposed
|
|
settingsUnreadable = false
|
|
guard state != .off else { return }
|
|
await convergeToReexpanded(catalogSnapshotReader.catalogSnapshot)
|
|
}
|
|
|
|
/// 系统代理例外是独立偏好;更新后沿用当前运行配置派生与系统代理应用路径。
|
|
func updateProxyExceptions(_ value: String) async {
|
|
controlPreferences.proxyExceptions = value
|
|
settings.proxyExceptions = value
|
|
guard state != .off else { return }
|
|
await convergeToReexpanded(catalogSnapshotReader.catalogSnapshot)
|
|
}
|
|
|
|
/// Applies the post-import 2.0 runtime boundary without touching
|
|
/// SystemConfiguration. Importing data leaves the user's system proxy
|
|
/// dictionary untouched; any currently running 2.0 runtime is stopped and
|
|
/// the existing 2.0 target/settings are reloaded.
|
|
func legacyImportDidCommit() async {
|
|
cancelFirewallObservation()
|
|
flowGeneration += 1
|
|
_ = await execute(.stop, document: nil)
|
|
state = .off
|
|
runtimeObservation = nil
|
|
skippedServers = []
|
|
lastActivationFailure = nil
|
|
// 导入边界不触碰 SystemConfiguration(既有不变量),系统代理状态面同
|
|
// 样不动:它呈现的是系统设置的真实作用,不由导入改写。
|
|
|
|
if let restored = try? listeningStore.load() {
|
|
settings.listen = restored
|
|
settingsUnreadable = false
|
|
}
|
|
let persistedTarget = try? activationFileStore.loadActiveTargetID()
|
|
machine = ActivationStateMachine(activeTargetID: persistedTarget ?? nil)
|
|
activeTargetID = machine.activeTargetID
|
|
}
|
|
}
|