From 4b2ddae99e06eee1ebc1708b8704663aacfe79cb Mon Sep 17 00:00:00 2001 From: Qiu Yuzhou Date: Sun, 4 Oct 2026 13:02:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor(paths):=20App=20Support=20=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E5=91=BD=E5=90=8D=E6=9D=83=E5=BD=92=20RuntimePaths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 六个 GUI 配置文件位置(catalog/settings/listen-settings/custom-rules/ activation 与 silent-launch 遗留迁移源)自拼 applicationSupport 目录的 写法收敛为 RuntimePaths 的具名函数,各 store 的 defaultFileURL 改一行 委托。RuntimePaths 宪章从「跨进程运行时契约」扩为「App Support 目录 全部文件清单」——目录里有过哪些文件从此一处可答。 评审候选⑥ 6c:目录改名从动六个文件缩为动一处。 --- .../Domain/ActivationStateFileStore.swift | 4 +- .../Domain/CatalogFileStore.swift | 4 +- ShadowsocksX-NG2/Domain/CustomRuleStore.swift | 4 +- .../Domain/ListenSettingsFileStore.swift | 4 +- .../Domain/ProxySettingsFileStore.swift | 4 +- ShadowsocksX-NG2/Domain/RuntimePaths.swift | 40 ++++++++++++++++++- .../Domain/SilentLaunchStore.swift | 6 +-- 7 files changed, 44 insertions(+), 22 deletions(-) diff --git a/ShadowsocksX-NG2/Domain/ActivationStateFileStore.swift b/ShadowsocksX-NG2/Domain/ActivationStateFileStore.swift index dc076410..4a1d59fb 100644 --- a/ShadowsocksX-NG2/Domain/ActivationStateFileStore.swift +++ b/ShadowsocksX-NG2/Domain/ActivationStateFileStore.swift @@ -21,9 +21,7 @@ struct ActivationStateFileStore { /// 默认位置:`~/Library/Application Support/ShadowsocksX-NG2/activation.json`。 static func defaultFileURL() -> URL { - let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[ - 0] - return support.appendingPathComponent("ShadowsocksX-NG2/activation.json") + RuntimePaths.activationStateFileURL() } /// 文件缺失/损坏/版本未知 → `nil`(安全侧:无活动目标)。 diff --git a/ShadowsocksX-NG2/Domain/CatalogFileStore.swift b/ShadowsocksX-NG2/Domain/CatalogFileStore.swift index ab2b4d4f..bd73ca98 100644 --- a/ShadowsocksX-NG2/Domain/CatalogFileStore.swift +++ b/ShadowsocksX-NG2/Domain/CatalogFileStore.swift @@ -27,9 +27,7 @@ struct CatalogFileStore { /// 默认位置:`~/Library/Application Support/ShadowsocksX-NG2/catalog.json`。 static func defaultFileURL() -> URL { - let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[ - 0] - return support.appendingPathComponent("ShadowsocksX-NG2/catalog.json") + RuntimePaths.catalogFileURL() } /// 文件缺失 → 全新空文档;存在但损坏/版本未知/结构不一致 → `.corrupt`。 diff --git a/ShadowsocksX-NG2/Domain/CustomRuleStore.swift b/ShadowsocksX-NG2/Domain/CustomRuleStore.swift index efb73de4..8c55cde3 100644 --- a/ShadowsocksX-NG2/Domain/CustomRuleStore.swift +++ b/ShadowsocksX-NG2/Domain/CustomRuleStore.swift @@ -10,9 +10,7 @@ struct CustomRuleStore { } static func defaultFileURL() -> URL { - let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[ - 0] - return support.appendingPathComponent("ShadowsocksX-NG2/custom-rules.json") + RuntimePaths.customRulesFileURL() } /// 读取规则集合;文件不存在视为无自定义规则。 diff --git a/ShadowsocksX-NG2/Domain/ListenSettingsFileStore.swift b/ShadowsocksX-NG2/Domain/ListenSettingsFileStore.swift index dda7b576..a4fd91b9 100644 --- a/ShadowsocksX-NG2/Domain/ListenSettingsFileStore.swift +++ b/ShadowsocksX-NG2/Domain/ListenSettingsFileStore.swift @@ -39,9 +39,7 @@ struct ListenSettingsFileStore: ListenSettingsStoring { let fileURL: URL static func defaultFileURL() -> URL { - let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[ - 0] - return support.appendingPathComponent("ShadowsocksX-NG2/listen-settings.json") + RuntimePaths.listenSettingsFileURL() } init(fileURL: URL = ListenSettingsFileStore.defaultFileURL()) { diff --git a/ShadowsocksX-NG2/Domain/ProxySettingsFileStore.swift b/ShadowsocksX-NG2/Domain/ProxySettingsFileStore.swift index 69547a9b..827e521a 100644 --- a/ShadowsocksX-NG2/Domain/ProxySettingsFileStore.swift +++ b/ShadowsocksX-NG2/Domain/ProxySettingsFileStore.swift @@ -15,9 +15,7 @@ struct ProxySettingsFileStore: ProxySettingsStoring { } static func defaultFileURL() -> URL { - let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[ - 0] - return support.appendingPathComponent("ShadowsocksX-NG2/settings.json") + RuntimePaths.settingsFileURL() } func load() throws -> ProxySettings { diff --git a/ShadowsocksX-NG2/Domain/RuntimePaths.swift b/ShadowsocksX-NG2/Domain/RuntimePaths.swift index 76f26c72..ef0d7f85 100644 --- a/ShadowsocksX-NG2/Domain/RuntimePaths.swift +++ b/ShadowsocksX-NG2/Domain/RuntimePaths.swift @@ -1,7 +1,9 @@ import Foundation -/// 跨进程运行时路径契约(spec #21 D5,issue #27):GUI 写、wrapper 读的固定 -/// 位置。目录 0700、文件 0600,由写入方与 wrapper 共同维护。 +/// App Support 目录全部文件的路径清单:GUI 配置与跨进程运行时契约共用同一 +/// 目录。运行时契约子集(spec #21 D5,issue #27)是 GUI 写、wrapper 读的固定 +/// 位置;GUI 配置文件 wrapper 不经手。目录 0700、文件 0600,由写入方与 +/// wrapper 共同维护。 enum RuntimePaths { static func runtimeDirectory() -> URL { FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] @@ -42,6 +44,40 @@ enum RuntimePaths { // 旧版端点签名/所有权文件(system-proxy-endpoint-signature.json、 // system-proxy-ownership.json)按 issue #71 就地遗留:不读取、不迁移、 // 不删除。 + + // MARK: - GUI 配置文件(GUI 私有读写,wrapper 不经手) + + /// 服务器目录(CatalogFileStore)。 + static func catalogFileURL() -> URL { + runtimeDirectory().appendingPathComponent("catalog.json") + } + + /// 代理偏好(ProxySettingsFileStore,#33)。 + static func settingsFileURL() -> URL { + runtimeDirectory().appendingPathComponent("settings.json") + } + + /// 监听设置(ListenSettingsFileStore;settings.json 未建立时的 legacy 回退 + /// 读取源)。 + static func listenSettingsFileURL() -> URL { + runtimeDirectory().appendingPathComponent("listen-settings.json") + } + + /// 自定义规则文档(CustomRuleStore,issue #66)。 + static func customRulesFileURL() -> URL { + runtimeDirectory().appendingPathComponent("custom-rules.json") + } + + /// 活动目标(ActivationStateFileStore,spec #21 D3/D5)。 + static func activationStateFileURL() -> URL { + runtimeDirectory().appendingPathComponent("activation.json") + } + + /// 静默启动偏好的 ADR 0017 初版文件形态,仅遗留迁移路径使用 + /// (SilentLaunchStore.migrateLegacyFileIfPresent)。 + static func silentLaunchLegacyFileURL() -> URL { + runtimeDirectory().appendingPathComponent("silent-launch.json") + } } /// ACL 活动链接的防逃逸校验(ADR-0011):解析 symlink 后必须仍是运行目录 diff --git a/ShadowsocksX-NG2/Domain/SilentLaunchStore.swift b/ShadowsocksX-NG2/Domain/SilentLaunchStore.swift index 5c210105..f2ed19fc 100644 --- a/ShadowsocksX-NG2/Domain/SilentLaunchStore.swift +++ b/ShadowsocksX-NG2/Domain/SilentLaunchStore.swift @@ -9,11 +9,7 @@ import Foundation struct SilentLaunchStore { private static let key = "silentLaunch" /// ADR 0017 初版(文件形态)的落盘位置,仅迁移路径使用。 - static let legacyFileURL: URL = { - let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[ - 0] - return support.appendingPathComponent("ShadowsocksX-NG2/silent-launch.json") - }() + static let legacyFileURL = RuntimePaths.silentLaunchLegacyFileURL() private static let currentVersion = 1