91 lines
3.4 KiB
Swift
91 lines
3.4 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,
|
|
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 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)
|
|
}
|
|
}
|