File size: 2,723 Bytes
7b2dfc5 | 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 | import EventKit
import Foundation
/// The production adapter is intentionally read-only and non-interactive.
/// Permission prompts belong to an explicit Settings flow, never tool
/// execution, so an agent run cannot surprise the user with a system prompt.
actor EventKitCalendarReader: SystemCalendarReading {
private let eventStore: EKEventStore
init(eventStore: EKEventStore = EKEventStore()) {
self.eventStore = eventStore
}
func authorizationStatus() -> SystemCalendarAuthorization {
Self.authorizationStatus(
from: EKEventStore.authorizationStatus(for: .event)
)
}
/// Called only from the explicit Settings button. Tool execution uses
/// `readEvents` and therefore can never trigger an iOS permission prompt.
func requestFullAccess() async throws -> SystemCalendarAuthorization {
_ = try await eventStore.requestFullAccessToEvents()
return authorizationStatus()
}
func readEvents(
in interval: DateInterval,
limit: Int
) throws -> SystemCalendarEventPage {
let authorization = authorizationStatus()
guard authorization == .fullAccess else {
throw SystemCalendarReaderError.fullAccessRequired(authorization)
}
let safeLimit = min(max(limit, 1), 11)
let predicate = eventStore.predicateForEvents(
withStart: interval.start,
end: interval.end,
calendars: nil
)
let matchingEvents = eventStore.events(matching: predicate)
.filter { event in
event.startDate < interval.end && event.endDate > interval.start
}
.sorted { first, second in
if first.startDate != second.startDate {
return first.startDate < second.startDate
}
if first.endDate != second.endDate {
return first.endDate < second.endDate
}
return (first.title ?? "").localizedStandardCompare(
second.title ?? ""
) == .orderedAscending
}
let visibleEvents = matchingEvents.prefix(safeLimit).map { event in
SystemCalendarEvent(
title: event.title ?? "Untitled event",
startsAt: event.startDate,
endsAt: max(event.startDate, event.endDate),
isAllDay: event.isAllDay
)
}
return SystemCalendarEventPage(
events: Array(visibleEvents),
hasMore: matchingEvents.count > safeLimit
)
}
private static func authorizationStatus(
from status: EKAuthorizationStatus
) -> SystemCalendarAuthorization {
switch status {
case .fullAccess:
.fullAccess
case .notDetermined:
.notDetermined
case .denied:
.denied
case .restricted:
.restricted
case .writeOnly:
.writeOnly
@unknown default:
.restricted
}
}
}
|