Files
conjure/Sources/ConjureKit/LaunchHistory.swift
T
2026-10-15 11:54:53 +02:00

93 lines
3.5 KiB
Swift

import Foundation
@MainActor
public final class LaunchHistory {
private struct Usage {
var count: Int
var lastLaunch: Date
}
private static let storageKey = "applicationLaunchHistory"
private static let favoritesStorageKey = "applicationFavorites"
private let defaults: UserDefaults
private let now: () -> Date
private var usage: [URL: Usage] = [:]
private var favorites = Set<URL>()
public init(defaults: UserDefaults = .standard, now: @escaping () -> Date = Date.init) {
self.defaults = defaults
self.now = now
for (identifier, value) in defaults.dictionary(forKey: Self.storageKey) ?? [:] {
guard let url = URL(string: identifier),
url.isFileURL || Application.commands.contains(where: { $0.id == url }),
let record = value as? [String: Any],
let count = record["count"] as? Int, count > 0,
let lastLaunch = record["lastLaunch"] as? Date,
lastLaunch.timeIntervalSinceReferenceDate.isFinite else { continue }
usage[url] = Usage(count: count, lastLaunch: lastLaunch)
}
for identifier in defaults.stringArray(forKey: Self.favoritesStorageKey) ?? [] {
guard let url = URL(string: identifier),
url.isFileURL || Application.commands.contains(where: { $0.id == url }) else { continue }
favorites.insert(url)
}
}
public func isFavorite(_ application: Application) -> Bool {
favorites.contains(application.id)
}
public func toggleFavorite(_ application: Application) {
if favorites.remove(application.id) == nil {
favorites.insert(application.id)
}
defaults.set(favorites.map(\.absoluteString), forKey: Self.favoritesStorageKey)
}
public func recordLaunch(of application: Application) {
let timestamp = now()
var record = usage[application.id] ?? Usage(count: 0, lastLaunch: timestamp)
if record.count < Int.max { record.count += 1 }
record.lastLaunch = timestamp
usage[application.id] = record
save()
}
public func resetRanking(of application: Application) {
usage.removeValue(forKey: application.id)
save()
}
private func save() {
var saved: [String: [String: Any]] = [:]
saved.reserveCapacity(usage.count)
for (url, record) in usage {
saved[url.absoluteString] = ["count": record.count, "lastLaunch": record.lastLaunch]
}
defaults.set(saved, forKey: Self.storageKey)
}
public func suggestions(in applications: [Application]) -> [Application] {
applications.sorted(by: preferred)
}
public func results(for query: String, in applications: [Application]) -> [Application] {
ApplicationSearch.results(for: query, in: applications, breakingTiesWith: preferred)
}
private func preferred(_ lhs: Application, _ rhs: Application) -> Bool {
let leftFavorite = isFavorite(lhs)
let rightFavorite = isFavorite(rhs)
if leftFavorite != rightFavorite { return leftFavorite }
let left = usage[lhs.id]
let right = usage[rhs.id]
let leftCount = left?.count ?? 0
let rightCount = right?.count ?? 0
if leftCount != rightCount { return leftCount > rightCount }
if let left, let right, left.lastLaunch != right.lastLaunch {
return left.lastLaunch > right.lastLaunch
}
return alphabetical(lhs, rhs)
}
}