Files
wehub-resource-sync 869b84f27c
Build / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:29:30 +08:00

167 lines
6.6 KiB
Swift

//
// Locations.swift
// PureMac
//
// Defines all macOS filesystem locations where applications leave files.
// Used by the heuristic scan engine to discover app-related artifacts.
//
import Foundation
let home = FileManager.default.homeDirectoryForCurrentUser.path
class Locations: ObservableObject {
struct SearchCategory {
let name: String
var paths: [String]
}
// Standard macOS Library subdirectories.
// Used to determine if depth=2 matches should add the parent directory (vendor folders)
// or the matched item itself (standard system folders).
static let standardLibrarySubdirectories: Set<String> = [
"Application Scripts", "Application Support", "Caches",
"Containers", "Group Containers", "HTTPStorages",
"Internet Plug-Ins", "LaunchAgents", "LaunchDaemons",
"Logs", "Preferences", "PreferencePanes",
"PrivilegedHelperTools", "Saved Application State",
"Services", "WebKit", "Extensions", "Frameworks"
]
let cacheDir: String
let tempDir: String
var appSearch: SearchCategory
var reverseSearch: SearchCategory
init() {
let (cacheDir, tempDir) = Locations.darwinCT()
self.cacheDir = cacheDir
self.tempDir = tempDir
self.appSearch = SearchCategory(name: "Apps", paths: [
// User home - bare "\(home)" is intentionally NOT scanned.
// Scanning bare $HOME matches top-level dotfiles like .claude,
// .ssh, .aws, .kube by normalized app-name ("claude" matching
// ".claude") and invites data loss when uninstalling unrelated
// webapps. Scoped subdirs below are still scanned.
"\(home)/.config",
"\(home)/Documents",
"\(home)/Desktop",
"\(home)/Applications",
// User Library
"\(home)/Library",
"\(home)/Library/Application Scripts",
"\(home)/Library/Application Support",
"\(home)/Library/Application Support/CrashReporter",
"\(home)/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments",
"\(home)/Library/Containers",
"\(home)/Library/Caches",
"\(home)/Library/Caches/com.apple.helpd/Generated",
"\(home)/Library/Caches/com.crashlytics",
"\(home)/Library/Caches/com.google.SoftwareUpdate",
"\(home)/Library/Caches/com.google.Keystone",
"\(home)/Library/Caches/org.sparkle-project.Sparkle",
"\(home)/Library/Caches/com.segment.analytics",
"\(home)/Library/Caches/SentryCrash",
"\(home)/Library/Caches/Rollbar",
"\(home)/Library/Caches/Amplitude",
"\(home)/Library/Caches/Realm",
"\(home)/Library/Caches/Parse",
"\(home)/Library/Group Containers",
"\(home)/Library/HTTPStorages",
"\(home)/Library/Internet Plug-Ins",
"\(home)/Library/LaunchAgents",
"\(home)/Library/Logs",
"\(home)/Library/Logs/DiagnosticReports",
"\(home)/Library/Preferences",
"\(home)/Library/PreferencePanes",
"\(home)/Library/Preferences/ByHost",
"\(home)/Library/Saved Application State",
"\(home)/Library/Services",
"\(home)/Library/WebKit",
// System-wide
"/Applications",
"/Users/Shared",
"/Users/Shared/Library/Application Support",
"/Library",
"/Library/Application Support",
"/Library/Application Support/CrashReporter",
"/Library/Caches",
"/Library/Extensions",
"/Library/Internet Plug-Ins",
"/Library/LaunchAgents",
"/Library/LaunchDaemons",
"/Library/Logs",
"/Library/Logs/DiagnosticReports",
"/Library/Preferences",
"/Library/PrivilegedHelperTools",
"/private/var/db/receipts",
"/private/tmp",
"/usr/local/bin",
"/usr/local/etc",
"/usr/local/opt",
"/usr/local/sbin",
"/usr/local/share",
"/usr/local/var",
cacheDir,
tempDir
])
// Dynamically append Application Support subfolders for deeper search
let subfolders = Locations.listAppSupportDirectories()
for folder in subfolders {
self.appSearch.paths.append("\(home)/Library/Application Support/\(folder)")
}
self.reverseSearch = SearchCategory(name: "Reverse", paths: [
"\(home)/Library/Application Scripts",
"\(home)/Library/Application Support",
"\(home)/Library/Application Support/Caches",
"\(home)/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments",
"\(home)/Library/Containers",
"\(home)/Library/Caches",
"\(home)/Library/HTTPStorages",
"\(home)/Library/Internet Plug-Ins",
"\(home)/Library/LaunchAgents",
"\(home)/Library/Logs",
"\(home)/Library/Preferences",
"\(home)/Library/PreferencePanes",
"\(home)/Library/Preferences/ByHost",
"\(home)/Library/Saved Application State",
"\(home)/Library/WebKit",
"/Users/Shared/Library/Application Support",
"/Library/Application Support",
"/Library/Application Support/CrashReporter",
"/Library/Internet Plug-Ins",
"/Library/LaunchAgents",
"/Library/LaunchDaemons",
"/Library/PrivilegedHelperTools",
])
}
static func darwinCT() -> (String, String) {
var cacheDir = ""
var tempDir = ""
if let cfCacheDir = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first {
cacheDir = cfCacheDir
}
if let cfTempDir = ProcessInfo.processInfo.environment["TMPDIR"] {
tempDir = cfTempDir
}
return (cacheDir, tempDir)
}
static func listAppSupportDirectories() -> [String] {
let appSupportPath = "\(home)/Library/Application Support"
guard let contents = try? FileManager.default.contentsOfDirectory(atPath: appSupportPath) else {
return []
}
return contents.filter { item in
var isDir: ObjCBool = false
let fullPath = (appSupportPath as NSString).appendingPathComponent(item)
return FileManager.default.fileExists(atPath: fullPath, isDirectory: &isDir) && isDir.boolValue
}
}
}