import BackgroundTasks import Foundation import WidgetKit enum SmartDataRefreshScheduler { static let taskIdentifier = "com.apt.fuelboard.smart-refresh" static func register() { BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { task in guard let task = task as? BGAppRefreshTask else { task.setTaskCompleted(success: false) return } handle(task) } } static func scheduleNextIfNeeded() { BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: taskIdentifier) guard FuelStore.loadDataRefreshMode() == .smart else { return } let request = BGAppRefreshTaskRequest(identifier: taskIdentifier) request.earliestBeginDate = Date(timeIntervalSinceNow: FuelStore.smartProbeInterval) do { try BGTaskScheduler.shared.submit(request) } catch { #if DEBUG print("SMART-REFRESH schedule failed: \(error)") #endif } } private static func handle(_ task: BGAppRefreshTask) { scheduleNextIfNeeded() let refreshTask = Task { let success = await SmartDataRefreshCoordinator.runBackgroundProbe() task.setTaskCompleted(success: success) } task.expirationHandler = { refreshTask.cancel() } } } enum SmartDataRefreshCoordinator { @discardableResult static func runBackgroundProbe(now: Date = Date()) async -> Bool { guard FuelStore.loadDataRefreshMode() == .smart else { return true } guard FuelStore.isSmartProbeDue(now: now) || FuelStore.loadStations().isEmpty else { return true } FuelStore.saveLastSmartProbe(now) guard !Task.isCancelled else { return false } guard let latest = await FuelHistoryStore.fetchLatest() else { return false } let latestDay = latest.availableTo ?? latest.date let latestUpdated = latest.dataUpdated let cachedDay = MirrorFuelProvider.loadDumpCache()?.day let savedUpdated = FuelStore.loadDataUpdated() let shouldFetchFullDump = FuelStore.loadStations().isEmpty || !FuelStore.isCacheFresh(now: now) || (latestDay != nil && latestDay != cachedDay) || (latestUpdated != nil && latestUpdated != savedUpdated) guard shouldFetchFullDump, !Task.isCancelled else { return true } do { let fetched = try await FuelPriceProvider.active.fetchStations( near: nil, lng: nil, fuel: FuelStore.loadSelectedFuel(), radiusKM: nil ) guard !Task.isCancelled else { return false } FuelStore.saveStations(fetched) FuelStore.saveLastRefresh(now) if let meta = LiveChainProvider.latestMeta { FuelStore.saveRelayMeta(meta) } WidgetCenter.shared.reloadAllTimelines() return true } catch { return false } } }