This commit is contained in:
Alin
2024-06-14 17:44:33 -06:00
parent ca0785f275
commit 1f2e1202a8
12 changed files with 345 additions and 137 deletions
+8 -4
View File
@@ -48,6 +48,7 @@
C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49EF2BABA14400CCBA16 /* Folders.swift */; }; C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49EF2BABA14400CCBA16 /* Folders.swift */; };
C7DE17652BE54F4900197715 /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE17642BE54F4900197715 /* Updater.swift */; }; C7DE17652BE54F4900197715 /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE17642BE54F4900197715 /* Updater.swift */; };
C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */; }; C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */; };
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */; };
C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B02BA8A35500F13DE4 /* Interface.swift */; }; C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B02BA8A35500F13DE4 /* Interface.swift */; };
C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B22BA8ED8800F13DE4 /* Trash.swift */; }; C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B22BA8ED8800F13DE4 /* Trash.swift */; };
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; }; C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; };
@@ -145,6 +146,7 @@
C7DD49EF2BABA14400CCBA16 /* Folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Folders.swift; sourceTree = "<group>"; }; C7DD49EF2BABA14400CCBA16 /* Folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Folders.swift; sourceTree = "<group>"; };
C7DE17642BE54F4900197715 /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; }; C7DE17642BE54F4900197715 /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItem.swift; sourceTree = "<group>"; }; C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItem.swift; sourceTree = "<group>"; };
C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionBuilderView.swift; sourceTree = "<group>"; };
C7ED86B02BA8A35500F13DE4 /* Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interface.swift; sourceTree = "<group>"; }; C7ED86B02BA8A35500F13DE4 /* Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interface.swift; sourceTree = "<group>"; };
C7ED86B22BA8ED8800F13DE4 /* Trash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trash.swift; sourceTree = "<group>"; }; C7ED86B22BA8ED8800F13DE4 /* Trash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trash.swift; sourceTree = "<group>"; };
C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; }; C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
@@ -309,6 +311,7 @@
C7FB173A2B96321300B96F9A /* AppsListView.swift */, C7FB173A2B96321300B96F9A /* AppsListView.swift */,
C7FEBA102BDC422200AE195F /* AppSearchView.swift */, C7FEBA102BDC422200AE195F /* AppSearchView.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -471,6 +474,7 @@
C7DE17652BE54F4900197715 /* Updater.swift in Sources */, C7DE17652BE54F4900197715 /* Updater.swift in Sources */,
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */, C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */,
C7045A322B068AD700376976 /* NewWin.swift in Sources */, C7045A322B068AD700376976 /* NewWin.swift in Sources */,
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */,
C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */, C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */,
C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */, C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */,
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */, C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */,
@@ -568,8 +572,8 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 47; APP_BUILD = 48;
APP_VERSION = 3.7.2; APP_VERSION = 3.7.3;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -638,8 +642,8 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 47; APP_BUILD = 48;
APP_VERSION = 3.7.2; APP_VERSION = 3.7.3;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+26 -11
View File
@@ -153,17 +153,15 @@ class AppPathFinder {
for condition in conditions { for condition in conditions {
if bundleIdentifierL.contains(condition.bundle_id) { if bundleIdentifierL.contains(condition.bundle_id) {
// Exclude and include keywords // Exclude keywords
let hasIncludeKeyword = condition.include.contains(where: itemL.contains)
let hasExcludeKeyword = condition.exclude.contains(where: itemL.contains) let hasExcludeKeyword = condition.exclude.contains(where: itemL.contains)
if hasExcludeKeyword { if hasExcludeKeyword {
return false return false
} }
// Include keywords
let hasIncludeKeyword = condition.include.contains(where: itemL.contains)
if hasIncludeKeyword { if hasIncludeKeyword {
if !condition.exclude.contains(where: itemL.contains) { return true
return true
}
} }
} }
} }
@@ -230,7 +228,7 @@ class AppPathFinder {
} }
private func handleOutliers() -> [URL] { private func handleOutliers(include: Bool = true) -> [URL] {
var outliers: [URL] = [] var outliers: [URL] = []
let bundleIdentifier = self.appInfo.bundleIdentifier.pearFormat() let bundleIdentifier = self.appInfo.bundleIdentifier.pearFormat()
@@ -239,14 +237,24 @@ class AppPathFinder {
bundleIdentifier.contains(condition.bundle_id) bundleIdentifier.contains(condition.bundle_id)
} }
for condition in matchingConditions { for condition in matchingConditions {
if let forceIncludes = condition.includeForce { if include {
for path in forceIncludes { // Handle includeForce
if let url = URL(string: path), FileManager.default.fileExists(atPath: url.path) { if let forceIncludes = condition.includeForce {
outliers.append(url) for path in forceIncludes {
outliers.append(path)
}
}
} else {
// Handle excludeForce
if let excludeForce = condition.excludeForce {
for path in excludeForce {
outliers.append(path)
} }
} }
} }
} }
return outliers return outliers
@@ -256,6 +264,7 @@ class AppPathFinder {
DispatchQueue.global(qos: .userInitiated).async { 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 outliers = self.handleOutliers()
let outliersEx = self.handleOutliers(include: false)
var tempCollection: [URL] = [] var tempCollection: [URL] = []
self.collectionAccessQueue.sync { self.collectionAccessQueue.sync {
tempCollection = self.collection tempCollection = self.collection
@@ -263,6 +272,12 @@ class AppPathFinder {
tempCollection.append(contentsOf: allContainers) tempCollection.append(contentsOf: allContainers)
tempCollection.append(contentsOf: outliers) tempCollection.append(contentsOf: outliers)
// Remove URLs based on outliersExcludes
let excludePaths = outliersEx.map { $0.path }
tempCollection.removeAll { url in
excludePaths.contains(url.path)
}
// Sort and standardize URLs to ensure consistent comparisons // Sort and standardize URLs to ensure consistent comparisons
let sortedCollection = tempCollection.map { $0.standardizedFileURL }.sorted(by: { $0.path < $1.path }) let sortedCollection = tempCollection.map { $0.standardizedFileURL }.sorted(by: { $0.path < $1.path })
var filteredCollection: [URL] = [] var filteredCollection: [URL] = []
+98 -51
View File
@@ -6,13 +6,32 @@
// //
import Foundation import Foundation
import SwiftData
struct Condition: Codable {
struct Condition: Decodable {
var bundle_id: String var bundle_id: String
var include: [String] var include: [String]
var exclude: [String] var exclude: [String]
var includeForce: [String]? var includeForce: [URL]?
var excludeForce: [URL]?
init(bundle_id: String, include: [String], exclude: [String], includeForce: [String]? = nil, excludeForce: [String]? = nil) {
self.bundle_id = bundle_id.pearFormat()
self.include = include.map { $0.pearFormat() }
self.exclude = exclude.map { $0.pearFormat() }
self.includeForce = includeForce?.compactMap { path in
if let url = URL(string: path), FileManager.default.fileExists(atPath: url.path) {
return url
}
return nil
}
self.excludeForce = excludeForce?.compactMap { path in
if let url = URL(string: path), FileManager.default.fileExists(atPath: url.path) {
return url
}
return nil
}
}
} }
struct SkipCondition { struct SkipCondition {
@@ -25,70 +44,75 @@ struct SkipCondition {
// Conditions for some apps that need to include/exclude certain files/folders when names are more complicated // Conditions for some apps that need to include/exclude certain files/folders when names are more complicated
var conditions: [Condition] = [ var conditions: [Condition] = [
Condition( Condition(
bundle_id: "comappledtxcode", bundle_id: "com.apple.dt.xcode",
include: ["comappledt", "xcode", "simulator"], include: ["com.apple.dt", "xcode", "simulator"],
exclude: ["comrobotsandpencilsxcodesapp", "comoneminutegamesxcodecleaner", "iohyperappxcodecleaner", "xcodesjson"], exclude: ["com.robotsandpencils.xcodesapp", "com.oneminutegames.xcodecleaner", "io.hyperapp.xcodecleaner", "xcodes.json"],
includeForce: ["\(home)/Library/Containers/com.apple.iphonesimulator.ShareExtension"] includeForce: ["\(home)/Library/Containers/com.apple.iphonesimulator.ShareExtension"]
), ),
Condition( Condition(
bundle_id: "comrobotsandpencilsxcodesapp", bundle_id: "com.robotsandpencils.xcodesapp",
include: [], include: [],
exclude: ["comappledtxcode", "comoneminutegamesxcodecleaner", "iohyperappxcodecleaner"] exclude: ["com.apple.dt.xcode", "com.oneminutegames.xcodecleaner", "io.hyperapp.xcodecleaner"]
), ),
Condition( Condition(
bundle_id: "iohyperappxcodecleaner", bundle_id: "io.hyperapp.xcodecleaner",
include: [], include: [],
exclude: ["comrobotsandpencilsxcodesapp", "comoneminutegamesxcodecleaner", "comappledtxcode", "xcodesjson"] exclude: ["com.robotsandpencils.xcodesapp", "com.oneminutegames.xcodecleaner", "com.apple.dt.xcode", "xcodes.json"]
), ),
Condition( Condition(
bundle_id: "uszoomxos", bundle_id: "us.zoom.xos",
include: ["zoom"], include: ["zoom"],
exclude: [] exclude: []
), ),
Condition( Condition(
bundle_id: "combravebrowser", bundle_id: "com.brave.browser",
include: ["brave"], include: ["brave"],
exclude: [] exclude: []
), ),
Condition( Condition(
bundle_id: "comoktamobile", bundle_id: "com.okta.mobile",
include: ["okta"], include: ["okta"],
exclude: [] exclude: []
), ),
Condition( Condition(
bundle_id: "comgooglechrome", bundle_id: "com.google.chrome",
include: ["google", "chrome"], include: ["google", "chrome"],
exclude: ["iterm", "chromefeaturestate"] exclude: ["iterm", "chromefeaturestate"]
), ),
Condition( Condition(
bundle_id: "commicrosoftedgemac", bundle_id: "com.microsoft.edgemac",
include: ["microsoft"], include: ["microsoft"],
exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"] exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"]
), ),
Condition( Condition(
bundle_id: "orgmozillafirefox", bundle_id: "org.mozilla.firefox",
include: ["mozilla", "firefox"], include: ["mozilla", "firefox"],
exclude: [] exclude: []
), ),
Condition( Condition(
bundle_id: "comlogioptionsplus", bundle_id: "org.mozilla.firefox.nightly",
include: ["mozilla", "firefox"],
exclude: []
),
Condition(
bundle_id: "com.logi.optionsplus",
include: ["logi"], include: ["logi"],
exclude: ["login", "logic"], exclude: ["login", "logic"],
includeForce: [] includeForce: []
), ),
Condition( Condition(
bundle_id: "commicrosoftvscode", bundle_id: "com.microsoft.vscode",
include: ["vscode"], include: ["vscode"],
exclude: [], exclude: [],
includeForce: ["\(home)/Library/Application Support/Code"] includeForce: ["\(home)/Library/Application Support/Code/"]
), ),
Condition( Condition(
bundle_id: "comfacebookarchondeveloperid", bundle_id: "com.facebook.archon.developerid",
include: ["archonloginhelper"], include: ["archon.loginhelper"],
exclude: [] exclude: []
), ),
Condition( Condition(
bundle_id: "euexelbanstats", bundle_id: "eu.exelban.stats",
include: [], include: [],
exclude: ["video"] exclude: ["video"]
), ),
@@ -114,36 +138,59 @@ let skipConditions: [SkipCondition] = [
let skipReverse = ["apple", "temporary", "btserver", "proapps", "scripteditor", "ilife", "livefsd", "siritoday", "addressbook", "animoji", "appstore", "askpermission", "callhistory", "clouddocs", "diskimages", "dock", "facetime", "fileprovider", "instruments", "knowledge", "mobilesync", "syncservices", "homeenergyd", "icloud", "icdd", "networkserviceproxy", "familycircle", "geoservices", "installation", "passkit", "sharedimagecache", "desktop", "mbuseragent", "swiftpm", "baseband", "coresimulator", "photoslegacyupgrade", "photosupgrade", "siritts", "ipod", "globalpreferences", "apmanalytics", "apmexperiment", "avatarcache", "byhost", "contextstoreagent", "mobilemeaccounts", "intentbuilderc", "loginwindow", "momc", "replayd", "sharedfilelistd", "clang", "audiocomponent", "csexattrcryptoservice", "livetranscriptionagent", "sandboxhelper", "statuskitagent", "betaenrollmentd", "contentlinkingd", "diagnosticextensionsd", "gamed", "heard", "homed", "itunescloudd", "lldb", "mds", "mediaanalysisd", "metrickitd", "mobiletimerd", "proactived", "ptpcamerad", "studentd", "talagent", "watchlistd", "apptranslocation", "xcrun", "ds_store", "caches", "crashreporter", "trash", "pearcleaner", "amsdatamigratortool", "arfilecache", "assistant", "chromium", "cloudkit", "webkit", "databases", "diagnostic", "cache", "gamekit", "homebrew", "logi", "microsoft", "mozilla", "sync", "google", "sentinel", "hexnode", "sentry", "tvappservices", "reminders"] let skipReverse = ["apple", "temporary", "btserver", "proapps", "scripteditor", "ilife", "livefsd", "siritoday", "addressbook", "animoji", "appstore", "askpermission", "callhistory", "clouddocs", "diskimages", "dock", "facetime", "fileprovider", "instruments", "knowledge", "mobilesync", "syncservices", "homeenergyd", "icloud", "icdd", "networkserviceproxy", "familycircle", "geoservices", "installation", "passkit", "sharedimagecache", "desktop", "mbuseragent", "swiftpm", "baseband", "coresimulator", "photoslegacyupgrade", "photosupgrade", "siritts", "ipod", "globalpreferences", "apmanalytics", "apmexperiment", "avatarcache", "byhost", "contextstoreagent", "mobilemeaccounts", "intentbuilderc", "loginwindow", "momc", "replayd", "sharedfilelistd", "clang", "audiocomponent", "csexattrcryptoservice", "livetranscriptionagent", "sandboxhelper", "statuskitagent", "betaenrollmentd", "contentlinkingd", "diagnosticextensionsd", "gamed", "heard", "homed", "itunescloudd", "lldb", "mds", "mediaanalysisd", "metrickitd", "mobiletimerd", "proactived", "ptpcamerad", "studentd", "talagent", "watchlistd", "apptranslocation", "xcrun", "ds_store", "caches", "crashreporter", "trash", "pearcleaner", "amsdatamigratortool", "arfilecache", "assistant", "chromium", "cloudkit", "webkit", "databases", "diagnostic", "cache", "gamekit", "homebrew", "logi", "microsoft", "mozilla", "sync", "google", "sentinel", "hexnode", "sentry", "tvappservices", "reminders"]
// Function to load additional conditions from a GitHub JSON file
func loadConditionsFromGitHub() {
let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/contents/conditions.json")!
var request = URLRequest(url: url)
request.setValue("application/vnd.github.VERSION.raw", forHTTPHeaderField: "Accept")
request.setValue("2022-11-28", forHTTPHeaderField: "X-GitHub-Api-Version")
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
// Assuming the JSON structure directly maps to an array of Condition
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
if let conditionArray = jsonObject as? [[String: Any]] {
let jsonData = try JSONSerialization.data(withJSONObject: conditionArray, options: [])
let additionalConditions = try JSONDecoder().decode([Condition].self, from: jsonData)
if !additionalConditions.isEmpty {
DispatchQueue.main.async {
conditions += additionalConditions // Store and load conditions locally via SwiftData
} class ConditionManager {
} static let shared = ConditionManager()
} else {
printOS("The data format is incorrect or empty for GitHub conditions processing.") private init() {
} loadConditions()
} catch { }
printOS("Failed to decode conditions JSON: \(error.localizedDescription)")
} // Function to save a condition
} else { func saveCondition(_ condition: Condition) {
printOS("Failed to fetch conditions data: \(error?.localizedDescription ?? "Unknown error")") if condition.include.isEmpty && condition.exclude.isEmpty && (condition.includeForce?.isEmpty ?? true) {
deleteCondition(bundle_id: condition.bundle_id)
return
} }
}.resume() let defaults = UserDefaults.standard
} let encoder = JSONEncoder()
let key = "Condition-\(condition.bundle_id)"
if let encoded = try? encoder.encode(condition) {
defaults.set(encoded, forKey: key)
conditions.append(condition)
}
}
// Function to delete a condition from defaults and conditions variable
func deleteCondition(bundle_id: String) {
let defaults = UserDefaults.standard
let key = "Condition-\(bundle_id.pearFormat())"
// Remove from UserDefaults
defaults.removeObject(forKey: key)
// Remove from conditions variable
conditions.removeAll { $0.bundle_id == bundle_id.pearFormat() }
}
// Function to load a condition and append to the global variable
func loadConditions() {
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
for (key, value) in defaults.dictionaryRepresentation() {
if key.starts(with: "Condition-"), let savedCondition = value as? Data {
if let loadedCondition = try? decoder.decode(Condition.self, from: savedCondition) {
conditions.append(loadedCondition)
}
}
}
}
}
+1
View File
@@ -26,6 +26,7 @@ class Locations: ObservableObject {
self.tempDir = tempDir self.tempDir = tempDir
self.apps = Category(name: "Apps", paths: [ self.apps = Category(name: "Apps", paths: [
"\(home)",
"\(home)/Library", "\(home)/Library",
"\(home)/Library/Application Scripts", "\(home)/Library/Application Scripts",
"\(home)/Library/Application Support", "\(home)/Library/Application Support",
+14
View File
@@ -630,6 +630,20 @@ struct AnimatedSearchStyle: TextFieldStyle {
} }
struct RoundedTextFieldStyle: TextFieldStyle {
func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.padding(8)
.cornerRadius(6)
.textFieldStyle(.plain)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(Color("mode").opacity(0.4), lineWidth: 0.8)
)
}
}
struct GlassEffect: NSViewRepresentable { struct GlassEffect: NSViewRepresentable {
var material: NSVisualEffectView.Material // Choose the material you want var material: NSVisualEffectView.Material // Choose the material you want
+4 -60
View File
@@ -52,10 +52,10 @@ func loadGithubReleases(appState: AppState, manual: Bool = false, releaseOnly: B
}.resume() }.resume()
} }
func checkForUpdate(appState: AppState, manual: Bool = false) { func checkForUpdate(appState: AppState, manual: Bool) {
guard let latestRelease = appState.releases.first else { return } guard let latestRelease = appState.releases.first else { return }
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
if latestRelease.tag_name > currentVersion ?? "" { if latestRelease.tag_name > currentVersion {
updateOnMain { updateOnMain {
appState.updateAvailable = true appState.updateAvailable = true
} }
@@ -198,14 +198,6 @@ enum UpdateFrequency: String, CaseIterable, Identifiable {
} }
} }
//func updateNextUpdateDate() {
// @AppStorage("settings.updater.updateFrequency") var updateFrequency: UpdateFrequency = .daily
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
//
// guard let updateInterval = updateFrequency.interval else { return }
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(updateInterval))
// nextUpdateDate = newUpdateDate.timeIntervalSinceReferenceDate
//}
func checkAndUpdateIfNeeded(appState: AppState) { func checkAndUpdateIfNeeded(appState: AppState) {
@AppStorage("settings.updater.updateFrequency") var updateFrequency: UpdateFrequency = .daily @AppStorage("settings.updater.updateFrequency") var updateFrequency: UpdateFrequency = .daily
@@ -230,7 +222,7 @@ func checkAndUpdateIfNeeded(appState: AppState) {
func updateApp(appState: AppState) { func updateApp(appState: AppState) {
// Perform your update logic here // Perform your update logic here
printOS("Updater: performing update") printOS("Updater: checking for update")
loadGithubReleases(appState: appState) loadGithubReleases(appState: appState)
} }
@@ -243,54 +235,6 @@ func isSameDay(date1: Date, date2: Date) -> Bool {
return Calendar.current.isDate(date1, inSameDayAs: date2) return Calendar.current.isDate(date1, inSameDayAs: date2)
} }
//func updateNextUpdateDate() {
// @AppStorage("settings.updater.updateTimeframe") var updateTimeframe: Int = 1
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
// let updateSeconds = updateTimeframe.daysToSeconds
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(updateSeconds))
// nextUpdateDate = newUpdateDate.timeIntervalSinceReferenceDate
//}
//
//func checkAndUpdateIfNeeded(appState: AppState) {
// @AppStorage("settings.updater.updateTimeframe") var updateTimeframe: Int = 1
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
//
// let updateSeconds = updateTimeframe.daysToSeconds
// let now = Date()
//
// // Retrieve the next update date from UserDefaults
// let nextUpdateDateLocal = Date(timeIntervalSinceReferenceDate: nextUpdateDate)
//// let nextUpdateDate = UserDefaults.standard.object(forKey: "settings.updater.nextUpdateDate") as? Date
//
// // If there's no stored next update date or it's in the past, update immediately
// if !isSameDay(date1: nextUpdateDateLocal, date2: now) {
// // Next update date is in the future, no need to update
// printOS("Updater: next update date is in the future, skipping")
// return
// }
//
// // Update immediately and set next update date
// updateApp(appState: appState)
// setNextUpdateDate(interval: updateSeconds)
//}
//
//func updateApp(appState: AppState) {
// // Perform your update logic here
// printOS("Updater: performing update")
// loadGithubReleases(appState: appState)
//}
//
//func setNextUpdateDate(interval: TimeInterval) {
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(interval))
// UserDefaults.standard.set(newUpdateDate.timeIntervalSinceReferenceDate, forKey: "settings.updater.nextUpdateDate")
//// UserDefaults.standard.set(newUpdateDate, forKey: "settings.updater.nextUpdateDate")
//}
//
//func isSameDay(date1: Date, date2: Date) -> Bool {
// return Calendar.current.isDate(date1, inSameDayAs: date2)
//}
// --- Updater Badge View // --- Updater Badge View
+25
View File
@@ -427,6 +427,31 @@ extension String {
} }
} }
// --- Returns comma separated string as array of strings
extension String {
func toConditionFormat() -> [String] {
if self.isEmpty {
return []
}
return self.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
}
}
// --- Overload the greater than operator ">" to do a semantic check on the string versions
extension String {
func versionStringToTuple() -> (Int, Int, Int) {
let components = self.split(separator: ".").compactMap { Int($0) }
return (components[0], components[1], components[2])
}
static func > (lhs: String, rhs: String) -> Bool {
let lhsVersion = lhs.versionStringToTuple()
let rhsVersion = rhs.versionStringToTuple()
return lhsVersion > rhsVersion
}
}
// --- Trash Relationship --- // --- Trash Relationship ---
//extension FileManager { //extension FileManager {
+2 -2
View File
@@ -27,7 +27,7 @@ struct PearcleanerApp: App {
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "trash" @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
var body: some Scene { var body: some Scene {
@@ -127,7 +127,7 @@ struct PearcleanerApp: App {
// Get new features // Get new features
getFeatures(appState: appState, features: $features) getFeatures(appState: appState, features: $features)
// Load extra conditions from GitHub // Load extra conditions from GitHub
loadConditionsFromGitHub() // loadConditionsFromGitHub() // REPLACE THIS WITH NEW CONDITION MANAGER
} }
} }
+1 -1
View File
@@ -238,7 +238,7 @@ struct SimpleSearchStyle: TextFieldStyle {
extension NSTextView { extension NSTextView {
open override var frame: CGRect { open override var frame: CGRect {
didSet { didSet {
insertionPointColor = .clear insertionPointColor = NSColor(Color("mode").opacity(0.2))//.clear
} }
} }
} }
@@ -0,0 +1,154 @@
//
// ConditionBuilderView.swift
// Pearcleaner
//
// Created by Alin Lupascu on 6/14/24.
//
import Foundation
import SwiftUI
struct ConditionBuilderView: View {
@Binding var showAlert:Bool
@State private var include = ""
@State private var exclude = ""
@State private var paths = ""
@State private var pathsEx = ""
@State var bundle: String
@State private var conditionExists = false
var body: some View {
VStack(spacing: 10) {
HStack {
Spacer()
Text("Condition Builder")
.font(.headline)
Spacer()
Button("Close") {
showAlert = false
}
.buttonStyle(SimpleButtonStyle(icon: "x.circle", iconFlip: "x.circle.fill", help: "Close"))
}
Divider()
Spacer()
InfoButton(text: "Some files/folders are not similar to the app name or bundle id, causing Pearcleaner to either not find them or find unrelated files. \nTo combat this, you may create a custom condition for each application bundle using keywords or direct paths. \n\n- Can add file/folder keywords that you want to either include or exclude in fuzzy searches. \n\n- Can explicitly add or remove a full Finder path to search results if you want a direct search.", label: "Instructions", edge: .bottom)
Spacer()
VStack {
HStack {
Text("Include Keywords:").font(.callout)
Spacer()
}
HStack {
TextField("keyword-1, keyword-2", text: $include)
.textFieldStyle(RoundedTextFieldStyle())
Button("Clear") {
include = ""
}
.buttonStyle(SimpleButtonStyle(icon: "xmark.circle.fill", help: "Clear", size: 15))
}
HStack {
Text("Exclude Keywords:").font(.callout)
Spacer()
}
HStack {
TextField("keyword-1, keyword-2", text: $exclude)
.textFieldStyle(RoundedTextFieldStyle())
Button("Clear") {
exclude = ""
}
.buttonStyle(SimpleButtonStyle(icon: "xmark.circle.fill", help: "Clear", size: 15))
}
HStack {
Text("Add Direct Paths:").font(.callout)
Spacer()
}
HStack {
TextField("/Full/Path/example-1.txt, /Full/Path/example-2.txt", text: $paths)
.textFieldStyle(RoundedTextFieldStyle())
Button("Clear") {
paths = ""
}
.buttonStyle(SimpleButtonStyle(icon: "xmark.circle.fill", help: "Clear", size: 15))
}
HStack {
Text("Remove Direct Paths:").font(.callout)
Spacer()
}
HStack {
TextField("/Full/Path/example-1.txt, /Full/Path/example-2.txt", text: $pathsEx)
.textFieldStyle(RoundedTextFieldStyle())
Button("Clear") {
pathsEx = ""
}
.buttonStyle(SimpleButtonStyle(icon: "xmark.circle.fill", help: "Clear", size: 15))
}
}
.padding(.horizontal)
Spacer()
HStack {
Spacer()
Button("Add/Save") {
showAlert = false
let newCondition = Condition(bundle_id: bundle, include: include.toConditionFormat(), exclude: exclude.toConditionFormat(), includeForce: paths.toConditionFormat(), excludeForce: pathsEx.toConditionFormat())
ConditionManager.shared.saveCondition(newCondition)
}
.buttonStyle(SimpleButtonStyle(icon: "plus.square.fill", label: conditionExists ? "Save" : "Add", help: "Save the condition for this application"))
Spacer()
Button("Remove") {
showAlert = false
include = ""
exclude = ""
paths = ""
pathsEx = ""
ConditionManager.shared.deleteCondition(bundle_id: bundle)
}
.buttonStyle(SimpleButtonStyle(icon: "minus.square.fill", label: "Remove", help: "Remove the condition from this application"))
.disabled(!conditionExists)
Spacer()
}
Spacer()
}
.padding(15)
.frame(width: 500, height: 500)
.background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow))
.onAppear {
loadCondition()
}
}
private func loadCondition() {
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
let key = "Condition-\(bundle.pearFormat())"
if let savedCondition = defaults.object(forKey: key) as? Data {
if let loadedCondition = try? decoder.decode(Condition.self, from: savedCondition) {
include = loadedCondition.include.joined(separator: ", ")
exclude = loadedCondition.exclude.joined(separator: ", ")
if let includeForce = loadedCondition.includeForce {
paths = includeForce.map { $0.absoluteString }.joined(separator: ", ")
}
if let excludeForce = loadedCondition.excludeForce {
pathsEx = excludeForce.map { $0.absoluteString }.joined(separator: ", ")
}
conditionExists = true
}
}
}
}
+12
View File
@@ -20,6 +20,7 @@ struct FilesView: View {
@AppStorage("settings.general.sizeType") var sizeType: String = "Real" @AppStorage("settings.general.sizeType") var sizeType: String = "Real"
@AppStorage("settings.general.filesWarning") private var warning: Bool = false @AppStorage("settings.general.filesWarning") private var warning: Bool = false
@State private var showAlert = false @State private var showAlert = false
@State private var showConditionBuilder = false
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@Binding var showPopover: Bool @Binding var showPopover: Bool
@Binding var search: String @Binding var search: String
@@ -277,6 +278,14 @@ struct FilesView: View {
.padding(.top) .padding(.top)
Button("Builder") {
showConditionBuilder = true
}
.buttonStyle(SimpleButtonStyle(icon: "hammer.fill", help: "Condition Builder", size: 14))
.padding(.top)
.padding(.trailing, 5)
Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") { Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") {
Task { Task {
@@ -389,6 +398,9 @@ struct FilesView: View {
.frame(width: 400, height: 200) .frame(width: 400, height: 200)
.background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow)) .background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow))
}) })
.sheet(isPresented: $showConditionBuilder, content: {
ConditionBuilderView(showAlert: $showConditionBuilder, bundle: appState.appInfo.bundleIdentifier)
})
.onAppear { .onAppear {
if !warning { if !warning {
showAlert = true showAlert = true
-8
View File
@@ -1,8 +0,0 @@
[
{
"bundle_id": "orgmozillanightly",
"include": ["mozilla", "firefox"],
"exclude": [],
"includeForce": []
}
]