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