mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 06:09:01 +00:00
progress
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "284df40ab461728f91e9fff8f6afbaf9f1121ce265b549a5187092a1097d4db1",
|
"originHash" : "bebe4a176f4f3b9e625a60cb3048363d2aeb03aa06dabf97821c3b73b1c5f149",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "alinfoundation",
|
"identity" : "alinfoundation",
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"location" : "https://github.com/alienator88/AlinFoundation",
|
"location" : "https://github.com/alienator88/AlinFoundation",
|
||||||
"state" : {
|
"state" : {
|
||||||
"branch" : "main",
|
"branch" : "main",
|
||||||
"revision" : "d660e9a1fb07ec3fa036c89c6f9fc1d573cecf9e"
|
"revision" : "986ac354d2a01f87ed4c37d19ce80a752a62fd4c"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,18 +15,18 @@ class AppPathFinder {
|
|||||||
private var appState: AppState
|
private var appState: AppState
|
||||||
private var locations: Locations
|
private var locations: Locations
|
||||||
private var backgroundRun: Bool
|
private var backgroundRun: Bool
|
||||||
private var reverseAddon: Bool
|
// private var reverseAddon: Bool
|
||||||
private var undo: Bool
|
private var undo: Bool
|
||||||
private var completion: () -> Void = {}
|
private var completion: () -> Void = {}
|
||||||
private var collection: [URL] = []
|
private var collection: [URL] = []
|
||||||
private let collectionAccessQueue = DispatchQueue(label: "com.alienator88.Pearcleaner.appPathFinder.collectionAccess")
|
private let collectionAccessQueue = DispatchQueue(label: "com.alienator88.Pearcleaner.appPathFinder.collectionAccess")
|
||||||
|
|
||||||
init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, reverseAddon: Bool = false, undo: Bool = false, completion: @escaping () -> Void = {}) {
|
init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, undo: Bool = false, completion: @escaping () -> Void = {}) {
|
||||||
self.appInfo = appInfo
|
self.appInfo = appInfo
|
||||||
self.appState = appState
|
self.appState = appState
|
||||||
self.locations = locations
|
self.locations = locations
|
||||||
self.backgroundRun = backgroundRun
|
self.backgroundRun = backgroundRun
|
||||||
self.reverseAddon = reverseAddon
|
// self.reverseAddon = reverseAddon
|
||||||
self.undo = undo
|
self.undo = undo
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
}
|
}
|
||||||
@@ -338,9 +338,9 @@ class AppPathFinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Append object to store if running reverse search with empty store
|
// Append object to store if running reverse search with empty store
|
||||||
if self.reverseAddon {
|
// if self.reverseAddon {
|
||||||
self.appState.appInfoStore.append(self.appInfo)
|
// self.appState.appInfoStore.append(self.appInfo)
|
||||||
}
|
// }
|
||||||
|
|
||||||
self.completion()
|
self.completion()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ let home = FileManager.default.homeDirectoryForCurrentUser.path
|
|||||||
|
|
||||||
class AppState: ObservableObject {
|
class AppState: ObservableObject {
|
||||||
@Published var appInfo: AppInfo
|
@Published var appInfo: AppInfo
|
||||||
@Published var appInfoStore: [AppInfo] = []
|
// @Published var appInfoStore: [AppInfo] = []
|
||||||
@Published var trashedFiles: [AppInfo] = []
|
@Published var trashedFiles: [AppInfo] = []
|
||||||
@Published var zombieFile: ZombieFile
|
@Published var zombieFile: ZombieFile
|
||||||
@Published var sortedApps: [AppInfo] = []
|
@Published var sortedApps: [AppInfo] = []
|
||||||
@@ -23,12 +23,24 @@ class AppState: ObservableObject {
|
|||||||
@Published var sidebar: Bool = true
|
@Published var sidebar: Bool = true
|
||||||
@Published var reload: Bool = false
|
@Published var reload: Bool = false
|
||||||
@Published var showProgress: Bool = false
|
@Published var showProgress: Bool = false
|
||||||
|
@Published var leftoverProgress: (String, Double) = ("", 0.0)
|
||||||
@Published var finderExtensionEnabled: Bool = false
|
@Published var finderExtensionEnabled: Bool = false
|
||||||
@Published var showUninstallAlert: Bool = false
|
@Published var showUninstallAlert: Bool = false
|
||||||
@Published var oneShotMode: Bool = false
|
@Published var oneShotMode: Bool = false
|
||||||
@Published var showConditionBuilder: Bool = false
|
@Published var showConditionBuilder: Bool = false
|
||||||
|
|
||||||
|
var operationQueueLeftover = OperationQueue()
|
||||||
|
@Published var shouldCancelOperations = false
|
||||||
|
|
||||||
|
func cancelQueueOperations() {
|
||||||
|
operationQueueLeftover.cancelAllOperations()
|
||||||
|
shouldCancelOperations = true
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.leftoverProgress = ("Search canceled", 0.0)
|
||||||
|
self.showProgress = false
|
||||||
|
self.currentView = .empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.appInfo = AppInfo(
|
self.appInfo = AppInfo(
|
||||||
|
|||||||
@@ -52,53 +52,62 @@ var conditions: [Condition] = [
|
|||||||
Condition(
|
Condition(
|
||||||
bundle_id: "com.robotsandpencils.xcodesapp",
|
bundle_id: "com.robotsandpencils.xcodesapp",
|
||||||
include: [],
|
include: [],
|
||||||
exclude: ["com.apple.dt.xcode", "com.oneminutegames.xcodecleaner", "io.hyperapp.xcodecleaner"]
|
exclude: ["com.apple.dt.xcode", "com.oneminutegames.xcodecleaner", "io.hyperapp.xcodecleaner"],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "io.hyperapp.xcodecleaner",
|
bundle_id: "io.hyperapp.xcodecleaner",
|
||||||
include: [],
|
include: [],
|
||||||
exclude: ["com.robotsandpencils.xcodesapp", "com.oneminutegames.xcodecleaner", "com.apple.dt.xcode", "xcodes.json"]
|
exclude: ["com.robotsandpencils.xcodesapp", "com.oneminutegames.xcodecleaner", "com.apple.dt.xcode", "xcodes.json"],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "us.zoom.xos",
|
bundle_id: "us.zoom.xos",
|
||||||
include: ["zoom"],
|
include: ["zoom"],
|
||||||
exclude: []
|
exclude: [],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "com.brave.browser",
|
bundle_id: "com.brave.browser",
|
||||||
include: ["brave"],
|
include: ["brave"],
|
||||||
exclude: []
|
exclude: [],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "com.okta.mobile",
|
bundle_id: "com.okta.mobile",
|
||||||
include: ["okta"],
|
include: ["okta"],
|
||||||
exclude: []
|
exclude: [],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "com.google.chrome",
|
bundle_id: "com.google.chrome",
|
||||||
include: ["google", "chrome"],
|
include: ["google", "chrome"],
|
||||||
exclude: ["iterm", "chromefeaturestate"]
|
exclude: ["iterm", "chromefeaturestate"],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "com.microsoft.edgemac",
|
bundle_id: "com.microsoft.edgemac",
|
||||||
include: ["microsoft"],
|
include: ["microsoft"],
|
||||||
exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"]
|
exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "org.mozilla.firefox",
|
bundle_id: "org.mozilla.firefox",
|
||||||
include: ["mozilla", "firefox"],
|
include: ["mozilla", "firefox"],
|
||||||
exclude: []
|
exclude: [],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "org.mozilla.firefox.nightly",
|
bundle_id: "org.mozilla.firefox.nightly",
|
||||||
include: ["mozilla", "firefox"],
|
include: ["mozilla", "firefox"],
|
||||||
exclude: []
|
exclude: [],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "com.logi.optionsplus",
|
bundle_id: "com.logi.optionsplus",
|
||||||
include: ["logi"],
|
include: ["logi"],
|
||||||
exclude: ["login", "logic"],
|
exclude: ["login", "logic"],
|
||||||
includeForce: []
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "com.microsoft.vscode",
|
bundle_id: "com.microsoft.vscode",
|
||||||
@@ -109,17 +118,20 @@ var conditions: [Condition] = [
|
|||||||
Condition(
|
Condition(
|
||||||
bundle_id: "com.facebook.archon.developerid",
|
bundle_id: "com.facebook.archon.developerid",
|
||||||
include: ["archon.loginhelper"],
|
include: ["archon.loginhelper"],
|
||||||
exclude: []
|
exclude: [],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "eu.exelban.stats",
|
bundle_id: "eu.exelban.stats",
|
||||||
include: [],
|
include: [],
|
||||||
exclude: ["video"]
|
exclude: ["video"],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
Condition(
|
Condition(
|
||||||
bundle_id: "jetbrains",
|
bundle_id: "jetbrains",
|
||||||
include: ["jetbrains", "jcef"],
|
include: ["jetbrains", "jcef"],
|
||||||
exclude: []
|
exclude: [],
|
||||||
|
includeForce: nil
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+142
-60
@@ -109,45 +109,108 @@ func listAppSupportDirectories() -> [String] {
|
|||||||
|
|
||||||
|
|
||||||
// Load app paths on launch
|
// Load app paths on launch
|
||||||
func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, reverseAddon: Bool = false, completion: @escaping () -> Void = {}) {
|
func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, completion: @escaping () -> Void = {}) {
|
||||||
let dispatchGroup = DispatchGroup()
|
// appState.operationQueueLeftover.maxConcurrentOperationCount = 10 // Adjust this value as needed
|
||||||
appState.appInfoStore.removeAll()
|
// appState.shouldCancelOperations = false
|
||||||
|
// appState.appInfoStore.removeAll()
|
||||||
|
// let sortedAllApps = allApps.sorted { $0.appName.localizedCompare($1.appName) == .orderedAscending }
|
||||||
|
|
||||||
for app in allApps {
|
updateOnMain {
|
||||||
dispatchGroup.enter()
|
appState.leftoverProgress.0 = "Finding leftover files, please wait..."
|
||||||
DispatchQueue.global(qos: .background).async {
|
|
||||||
AppPathFinder(appInfo: app, appState: appState, locations: locations, backgroundRun: true, reverseAddon: reverseAddon).findPaths()
|
|
||||||
dispatchGroup.leave()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ReversePathsSearcher(appState: appState, locations: locations, fsm: fsm, sortedApps: allApps).reversePathsSearch {
|
||||||
dispatchGroup.notify(queue: DispatchQueue.global(qos: .background)) {
|
updateOnMain {
|
||||||
|
printOS("Reverse search processed successfully")
|
||||||
func checkAllAppsProcessed(retryCount: Int = 0, maxRetry: Int = 120) {
|
appState.showProgress = false
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
withAnimation {
|
||||||
if appState.appInfoStore.count == allApps.count {
|
appState.leftoverProgress.1 = 0.0
|
||||||
ReversePathsSearcher(appState: appState, locations: locations, fsm: fsm).reversePathsSearch() {
|
|
||||||
updateOnMain {
|
|
||||||
appState.showProgress = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
completion()
|
|
||||||
} else if retryCount < maxRetry {
|
|
||||||
checkAllAppsProcessed(retryCount: retryCount + 1, maxRetry: maxRetry)
|
|
||||||
} else {
|
|
||||||
// Reset progress values to 0
|
|
||||||
updateOnMain {
|
|
||||||
appState.showProgress = false
|
|
||||||
}
|
|
||||||
printOS("loadAllPaths - Retry limit reached. Not all paths were loaded.")
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
appState.leftoverProgress.0 = "Reverse search completed successfully"
|
||||||
}
|
}
|
||||||
|
completion()
|
||||||
checkAllAppsProcessed()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -161,37 +224,56 @@ func showAppInFiles(appInfo: AppInfo, appState: AppState, locations: Locations,
|
|||||||
appState.appInfo = .empty
|
appState.appInfo = .empty
|
||||||
appState.selectedItems = []
|
appState.selectedItems = []
|
||||||
|
|
||||||
// Check if the appInfo exists in the appState.appInfoStore
|
// When the appInfo is not found, show progress, and search for paths.
|
||||||
if let storedAppInfo = appState.appInfoStore.first(where: { $0.path == appInfo.path }) {
|
appState.showProgress = true
|
||||||
// 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.
|
// Initialize the path finder and execute its search.
|
||||||
withAnimation(Animation.easeIn(duration: 0.4)) {
|
AppPathFinder(appInfo: appInfo, appState: appState, locations: locations) {
|
||||||
appState.currentView = .files
|
updateOnMain {
|
||||||
showPopover.wrappedValue.toggle()
|
// Update the progress indicator on the main thread once the search completes.
|
||||||
|
appState.showProgress = false
|
||||||
}
|
}
|
||||||
} else {
|
}.findPaths()
|
||||||
// When the appInfo is not found, show progress, and search for paths.
|
|
||||||
appState.showProgress = true
|
|
||||||
|
|
||||||
// Initialize the path finder and execute its search.
|
appState.appInfo = appInfo
|
||||||
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)) {
|
||||||
// Animate the view change and popover display.
|
appState.currentView = .files
|
||||||
withAnimation(Animation.easeIn(duration: 0.4)) {
|
showPopover.wrappedValue.toggle()
|
||||||
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()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ class ReversePathsSearcher {
|
|||||||
private var fileSizeLogical: [URL: Int64] = [:]
|
private var fileSizeLogical: [URL: Int64] = [:]
|
||||||
private var fileIcon: [URL: NSImage?] = [:]
|
private var fileIcon: [URL: NSImage?] = [:]
|
||||||
private let dispatchGroup = DispatchGroup()
|
private let dispatchGroup = DispatchGroup()
|
||||||
|
private let sortedApps: [AppInfo]
|
||||||
|
|
||||||
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager) {
|
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager, sortedApps: [AppInfo]) {
|
||||||
self.appState = appState
|
self.appState = appState
|
||||||
self.locations = locations
|
self.locations = locations
|
||||||
self.fsm = fsm
|
self.fsm = fsm
|
||||||
|
self.sortedApps = sortedApps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func reversePathsSearch(completion: @escaping () -> Void = {}) {
|
func reversePathsSearch(completion: @escaping () -> Void = {}) {
|
||||||
Task(priority: .high) {
|
Task(priority: .high) {
|
||||||
self.processLocations()
|
self.processLocations()
|
||||||
@@ -38,51 +38,74 @@ class ReversePathsSearcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func processLocations() {
|
private func processLocations() {
|
||||||
let allPaths = appState.appInfoStore.flatMap { $0.fileSize.keys.map { $0.path.pearFormat() } }
|
|
||||||
let allNames = appState.appInfoStore.map { $0.appName.pearFormat() }
|
|
||||||
|
|
||||||
for location in locations.reverse.paths where fileManager.fileExists(atPath: location) {
|
for location in locations.reverse.paths where fileManager.fileExists(atPath: location) {
|
||||||
|
|
||||||
dispatchGroup.enter()
|
dispatchGroup.enter()
|
||||||
processLocation(location, allPaths: allPaths, allNames: allNames)
|
processLocation(location)
|
||||||
dispatchGroup.leave()
|
dispatchGroup.leave()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processLocation(_ location: String, allPaths: [String], allNames: [String]) {
|
private func processLocation(_ location: String) {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let contents = try fileManager.contentsOfDirectory(atPath: location)
|
let contents = try fileManager.contentsOfDirectory(atPath: location)
|
||||||
contents.forEach { itemName in
|
contents.forEach { itemName in
|
||||||
let itemURL = URL(fileURLWithPath: location).appendingPathComponent(itemName)
|
let itemURL = URL(fileURLWithPath: location).appendingPathComponent(itemName)
|
||||||
processItem(itemName, itemURL: itemURL, allPaths: allPaths, allNames: allNames)
|
processItem(itemName, itemURL: itemURL)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
printOS("Error processing location: \(location), error: \(error)")
|
printOS("Error processing location: \(location), error: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processItem(_ itemName: String, itemURL: URL, allPaths: [String], allNames: [String]) {
|
private func processItem(_ itemName: String, itemURL: URL) {
|
||||||
let formattedItemName = itemName.pearFormat()
|
|
||||||
let itemPath = itemURL.path.pearFormat()
|
let itemPath = itemURL.path.pearFormat()
|
||||||
let itemLastPathComponent = itemURL.lastPathComponent.pearFormat()
|
|
||||||
let exclusionList = fsm.fileFolderPathsZ.map { $0.pearFormat() }
|
let exclusionList = fsm.fileFolderPathsZ.map { $0.pearFormat() }
|
||||||
|
|
||||||
if exclusionList.contains(itemPath) || itemPath.contains("dsstore") || itemPath.contains("daemonnameoridentifierhere") {
|
if exclusionList.contains(itemPath) || itemPath.contains("dsstore") || itemPath.contains("daemonnameoridentifierhere") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard !skipReverse.contains(where: { formattedItemName.contains($0) }),
|
guard !skipReverse.contains(where: { itemName.pearFormat().contains($0) }),
|
||||||
!allPaths.contains(where: { $0 == itemPath || $0.hasSuffix("/\(itemLastPathComponent)") }),
|
isSupportedFileType(at: itemURL.path),
|
||||||
!allNames.contains(formattedItemName),
|
!isRelatedToInstalledApp(itemPath: itemPath),
|
||||||
isSupportedFileType(at: itemURL.path) else {
|
!isExcludedByConditions(itemPath: itemPath) else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.append(itemURL)
|
collection.append(itemURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func isRelatedToInstalledApp(itemPath: String) -> Bool {
|
||||||
|
for app in sortedApps {
|
||||||
|
if itemPath.contains(app.bundleIdentifier.pearFormat()) || itemPath.contains(app.appName.pearFormat()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isExcludedByConditions(itemPath: String) -> Bool {
|
||||||
|
|
||||||
|
for condition in conditions {
|
||||||
|
// Include keywords
|
||||||
|
if condition.include.contains(where: { itemPath.contains($0.pearFormat()) }) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Include force
|
||||||
|
if let includeForce = condition.includeForce,
|
||||||
|
includeForce.contains(where: { itemPath.contains($0.path.pearFormat()) }) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
private func calculateFileDetails() {
|
private func calculateFileDetails() {
|
||||||
collection.forEach { path in
|
collection.forEach { path in
|
||||||
let size = totalSizeOnDisk(for: path)
|
let size = totalSizeOnDisk(for: path)
|
||||||
@@ -104,3 +127,5 @@ class ReversePathsSearcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -208,9 +208,9 @@ func removeApp(appState: AppState, withPath path: URL) {
|
|||||||
// return // Exit the function if the app was found and removed
|
// return // Exit the function if the app was found and removed
|
||||||
}
|
}
|
||||||
// Remove from appInfoStore if found
|
// Remove from appInfoStore if found
|
||||||
if let index = appState.appInfoStore.firstIndex(where: { $0.path == path }) {
|
// if let index = appState.appInfoStore.firstIndex(where: { $0.path == path }) {
|
||||||
appState.appInfoStore.remove(at: index)
|
// appState.appInfoStore.remove(at: index)
|
||||||
}
|
// }
|
||||||
// Brew cleanup if enabled
|
// Brew cleanup if enabled
|
||||||
if brew {
|
if brew {
|
||||||
caskCleanup(app: appState.appInfo.appName)
|
caskCleanup(app: appState.appInfo.appName)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ struct PearcleanerApp: App {
|
|||||||
@AppStorage("settings.general.brew") private var brew: Bool = false
|
@AppStorage("settings.general.brew") private var brew: Bool = false
|
||||||
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
||||||
@AppStorage("settings.menubar.mainWin") private var mainWinEnabled: Bool = false
|
@AppStorage("settings.menubar.mainWin") private var mainWinEnabled: Bool = false
|
||||||
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "trash"
|
|
||||||
@State private var search = ""
|
@State private var search = ""
|
||||||
@State private var showPopover: Bool = false
|
@State private var showPopover: Bool = false
|
||||||
let conditionManager = ConditionManager.shared
|
let conditionManager = ConditionManager.shared
|
||||||
@@ -34,11 +33,11 @@ struct PearcleanerApp: App {
|
|||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
Group {
|
Group {
|
||||||
if !mini {
|
if mini {
|
||||||
RegularMode(search: $search, showPopover: $showPopover)
|
MiniMode(search: $search, showPopover: $showPopover)
|
||||||
} else {
|
} else {
|
||||||
MiniMode(search: $search, showPopover: $showPopover)
|
RegularMode(search: $search, showPopover: $showPopover)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environmentObject(appState)
|
.environmentObject(appState)
|
||||||
.environmentObject(locations)
|
.environmentObject(locations)
|
||||||
@@ -92,13 +91,13 @@ struct PearcleanerApp: App {
|
|||||||
appState.currentView = .empty
|
appState.currentView = .empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Disable tabbing
|
// Disable tabbing
|
||||||
NSWindow.allowsAutomaticWindowTabbing = false
|
NSWindow.allowsAutomaticWindowTabbing = false
|
||||||
|
|
||||||
// Load apps list on startup
|
// Load apps list on startup
|
||||||
reloadAppsList(appState: appState, fsm: fsm)
|
reloadAppsList(appState: appState, fsm: fsm)
|
||||||
|
|
||||||
|
|
||||||
// Enable menubar item
|
// Enable menubar item
|
||||||
if menubarEnabled {
|
if menubarEnabled {
|
||||||
MenuBarExtraManager.shared.addMenuBarExtra(withView: {
|
MenuBarExtraManager.shared.addMenuBarExtra(withView: {
|
||||||
@@ -110,19 +109,13 @@ struct PearcleanerApp: App {
|
|||||||
.environmentObject(updater)
|
.environmentObject(updater)
|
||||||
.environmentObject(permissionManager)
|
.environmentObject(permissionManager)
|
||||||
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}, icon: selectedMenubarIcon)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
Task {
|
Task {
|
||||||
|
|
||||||
/// This will check for updates on load based on the update frequency
|
|
||||||
updater.checkAndUpdateIfNeeded()
|
|
||||||
|
|
||||||
/// Get new features
|
|
||||||
updater.checkForAnnouncement()
|
|
||||||
|
|
||||||
// Make sure App Support folder exists in the future if needed for storage
|
// Make sure App Support folder exists in the future if needed for storage
|
||||||
// ensureApplicationSupportFolderExists(appState: appState)
|
// ensureApplicationSupportFolderExists(appState: appState)
|
||||||
|
|
||||||
@@ -136,8 +129,6 @@ struct PearcleanerApp: App {
|
|||||||
.windowResizability(.contentMinSize)
|
.windowResizability(.contentMinSize)
|
||||||
.commands {
|
.commands {
|
||||||
AppCommands(appState: appState, locations: locations, fsm: fsm, updater: updater, themeManager: themeManager)
|
AppCommands(appState: appState, locations: locations, fsm: fsm, updater: updater, themeManager: themeManager)
|
||||||
// CommandGroup(replacing: .newItem, addition: { })
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ struct GeneralSettingsTab: View {
|
|||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
@EnvironmentObject var themeManager: ThemeManager
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@State private var windowSettings = WindowSettings()
|
|
||||||
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
|
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
|
||||||
@AppStorage("settings.general.brew") private var brew: Bool = false
|
@AppStorage("settings.general.brew") private var brew: Bool = false
|
||||||
@AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false
|
@AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ struct InterfaceSettingsTab: View {
|
|||||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||||
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
||||||
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
||||||
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles"
|
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles.fill"
|
||||||
@State private var isLaunchAtLoginEnabled: Bool = false
|
@State private var isLaunchAtLoginEnabled: Bool = false
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@@ -143,9 +143,7 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(updater)
|
.environmentObject(updater)
|
||||||
.environmentObject(permissionManager)
|
.environmentObject(permissionManager)
|
||||||
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}) {
|
})
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if appState.appInfo.appName.isEmpty {
|
if appState.appInfo.appName.isEmpty {
|
||||||
appState.currentView = .empty
|
appState.currentView = .empty
|
||||||
@@ -162,9 +160,7 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(permissionManager)
|
.environmentObject(permissionManager)
|
||||||
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}
|
}
|
||||||
) {
|
)
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -265,10 +261,9 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(updater)
|
.environmentObject(updater)
|
||||||
.environmentObject(permissionManager)
|
.environmentObject(permissionManager)
|
||||||
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}, icon: selectedMenubarIcon)
|
})
|
||||||
NSApplication.shared.setActivationPolicy(.accessory)
|
NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
findAndHideWindows(named: ["Pearcleaner"])
|
findAndHideWindows(named: ["Pearcleaner"])
|
||||||
// findAndShowWindows(named: ["Pearcleaner", "Interface"])
|
|
||||||
} else {
|
} else {
|
||||||
MenuBarExtraManager.shared.removeMenuBarExtra()
|
MenuBarExtraManager.shared.removeMenuBarExtra()
|
||||||
NSApplication.shared.setActivationPolicy(.regular)
|
NSApplication.shared.setActivationPolicy(.regular)
|
||||||
@@ -283,9 +278,7 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(updater)
|
.environmentObject(updater)
|
||||||
.environmentObject(permissionManager)
|
.environmentObject(permissionManager)
|
||||||
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}) {
|
})
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
windowSettings.newWindow(withView: {
|
windowSettings.newWindow(withView: {
|
||||||
RegularMode(search: $search, showPopover: $showPopover)
|
RegularMode(search: $search, showPopover: $showPopover)
|
||||||
@@ -296,9 +289,7 @@ struct InterfaceSettingsTab: View {
|
|||||||
.environmentObject(updater)
|
.environmentObject(updater)
|
||||||
.environmentObject(permissionManager)
|
.environmentObject(permissionManager)
|
||||||
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
.preferredColorScheme(themeManager.displayMode.colorScheme)
|
||||||
}) {
|
})
|
||||||
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ struct AppListItems: View {
|
|||||||
@EnvironmentObject var themeManager: ThemeManager
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
@State private var windowSettings = WindowSettings()
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@AppStorage("displayMode") var displayMode: DisplayMode = .system
|
@AppStorage("displayMode") var displayMode: DisplayMode = .system
|
||||||
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
||||||
@@ -79,13 +78,10 @@ struct AppListItems: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bundleSize == 0 {
|
Text(bundleSize == 0 ? "v\(appInfo.appVersion)" : (isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human))
|
||||||
ProgressView().controlSize(.mini).padding(.leading, 5)
|
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
|
||||||
} else {
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
Text("\(isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human)")
|
|
||||||
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
|
|
||||||
.foregroundStyle(.primary.opacity(0.5))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ struct AppSearchView: View {
|
|||||||
appState.currentView = .zombie
|
appState.currentView = .zombie
|
||||||
appState.showProgress.toggle()
|
appState.showProgress.toggle()
|
||||||
showPopover.toggle()
|
showPopover.toggle()
|
||||||
reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm, reverseAddon: true)
|
reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm)
|
||||||
} else {
|
} else {
|
||||||
appState.currentView = .zombie
|
appState.currentView = .zombie
|
||||||
showPopover.toggle()
|
showPopover.toggle()
|
||||||
|
|||||||
@@ -46,13 +46,11 @@ struct SectionView: View {
|
|||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
Header(title: title, count: count, showPopover: $showPopover)
|
Header(title: title, count: count, showPopover: $showPopover)
|
||||||
.padding(.leading, 5)
|
.padding(.leading, 5)
|
||||||
ForEach(apps, id: \.self) { appInfo in
|
ForEach(apps, id: \.self) { appInfo in
|
||||||
AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo)
|
AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo)
|
||||||
if appInfo != apps.last {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ struct FilesView: View {
|
|||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@State private var elapsedTime = 0
|
// @State private var elapsedTime = 0
|
||||||
@State private var timer: Timer? = nil
|
// @State private var timer: Timer? = nil
|
||||||
@State private var isShowingInspector = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
@@ -51,35 +50,42 @@ struct FilesView: View {
|
|||||||
if appState.showProgress {
|
if appState.showProgress {
|
||||||
VStack {
|
VStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Searching the file system").font(.title3)
|
|
||||||
.foregroundStyle((.primary.opacity(0.5)))
|
|
||||||
|
|
||||||
ProgressView()
|
HStack(spacing: 10) {
|
||||||
.progressViewStyle(.linear)
|
Text("Searching the file system").font(.title3)
|
||||||
.frame(width: 400, height: 10)
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
ProgressView().controlSize(.small)
|
||||||
|
}
|
||||||
|
|
||||||
Text("\(elapsedTime)")
|
// Text("Searching the file system").font(.title3)
|
||||||
.font(.title).monospacedDigit()
|
// .foregroundStyle((.primary.opacity(0.5)))
|
||||||
.foregroundStyle((.primary.opacity(0.5)))
|
//
|
||||||
.opacity(elapsedTime == 0 ? 0 : 1)
|
// ProgressView()
|
||||||
.contentTransition(.numericText())
|
// .progressViewStyle(.linear)
|
||||||
|
// .frame(width: 400, height: 10)
|
||||||
|
//
|
||||||
|
// Text("\(elapsedTime)")
|
||||||
|
// .font(.title).monospacedDigit()
|
||||||
|
// .foregroundStyle((.primary.opacity(0.5)))
|
||||||
|
// .opacity(elapsedTime == 0 ? 0 : 1)
|
||||||
|
// .contentTransition(.numericText())
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.onAppear {
|
// .onAppear {
|
||||||
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
// self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
||||||
withAnimation {
|
// withAnimation {
|
||||||
self.elapsedTime += 1
|
// self.elapsedTime += 1
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
.onDisappear {
|
// .onDisappear {
|
||||||
self.timer?.invalidate()
|
// self.timer?.invalidate()
|
||||||
self.timer = nil
|
// self.timer = nil
|
||||||
self.elapsedTime = 0
|
// self.elapsedTime = 0
|
||||||
}
|
// }
|
||||||
} else {
|
} else {
|
||||||
// Titlebar
|
// Titlebar
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
@@ -355,9 +361,9 @@ struct FilesView: View {
|
|||||||
appState.trashedFiles.append(appState.appInfo)
|
appState.trashedFiles.append(appState.appInfo)
|
||||||
|
|
||||||
// Clear out appInfoStore object (Used for leftover file search)
|
// Clear out appInfoStore object (Used for leftover file search)
|
||||||
if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) {
|
// if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) {
|
||||||
appState.appInfoStore[index] = .empty
|
// appState.appInfoStore[index] = .empty
|
||||||
}
|
// }
|
||||||
|
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
// Remove items from the list
|
// Remove items from the list
|
||||||
|
|||||||
@@ -13,21 +13,20 @@ class MenuBarExtraManager {
|
|||||||
private var statusItem: NSStatusItem?
|
private var statusItem: NSStatusItem?
|
||||||
private var popover = NSPopover()
|
private var popover = NSPopover()
|
||||||
private var lastView: (() -> AnyView)?
|
private var lastView: (() -> AnyView)?
|
||||||
private var lastIcon: String?
|
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles.fill"
|
||||||
|
|
||||||
func addMenuBarExtra<V: View>(withView view: @escaping () -> V, icon: String) {
|
func addMenuBarExtra<V: View>(withView view: @escaping () -> V) {
|
||||||
guard statusItem == nil else { return }
|
guard statusItem == nil else { return }
|
||||||
|
|
||||||
// Remember the last view and icon
|
// Remember the last view
|
||||||
lastView = { AnyView(view()) }
|
lastView = { AnyView(view()) }
|
||||||
lastIcon = icon
|
|
||||||
|
|
||||||
// Initialize the status item
|
// Initialize the status item
|
||||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||||
|
|
||||||
// Set up the status item's button
|
// Set up the status item's button
|
||||||
if let button = statusItem?.button{
|
if let button = statusItem?.button{
|
||||||
button.image = NSImage(systemSymbolName: icon, accessibilityDescription: "Pearcleaner")
|
button.image = NSImage(systemSymbolName: selectedMenubarIcon, accessibilityDescription: "Pearcleaner")
|
||||||
button.action = #selector(togglePopover(_:))
|
button.action = #selector(togglePopover(_:))
|
||||||
button.target = self
|
button.target = self
|
||||||
button.sendAction(on: [.leftMouseDown, .rightMouseDown])
|
button.sendAction(on: [.leftMouseDown, .rightMouseDown])
|
||||||
@@ -51,8 +50,6 @@ class MenuBarExtraManager {
|
|||||||
guard let button = statusItem?.button else { return }
|
guard let button = statusItem?.button else { return }
|
||||||
if let image = NSImage(systemSymbolName: icon, accessibilityDescription: nil) {
|
if let image = NSImage(systemSymbolName: icon, accessibilityDescription: nil) {
|
||||||
button.image = image
|
button.image = image
|
||||||
} else {
|
|
||||||
button.image = NSImage(named: icon)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +58,8 @@ class MenuBarExtraManager {
|
|||||||
self.removeMenuBarExtra()
|
self.removeMenuBarExtra()
|
||||||
|
|
||||||
// Ensure the last view and icon are available before re-adding
|
// Ensure the last view and icon are available before re-adding
|
||||||
if let lastView = self.lastView, let lastIcon = self.lastIcon {
|
if let lastView = self.lastView {
|
||||||
self.addMenuBarExtra(withView: lastView, icon: lastIcon)
|
self.addMenuBarExtra(withView: lastView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AlinFoundation
|
import AlinFoundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
class WindowSettings {
|
class WindowSettings {
|
||||||
private let windowWidthKey = "windowWidthKey"
|
private let windowWidthKey = "windowWidthKey"
|
||||||
@@ -76,7 +78,7 @@ class WindowSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Launch new app windows on demand
|
// Launch new app windows on demand
|
||||||
func newWindow<V: View>(withView view: @escaping () -> V, completion: @escaping () -> Void = {}) {
|
func newWindow<V: View>(withView view: @escaping () -> V) {
|
||||||
findAndHideWindows(named: ["Pearcleaner"])
|
findAndHideWindows(named: ["Pearcleaner"])
|
||||||
let contentView = view
|
let contentView = view
|
||||||
let frame = self.loadWindowSettings()
|
let frame = self.loadWindowSettings()
|
||||||
@@ -94,26 +96,6 @@ class WindowSettings {
|
|||||||
newWindow.contentView = NSHostingView(rootView: contentView())
|
newWindow.contentView = NSHostingView(rootView: contentView())
|
||||||
self.windows.append(newWindow)
|
self.windows.append(newWindow)
|
||||||
newWindow.makeKeyAndOrderFront(nil)
|
newWindow.makeKeyAndOrderFront(nil)
|
||||||
completion()
|
resizeWindowAuto(windowSettings: self, title: "Pearcleaner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//struct WillRestore: ViewModifier {
|
|
||||||
// let restore: Bool
|
|
||||||
//
|
|
||||||
// func body(content: Content) -> some View {
|
|
||||||
// content
|
|
||||||
// .onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification), perform: { output in
|
|
||||||
// let window = output.object as! NSWindow
|
|
||||||
// window.isRestorable = false
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//extension View {
|
|
||||||
// func willRestore(_ restoreState: Bool = true) -> some View {
|
|
||||||
// modifier(WillRestore(restore: restoreState))
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ struct ZombieView: View {
|
|||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@State private var searchZ: String = ""
|
@State private var searchZ: String = ""
|
||||||
@State private var elapsedTime = 0
|
// @State private var elapsedTime = 0
|
||||||
@State private var timer: Timer? = nil
|
// @State private var timer: Timer? = nil
|
||||||
@State private var selectedZombieItemsLocal: Set<URL> = []
|
@State private var selectedZombieItemsLocal: Set<URL> = []
|
||||||
@State private var memoizedFiles: [URL] = []
|
@State private var memoizedFiles: [URL] = []
|
||||||
@State private var lastSearchTermUsed: String? = nil
|
@State private var lastSearchTermUsed: String? = nil
|
||||||
@@ -45,39 +45,73 @@ struct ZombieView: View {
|
|||||||
Group {
|
Group {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("Searching the file system").font(.title3)
|
HStack(spacing: 10) {
|
||||||
.foregroundStyle(.primary.opacity(0.5))
|
Text("Searching the file system").font(.title3)
|
||||||
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
ProgressView()
|
ProgressView().controlSize(.small)
|
||||||
.progressViewStyle(.linear)
|
}
|
||||||
.frame(width: 400, height: 10)
|
|
||||||
|
|
||||||
|
|
||||||
Text("\(elapsedTime)")
|
// ProgressView()
|
||||||
.font(.title).monospacedDigit()
|
// .progressViewStyle(.linear)
|
||||||
.foregroundStyle(.primary.opacity(0.5))
|
// .frame(width: 400, height: 10)
|
||||||
.opacity(elapsedTime == 0 ? 0 : 1)
|
|
||||||
.contentTransition(.numericText())
|
|
||||||
|
// Text("\(elapsedTime)")
|
||||||
|
// .font(.title).monospacedDigit()
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// .opacity(elapsedTime == 0 ? 0 : 1)
|
||||||
|
// .contentTransition(.numericText())
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
|
// Spacer()
|
||||||
|
//
|
||||||
|
// HStack {
|
||||||
|
// Text("\(appState.leftoverProgress.0)").font(.title3)
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// Spacer()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// HStack {
|
||||||
|
// ProgressView(value: appState.leftoverProgress.1, total: 1.0)
|
||||||
|
// .progressViewStyle(.linear)
|
||||||
|
// Button("Cancel") {
|
||||||
|
// updateOnMain {
|
||||||
|
// appState.cancelQueueOperations()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .buttonStyle(SimpleButtonBrightStyle(icon: "x.circle", help: "Cancel search", color: .primary))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Text("\(Int(appState.leftoverProgress.1 * 100)) %")
|
||||||
|
// .font(.title).monospacedDigit()
|
||||||
|
// .foregroundStyle(.primary.opacity(0.5))
|
||||||
|
// .contentTransition(.numericText())
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Spacer()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.padding(50)
|
||||||
|
.transition(.opacity)
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.onAppear {
|
// .onAppear {
|
||||||
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
// self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
||||||
withAnimation {
|
// withAnimation {
|
||||||
self.elapsedTime += 1
|
// self.elapsedTime += 1
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
.onDisappear {
|
// .onDisappear {
|
||||||
self.timer?.invalidate()
|
// self.timer?.invalidate()
|
||||||
self.timer = nil
|
// self.timer = nil
|
||||||
self.elapsedTime = 0
|
// self.elapsedTime = 0
|
||||||
}
|
// }
|
||||||
} else {
|
} else {
|
||||||
// Titlebar
|
// Titlebar
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
@@ -226,7 +260,7 @@ struct ZombieView: View {
|
|||||||
updateOnMain {
|
updateOnMain {
|
||||||
appState.zombieFile = .empty
|
appState.zombieFile = .empty
|
||||||
appState.showProgress.toggle()
|
appState.showProgress.toggle()
|
||||||
reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm, reverseAddon: true)
|
reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(RescanButton())
|
.buttonStyle(RescanButton())
|
||||||
|
|||||||
Reference in New Issue
Block a user