From e2413e52d25c9db34662002f5c09792cc2447394 Mon Sep 17 00:00:00 2001 From: maxsoch Date: Thu, 9 Jul 2026 19:37:41 +0200 Subject: [PATCH] =?UTF-8?q?add=20brew=20cleanup:=20menu=20action,=20?= =?UTF-8?q?=E2=8C=A5=20purge=20variant,=20optional=20post-upgrade=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Cleanup menu item runs brew cleanup; holding ⌥ swaps it for a purge variant (cleanup --prune=all, which also drops cached downloads of current versions). A new opt-out-by-default setting interposes a plain cleanup between the end of any upgrade path and its closing refetch. Co-Authored-By: Claude Fable 5 --- BrewBar/BrewBarApp.swift | 48 +++++++++++++++++++++++--- BrewBar/Settings.swift | 5 +++ BrewBar/SettingsWindowController.swift | 13 +++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/BrewBar/BrewBarApp.swift b/BrewBar/BrewBarApp.swift index b56da5a..3743da2 100644 --- a/BrewBar/BrewBarApp.swift +++ b/BrewBar/BrewBarApp.swift @@ -76,6 +76,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { outdatedItem.submenu = outdatedSubmenu outdatedItem.isHidden = true // shown once a fetch finds outdated packages 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.separator()) @@ -252,6 +258,34 @@ class AppDelegate: NSObject, NSApplicationDelegate { 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() { if Settings.confirmBeforeUpgradeAll { guard confirmUpgrade() else { return } @@ -265,8 +299,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { let caskNames = self.cachedOutdated.casks.map(\.name) let upgradeCasksThenRefresh = { self.upgradeCasksSequentially(caskNames) { - // everything was just upgraded; don't chain an auto-upgrade - self.fetchOutdated(allowAutoUpgrade: false) + self.cleanupAfterUpgradeIfEnabled { + // everything was just upgraded; don't chain an auto-upgrade + self.fetchOutdated(allowAutoUpgrade: false) + } } } @@ -382,7 +418,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { let caskNames = casks ? cachedOutdated.casks.map(\.name) : [] let upgradeCasksThenRefresh = { self.upgradeCasksSequentially(caskNames) { - self.fetchOutdated(allowAutoUpgrade: false) + self.cleanupAfterUpgradeIfEnabled { + self.fetchOutdated(allowAutoUpgrade: false) + } } } @@ -427,7 +465,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { guard let formula = sender.representedObject as? String else { return } runBrew("upgrade \(formula)", askpassMessage: "upgrade \(formula)") { _ in - self.refreshAll() + self.cleanupAfterUpgradeIfEnabled { + self.refreshAll() + } } } diff --git a/BrewBar/Settings.swift b/BrewBar/Settings.swift index 60ab21b..24eac13 100644 --- a/BrewBar/Settings.swift +++ b/BrewBar/Settings.swift @@ -44,4 +44,9 @@ enum Settings { get { UserDefaults.standard.bool(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") } + } } diff --git a/BrewBar/SettingsWindowController.swift b/BrewBar/SettingsWindowController.swift index f5ad924..935abd5 100644 --- a/BrewBar/SettingsWindowController.swift +++ b/BrewBar/SettingsWindowController.swift @@ -22,6 +22,7 @@ class SettingsWindowController: NSWindowController { var notifyCheckbox: NSButton! var showCountCheckbox: NSButton! var confirmCheckbox: NSButton! + var cleanupCheckbox: NSButton! convenience init(appDelegate: AppDelegate) { let window = NSWindow( @@ -125,6 +126,12 @@ class SettingsWindowController: NSWindowController { action: #selector(toggleConfirm(_:)) ) + cleanupCheckbox = NSButton( + checkboxWithTitle: "Run brew cleanup after upgrades", + target: self, + action: #selector(toggleCleanup(_:)) + ) + let stack = NSStackView(views: [ frequencyRow, customRow, @@ -138,6 +145,7 @@ class SettingsWindowController: NSWindowController { notifyCheckbox, showCountCheckbox, confirmCheckbox, + cleanupCheckbox, launchAtLoginCheckbox, ]) stack.orientation = .vertical @@ -179,6 +187,7 @@ class SettingsWindowController: NSWindowController { notifyCheckbox.state = Settings.notifyOnNewUpdates ? .on : .off showCountCheckbox.state = Settings.showCountInMenuBar ? .on : .off confirmCheckbox.state = Settings.confirmBeforeUpgradeAll ? .on : .off + cleanupCheckbox.state = Settings.cleanupAfterUpgrade ? .on : .off } func separator() -> NSBox { @@ -311,6 +320,10 @@ class SettingsWindowController: NSWindowController { Settings.confirmBeforeUpgradeAll = sender.state == .on } + @objc func toggleCleanup(_ sender: NSButton) { + Settings.cleanupAfterUpgrade = sender.state == .on + } + @objc func toggleLaunchAtLogin(_ sender: NSButton) { do { if sender.state == .on {