mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 18:23:50 +00:00
App/ 根目录原有 73 个散落源文件,已无法从目录读出任何分组信息。 按「目录即分层」重组为 Composition / Application / Presentation / UI / PlatformEffects / Resources 六个目录,UI 层再按子视图分目录(Workspace、 Home、Servers(含 Plugin)、Subscriptions、Rules、Settings、Diagnostics、 StatusMenu)。纯移动:类型名、模块名与 import 均未改动,App/ 根层不再有 散落文件。 配套改动: - project.yml:Core 测试镜像的 excludes 由裸文件名改为 `**/` 递归形式。 XcodeGen 的 exclude 不跨 `/`,裸名只匹配 App 根层;MainApp.swift 下移后 旧写法会静默失效,把 @main 组合根编进测试镜像。info.path 与 entitlements.path 指向 App/Resources/;并排除 `**/*.md`——App/ 下任何 非源码文件都会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle。 - SkeletonTests 的架构守卫由「默认扫描全部 App/ + 7 个豁免文件 + 1 个豁免 前缀 + 4 个豁免目录」改为正面圈定 App/UI/,豁免登记全部删除;新增运行时 适配器放进 Application/ 即自动落在扫描范围外,无需再登记。 - README 工程结构说明同步;根 .gitignore 的生成物路径随 Resources/ 归位。 验证:clean build 0 error;task test 1022 用例全通过(含两个架构守卫); task format 无改动(证明纯移动);task lint 与移动前同为 21 条内容型 warning;packaging-gate.sh ALL CHECKS PASSED,确认 plist/entitlements 归位 后签名与 entitlements 语义不变。 注意:迁移时须先删旧的生成物再 xcodegen generate,否则遗留的 App/Info.plist 会被当作普通资源扫进 Resources 阶段。
73 lines
3.0 KiB
Swift
73 lines
3.0 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: ProxySettings
|
|
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.ruleDefaultAction) : []
|
|
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 }
|
|
return result
|
|
} 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.ruleDefaultAction == settings.ruleDefaultAction
|
|
&& ticket.settings.agentEnabled == settings.agentEnabled
|
|
&& ticket.target == activeTargetID
|
|
&& (ticket.ruleRevision == nil || ticket.ruleRevision == ruleDocuments.revision)
|
|
}
|
|
|
|
private func runtimeBuiltinRules(_ action: RuleDefaultAction) async throws -> [ProxyRule] {
|
|
switch action {
|
|
case .proxyWhenUnmatched:
|
|
let geolocation = try await ruleSnapshots.load(.geolocationCN)
|
|
let china = try await ruleSnapshots.load(.chinaIPv4)
|
|
return await Task.detached(priority: .userInitiated) {
|
|
BuiltinRuleCatalog.chinaDirectRules(from: [geolocation, china])
|
|
}.value
|
|
case .directWhenUnmatched:
|
|
let snapshot = try await ruleSnapshots.load(.gfwlist)
|
|
return await Task.detached(priority: .userInitiated) {
|
|
BuiltinRuleCatalog.gfwlistRules(from: snapshot)
|
|
}.value
|
|
}
|
|
}
|
|
|
|
}
|