From 29e37055748bd84da66267a368941c32d6f032e6 Mon Sep 17 00:00:00 2001 From: Alin Date: Mon, 1 Jul 2024 15:20:28 -0600 Subject: [PATCH] In progress --- Pearcleaner.xcodeproj/project.pbxproj | 4 + Pearcleaner/Logic/AppPathsFetch-Async.swift | 334 ++++++++++++++++++++ Pearcleaner/Logic/AppPathsFetch.swift | 321 ------------------- 3 files changed, 338 insertions(+), 321 deletions(-) create mode 100644 Pearcleaner/Logic/AppPathsFetch-Async.swift diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index c7eb8fd..2320f8e 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; }; C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; }; C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; }; + C736E7652C2DF1C9009EDB6A /* AppPathsFetch-Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */; }; C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */; }; C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74FDC622BCD833D00B8960F /* Conditions.swift */; }; C75347642BE599FA00AAFCC3 /* PermissionChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75347632BE599FA00AAFCC3 /* PermissionChecker.swift */; }; @@ -109,6 +110,7 @@ C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PearcleanerSentinel; sourceTree = BUILT_PRODUCTS_DIR; }; C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPathsFetch-Async.swift"; sourceTree = ""; }; C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = ""; }; C74FDC622BCD833D00B8960F /* Conditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conditions.swift; sourceTree = ""; }; C75347632BE599FA00AAFCC3 /* PermissionChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionChecker.swift; sourceTree = ""; }; @@ -201,6 +203,7 @@ C77B90192AF1938F009CC655 /* Logic.swift */, C7DD49E92BAB4D8100CCBA16 /* AppInfoFetch.swift */, C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */, + C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */, C7DD49ED2BAB7F6000CCBA16 /* ReversePathsFetch.swift */, C77B90172AF19382009CC655 /* AppCommands.swift */, C77B90152AF19377009CC655 /* AppState.swift */, @@ -487,6 +490,7 @@ C7045A282B03E71D00376976 /* MiniMode.swift in Sources */, C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */, C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */, + C736E7652C2DF1C9009EDB6A /* AppPathsFetch-Async.swift in Sources */, C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */, C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */, C76D084A2AF83C6E00D07867 /* General.swift in Sources */, diff --git a/Pearcleaner/Logic/AppPathsFetch-Async.swift b/Pearcleaner/Logic/AppPathsFetch-Async.swift new file mode 100644 index 0000000..42a85a9 --- /dev/null +++ b/Pearcleaner/Logic/AppPathsFetch-Async.swift @@ -0,0 +1,334 @@ +//// +//// AppPathsFetch-Async.swift +//// Pearcleaner +//// +//// Created by Alin Lupascu on 6/27/24. +//// +// +//import Foundation +//import AppKit +//import SwiftUI +// +//actor PathFinderState { +// var fileSize: [URL: Int64] = [:] +// var fileIconData: [URL: Data?] = [:] +// +// func setFileDetails(for path: URL, size: Int64, icon: Data?) { +// fileSize[path] = size +// fileIconData[path] = icon +// } +// +// func getFileSize() -> [URL: Int64] { +// return fileSize +// } +// +// func getFileIconData() -> [URL: Data?] { +// return fileIconData +// } +//} +// +//class AppPathFinderAsync { +// private var appInfo: AppInfo +// private var appState: AppState +// private var locations: Locations +// private var backgroundRun: Bool +// private var reverseAddon: Bool +// private var undo: Bool +// private var completion: () -> Void = {} +// private var collection: [URL] = [] +// private let collectionAccessQueue = DispatchQueue(label: "com.alienator88.Pearcleaner.appPathFinder.collectionAccess") +// private var state = PathFinderState() // Actor instance +// +// init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, reverseAddon: Bool = false, undo: Bool = false, completion: @escaping () -> Void = {}) { +// self.appInfo = appInfo +// self.appState = appState +// self.locations = locations +// self.backgroundRun = backgroundRun +// self.reverseAddon = reverseAddon +// self.undo = undo +// self.completion = completion +// } +// +// func findPaths() async { +// await initialURLProcessing() +// await withTaskGroup(of: Void.self) { group in +// for location in self.locations.apps.paths { +// group.addTask { +// await self.processDirectoryLocation(location) +// } +// } +// } +// await withTaskGroup(of: Void.self) { group in +// for location in self.locations.apps.paths { +// group.addTask { +// await self.processFileLocation(location) +// } +// } +// } +// await finalizeCollection() +// } +// +// private func initialURLProcessing() async { +// if let url = URL(string: self.appInfo.path.absoluteString), !url.path.contains(".Trash") { +// let modifiedUrl = url.path.contains("Wrapper") ? url.deletingLastPathComponent().deletingLastPathComponent() : url +// collectionAccessQueue.sync { +// self.collection.append(modifiedUrl) +// } +// } +// } +// +// private func processDirectoryLocation(_ location: String) async { +// do { +// 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 { +// 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) +// } +// } +// } +// } +// } catch { +// print("Error processing directory location: \(location), error: \(error)") +// } +// } +// +// private func processFileLocation(_ location: String) async { +// do { +// 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) +// } +// } +// } +// } catch { +// print("Error processing file location: \(location), error: \(error)") +// } +// } +// +// 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 +// } +// } +// } +// +// return false +// } +// +// 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 nameP = self.appInfo.path.lastPathComponent.replacingOccurrences(of: ".app", with: "") +// +// for condition in conditions { +// if bundleIdentifierL.contains(condition.bundle_id) { +// let hasIncludeKeyword = condition.include.contains(where: itemL.contains) +// let hasExcludeKeyword = condition.exclude.contains(where: itemL.contains) +// +// if hasExcludeKeyword { +// return false +// } +// if hasIncludeKeyword { +// if !condition.exclude.contains(where: itemL.contains) { +// 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)) +// } +// +// func getAllContainers(bundleURL: URL) async -> [URL] { +// await withCheckedContinuation { continuation in +// var containers: [URL] = [] +// +// let bundleIdentifier = Bundle(url: bundleURL)?.bundleIdentifier +// +// guard let containerBundleIdentifier = bundleIdentifier else { +// printOS("Get Containers: No bundle identifier found for the given bundle URL.") +// continuation.resume(returning: containers) +// return +// } +// +// 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) +// +// 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 +// +// if uuidRegex.firstMatch(in: directoryName, options: [], range: NSRange(location: 0, length: directoryName.utf16.count)) != nil { +// 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)") +// } +// +// continuation.resume(returning: containers) +// } +// } +// +// private func handleOutliers(include: Bool = true) -> [URL] { +// var outliers: [URL] = [] +// let bundleIdentifier = self.appInfo.bundleIdentifier.pearFormat() +// +// let matchingConditions = conditions.filter { condition in +// bundleIdentifier.contains(condition.bundle_id) +// } +// +// for condition in matchingConditions { +// if include { +// if let forceIncludes = condition.includeForce { +// for path in forceIncludes { +// outliers.append(path) +// } +// } +// } else { +// if let excludeForce = condition.excludeForce { +// for path in excludeForce { +// outliers.append(path) +// } +// } +// } +// } +// +// return outliers +// } +// +// private func finalizeCollection() async { +// let allContainers = await getAllContainers(bundleURL: self.appInfo.path) +// let outliers = handleOutliers() +// let outliersEx = handleOutliers(include: false) +// var tempCollection: [URL] = [] +// collectionAccessQueue.sync { +// tempCollection = self.collection +// } +// tempCollection.append(contentsOf: allContainers) +// 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 +// } +// +// await handlePostProcessing(sortedCollection: filteredCollection) +// } +// +// private func handlePostProcessing(sortedCollection: [URL]) async { +// for path in sortedCollection { +// let size = totalSizeOnDisk(for: path) +// if let icon = getIconForFileOrFolderNS(atPath: path) { +// let iconData = serializeImage(icon) +// await state.setFileDetails(for: path, size: size.real, icon: iconData) +// } else { +// await state.setFileDetails(for: path, size: size.real, icon: nil) +// } +// } +// await updateAppState(with: sortedCollection) +// } +// +// private func updateAppState(with sortedCollection: [URL]) async { +// let fileSize = await self.state.getFileSize() +// let fileIconData = await self.state.getFileIconData() +// let fileIcons = fileIconData.mapValues { deserializeImage($0) } +// let arch = checkAppBundleArchitecture(at: self.appInfo.path.path) +// +// self.appInfo.fileSize = fileSize +// self.appInfo.fileIcon = fileIcons +// self.appInfo.arch = arch +// +// await MainActor.run { +// if !self.backgroundRun { +// self.appState.appInfo = self.appInfo +// if !self.undo { +// self.appState.selectedItems = Set(sortedCollection) +// } +// } +// +// if self.reverseAddon { +// self.appState.appInfoStore.append(self.appInfo) +// } +// +// self.completion() +// } +// } +//} +// +//func serializeImage(_ image: NSImage?) -> Data? { +// guard let image = image else { return nil } +// guard let tiffData = image.tiffRepresentation else { return nil } +// let bitmapImage = NSBitmapImageRep(data: tiffData) +// return bitmapImage?.representation(using: .png, properties: [:]) +//} +// +//func deserializeImage(_ data: Data?) -> NSImage? { +// guard let data = data else { return nil } +// return NSImage(data: data) +//} diff --git a/Pearcleaner/Logic/AppPathsFetch.swift b/Pearcleaner/Logic/AppPathsFetch.swift index 9813d76..c9f580f 100644 --- a/Pearcleaner/Logic/AppPathsFetch.swift +++ b/Pearcleaner/Logic/AppPathsFetch.swift @@ -346,324 +346,3 @@ class AppPathFinder { } } } - - - - - - - - -// Async Test -//class AppPathFinder { -// private var appInfo: AppInfo -// private var appState: AppState -// private var locations: Locations -// private var backgroundRun: Bool -// private var reverseAddon: Bool -// private var completion: () -> Void = {} -// private var collection: [URL] = [] -// private let collectionAccessQueue = DispatchQueue(label: "com.alienator88.Pearcleaner.appPathFinder.collectionAccess") -// private var state = PathFinderState() // Actor instance -// -// init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, reverseAddon: Bool = false, completion: @escaping () -> Void = {}) { -// self.appInfo = appInfo -// self.appState = appState -// self.locations = locations -// self.backgroundRun = backgroundRun -// self.reverseAddon = reverseAddon -// self.completion = completion -// } -// -// func findPaths() async { -// await initialURLProcessing() -// await collectDirectories() -// await collectFiles() -// await finalizeCollection() -// } -// -// private func initialURLProcessing() async { -// if let url = URL(string: self.appInfo.path.absoluteString), !url.path.contains(".Trash") { -// let modifiedUrl = url.path.contains("Wrapper") ? url.deletingLastPathComponent().deletingLastPathComponent() : url -// collectionAccessQueue.sync { -// self.collection.append(modifiedUrl) -// } -// } -// } -// -// private func collectDirectories() async { -// for location in self.locations.apps.paths { -// await processDirectoryLocation(location) -// } -// } -// -// private func processDirectoryLocation(_ location: String) async { -// do { -// 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) -// } -// } -// } -// } -// } catch { -// print("Error processing directory location: \(location), error: \(error)") -// } -// } -// -// private func collectFiles() async { -// for location in self.locations.apps.paths { -// await processFileLocation(location) -// } -// } -// -// private func processFileLocation(_ location: String) async { -// do { -// 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) -// } -// } -// } -// } catch { -// print("Error processing file location: \(location), error: \(error)") -// } -// } -// -// -// -// 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 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 nameP = self.appInfo.path.lastPathComponent.replacingOccurrences(of: ".app", with: "") -// -// for condition in conditions { -// if bundleIdentifierL.contains(condition.bundle_id) { -// // Exclude and include keywords -// let hasIncludeKeyword = condition.include.contains(where: itemL.contains) -// let hasExcludeKeyword = condition.exclude.contains(where: itemL.contains) -// -// if hasExcludeKeyword { -// return false -// } -// if hasIncludeKeyword { -// if !condition.exclude.contains(where: itemL.contains) { -// 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)) -// -// } -// -// -// func getGroupContainers(bundleURL: URL) async -> [URL] { -// await withCheckedContinuation { continuation in -// var groupContainers: [URL] = [] -// // Assume creating a SecStaticCode is synchronous and quick. -// var staticCode: SecStaticCode? -// let status = SecStaticCodeCreateWithPath(bundleURL as CFURL, [], &staticCode) -// -// if status == errSecSuccess, let staticCode = staticCode { -// var signingInformation: CFDictionary? -// -// // This may involve a blocking call to fetch signing information. -// let status = SecCodeCopySigningInformation(staticCode, SecCSFlags(), &signingInformation) -// -// if status == errSecSuccess, let infoDict = signingInformation as? [String: Any], -// let entitlementsDict = infoDict["entitlements-dict"] as? [String: Any], -// let appGroups = entitlementsDict["com.apple.security.application-groups"] as? [String] { -// -// let fileManager = FileManager.default -// let home = NSHomeDirectory() // Assuming `home` is derived earlier or statically known. -// -// for groupID in appGroups { -// let groupURL = URL(fileURLWithPath: "\(home)/Library/Group Containers/\(groupID)") -// if fileManager.fileExists(atPath: groupURL.path) { -// groupContainers.append(groupURL) -// } -// } -// } -// } -// -// // Continue once all operations are complete. -// continuation.resume(returning: groupContainers) -// } -// } -// -// -// private func handleOutliers() -> [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 let forceIncludes = condition.includeForce { -// for path in forceIncludes { -// if let url = URL(string: path), FileManager.default.fileExists(atPath: url.path) { -// outliers.append(url) -// } -// } -// } -// } -// -// return outliers -// } -// -// private func finalizeCollection() async { -// let groupContainers = await getGroupContainers(bundleURL: self.appInfo.path) -// let outliers = handleOutliers() -// var tempCollection: [URL] = [] -// -// collectionAccessQueue.sync { -// tempCollection = self.collection -// } -// -// tempCollection.append(contentsOf: groupContainers) -// tempCollection.append(contentsOf: outliers) -// -// // Sort and standardize URLs to ensure consistent comparisons -// let sortedCollection = tempCollection.map { $0.standardizedFileURL }.sorted(by: { $0.path < $1.path }) -// var filteredCollection: [URL] = [] -// var previousUrl: URL? -// -// for url in sortedCollection { -// if let previous = previousUrl, url.path.hasPrefix(previous.path + "/") { -// // Current URL is a subdirectory of the previous one, so skip it -// continue -// } -// // This URL is not a subdirectory of the previous one, so keep it and set it as the previous URL -// filteredCollection.append(url) -// previousUrl = url -// } -// -// await handlePostProcessing(sortedCollection: filteredCollection) -// } -// -// private func handlePostProcessing(sortedCollection: [URL]) async { -// for path in sortedCollection { -// let size = totalSizeOnDisk(for: path) -// if let icon = getIconForFileOrFolderNS(atPath: path) { // Retrieve NSImage -// let iconData = serializeImage(icon) // Convert NSImage to Data -// await state.setFileDetails(for: path, size: size, icon: iconData) // Pass Data to actor -// } else { -// await state.setFileDetails(for: path, size: size, icon: nil) -// } -// } -// await updateAppState(with: sortedCollection) -// } -// -// private func updateAppState(with sortedCollection: [URL]) async { -// // Retrieve state from the actor -// let fileSize = await self.state.getFileSize() -// let fileIconData = await self.state.getFileIconData() -// let fileIcons = fileIconData.mapValues { deserializeImage($0) } -// -// // Assume updating of appInfo needs to happen on the main thread -// self.appInfo.fileSize = fileSize -// self.appInfo.fileIcon = fileIcons -// -// // Execute UI related updates on the main thread using MainActor -// await MainActor.run { -// if !self.backgroundRun { -// self.appState.appInfo = self.appInfo -// self.appState.selectedItems = Set(sortedCollection) -// } -// -// -// } -// -// self.completion() // Call the completion handler -// } -//} -// -//actor PathFinderState { -// var fileSize: [URL: Int64] = [:] -// var fileIconData: [URL: Data?] = [:] -// -// func setFileDetails(for path: URL, size: Int64, icon: Data?) { -// fileSize[path] = size -// fileIconData[path] = icon -// } -// -// func getFileSize() -> [URL: Int64] { -// return fileSize -// } -// -// func getFileIconData() -> [URL: Data?] { -// return fileIconData -// } -//} -// -// -//func serializeImage(_ image: NSImage?) -> Data? { -// guard let image = image else { return nil } -// guard let tiffData = image.tiffRepresentation else { return nil } -// let bitmapImage = NSBitmapImageRep(data: tiffData) -// return bitmapImage?.representation(using: .png, properties: [:]) -//} -// -//func deserializeImage(_ data: Data?) -> NSImage? { -// guard let data = data else { return nil } -// return NSImage(data: data) -//} -