From ffc50a6b927afd16601e0bc01a53d76ae6069ba3 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Wed, 03 Jun 2026 21:22:41 +0800
Subject: [PATCH] Refresh app index for quick search
---
Apptag/ContentView.swift | 17 +++++++-
Apptag/DataLayer.swift | 40 ++++++++++++++++++--
Apptag/AppLibraryController.swift | 4 +-
3 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/Apptag/AppLibraryController.swift b/Apptag/AppLibraryController.swift
index 090a31b..aa2d4db 100644
--- a/Apptag/AppLibraryController.swift
+++ b/Apptag/AppLibraryController.swift
@@ -23,8 +23,8 @@
}
enum AppLibraryController {
- static func refresh() -> AppLibraryRefreshResult {
- let scannedApps = AppIndexer.scan()
+ static func refresh(useCache: Bool = true) -> AppLibraryRefreshResult {
+ let scannedApps = AppIndexer.scan(useCache: useCache)
let reconciledStore = TagEditor.reconcileScannedApps(scannedApps)
let smartStartResult = SmartStartService.runIfNeeded(
apps: scannedApps,
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index f29d986..0fbfc7f 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -731,6 +731,7 @@
quickSearchErrorMessage = nil
quickSearchFocusToken &+= 1
refreshQuickSearchResults()
+ refreshApps()
NotificationCenter.default.post(
name: .tagLauncherQuickSearchVisibilityChanged,
object: nil,
@@ -777,7 +778,10 @@
private func refreshQuickSearchResults() {
let previousSelection = quickSearchSelectedID
- quickSearchResults = QuickSearchEngine.search(quickSearchQuery, documents: quickSearchDocuments)
+ quickSearchResults = QuickSearchEngine.search(
+ quickSearchQuery,
+ documents: quickSearchDocuments.filter { FileManager.default.fileExists(atPath: $0.app.path.path) }
+ )
if quickSearchResults.isEmpty {
quickSearchSelectedID = nil
@@ -2369,7 +2373,7 @@
}
}
- func refreshApps(forceLayoutRefresh: Bool = false) {
+ func refreshApps(forceLayoutRefresh: Bool = false, useCache: Bool = true) {
guard !refreshInProgress else {
if forceLayoutRefresh {
refreshAgainAfterCurrent = true
@@ -2380,7 +2384,7 @@
refreshInProgress = true
DispatchQueue.global(qos: .userInitiated).async {
- let result = AppLibraryController.refresh()
+ let result = AppLibraryController.refresh(useCache: useCache)
DispatchQueue.main.async {
applyAppLibrarySnapshot(result.snapshot)
handleSmartStartRunResult(result.smartStartResult)
@@ -2417,6 +2421,13 @@
) {
appGridInteraction.appDragModeActive = false
endTagNavReorder()
+ guard FileManager.default.fileExists(atPath: app.path.path) else {
+ AppIndexer.invalidateScanCache()
+ refreshApps()
+ onFailure?()
+ return
+ }
+
let closeQuickSearchAndOverlayBeforeOpening = closeQuickSearchOnSuccess
&& closeOverlayOnSuccess
&& (quickSearchCloseHidesOverlay || quickSearchOnlySession)
diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index 9f9b9f0..91fed17 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -94,9 +94,16 @@
// 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] = [
@@ -120,12 +127,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,27 +141,51 @@
scanCacheLock.lock()
cachedScanApps = nil
cachedScanAt = nil
+ cachedSearchPathSignature = nil
scanCacheLock.unlock()
}
- private static func cachedScanIfFresh() -> [AppInfo]? {
+ 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] {
var seenResolvedPaths = Set<String>()
var apps: [AppInfo] = []
--
Gitblit v1.9.3