import Foundation extension UsageStore { enum StartupBehavior { case automatic case full case testing var automaticallyStartsBackgroundWork: Bool { switch self { case .automatic, .full: true case .testing: false } } func resolved(isRunningTests: Bool) -> StartupBehavior { switch self { case .automatic: isRunningTests ? .testing : .full case .full, .testing: self } } } func recordStartupConnectivityRetryableFailure(_ error: Error) { guard self.startupConnectivityRetryRefreshActive else { return } guard Self.isStartupConnectivityRetryableError(error) else { return } self.startupConnectivityRetryNeeded = true } func completeStartupConnectivityRetryPass(currentAttempt: Int) { guard self.startupConnectivityRetryNeeded else { self.cancelStartupConnectivityRetry() return } let nextAttempt = currentAttempt + 1 guard let delay = Self.startupConnectivityRetryDelay(forAttempt: nextAttempt) else { self.cancelStartupConnectivityRetry() return } self.scheduleStartupConnectivityRetry(attempt: nextAttempt, delay: delay) } private func scheduleStartupConnectivityRetry(attempt: Int, delay: TimeInterval) { guard self.startupBehavior.automaticallyStartsBackgroundWork || self._test_startupConnectivityRetryScheduled != nil || self._test_startupConnectivityRetrySleepOverride != nil else { return } self.startupConnectivityRetryTask?.cancel() self._test_startupConnectivityRetryScheduled?(attempt, delay) self.startupConnectivityRetryTask = Task { @MainActor [weak self] in guard let self else { return } do { try await self.sleepForStartupConnectivityRetry(delay) guard !Task.isCancelled else { return } await self.runRefresh( startupConnectivityRetryAttempt: attempt, waitForRefreshAvailability: true) } catch { return } } } private func cancelStartupConnectivityRetry() { self.startupConnectivityRetryTask?.cancel() self.startupConnectivityRetryTask = nil } private func sleepForStartupConnectivityRetry(_ delay: TimeInterval) async throws { if let override = self._test_startupConnectivityRetrySleepOverride { try await override(delay) return } try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) } }