1+ use std:: panic:: AssertUnwindSafe ;
2+ use std:: time:: Duration ;
3+
4+ use backon:: { BlockingRetryable , ConstantBuilder } ;
15use itertools:: Itertools ;
26use objc2:: { AllocAnyThread , rc:: Retained } ;
37use objc2_event_kit:: { EKAuthorizationStatus , EKCalendar , EKEntityType , EKEvent , EKEventStore } ;
@@ -9,31 +13,38 @@ use crate::types::{AppleCalendar, AppleEvent};
913
1014use super :: transforms:: { transform_calendar, transform_event} ;
1115
12- pub struct Handle {
13- event_store : Retained < EKEventStore > ,
16+ fn retry_backoff ( ) -> ConstantBuilder {
17+ ConstantBuilder :: default ( )
18+ . with_delay ( Duration :: from_millis ( 100 ) )
19+ . with_max_times ( 3 )
1420}
1521
16- impl Default for Handle {
17- fn default ( ) -> Self {
18- let event_store = unsafe { EKEventStore :: new ( ) } ;
19- Self { event_store }
22+ pub struct Handle ;
23+
24+ impl Handle {
25+ fn create_event_store ( ) -> Retained < EKEventStore > {
26+ unsafe { EKEventStore :: new ( ) }
2027 }
2128}
2229
2330impl Handle {
24- fn has_calendar_access ( & self ) -> bool {
31+ fn has_calendar_access ( ) -> bool {
2532 let status = unsafe { EKEventStore :: authorizationStatusForEntityType ( EKEntityType :: Event ) } ;
2633 matches ! ( status, EKAuthorizationStatus :: FullAccess )
2734 }
2835
29- fn fetch_events ( & self , filter : & EventFilter ) -> Result < Retained < NSArray < EKEvent > > , Error > {
30- let calendars: Retained < NSArray < EKCalendar > > = unsafe { self . event_store . calendars ( ) }
31- . into_iter ( )
32- . filter ( |c| {
33- let id = unsafe { c. calendarIdentifier ( ) } . to_string ( ) ;
34- filter. calendar_tracking_id . eq ( & id)
35- } )
36- . collect ( ) ;
36+ fn fetch_events (
37+ event_store : & EKEventStore ,
38+ filter : & EventFilter ,
39+ ) -> Result < Retained < NSArray < EKEvent > > , Error > {
40+ let calendars: Retained < NSArray < EKCalendar > > =
41+ Self :: get_calendars_with_exception_handling ( event_store) ?
42+ . into_iter ( )
43+ . filter ( |c| {
44+ let id = unsafe { c. calendarIdentifier ( ) } . to_string ( ) ;
45+ filter. calendar_tracking_id . eq ( & id)
46+ } )
47+ . collect ( ) ;
3748
3849 if calendars. is_empty ( ) {
3950 return Err ( Error :: CalendarNotFound ) ;
@@ -50,58 +61,84 @@ impl Handle {
5061 . collect_tuple ( )
5162 . ok_or_else ( || Error :: InvalidDateRange ) ?;
5263
53- let predicate = unsafe {
54- self . event_store
55- . predicateForEventsWithStartDate_endDate_calendars (
56- & start_date,
57- & end_date,
58- Some ( & calendars) ,
59- )
60- } ;
64+ let event_store = AssertUnwindSafe ( event_store) ;
65+ let calendars = AssertUnwindSafe ( calendars) ;
66+ let start_date = AssertUnwindSafe ( start_date) ;
67+ let end_date = AssertUnwindSafe ( end_date) ;
68+
69+ let result = objc2:: exception:: catch ( || unsafe {
70+ let predicate = event_store. predicateForEventsWithStartDate_endDate_calendars (
71+ & start_date,
72+ & end_date,
73+ Some ( & calendars) ,
74+ ) ;
75+ event_store. eventsMatchingPredicate ( & predicate)
76+ } ) ;
77+
78+ result. map_err ( |_| Error :: XpcConnectionFailed )
79+ }
6180
62- Ok ( unsafe { self . event_store . eventsMatchingPredicate ( & predicate) } )
81+ fn get_calendars_with_exception_handling (
82+ event_store : & EKEventStore ,
83+ ) -> Result < Retained < NSArray < EKCalendar > > , Error > {
84+ let event_store = AssertUnwindSafe ( event_store) ;
85+ objc2:: exception:: catch ( || unsafe { event_store. calendars ( ) } )
86+ . map_err ( |_| Error :: XpcConnectionFailed )
6387 }
6488
6589 pub fn list_calendars ( & self ) -> Result < Vec < AppleCalendar > , Error > {
66- if !self . has_calendar_access ( ) {
90+ if !Self :: has_calendar_access ( ) {
6791 return Err ( Error :: CalendarAccessDenied ) ;
6892 }
6993
70- let calendars = unsafe { self . event_store . calendars ( ) } ;
71-
72- let list = calendars
73- . iter ( )
74- . map ( |calendar| transform_calendar ( & calendar) )
75- . sorted_by ( |a, b| a. title . cmp ( & b. title ) )
76- . collect ( ) ;
94+ let fetch = || {
95+ let event_store = Self :: create_event_store ( ) ;
96+ let calendars = Self :: get_calendars_with_exception_handling ( & event_store) ?;
97+ let list = calendars
98+ . iter ( )
99+ . map ( |calendar| transform_calendar ( & calendar) )
100+ . sorted_by ( |a, b| a. title . cmp ( & b. title ) )
101+ . collect ( ) ;
102+ Ok ( list)
103+ } ;
77104
78- Ok ( list)
105+ fetch
106+ . retry ( retry_backoff ( ) )
107+ . when ( |e| matches ! ( e, Error :: XpcConnectionFailed ) )
108+ . call ( )
79109 }
80110
81111 pub fn list_events ( & self , filter : EventFilter ) -> Result < Vec < AppleEvent > , Error > {
82- if !self . has_calendar_access ( ) {
112+ if !Self :: has_calendar_access ( ) {
83113 return Err ( Error :: CalendarAccessDenied ) ;
84114 }
85115
86- let events_array = self . fetch_events ( & filter) ?;
116+ let fetch = || {
117+ let event_store = Self :: create_event_store ( ) ;
118+ let events_array = Self :: fetch_events ( & event_store, & filter) ?;
87119
88- let events: Result < Vec < _ > , _ > = events_array
89- . iter ( )
90- . filter_map ( |event| {
91- let calendar = unsafe { event. calendar ( ) } ?;
92- let calendar_id = unsafe { calendar. calendarIdentifier ( ) } ;
120+ let events: Result < Vec < _ > , _ > = events_array
121+ . iter ( )
122+ . filter_map ( |event| {
123+ let calendar = unsafe { event. calendar ( ) } ?;
124+ let calendar_id = unsafe { calendar. calendarIdentifier ( ) } ;
93125
94- if !filter. calendar_tracking_id . eq ( & calendar_id. to_string ( ) ) {
95- return None ;
96- }
126+ if !filter. calendar_tracking_id . eq ( & calendar_id. to_string ( ) ) {
127+ return None ;
128+ }
97129
98- Some ( transform_event ( & event) )
99- } )
100- . collect ( ) ;
130+ Some ( transform_event ( & event) )
131+ } )
132+ . collect ( ) ;
101133
102- let mut events = events?;
103- events. sort_by ( |a, b| a. start_date . cmp ( & b. start_date ) ) ;
134+ let mut events = events?;
135+ events. sort_by ( |a, b| a. start_date . cmp ( & b. start_date ) ) ;
136+ Ok ( events)
137+ } ;
104138
105- Ok ( events)
139+ fetch
140+ . retry ( retry_backoff ( ) )
141+ . when ( |e| matches ! ( e, Error :: XpcConnectionFailed ) )
142+ . call ( )
106143 }
107144}
0 commit comments