feat(cleaner): 将清理功能设为可选并校正确认提示
This commit is contained in:
1 parent
b618a06d2b
commit
6d1561c6bf
22 files changed
+163
-23
No files matched your search
@@ -185,8 +185,7 @@ public enum CleanEngine {
|
||||
await onProgress?(currentProgress())
|
||||
continue
|
||||
}
|
||||
// 废纸篓规则本身必须永久删除;其他可再生内容按用户偏好决定
|
||||
let useTrash = scan.rule.id != RuleCatalog.trash.id && (scan.rule.policy == .trash || preferTrash)
|
||||
let useTrash = !scan.rule.deletesPermanently(preferTrash: preferTrash)
|
||||
|
||||
for item in scan.items {
|
||||
if Task.isCancelled { report.wasCancelled = true; return report }
|
||||
|
||||
@@ -111,6 +111,15 @@ public struct CleanRule: Sendable, Identifiable {
|
||||
}
|
||||
|
||||
public var usesToolCleaner: Bool { cleanWithTool != nil }
|
||||
|
||||
/// 确认提示与清理执行共用的删除方式;工具清理和清空废纸篓始终不可恢复。
|
||||
public func deletesPermanently(preferTrash: Bool) -> Bool {
|
||||
if usesToolCleaner || id == RuleCatalog.trash.id { return true }
|
||||
switch policy {
|
||||
case .delete: return !preferTrash
|
||||
case .trash: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CleanItem: Sendable, Identifiable, Hashable {
|
||||
|
||||
@@ -1026,7 +1026,7 @@ uv 下载的 Python 包与构建缓存,由 uv 自行清理 ذاكرة حزم P
|
||||
继续运行 مواصلة التشغيل
|
||||
绿色正常、橙色偏高(60% 以上)、红色很高(85% 以上) أخضر: طبيعي؛ برتقالي: مرتفع (فوق 60%)؛ أحمر: مرتفع جدًا (فوق 85%)
|
||||
缓存 الذاكرات المؤقتة
|
||||
缓存与日志直接删除,下载内容移到废纸篓。 تُحذف الذاكرات المؤقتة والسجلات؛ وتُنقل التنزيلات إلى سلة المهملات.
|
||||
缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。 ستُحذف الذاكرات المؤقتة والسجلات نهائيًا ولا يمكن استعادتها؛ وتُنقل التنزيلات إلى سلة المهملات.
|
||||
缓存也先移到废纸篓 نقل الذاكرات المؤقتة إلى السلة أيضًا
|
||||
编辑 تعديل
|
||||
网站 الموقع
|
||||
@@ -1568,3 +1568,5 @@ Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依
|
||||
已停止,已清理部分不会恢复 توقف التنظيف. لا يمكن استعادة العناصر التي نُظفت بالفعل.
|
||||
扫描大小约 {} الحجم المفحوص: نحو {}
|
||||
正在取消… جارٍ الإلغاء…
|
||||
按需扫描缓存、日志与项目产物,清理前逐项确认。 افحص الذاكرات المؤقتة والسجلات ومخرجات المشاريع عند الحاجة، وراجع العناصر قبل تنظيفها.
|
||||
“{}”只剩 {} 可用。请检查不再需要的文件。 لم يتبقَّ على «{}» سوى {}. راجع الملفات التي لم تعد بحاجة إليها.
|
||||
|
@@ -1026,7 +1026,7 @@ uv 下载的 Python 包与构建缓存,由 uv 自行清理 Von uv geladener Py
|
||||
继续运行 Weiterlaufen lassen
|
||||
绿色正常、橙色偏高(60% 以上)、红色很高(85% 以上) Grün: normal, Orange: hoch (über 60 %), Rot: sehr hoch (über 85 %)
|
||||
缓存 Caches
|
||||
缓存与日志直接删除,下载内容移到废纸篓。 Caches und Protokolle werden direkt gelöscht, Downloads in den Papierkorb verschoben.
|
||||
缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。 Caches und Protokolle werden endgültig gelöscht und können nicht wiederhergestellt werden; Downloads werden in den Papierkorb verschoben.
|
||||
缓存也先移到废纸篓 Auch Caches in den Papierkorb verschieben
|
||||
编辑 Bearbeiten
|
||||
网站 Website
|
||||
@@ -1568,3 +1568,5 @@ Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依
|
||||
已停止,已清理部分不会恢复 Gestoppt. Bereits bereinigte Inhalte können nicht wiederhergestellt werden.
|
||||
扫描大小约 {} Erfasste Größe: ca. {}
|
||||
正在取消… Wird abgebrochen…
|
||||
按需扫描缓存、日志与项目产物,清理前逐项确认。 Caches, Protokolle und Projektartefakte bei Bedarf scannen. Elemente vor dem Bereinigen einzeln prüfen.
|
||||
“{}”只剩 {} 可用。请检查不再需要的文件。 Auf „{}“ sind nur noch {} verfügbar. Prüfe Dateien, die du nicht mehr benötigst.
|
||||
|
@@ -1026,7 +1026,7 @@ uv 下载的 Python 包与构建缓存,由 uv 自行清理 Cachés de paquetes
|
||||
继续运行 Seguir funcionando
|
||||
绿色正常、橙色偏高(60% 以上)、红色很高(85% 以上) Verde: normal; naranja: alto (más del 60%); rojo: muy alto (más del 85%)
|
||||
缓存 Cachés
|
||||
缓存与日志直接删除,下载内容移到废纸篓。 Las cachés y registros se eliminan; las descargas van a la Papelera.
|
||||
缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。 Las cachés y registros se eliminarán de forma permanente y no se podrán recuperar; las descargas van a la Papelera.
|
||||
缓存也先移到废纸篓 Mover también las cachés a la Papelera
|
||||
编辑 Editar
|
||||
网站 Sitio
|
||||
@@ -1568,3 +1568,5 @@ Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依
|
||||
已停止,已清理部分不会恢复 Detenido. Los elementos ya eliminados no se pueden restaurar.
|
||||
扫描大小约 {} Tamaño analizado: aprox. {}
|
||||
正在取消… Cancelando…
|
||||
按需扫描缓存、日志与项目产物,清理前逐项确认。 Analiza cachés, registros y artefactos de proyectos cuando lo necesites. Revisa cada elemento antes de limpiar.
|
||||
“{}”只剩 {} 可用。请检查不再需要的文件。 En «{}» solo quedan {} disponibles. Revisa los archivos que ya no necesitas.
|
||||
|
@@ -1026,7 +1026,7 @@ uv 下载的 Python 包与构建缓存,由 uv 自行清理 Caches de paquets P
|
||||
继续运行 Continuer l’exécution
|
||||
绿色正常、橙色偏高(60% 以上)、红色很高(85% 以上) Vert : normal, orange : élevé (au-dessus de 60 %), rouge : très élevé (au-dessus de 85 %)
|
||||
缓存 Caches
|
||||
缓存与日志直接删除,下载内容移到废纸篓。 Les caches et journaux sont supprimés ; les téléchargements sont déplacés vers la Corbeille.
|
||||
缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。 Les caches et journaux seront supprimés définitivement, sans possibilité de récupération ; les téléchargements sont déplacés vers la Corbeille.
|
||||
缓存也先移到废纸篓 Déplacer aussi les caches vers la Corbeille
|
||||
编辑 Édition
|
||||
网站 Site
|
||||
@@ -1568,3 +1568,5 @@ Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依
|
||||
已停止,已清理部分不会恢复 Arrêté. Les éléments déjà supprimés ne peuvent pas être restaurés.
|
||||
扫描大小约 {} Taille analysée : environ {}
|
||||
正在取消… Annulation en cours…
|
||||
按需扫描缓存、日志与项目产物,清理前逐项确认。 Analysez les caches, journaux et artefacts de projet à la demande. Vérifiez les éléments avant le nettoyage.
|
||||
“{}”只剩 {} 可用。请检查不再需要的文件。 « {} » : il ne reste que {} disponibles. Vérifiez les fichiers dont vous n’avez plus besoin.
|
||||
|
@@ -1026,7 +1026,7 @@ uv 下载的 Python 包与构建缓存,由 uv 自行清理 uvのPythonパッ
|
||||
继续运行 実行を続ける
|
||||
绿色正常、橙色偏高(60% 以上)、红色很高(85% 以上) 緑は正常、オレンジは高め(60%超)、赤は非常に高い(85%超)
|
||||
缓存 キャッシュ
|
||||
缓存与日志直接删除,下载内容移到废纸篓。 キャッシュとログは直接削除し、ダウンロードはゴミ箱に移動します。
|
||||
缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。 キャッシュとログは直接削除され、復元できません。ダウンロードはゴミ箱に移動します。
|
||||
缓存也先移到废纸篓 キャッシュもゴミ箱に移動
|
||||
编辑 編集
|
||||
网站 サイト
|
||||
@@ -1568,3 +1568,5 @@ Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依
|
||||
已停止,已清理部分不会恢复 停止しました。すでに削除した内容は復元できません
|
||||
扫描大小约 {} スキャン時のサイズ:約{}
|
||||
正在取消… キャンセル中…
|
||||
按需扫描缓存、日志与项目产物,清理前逐项确认。 キャッシュ、ログ、プロジェクトの生成物を必要なときにスキャンし、削除前に項目を確認します。
|
||||
“{}”只剩 {} 可用。请检查不再需要的文件。 「{}」の空き容量は残り {} です。不要なファイルを確認してください。
|
||||
|
@@ -1026,7 +1026,7 @@ uv 下载的 Python 包与构建缓存,由 uv 自行清理 uv의 Python 패키
|
||||
继续运行 계속 실행
|
||||
绿色正常、橙色偏高(60% 以上)、红色很高(85% 以上) 초록색은 정상, 주황색은 높음(60% 초과), 빨간색은 매우 높음(85% 초과)
|
||||
缓存 캐시
|
||||
缓存与日志直接删除,下载内容移到废纸篓。 캐시와 로그는 바로 삭제하고 다운로드는 휴지통으로 이동합니다.
|
||||
缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。 캐시와 로그는 영구 삭제되며 복구할 수 없습니다. 다운로드는 휴지통으로 이동합니다.
|
||||
缓存也先移到废纸篓 캐시도 휴지통으로 이동
|
||||
编辑 편집
|
||||
网站 사이트
|
||||
@@ -1568,3 +1568,5 @@ Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依
|
||||
已停止,已清理部分不会恢复 중지됨. 이미 정리된 항목은 복원할 수 없습니다.
|
||||
扫描大小约 {} 스캔 크기 약 {}
|
||||
正在取消… 취소하는 중…
|
||||
按需扫描缓存、日志与项目产物,清理前逐项确认。 필요할 때 캐시, 로그 및 프로젝트 빌드 산출물을 검사하고 정리 전에 항목을 확인합니다.
|
||||
“{}”只剩 {} 可用。请检查不再需要的文件。 “{}”의 남은 공간은 {}입니다. 더 이상 필요하지 않은 파일을 확인하세요.
|
||||
|
@@ -1027,7 +1027,7 @@ uv 下载的 Python 包与构建缓存,由 uv 自行清理 uv 下載的 Python
|
||||
继续运行 繼續運行
|
||||
绿色正常、橙色偏高(60% 以上)、红色很高(85% 以上) 綠色正常、橙色偏高(60% 以上)、紅色很高(85% 以上)
|
||||
缓存 緩存
|
||||
缓存与日志直接删除,下载内容移到废纸篓。 緩存與日誌直接刪除,下載內容移到廢紙簍。
|
||||
缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。 緩存與日誌將直接刪除且無法復原,下載內容移到廢紙簍。
|
||||
缓存也先移到废纸篓 緩存也先移到廢紙簍
|
||||
编辑 編輯
|
||||
网站 網站
|
||||
@@ -1569,3 +1569,5 @@ Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依
|
||||
已停止,已清理部分不会恢复 已停止,已清理部分無法復原
|
||||
扫描大小约 {} 掃描大小約 {}
|
||||
正在取消… 正在取消…
|
||||
按需扫描缓存、日志与项目产物,清理前逐项确认。 按需掃描快取、日誌與專案產物,清理前逐項確認。
|
||||
“{}”只剩 {} 可用。请检查不再需要的文件。 「{}」只剩 {} 可用。請檢查不再需要的檔案。
|
||||
|
@@ -1149,7 +1149,7 @@ uv 下载的 Python 包与构建缓存,由 uv 自行清理 Python package and
|
||||
继续运行 Keep running
|
||||
绿色正常、橙色偏高(60% 以上)、红色很高(85% 以上) Green is normal, orange is high (above 60%), red is very high (above 85%)
|
||||
缓存 Caches
|
||||
缓存与日志直接删除,下载内容移到废纸篓。 Caches and logs are deleted; downloads go to the Trash.
|
||||
缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。 Caches and logs will be permanently deleted and cannot be recovered; downloads go to the Trash.
|
||||
缓存也先移到废纸篓 Move caches to Trash too
|
||||
编辑 Edit
|
||||
网站 Site
|
||||
@@ -1419,5 +1419,7 @@ Agent 工作树 Agent worktree
|
||||
Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依赖。 Homebrew will clean old versions and caches based on their state at execution time, without automatically removing dependencies.
|
||||
尚未扫描 Not scanned yet
|
||||
点击重新扫描以计算可清理空间 Click Rescan to calculate reclaimable space
|
||||
按需扫描缓存、日志与项目产物,清理前逐项确认。 Scan caches, logs, and project artifacts on demand. Review items before cleaning.
|
||||
“{}”只剩 {} 可用。请检查不再需要的文件。 “{}” has only {} available. Check files you no longer need.
|
||||
"""#
|
||||
}
|
||||
@@ -101,9 +101,10 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
self?.speedTestWindow.show()
|
||||
}
|
||||
model.openProjectPurgeWindow = { [weak self] in
|
||||
self?.menuBar.dismissPopovers()
|
||||
self?.calendarMenuBar.dismiss()
|
||||
self?.projectPurgeWindow.show()
|
||||
guard let self, self.model.settings.cleanerEnabled else { return }
|
||||
self.menuBar.dismissPopovers()
|
||||
self.calendarMenuBar.dismiss()
|
||||
self.projectPurgeWindow.show()
|
||||
}
|
||||
model.quit = { NSApp.terminate(nil) }
|
||||
model.helper.refreshStatus()
|
||||
@@ -119,6 +120,7 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
model.screenTime.start()
|
||||
observeScreenTimeHistorySetting()
|
||||
observeRestSettings()
|
||||
observeCleanerSetting()
|
||||
observeRestMode()
|
||||
observeRestSound()
|
||||
rest.sync()
|
||||
@@ -632,6 +634,26 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// 关闭清理模块时停止尚未结束的扫描或清理,并关闭其独立窗口。
|
||||
private func observeCleanerSetting() {
|
||||
withObservationTracking {
|
||||
_ = model.settings.cleanerEnabled
|
||||
} onChange: { [weak self] in
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else { return }
|
||||
if !self.model.settings.cleanerEnabled {
|
||||
self.model.cleaner.cancelClean()
|
||||
self.model.cleaner.cancelOperation()
|
||||
self.model.projectPurge.isConfirming = false
|
||||
self.model.projectPurge.cancelScan()
|
||||
self.model.projectPurge.cancelClean()
|
||||
self.projectPurgeWindow.close()
|
||||
}
|
||||
self.observeCleanerSetting()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func observeRestMode() {
|
||||
withObservationTracking {
|
||||
_ = model.settings.restMode
|
||||
|
||||
@@ -20,6 +20,8 @@ final class ProjectPurgeWindowController: NSObject, NSWindowDelegate {
|
||||
|
||||
func refreshLanguage() { window?.title = tr("项目产物") }
|
||||
|
||||
func close() { window?.close() }
|
||||
|
||||
func show() {
|
||||
let window = self.window ?? makeWindow()
|
||||
self.window = window
|
||||
|
||||
@@ -81,12 +81,22 @@ private struct SummaryCard: View {
|
||||
}
|
||||
|
||||
if cleaner.isConfirming {
|
||||
InfoBanner(icon: "exclamationmark.triangle.fill", text: confirmationText, tone: .warning) {
|
||||
let deletesPermanently = cleaner.selectedScans.contains {
|
||||
$0.rule.deletesPermanently(preferTrash: model.settings.cleanPrefersTrash)
|
||||
}
|
||||
InfoBanner(icon: "exclamationmark.triangle.fill", text: confirmationText,
|
||||
tone: deletesPermanently ? .error : .warning) {
|
||||
HStack(spacing: DS.Space.s2) {
|
||||
Button(tr("取消")) { cleaner.cancelClean() }
|
||||
.buttonStyle(DSButtonStyle(kind: .secondary))
|
||||
Button(tr("确认清理")) { cleaner.confirmClean() }
|
||||
.buttonStyle(DSButtonStyle(kind: .primary))
|
||||
if deletesPermanently {
|
||||
Button(tr("确认清理"), role: .destructive) { cleaner.confirmClean() }
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(DS.Palette.error)
|
||||
} else {
|
||||
Button(tr("确认清理")) { cleaner.confirmClean() }
|
||||
.buttonStyle(DSButtonStyle(kind: .primary))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -152,7 +162,7 @@ private struct SummaryCard: View {
|
||||
let count = cleaner.selectedScans.reduce(0) { $0 + $1.items.count }
|
||||
let trashNote = model.settings.cleanPrefersTrash
|
||||
? tr("支持的内容先移到废纸篓;工具缓存由对应命令直接清理。")
|
||||
: tr("缓存与日志直接删除,下载内容移到废纸篓。")
|
||||
: tr("缓存与日志将直接删除且无法恢复,下载内容移到废纸篓。")
|
||||
var text = tr("将清理 \(count) 个项目,共 \(Format.bytes(cleaner.selectedBytes, base: .decimal))。\(trashNote)")
|
||||
if cleaner.selectedScans.contains(where: { $0.id == "developer.homebrew" }) {
|
||||
text += " " + tr("Homebrew 将按执行时的状态清理旧版本与缓存,不自动移除依赖。")
|
||||
|
||||
@@ -196,7 +196,13 @@ private struct MainSidebar: View {
|
||||
private var navigation: some View {
|
||||
VStack(alignment: .leading, spacing: DS.Space.s1) {
|
||||
group(tr("监控"), PanelTab.monitors.filter { $0 != .aiUsage || model.settings.aiUsageEnabled })
|
||||
group(tr("工具"), PanelTab.tools.filter { $0 != .rest || model.settings.restEnabled })
|
||||
group(tr("工具"), PanelTab.tools.filter { tab in
|
||||
switch tab {
|
||||
case .rest: model.settings.restEnabled
|
||||
case .cleaner: model.settings.cleanerEnabled
|
||||
default: true
|
||||
}
|
||||
})
|
||||
group(tr("设置"), PanelTab.settings)
|
||||
}
|
||||
.sidebarGlider()
|
||||
|
||||
@@ -122,6 +122,13 @@ struct GeneralSettings: View {
|
||||
DSToggle(isOn: $settings.aiUsageEnabled, label: tr("AI 用量与额度"))
|
||||
}
|
||||
}
|
||||
GroupRow(showsDivider: false) {
|
||||
SettingRow(title: tr("清理"),
|
||||
subtitle: tr("按需扫描缓存、日志与项目产物,清理前逐项确认。"),
|
||||
icon: "eraser") {
|
||||
DSToggle(isOn: $settings.cleanerEnabled, label: tr("清理"))
|
||||
}
|
||||
}
|
||||
GroupRow {
|
||||
SettingRow(title: tr("菜单栏日历"),
|
||||
subtitle: tr("独立显示日期,点击打开月历;不受指标合并布局影响"),
|
||||
|
||||
@@ -28,6 +28,7 @@ enum SnapshotRenderer {
|
||||
settings.language = L10n.language
|
||||
settings.menuBarItems = [.cpu, .gpu, .memory, .network, .disk, .temperature, .battery, .aiUsage]
|
||||
settings.aiUsageEnabled = true
|
||||
settings.cleanerEnabled = true
|
||||
// 历史页用示例数据:最近 24 小时每分钟一条,中间留一段“睡眠”空档
|
||||
let historyURL = FileManager.default.temporaryDirectory.appendingPathComponent("xstats-snapshot-history.sqlite")
|
||||
try? FileManager.default.removeItem(at: historyURL)
|
||||
|
||||
@@ -199,8 +199,10 @@ public final class AlertController: NSObject {
|
||||
let enabled = settings.enabledAlerts
|
||||
if enabled.contains(.diskSpace), let disk = DiskSampler.sample() {
|
||||
let low = disk.total > 0 && (Double(disk.available) / Double(disk.total) < 0.1 || disk.available < 10_000_000_000)
|
||||
fireIfNeeded(.diskSpace, isActive: low, now: now,
|
||||
body: tr("“\(disk.volumeName)”只剩 \(Format.bytes(disk.available, base: .decimal)) 可用。可以用 XStats 的清理功能释放缓存。"))
|
||||
let body = settings.cleanerEnabled
|
||||
? tr("“\(disk.volumeName)”只剩 \(Format.bytes(disk.available, base: .decimal)) 可用。可以用 XStats 的清理功能释放缓存。")
|
||||
: tr("“\(disk.volumeName)”只剩 \(Format.bytes(disk.available, base: .decimal)) 可用。请检查不再需要的文件。")
|
||||
fireIfNeeded(.diskSpace, isActive: low, now: now, body: body)
|
||||
}
|
||||
if enabled.contains(.batteryHealth), let health = BatterySampler.sample()?.health {
|
||||
// 冷却时间跨越重启,记在偏好设置里
|
||||
|
||||
@@ -514,6 +514,13 @@ public final class AppSettings {
|
||||
if !aiUsageEnabled && panelTab == .aiUsage { panelTab = .settingsGeneral }
|
||||
}
|
||||
}
|
||||
/// 清理工具默认关闭;扫描只在打开页面后按需执行。
|
||||
public var cleanerEnabled: Bool {
|
||||
didSet {
|
||||
defaults.set(cleanerEnabled, forKey: Keys.cleanerEnabled)
|
||||
if !cleanerEnabled && panelTab == .cleaner { panelTab = .settingsGeneral }
|
||||
}
|
||||
}
|
||||
/// 来源开关只保存在本机;关闭后不扫描,也不显示其历史缓存。
|
||||
public var aiUsageSources: Set<AIProviderID> {
|
||||
didSet { defaults.set(aiUsageSources.map(\.rawValue).sorted(), forKey: Keys.aiUsageSources) }
|
||||
@@ -708,6 +715,8 @@ public final class AppSettings {
|
||||
? defaults.integer(forKey: Keys.refreshSeconds) : 2
|
||||
let isAIUsageEnabled = defaults.bool(forKey: Keys.aiUsageEnabled)
|
||||
aiUsageEnabled = isAIUsageEnabled
|
||||
let isCleanerEnabled = defaults.bool(forKey: Keys.cleanerEnabled)
|
||||
cleanerEnabled = isCleanerEnabled
|
||||
aiUsageSources = defaults.stringArray(forKey: Keys.aiUsageSources)
|
||||
.map { Set($0.compactMap(AIProviderID.init(rawValue:))) } ?? Set(AIProviderID.allCases)
|
||||
aiUsageShowsLocalUsage = defaults.object(forKey: Keys.aiUsageShowsLocalUsage) as? Bool ?? true
|
||||
@@ -726,6 +735,7 @@ public final class AppSettings {
|
||||
panelTab = savedPanelTabValue == "settingsAI" || savedPanelTab == .settingsAccount
|
||||
|| (savedPanelTab == .aiUsage && !isAIUsageEnabled)
|
||||
|| (savedPanelTab == .rest && !isRestEnabled)
|
||||
|| (savedPanelTab == .cleaner && !isCleanerEnabled)
|
||||
? .settingsGeneral : savedPanelTab
|
||||
appearance = defaults.string(forKey: Keys.appearance).flatMap(AppearanceMode.init(rawValue:)) ?? .system
|
||||
showDockIcon = defaults.bool(forKey: Keys.showDockIcon)
|
||||
@@ -817,6 +827,7 @@ public final class AppSettings {
|
||||
static let styleOverrides = "styleOverrides"
|
||||
static let refreshSeconds = "refreshSeconds"
|
||||
static let aiUsageEnabled = "aiUsageEnabled"
|
||||
static let cleanerEnabled = "cleanerEnabled"
|
||||
static let aiUsageSources = "aiUsageSources"
|
||||
static let aiUsageShowsLocalUsage = "aiUsageShowsLocalUsage"
|
||||
static let aiUsageRefreshMinutes = "aiUsageRefreshMinutes"
|
||||
|
||||
@@ -36,7 +36,7 @@ public final class CleanerController {
|
||||
var isBusy: Bool { phase == .scanning || phase == .cleaning }
|
||||
|
||||
func scan(ifNeeded: Bool = false) {
|
||||
guard !isBusy, !(ifNeeded && lastScan != nil) else { return }
|
||||
guard settings.cleanerEnabled, !isBusy, !(ifNeeded && lastScan != nil) else { return }
|
||||
phase = .scanning
|
||||
isConfirming = false
|
||||
let environment = Self.environment()
|
||||
@@ -51,7 +51,7 @@ public final class CleanerController {
|
||||
}
|
||||
|
||||
func requestClean() {
|
||||
guard phase == .ready || phase == .finished, !selectedScans.isEmpty else { return }
|
||||
guard settings.cleanerEnabled, (phase == .ready || phase == .finished), !selectedScans.isEmpty else { return }
|
||||
isConfirming = true
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public final class CleanerController {
|
||||
}
|
||||
|
||||
func confirmClean() {
|
||||
guard isConfirming else { return }
|
||||
guard settings.cleanerEnabled, isConfirming else { return }
|
||||
isConfirming = false
|
||||
report = nil
|
||||
cleanProgress = nil
|
||||
|
||||
@@ -55,6 +55,7 @@ public struct SettingsDocument: Codable, Equatable, Sendable {
|
||||
public var historyEnabled: Bool?
|
||||
public var autoCheckUpdates: Bool?
|
||||
public var updateCheckSchedule: String?
|
||||
public var cleanerEnabled: Bool?
|
||||
public var cleanPrefersTrash: Bool?
|
||||
|
||||
public init() {}
|
||||
@@ -103,6 +104,7 @@ extension AppSettings {
|
||||
doc.historyEnabled = historyEnabled
|
||||
doc.autoCheckUpdates = autoCheckUpdates
|
||||
doc.updateCheckSchedule = updateCheckSchedule.rawValue
|
||||
doc.cleanerEnabled = cleanerEnabled
|
||||
doc.cleanPrefersTrash = cleanPrefersTrash
|
||||
return doc
|
||||
}
|
||||
@@ -172,6 +174,7 @@ extension AppSettings {
|
||||
} else {
|
||||
assign(\.autoCheckUpdates, doc.autoCheckUpdates)
|
||||
}
|
||||
assign(\.cleanerEnabled, doc.cleanerEnabled)
|
||||
assign(\.cleanPrefersTrash, doc.cleanPrefersTrash)
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,19 @@ private struct FakeHome {
|
||||
func remove() { try? FileManager.default.removeItem(at: root) }
|
||||
}
|
||||
|
||||
@Suite("清理方式")
|
||||
struct CleanDeletionPolicyTests {
|
||||
@Test func confirmationMatchesActualDeletionPolicy() {
|
||||
for preferTrash in [false, true] {
|
||||
#expect(RuleCatalog.trash.deletesPermanently(preferTrash: preferTrash))
|
||||
#expect(RuleCatalog.uvCache.deletesPermanently(preferTrash: preferTrash))
|
||||
#expect(!RuleCatalog.incompleteDownloads.deletesPermanently(preferTrash: preferTrash))
|
||||
#expect(!RuleCatalog.xcodeArchives.deletesPermanently(preferTrash: preferTrash))
|
||||
#expect(RuleCatalog.userCaches.deletesPermanently(preferTrash: preferTrash) == !preferTrash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suite("安全守卫")
|
||||
struct SafetyGuardTests {
|
||||
let guardian = SafetyGuard(home: "/Users/tester")
|
||||
|
||||
@@ -271,6 +271,45 @@ private func isolatedDefaults() -> UserDefaults {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Suite struct CleanerFeatureTests {
|
||||
@Test func defaultsOffAndHidesSavedPageWhenDisabled() {
|
||||
let defaults = isolatedDefaults()
|
||||
defaults.set("cleaner", forKey: "panelTab")
|
||||
let settings = AppSettings(defaults: defaults)
|
||||
#expect(!settings.cleanerEnabled)
|
||||
#expect(settings.panelTab == .settingsGeneral)
|
||||
|
||||
let cleaner = CleanerController(settings: settings)
|
||||
cleaner.scan()
|
||||
#expect(cleaner.phase == .idle)
|
||||
|
||||
settings.cleanerEnabled = true
|
||||
settings.panelTab = .cleaner
|
||||
#expect(AppSettings(defaults: defaults).panelTab == .cleaner)
|
||||
|
||||
settings.cleanerEnabled = false
|
||||
#expect(settings.panelTab == .settingsGeneral)
|
||||
#expect(!AppSettings(defaults: defaults).cleanerEnabled)
|
||||
defaults.set("cleaner", forKey: "panelTab")
|
||||
#expect(AppSettings(defaults: defaults).panelTab == .settingsGeneral)
|
||||
}
|
||||
|
||||
@Test func backupRestoresModulePreferenceAndOldBackupKeepsCurrentValue() throws {
|
||||
let source = AppSettings(defaults: isolatedDefaults())
|
||||
source.cleanerEnabled = true
|
||||
let data = try JSONEncoder().encode(source.exportDocument())
|
||||
let document = try JSONDecoder().decode(SettingsDocument.self, from: data)
|
||||
#expect(document.cleanerEnabled == true)
|
||||
|
||||
let target = AppSettings(defaults: isolatedDefaults())
|
||||
target.apply(document)
|
||||
#expect(target.cleanerEnabled)
|
||||
target.apply(SettingsDocument())
|
||||
#expect(target.cleanerEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Suite struct CleanerSelectionTests {
|
||||
private let toolRuleIDs: Set<String> = [
|
||||
|
||||
Reference in new issue
Block a user