Skip to content

Commit e2dede7

Browse files
committed
apple-calendar-error-handle-retry (#2485)
1 parent 1d16ec8 commit e2dede7

File tree

6 files changed

+93
-52
lines changed

6 files changed

+93
-52
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ tokio-util = "0.7.15"
169169

170170
anyhow = "1"
171171
approx = "0.5.1"
172-
backon = "1.5.2"
172+
backon = "1.6.0"
173173
base64 = "0.22.1"
174174
bytes = "1.9.0"
175175
cached = "0.55.1"

plugins/apple-calendar/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ serde = { workspace = true, features = ["derive"] }
2121
serde_json = { workspace = true }
2222
specta = { workspace = true, features = ["chrono"] }
2323

24+
backon = { workspace = true, features = ["std-blocking-sleep"] }
2425
chrono = { workspace = true, features = ["serde"] }
2526
itertools = { workspace = true }
2627
thiserror = { workspace = true }
Lines changed: 86 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
use std::panic::AssertUnwindSafe;
2+
use std::time::Duration;
3+
4+
use backon::{BlockingRetryable, ConstantBuilder};
15
use itertools::Itertools;
26
use objc2::{AllocAnyThread, rc::Retained};
37
use objc2_event_kit::{EKAuthorizationStatus, EKCalendar, EKEntityType, EKEvent, EKEventStore};
@@ -9,31 +13,38 @@ use crate::types::{AppleCalendar, AppleEvent};
913

1014
use 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

2330
impl 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
}

plugins/apple-calendar/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub enum Error {
1616
InvalidDateRange,
1717
#[error("objective-c exception: {0}")]
1818
ObjectiveCException(String),
19+
#[error("xpc connection failed")]
20+
XpcConnectionFailed,
1921
#[error("transform error: {0}")]
2022
TransformError(String),
2123
#[error("permission denied: {0}")]

plugins/apple-calendar/src/ext.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager<R>> AppleCalendarExt<'a, R, M> {
3434

3535
#[tracing::instrument(skip_all)]
3636
pub fn list_calendars(&self) -> Result<Vec<AppleCalendar>, String> {
37-
let handle = crate::apple::Handle::default();
37+
let handle = crate::apple::Handle;
3838
handle.list_calendars().map_err(|e| e.to_string())
3939
}
4040

4141
#[tracing::instrument(skip_all)]
4242
pub fn list_events(&self, filter: EventFilter) -> Result<Vec<AppleEvent>, String> {
43-
let handle = crate::apple::Handle::default();
43+
let handle = crate::apple::Handle;
4444
handle.list_events(filter).map_err(|e| e.to_string())
4545
}
4646
}

0 commit comments

Comments
 (0)