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 阶段。
82 lines
2.6 KiB
Swift
82 lines
2.6 KiB
Swift
import Combine
|
|
import Foundation
|
|
|
|
struct PluginEditorSession: Identifiable {
|
|
let id = UUID()
|
|
/// nil only for a new plugin; fixed names cannot be changed by editing.
|
|
let program: String?
|
|
let isEditing: Bool
|
|
let initialPath: String
|
|
}
|
|
|
|
@MainActor
|
|
final class PluginManagementModel: ObservableObject {
|
|
let catalog: PluginCatalog
|
|
@Published private(set) var snapshot: PluginCatalogSnapshot
|
|
private var subscription: AnyCancellable?
|
|
|
|
init(catalog: PluginCatalog) {
|
|
self.catalog = catalog
|
|
snapshot = catalog.catalogSnapshot()
|
|
subscription = catalog.didCommit.sink { [weak self] _ in
|
|
MainActor.assumeIsolated {
|
|
guard let self else { return }
|
|
self.snapshot = self.catalog.catalogSnapshot()
|
|
}
|
|
}
|
|
}
|
|
|
|
func beginAdding() -> PluginEditorSession {
|
|
PluginEditorSession(program: nil, isEditing: false, initialPath: "")
|
|
}
|
|
|
|
func beginEditing(_ program: String) -> PluginEditorSession? {
|
|
guard !snapshot.mappingsUnreadable, let path = catalog.userMappings[program] else { return nil }
|
|
return PluginEditorSession(program: program, isEditing: true, initialPath: path)
|
|
}
|
|
|
|
func beginOverriding(_ program: String) -> PluginEditorSession? {
|
|
guard !snapshot.mappingsUnreadable, catalog.userMappings[program] == nil,
|
|
ManagedPluginCatalog.info(forProgram: program) != nil
|
|
else { return nil }
|
|
return PluginEditorSession(program: program, isEditing: false, initialPath: "")
|
|
}
|
|
|
|
func nameIssue(_ name: String, session: PluginEditorSession) -> PluginMappingError? {
|
|
let name = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if name.isEmpty || name.contains("\0") { return .invalidName }
|
|
if let fixed = session.program, fixed != name { return .invalidName }
|
|
if !session.isEditing && catalog.userMappings[name] != nil { return .nameExists }
|
|
return nil
|
|
}
|
|
|
|
func pathIssue(_ path: String) -> PluginMappingError? {
|
|
path.hasPrefix("/") && !path.contains("\0") ? nil : .invalidPath
|
|
}
|
|
|
|
func save(_ session: PluginEditorSession, name: String, path: String) throws {
|
|
if let issue = nameIssue(name, session: session) { throw issue }
|
|
if let issue = pathIssue(path) { throw issue }
|
|
let name = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
try catalog.commit([
|
|
session.isEditing ? .update(program: name, path: path) : .add(program: name, path: path)
|
|
])
|
|
refresh()
|
|
}
|
|
|
|
func remove(_ program: String) throws {
|
|
try catalog.commit([.remove(program: program)])
|
|
refresh()
|
|
}
|
|
|
|
func retryReading() throws {
|
|
try catalog.retryReadingMappings()
|
|
refresh()
|
|
}
|
|
|
|
func refresh() {
|
|
snapshot = catalog.catalogSnapshot()
|
|
catalog.refreshSecurityFacts()
|
|
}
|
|
}
|