From e5627facd571293ebba8f05f970121716af39603 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 05 Jun 2026 01:30:07 +0800
Subject: [PATCH] Include CoreServices apps in quick search index

---
 Apptag/DataLayer.swift |   54 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index 9f9b9f0..b77bf4c 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -94,14 +94,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 +118,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 +129,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 +143,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] {
@@ -448,7 +492,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))"
     }

--
Gitblit v1.9.3