mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 03:48:55 +00:00
Updates
This commit is contained in:
@@ -47,7 +47,7 @@ class AppInfoFetcher {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let appName = wrapped ? path.deletingLastPathComponent().deletingLastPathComponent().deletingPathExtension().lastPathComponent.capitalizingFirstLetter() : path.deletingPathExtension().lastPathComponent.capitalizingFirstLetter()
|
let appName = wrapped ? path.deletingLastPathComponent().deletingLastPathComponent().deletingPathExtension().lastPathComponent.capitalizingFirstLetter() : path.localizedName().capitalizingFirstLetter()
|
||||||
|
|
||||||
let appVersion = (bundle.infoDictionary?["CFBundleShortVersionString"] as? String)?.isEmpty ?? true
|
let appVersion = (bundle.infoDictionary?["CFBundleShortVersionString"] as? String)?.isEmpty ?? true
|
||||||
? bundle.infoDictionary?["CFBundleVersion"] as? String ?? ""
|
? bundle.infoDictionary?["CFBundleVersion"] as? String ?? ""
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class AppState: ObservableObject {
|
|||||||
@Published var finderExtensionEnabled: Bool = false
|
@Published var finderExtensionEnabled: Bool = false
|
||||||
@Published var showUninstallAlert: Bool = false
|
@Published var showUninstallAlert: Bool = false
|
||||||
@Published var oneShotMode: Bool = false
|
@Published var oneShotMode: Bool = false
|
||||||
|
@Published var sentinelMode: Bool = false
|
||||||
@Published var showConditionBuilder: Bool = false
|
@Published var showConditionBuilder: Bool = false
|
||||||
|
|
||||||
var operationQueueLeftover = OperationQueue()
|
var operationQueueLeftover = OperationQueue()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class DeeplinkManager {
|
|||||||
static let scheme = "pear"
|
static let scheme = "pear"
|
||||||
static let host = "com.alienator88.Pearcleaner"
|
static let host = "com.alienator88.Pearcleaner"
|
||||||
static let query = "path"
|
static let query = "path"
|
||||||
|
static let brew = "brew"
|
||||||
}
|
}
|
||||||
|
|
||||||
func manage(url: URL, appState: AppState, locations: Locations) {
|
func manage(url: URL, appState: AppState, locations: Locations) {
|
||||||
@@ -37,11 +38,19 @@ class DeeplinkManager {
|
|||||||
let pathURL = URL(fileURLWithPath: path)
|
let pathURL = URL(fileURLWithPath: path)
|
||||||
let appInfo = AppInfoFetcher.getAppInfo(atPath: pathURL)
|
let appInfo = AppInfoFetcher.getAppInfo(atPath: pathURL)
|
||||||
showAppInFiles(appInfo: appInfo!, appState: appState, locations: locations, showPopover: $showPopover)
|
showAppInFiles(appInfo: appInfo!, appState: appState, locations: locations, showPopover: $showPopover)
|
||||||
|
// Check if the 'trash' parameter is set to true
|
||||||
|
if let brewValue = queryItems.first(where: { $0.name == DeepLinkConstants.brew })?.value, brewValue == "true" {
|
||||||
|
updateOnMain {
|
||||||
|
appState.sentinelMode = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if oneShotMode {
|
if oneShotMode {
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
appState.oneShotMode = true
|
appState.oneShotMode = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
printOS("No path query parameter found in the URL")
|
printOS("No path query parameter found in the URL")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import AlinFoundation
|
|||||||
|
|
||||||
|
|
||||||
// Get all apps from /Applications and ~/Applications
|
// Get all apps from /Applications and ~/Applications
|
||||||
func getSortedApps(paths: [String], appState: AppState) -> [AppInfo] {
|
func getSortedApps(paths: [String]) -> [AppInfo] {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
var apps: [URL] = []
|
var apps: [URL] = []
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import AppKit
|
|||||||
import AlinFoundation
|
import AlinFoundation
|
||||||
|
|
||||||
class ReversePathsSearcher {
|
class ReversePathsSearcher {
|
||||||
private let appState: AppState
|
private let appState: AppState?
|
||||||
private let locations: Locations
|
private let locations: Locations
|
||||||
private let fsm: FolderSettingsManager
|
private let fsm: FolderSettingsManager
|
||||||
private let fileManager = FileManager.default
|
private let fileManager = FileManager.default
|
||||||
@@ -21,7 +21,7 @@ class ReversePathsSearcher {
|
|||||||
private let dispatchGroup = DispatchGroup()
|
private let dispatchGroup = DispatchGroup()
|
||||||
private let sortedApps: [AppInfo]
|
private let sortedApps: [AppInfo]
|
||||||
|
|
||||||
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager, sortedApps: [AppInfo]) {
|
init(appState: AppState? = nil, locations: Locations, fsm: FolderSettingsManager, sortedApps: [AppInfo]) {
|
||||||
self.appState = appState
|
self.appState = appState
|
||||||
self.locations = locations
|
self.locations = locations
|
||||||
self.fsm = fsm
|
self.fsm = fsm
|
||||||
@@ -37,14 +37,22 @@ class ReversePathsSearcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reversePathsSearchCLI() -> [URL] {
|
||||||
|
self.processLocationsCLI()
|
||||||
|
return collection
|
||||||
|
}
|
||||||
|
|
||||||
private func processLocations() {
|
private func processLocations() {
|
||||||
|
|
||||||
for location in locations.reverse.paths where fileManager.fileExists(atPath: location) {
|
for location in locations.reverse.paths where fileManager.fileExists(atPath: location) {
|
||||||
|
|
||||||
dispatchGroup.enter()
|
dispatchGroup.enter()
|
||||||
processLocation(location)
|
processLocation(location)
|
||||||
dispatchGroup.leave()
|
dispatchGroup.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func processLocationsCLI() {
|
||||||
|
for location in locations.reverse.paths where fileManager.fileExists(atPath: location) {
|
||||||
|
processLocation(location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +140,8 @@ class ReversePathsSearcher {
|
|||||||
updatedZombieFile.fileSize = self.fileSize
|
updatedZombieFile.fileSize = self.fileSize
|
||||||
updatedZombieFile.fileSizeLogical = self.fileSizeLogical
|
updatedZombieFile.fileSizeLogical = self.fileSizeLogical
|
||||||
updatedZombieFile.fileIcon = self.fileIcon
|
updatedZombieFile.fileIcon = self.fileIcon
|
||||||
self.appState.zombieFile = updatedZombieFile
|
self.appState?.zombieFile = updatedZombieFile
|
||||||
self.appState.showProgress = false
|
self.appState?.showProgress = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import AppKit
|
|||||||
func reloadAppsList(appState: AppState, fsm: FolderSettingsManager) {
|
func reloadAppsList(appState: AppState, fsm: FolderSettingsManager) {
|
||||||
appState.reload = true
|
appState.reload = true
|
||||||
updateOnBackground {
|
updateOnBackground {
|
||||||
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
|
let sortedApps = getSortedApps(paths: fsm.folderPaths)
|
||||||
// Update UI on the main thread
|
// Update UI on the main thread
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
appState.sortedApps = sortedApps
|
appState.sortedApps = sortedApps
|
||||||
@@ -59,7 +59,7 @@ func findAndSetWindowFrame(named titles: [String], windowSettings: WindowSetting
|
|||||||
|
|
||||||
|
|
||||||
// Process CLI // ========================================================================================================
|
// Process CLI // ========================================================================================================
|
||||||
func processCLI(arguments: [String], appState: AppState, locations: Locations) {
|
func processCLI(arguments: [String], appState: AppState, locations: Locations, fsm: FolderSettingsManager) {
|
||||||
let options = Array(arguments.dropFirst()) // Remove the first argument (binary path)
|
let options = Array(arguments.dropFirst()) // Remove the first argument (binary path)
|
||||||
|
|
||||||
// Private function to list files for uninstall, using the provided path
|
// Private function to list files for uninstall, using the provided path
|
||||||
@@ -67,11 +67,11 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations) {
|
|||||||
// Convert the provided string path to a URL
|
// Convert the provided string path to a URL
|
||||||
let url = URL(fileURLWithPath: path)
|
let url = URL(fileURLWithPath: path)
|
||||||
|
|
||||||
// print("[BETA] Pearcleaner CLI | List Files:\n")
|
print("[BETA] Pearcleaner CLI | List Application Files:\n")
|
||||||
|
|
||||||
// Fetch the app info and safely unwrap
|
// Fetch the app info and safely unwrap
|
||||||
guard let appInfo = AppInfoFetcher.getAppInfo(atPath: url) else {
|
guard let appInfo = AppInfoFetcher.getAppInfo(atPath: url) else {
|
||||||
print("Error: Invalid path or unable to fetch app info at path: \(path)")
|
print("Error: Invalid path or unable to fetch app info at path: \(path)\n")
|
||||||
exit(1) // Exit with non-zero code to indicate failure
|
exit(1) // Exit with non-zero code to indicate failure
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +85,27 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations) {
|
|||||||
for path in foundPaths {
|
for path in foundPaths {
|
||||||
print(path.path)
|
print(path.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("\nFound \(foundPaths.count) application files.\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private function to list leftover files for uninstall, using the provided path
|
||||||
|
func listLeftoverFiles() {
|
||||||
|
print("[BETA] Pearcleaner CLI | List Leftover Files:\n")
|
||||||
|
|
||||||
|
// Get installed apps for filtering
|
||||||
|
let sortedApps = getSortedApps(paths: fsm.folderPaths)
|
||||||
|
|
||||||
|
// Find leftover files
|
||||||
|
let foundPaths = ReversePathsSearcher(locations: locations, fsm: fsm, sortedApps: sortedApps)
|
||||||
|
.reversePathsSearchCLI()
|
||||||
|
|
||||||
|
// Print each path in the array to the console
|
||||||
|
for path in foundPaths {
|
||||||
|
print(path.path)
|
||||||
|
}
|
||||||
|
print("\nFound \(foundPaths.count) leftover files.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private function to uninstall the application bundle at a given path
|
// Private function to uninstall the application bundle at a given path
|
||||||
@@ -95,17 +116,17 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations) {
|
|||||||
|
|
||||||
// Fetch the app info and safely unwrap
|
// Fetch the app info and safely unwrap
|
||||||
guard let appInfo = AppInfoFetcher.getAppInfo(atPath: url) else {
|
guard let appInfo = AppInfoFetcher.getAppInfo(atPath: url) else {
|
||||||
print("Error: Invalid path or unable to fetch app info at path: \(path)")
|
print("Error: Invalid path or unable to fetch app info at path: \(path)\n")
|
||||||
exit(1) // Exit with non-zero code to indicate failure
|
exit(1) // Exit with non-zero code to indicate failure
|
||||||
}
|
}
|
||||||
|
|
||||||
killApp(appId: appInfo.bundleIdentifier) {
|
killApp(appId: appInfo.bundleIdentifier) {
|
||||||
let success = moveFilesToTrashCLI(at: [appInfo.path])
|
let success = moveFilesToTrashCLI(at: [appInfo.path])
|
||||||
if success {
|
if success {
|
||||||
print("Application moved to the trash successfully.")
|
print("Application moved to the trash successfully.\n")
|
||||||
exit(0)
|
exit(0)
|
||||||
} else {
|
} else {
|
||||||
print("Failed to move application to trash.")
|
print("Failed to move application to trash.\n")
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +136,7 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations) {
|
|||||||
func uninstallAll(at path: String) {
|
func uninstallAll(at path: String) {
|
||||||
// Convert the provided string path to a URL
|
// Convert the provided string path to a URL
|
||||||
let url = URL(fileURLWithPath: path)
|
let url = URL(fileURLWithPath: path)
|
||||||
print("[BETA] Pearcleaner CLI | Uninstall Application + Related Files:\n")
|
print("[BETA] Pearcleaner CLI | Uninstall Application & Related Files:\n")
|
||||||
|
|
||||||
// Fetch the app info and safely unwrap
|
// Fetch the app info and safely unwrap
|
||||||
guard let appInfo = AppInfoFetcher.getAppInfo(atPath: url) else {
|
guard let appInfo = AppInfoFetcher.getAppInfo(atPath: url) else {
|
||||||
@@ -132,46 +153,75 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations) {
|
|||||||
killApp(appId: appInfo.bundleIdentifier) {
|
killApp(appId: appInfo.bundleIdentifier) {
|
||||||
let success = moveFilesToTrashCLI(at: Array(foundPaths))
|
let success = moveFilesToTrashCLI(at: Array(foundPaths))
|
||||||
if success {
|
if success {
|
||||||
print("The following files have been moved to the trash successfully:\n")
|
print("The application and related files have been moved to the trash successfully.\n")
|
||||||
// Print each path in the Set to the console
|
|
||||||
for path in foundPaths {
|
|
||||||
print(path.path)
|
|
||||||
}
|
|
||||||
exit(0)
|
exit(0)
|
||||||
} else {
|
} else {
|
||||||
print("Failed to move application and related files to trash.")
|
print("Failed to move application and related files to trash.\n")
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Private function to remove the leftover files
|
||||||
|
func removeLeftoverFiles() {
|
||||||
|
print("[BETA] Pearcleaner CLI | Remove Leftover Files:\n")
|
||||||
|
|
||||||
|
// Get installed apps for filtering
|
||||||
|
let sortedApps = getSortedApps(paths: fsm.folderPaths)
|
||||||
|
|
||||||
|
// Find leftover files
|
||||||
|
let foundPaths = ReversePathsSearcher(locations: locations, fsm: fsm, sortedApps: sortedApps)
|
||||||
|
.reversePathsSearchCLI()
|
||||||
|
|
||||||
|
let success = moveFilesToTrashCLI(at: foundPaths)
|
||||||
|
if success {
|
||||||
|
print("Leftover files have been moved to the trash successfully.\n")
|
||||||
|
exit(0)
|
||||||
|
} else {
|
||||||
|
print("Failed to move leftover files to trash.\n")
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle help option (-h or --help)
|
// Handle help option (-h or --help)
|
||||||
if options.contains("-h") || options.contains("--help") {
|
if options.contains("-h") || options.contains("--help") {
|
||||||
displayHelp()
|
displayHelp()
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle --list or -l option with a path argument
|
// Handle --list or -l option with a path argument for listing app bundle files
|
||||||
if let listIndex = options.firstIndex(where: { $0 == "--list" || $0 == "-l" }), listIndex + 1 < options.count {
|
if let listIndex = options.firstIndex(where: { $0 == "--list" || $0 == "-l" }), listIndex + 1 < options.count {
|
||||||
let path = options[listIndex + 1] // Path provided after --list or -l
|
let path = options[listIndex + 1] // Path provided after --list or -l
|
||||||
listFiles(at: path)
|
listFiles(at: path)
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle --uninstall or -u option with a path argument
|
// Handle --listlf or -lf option with a path argument for listing leftover files
|
||||||
|
if options.contains("--list-leftover") || options.contains("-lf") {
|
||||||
|
listLeftoverFiles()
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle --uninstall or -u option with a path argument to uninstall app bundle only
|
||||||
if let uninstallIndex = options.firstIndex(where: { $0 == "--uninstall" || $0 == "-u" }), uninstallIndex + 1 < options.count {
|
if let uninstallIndex = options.firstIndex(where: { $0 == "--uninstall" || $0 == "-u" }), uninstallIndex + 1 < options.count {
|
||||||
let path = options[uninstallIndex + 1] // Path provided after --uninstall or -u
|
let path = options[uninstallIndex + 1] // Path provided after --uninstall or -u
|
||||||
uninstallApp(at: path)
|
uninstallApp(at: path)
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle --uninstall-all or -ua option with a path argument
|
// Handle --uninstall-all or -ua option with a path argument to uninstall app bundle and related files
|
||||||
if let uninstallAllIndex = options.firstIndex(where: { $0 == "--uninstall-all" || $0 == "-ua" }), uninstallAllIndex + 1 < options.count {
|
if let uninstallAllIndex = options.firstIndex(where: { $0 == "--uninstall-all" || $0 == "-ua" }), uninstallAllIndex + 1 < options.count {
|
||||||
let path = options[uninstallAllIndex + 1] // Path provided after --uninstall-all or -ua
|
let path = options[uninstallAllIndex + 1] // Path provided after --uninstall-all or -ua
|
||||||
uninstallAll(at: path)
|
uninstallAll(at: path)
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle --uninstall-lf or -ulf option with a path argument for listing leftover files
|
||||||
|
if options.contains("--remove-leftover") || options.contains("-rl") {
|
||||||
|
removeLeftoverFiles()
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
// If no valid option was provided, show the help menu by default
|
// If no valid option was provided, show the help menu by default
|
||||||
displayHelp()
|
displayHelp()
|
||||||
exit(0)
|
exit(0)
|
||||||
@@ -183,9 +233,11 @@ func displayHelp() {
|
|||||||
|
|
||||||
[BETA] Pearcleaner CLI | Usage:
|
[BETA] Pearcleaner CLI | Usage:
|
||||||
|
|
||||||
--list <path>, -l <path> Find application files available for uninstall at the specified path
|
--list <path>, -l <path> List application files available for uninstall at the specified path
|
||||||
--uninstall <path>, -u <path> Remove only the application bundle at the specified path
|
--list-leftover, -lf List leftover files available for removal
|
||||||
--uninstall-all <path>, -ua <path> Remove the application bundle and all related files at the specified path
|
--uninstall <path>, -u <path> Uninstall only the application bundle at the specified path
|
||||||
|
--uninstall-all <path>, -ua <path> Uninstall application bundle and ALL related files at the specified path
|
||||||
|
--remove-leftover, -rl Remove ALL leftover files (To ignore files, add them to the exception list within Pearcleaner settings)
|
||||||
--help, -h Show this help message
|
--help, -h Show this help message
|
||||||
|
|
||||||
""")
|
""")
|
||||||
@@ -197,8 +249,16 @@ func checkCLISymlink() -> Bool {
|
|||||||
let filePath = "/usr/local/bin/pearcleaner"
|
let filePath = "/usr/local/bin/pearcleaner"
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
|
|
||||||
// Check if the file exists at the given path
|
// Check if the file exists and is a symlink
|
||||||
return fileManager.fileExists(atPath: filePath)
|
guard fileManager.fileExists(atPath: filePath) else { return false }
|
||||||
|
|
||||||
|
// Check if the symlink points to the correct path
|
||||||
|
do {
|
||||||
|
let destination = try fileManager.destinationOfSymbolicLink(atPath: filePath)
|
||||||
|
return destination == Bundle.main.executablePath
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install/uninstall symlink for CLI
|
// Install/uninstall symlink for CLI
|
||||||
@@ -278,6 +338,93 @@ func manageFinderPlugin(install: Bool) {
|
|||||||
// Brew cleanup
|
// Brew cleanup
|
||||||
func caskCleanup(app: String) {
|
func caskCleanup(app: String) {
|
||||||
Task(priority: .high) {
|
Task(priority: .high) {
|
||||||
|
|
||||||
|
#if arch(x86_64)
|
||||||
|
let cmd = "/usr/local/bin/brew"
|
||||||
|
#elseif arch(arm64)
|
||||||
|
let cmd = "/opt/homebrew/bin/brew"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if let formattedApp = getCaskIdentifier(for: app) {
|
||||||
|
// App found, proceed with cleanup
|
||||||
|
let script = """
|
||||||
|
tell application "Terminal"
|
||||||
|
activate
|
||||||
|
do script "/bin/bash --noprofile --norc -c '
|
||||||
|
clear;
|
||||||
|
echo \\"[Pearcleaner] Homebrew cleanup for \(app):\n\\";
|
||||||
|
\(cmd) uninstall --cask \(formattedApp) --force;
|
||||||
|
\(cmd) cleanup;
|
||||||
|
killall Terminal;
|
||||||
|
'" in front window
|
||||||
|
end tell
|
||||||
|
"""
|
||||||
|
|
||||||
|
var error: NSDictionary?
|
||||||
|
if let appleScript = NSAppleScript(source: script) {
|
||||||
|
appleScript.executeAndReturnError(&error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
printOS("AppleScript Error: \(error)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printOS("Brew cleanup: No cask found for \(app).")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func getCaskIdentifier(for appName: String) -> String? {
|
||||||
|
|
||||||
|
#if arch(x86_64)
|
||||||
|
let caskroomPath = "/usr/local/Caskroom/"
|
||||||
|
#elseif arch(arm64)
|
||||||
|
let caskroomPath = "/opt/homebrew/Caskroom/"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
let lowercasedAppName = appName.lowercased()
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Get all cask directories from Caskroom, ignoring hidden files
|
||||||
|
let casks = try fileManager.contentsOfDirectory(atPath: caskroomPath).filter { !$0.hasPrefix(".") }
|
||||||
|
|
||||||
|
for cask in casks {
|
||||||
|
// Construct the path to the cask directory
|
||||||
|
let caskSubPath = caskroomPath + cask
|
||||||
|
|
||||||
|
// Get all version directories for this cask, ignoring hidden files
|
||||||
|
let versions = try fileManager.contentsOfDirectory(atPath: caskSubPath).filter { !$0.hasPrefix(".") }
|
||||||
|
|
||||||
|
// Only check the first valid version directory to improve efficiency
|
||||||
|
if let latestVersion = versions.first {
|
||||||
|
let appDirectory = "\(caskSubPath)/\(latestVersion)/"
|
||||||
|
|
||||||
|
// List all files in the version directory and check for .app file
|
||||||
|
let appsInDir = try fileManager.contentsOfDirectory(atPath: appDirectory).filter { !$0.hasPrefix(".") }
|
||||||
|
|
||||||
|
if let appFile = appsInDir.first(where: { $0.hasSuffix(".app") }) {
|
||||||
|
let realAppName = appFile.replacingOccurrences(of: ".app", with: "").lowercased()
|
||||||
|
|
||||||
|
// Compare the lowercased app names for case-insensitive match
|
||||||
|
if realAppName == lowercasedAppName {
|
||||||
|
return realAppName.replacingOccurrences(of: " ", with: "-").lowercased()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error reading cask metadata: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no match is found, return nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func caskCleanup2(app: String) {
|
||||||
|
Task(priority: .high) {
|
||||||
let formattedApp = app.lowercased().replacingOccurrences(of: " ", with: "-")
|
let formattedApp = app.lowercased().replacingOccurrences(of: " ", with: "-")
|
||||||
|
|
||||||
#if arch(x86_64)
|
#if arch(x86_64)
|
||||||
@@ -289,20 +436,23 @@ func caskCleanup(app: String) {
|
|||||||
let script = """
|
let script = """
|
||||||
tell application "Terminal"
|
tell application "Terminal"
|
||||||
activate
|
activate
|
||||||
do script "
|
do script "/bin/bash --noprofile --norc -c '
|
||||||
found_cask=$(\(cmd) list --cask | grep '\(formattedApp)');
|
found_cask=$(\(cmd) list --cask | grep '\(formattedApp)');
|
||||||
if [ -n \\"$found_cask\\" ]; then
|
if [ -n \\"$found_cask\\" ]; then
|
||||||
clear;
|
clear;
|
||||||
|
echo \\"[Pearcleaner] Homebrew cleanup for \(formattedApp):\n\\";
|
||||||
\(cmd) uninstall --cask \\"$found_cask\\" --force;
|
\(cmd) uninstall --cask \\"$found_cask\\" --force;
|
||||||
\(cmd) cleanup;
|
\(cmd) cleanup;
|
||||||
|
killall Terminal;
|
||||||
else
|
else
|
||||||
echo \\"Cask not found for \(formattedApp)\\";
|
clear;
|
||||||
fi"
|
echo \\"[Pearcleaner] \(formattedApp) cask not found for homebrew cleanup!\nClosing window in 5 seconds..\\";
|
||||||
|
sleep 5;
|
||||||
|
killall Terminal;
|
||||||
|
fi'" in front window
|
||||||
end tell
|
end tell
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print(script)
|
|
||||||
|
|
||||||
var error: NSDictionary?
|
var error: NSDictionary?
|
||||||
if let appleScript = NSAppleScript(source: script) {
|
if let appleScript = NSAppleScript(source: script) {
|
||||||
appleScript.executeAndReturnError(&error)
|
appleScript.executeAndReturnError(&error)
|
||||||
@@ -315,7 +465,6 @@ func caskCleanup(app: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Print list of files locally
|
// Print list of files locally
|
||||||
func saveURLsToFile(urls: Set<URL>, appState: AppState, copy: Bool = false) {
|
func saveURLsToFile(urls: Set<URL>, appState: AppState, copy: Bool = false) {
|
||||||
|
|
||||||
@@ -447,10 +596,7 @@ func removeApp(appState: AppState, withPath path: URL) {
|
|||||||
appState.sortedApps.remove(at: index)
|
appState.sortedApps.remove(at: index)
|
||||||
// return // Exit the function if the app was found and removed
|
// return // Exit the function if the app was found and removed
|
||||||
}
|
}
|
||||||
// Remove from appInfoStore if found
|
|
||||||
// if let index = appState.appInfoStore.firstIndex(where: { $0.path == path }) {
|
|
||||||
// appState.appInfoStore.remove(at: index)
|
|
||||||
// }
|
|
||||||
// Brew cleanup if enabled
|
// Brew cleanup if enabled
|
||||||
if brew {
|
if brew {
|
||||||
caskCleanup(app: appState.appInfo.appName)
|
caskCleanup(app: appState.appInfo.appName)
|
||||||
@@ -481,26 +627,26 @@ func showLocalized(url: URL) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension URL {
|
extension URL {
|
||||||
func localizedName() -> String? {
|
func localizedName() -> String {
|
||||||
do {
|
do {
|
||||||
let resourceValues = try self.resourceValues(forKeys: [.localizedNameKey])
|
let resourceValues = try self.resourceValues(forKeys: [.localizedNameKey])
|
||||||
return resourceValues.localizedName
|
return resourceValues.localizedName?.replacingOccurrences(of: ".app", with: "") ?? self.lastPathComponent.replacingOccurrences(of: ".app", with: "")
|
||||||
} catch {
|
} catch {
|
||||||
print("Error getting localized name: \(error)")
|
print("Error getting localized name: \(error)")
|
||||||
return nil
|
return self.lastPathComponent.replacingOccurrences(of: ".app", with: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
func localizedName() -> String? {
|
func localizedName() -> String {
|
||||||
let url = URL(fileURLWithPath: self)
|
let url = URL(fileURLWithPath: self)
|
||||||
do {
|
do {
|
||||||
let resourceValues = try url.resourceValues(forKeys: [.localizedNameKey])
|
let resourceValues = try url.resourceValues(forKeys: [.localizedNameKey])
|
||||||
return resourceValues.localizedName
|
return resourceValues.localizedName?.replacingOccurrences(of: ".app", with: "") ?? self
|
||||||
} catch {
|
} catch {
|
||||||
print("Error getting localized name: \(error)")
|
print("Error getting localized name: \(error)")
|
||||||
return nil
|
return self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ struct PearcleanerApp: App {
|
|||||||
// The first argument is always the binary path, so check if there are more than 1 arguments
|
// The first argument is always the binary path, so check if there are more than 1 arguments
|
||||||
if filteredArguments.count > 1 {
|
if filteredArguments.count > 1 {
|
||||||
// Process the CLI options
|
// Process the CLI options
|
||||||
processCLI(arguments: arguments, appState: appState, locations: locations)
|
processCLI(arguments: arguments, appState: appState, locations: locations, fsm: fsm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -359,19 +359,20 @@ struct FilesView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// Run brew cleanup if sent from Sentinel
|
||||||
|
if brew && appState.sentinelMode {
|
||||||
|
caskCleanup(app: appState.appInfo.appName)
|
||||||
|
}
|
||||||
|
|
||||||
// Add deleted appInfo object to trashed array
|
// Add deleted appInfo object to trashed array
|
||||||
appState.trashedFiles.append(appState.appInfo)
|
appState.trashedFiles.append(appState.appInfo)
|
||||||
|
|
||||||
// Clear out appInfoStore object (Used for leftover file search)
|
|
||||||
// if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) {
|
|
||||||
// appState.appInfoStore[index] = .empty
|
|
||||||
// }
|
|
||||||
|
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
// Remove items from the list
|
// Remove items from the list
|
||||||
appState.appInfo.fileSize = appState.appInfo.fileSize.filter { !appState.selectedItems.contains($0.key) }
|
appState.appInfo.fileSize = appState.appInfo.fileSize.filter { !appState.selectedItems.contains($0.key) }
|
||||||
// Update the selectedFiles to remove references that are no longer present
|
// Update the selectedFiles to remove references that are no longer present
|
||||||
appState.selectedItems.removeAll()
|
appState.selectedItems.removeAll()
|
||||||
|
appState.sentinelMode = false
|
||||||
if appState.oneShotMode {
|
if appState.oneShotMode {
|
||||||
NSApp.terminate(nil)
|
NSApp.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func checkApp(file: String) {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if FileManager.default.isInTrash(app) {
|
if FileManager.default.isInTrash(app) {
|
||||||
NSWorkspace.shared.open(URL(string: "pear://com.alienator88.Pearcleaner?path=\(file)")!)
|
NSWorkspace.shared.open(URL(string: "pear://com.alienator88.Pearcleaner?path=\(file)&brew=true")!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user