diff --git a/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 5b2c045..dc849b9 100644
--- a/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -7,7 +7,7 @@
"location" : "https://github.com/alienator88/AlinFoundation",
"state" : {
"branch" : "main",
- "revision" : "a4df813f0186209491a2568d03aab8da8eab7ae0"
+ "revision" : "59b3014a76ad187672b46727168634c606d96953"
}
},
{
diff --git a/Pearcleaner.xcodeproj/xcshareddata/xcschemes/Pearcleaner Release.xcscheme b/Pearcleaner.xcodeproj/xcshareddata/xcschemes/Pearcleaner Release.xcscheme
index 0d583ba..d65020a 100644
--- a/Pearcleaner.xcodeproj/xcshareddata/xcschemes/Pearcleaner Release.xcscheme
+++ b/Pearcleaner.xcodeproj/xcshareddata/xcschemes/Pearcleaner Release.xcscheme
@@ -49,13 +49,6 @@
ReferencedContainer = "container:Pearcleaner.xcodeproj">
-
-
-
-
Set {
-// if self.appInfo.webApp {
-// containerCollection = getAllContainers(bundleURL: self.appInfo.path)
-// self.initialURLProcessing()
-// return finalizeCollection() // Return the collected paths
-// } else {
-// containerCollection = getAllContainers(bundleURL: self.appInfo.path)
-// self.initialURLProcessing()
-// self.collectDirectories() // Synchronous version
-// self.collectFiles() // Synchronous version
-// return finalizeCollection() // Return the collected paths
-// }
-// }
-//
-// private func initialURLProcessing() {
-// if let url = URL(string: self.appInfo.path.absoluteString), !url.path.contains(".Trash") {
-// let modifiedUrl = url.path.contains("Wrapper") ? url.deletingLastPathComponent().deletingLastPathComponent() : url
-// self.collection.append(modifiedUrl)
-// }
-// }
-//
-// private func shouldSkipItem(_ itemL: String, at itemURL: URL) -> Bool {
-// var containsItem = false
-// collectionAccessQueue.sync {
-// containsItem = self.collection.contains(itemURL)
-// }
-// if containsItem || !isSupportedFileType(at: itemURL.path) {
-// return true
-// }
-//
-// for skipCondition in skipConditions {
-// if itemL.hasPrefix(skipCondition.skipPrefix) {
-// let isAllowed = skipCondition.allowPrefixes.contains(where: itemL.hasPrefix)
-// if !isAllowed {
-// return true // Skip because it starts with a base prefix but is not in the allowed list
-// }
-// }
-// }
-//
-// return false
-// }
-//
-// private func processDirectoryLocation(_ location: String) {
-// if let contents = try? FileManager.default.contentsOfDirectory(atPath: location) {
-// for item in contents {
-// let itemURL = URL(fileURLWithPath: location).appendingPathComponent(item)
-// let itemL = item.replacingOccurrences(of: ".", with: "").replacingOccurrences(of: " ", with: "").lowercased()
-//
-// var isDirectory: ObjCBool = false
-// if FileManager.default.fileExists(atPath: itemURL.path, isDirectory: &isDirectory), isDirectory.boolValue {
-// // Perform the check to skip the item if needed
-// if shouldSkipItem(itemL, at: itemURL) {
-// continue
-// }
-//
-// collectionAccessQueue.sync {
-// let alreadyIncluded = self.collection.contains { existingURL in
-// itemURL.path.hasPrefix(existingURL.path)
-// }
-//
-// if !alreadyIncluded && specificCondition(itemL: itemL, itemURL: itemURL) {
-// self.collection.append(itemURL)
-// }
-// }
-// }
-// }
-// }
-// }
-//
-// private func specificCondition(itemL: String, itemURL: URL) -> Bool {
-// let bundleIdentifierL = self.appInfo.bundleIdentifier.pearFormat()
-// let bundleComponents = self.appInfo.bundleIdentifier.components(separatedBy: ".").compactMap { $0 != "-" ? $0.lowercased() : nil }
-// let bundle = bundleComponents.suffix(2).joined()
-// let nameL = self.appInfo.appName.pearFormat()
-// let nameLFiltered = nameL.filter { $0.isLetter }
-//
-// let nameP = self.appInfo.path.lastPathComponent.replacingOccurrences(of: ".app", with: "")
-//
-// for condition in conditions {
-// if bundleIdentifierL.contains(condition.bundle_id) {
-// // Exclude keywords
-// let hasExcludeKeyword = condition.exclude.contains(where: itemL.contains)
-// if hasExcludeKeyword {
-// return false
-// }
-// // Include keywords
-// let hasIncludeKeyword = condition.include.contains(where: itemL.contains)
-// if hasIncludeKeyword {
-// return true
-// }
-// }
-// }
-//
-//
-// if self.appInfo.webApp {
-// return itemL.contains(bundleIdentifierL)
-// }
-//
-// return itemL.contains(bundleIdentifierL) || itemL.contains(bundle) || (nameL.count > 3 && itemL.contains(nameL)) || (nameP.count > 3 && itemL.contains(nameP) || (nameLFiltered.count > 3 && itemL.contains(nameLFiltered)))
-//
-// }
-//
-// // Make collectDirectories synchronous
-// private func collectDirectories() {
-// for location in self.locations.apps.paths {
-// self.processDirectoryLocation(location) // Synchronous call
-// }
-// }
-//
-// private func processFileLocation(_ location: String) {
-// if let contents = try? FileManager.default.contentsOfDirectory(atPath: location) {
-// for item in contents {
-// let itemURL = URL(fileURLWithPath: location).appendingPathComponent(item)
-// let itemL = item.replacingOccurrences(of: ".", with: "").replacingOccurrences(of: " ", with: "").lowercased()
-//
-// if FileManager.default.fileExists(atPath: itemURL.path),
-// !shouldSkipItem(itemL, at: itemURL),
-// specificCondition(itemL: itemL, itemURL: itemURL) {
-// collectionAccessQueue.sync {
-// self.collection.append(itemURL)
-// }
-// }
-// }
-// }
-// }
-//
-// private func getAllContainers(bundleURL: URL) -> [URL] {
-// var containers: [URL] = []
-//
-// // Extract bundle identifier from bundleURL
-// let bundleIdentifier = Bundle(url: bundleURL)?.bundleIdentifier
-//
-// // Ensure the bundleIdentifier is not nil
-// guard let containerBundleIdentifier = bundleIdentifier else {
-// printOS("Get Containers: No bundle identifier found for the given bundle URL.")
-// return containers // Returns whatever was found so far, possibly empty
-// }
-//
-// // Get the regular container URL for the extracted bundle identifier
-// if let groupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: containerBundleIdentifier) {
-// if FileManager.default.fileExists(atPath: groupContainer.path) {
-// containers.append(groupContainer)
-// }
-// } else {
-// printOS("Get Containers: Failed to retrieve container URL for bundle identifier: \(containerBundleIdentifier)")
-// }
-//
-// let containersPath = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first?.appendingPathComponent("Containers")
-//
-// do {
-// let containerDirectories = try FileManager.default.contentsOfDirectory(at: containersPath!, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
-//
-// // Define a regular expression to match UUID format
-// let uuidRegex = try NSRegularExpression(pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$", options: .caseInsensitive)
-//
-// for directory in containerDirectories {
-// let directoryName = directory.lastPathComponent
-//
-// // Check if the directory name matches the UUID pattern
-// if uuidRegex.firstMatch(in: directoryName, options: [], range: NSRange(location: 0, length: directoryName.utf16.count)) != nil {
-// // Attempt to read the metadata plist file
-// let metadataPlistURL = directory.appendingPathComponent(".com.apple.containermanagerd.metadata.plist")
-// if let metadataDict = NSDictionary(contentsOf: metadataPlistURL), let applicationBundleID = metadataDict["MCMMetadataIdentifier"] as? String {
-// if applicationBundleID == self.appInfo.bundleIdentifier {
-// containers.append(directory)
-// }
-// }
-// }
-// }
-// } catch {
-// printOS("Error accessing the Containers directory: \(error)")
-// }
-//
-// // Return all found containers
-// return containers
-// }
-//
-// // Make collectFiles synchronous
-// private func collectFiles() {
-// for location in self.locations.apps.paths {
-// self.processFileLocation(location) // Synchronous call
-// }
-// }
-//
-// private func handleOutliers(include: Bool = true) -> [URL] {
-// var outliers: [URL] = []
-// let bundleIdentifier = self.appInfo.bundleIdentifier.pearFormat()
-//
-// // Find conditions that match the current app's bundle identifier
-// let matchingConditions = conditions.filter { condition in
-// bundleIdentifier.contains(condition.bundle_id)
-// }
-//
-//
-// for condition in matchingConditions {
-// if include {
-// // Handle includeForce
-// if let forceIncludes = condition.includeForce {
-// for path in forceIncludes {
-// outliers.append(path)
-// }
-// }
-// } else {
-// // Handle excludeForce
-// if let excludeForce = condition.excludeForce {
-// for path in excludeForce {
-// outliers.append(path)
-// }
-// }
-// }
-//
-// }
-//
-// return outliers
-// }
-//
-// // No need to dispatch asynchronously here
-// private func finalizeCollection() -> Set {
-// let outliers = self.handleOutliers()
-// let outliersEx = self.handleOutliers(include: false)
-// var tempCollection: [URL] = []
-// self.collectionAccessQueue.sync {
-// tempCollection = self.collection
-// }
-// tempCollection.append(contentsOf: self.containerCollection)
-// tempCollection.append(contentsOf: outliers)
-//
-// let excludePaths = outliersEx.map { $0.path }
-// tempCollection.removeAll { url in
-// excludePaths.contains(url.path)
-// }
-//
-// let sortedCollection = tempCollection.map { $0.standardizedFileURL }.sorted(by: { $0.path < $1.path })
-// var filteredCollection: [URL] = []
-// var previousUrl: URL?
-//
-// for url in sortedCollection {
-// if let previousUrl = previousUrl, url.path.hasPrefix(previousUrl.path + "/") {
-// continue
-// }
-// filteredCollection.append(url)
-// previousUrl = url
-// }
-//
-// // Handle Trash directory case
-// var updatedCollection = filteredCollection
-// if updatedCollection.count == 1, let firstURL = updatedCollection.first, firstURL.path.contains(".Trash") {
-// // If only one item exists and it's in the Trash, remove it
-// updatedCollection.removeAll()
-// }
-//
-// return Set(updatedCollection)
-// }
-//
-//}
diff --git a/Pearcleaner/Logic/Conditions.swift b/Pearcleaner/Logic/Conditions.swift
index 465b224..705d454 100644
--- a/Pearcleaner/Logic/Conditions.swift
+++ b/Pearcleaner/Logic/Conditions.swift
@@ -35,7 +35,7 @@ struct Condition: Codable {
}
struct SkipCondition {
- var skipPrefix: String
+ var skipPrefix: [String]
var allowPrefixes: [String]
}
@@ -140,7 +140,7 @@ var conditions: [Condition] = [
// Skip com.apple files/folders since most are system originated, allow some for apps
let skipConditions: [SkipCondition] = [
SkipCondition(
- skipPrefix: "comapple",
+ skipPrefix: ["comapple", "mobiledocuments", "reminders"],
allowPrefixes: ["comappleconfigurator", "comappledt", "comappleiwork", "comapplesfsymbols", "comappletestflight"]
)
]
diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift
index e37a4d6..b508408 100644
--- a/Pearcleaner/PearcleanerApp.swift
+++ b/Pearcleaner/PearcleanerApp.swift
@@ -206,6 +206,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
}
}
+ ensureApplicationSupportFolderExists()
+
}
diff --git a/Pearcleaner/Resources/Localizable.xcstrings b/Pearcleaner/Resources/Localizable.xcstrings
index f468581..a840eba 100644
--- a/Pearcleaner/Resources/Localizable.xcstrings
+++ b/Pearcleaner/Resources/Localizable.xcstrings
@@ -172,6 +172,9 @@
},
"Folders" : {
+ },
+ "Force Refresh" : {
+
},
"Functionality" : {
diff --git a/Pearcleaner/Settings/Update.swift b/Pearcleaner/Settings/Update.swift
index 9daa421..ddf72b2 100644
--- a/Pearcleaner/Settings/Update.swift
+++ b/Pearcleaner/Settings/Update.swift
@@ -39,6 +39,11 @@ struct UpdateSettingsTab: View {
updater.checkForUpdatesForce(showSheet: false)
} label: { EmptyView() }
.buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", label: String(localized: "Refresh"), help: String(localized: "Refresh updater")))
+ .contextMenu {
+ Button("Force Refresh") {
+ updater.checkForUpdatesForce(showSheet: true)
+ }
+ }
Button {
diff --git a/Pearcleaner/Views/AppSearchView.swift b/Pearcleaner/Views/AppSearchView.swift
index 89961d0..0e8ea66 100644
--- a/Pearcleaner/Views/AppSearchView.swift
+++ b/Pearcleaner/Views/AppSearchView.swift
@@ -34,8 +34,7 @@ struct AppSearchView: View {
searchBarComponent
- .padding(.horizontal, search.isEmpty ? 10 : 5)
- .padding(.bottom, 5)
+ .padding(8)
Divider()
@@ -52,15 +51,15 @@ struct AppSearchView: View {
if updater.updateAvailable {
Divider()
UpdateBadge(updater: updater)
- .padding(8)
+ .padding()
} else if let _ = permissionManager.results, !permissionManager.allPermissionsGranted {
Divider()
PermissionsBadge()
- .padding(8)
+ .padding()
} else if updater.announcementAvailable {
Divider()
FeatureBadge(updater: updater)
- .padding(8)
+ .padding()
}
}
@@ -81,6 +80,7 @@ struct AppSearchView: View {
.buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle", help: String(localized: "Refresh apps (⌘+R)"), size: 16))
}
+
SearchBar(search: $search, darker: (mini || menubarEnabled) ? false : true, glass: glass, sidebar: false)
@@ -92,6 +92,14 @@ struct AppSearchView: View {
.popover(isPresented: $showMenu) {
VStack(alignment: .leading) {
+// Button("Refresh") {
+// withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
+// showPopover = false
+// reloadAppsList(appState: appState, fsm: fsm)
+// }
+// }
+// .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Refresh List", help: String(localized: "Refresh apps (⌘+R)"), size: 5))
+
Button {
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
// Cycle through all enum cases using `CaseIterable`
@@ -204,6 +212,8 @@ struct AppSearchView: View {
}
+ .frame(minHeight: 30)
+
}
@@ -290,12 +300,12 @@ struct SimpleSearchStyle: TextFieldStyle {
Button {
text = ""
} label: { EmptyView() }
- .buttonStyle(SimpleButtonStyle(icon: "delete.left.fill", help: String(localized: "Clear text"), size: 14, padding: padding))
+ .buttonStyle(SimpleButtonStyle(icon: "delete.left.fill", help: String(localized: "Clear text"), size: 16, padding: 0))
}
}
}
- .padding(.horizontal, 8)
+ .padding(.horizontal, 5)
}
.onHover { hovering in