mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 11:14:51 +00:00
Move InMemoryDiagnosticReportExporter and InMemoryConfigurationGroupFileExporter from App/PlatformEffects to Tests/Support: both are test doubles with no production caller, and production composition wires only the AppKit adapters. Delete RuleRuntimeCompiler.validation, a wrapper whose only callers were two assertion blocks in the controller deployment test; those restated custom-to-custom shadowing and validation order-independence, both pinned by CustomRuleValidatorTests and RuleAnalysisTests. RuleRuntimeCompiler.compile stays and keeps its production caller. Co-Authored-By: DeepSeek Harness <noreply@deepseek.com>
75 lines
2.3 KiB
Swift
75 lines
2.3 KiB
Swift
import AppKit
|
|
import Foundation
|
|
import UniformTypeIdentifiers
|
|
|
|
/// 配置组 JSON / URI 列表草稿的文件写入边界;内容准备由 CatalogWorkflow 拥有。
|
|
@MainActor
|
|
protocol ConfigurationGroupFileExporter {
|
|
func export(draft: ConfigurationGroupExportDraft) -> ConfigurationGroupFileExportResult
|
|
}
|
|
|
|
enum ConfigurationGroupFileExportFailure: Equatable, Error {
|
|
case writeFailed
|
|
case uriListWriteFailed
|
|
}
|
|
|
|
enum ConfigurationGroupFileExportResult: Equatable {
|
|
case cancelled
|
|
case saved(URL)
|
|
case failed(ConfigurationGroupFileExportFailure)
|
|
}
|
|
|
|
enum ConfigurationGroupExportOutcome: Equatable {
|
|
case preparationFailed(ConfigurationGroupExportFailure)
|
|
case cancelled
|
|
case saved(URL)
|
|
case exportFailed(ConfigurationGroupFileExportFailure)
|
|
}
|
|
|
|
/// 先准备完整文档,再调用 exporter;准备失败时不会打开面板或接触文件效果。
|
|
@MainActor
|
|
struct ConfigurationGroupExportAction {
|
|
let workflow: CatalogWorkflow
|
|
let exporter: any ConfigurationGroupFileExporter
|
|
|
|
func perform(for groupID: NodeID) -> ConfigurationGroupExportOutcome {
|
|
let draft: ConfigurationGroupExportDraft
|
|
do {
|
|
draft = try workflow.configurationGroupExportDraft(for: groupID)
|
|
} catch let failure as ConfigurationGroupExportFailure {
|
|
return .preparationFailed(failure)
|
|
} catch {
|
|
return .preparationFailed(.documentEncodingFailed)
|
|
}
|
|
|
|
switch exporter.export(draft: draft) {
|
|
case .cancelled:
|
|
return .cancelled
|
|
case .saved(let url):
|
|
return .saved(url)
|
|
case .failed(let failure):
|
|
return .exportFailed(failure)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 生产文件边界:JSON / UTF-8 文本保存面板与原子文件写入集中在 AppKit adapter。
|
|
@MainActor
|
|
final class AppKitConfigurationGroupFileExporter: ConfigurationGroupFileExporter {
|
|
func export(draft: ConfigurationGroupExportDraft) -> ConfigurationGroupFileExportResult {
|
|
let panel = NSSavePanel()
|
|
panel.allowedContentTypes = draft.format == .json ? [.json] : [.plainText]
|
|
panel.nameFieldStringValue = draft.suggestedFileName
|
|
guard panel.runModal() == .OK, let url = panel.url else {
|
|
return .cancelled
|
|
}
|
|
|
|
do {
|
|
try draft.data.write(to: url, options: .atomic)
|
|
return .saved(url)
|
|
} catch {
|
|
return .failed(draft.format == .json ? .writeFailed : .uriListWriteFailed)
|
|
}
|
|
}
|
|
}
|