183 lines · 6.8 KB · Swift
RawDownload
import Foundation public struct WidgetSnapshot: Codable, Sendable {    public struct ProviderEntry: Codable, Sendable {        public let provider: UsageProvider        public let updatedAt: Date        public let primary: RateWindow?        public let secondary: RateWindow?        public let tertiary: RateWindow?        public let creditsRemaining: Double?        public let codeReviewRemainingPercent: Double?        public let tokenUsage: TokenUsageSummary?        public let dailyUsage: [DailyUsagePoint]         public init(            provider: UsageProvider,            updatedAt: Date,            primary: RateWindow?,            secondary: RateWindow?,            tertiary: RateWindow?,            creditsRemaining: Double?,            codeReviewRemainingPercent: Double?,            tokenUsage: TokenUsageSummary?,            dailyUsage: [DailyUsagePoint])        {            self.provider = provider            self.updatedAt = updatedAt            self.primary = primary            self.secondary = secondary            self.tertiary = tertiary            self.creditsRemaining = creditsRemaining            self.codeReviewRemainingPercent = codeReviewRemainingPercent            self.tokenUsage = tokenUsage            self.dailyUsage = dailyUsage        }    }     public struct TokenUsageSummary: Codable, Sendable {        public let sessionCostUSD: Double?        public let sessionTokens: Int?        public let last30DaysCostUSD: Double?        public let last30DaysTokens: Int?         public init(            sessionCostUSD: Double?,            sessionTokens: Int?,            last30DaysCostUSD: Double?,            last30DaysTokens: Int?)        {            self.sessionCostUSD = sessionCostUSD            self.sessionTokens = sessionTokens            self.last30DaysCostUSD = last30DaysCostUSD            self.last30DaysTokens = last30DaysTokens        }    }     public struct DailyUsagePoint: Codable, Sendable {        public let dayKey: String        public let totalTokens: Int?        public let costUSD: Double?         public init(dayKey: String, totalTokens: Int?, costUSD: Double?) {            self.dayKey = dayKey            self.totalTokens = totalTokens            self.costUSD = costUSD        }    }     public let entries: [ProviderEntry]    public let enabledProviders: [UsageProvider]    public let generatedAt: Date     public init(entries: [ProviderEntry], enabledProviders: [UsageProvider]? = nil, generatedAt: Date) {        self.entries = entries        self.enabledProviders = enabledProviders ?? entries.map(\.provider)        self.generatedAt = generatedAt    }     private enum CodingKeys: String, CodingKey {        case entries        case enabledProviders        case generatedAt    }     public init(from decoder: Decoder) throws {        let container = try decoder.container(keyedBy: CodingKeys.self)        self.entries = try container.decode([ProviderEntry].self, forKey: .entries)        self.generatedAt = try container.decode(Date.self, forKey: .generatedAt)        self.enabledProviders = try container.decodeIfPresent([UsageProvider].self, forKey: .enabledProviders)            ?? self.entries.map(\.provider)    }     public func encode(to encoder: Encoder) throws {        var container = encoder.container(keyedBy: CodingKeys.self)        try container.encode(self.entries, forKey: .entries)        try container.encode(self.enabledProviders, forKey: .enabledProviders)        try container.encode(self.generatedAt, forKey: .generatedAt)    }} public enum WidgetSnapshotStore {    public static let appGroupID = "group.com.steipete.codexbar"    private static let filename = "widget-snapshot.json"     public static func load(bundleID: String? = Bundle.main.bundleIdentifier) -> WidgetSnapshot? {        guard let url = self.snapshotURL(bundleID: bundleID) else { return nil }        guard let data = try? Data(contentsOf: url) else { return nil }        return try? self.decoder.decode(WidgetSnapshot.self, from: data)    }     public static func save(_ snapshot: WidgetSnapshot, bundleID: String? = Bundle.main.bundleIdentifier) {        guard let url = self.snapshotURL(bundleID: bundleID) else { return }        do {            let data = try self.encoder.encode(snapshot)            try data.write(to: url, options: [.atomic])        } catch {            return        }    }     private static func snapshotURL(bundleID: String?) -> URL? {        let fm = FileManager.default        let groupID = self.groupID(for: bundleID)        #if os(macOS)        if let groupID, let container = fm.containerURL(forSecurityApplicationGroupIdentifier: groupID) {            return container.appendingPathComponent(self.filename, isDirectory: false)        }        #endif         let base = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first            ?? fm.temporaryDirectory        let dir = base.appendingPathComponent("CodexBar", isDirectory: true)        try? fm.createDirectory(at: dir, withIntermediateDirectories: true)        return dir.appendingPathComponent(self.filename, isDirectory: false)    }     public static func appGroupID(for bundleID: String?) -> String? {        self.groupID(for: bundleID)    }     private static func groupID(for bundleID: String?) -> String? {        guard let bundleID, !bundleID.isEmpty else { return self.appGroupID }        if bundleID.contains(".debug") {            return "group.com.steipete.codexbar.debug"        }        return self.appGroupID    }     private static var encoder: JSONEncoder {        let encoder = JSONEncoder()        encoder.dateEncodingStrategy = .iso8601        return encoder    }     private static var decoder: JSONDecoder {        let decoder = JSONDecoder()        decoder.dateDecodingStrategy = .iso8601        return decoder    }} public enum WidgetSelectionStore {    private static let selectedProviderKey = "widgetSelectedProvider"     public static func loadSelectedProvider(bundleID: String? = Bundle.main.bundleIdentifier) -> UsageProvider? {        guard let defaults = self.sharedDefaults(bundleID: bundleID) else { return nil }        guard let raw = defaults.string(forKey: self.selectedProviderKey) else { return nil }        return UsageProvider(rawValue: raw)    }     public static func saveSelectedProvider(        _ provider: UsageProvider,        bundleID: String? = Bundle.main.bundleIdentifier)    {        guard let defaults = self.sharedDefaults(bundleID: bundleID) else { return }        defaults.set(provider.rawValue, forKey: self.selectedProviderKey)    }     private static func sharedDefaults(bundleID: String?) -> UserDefaults? {        guard let groupID = WidgetSnapshotStore.appGroupID(for: bundleID) else { return nil }        return UserDefaults(suiteName: groupID)    }}