From 7c8c804dacd1747101c0949a3ca26c94c690af79 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Wed, 20 May 2026 02:35:59 +0800
Subject: [PATCH] Release TagLauncher 7.5.1
---
Apptag/DataLayer.swift | 63 ++++++++++++++++++++++++++++++-
1 files changed, 60 insertions(+), 3 deletions(-)
diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index 6b0f69a..59371d9 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -51,6 +51,10 @@
// MARK: - App Scanner
enum AppIndexer {
+ private static let scanCacheLock = NSLock()
+ private static var cachedScanApps: [AppInfo]? = nil
+ private static var cachedScanAt: Date? = nil
+ private static let scanCacheTTL: TimeInterval = 1800
static let searchPaths: [URL] = [
URL(fileURLWithPath: "/Applications"),
@@ -72,7 +76,42 @@
}
/// Scan all standard locations. Tags are annotated from TagDatabase by the caller.
- static func scan() -> [AppInfo] {
+ static func scan(useCache: Bool = true) -> [AppInfo] {
+ if useCache, let cached = cachedScanIfFresh() {
+ return cached
+ }
+
+ let apps = performScan()
+ updateScanCache(apps)
+ return apps
+ }
+
+ static func invalidateScanCache() {
+ scanCacheLock.lock()
+ cachedScanApps = nil
+ cachedScanAt = nil
+ scanCacheLock.unlock()
+ }
+
+ private static func cachedScanIfFresh() -> [AppInfo]? {
+ scanCacheLock.lock()
+ defer { scanCacheLock.unlock() }
+
+ guard let cachedScanApps,
+ let cachedScanAt,
+ Date().timeIntervalSince(cachedScanAt) < scanCacheTTL
+ else { return nil }
+ return cachedScanApps
+ }
+
+ private static func updateScanCache(_ apps: [AppInfo]) {
+ scanCacheLock.lock()
+ cachedScanApps = apps
+ cachedScanAt = Date()
+ scanCacheLock.unlock()
+ }
+
+ private static func performScan() -> [AppInfo] {
var seenResolvedPaths = Set<String>()
var apps: [AppInfo] = []
@@ -300,6 +339,7 @@
var uncommonAppPaths: [String] = [] // special marker; does not affect normal groups
var uncommonSources: [String: UncommonSource] = [:] // current uncommon source: auto/manual
var appOpenCounts: [String: Int] = [:] // launches opened from TagLauncher
+ var appLastOpenedAt: [String: Date] = [:] // successful launches opened from TagLauncher
var knownAppPaths: [String] = [] // baseline set to detect newly installed apps
var appNotes: [String: String] = [:] // path → user note; retained even if marker is removed
var disabledSystemCategoryIDs: [SmartCategoryID] = [] // system categories the user deleted
@@ -314,6 +354,7 @@
case uncommonAppPaths
case uncommonSources
case appOpenCounts
+ case appLastOpenedAt
case knownAppPaths
case appNotes
case disabledSystemCategoryIDs
@@ -332,6 +373,7 @@
uncommonAppPaths = try container.decodeIfPresent([String].self, forKey: .uncommonAppPaths) ?? []
uncommonSources = try container.decodeIfPresent([String: UncommonSource].self, forKey: .uncommonSources) ?? [:]
appOpenCounts = try container.decodeIfPresent([String: Int].self, forKey: .appOpenCounts) ?? [:]
+ appLastOpenedAt = try container.decodeIfPresent([String: Date].self, forKey: .appLastOpenedAt) ?? [:]
knownAppPaths = try container.decodeIfPresent([String].self, forKey: .knownAppPaths) ?? []
appNotes = try container.decodeIfPresent([String: String].self, forKey: .appNotes) ?? [:]
disabledSystemCategoryIDs = try container.decodeIfPresent(
@@ -1142,7 +1184,21 @@
}
let newPaths = scannedPaths.subtracting(knownPaths)
- guard !newPaths.isEmpty else { return store }
+ let removedPaths = knownPaths.subtracting(scannedPaths)
+ if !removedPaths.isEmpty {
+ for path in removedPaths {
+ store.appOpenCounts.removeValue(forKey: path)
+ store.appLastOpenedAt.removeValue(forKey: path)
+ }
+ store.knownAppPaths = knownPaths.subtracting(removedPaths).sorted()
+ }
+
+ guard !newPaths.isEmpty else {
+ if !removedPaths.isEmpty {
+ TagDatabase.save(store)
+ }
+ return store
+ }
var uncommonPaths = Set(store.uncommonAppPaths)
let appsByPath = Dictionary(uniqueKeysWithValues: apps.map { ($0.path.path, $0) })
@@ -1162,7 +1218,7 @@
let newApps = apps.filter { newPaths.contains($0.path.path) }
_ = seedDefaultAppleAppNotes(for: newApps, in: &store)
store.uncommonAppPaths = uncommonPaths.sorted()
- store.knownAppPaths = knownPaths.union(newPaths).sorted()
+ store.knownAppPaths = Set(store.knownAppPaths).union(newPaths).sorted()
TagDatabase.save(store)
return store
}
@@ -1214,6 +1270,7 @@
static func recordLauncherOpen(for path: String) {
var store = TagDatabase.load()
store.appOpenCounts[path] = (store.appOpenCounts[path] ?? 0) + 1
+ store.appLastOpenedAt[path] = Date()
if store.uncommonSources[path] == .auto,
store.appOpenCounts[path, default: 0] >= TagDatabase.autoUncommonOpenThreshold {
--
Gitblit v1.9.3