@@ -31,12 +31,23 @@ pub async fn sync_events(
3131 let db_events_with_session = list_db_events_with_session ( & db, & user_id) . await ?;
3232 let db_selected_calendars = list_db_calendars_selected ( & db, & user_id) . await ?;
3333
34+ // Batch API call instead of individual calls
35+ let calendar_tracking_ids: Vec < String > = db_selected_calendars
36+ . iter ( )
37+ . map ( |cal| cal. tracking_id . clone ( ) )
38+ . collect ( ) ;
39+
40+ let system_events_per_tracking_id =
41+ list_system_events_for_calendars ( calendar_tracking_ids) . await ;
42+
43+ // Convert from tracking_id -> database_id mapping
3444 let mut system_events_per_selected_calendar = std:: collections:: HashMap :: new ( ) ;
3545 for db_calendar in & db_selected_calendars {
36- system_events_per_selected_calendar. insert (
37- db_calendar. id . clone ( ) ,
38- list_system_events ( db_calendar. tracking_id . clone ( ) ) . await ,
39- ) ;
46+ let events = system_events_per_tracking_id
47+ . get ( & db_calendar. tracking_id )
48+ . unwrap_or ( & vec ! [ ] )
49+ . clone ( ) ;
50+ system_events_per_selected_calendar. insert ( db_calendar. id . clone ( ) , events) ;
4051 }
4152
4253 _sync_events (
@@ -81,7 +92,7 @@ async fn _sync_calendars(
8192 . find ( |db_c| db_c. tracking_id == sys_c. id ) ;
8293
8394 hypr_db_user:: Calendar {
84- id : uuid:: Uuid :: new_v4 ( ) . to_string ( ) ,
95+ id : existing . map_or ( uuid:: Uuid :: new_v4 ( ) . to_string ( ) , |c| c . id . clone ( ) ) ,
8596 tracking_id : sys_c. id . clone ( ) ,
8697 user_id : user_id. clone ( ) ,
8798 name : sys_c. name . clone ( ) ,
@@ -121,13 +132,10 @@ async fn _sync_events(
121132 . collect ( ) ;
122133
123134 // Process existing events:
124- // 1. Delete events from unselected calendars that have no sessions
125- // 2. Handle rescheduled events (update instead of delete + create)
126- // 3. Delete events that no longer exist in the system calendar
127135 for ( db_event, session) in & db_events_with_session {
128136 let is_selected_cal = db_selected_calendars
129137 . iter ( )
130- . any ( |c| c. tracking_id == db_event. calendar_id . clone ( ) . unwrap_or_default ( ) ) ;
138+ . any ( |c| c. id == db_event. calendar_id . clone ( ) . unwrap_or_default ( ) ) ;
131139
132140 if !is_selected_cal && session. as_ref ( ) . map_or ( true , |s| s. is_empty ( ) ) {
133141 state. to_delete . push ( db_event. clone ( ) ) ;
@@ -138,38 +146,29 @@ async fn _sync_events(
138146 if let Some ( events) = system_events_per_selected_calendar. get ( calendar_id) {
139147 // Check if event exists with same tracking_id
140148 if let Some ( matching_event) = events. iter ( ) . find ( |e| e. id == db_event. tracking_id ) {
141- // Event exists with same tracking_id - it may have been updated
142149 let updated_event = hypr_db_user:: Event {
143- id : db_event. id . clone ( ) , // Preserve the original database ID
150+ id : db_event. id . clone ( ) ,
144151 tracking_id : matching_event. id . clone ( ) ,
145152 user_id : user_id. clone ( ) ,
146153 calendar_id : Some ( calendar_id. clone ( ) ) ,
147154 name : matching_event. name . clone ( ) ,
148155 note : matching_event. note . clone ( ) ,
149156 start_date : matching_event. start_date ,
150157 end_date : matching_event. end_date ,
151- google_event_url : db_event. google_event_url . clone ( ) , // Preserve any existing URL
158+ google_event_url : db_event. google_event_url . clone ( ) ,
152159 } ;
153160 state. to_update . push ( updated_event) ;
154161 continue ;
155162 }
156163
157- // Check if this might be a rescheduled event (same name, calendar, but different tracking_id)
164+ // Check for rescheduled events
158165 if let Some ( rescheduled_event) = find_potentially_rescheduled_event (
159166 & db_event,
160167 & all_system_events,
161168 & db_selected_calendars,
162169 ) {
163- tracing:: info!(
164- "Detected rescheduled event: {} -> {}, event: '{}'" ,
165- db_event. tracking_id,
166- rescheduled_event. id,
167- db_event. name
168- ) ;
169-
170- // Update the existing database event with new tracking_id and details
171170 let updated_event = hypr_db_user:: Event {
172- id : db_event. id . clone ( ) , // Preserve the original database ID to keep user notes/sessions
171+ id : db_event. id . clone ( ) ,
173172 tracking_id : rescheduled_event. id . clone ( ) ,
174173 user_id : user_id. clone ( ) ,
175174 calendar_id : db_event. calendar_id . clone ( ) ,
@@ -183,54 +182,50 @@ async fn _sync_events(
183182 continue ;
184183 }
185184
186- // Event not found - mark for deletion
187- tracing:: info!(
188- "Event not found in system calendar, marking for deletion: {} '{}'" ,
189- db_event. tracking_id,
190- db_event. name
191- ) ;
185+ state. to_delete . push ( db_event. clone ( ) ) ;
186+ } else {
192187 state. to_delete . push ( db_event. clone ( ) ) ;
193188 }
189+ } else {
190+ state. to_delete . push ( db_event. clone ( ) ) ;
194191 }
195192 }
196193
197194 // Add new events (that haven't been handled as updates)
198195 for db_calendar in db_selected_calendars {
199- let fresh_events = system_events_per_selected_calendar
200- . get ( & db_calendar. id )
201- . unwrap ( ) ;
196+ if let Some ( fresh_events) = system_events_per_selected_calendar. get ( & db_calendar. id ) {
197+ for system_event in fresh_events {
198+ // Skip if this event was already handled as an update
199+ let already_handled = state
200+ . to_update
201+ . iter ( )
202+ . any ( |e| e. tracking_id == system_event. id ) ;
203+ if already_handled {
204+ continue ;
205+ }
202206
203- for system_event in fresh_events {
204- // Skip if this event was already handled as an update
205- let already_handled = state
206- . to_update
207- . iter ( )
208- . any ( |e| e. tracking_id == system_event. id ) ;
209- if already_handled {
210- continue ;
211- }
207+ // Skip if this event already exists in the database with the same tracking_id
208+ let already_exists = db_events_with_session
209+ . iter ( )
210+ . any ( |( db_event, _) | db_event. tracking_id == system_event. id ) ;
211+ if already_exists {
212+ continue ;
213+ }
212214
213- // Skip if this event already exists in the database with the same tracking_id
214- let already_exists = db_events_with_session
215- . iter ( )
216- . any ( |( db_event, _) | db_event. tracking_id == system_event. id ) ;
217- if already_exists {
218- continue ;
215+ // This is a genuinely new event
216+ let new_event = hypr_db_user:: Event {
217+ id : uuid:: Uuid :: new_v4 ( ) . to_string ( ) ,
218+ tracking_id : system_event. id . clone ( ) ,
219+ user_id : user_id. clone ( ) ,
220+ calendar_id : Some ( db_calendar. id . clone ( ) ) ,
221+ name : system_event. name . clone ( ) ,
222+ note : system_event. note . clone ( ) ,
223+ start_date : system_event. start_date ,
224+ end_date : system_event. end_date ,
225+ google_event_url : None ,
226+ } ;
227+ state. to_upsert . push ( new_event) ;
219228 }
220-
221- // This is a genuinely new event
222- let new_event = hypr_db_user:: Event {
223- id : uuid:: Uuid :: new_v4 ( ) . to_string ( ) ,
224- tracking_id : system_event. id . clone ( ) ,
225- user_id : user_id. clone ( ) ,
226- calendar_id : Some ( db_calendar. id . clone ( ) ) ,
227- name : system_event. name . clone ( ) ,
228- note : system_event. note . clone ( ) ,
229- start_date : system_event. start_date ,
230- end_date : system_event. end_date ,
231- google_event_url : None ,
232- } ;
233- state. to_upsert . push ( new_event) ;
234229 }
235230 }
236231
@@ -279,25 +274,55 @@ async fn list_system_calendars() -> Vec<hypr_calendar_interface::Calendar> {
279274 . unwrap_or_default ( )
280275}
281276
282- async fn list_system_events ( calendar_tracking_id : String ) -> Vec < hypr_calendar_interface:: Event > {
277+ async fn list_system_events_for_calendars (
278+ calendar_tracking_ids : Vec < String > ,
279+ ) -> std:: collections:: HashMap < String , Vec < hypr_calendar_interface:: Event > > {
280+ let now = Utc :: now ( ) ;
281+
282+ for ( i, id) in calendar_tracking_ids. iter ( ) . enumerate ( ) {
283+ tracing:: info!( " Calendar {}: tracking_id={}" , i + 1 , id) ;
284+ }
285+
283286 tauri:: async_runtime:: spawn_blocking ( move || {
284287 let handle = hypr_calendar_apple:: Handle :: new ( ) ;
285288
286- let filter = EventFilter {
287- calendar_tracking_id,
288- from : Utc :: now ( ) ,
289- to : Utc :: now ( ) + chrono:: Duration :: days ( 28 ) ,
290- } ;
289+ let mut results = std:: collections:: HashMap :: new ( ) ;
291290
292- let rt = tokio:: runtime:: Builder :: new_current_thread ( )
293- . enable_all ( )
294- . build ( )
295- . unwrap ( ) ;
291+ for ( i, calendar_tracking_id) in calendar_tracking_ids. iter ( ) . enumerate ( ) {
292+ let filter = EventFilter {
293+ calendar_tracking_id : calendar_tracking_id. clone ( ) ,
294+ from : now,
295+ to : now + chrono:: Duration :: days ( 28 ) ,
296+ } ;
296297
297- rt. block_on ( async { handle. list_events ( filter) . await . unwrap_or_default ( ) } )
298+ // Add small delay between API calls to avoid overwhelming EventKit
299+ if i > 0 {
300+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 50 ) ) ;
301+ tracing:: info!( " Applied 50ms delay after calendar {}" , i) ;
302+ }
303+
304+ let events = match tokio:: runtime:: Handle :: try_current ( ) {
305+ Ok ( rt) => {
306+ tracing:: info!( " Using existing tokio runtime" ) ;
307+ rt. block_on ( handle. list_events ( filter) ) . unwrap_or_default ( )
308+ }
309+ Err ( _) => {
310+ tracing:: info!( " Creating new tokio runtime" ) ;
311+ let rt = tokio:: runtime:: Builder :: new_current_thread ( )
312+ . enable_all ( )
313+ . build ( )
314+ . unwrap ( ) ;
315+ rt. block_on ( handle. list_events ( filter) ) . unwrap_or_default ( )
316+ }
317+ } ;
318+
319+ results. insert ( calendar_tracking_id. clone ( ) , events) ;
320+ }
321+
322+ results
298323 } )
299324 . await
300- . unwrap_or_default ( )
325+ . unwrap_or_else ( |e| std :: collections :: HashMap :: new ( ) )
301326}
302327
303328async fn list_db_calendars (
0 commit comments