Files
ShadowsocksX-NG/ShadowsocksX-NG2/Domain/ActivationStateFileStore.swift
T
Qiu Yuzhou 4b2ddae99e refactor(paths): App Support 目录命名权归 RuntimePaths
六个 GUI 配置文件位置(catalog/settings/listen-settings/custom-rules/
activation 与 silent-launch 遗留迁移源)自拼 applicationSupport 目录的
写法收敛为 RuntimePaths 的具名函数,各 store 的 defaultFileURL 改一行
委托。RuntimePaths 宪章从「跨进程运行时契约」扩为「App Support 目录
全部文件清单」——目录里有过哪些文件从此一处可答。

评审候选⑥ 6c:目录改名从动六个文件缩为动一处。
2026-10-04 13:02:31 +08:00

63 lines
2.3 KiB
Swift

import Foundation
/// 活动目标持久化(spec #21 D3/D5):落盘 `~/Library/Application Support/
/// ShadowsocksX-NG2/activation.json`,权限与原子替换基线同 catalog.json。
/// 只含目标身份,永不落秘密明文。损坏或版本未知按安全侧恢复为「无活动目标」
/// (代理保持停止,由状态机重校验同步);文件系统级错误仍抛出。
struct ActivationStateFileStore {
enum PersistenceError: Error, Equatable {
/// 读取或写入时的文件系统错误。
case ioFailure(detail: String)
}
private static let currentVersion = 1
private static let jsonEncoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
return encoder
}()
let fileURL: URL
/// 默认位置:`~/Library/Application Support/ShadowsocksX-NG2/activation.json`。
static func defaultFileURL() -> URL {
RuntimePaths.activationStateFileURL()
}
/// 文件缺失/损坏/版本未知 → `nil`(安全侧:无活动目标)。
func loadActiveTargetID() throws -> NodeID? {
guard FileManager.default.fileExists(atPath: fileURL.path) else { return nil }
let data: Data
do {
data = try Data(contentsOf: fileURL)
} catch {
throw PersistenceError.ioFailure(detail: String(describing: error))
}
let payload = try? JSONDecoder().decode(ActivationStatePayload.self, from: data)
guard let payload, payload.version == Self.currentVersion else { return nil }
return payload.activeTargetID
}
/// 整体重写并原子替换;失败时保留原文件。目录按 D5 基线强制 0700(含自愈)。
func save(activeTargetID: NodeID?) throws {
let data: Data
do {
data = try Self.jsonEncoder.encode(
ActivationStatePayload(version: Self.currentVersion, activeTargetID: activeTargetID))
} catch {
throw PersistenceError.ioFailure(detail: String(describing: error))
}
do {
try AtomicFileWriter.write(data, to: fileURL)
} catch {
throw PersistenceError.ioFailure(detail: String(describing: error))
}
}
}
/// 落盘文档形态:版本号 + 活动目标身份(可为 null)。
private struct ActivationStatePayload: Codable {
var version: Int
var activeTargetID: NodeID?
}