mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 18:23:50 +00:00
The toggle only gated a convenience endpoint: system proxy never used the HTTP port, which serves the terminal export line and LAN proxy. Stored httpProxyEnabled:false is silently ignored, no migration. Proxy-endpoint settings rows lose their subtitles; glossary term is now the permanent "HTTP proxy inbound".
164 lines
5.4 KiB
Swift
164 lines
5.4 KiB
Swift
import Foundation
|
||
|
||
// MARK: - 平坦草稿(UI 形状,设置编辑态的唯一 source of truth)
|
||
|
||
/// UI 形状端口标识:设置页三个端口字段的稳定标识。端口行与建议空闲端口命令
|
||
/// 都用本标识;视图不导入探测层端点类型。
|
||
enum SettingsPortID: CaseIterable, Hashable, Sendable {
|
||
case socks
|
||
case http
|
||
case pac
|
||
}
|
||
|
||
/// 平坦的 UI 形状编辑草稿:字段全部是 UI 原语。监听范围拆成布尔开关与公布
|
||
/// 地址文本两个字段,端口与超时保持整数;持久化的当前模式不在草稿中。
|
||
/// 与 Domain 快照的互转只在 `SettingsDraftAdapter`。
|
||
struct SettingsDraft: Equatable, Sendable {
|
||
var isHostScope: Bool
|
||
var advertisedAddress: String
|
||
var socksPort: Int
|
||
var httpPort: Int
|
||
var pacPort: Int
|
||
var timeoutSeconds: Int
|
||
var verboseLogging: Bool
|
||
var proxyExceptions: String
|
||
var gfwListURL: String
|
||
var pacUserRules: String
|
||
|
||
func portValue(for id: SettingsPortID) -> Int {
|
||
switch id {
|
||
case .socks: socksPort
|
||
case .http: httpPort
|
||
case .pac: pacPort
|
||
}
|
||
}
|
||
|
||
mutating func setPortValue(_ value: Int, for id: SettingsPortID) {
|
||
switch id {
|
||
case .socks: socksPort = value
|
||
case .http: httpPort = value
|
||
case .pac: pacPort = value
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 按字段归位的校验问题
|
||
|
||
/// 字段标识(按字段归位查询);端口问题用端口标识变体。
|
||
enum SettingsFieldID: Hashable, Sendable {
|
||
case advertisedAddress
|
||
case port(SettingsPortID)
|
||
case timeoutSeconds
|
||
case gfwListURL
|
||
}
|
||
|
||
/// 按字段归位的校验问题:每条保留 Domain 的 typed fact;文案由 App
|
||
/// presentation edge 派生。
|
||
enum SettingsFieldIssue: Equatable, Sendable {
|
||
case port(SettingsPortID, error: ProxySettingsValidationError)
|
||
case advertisedAddress(error: ProxySettingsValidationError)
|
||
case timeoutSeconds(error: ProxySettingsValidationError)
|
||
case gfwListURL(error: ProxySettingsValidationError)
|
||
|
||
var field: SettingsFieldID {
|
||
switch self {
|
||
case .port(let id, _): .port(id)
|
||
case .advertisedAddress: .advertisedAddress
|
||
case .timeoutSeconds: .timeoutSeconds
|
||
case .gfwListURL: .gfwListURL
|
||
}
|
||
}
|
||
|
||
var error: ProxySettingsValidationError {
|
||
switch self {
|
||
case .port(_, let error), .advertisedAddress(let error), .timeoutSeconds(let error),
|
||
.gfwListURL(let error):
|
||
error
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 端口 field state
|
||
|
||
/// 端口占用的类型化事实(UI 形状):文案与颜色由视图决定。
|
||
enum SettingsPortOccupancy: Equatable, Sendable {
|
||
case free
|
||
/// 端口不可绑定;占用进程名尽力解析(未知为 nil)。
|
||
case occupied(occupier: String?)
|
||
case unknown(detail: String)
|
||
}
|
||
|
||
/// 每个端口字段的 field state:三个端口行复用同一套呈现逻辑。
|
||
struct SettingsPortFieldState: Equatable, Sendable {
|
||
let id: SettingsPortID
|
||
/// 该端口当前草稿值。
|
||
let draftValue: Int
|
||
/// 类型化占用事实;`nil` = 尚未探测。
|
||
let occupancy: SettingsPortOccupancy?
|
||
/// 该端口的字段问题事实;文案由 presentation edge 派生。
|
||
let issues: [SettingsFieldIssue]
|
||
/// 是否为运行中端口例外(代理在跑且端口未变;保存其他设置不会触发冲突)。
|
||
let isRuntimePortException: Bool
|
||
/// 是否可建议空闲端口(被占用且不是运行中端口例外)。
|
||
let canSuggestFreePort: Bool
|
||
}
|
||
|
||
// MARK: - 统一确认事实
|
||
|
||
/// 确认事实(统一种类):seam 裁定动作是否需要确认及摘要内容;视图只持有
|
||
/// alert 呈现状态并把用户选择作为 typed command 发回。
|
||
enum SettingsConfirmation: Equatable, Sendable {
|
||
/// PAC 地址将失效;仅携带前后端口事实。
|
||
case pacInvalidation(previousPort: Int, nextPort: Int)
|
||
/// 重置偏好;范围由这个 typed fact 固定,文案由 presentation edge 派生。
|
||
case resetPreferences
|
||
}
|
||
|
||
/// Typed reasons why a command cannot proceed. The workflow exposes facts,
|
||
/// not localized strings or a Boolean that makes validation and occupancy
|
||
/// failures indistinguishable.
|
||
enum SettingsCommandRejection: Equatable, Sendable {
|
||
case inProgress
|
||
case confirmationPending
|
||
case superseded
|
||
case validation([SettingsFieldIssue])
|
||
case occupied([SettingsPortID])
|
||
case noPendingConfirmation
|
||
case noFreePort(SettingsPortID)
|
||
}
|
||
|
||
/// Runtime convergence is deliberately separate from settings persistence.
|
||
/// A persisted snapshot can therefore be retained even when the runtime has
|
||
/// an independent, safely projected failure.
|
||
enum SettingsRuntimeOutcome: Equatable, Sendable {
|
||
case notRunning
|
||
case converged
|
||
case stopped
|
||
case failed(RuntimeFailureFacts?)
|
||
}
|
||
|
||
enum SettingsPersistenceFailure: Error, Equatable, Sendable {
|
||
case store(ProxySettingsStoreError)
|
||
case unknown
|
||
}
|
||
|
||
/// Result returned by every discrete SettingsWorkflow command. Confirmation,
|
||
/// draft-only changes, persistence and runtime facts remain distinguishable.
|
||
enum SettingsCommandOutcome: Equatable, Sendable {
|
||
case rejected(SettingsCommandRejection)
|
||
case confirmationRequired(SettingsConfirmation)
|
||
case confirmationCancelled
|
||
case draftUpdated(port: SettingsPortID, value: Int)
|
||
case reloaded
|
||
case persisted(runtime: SettingsRuntimeOutcome)
|
||
case persistenceFailed(SettingsPersistenceFailure)
|
||
}
|
||
|
||
/// Settings workflow 的最后失败仍是 typed;workflow 不提前渲染句子。
|
||
enum SettingsWorkflowFailure: Error, Equatable, Sendable {
|
||
case store(ProxySettingsStoreError)
|
||
case mode(ProxyModeError)
|
||
case runtime(RuntimeFailureFacts?)
|
||
case unknown
|
||
}
|