Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,6 @@ build/
PLAN.md
macos-cache-cleanup.sh
xcuserdata/
*.xcuserstate
*.xcuserstate
opencode.json
.agents
11 changes: 4 additions & 7 deletions MacOSCleaner/App/MacOSCleanerApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ struct MacOSCleanerApp: App {
let engine = CleanupEngine(commandRunner: commandRunner)
self.cleanupViewModel = CleanupViewModel(engine: engine, journal: journal, settings: appSettings)

// Request permissions at startup
let manager = permissionsManager
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
manager.showGuidanceIfNeeded()
}
// Preload Launch Services cache
Task { await LSRegisterCache().warmup() }
}

private static func installCrashHandlers() {
Expand Down Expand Up @@ -66,13 +63,13 @@ struct MacOSCleanerApp: App {
}
.commands {
CommandGroup(replacing: .appInfo) {
Button("About MacOS Cleaner") {
Button("about_title".localized) {
openWindow(id: "about")
}
}
}

Window("About MacOS Cleaner", id: "about") {
Window("about_title".localized, id: "about") {
AboutView()
}
.windowResizability(.contentSize)
Expand Down
4 changes: 2 additions & 2 deletions MacOSCleaner/App/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ struct RootView: View {
} detail: {
if let selectedItem {
contentView(for: selectedItem)
.frame(minWidth: 900, minHeight: 600)
.frame(minWidth: 800, minHeight: 600)
} else {
Text("sidebar_select_item".localized)
.foregroundColor(.secondary)
.frame(minWidth: 900, minHeight: 600)
.frame(minWidth: 800, minHeight: 600)
}
}

Expand Down
85 changes: 85 additions & 0 deletions MacOSCleaner/Domains/Cleanup/CleanupCategory+FixtureMapping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Foundation

extension CleanupCategory {

public var localizedTitle: String {
"category.\(rawValue)".localized
}

public static func fromFixturePathType(_ pathType: CleanupPathType) -> [CleanupCategory] {
switch pathType {
case .caches:
return [.appCaches, .browserCaches, .ideCaches, .messagingMedia, .languageCaches, .systemCaches]
case .applicationSupport:
return [.ideCaches, .orphanedRemnants, .orphanedFiles]
case .containers:
return [.appContainers, .orphanedRemnants]
case .groupContainers:
return [.appContainers, .orphanedRemnants]
case .preferences:
return [.orphanedRemnants, .savedAppState]
case .logs:
return [.userLogs, .crashReporter]
case .savedState:
return [.savedAppState]
case .httpStorages:
return [.orphanedFiles]
case .webkit:
return [.orphanedFiles]
case .applicationScripts:
return [.orphanedRemnants]
case .launchAgents:
return [.launchAgents]
case .launchDaemons:
return [.launchDaemons]
case .privilegedHelperTools:
return [.privilegedHelpers]
case .pkgReceipts:
return [.pkgReceipts]
case .internetPlugins:
return [.internetPlugins]
case .cookies:
return [.orphanedFiles]
case .diagnosticReports:
return [.crashReporter]
case .cloudDocs:
return [.cloudDocs]
case .sharedFileLists:
return [.sharedFileLists]
case .developerArtifacts:
return []
}
}

public static func fromCleanupJsonScannerId(_ scannerId: String) -> CleanupCategory? {
switch scannerId {
case "browser_data_scanner": return .browserCaches
case "logs_scanner": return .userLogs
case "language_toolchain_scanner": return .languageCaches
case "cache_scanner": return .appCaches
case "xcode_derived_data_scanner": return .xcode
case "simulator_scanner": return .iosSimulators
case "quicklook_cache_scanner": return .systemCaches
case "photo_library_cache_scanner": return .photosCache
case "voice_memos_scanner": return .voiceMemos
case "garageband_logic_scanner": return .garageBandLogic
case "imovie_final_cut_scanner": return .iMovieFinalCut
case "garmin_fitbit_scanner": return .garminFitbit
case "old_backups_scanner": return .oldBackups
case "mail_attachments_scanner": return .mailDownloads
case "dns_cache_scanner": return .dnsFlush
case "font_cache_scanner": return .fontCache
case "sleep_image_scanner": return .sleepImage
case "duplicate_files_scanner": return .duplicateFiles
case "unused_apps_scanner": return .unusedApps
case "docker_scanner": return .docker
case "downloads_scanner": return .largeFiles
case "trash_scanner": return .scatteredJunk
case "time_machine_local_snapshots_scanner": return .timeMachineSnapshots
case "itunes_backup_scanner": return .iosBackups
case "spotlight_index_scanner": return .systemCaches
case "swap_files_scanner": return .systemCaches
default: return nil
}
}
}
82 changes: 44 additions & 38 deletions MacOSCleaner/Domains/Cleanup/CleanupCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,49 +207,55 @@ public final class CleanupCoordinator: @unchecked Sendable {
}

private func parseSkippedFromLog(_ log: String) -> SkippedCleanupItem? {
let patterns: [(label: String, keywords: [String])] = [
("App containers", ["App containers"]),
("Orphaned remnants", ["Orphaned remnants"]),
("Orphaned files", ["Orphaned files"]),
("Large files", ["Large files"]),
("Dynamic cache discovery", ["Dynamic cache discovery"]),
("App caches", ["App caches"]),
("Package managers", ["Package managers"]),
("Gradle + Maven", ["Gradle"]),
("Flutter / Dart", ["Flutter"]),
("Xcode", ["Xcode"]),
("iOS Simulators", ["iOS Simulators"]),
("Android caches", ["Android caches"]),
("Android SDK", ["Android SDK"]),
("IDE / Electron caches", ["IDE"]),
("Browser caches", ["Browser caches"]),
("Messaging / media", ["Messaging"]),
("Docker", ["Docker"]),
("Language caches", ["Language caches"]),
("User logs", ["User logs"]),
("System caches", ["System caches"]),
("Dotfile caches", ["Dotfile caches"]),
("Scattered junk", ["Scattered junk"]),
("Time Machine Snapshots", ["Time Machine"]),
("iOS Backups", ["iOS Backups"]),
("Mail Downloads", ["Mail Downloads"]),
("Saved Application State", ["Saved Application State"]),
("Crash Reporter", ["Crash Reporter"]),
("AssetsV2", ["AssetsV2"]),
("CloudKit Cache", ["CloudKit"]),
("Swift Package Manager Cache", ["SwiftPM"]),
("Carthage Cache", ["Carthage"]),
("Steam Cache", ["Steam"]),
("Microsoft Teams Cache", ["Teams"]),
("Adobe Caches", ["Adobe"]),
("Chrome Extra Caches", ["Chrome"]),
let patterns: [(category: CleanupCategory?, keywords: [String])] = [
(.appContainers, ["App containers"]),
(.orphanedRemnants, ["Orphaned remnants"]),
(.orphanedFiles, ["Orphaned files"]),
(.largeFiles, ["Large files"]),
(.dynamicCacheDiscovery, ["Dynamic cache discovery"]),
(.appCaches, ["App caches"]),
(.packageManagers, ["Package managers"]),
(.gradleMaven, ["Gradle"]),
(.flutterDart, ["Flutter"]),
(.xcode, ["Xcode"]),
(.iosSimulators, ["iOS Simulators"]),
(.androidCaches, ["Android caches"]),
(.androidSDK, ["Android SDK"]),
(.ideCaches, ["IDE"]),
(.browserCaches, ["Browser caches"]),
(.messagingMedia, ["Messaging"]),
(.docker, ["Docker"]),
(.languageCaches, ["Language caches"]),
(.userLogs, ["User logs"]),
(.systemCaches, ["System caches"]),
(.dotfileCaches, ["Dotfile caches"]),
(.scatteredJunk, ["Scattered junk"]),
(.timeMachineSnapshots, ["Time Machine"]),
(.iosBackups, ["iOS Backups"]),
(.mailDownloads, ["Mail Downloads"]),
(.savedAppState, ["Saved Application State"]),
(.crashReporter, ["Crash Reporter"]),
(.assetsV2, ["AssetsV2"]),
(.cloudKitCache, ["CloudKit"]),
(.swiftPMCache, ["SwiftPM"]),
(.carthageCache, ["Carthage"]),
(.steamCache, ["Steam"]),
(.teamsCache, ["Teams"]),
(.adobeCaches, ["Adobe"]),
(.chromeExtraCaches, ["Chrome"]),
]

guard let matched = patterns.first(where: { $0.keywords.contains(where: { log.contains($0) }) }) else {
return nil
}

// Extract reason — everything between "—" and ", skipped"
let label: String
if let category = matched.category {
label = category.localizedTitle
} else {
label = matched.keywords[0]
}

let reason: String
if let range = log.range(of: "— ") {
let afterDash = log[range.upperBound...]
Expand All @@ -262,7 +268,7 @@ public final class CleanupCoordinator: @unchecked Sendable {
reason = "unknown"
}

return SkippedCleanupItem(label: matched.label, reason: reason)
return SkippedCleanupItem(label: label, reason: reason)
}

@MainActor
Expand Down
Loading
Loading