feat(cleaner): 新增 Homebrew 下载缓存清理
This commit is contained in:
1 parent
ce18018f27
commit
f1e7de9b0e
11 files changed
+87
-4
No files matched your search
@@ -6,7 +6,7 @@ public enum RuleCatalog {
|
||||
public static func rules() -> [CleanRule] {
|
||||
[userCaches, logs] + browsers.map(browserRule) + [xcodeDerivedData, simulatorCaches,
|
||||
npmCache, yarnCache, pnpmCache, bunCache,
|
||||
goCache, rustCache, uvCache, xcodeArchives,
|
||||
goCache, rustCache, uvCache, homebrewCache, xcodeArchives,
|
||||
incompleteDownloads, installers, trash]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public enum RuleCatalog {
|
||||
static let excludedCacheNames: Set<String> = [
|
||||
"Google", "Firefox", "Microsoft Edge", "BraveSoftware", "Arc",
|
||||
"com.google.Chrome", "com.microsoft.edgemac", "com.brave.Browser", "company.thebrowser.Browser",
|
||||
"org.mozilla.firefox", "com.apple.Safari",
|
||||
"org.mozilla.firefox", "com.apple.Safari", "Homebrew",
|
||||
]
|
||||
|
||||
static let userCaches = CleanRule(
|
||||
@@ -215,6 +215,30 @@ public enum RuleCatalog {
|
||||
try childrenIfExists(of: env.home + "/.cache/uv")
|
||||
}
|
||||
|
||||
static let homebrewCache = CleanRule(
|
||||
id: "developer.homebrew", category: .developer, title: tr("Homebrew 下载缓存"),
|
||||
detail: tr("超过 1 天的 Homebrew 下载文件,不影响已安装软件"), symbol: "shippingbox",
|
||||
selectedByDefault: false, minimumAge: 86_400
|
||||
) { env in
|
||||
// 仅识别 Homebrew 已完成的内容寻址下载;同目录中的临时文件和其他缓存不属于本规则。
|
||||
let cacheDirectory = URL(fileURLWithPath: env.home + "/Library/Caches/Homebrew")
|
||||
let downloads = cacheDirectory.appendingPathComponent("downloads")
|
||||
guard FileManager.default.fileExists(atPath: downloads.path) else { return [] }
|
||||
guard try cacheDirectory.resourceValues(forKeys: [.isSymbolicLinkKey]).isSymbolicLink != true,
|
||||
try downloads.resourceValues(forKeys: [.isSymbolicLinkKey]).isSymbolicLink != true else {
|
||||
return []
|
||||
}
|
||||
return try children(of: downloads.path).filter { url in
|
||||
let name = url.lastPathComponent
|
||||
guard !name.hasSuffix(".incomplete"),
|
||||
name.range(of: #"^[0-9a-f]{64}--.+$"#, options: .regularExpression) != nil,
|
||||
let values = try? url.resourceValues(forKeys: [.isRegularFileKey, .isSymbolicLinkKey]) else {
|
||||
return false
|
||||
}
|
||||
return values.isRegularFile == true && values.isSymbolicLink != true
|
||||
}
|
||||
}
|
||||
|
||||
static let xcodeArchives = CleanRule(
|
||||
id: "developer.archives", category: .developer, title: tr("Xcode 归档"),
|
||||
detail: tr("打包生成的 .xcarchive,删除后无法重新符号化旧版本崩溃日志"), symbol: "archivebox.fill",
|
||||
|
||||
@@ -116,6 +116,8 @@ uv 缓存 ذاكرة uv المؤقتة
|
||||
Yarn 缓存 ذاكرة Yarn المؤقتة
|
||||
pnpm 缓存 ذاكرة pnpm المؤقتة
|
||||
Bun 缓存 ذاكرة Bun المؤقتة
|
||||
Homebrew 下载缓存 ذاكرة تنزيلات Homebrew المؤقتة
|
||||
超过 1 天的 Homebrew 下载文件,不影响已安装软件 ملفات تنزيل Homebrew الأقدم من يوم واحد؛ لا تتأثر البرامج المثبتة
|
||||
npm 下载缓存,由 npm 自行清理 ذاكرة تنزيل npm المؤقتة، ينظفها npm بنفسه
|
||||
Yarn 全局与离线镜像缓存,由 Yarn 自行清理 ذاكرة Yarn العامة ومرآة العمل دون اتصال، ينظفها Yarn بنفسه
|
||||
pnpm 内容寻址存储,仅清理未被引用的包 مخزن pnpm المعنون بالمحتوى؛ تزال الحزم غير المشار إليها فقط
|
||||
|
||||
|
@@ -116,6 +116,8 @@ uv 缓存 uv-Cache
|
||||
Yarn 缓存 Yarn-Cache
|
||||
pnpm 缓存 pnpm-Cache
|
||||
Bun 缓存 Bun-Cache
|
||||
Homebrew 下载缓存 Homebrew-Download-Cache
|
||||
超过 1 天的 Homebrew 下载文件,不影响已安装软件 Homebrew-Downloads, die älter als einen Tag sind; installierte Software bleibt erhalten
|
||||
npm 下载缓存,由 npm 自行清理 npm-Download-Cache; wird von npm selbst bereinigt
|
||||
Yarn 全局与离线镜像缓存,由 Yarn 自行清理 Globaler Yarn- und Offline-Mirror-Cache; wird von Yarn selbst bereinigt
|
||||
pnpm 内容寻址存储,仅清理未被引用的包 Inhaltsadressierter pnpm-Speicher; nur nicht referenzierte Pakete werden entfernt
|
||||
|
||||
|
@@ -116,6 +116,8 @@ uv 缓存 Caché de uv
|
||||
Yarn 缓存 Caché de Yarn
|
||||
pnpm 缓存 Caché de pnpm
|
||||
Bun 缓存 Caché de Bun
|
||||
Homebrew 下载缓存 Caché de descargas de Homebrew
|
||||
超过 1 天的 Homebrew 下载文件,不影响已安装软件 Descargas de Homebrew de más de un día; el software instalado no se verá afectado
|
||||
npm 下载缓存,由 npm 自行清理 Caché de descargas de npm, limpiada por npm
|
||||
Yarn 全局与离线镜像缓存,由 Yarn 自行清理 Caché global y espejo sin conexión de Yarn, limpiadas por Yarn
|
||||
pnpm 内容寻址存储,仅清理未被引用的包 Almacén direccionado por contenido de pnpm; solo se eliminan paquetes sin referencias
|
||||
|
||||
|
@@ -116,6 +116,8 @@ uv 缓存 Cache uv
|
||||
Yarn 缓存 Cache Yarn
|
||||
pnpm 缓存 Cache pnpm
|
||||
Bun 缓存 Cache Bun
|
||||
Homebrew 下载缓存 Cache des téléchargements Homebrew
|
||||
超过 1 天的 Homebrew 下载文件,不影响已安装软件 Téléchargements Homebrew datant de plus d’un jour ; les logiciels installés ne sont pas affectés
|
||||
npm 下载缓存,由 npm 自行清理 Cache de téléchargement npm, nettoyé par npm
|
||||
Yarn 全局与离线镜像缓存,由 Yarn 自行清理 Caches global et miroir hors ligne de Yarn, nettoyés par Yarn
|
||||
pnpm 内容寻址存储,仅清理未被引用的包 Stockage adressé par contenu de pnpm ; seuls les paquets non référencés sont supprimés
|
||||
|
||||
|
@@ -116,6 +116,8 @@ uv 缓存 uvキャッシュ
|
||||
Yarn 缓存 Yarnキャッシュ
|
||||
pnpm 缓存 pnpmキャッシュ
|
||||
Bun 缓存 Bunキャッシュ
|
||||
Homebrew 下载缓存 Homebrewダウンロードキャッシュ
|
||||
超过 1 天的 Homebrew 下载文件,不影响已安装软件 1日以上前のHomebrewダウンロードファイル。インストール済みのソフトウェアには影響しません
|
||||
npm 下载缓存,由 npm 自行清理 npmのダウンロードキャッシュ。npmでクリーンアップします
|
||||
Yarn 全局与离线镜像缓存,由 Yarn 自行清理 Yarnのグローバルキャッシュとオフラインミラー。Yarnでクリーンアップします
|
||||
pnpm 内容寻址存储,仅清理未被引用的包 pnpmのコンテンツアドレスストア。参照されていないパッケージのみ削除します
|
||||
|
||||
|
@@ -116,6 +116,8 @@ uv 缓存 uv 캐시
|
||||
Yarn 缓存 Yarn 캐시
|
||||
pnpm 缓存 pnpm 캐시
|
||||
Bun 缓存 Bun 캐시
|
||||
Homebrew 下载缓存 Homebrew 다운로드 캐시
|
||||
超过 1 天的 Homebrew 下载文件,不影响已安装软件 1일 이상 지난 Homebrew 다운로드 파일만 정리하며 설치된 소프트웨어에는 영향을 주지 않습니다
|
||||
npm 下载缓存,由 npm 自行清理 npm 다운로드 캐시이며 npm이 직접 정리합니다
|
||||
Yarn 全局与离线镜像缓存,由 Yarn 自行清理 Yarn 전역 및 오프라인 미러 캐시이며 Yarn이 직접 정리합니다
|
||||
pnpm 内容寻址存储,仅清理未被引用的包 pnpm 콘텐츠 주소 지정 저장소이며 참조되지 않은 패키지만 정리합니다
|
||||
|
||||
|
@@ -117,6 +117,8 @@ uv 缓存 uv 緩存
|
||||
Yarn 缓存 Yarn 緩存
|
||||
pnpm 缓存 pnpm 緩存
|
||||
Bun 缓存 Bun 緩存
|
||||
Homebrew 下载缓存 Homebrew 下載緩存
|
||||
超过 1 天的 Homebrew 下载文件,不影响已安装软件 超過 1 天的 Homebrew 下載檔案,不影響已安裝軟件
|
||||
npm 下载缓存,由 npm 自行清理 npm 下載緩存,由 npm 自行清理
|
||||
Yarn 全局与离线镜像缓存,由 Yarn 自行清理 Yarn 全局與離線鏡像緩存,由 Yarn 自行清理
|
||||
pnpm 内容寻址存储,仅清理未被引用的包 pnpm 內容尋址存儲,僅清理未被引用的包
|
||||
|
||||
|
@@ -241,6 +241,8 @@ uv 缓存 uv Cache
|
||||
Yarn 缓存 Yarn Cache
|
||||
pnpm 缓存 pnpm Cache
|
||||
Bun 缓存 Bun Cache
|
||||
Homebrew 下载缓存 Homebrew Download Cache
|
||||
超过 1 天的 Homebrew 下载文件,不影响已安装软件 Homebrew downloads older than one day; installed software is unaffected
|
||||
npm 下载缓存,由 npm 自行清理 npm download cache; cleaned by npm
|
||||
Yarn 全局与离线镜像缓存,由 Yarn 自行清理 Yarn global and offline mirror caches; cleaned by Yarn
|
||||
pnpm 内容寻址存储,仅清理未被引用的包 pnpm content-addressable store; only unreferenced packages are removed
|
||||
|
||||
@@ -66,11 +66,11 @@ public final class CleanerController {
|
||||
task = Task {
|
||||
let result = await CleanEngine.clean(scans, selected: selection, preferTrash: preferTrash, environment: environment)
|
||||
report = result
|
||||
phase = .finished
|
||||
// 清理后重新计算剩余可清理空间
|
||||
let rescanned = await CleanEngine.scan(rules, environment: Self.environment())
|
||||
self.scans = rescanned
|
||||
lastScan = Date()
|
||||
phase = .finished
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,12 +122,55 @@ struct DeveloperCacheRuleTests {
|
||||
let rules = Dictionary(uniqueKeysWithValues: RuleCatalog.rules().map { ($0.id, $0) })
|
||||
|
||||
for id in ["developer.npm", "developer.yarn", "developer.pnpm", "developer.bun",
|
||||
"developer.go", "developer.rust", "developer.uv"] {
|
||||
"developer.go", "developer.rust", "developer.uv", "developer.homebrew"] {
|
||||
#expect(rules[id]?.category == .developer)
|
||||
#expect(rules[id]?.selectedByDefault == false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func homebrewRuleOnlyCleansCompletedOlderDownloads() async throws {
|
||||
let home = try FakeHome()
|
||||
defer { home.remove() }
|
||||
let hash = String(repeating: "a", count: 64)
|
||||
let oldDownload = try home.file("Library/Caches/Homebrew/downloads/\(hash)--wget-1.0.tar.gz", age: 172_800)
|
||||
let recentDownload = try home.file("Library/Caches/Homebrew/downloads/\(hash)--curl-1.0.tar.gz", age: 60)
|
||||
let incomplete = try home.file("Library/Caches/Homebrew/downloads/\(hash)--git-1.0.tar.gz.incomplete", age: 172_800)
|
||||
let unrelated = try home.file("Library/Caches/Homebrew/downloads/notes.txt", age: 172_800)
|
||||
let backup = try home.file("Library/Caches/Homebrew/Backup/important.txt", age: 172_800)
|
||||
let environment = home.environment()
|
||||
|
||||
let generic = try RuleCatalog.userCaches.locate(environment)
|
||||
#expect(!generic.contains { $0.lastPathComponent == "Homebrew" })
|
||||
|
||||
let scans = await CleanEngine.scan([RuleCatalog.homebrewCache], environment: environment)
|
||||
let scan = try #require(scans.first)
|
||||
#expect(scan.items.map { $0.url.resolvingSymlinksInPath() } == [oldDownload.resolvingSymlinksInPath()])
|
||||
|
||||
let report = await CleanEngine.clean(scans, selected: [RuleCatalog.homebrewCache.id],
|
||||
preferTrash: false, environment: environment, log: nil)
|
||||
#expect(report.removedCount == 1)
|
||||
#expect(!FileManager.default.fileExists(atPath: oldDownload.path))
|
||||
for retained in [recentDownload, incomplete, unrelated, backup] {
|
||||
#expect(FileManager.default.fileExists(atPath: retained.path))
|
||||
}
|
||||
}
|
||||
|
||||
@Test func homebrewRuleRejectsLinkedDownloadDirectory() async throws {
|
||||
let home = try FakeHome()
|
||||
defer { home.remove() }
|
||||
let hash = String(repeating: "b", count: 64)
|
||||
let backup = try home.file("Library/Caches/Homebrew/Backup/\(hash)--archive.tar.gz", age: 172_800)
|
||||
let downloads = home.root.appendingPathComponent("Library/Caches/Homebrew/downloads")
|
||||
try FileManager.default.createSymbolicLink(at: downloads, withDestinationURL: backup.deletingLastPathComponent())
|
||||
|
||||
let environment = home.environment()
|
||||
#expect(try RuleCatalog.homebrewCache.locate(environment).isEmpty)
|
||||
let scans = await CleanEngine.scan([RuleCatalog.homebrewCache], environment: environment)
|
||||
|
||||
#expect(scans.first?.items.isEmpty == true)
|
||||
#expect(FileManager.default.fileExists(atPath: backup.path))
|
||||
}
|
||||
|
||||
@Test func packageManagerCachesUseTheirBrandLogos() {
|
||||
let rules = Dictionary(uniqueKeysWithValues: RuleCatalog.rules().map { ($0.id, $0) })
|
||||
let expected = [
|
||||
|
||||
Reference in new issue
Block a user