Includes provider integrations, settings, NSStatusItem UI, tests, Makefile, and README with screenshot. Co-authored-by: Cursor <cursoragent@cursor.com>
56 lines
1.9 KiB
Swift
56 lines
1.9 KiB
Swift
import Foundation
|
|
|
|
struct UsageCacheEntry: Codable, Sendable {
|
|
let fetchedAt: Date
|
|
let payload: Data
|
|
}
|
|
|
|
actor UsageCache {
|
|
static let shared = UsageCache()
|
|
|
|
private let directory: URL
|
|
private let fileManager = FileManager.default
|
|
|
|
init() {
|
|
let base = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
directory = base.appendingPathComponent("ai-usage", isDirectory: true)
|
|
try? fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
|
|
}
|
|
|
|
func load(provider: ProviderID, maxAge: TimeInterval) -> UsageCacheEntry? {
|
|
let url = cacheURL(for: provider)
|
|
guard fileManager.fileExists(atPath: url.path),
|
|
let data = try? Data(contentsOf: url),
|
|
let entry = try? JSONDecoder().decode(UsageCacheEntry.self, from: data)
|
|
else {
|
|
return nil
|
|
}
|
|
if Date().timeIntervalSince(entry.fetchedAt) > maxAge {
|
|
return nil
|
|
}
|
|
return entry
|
|
}
|
|
|
|
func loadStale(provider: ProviderID) -> UsageCacheEntry? {
|
|
let url = cacheURL(for: provider)
|
|
guard fileManager.fileExists(atPath: url.path),
|
|
let data = try? Data(contentsOf: url),
|
|
let entry = try? JSONDecoder().decode(UsageCacheEntry.self, from: data)
|
|
else {
|
|
return nil
|
|
}
|
|
return entry
|
|
}
|
|
|
|
func save(provider: ProviderID, payload: Data) {
|
|
let entry = UsageCacheEntry(fetchedAt: Date(), payload: payload)
|
|
guard let data = try? JSONEncoder().encode(entry) else { return }
|
|
let url = cacheURL(for: provider)
|
|
try? data.write(to: url, options: .atomic)
|
|
try? fileManager.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
|
|
}
|
|
|
|
private func cacheURL(for provider: ProviderID) -> URL {
|
|
directory.appendingPathComponent("\(provider.rawValue)-usage.json")
|
|
}
|
|
}
|