Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

199 lines
5.8 KiB
Swift

import Foundation
import MacToolsPluginKit
enum XcodeCleanCategory: String, CaseIterable, Identifiable, Equatable, Sendable {
case derivedData
case deviceSupport
case archives
case simulatorCaches
case previews
case xcodeAppCaches
var id: String { rawValue }
var title: String {
title()
}
func title(localization: PluginLocalization = PluginLocalization(bundle: .main)) -> String {
switch self {
case .derivedData:
return localization.string("category.derivedData.title", defaultValue: "DerivedData")
case .deviceSupport:
return localization.string("category.deviceSupport.title", defaultValue: "设备支持文件")
case .archives:
return localization.string("category.archives.title", defaultValue: "Archives")
case .simulatorCaches:
return localization.string("category.simulatorCaches.title", defaultValue: "模拟器缓存")
case .previews:
return localization.string("category.previews.title", defaultValue: "预览缓存")
case .xcodeAppCaches:
return localization.string("category.xcodeAppCaches.title", defaultValue: "Xcode 应用缓存")
}
}
var summary: String {
summary()
}
func summary(localization: PluginLocalization = PluginLocalization(bundle: .main)) -> String {
switch self {
case .derivedData:
return localization.string("category.derivedData.summary", defaultValue: "构建中间产物、索引与日志")
case .deviceSupport:
return localization.string("category.deviceSupport.summary", defaultValue: "调试旧设备时下载的符号文件")
case .archives:
return localization.string("category.archives.summary", defaultValue: "包含已发布版本的归档与 dSYM,谨慎清理")
case .simulatorCaches:
return localization.string("category.simulatorCaches.summary", defaultValue: "模拟器运行时缓存,不影响已创建的模拟器设备")
case .previews:
return localization.string("category.previews.summary", defaultValue: "SwiftUI 预览的中间渲染缓存")
case .xcodeAppCaches:
return localization.string("category.xcodeAppCaches.summary", defaultValue: "Xcode 自身的会话与界面状态缓存")
}
}
var risk: XcodeCleanRisk {
switch self {
case .archives:
return .medium
default:
return .low
}
}
}
enum XcodeCleanRisk: Equatable, Sendable {
case low
case medium
}
enum XcodeCleanSafetyStatus: Equatable, Sendable {
case allowed
case outsideAllowedRoot
case xcodeRunning
case missing
var isCleanable: Bool {
if case .allowed = self { return true }
return false
}
}
enum XcodeCleanScanLogTone: Equatable, Sendable {
case info
case success
case warning
case error
}
struct XcodeCleanScanLogMessage: Equatable, Sendable {
let text: String
let tone: XcodeCleanScanLogTone
}
struct XcodeCleanScanLogEntry: Identifiable, Equatable, Sendable {
let id: Int
let text: String
let tone: XcodeCleanScanLogTone
}
struct XcodeCleanCandidate: Identifiable, Equatable, Sendable {
let id: String
let category: XcodeCleanCategory
let path: String
let sizeBytes: Int64
let safety: XcodeCleanSafetyStatus
}
struct XcodeCleanCategorySummary: Equatable, Sendable {
let category: XcodeCleanCategory
let candidateCount: Int
let totalBytes: Int64
}
struct XcodeCleanScanResult: Equatable, Sendable {
let categories: Set<XcodeCleanCategory>
let candidates: [XcodeCleanCandidate]
let scannedAt: Date
var cleanableCandidates: [XcodeCleanCandidate] {
candidates.filter { $0.safety.isCleanable }
}
var cleanableSizeBytes: Int64 {
cleanableCandidates.reduce(0) { $0 + max($1.sizeBytes, 0) }
}
var protectedCount: Int {
candidates.count - cleanableCandidates.count
}
func summary(for category: XcodeCleanCategory) -> XcodeCleanCategorySummary {
let scoped = candidates.filter { $0.category == category }
let totalBytes = scoped.reduce(Int64(0)) { $0 + max($1.sizeBytes, 0) }
return XcodeCleanCategorySummary(
category: category,
candidateCount: scoped.count,
totalBytes: totalBytes
)
}
}
struct XcodeCleanExecutionItemResult: Equatable, Sendable {
enum Outcome: Equatable, Sendable {
case removed(reclaimedBytes: Int64)
case skipped(XcodeCleanSafetyStatus)
case failed(message: String)
}
let candidateID: XcodeCleanCandidate.ID
let path: String
let outcome: Outcome
var reclaimedBytes: Int64 {
if case let .removed(reclaimedBytes) = outcome {
return max(reclaimedBytes, 0)
}
return 0
}
}
struct XcodeCleanExecutionResult: Equatable, Sendable {
let itemResults: [XcodeCleanExecutionItemResult]
var removedCount: Int {
itemResults.filter {
if case .removed = $0.outcome { return true }
return false
}.count
}
var skippedCount: Int {
itemResults.filter {
if case .skipped = $0.outcome { return true }
return false
}.count
}
var failedCount: Int {
itemResults.filter {
if case .failed = $0.outcome { return true }
return false
}.count
}
var reclaimedBytes: Int64 {
itemResults.reduce(0) { $0 + $1.reclaimedBytes }
}
}
enum XcodeCleanByteFormatter {
static func string(fromByteCount bytes: Int64) -> String {
let formatter = ByteCountFormatter()
formatter.countStyle = .file
formatter.allowsNonnumericFormatting = false
return formatter.string(fromByteCount: max(bytes, 0))
}
}