mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 11:14:51 +00:00
Add Mainland China proxy routing with independent rule workspaces. Preserve existing routing choices through equivalent preset mappings. BREAKING CHANGE: existing custom rules and enablement settings are not migrated. Recreate them in the appropriate preset.
76 lines
3.4 KiB
Swift
76 lines
3.4 KiB
Swift
import Foundation
|
|
|
|
// 运行状态收敛 module 的配置派生实现;异步计算前捕获事实,恢复后拒绝旧结果。
|
|
extension ProxyRuntimeController {
|
|
enum RulePreparationError: Error { case superseded }
|
|
|
|
private struct RulePreparationTicket {
|
|
let generation: Int
|
|
let flow: Int
|
|
let modeGeneration: Int
|
|
let settings: ProxyRuntimeSettings
|
|
let target: NodeID?
|
|
let ruleRevision: Int?
|
|
}
|
|
|
|
/// Capture mutable facts before leaving the UI actor. A stale task may not
|
|
/// mutate runtime state or write a contract, even when its calculation fails.
|
|
func runtimeDocument(
|
|
_ source: SslocalRuntimeDocument, for mode: ProxyMode
|
|
) async throws -> SslocalRuntimeDocument {
|
|
runtimePreparationGeneration += 1
|
|
let ticket = RulePreparationTicket(
|
|
generation: runtimePreparationGeneration, flow: flowGeneration,
|
|
modeGeneration: modeChangeGeneration, settings: settings, target: activeTargetID,
|
|
ruleRevision: mode == .rule ? ruleDocuments.revision : nil)
|
|
do {
|
|
let user = mode == .rule ? try ruleDocuments.load() : nil
|
|
let builtIn =
|
|
mode == .rule ? try await runtimeBuiltinRules(ticket.settings.rulePreset) : []
|
|
let aclURL = runtimeFileStore.aclFileURL
|
|
let result = await Task.detached(priority: .userInitiated) {
|
|
RuleRuntimeCompiler.compile(
|
|
.init(
|
|
source: source, mode: mode, defaultAction: ticket.settings.ruleDefaultAction,
|
|
document: user, builtIn: builtIn, aclURL: aclURL))
|
|
}.value
|
|
guard preparationIsCurrent(ticket) else { throw RulePreparationError.superseded }
|
|
let lan = ticket.settings.listen.lan
|
|
guard lan.isEnabled && lan.authenticationEnabled else { return result.authenticatingLAN(nil) }
|
|
guard let password = lan.password else {
|
|
throw ProxyListeningStoreError.credentialFailure(detail: String(localized: "局域网身份验证密码不可读"))
|
|
}
|
|
let auth = RuntimeListenerAuthentication(
|
|
username: lan.username, password: password,
|
|
directory: runtimeFileStore.fileURL.deletingLastPathComponent().standardizedFileURL.path)
|
|
guard auth.isValid else {
|
|
throw ProxyListeningStoreError.credentialFailure(detail: String(localized: "局域网身份验证凭据无效"))
|
|
}
|
|
return result.authenticatingLAN(auth)
|
|
} catch {
|
|
guard preparationIsCurrent(ticket) else { throw RulePreparationError.superseded }
|
|
throw error
|
|
}
|
|
}
|
|
|
|
private func preparationIsCurrent(_ ticket: RulePreparationTicket) -> Bool {
|
|
ticket.generation == runtimePreparationGeneration && ticket.flow == flowGeneration
|
|
&& ticket.modeGeneration == modeChangeGeneration
|
|
&& ticket.settings.listen == settings.listen
|
|
&& ticket.settings.preferredMode == settings.preferredMode
|
|
&& ticket.settings.rulePreset == settings.rulePreset
|
|
&& ticket.settings.agentEnabled == settings.agentEnabled
|
|
&& ticket.target == activeTargetID
|
|
&& (ticket.ruleRevision == nil || ticket.ruleRevision == ruleDocuments.revision)
|
|
}
|
|
|
|
private func runtimeBuiltinRules(_ preset: RulePreset) async throws -> [ProxyRule] {
|
|
var snapshots: [RuleSnapshot] = []
|
|
for source in preset.sources { snapshots.append(try await ruleSnapshots.load(source)) }
|
|
let captured = snapshots
|
|
return await Task.detached(priority: .userInitiated) {
|
|
captured.flatMap { preset.rules(from: $0) }
|
|
}.value
|
|
}
|
|
}
|