Merge branch 'feature/cleanup'

Resolved by combining both sides in upgradeSingle (two-param runBrew
closure from fix/offline-refresh + cleanup hook from feature/cleanup)
and adapting the cleanup branch's runBrew calls to the new signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
maxsoch 2026-07-13 18:36:08 +02:00
commit 6a1c92a6fd
3 changed files with 62 additions and 4 deletions

View file

@ -82,6 +82,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
outdatedItem.submenu = outdatedSubmenu outdatedItem.submenu = outdatedSubmenu
outdatedItem.isHidden = true // shown once a fetch finds outdated packages outdatedItem.isHidden = true // shown once a fetch finds outdated packages
menu.addItem(outdatedItem) menu.addItem(outdatedItem)
menu.addItem(NSMenuItem(title: "🧹 Cleanup", action: #selector(cleanupAction), keyEquivalent: ""))
// isAlternate swaps this in for the item right above while is held
let purgeItem = NSMenuItem(title: "🧹 Cleanup (purge cache)", action: #selector(cleanupPurgeAction), keyEquivalent: "")
purgeItem.keyEquivalentModifierMask = .option
purgeItem.isAlternate = true
menu.addItem(purgeItem)
menu.addItem(NSMenuItem(title: "📜 Logs", action: #selector(showLogs), keyEquivalent: "l")) menu.addItem(NSMenuItem(title: "📜 Logs", action: #selector(showLogs), keyEquivalent: "l"))
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
@ -271,6 +277,34 @@ class AppDelegate: NSObject, NSApplicationDelegate {
refreshAll() refreshAll()
} }
@objc func cleanupAction() {
runCleanup(purgeCache: false)
}
@objc func cleanupPurgeAction() {
runCleanup(purgeCache: true)
}
/// --prune=all also deletes cached downloads brew would normally keep
/// (current versions); the only cost is re-downloading at next install
func runCleanup(purgeCache: Bool) {
statusMenuItem.title = "Cleaning up..."
runBrew(purgeCache ? "cleanup --prune=all" : "cleanup") { _, _ in
self.updateStatus(count: self.cachedOutdated.count)
}
}
/// Interposed between the end of an upgrade and its closing refetch;
/// passes straight through unless the user opted in.
func cleanupAfterUpgradeIfEnabled(completion: @escaping () -> Void) {
guard Settings.cleanupAfterUpgrade else {
completion()
return
}
statusMenuItem.title = "Cleaning up..."
runBrew("cleanup") { _, _ in completion() }
}
@objc func upgradeAll() { @objc func upgradeAll() {
if Settings.confirmBeforeUpgradeAll { if Settings.confirmBeforeUpgradeAll {
guard confirmUpgrade() else { return } guard confirmUpgrade() else { return }
@ -284,8 +318,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let caskNames = self.cachedOutdated.casks.map(\.name) let caskNames = self.cachedOutdated.casks.map(\.name)
let upgradeCasksThenRefresh = { let upgradeCasksThenRefresh = {
self.upgradeCasksSequentially(caskNames) { self.upgradeCasksSequentially(caskNames) {
// everything was just upgraded; don't chain an auto-upgrade self.cleanupAfterUpgradeIfEnabled {
self.fetchOutdated(allowAutoUpgrade: false) // everything was just upgraded; don't chain an auto-upgrade
self.fetchOutdated(allowAutoUpgrade: false)
}
} }
} }
@ -409,7 +445,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let caskNames = casks ? cachedOutdated.casks.map(\.name) : [] let caskNames = casks ? cachedOutdated.casks.map(\.name) : []
let upgradeCasksThenRefresh = { let upgradeCasksThenRefresh = {
self.upgradeCasksSequentially(caskNames) { self.upgradeCasksSequentially(caskNames) {
self.fetchOutdated(allowAutoUpgrade: false) self.cleanupAfterUpgradeIfEnabled {
self.fetchOutdated(allowAutoUpgrade: false)
}
} }
} }
@ -454,7 +492,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
guard let formula = sender.representedObject as? String else { return } guard let formula = sender.representedObject as? String else { return }
runBrew("upgrade \(formula)", askpassMessage: "upgrade \(formula)") { _, _ in runBrew("upgrade \(formula)", askpassMessage: "upgrade \(formula)") { _, _ in
self.refreshAll() self.cleanupAfterUpgradeIfEnabled {
self.refreshAll()
}
} }
} }

View file

@ -44,4 +44,9 @@ enum Settings {
get { UserDefaults.standard.bool(forKey: "confirmBeforeUpgradeAll") } get { UserDefaults.standard.bool(forKey: "confirmBeforeUpgradeAll") }
set { UserDefaults.standard.set(newValue, forKey: "confirmBeforeUpgradeAll") } set { UserDefaults.standard.set(newValue, forKey: "confirmBeforeUpgradeAll") }
} }
static var cleanupAfterUpgrade: Bool {
get { UserDefaults.standard.bool(forKey: "cleanupAfterUpgrade") }
set { UserDefaults.standard.set(newValue, forKey: "cleanupAfterUpgrade") }
}
} }

View file

@ -22,6 +22,7 @@ class SettingsWindowController: NSWindowController {
var notifyCheckbox: NSButton! var notifyCheckbox: NSButton!
var showCountCheckbox: NSButton! var showCountCheckbox: NSButton!
var confirmCheckbox: NSButton! var confirmCheckbox: NSButton!
var cleanupCheckbox: NSButton!
convenience init(appDelegate: AppDelegate) { convenience init(appDelegate: AppDelegate) {
let window = NSWindow( let window = NSWindow(
@ -125,6 +126,12 @@ class SettingsWindowController: NSWindowController {
action: #selector(toggleConfirm(_:)) action: #selector(toggleConfirm(_:))
) )
cleanupCheckbox = NSButton(
checkboxWithTitle: "Run brew cleanup after upgrades",
target: self,
action: #selector(toggleCleanup(_:))
)
let stack = NSStackView(views: [ let stack = NSStackView(views: [
frequencyRow, frequencyRow,
customRow, customRow,
@ -138,6 +145,7 @@ class SettingsWindowController: NSWindowController {
notifyCheckbox, notifyCheckbox,
showCountCheckbox, showCountCheckbox,
confirmCheckbox, confirmCheckbox,
cleanupCheckbox,
launchAtLoginCheckbox, launchAtLoginCheckbox,
]) ])
stack.orientation = .vertical stack.orientation = .vertical
@ -179,6 +187,7 @@ class SettingsWindowController: NSWindowController {
notifyCheckbox.state = Settings.notifyOnNewUpdates ? .on : .off notifyCheckbox.state = Settings.notifyOnNewUpdates ? .on : .off
showCountCheckbox.state = Settings.showCountInMenuBar ? .on : .off showCountCheckbox.state = Settings.showCountInMenuBar ? .on : .off
confirmCheckbox.state = Settings.confirmBeforeUpgradeAll ? .on : .off confirmCheckbox.state = Settings.confirmBeforeUpgradeAll ? .on : .off
cleanupCheckbox.state = Settings.cleanupAfterUpgrade ? .on : .off
} }
func separator() -> NSBox { func separator() -> NSBox {
@ -311,6 +320,10 @@ class SettingsWindowController: NSWindowController {
Settings.confirmBeforeUpgradeAll = sender.state == .on Settings.confirmBeforeUpgradeAll = sender.state == .on
} }
@objc func toggleCleanup(_ sender: NSButton) {
Settings.cleanupAfterUpgrade = sender.state == .on
}
@objc func toggleLaunchAtLogin(_ sender: NSButton) { @objc func toggleLaunchAtLogin(_ sender: NSButton) {
do { do {
if sender.state == .on { if sender.state == .on {