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 阶段。
105 lines
4.1 KiB
Swift
105 lines
4.1 KiB
Swift
import Foundation
|
||
|
||
// MARK: - 只读事实投影与静态缝
|
||
|
||
extension ProxyRuntimeController {
|
||
var isActiveTargetPresent: Bool { machine.activeTargetID != nil }
|
||
|
||
/// Agent 开关意图(持久化事实;开关 UI 的绑定来源,issue #60)。
|
||
var agentIntentEnabled: Bool { settings.agentEnabled }
|
||
|
||
/// 系统代理开关意图(持久化事实;开关 UI 的绑定来源,issue #60)。
|
||
var systemProxyIntentEnabled: Bool { settings.systemProxyEnabled }
|
||
|
||
/// 系统代理观察机的呈现事实门面:存储与发布在 `SystemProxyObserver`,
|
||
/// 此处只读投影供既有测试与诊断面沿用原名。
|
||
var systemProxyState: SystemProxyApplicationFacts { systemProxyObserver.systemProxyState }
|
||
var systemProxyApprovalRequired: Bool { systemProxyObserver.systemProxyApprovalRequired }
|
||
var systemProxyInspection: SystemProxyInspectionFacts {
|
||
systemProxyObserver.systemProxyInspection
|
||
}
|
||
|
||
/// Desired system proxy configuration:当前保存设置要求的代理值(ADR-0022)。
|
||
/// 观察机经 init 闭包读取同一事实源。
|
||
var desiredSystemProxyConfiguration: SystemProxyConfiguration? {
|
||
guard let document = lastDocument else { return nil }
|
||
return try? proxyMode.systemProxyConfiguration(
|
||
for: document, exceptions: settings.proxyExceptionList)
|
||
}
|
||
|
||
/// Stable app-facing projection for status-menu presentation. The menu does
|
||
/// not depend on this controller's nested state representation.
|
||
var runtimeFacts: ProxyRuntimeFacts {
|
||
let facts = ProxyRuntimeFacts(state: state)
|
||
return ProxyRuntimeFacts(
|
||
status: facts.status, isOn: facts.isOn,
|
||
failure: facts.failure
|
||
?? (proxyMode == .rule && settings.agentEnabled ? ruleApplicationFailure : nil))
|
||
}
|
||
|
||
static var defaultFirewallExecutableURLs: [URL] {
|
||
let bundle = Bundle.main.bundleURL
|
||
return [
|
||
bundle.appendingPathComponent("Contents/MacOS/ShadowsocksX-NG2Agent"),
|
||
bundle.appendingPathComponent("Contents/Helpers/sslocal"),
|
||
]
|
||
}
|
||
|
||
/// 诊断只读面(issue #34):当前监听设置;监听地址在导出中只以回环/非回环
|
||
/// 两态呈现(D7)。
|
||
var listenSettings: SslocalListenSettings { settings.listen }
|
||
|
||
/// 当前已部署 runtime 的完整监听身份。已停止或启动失败时没有有效的
|
||
/// runtime 例外,不能拿最后一次设置快照冒充仍在监听。
|
||
var effectiveRuntimeListenFacts: RuntimeListenFacts? {
|
||
let isListening: Bool
|
||
switch state {
|
||
case .running, .firewallBlocked:
|
||
isListening = true
|
||
case .off, .starting, .launchFailed, .requiresApproval, .serviceFailed:
|
||
isListening = false
|
||
}
|
||
guard isListening, let lastDocument else { return nil }
|
||
return RuntimeListenFacts(document: lastDocument)
|
||
}
|
||
|
||
/// PID of the sslocal child that owns the active runtime document. A process
|
||
/// name alone is not sufficient to treat an occupied listener as replaceable.
|
||
var effectiveRuntimeListenerProcessID: Int32? {
|
||
guard effectiveRuntimeListenFacts != nil,
|
||
let lastDocument,
|
||
let receipt = runtimeFileStore.readRuntimeReceipt(),
|
||
receipt.contractSHA256 == lastDocument.deploymentSHA256,
|
||
processIsAlive(receipt.processID)
|
||
else { return nil }
|
||
return receipt.processID
|
||
}
|
||
|
||
/// 运行时契约的脱敏摘要(数量与协议元数据,D5);契约缺失或无效返回 nil。
|
||
/// 诊断导出不读契约内容,只携带此摘要。
|
||
func runtimeDocumentSummary() -> String? {
|
||
runtimeFileStore.loadDocument().map { Redactor.documentSummary($0) }
|
||
}
|
||
}
|
||
|
||
extension ProxyRuntimeController.AgentRunState {
|
||
/// Agent 运行状态的「在跑」投影(状态摘要与诊断口径):启动失败等未运行
|
||
/// 态视为未跑。
|
||
var isOn: Bool {
|
||
switch self {
|
||
case .off, .launchFailed, .serviceFailed:
|
||
false
|
||
case .starting, .running, .firewallBlocked, .requiresApproval:
|
||
true
|
||
}
|
||
}
|
||
}
|
||
|
||
extension ProxyRuntimeController: Activating {}
|
||
|
||
extension ProxyRuntimeController {
|
||
static func makeProxyMode(from settings: ProxySettings) -> ProxyMode {
|
||
ProxyMode.availableModes.first { $0.kind == settings.preferredMode } ?? .rule
|
||
}
|
||
}
|