From 4f5d7c13209555a114761f5027299f3ba1b42217 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Mon, 08 Jun 2026 13:27:09 +0800
Subject: [PATCH] Release 7.8.22 Apple default app catalog
---
Apptag/DataLayer.swift | 113 +++++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 99 insertions(+), 14 deletions(-)
diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index 9f9b9f0..17a0605 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -28,6 +28,9 @@
return localizedName
}
}
+ if let localizedName = localizedNames.first, !localizedName.isEmpty {
+ return localizedName
+ }
return name
}
@@ -94,14 +97,22 @@
// MARK: - App Scanner
enum AppIndexer {
+ private struct SearchPathSignature: Equatable {
+ let path: String
+ let exists: Bool
+ let contentModificationTime: TimeInterval?
+ }
+
private static let scanCacheLock = NSLock()
private static var cachedScanApps: [AppInfo]? = nil
private static var cachedScanAt: Date? = nil
+ private static var cachedSearchPathSignature: [SearchPathSignature]? = nil
private static let scanCacheTTL: TimeInterval = 1800
static let searchPaths: [URL] = [
URL(fileURLWithPath: "/Applications"),
URL(fileURLWithPath: "/System/Applications"),
+ URL(fileURLWithPath: "/System/Library/CoreServices/Applications"),
URL(fileURLWithPath: "/System/Cryptexes/App/System/Applications"),
URL(fileURLWithPath: "/System/Volumes/Preboot/Cryptexes/App/System/Applications"),
FileManager.default.homeDirectoryForCurrentUser
@@ -110,6 +121,7 @@
private static let systemAppPathPrefixes = [
"/System/Applications/",
+ "/System/Library/CoreServices/Applications/",
"/System/Cryptexes/App/System/Applications/",
"/System/Volumes/Preboot/Cryptexes/App/System/Applications/"
]
@@ -120,12 +132,13 @@
/// Scan all standard locations. Tags are annotated from TagDatabase by the caller.
static func scan(useCache: Bool = true) -> [AppInfo] {
- if useCache, let cached = cachedScanIfFresh() {
+ let searchPathSignature = currentSearchPathSignature()
+ if useCache, let cached = cachedScanIfFresh(matching: searchPathSignature) {
return cached
}
let apps = performScan()
- updateScanCache(apps)
+ updateScanCache(apps, searchPathSignature: searchPathSignature)
return apps
}
@@ -133,25 +146,59 @@
scanCacheLock.lock()
cachedScanApps = nil
cachedScanAt = nil
+ cachedSearchPathSignature = nil
scanCacheLock.unlock()
}
- private static func cachedScanIfFresh() -> [AppInfo]? {
+ static func shouldRefreshForSearchPathChanges() -> Bool {
+ scanCacheLock.lock()
+ let hasCachedApps = cachedScanApps != nil
+ let cachedSignature = cachedSearchPathSignature
+ scanCacheLock.unlock()
+
+ guard hasCachedApps else { return true }
+ return cachedSignature != currentSearchPathSignature()
+ }
+
+ private static func cachedScanIfFresh(matching searchPathSignature: [SearchPathSignature]) -> [AppInfo]? {
scanCacheLock.lock()
defer { scanCacheLock.unlock() }
guard let cachedScanApps,
let cachedScanAt,
+ cachedSearchPathSignature == searchPathSignature,
Date().timeIntervalSince(cachedScanAt) < scanCacheTTL
else { return nil }
return cachedScanApps
}
- private static func updateScanCache(_ apps: [AppInfo]) {
+ private static func updateScanCache(_ apps: [AppInfo], searchPathSignature: [SearchPathSignature]) {
scanCacheLock.lock()
cachedScanApps = apps
cachedScanAt = Date()
+ cachedSearchPathSignature = searchPathSignature
scanCacheLock.unlock()
+ }
+
+ private static func currentSearchPathSignature() -> [SearchPathSignature] {
+ searchPaths.map { url in
+ var isDirectory: ObjCBool = false
+ let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)
+ guard exists, isDirectory.boolValue else {
+ return SearchPathSignature(
+ path: url.path,
+ exists: false,
+ contentModificationTime: nil
+ )
+ }
+
+ let values = try? url.resourceValues(forKeys: [.contentModificationDateKey])
+ return SearchPathSignature(
+ path: url.path,
+ exists: true,
+ contentModificationTime: values?.contentModificationDate?.timeIntervalSinceReferenceDate
+ )
+ }
}
private static func performScan() -> [AppInfo] {
@@ -222,10 +269,19 @@
let name = displayURL.deletingPathExtension().lastPathComponent
let icon = NSWorkspace.shared.icon(forFile: displayURL.path)
icon.size = NSSize(width: 96, height: 96)
- let localizedNamesByLanguage = localizedAppNameMap(
+ var localizedNamesByLanguage = localizedAppNameMap(
bundle: bundle,
fallbackName: name
)
+ if let bundleId {
+ for (languageCode, displayName) in AppleDefaultAppCatalog.localizedNamesByLanguage(
+ forBundleIdentifier: bundleId
+ ) {
+ if localizedNamesByLanguage[languageCode]?.isEmpty != false {
+ localizedNamesByLanguage[languageCode] = displayName
+ }
+ }
+ }
let localizedNames = localizedAppNames(
for: displayURL,
fallbackName: name,
@@ -274,6 +330,7 @@
localizedNamesByLanguage: [String: String]
) -> [String] {
var values = L10n.supported.map { localizedNamesByLanguage[$0.code] }
+ values.append(resourceLocalizedName(for: appURL))
values.append(spotlightDisplayName(for: appURL))
values.append(FileManager.default.displayName(atPath: appURL.path).replacingOccurrences(of: ".app", with: ""))
@@ -397,6 +454,15 @@
return trimmed.isEmpty ? nil : trimmed
}
+ private static func resourceLocalizedName(for appURL: URL) -> String? {
+ guard let value = try? appURL.resourceValues(forKeys: [.localizedNameKey]).localizedName else {
+ return nil
+ }
+
+ let trimmed = value.replacingOccurrences(of: ".app", with: "").trimmingCharacters(in: .whitespacesAndNewlines)
+ return trimmed.isEmpty ? nil : trimmed
+ }
+
private static func uniqueLocalizedNames(_ values: [String?], excluding excludedValue: String) -> [String] {
let normalizedExcluded = normalizedLocalizedName(excludedValue)
var seen = Set<String>()
@@ -448,7 +514,7 @@
private static func deduplicationIdentity(for app: AppInfo) -> String {
if let bundleIdentifier = app.bundleIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines),
!bundleIdentifier.isEmpty {
- return "bundle:\(bundleIdentifier.lowercased())"
+ return "bundle:\(bundleIdentifier.lowercased())|name:\(normalizedAppName(app.name))"
}
return "name:\(normalizedAppName(app.name))"
}
@@ -567,6 +633,7 @@
struct AppNoteMetadata: Codable, Equatable {
var origin: AppNoteOrigin
var catalog: SmartDefaultNoteProvenance? = nil
+ var apple: SmartDefaultNoteProvenance? = nil
var noteFingerprint: String
}
@@ -1424,7 +1491,7 @@
for path in newPaths {
if let app = appsByPath[path],
app.isAppleApp,
- AppleDefaultAppNotes.isFamiliarAppleApp(app) {
+ AppleDefaultAppCatalog.isFamiliarAppleApp(app) {
continue
}
uncommonPaths.insert(path)
@@ -1450,16 +1517,31 @@
for app in apps where app.isAppleApp {
let path = app.path.path
- if store.appNotes[path]?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
+
+ let currentNote = store.appNotes[path]?.trimmingCharacters(in: .whitespacesAndNewlines)
+ let existingMetadata = store.appNoteMetadata[path]
+ if existingMetadata?.origin == .manual {
continue
}
- guard let note = AppleDefaultAppNotes.note(for: app) else { continue }
- let limited = String(note.prefix(TagDatabase.maxAppNoteLength))
- store.appNotes[path] = limited
+ if let currentNote, !currentNote.isEmpty {
+ let currentFingerprint = TagDatabase.noteFingerprint(currentNote)
+ let currentMatchesDefault = existingMetadata?.origin == .appleDefault
+ && currentFingerprint == existingMetadata?.noteFingerprint
+ guard currentMatchesDefault else {
+ continue
+ }
+ }
+
+ guard let defaultNote = AppleDefaultAppCatalog.defaultNote(for: app),
+ defaultNote.note != currentNote
+ else { continue }
+
+ store.appNotes[path] = defaultNote.note
store.appNoteMetadata[path] = TagDatabase.AppNoteMetadata(
origin: .appleDefault,
- noteFingerprint: TagDatabase.noteFingerprint(limited)
+ apple: defaultNote.provenance,
+ noteFingerprint: defaultNote.provenance.noteFingerprint
)
changed = true
}
@@ -1474,7 +1556,7 @@
var uncommonPaths = Set(store.uncommonAppPaths)
var changed = false
- for app in apps where app.isAppleApp && !AppleDefaultAppNotes.isFamiliarAppleApp(app) {
+ for app in apps where app.isAppleApp && !AppleDefaultAppCatalog.isFamiliarAppleApp(app) {
let path = app.path.path
if uncommonPaths.insert(path).inserted {
changed = true
@@ -1514,7 +1596,10 @@
let limited = String(trimmed.prefix(TagDatabase.maxAppNoteLength))
if limited.isEmpty {
store.appNotes.removeValue(forKey: path)
- store.appNoteMetadata.removeValue(forKey: path)
+ store.appNoteMetadata[path] = TagDatabase.AppNoteMetadata(
+ origin: .manual,
+ noteFingerprint: TagDatabase.noteFingerprint("")
+ )
} else {
store.appNotes[path] = limited
store.appNoteMetadata[path] = TagDatabase.AppNoteMetadata(
--
Gitblit v1.9.3