From d93dc9ce67c537c5f9d50b2b60e0394dedb413d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1rady=20Mil=C3=A1n?= <61704770+MilanVarady@users.noreply.github.com> Date: Sat, 28 Oct 2023 13:42:52 +0200 Subject: [PATCH] v1.2.2 update - Add option to completely uninstall apps (--zap flag) - Add option to view all updates (--greedy flag) - Fix incorrect self uninstall paths --- Applite.xcodeproj/project.pbxproj | 8 +- Applite/Model/Cask Data/Cask.swift | 19 +++- Applite/Model/Cask Data/CaskData.swift | 11 ++- Applite/Utilities/UninstallSelf.swift | 37 +++++--- Applite/Views/App Views/AppView.swift | 72 +++++++++------- Applite/Views/Commands.swift | 34 ++++---- .../Views/Detail Views/ActiveTasksView.swift | 23 +++-- Applite/Views/Detail Views/UpdateView.swift | 26 +++++- Localizable.xcstrings | 86 ++++++++++++++++++- appcast.xml | 11 +++ 10 files changed, 243 insertions(+), 84 deletions(-) diff --git a/Applite.xcodeproj/project.pbxproj b/Applite.xcodeproj/project.pbxproj index fbb9ce5..7228ae7 100644 --- a/Applite.xcodeproj/project.pbxproj +++ b/Applite.xcodeproj/project.pbxproj @@ -657,7 +657,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Applite/Preview Content\""; DEVELOPMENT_TEAM = 9CLTNBW4Z3; @@ -674,7 +674,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.2.1; + MARKETING_VERSION = 1.2.2; PRODUCT_BUNDLE_IDENTIFIER = dev.aerolite.Applite; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -692,7 +692,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Applite/Preview Content\""; DEVELOPMENT_TEAM = 9CLTNBW4Z3; @@ -709,7 +709,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.2.1; + MARKETING_VERSION = 1.2.2; PRODUCT_BUNDLE_IDENTIFIER = dev.aerolite.Applite; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Applite/Model/Cask Data/Cask.swift b/Applite/Model/Cask Data/Cask.swift index 09e292f..77d9ffe 100755 --- a/Applite/Model/Cask Data/Cask.swift +++ b/Applite/Model/Cask Data/Cask.swift @@ -150,9 +150,12 @@ final class Cask: Identifiable, Decodable, Hashable, ObservableObject { } /// Uninstalls the cask + /// - Parameters: + /// - caskData: ``CaskData`` object + /// - zap: If true the app will be uninstalled completely using the brew --zap flag /// - Returns: Bool - Whether the task has failed or not @discardableResult - func uninstall(caskData: CaskData) async -> Bool { + func uninstall(caskData: CaskData, zap: Bool = false) async -> Bool { defer { resetProgressState(caskData: caskData) } @@ -161,8 +164,10 @@ final class Cask: Identifiable, Decodable, Hashable, ObservableObject { caskData.busyCasks.insert(self) } + let arguments: [String] = if zap { ["--zap", self.id] } else { [self.id] } + return await runBrewCommand(command: "uninstall", - arguments: [self.id], + arguments: arguments, taskDescription: "Uninstalling", notificationSuccess: String(localized:"\(self.name) successfully uninstalled"), notificationFailure: "Failed to uninstall \(self.name)", @@ -254,10 +259,13 @@ final class Cask: Identifiable, Decodable, Hashable, ObservableObject { // Log and Notify if result.didFail { - sendNotification(title: notificationFailure, reason: .failure) Self.logger.error("Failed to run brew command \"\(command)\" with arguments \"\(arguments)\", output: \(result.output)") + + sendNotification(title: notificationFailure, reason: .failure) await MainActor.run { self.progressState = .failed(output: result.output) } } else { + Self.logger.notice("Successfully run brew command \"\(command)\" with arguments \"\(arguments)\", output: \(result.output)") + sendNotification(title: notificationSuccess, reason: .success) await MainActor.run { self.progressState = .success } try? await Task.sleep(for: .seconds(2)) @@ -330,11 +338,14 @@ final class Cask: Identifiable, Decodable, Hashable, ObservableObject { private func resetProgressState(caskData: CaskData) { Task { await MainActor.run { + // Only reset state if it's not failed if case .failed(_) = self.progressState { } else { - // Only reset state if it's not failed self.progressState = .idle caskData.busyCasks.remove(self) + + // Filter busy casks to make sure + caskData.filterBusyCasks() } } } diff --git a/Applite/Model/Cask Data/CaskData.swift b/Applite/Model/Cask Data/CaskData.swift index 95c58df..af68a37 100755 --- a/Applite/Model/Cask Data/CaskData.swift +++ b/Applite/Model/Cask Data/CaskData.swift @@ -247,8 +247,8 @@ final class CaskData: ObservableObject { (casksByCategory, casksByCategoryCoupled) = fillCategoryDicts() } - func refreshOutdatedApps() async -> Void { - let outdatedCaskIDs = await shell("\(BrewPaths.currentBrewExecutable) outdated --cask -q").output + func refreshOutdatedApps(greedy: Bool = false) async -> Void { + let outdatedCaskIDs = await shell("\(BrewPaths.currentBrewExecutable) outdated --cask \(greedy ? "-g" : "") -q").output .components(separatedBy: "\n") .filter({ $0.count > 0 }) // Remove empty strings .map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) }) // Trim whitespace @@ -262,4 +262,11 @@ final class CaskData: ObservableObject { Self.logger.info("Outdated apps refreshed") } + + /// Filters busy casks + func filterBusyCasks() { + self.busyCasks = self.busyCasks.filter { + $0.progressState != .idle + } + } } diff --git a/Applite/Utilities/UninstallSelf.swift b/Applite/Utilities/UninstallSelf.swift index 451edd3..97dae1c 100755 --- a/Applite/Utilities/UninstallSelf.swift +++ b/Applite/Utilities/UninstallSelf.swift @@ -6,6 +6,7 @@ // import Foundation +import os enum UninstallError: Error { case fileError @@ -13,27 +14,39 @@ enum UninstallError: Error { /// This function will uninstall Applite and all it's related files func uninstallSelf(deleteBrewCache: Bool) { + let logger = Logger() + + logger.notice("Applite uninstallation stated. deleteBrewCache: \(deleteBrewCache)") + // Delete related files and cache let command = """ - rm -rf "~/Library/Application Support/\(Bundle.main.appName)"; - rm -rf "~/Library/Application Support/\(Bundle.main.bundleIdentifier!)"; - rm -rf ~/Library/Containers/\(Bundle.main.bundleIdentifier!); - rm -rf ~/Library/Caches/\(Bundle.main.appName); - rm -rf ~/Library/Caches/\(Bundle.main.bundleIdentifier!); - rm -rf ~/Library/\(Bundle.main.appName); - rm -rf ~/Library/Preferences/*\(Bundle.main.bundleIdentifier!)*.plist; - rm -rf "~/Library/Saved Application State/\(Bundle.main.bundleIdentifier!).savedState"; - rm -rf ~/Library/SyncedPreferences/\(Bundle.main.bundleIdentifier!)*.plist; - rm -rf ~/Library/WebKit/\(Bundle.main.bundleIdentifier!); + rm -r "$HOME/Library/Application Support/\(Bundle.main.appName)"; + rm -r "$HOME/Library/Application Support/\(Bundle.main.bundleIdentifier!)"; + rm -r $HOME/Library/Containers/\(Bundle.main.bundleIdentifier!); + rm -r $HOME/Library/Caches/\(Bundle.main.appName); + rm -r $HOME/Library/Caches/\(Bundle.main.bundleIdentifier!); + rm -r $HOME/Library/\(Bundle.main.appName); + rm -r $HOME/Library/Preferences/*\(Bundle.main.bundleIdentifier!)*.plist; + rm -r "$HOME/Library/Saved Application State/\(Bundle.main.bundleIdentifier!).savedState"; + rm -r $HOME/Library/SyncedPreferences/\(Bundle.main.bundleIdentifier!)*.plist; + rm -r $HOME/Library/WebKit/\(Bundle.main.bundleIdentifier!); + rm -r $HOME/Library/HTTPStorages/dev.aerolite.Applite """ - shell(command) + logger.notice("Running command: \(command)") + + let result = shell(command) + + + logger.notice("Uninstall result: \(result.output)") // Homebrew cache if deleteBrewCache { - shell("rm -rf ~/Library/Caches/Homebrew") + shell("rm -rf $HOME/Library/Caches/Homebrew") } + logger.notice("Self destructing. Goodbye world!") + // Quit the app and remove it let process = Process() process.launchPath = "/bin/bash" diff --git a/Applite/Views/App Views/AppView.swift b/Applite/Views/App Views/AppView.swift index b6b07cf..a680b12 100755 --- a/Applite/Views/App Views/AppView.swift +++ b/Applite/Views/App Views/AppView.swift @@ -124,7 +124,7 @@ struct AppView: View { switch role { case .installAndManage: if cask.isInstalled { - OpenAndManageAppView(cask: cask, deleteButton: false, moreOptionsButton: true) + OpenAndManageAppView(cask: cask, deleteButton: false) } else { DownloadButton(cask: cask) .padding(.trailing, 5) @@ -134,7 +134,7 @@ struct AppView: View { UpdateButton(cask: cask) case .installed: - OpenAndManageAppView(cask: cask, deleteButton: true, moreOptionsButton: false) + OpenAndManageAppView(cask: cask, deleteButton: true) .padding(.trailing, 5) } } else { @@ -337,13 +337,14 @@ struct AppView: View { private struct OpenAndManageAppView: View { @StateObject var cask: Cask let deleteButton: Bool - let moreOptionsButton: Bool @EnvironmentObject var caskData: CaskData @State var appNotFoundShowing = false @State var showingPopover = false + @State private var isOptionKeyDown = false + var body: some View { // Lauch app Button("Open") { @@ -364,38 +365,47 @@ struct AppView: View { UninstallButton(cask: cask) } - if moreOptionsButton { - // More options popover - Button() { - showingPopover = true - } label: { - Image(systemName: "chevron.down") - .padding(.vertical) - .contentShape(Rectangle()) - } - .popover(isPresented: $showingPopover) { - VStack(alignment: .leading, spacing: 6) { - // Reinstall button - Button { - Task { - await cask.reinstall(caskData: caskData) - } - } label: { - Label("Reinstall", systemImage: "arrow.2.squarepath") + // More options popover + Button() { + showingPopover = true + } label: { + Image(systemName: "chevron.down") + .padding(.vertical) + .contentShape(Rectangle()) + } + .popover(isPresented: $showingPopover) { + VStack(alignment: .leading, spacing: 6) { + // Reinstall button + Button { + Task { + await cask.reinstall(caskData: caskData) } - - // Uninstall button - Button(role: .destructive) { - Task { - await cask.uninstall(caskData: caskData) - } - } label: { - Label("Uninstall", systemImage: "trash") + } label: { + Label("Reinstall", systemImage: "arrow.2.squarepath") + } + + // Uninstall button + Button(role: .destructive) { + Task { + await cask.uninstall(caskData: caskData) + } + } label: { + Label("Uninstall", systemImage: "trash") + .foregroundStyle(.red) + } + + // Uninstall completely button + Button(role: .destructive) { + Task { + await cask.uninstall(caskData: caskData, zap: true) } + } label: { + Label("Uninstall Completely", systemImage: "trash.fill") + .foregroundStyle(.red) } - .padding(8) - .buttonStyle(.plain) } + .padding(8) + .buttonStyle(.plain) } } } diff --git a/Applite/Views/Commands.swift b/Applite/Views/Commands.swift index 20d3952..9c79cbd 100755 --- a/Applite/Views/Commands.swift +++ b/Applite/Views/Commands.swift @@ -17,26 +17,26 @@ struct CommandsMenu: Commands { SidebarCommands() CommandGroup(replacing: .appInfo) { - Button("About \(Bundle.main.appName)") { - NSApplication.shared.orderFrontStandardAboutPanel( - options: [ - NSApplication.AboutPanelOptionKey.credits: NSAttributedString( - string: "MIT Licence", - attributes: [ - NSAttributedString.Key.font: NSFont.systemFont( - ofSize: NSFont.smallSystemFontSize) - ] - ), - NSApplication.AboutPanelOptionKey( - rawValue: "Copyright" - ): "© 2023 Milán Várady" + Button("About \(Bundle.main.appName)") { + NSApplication.shared.orderFrontStandardAboutPanel( + options: [ + NSApplication.AboutPanelOptionKey.credits: NSAttributedString( + string: "MIT Licence", + attributes: [ + NSAttributedString.Key.font: NSFont.systemFont( + ofSize: NSFont.smallSystemFontSize) ] - ) - } - } + ), + NSApplication.AboutPanelOptionKey( + rawValue: "Copyright" + ): "© 2023 Milán Várady" + ] + ) + } + } CommandGroup(before: .systemServices) { - Button("Uninstall...") { + Button("Uninstall Applite...") { openWindow(id: "uninstall-self") } diff --git a/Applite/Views/Detail Views/ActiveTasksView.swift b/Applite/Views/Detail Views/ActiveTasksView.swift index 65255f3..c4c7f93 100644 --- a/Applite/Views/Detail Views/ActiveTasksView.swift +++ b/Applite/Views/Detail Views/ActiveTasksView.swift @@ -11,17 +11,22 @@ struct ActiveTasksView: View { @EnvironmentObject var caskData: CaskData var body: some View { - VStack { - if caskData.busyCasks.isEmpty { - Text("No Active Tasks") - .font(.title) - } else { - AppGridView(casks: Array(caskData.busyCasks), appRole: .update) + ScrollView { + VStack { + if caskData.busyCasks.isEmpty { + Text("No Active Tasks") + .font(.title) + } else { + AppGridView(casks: Array(caskData.busyCasks), appRole: .update) + } + + Spacer() } - - Spacer() + .padding() + } + .onAppear { + caskData.filterBusyCasks() } - .padding() } } diff --git a/Applite/Views/Detail Views/UpdateView.swift b/Applite/Views/Detail Views/UpdateView.swift index d30a91e..426181e 100755 --- a/Applite/Views/Detail Views/UpdateView.swift +++ b/Applite/Views/Detail Views/UpdateView.swift @@ -18,6 +18,8 @@ struct UpdateView: View { @State var updateAllFinished = false @State var updateAllButtonRotation = 0.0 + @State var showingGreedyUpdateConfirm = false + // Filter outdated casks var casks: [Cask] { var filteredCasks = caskData.casks.filter { $0.isOutdated } @@ -73,8 +75,8 @@ struct UpdateView: View { Text("Update All") } } - .bigButton() - .padding(.top) + .bigButton(backgroundColor: .accentColor) + .padding(.vertical) .disabled(isUpdatingAll) } @@ -92,6 +94,26 @@ struct UpdateView: View { } .searchable(text: $searchText) .toolbar { + Button { + showingGreedyUpdateConfirm = true + } label: { + Label("Show All Updates", systemImage: "eye") + } + .labelStyle(.titleAndIcon) + .alert("Notice", isPresented: $showingGreedyUpdateConfirm) { + Button("Show All") { + Task { + await caskData.refreshOutdatedApps(greedy: true) + } + } + + Button("Cancel", role: .cancel) { } + } message: { + VStack { + Text("This will show updates from applications that have auto-update turned off, i.e., applications that are taking care of their own updates.") + } + } + // Refresh outdated casks if refreshing { SmallProgressView() diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 6afac1e..869ef14 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -1833,6 +1833,22 @@ } } }, + "Notice" : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Attention" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Figyelem!" + } + } + } + }, "Notifications" : { "localizations" : { "fr" : { @@ -2237,6 +2253,38 @@ } } }, + "Show All" : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher tout" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mutasd mindet" + } + } + } + }, + "Show All Updates" : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher toutes les mises à jour" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Összes frissítés megjelenítése" + } + } + } + }, "Sort by" : { "localizations" : { "fr" : { @@ -2421,6 +2469,22 @@ } } }, + "This will show updates from applications that have auto-update turned off, i.e., applications that are taking care of their own updates." : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cette option affiche les mises à jour des applications dont la mise à jour automatique est désactivée, c'est-à-dire les applications qui s'occupent elles-mêmes de leurs mises à jour." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ez olyan alkalmazások frissítéseit jeleníti meg, amelyeknél az automatikus frissítés ki van kapcsolva, azaz amelyek maguk gondoskodnak a saját frissítéseikről." + } + } + } + }, "This will uninstall all files and cache associated with %@." : { "localizations" : { "fr" : { @@ -2517,18 +2581,34 @@ } } }, - "Uninstall..." : { + "Uninstall Applite..." : { + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Désinstaller Applite..." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Az Applite törlése..." + } + } + } + }, + "Uninstall Completely" : { "localizations" : { "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Désinstaller..." + "value" : "Désinstaller totalement" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Törlés..." + "value" : "Teljes törlés" } } } diff --git a/appcast.xml b/appcast.xml index 6cf5f56..55de7df 100644 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,17 @@ Applite + + 1.2.2 + Sat, 28 Oct 2023 12:59:06 +0200 + 8 + 1.2.2 + 13.0 + + https://aerolite.dev/applite/releases/1.2.2.html + + + 1.2.1 Sat, 30 Sep 2023 14:17:47 +0200