Ariver
2026-06-08 4f5d7c13209555a114761f5027299f3ba1b42217
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(