This commit is contained in:
Alin
2024-06-11 15:10:08 -06:00
parent 970561fb75
commit 6355eb6bb2
18 changed files with 163 additions and 138 deletions
+9 -9
View File
@@ -10,17 +10,17 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.935.0)
aws-sdk-core (3.196.1)
aws-partitions (1.943.0)
aws-sdk-core (3.197.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.82.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sdk-kms (1.83.0)
aws-sdk-core (~> 3, >= 3.197.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-s3 (1.152.1)
aws-sdk-core (~> 3, >= 3.197.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
@@ -147,7 +147,7 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
http-cookie (1.0.6)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
@@ -171,8 +171,8 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.8)
strscan (>= 3.0.9)
rexml (3.2.9)
strscan
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
+5 -5
View File
@@ -305,10 +305,10 @@
C77B90222AF2D616009CC655 /* FilesView.swift */,
C71848432B8D2D600046CB13 /* ZombieView.swift */,
C76D08542AF89CDE00D07867 /* RegularMode.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
C7045A272B03E71D00376976 /* MiniMode.swift */,
C7FB173A2B96321300B96F9A /* AppsListView.swift */,
C7FEBA102BDC422200AE195F /* AppSearchView.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -568,8 +568,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 44;
APP_VERSION = 3.6.5;
APP_BUILD = 45;
APP_VERSION = 3.7.0;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -638,8 +638,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 44;
APP_VERSION = 3.6.5;
APP_BUILD = 45;
APP_VERSION = 3.7.0;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+4 -16
View File
@@ -33,16 +33,7 @@ struct AppCommands: Commands {
Button {
withAnimation(.easeInOut(duration: 0.5)) {
// Refresh Apps list
updateOnMain {
appState.reload.toggle()
}
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps = []
appState.sortedApps = sortedApps
appState.reload.toggle()
}
reloadAppsList(appState: appState, fsm: fsm)
}
} label: {
Text("Refresh Apps")
@@ -65,12 +56,9 @@ struct AppCommands: Commands {
Button
{
undoTrash(appState: appState) {
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
appState.sortedApps = sortedApps
for app in appState.trashedFiles {
AppPathFinder(appInfo: app, appState: appState, locations: locations, undo: true).findPaths()
}
reloadAppsList(appState: appState, fsm: fsm)
for app in appState.trashedFiles {
AppPathFinder(appInfo: app, appState: appState, locations: locations, undo: true).findPaths()
}
}
} label: {
+5 -8
View File
@@ -46,22 +46,19 @@ class AppInfoFetcher {
return nil
}
// let appName = bundle.infoDictionary?["CFBundleDisplayName"] as? String ?? bundle.infoDictionary?["CFBundleName"] as? String
// ?? bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String ?? path.deletingPathExtension().lastPathComponent
let appName = wrapped ? path.deletingLastPathComponent().deletingLastPathComponent().deletingPathExtension().lastPathComponent.capitalizingFirstLetter() : path.deletingPathExtension().lastPathComponent.capitalizingFirstLetter()
let appVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String
?? bundle.infoDictionary?["CFBundleVersion"] as? String
?? ""
let appVersion = (bundle.infoDictionary?["CFBundleShortVersionString"] as? String)?.isEmpty ?? true
? bundle.infoDictionary?["CFBundleVersion"] as? String ?? ""
: bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
let appIcon = fetchAppIcon(for: path, wrapped: wrapped)
let webApp = (bundle.infoDictionary?["LSTemplateApplication"] as? Bool ?? false || bundle.infoDictionary?["CFBundleExecutable"] as? String == "app_mode_loader")
let webApp = bundle.infoDictionary?["LSTemplateApplication"] as? Bool ?? false
let system = !path.path.contains(NSHomeDirectory())
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName, appVersion: appVersion, appIcon: appIcon,
webApp: webApp, wrapped: wrapped, system: system, files: [], fileSize: [:], fileSizeLogical: [:], fileIcon: [:])
webApp: webApp, wrapped: wrapped, system: system, bundleSize: 0, files: [], fileSize: [:], fileSizeLogical: [:], fileIcon: [:])
}
private static func fetchAppIcon(for path: URL, wrapped: Bool) -> NSImage? {
+3 -1
View File
@@ -47,6 +47,7 @@ class AppState: ObservableObject {
webApp: false,
wrapped: false,
system: false,
bundleSize: 0,
files: [],
fileSize: [:],
fileSizeLogical: [:],
@@ -98,6 +99,7 @@ struct AppInfo: Identifiable, Equatable, Hashable {
let webApp: Bool
let wrapped: Bool
let system: Bool
var bundleSize: Int64
var files: [URL]
var fileSize: [URL:Int64]
var fileSizeLogical: [URL:Int64]
@@ -112,7 +114,7 @@ struct AppInfo: Identifiable, Equatable, Hashable {
}
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false, wrapped: false, system: false, files: [], fileSize: [:], fileSizeLogical: [:], fileIcon: [:])
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false, wrapped: false, system: false, bundleSize: 0, files: [], fileSize: [:], fileSizeLogical: [:], fileIcon: [:])
}
-2
View File
@@ -39,8 +39,6 @@ func getSortedApps(paths: [String], appState: AppState) -> [AppInfo] {
// Get app info and sort
let sortedApps = apps
.compactMap { AppInfoFetcher.getAppInfo(atPath: $0) }
.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() }
return sortedApps
}
+1 -7
View File
@@ -570,13 +570,7 @@ struct AnimatedSearchStyle: TextFieldStyle {
.foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5))
.onTapGesture {
withAnimation {
// Refresh Apps list
appState.reload.toggle()
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps = sortedApps
appState.reload.toggle()
}
reloadAppsList(appState: appState, fsm: fsm)
}
}
+32 -15
View File
@@ -27,19 +27,25 @@ func updateOnMain(after delay: Double? = nil, _ updates: @escaping () -> Void) {
// Execute functions on background thread
func updateOnBackground(_ updates: @escaping () -> Void) {
DispatchQueue.global(qos: .background).async {
DispatchQueue.global(qos: .userInitiated).async {
updates()
}
}
// Reload apps list
func reloadAppsList(appState: AppState, fsm: FolderSettingsManager) {
appState.reload = true
updateOnBackground {
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
// Update UI on the main thread
updateOnMain {
appState.sortedApps = sortedApps
appState.reload = false
}
}
}
// Resize window
//func resizeWindow(width: CGFloat, height: CGFloat) {
// if let window = NSApplication.shared.windows.first {
// let newSize = NSSize(width: width, height: height)
// window.setContentSize(newSize)
// }
//}
func resizeWindowAuto(windowSettings: WindowSettings, title: String) {
if let window = NSApplication.shared.windows.first(where: { $0.title == title }) {
@@ -160,16 +166,26 @@ func caskCleanup(app: String) {
#elseif arch(arm64)
let cmd = "/opt/homebrew/bin/brew"
#endif
let formattedApp = app.lowercased().replacingOccurrences(of: " ", with: "-")
process.executableURL = URL(fileURLWithPath: "/bin/zsh")
process.arguments = ["-c", "\(cmd) uninstall --cask \(app) --force; \(cmd) cleanup"]
// let pipe = Pipe()
process.standardOutput = FileHandle.nullDevice//pipe
process.standardError = FileHandle.nullDevice//pipe
// process.arguments = ["-c", "\(cmd) uninstall --cask \"\(formattedApp)\" --force; \(cmd) cleanup"]
process.arguments = ["-c", """
found_cask=$(\(cmd) list --cask | grep "\(formattedApp)")
if [ -n "$found_cask" ]; then
\(cmd) uninstall --cask "$found_cask" --force;
\(cmd) cleanup;
else
echo "Cask not found for \(formattedApp)";
fi
"""]
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
try? process.run()
process.waitUntilExit() // Ensure the process completes
// let data = pipe.fileHandleForReading.readDataToEndOfFile()
// let output = (String(data: data, encoding: .utf8) ?? "") as String
// print(output)
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = (String(data: data, encoding: .utf8) ?? "") as String
print(output)
}
}
@@ -499,6 +515,7 @@ func formatByte(size: Int64) -> (human: String, byte: String) {
}
// Only process supported files
func isSupportedFileType(at path: String) -> Bool {
let fileManager = FileManager.default
+3 -3
View File
@@ -88,9 +88,9 @@ struct PearcleanerApp: App {
// Disable tabbing
NSWindow.allowsAutomaticWindowTabbing = false
// Get Apps
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
appState.sortedApps = sortedApps
// Load apps list on startup
reloadAppsList(appState: appState, fsm: fsm)
// Enable menubar item
if menubarEnabled {
+24 -17
View File
@@ -24,10 +24,10 @@ struct AppListItems: View {
@EnvironmentObject var locations: Locations
let itemId = UUID()
let appInfo: AppInfo
var isSelected: Bool {
appState.appInfo.path == appInfo.path
}
var isSelected: Bool { appState.appInfo.path == appInfo.path }
@State private var hoveredItemPath: URL? = nil
@State private var bundleSize: Int64 = 0
var body: some View {
ZStack() {
@@ -40,9 +40,7 @@ struct AppListItems: View {
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30)
// .frame(width: (isHovered || isSelected) ? 32 : 30, height: (isHovered || isSelected) ? 32 : 30)
.clipShape(RoundedRectangle(cornerRadius: 8))
// .shadow(color: Color("pop").opacity(0.8), radius: isSelected ? 2 : 0)
}
}
@@ -80,17 +78,13 @@ struct AppListItems: View {
)
}
Text(appInfo.appVersion)
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
.foregroundStyle(Color("mode").opacity(0.5))
// if (isHovered || isSelected) {
// RoundedRectangle(cornerRadius: 50)
// .fill(isSelected ? Color("pear") : Color("mode").opacity(0.5))
// .frame(width: isSelected ? 4 : 2, height: 25)
// .padding(.trailing, 5)
// }
if bundleSize == 0 {
ProgressView().controlSize(.mini)
} else {
Text("\(isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human)")
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
.foregroundStyle(Color("mode").opacity(0.5))
}
}
}
@@ -99,7 +93,7 @@ struct AppListItems: View {
.padding(.vertical, 5)
.help(appInfo.appName)
.onHover { hovering in
withAnimation(Animation.easeIn(duration: 0.2)) {
withAnimation(Animation.easeIn(duration: 0.3)) {
self.isHovered = hovering
self.hoveredItemPath = isHovered ? appInfo.path : nil
}
@@ -132,6 +126,19 @@ struct AppListItems: View {
}
}
.onAppear {
DispatchQueue.global(qos: .userInitiated).async {
let size = totalSizeOnDisk(for: appInfo.path).logical
DispatchQueue.main.async {
self.bundleSize = size
// Optionally, update the appInfo in the appState array
if let index = appState.sortedApps.firstIndex(where: { $0.path == appInfo.path }) {
appState.sortedApps[index].bundleSize = size
}
}
}
}
}
+23 -10
View File
@@ -22,6 +22,9 @@ struct AppSearchView: View {
@State private var showSys: Bool = true
@State private var showUsr: Bool = true
@Binding var isMenuBar: Bool
@AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true
@State private var progress: Double = 0.0
var body: some View {
VStack(alignment: .center, spacing: 0) {
@@ -53,17 +56,11 @@ struct AppSearchView: View {
if search.isEmpty {
Button("Refresh") {
withAnimation {
// Refresh Apps list
appState.reload.toggle()
showPopover = false
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
appState.sortedApps = sortedApps
appState.reload.toggle()
}
reloadAppsList(appState: appState, fsm: fsm)
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.backward.circle", help: "Refresh apps (⌘+R)", size: 16))
.buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle", help: "Refresh apps (⌘+R)", size: 16))
}
SearchBar(search: $search, darker: (mini || menubarEnabled) ? false : true, glass: glass)
@@ -77,6 +74,13 @@ struct AppSearchView: View {
.popover(isPresented: $showMenu) {
VStack(alignment: .leading) {
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
selectedSortAlpha.toggle()
}
}
.buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "App List Sorting", help: "Can also click on User/System headers to toggle this", size: 5))
if mini && !menubarEnabled {
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
@@ -142,12 +146,21 @@ struct AppSearchView: View {
}
private var filteredApps: [AppInfo] {
let apps: [AppInfo]
if search.isEmpty {
return appState.sortedApps
apps = appState.sortedApps
} else {
return appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
apps = appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
}
switch selectedSortAlpha {
case true:
return apps.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() }
case false:
return apps.sorted { $0.bundleSize > $1.bundleSize }
}
}
}
+6 -1
View File
@@ -12,6 +12,7 @@ struct AppsListView: View {
@Binding var search: String
@Binding var showPopover: Bool
@AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
var filteredApps: [AppInfo]
@@ -62,12 +63,12 @@ struct SectionView: View {
struct Header: View {
let title: String
let count: Int
// @State private var hovered = false
@EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations
@EnvironmentObject var fsm: FolderSettingsManager
@Binding var showPopover: Bool
@AppStorage("settings.general.glass") private var glass: Bool = true
@AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true
var body: some View {
@@ -87,6 +88,10 @@ struct Header: View {
}
.frame(minHeight: 20)
.padding(5)
.help("Click header to change sorting order")
.onTapGesture {
selectedSortAlpha.toggle()
}
}
}
+1 -1
View File
@@ -120,7 +120,7 @@ struct FilesView: View {
HStack {
Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold).lineLimit(1)
Text("").foregroundStyle(Color("AccentColor"))
Text("\(appState.appInfo.appVersion)").font(.title3)
Text("v\(appState.appInfo.appVersion)").font(.title3)
if appState.appInfo.appName.count < 5 {
InfoButton(text: "Pearcleaner searches for files via a combination of bundle id and app name. \(appState.appInfo.appName) has a common or short app name so there might be unrelated files found. Please check the list thoroughly before uninstalling.")
}
+6 -1
View File
@@ -173,7 +173,12 @@ struct MiniAppView: View {
if appState.reload {
VStack {
Spacer()
ProgressView("Refreshing app list")
ProgressView() {
Text("Gathering app details")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
.padding(5)
}
Spacer()
}
.padding(.vertical)
+6 -1
View File
@@ -33,7 +33,12 @@ struct RegularMode: View {
if appState.reload {
VStack {
Spacer()
ProgressView("Refreshing app list")
ProgressView() {
Text("Gathering app details")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
.padding(5)
}
Spacer()
}
.frame(width: sidebarWidth)
+28 -40
View File
@@ -13,6 +13,8 @@ struct ZombieView: View {
@EnvironmentObject var locations: Locations
@EnvironmentObject var fsm: FolderSettingsManager
@State private var showPop: Bool = false
@AppStorage("settings.general.leftoverWarning") private var warning: Bool = false
@State private var showAlert = false
@AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("settings.general.glass") private var glass: Bool = false
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
@@ -36,45 +38,6 @@ struct ZombieView: View {
var body: some View {
// let totalSelectedZombieSize: (real: String, logical: String, finder: String) = {
// var totalReal: Int64 = 0
// var totalLogical: Int64 = 0
//
// for url in selectedZombieItemsLocal {
// let realSize = appState.zombieFile.fileSize[url] ?? 0
// let logicalSize = appState.zombieFile.fileSizeLogical[url] ?? 0
// totalReal += realSize
// totalLogical += logicalSize
// }
// return (formatByte(size: totalReal).human, formatByte(size:totalLogical).human, "\(formatByte(size: totalLogical).byte) (\(formatByte(size: totalReal).human))")
// }()
// let filteredAndSortedFiles: ([URL], Int64, Int64) = {
// let fileSizeReal = appState.zombieFile.fileSize
// let fileSizeLogical = appState.zombieFile.fileSizeLogical
// let filteredFilesReal = fileSizeReal.filter { (url, _) in
// searchZ.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchZ)
// }
// let filteredFilesLogical = fileSizeLogical.filter { (url, _) in
// searchZ.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchZ)
// }
// let filesToSort = (sizeType == "Real" || sizeType == "Finder" ? filteredFilesReal : filteredFilesLogical)
// let sortedFilteredFiles = filesToSort.sorted(by: {
// if selectedSortAlpha {
// return $0.key.lastPathComponent.pearFormat() < $1.key.lastPathComponent.pearFormat()
// } else {
// return $0.value > $1.value
// }
// }).map { $0.key }
// let totalSize = filteredFilesReal.values.reduce(0, +)
// let totalSizeL = filteredFilesLogical.values.reduce(0, +)
// return (sortedFilteredFiles, totalSize, totalSizeL)
// }()
// let displaySizeTotal = sizeType == "Real" ? formatByte(size: filteredAndSortedFiles.1).human :
// sizeType == "Logical" ? formatByte(size: filteredAndSortedFiles.2).human :
// "\(formatByte(size: filteredAndSortedFiles.2).byte) (\(formatByte(size: filteredAndSortedFiles.1).human))"
VStack(alignment: .center) {
if appState.showProgress {
VStack {
@@ -150,7 +113,7 @@ struct ZombieView: View {
VStack(alignment: .leading, spacing: 10){
HStack {
Text("Leftover Files").font(.title).fontWeight(.bold)
InfoButton(text: "Leftover file search is not and can never be 100% accurate as it doesn't have any old/uninstalled app bundles to check against for file exclusion. This does a best guess search for files/folders and excludes the ones that have overlap with your currently installed applications. Please confirm files marked for deletion really do belong to old/uninstalled applications.", color: .orange, warning: true)
InfoButton(text: "Leftover file search is not 100% accurate as it doesn't have any uninstalled app bundles to check against for file exclusion. This does a best guess search for files/folders and excludes the ones that have overlap with your currently installed applications. Please confirm files marked for deletion really do belong to uninstalled applications.", color: .orange, warning: true)
Spacer()
}
@@ -326,9 +289,34 @@ struct ZombieView: View {
.onAppear {
updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
}
.sheet(isPresented: $showAlert, content: {
VStack(spacing: 10) {
Text("Important")
.font(.headline)
Spacer()
Text("Leftover file search is not 100% accurate as it doesn't have any uninstalled app bundles to check against for file exclusion. This does a best guess search for files/folders and excludes the ones that have overlap with your currently installed applications. Please confirm files marked for deletion really do belong to uninstalled applications.")
.font(.subheadline)
Spacer()
Button("Close") {
warning = true
showAlert = false
}
.buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", label: "Close", help: "Dismiss"))
Spacer()
}
.padding(15)
.frame(width: 400, height: 250)
.background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow))
})
}
}
.onAppear {
if !warning {
showAlert = true
}
}
}
private func binding(for file: URL) -> Binding<Bool> {
+6 -1
View File
@@ -11,7 +11,7 @@
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
update_fastlane
opt_out_usage
@@ -51,6 +51,11 @@ platform :mac do
UI.user_error!("Failed to find .app file in #{output_path}")
end
# Empty the Archives folder
archives_path = "#{Dir.home}/Library/Developer/Xcode/Archives"
FileUtils.rm_rf(Dir.glob("#{archives_path}/*"))
# Open Finder at the Downloads folder
system("open #{output_path}")
+1
View File
@@ -1,4 +1,5 @@
{
"3.7.0": "- App List Size Sorting: You can now sort the sidebar app list alphabetically or by descending size. Click the User/System header to toggle or from the searchbar menu|- Leftover Files: On first use, a warning/explanation sheet will be presented to the user",
"3.5.0": "- Finder Extension: When enabled, you can right click an app in finder and uninstall with Pearcleaner directly|- Theme System: You can now select a base theme color for the application window",
"3.3.0": "- Folder Settings: Add more directories where Pearcleaner should search for app files|- Progress: Show progress bar on startup when loading all app files with instant search enabled|- App Icon: Show background color behind app icons in FilesView based on icon's average color mapping|- Progress: Show time counter on regular file search views",
"3.2.0": "- Menubar Item: You can choose to access Pearcleaner from the menubar. This will show a slightly modified Mini Mode view.",