Spaces:
Running
Running
File size: 4,552 Bytes
4fc4790 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | import OpenClawKit
import CoreLocation
import Foundation
@MainActor
final class LocationService: NSObject, CLLocationManagerDelegate {
enum Error: Swift.Error {
case timeout
case unavailable
}
private let manager = CLLocationManager()
private var authContinuation: CheckedContinuation<CLAuthorizationStatus, Never>?
private var locationContinuation: CheckedContinuation<CLLocation, Swift.Error>?
override init() {
super.init()
self.manager.delegate = self
self.manager.desiredAccuracy = kCLLocationAccuracyBest
}
func authorizationStatus() -> CLAuthorizationStatus {
self.manager.authorizationStatus
}
func accuracyAuthorization() -> CLAccuracyAuthorization {
if #available(iOS 14.0, *) {
return self.manager.accuracyAuthorization
}
return .fullAccuracy
}
func ensureAuthorization(mode: OpenClawLocationMode) async -> CLAuthorizationStatus {
guard CLLocationManager.locationServicesEnabled() else { return .denied }
let status = self.manager.authorizationStatus
if status == .notDetermined {
self.manager.requestWhenInUseAuthorization()
let updated = await self.awaitAuthorizationChange()
if mode != .always { return updated }
}
if mode == .always {
let current = self.manager.authorizationStatus
if current == .authorizedWhenInUse {
self.manager.requestAlwaysAuthorization()
return await self.awaitAuthorizationChange()
}
return current
}
return self.manager.authorizationStatus
}
func currentLocation(
params: OpenClawLocationGetParams,
desiredAccuracy: OpenClawLocationAccuracy,
maxAgeMs: Int?,
timeoutMs: Int?) async throws -> CLLocation
{
let now = Date()
if let maxAgeMs,
let cached = self.manager.location,
now.timeIntervalSince(cached.timestamp) * 1000 <= Double(maxAgeMs)
{
return cached
}
self.manager.desiredAccuracy = Self.accuracyValue(desiredAccuracy)
let timeout = max(0, timeoutMs ?? 10000)
return try await self.withTimeout(timeoutMs: timeout) {
try await self.requestLocation()
}
}
private func requestLocation() async throws -> CLLocation {
try await withCheckedThrowingContinuation { cont in
self.locationContinuation = cont
self.manager.requestLocation()
}
}
private func awaitAuthorizationChange() async -> CLAuthorizationStatus {
await withCheckedContinuation { cont in
self.authContinuation = cont
}
}
private func withTimeout<T: Sendable>(
timeoutMs: Int,
operation: @escaping @Sendable () async throws -> T) async throws -> T
{
try await AsyncTimeout.withTimeoutMs(timeoutMs: timeoutMs, onTimeout: { Error.timeout }, operation: operation)
}
private static func accuracyValue(_ accuracy: OpenClawLocationAccuracy) -> CLLocationAccuracy {
switch accuracy {
case .coarse:
kCLLocationAccuracyKilometer
case .balanced:
kCLLocationAccuracyHundredMeters
case .precise:
kCLLocationAccuracyBest
}
}
nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
Task { @MainActor in
if let cont = self.authContinuation {
self.authContinuation = nil
cont.resume(returning: status)
}
}
}
nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locs = locations
Task { @MainActor in
guard let cont = self.locationContinuation else { return }
self.locationContinuation = nil
if let latest = locs.last {
cont.resume(returning: latest)
} else {
cont.resume(throwing: Error.unavailable)
}
}
}
nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) {
let err = error
Task { @MainActor in
guard let cont = self.locationContinuation else { return }
self.locationContinuation = nil
cont.resume(throwing: err)
}
}
}
|