diff --git a/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 23d1012..bf5f144 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" : "705a7e5fe3c1374000d2fd29f1c58fa476b0453c" + "revision" : "eb160d04a32e1e2fe54bfa3fac1f6a7535d7b2fb" } }, { diff --git a/Pearcleaner/Logic/AppPathsFetch.swift b/Pearcleaner/Logic/AppPathsFetch.swift index 5ab74cf..5b644b7 100644 --- a/Pearcleaner/Logic/AppPathsFetch.swift +++ b/Pearcleaner/Logic/AppPathsFetch.swift @@ -15,10 +15,10 @@ class AppPathFinder { 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 var containerCollection: [URL] = [] private let collectionAccessQueue = DispatchQueue(label: "com.alienator88.Pearcleaner.appPathFinder.collectionAccess") init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, undo: Bool = false, completion: @escaping () -> Void = {}) { @@ -26,7 +26,6 @@ class AppPathFinder { self.appState = appState self.locations = locations self.backgroundRun = backgroundRun -// self.reverseAddon = reverseAddon self.undo = undo self.completion = completion } @@ -34,9 +33,11 @@ class AppPathFinder { func findPaths() { Task(priority: .background) { if self.appInfo.webApp { + containerCollection = self.getAllContainers(bundleURL: self.appInfo.path) self.initialURLProcessing() self.finalizeCollection() } else { + containerCollection = self.getAllContainers(bundleURL: self.appInfo.path) self.initialURLProcessing() self.collectDirectories() self.collectFiles() @@ -268,14 +269,14 @@ class AppPathFinder { private func finalizeCollection() { DispatchQueue.global(qos: .userInitiated).async { - let allContainers = self.getAllContainers(bundleURL: self.appInfo.path) +// let allContainers = self.getAllContainers(bundleURL: self.appInfo.path) let outliers = self.handleOutliers() let outliersEx = self.handleOutliers(include: false) var tempCollection: [URL] = [] self.collectionAccessQueue.sync { tempCollection = self.collection } - tempCollection.append(contentsOf: allContainers) + tempCollection.append(contentsOf: self.containerCollection) tempCollection.append(contentsOf: outliers) // Remove URLs based on outliersExcludes diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index 8c8811f..f715d76 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -111,10 +111,6 @@ func listAppSupportDirectories() -> [String] { // Load app paths on launch func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, completion: @escaping () -> Void = {}) { @AppStorage("settings.interface.animationEnabled") var animationEnabled: Bool = true -// appState.operationQueueLeftover.maxConcurrentOperationCount = 10 // Adjust this value as needed -// appState.shouldCancelOperations = false -// appState.appInfoStore.removeAll() -// let sortedAllApps = allApps.sorted { $0.appName.localizedCompare($1.appName) == .orderedAscending } updateOnMain { appState.leftoverProgress.0 = "Finding leftover files, please wait..." @@ -130,88 +126,6 @@ func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locatio } completion() } - -// let totalApps = sortedAllApps.count -// var processedApps = 0 -// -// DispatchQueue.global(qos: .userInitiated).async { -// for (index, app) in sortedAllApps.enumerated() { -// autoreleasepool { -// if appState.shouldCancelOperations { -// printOS("Operations cancelled.") // Debug print -// return -// } -// printOS("Processing app \(index + 1) of \(totalApps): \(app.appName)") -// -// let semaphore = DispatchSemaphore(value: 0) -// appState.operationQueueLeftover.addOperation { -// if appState.shouldCancelOperations { -// printOS("Operations cancelled.") // Debug print -// return -// } -// let pathFinder = AppPathFinder( -// appInfo: app, -// appState: appState, -// locations: locations, -// backgroundRun: true, -// reverseAddon: reverseAddon, -// completion: { -// semaphore.signal() -// } -// ) -// pathFinder.findPaths() -// } -// -// semaphore.wait() -// -// DispatchQueue.main.async { -// processedApps += 1 -// let progress = Double(processedApps) / Double(totalApps) -// withAnimation { -// appState.leftoverProgress.1 = progress -// } -// appState.leftoverProgress.0 = "Excluding application files for \(app.appName)" -// printOS("Progress: \(Int(progress * 100))%") // Debug print -// } -// } -// -// } -// -// appState.operationQueueLeftover.waitUntilAllOperationsAreFinished() -// -// DispatchQueue.main.async { -// if appState.shouldCancelOperations { -// completion() -// return -// } -// -// if appState.appInfoStore.count == sortedAllApps.count { -// printOS("All apps processed successfully") -// appState.leftoverProgress.0 = "Finding leftover files, please wait..." -// ReversePathsSearcher(appState: appState, locations: locations, fsm: fsm, sortedApps: sortedAllApps).reversePathsSearch { -// updateOnMain { -// printOS("Reverse search processed successfully") -// appState.showProgress = false -// withAnimation { -// appState.leftoverProgress.1 = 0.0 -// } -// appState.leftoverProgress.0 = "Reverse search completed successfully" -// } -// completion() -// } -// } else { -// printOS("reversePreloader - Not all paths were loaded. Expected: \(sortedAllApps.count), Actual: \(appState.appInfoStore.count)") -// updateOnMain { -// appState.showProgress = false -// withAnimation { -// appState.leftoverProgress.1 = 0.0 -// } -// appState.leftoverProgress.0 = "Reverse search failed to process existing application files (\(sortedAllApps.count)/\(appState.appInfoStore.count))" -// } -// completion() -// } -// } -// } } @@ -245,38 +159,6 @@ func showAppInFiles(appInfo: AppInfo, appState: AppState, locations: Locations, appState.currentView = .files showPopover.wrappedValue.toggle() } - - // Check if the appInfo exists in the appState.appInfoStore -// if let storedAppInfo = appState.appInfoStore.first(where: { $0.path == appInfo.path }) { -// // Update appState with the stored app info and selected items. -// appState.appInfo = storedAppInfo -// appState.selectedItems = Set(storedAppInfo.files) -// -// // Trigger the animation for changing views and showing the popover. -// withAnimation(Animation.easeIn(duration: 0.4)) { -// appState.currentView = .files -// showPopover.wrappedValue.toggle() -// } -// } else { -// // When the appInfo is not found, show progress, and search for paths. -// appState.showProgress = true -// -// // Initialize the path finder and execute its search. -// AppPathFinder(appInfo: appInfo, appState: appState, locations: locations) { -// updateOnMain { -// // Update the progress indicator on the main thread once the search completes. -// appState.showProgress = false -// } -// }.findPaths() -// -// appState.appInfo = appInfo -// -// // Animate the view change and popover display. -// withAnimation(Animation.easeIn(duration: 0.4)) { -// appState.currentView = .files -// showPopover.wrappedValue.toggle() -// } -// } } } @@ -297,23 +179,28 @@ func moveFilesToTrash(appState: AppState, at fileURLs: [URL], completion: @escap var error: NSDictionary? if let scriptObject = NSAppleScript(source: scriptSource) { let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error) + + // Handle any AppleScript errors if let error = error { -// checkAllPermissions(appState: appState) { results in -// appState.permissionResults = results -// if !results.allPermissionsGranted { -// updateOnMain { -// appState.permissionsOkay = false -// } -// } -// } - printOS("Trash Error: \(error)") DispatchQueue.main.async { + printOS("Trash Error: \(error)") completion(false) // Indicate failure } return } + + // Check if output is null, indicating the user canceled the operation + if output.descriptorType == typeNull { + DispatchQueue.main.async { + printOS("Trash Error: operation canceled by the user") + completion(false) // Indicate failure due to cancellation + } + return + } + + // Process output if it exists if let outputString = output.stringValue { - printOS(outputString) + printOS("Trash: \(outputString)") } } DispatchQueue.main.async { @@ -347,14 +234,6 @@ func undoTrash(appState: AppState, completion: @escaping () -> Void = {}) { if let scriptObject = NSAppleScript(source: scriptSource) { let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error) if let error = error { -// checkAllPermissions(appState: appState) { results in -// appState.permissionResults = results -// if !results.allPermissionsGranted { -// updateOnMain { -// appState.permissionsOkay = false -// } -// } -// } printOS("Undo Trash Error: \(error)") } else if let outputString = output.stringValue { printOS(outputString) diff --git a/Pearcleaner/Settings/About.swift b/Pearcleaner/Settings/About.swift index ccae8d7..5ba5769 100644 --- a/Pearcleaner/Settings/About.swift +++ b/Pearcleaner/Settings/About.swift @@ -160,6 +160,8 @@ struct Sponsor: Identifiable { let url: URL static let sponsors: [Sponsor] = [ + Sponsor(name: "fpuhan", url: URL(string: "https://github.com/fpuhan")!), + Sponsor(name: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!), Sponsor(name: "DharsanB", url: URL(string: "https://github.com/dharsanb")!), Sponsor(name: "MadMacMad", url: URL(string: "https://github.com/MadMacMad")!), Sponsor(name: "Butterdawgs", url: URL(string: "https://github.com/butterdawgs")!), diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index a30a3c0..758cb19 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -240,7 +240,7 @@ struct ZombieView: View { ForEach(memoizedFiles, id: \.self) { file in if let fileSize = appState.zombieFile.fileSize[file], let fileSizeL = appState.zombieFile.fileSizeLogical[file], let fileIcon = appState.zombieFile.fileIcon[file], let iconImage = fileIcon.map(Image.init(nsImage:)) { VStack { - ZombieFileDetailsItem(size: fileSize, sizeL: fileSizeL, icon: iconImage, path: file, isSelected: self.binding(for: file)) + ZombieFileDetailsItem(size: fileSize, sizeL: fileSizeL, icon: iconImage, path: file, memoizedFiles: $memoizedFiles, isSelected: self.binding(for: file)) .padding(.vertical, 5) } } @@ -462,6 +462,7 @@ struct ZombieView: View { struct ZombieFileDetailsItem: View { @EnvironmentObject var appState: AppState + @EnvironmentObject var fsm: FolderSettingsManager @State private var isHovered = false @AppStorage("settings.general.sizeType") var sizeType: String = "Real" @AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true @@ -469,6 +470,8 @@ struct ZombieFileDetailsItem: View { let sizeL: Int64? let icon: Image? let path: URL + @Binding var memoizedFiles: [URL] + @Binding var isSelected: Bool var body: some View { @@ -550,6 +553,12 @@ struct ZombieFileDetailsItem: View { Button("View in Finder") { NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) } + Divider() + Button("Exclude") { + fsm.addPathZ(path.path) + memoizedFiles.removeAll { $0 == path } + } + .help("This adds the file/folder to the Exclusions list. Edit the exclusions list from Settings > Folders tab") } } }