1use crate::{
2 config::{Config, DBType},
3 db::RemoteClient,
4 do_client,
5 record::{Record, RecordType, RecurringRecord},
6 time::{now, window},
7};
8use anyhow::{anyhow, Result};
9use async_trait::async_trait;
10use chrono::Timelike;
11use gcal::{
12 oauth::{request_access_token, AccessToken},
13 resources::{
14 CalendarListClient, CalendarListItem, DefaultReminder, Event, EventCalendarDate,
15 EventClient, EventReminder, EventStatus,
16 },
17 Client, ClientError,
18};
19use std::collections::{BTreeMap, BTreeSet};
20
21#[derive(Debug, Clone, Default)]
22pub struct GoogleClient {
23 client: Option<Client>,
24 config: Config,
25 ical_map: BTreeMap<String, u64>,
26}
27
28impl GoogleClient {
29 pub fn new(config: Config) -> Result<Self> {
30 if !matches!(config.db_type(), DBType::Google) {
31 return Err(anyhow!("DBType must be set to google"));
32 }
33
34 if !config.has_client() {
35 return Err(anyhow!("Must have client information configured"));
36 }
37
38 let client = if let Some(access_token) = config.access_token() {
39 Client::new(access_token)?
40 } else {
41 return Err(anyhow!("You must have an access token to make calls. Use `saturn config get-token` to retrieve one."));
42 };
43
44 Ok(Self {
45 client: Some(client),
46 config,
47 ical_map: Default::default(),
48 })
49 }
50
51 pub fn client(&self) -> Client {
53 self.client.clone().unwrap()
54 }
55
56 pub fn pick_uid(&self) -> u64 {
57 self.ical_map.values().max().cloned().unwrap_or_default() + 1
58 }
59
60 pub async fn list_calendars(&mut self) -> Result<Vec<CalendarListItem>, ClientError> {
61 let listclient = CalendarListClient::new(self.client().clone());
62 do_client!(self, { listclient.list() })
63 }
64
65 pub async fn record_to_event(&mut self, calendar_id: String, record: &mut Record) -> Event {
66 let start_chrono = record.datetime().with_timezone(&chrono::Local);
67 let utc = start_chrono.with_timezone(&chrono_tz::UTC);
68
69 let start = EventCalendarDate {
70 date_time: Some(utc.to_rfc3339()),
71 time_zone: Some(utc.timezone().to_string()),
72 ..Default::default()
73 };
74
75 let end = match record.record_type() {
76 RecordType::At => Some(EventCalendarDate {
77 date_time: Some((utc + self.config.default_duration().duration()).to_rfc3339()),
78 time_zone: Some(utc.timezone().to_string()),
79 ..Default::default()
80 }),
81 RecordType::Schedule => {
82 let dt = chrono::NaiveDateTime::new(record.date(), record.scheduled().unwrap().1)
83 .and_local_timezone(chrono::Local)
84 .unwrap();
85
86 Some(EventCalendarDate {
87 date_time: Some(dt.with_timezone(&chrono_tz::UTC).to_rfc3339()),
88 time_zone: Some(dt.with_timezone(&chrono_tz::UTC).to_string()),
89 ..Default::default()
90 })
91 }
92 RecordType::AllDay => Some(EventCalendarDate {
93 date_time: Some(
94 (utc + chrono::TimeDelta::try_days(1).unwrap_or_default()).to_rfc3339(),
95 ),
96 time_zone: Some(utc.timezone().to_string()),
97 ..Default::default()
98 }),
99 };
100
101 let event_client = EventClient::new(self.client());
102
103 let mut f = |record: Record| {
104 let mut event = Event::default();
105 event.id = record.internal_key();
106 event.calendar_id = Some(calendar_id.clone());
107 event.ical_uid = if let Some(key) = record.internal_key() {
108 if let Some(uid) = self.ical_map.get(&key) {
109 Some(format!("UID:{}", uid))
110 } else {
111 let uid = self.pick_uid();
112 self.ical_map.insert(key, uid);
113 Some(format!("UID:{}", uid))
114 }
115 } else {
116 Some(format!("UID:{}", self.pick_uid()))
117 };
118 event
119 };
120
121 let mut event = if let Some(key) = record.internal_key() {
122 if let Ok(event) = event_client.get(calendar_id.clone(), key).await {
123 event
124 } else {
125 f(record.clone())
126 }
127 } else {
128 f(record.clone())
129 };
130
131 event.start = Some(start);
132
133 if end.is_some() {
134 event.end = end;
135 }
136
137 if let Some(notifications) = record.notifications() {
138 let mut reminders = EventReminder::default();
139
140 for notification in notifications {
141 if notification.duration() == chrono::TimeDelta::try_minutes(10).unwrap_or_default()
142 {
143 reminders.use_default = true;
144 } else {
145 let mut overrides = Vec::new();
146 if let Ok(minutes) = notification.duration().num_minutes().try_into() {
147 overrides.push(DefaultReminder {
148 method: gcal::ReminderMethod::PopUp,
149 minutes,
150 });
151 }
152 reminders.overrides = Some(overrides);
153 }
154 }
155
156 if !reminders.use_default || reminders.overrides.is_some() {
157 event.reminders = Some(reminders);
158 } else {
159 event.reminders = None;
160 }
161 }
162
163 event.calendar_id = Some(calendar_id.clone());
164 event.summary = Some(record.detail());
165
166 event
167 }
168
169 pub async fn refresh_access_token(&mut self) -> Result<()> {
170 let res: Result<AccessToken, ClientError> =
171 request_access_token(self.config.clone().into(), None, None, true)
172 .await
173 .map_err(|e| e.into());
174 let token = res?;
175 self.config.set_access_token(Some(token.access_token));
176 self.config.set_access_token_expires_at(Some(
177 now().naive_utc()
178 + chrono::TimeDelta::try_seconds(token.expires_in).unwrap_or_default(),
179 ));
180
181 if let Some(refresh_token) = token.refresh_token {
182 self.config.set_refresh_token(Some(refresh_token));
183 if let Some(expires_in) = token.refresh_token_expires_in {
184 self.config.set_refresh_token_expires_at(Some(
185 now().naive_utc()
186 + chrono::TimeDelta::try_seconds(expires_in).unwrap_or_default(),
187 ));
188 } else {
189 self.config.set_refresh_token_expires_at(Some(
190 now().naive_utc() + chrono::TimeDelta::try_seconds(3600).unwrap_or_default(),
191 ));
192 }
193 }
194
195 self.config.save(None)?;
196 Ok(())
197 }
198
199 async fn perform_list(
200 &mut self,
201 calendar_id: String,
202 start: chrono::DateTime<chrono::Local>,
203 end: chrono::DateTime<chrono::Local>,
204 ) -> Result<Vec<Record>> {
205 let list = EventClient::new(self.client());
206
207 let events = do_client!(self, { list.list(calendar_id.clone(), start, end) })?;
208
209 let mut records = Vec::new();
210
211 for mut event in events {
212 if event.recurrence.is_some() {
213 event.calendar_id = Some(calendar_id.clone());
214
215 let instances = EventClient::new(self.client())
216 .instances(event.clone())
217 .await?;
218
219 for new_event in instances.items {
220 if let Some(new_start) = &new_event.start {
221 if let Some(new_start) = &new_start.date {
222 if let Ok(new_start) = new_start.parse::<chrono::NaiveDate>() {
223 if new_start > start.date_naive() && new_start < end.date_naive() {
224 if let Some(status) = new_event.status.clone() {
225 if !matches!(status, EventStatus::Cancelled) {
226 records.push(self.event_to_record(new_event)?);
227 }
228 }
229 }
230 }
231 } else if let Some(new_start) = &new_start.date_time {
232 if let Ok(new_start) =
233 new_start.parse::<chrono::DateTime<chrono::Local>>()
234 {
235 if new_start > start && new_start < end {
236 if let Some(status) = new_event.status.clone() {
237 if !matches!(status, EventStatus::Cancelled) {
238 records.push(self.event_to_record(new_event)?);
239 }
240 }
241 }
242 }
243 }
244 }
245 }
246 } else {
247 event.calendar_id = Some(calendar_id.clone());
248 if let Some(status) = event.status.clone() {
249 if !matches!(status, EventStatus::Cancelled) {
250 records.push(self.event_to_record(event)?)
251 }
252 } else {
253 records.push(self.event_to_record(event)?)
254 }
255 }
256 }
257
258 Ok(records)
259 }
260
261 pub fn event_to_record(&mut self, event: Event) -> Result<Record, ClientError> {
262 let mut record = Record::default();
263
264 record.set_internal_key(event.id.clone());
265 record.set_internal_recurrence_key(event.id.clone());
266
267 let original_start = event.original_start_time;
268
269 let start_time = event
270 .start
271 .clone()
272 .or(original_start.clone())
273 .map(|x| {
274 x.date_time.map(|y| {
275 y.parse::<chrono::DateTime<chrono::Local>>()
276 .map_or_else(|_| None, |z| Some(z.naive_local()))
277 })
278 })
279 .flatten()
280 .flatten();
281
282 let date = event
283 .start
284 .clone()
285 .or(original_start.clone())
286 .map(|x| {
287 x.date
288 .map(|y| y.parse::<chrono::NaiveDate>().map_or_else(|_| None, Some))
289 })
290 .flatten()
291 .flatten();
292
293 let has_start_time = start_time.is_some();
294
295 let has_end_time = match event.end.clone() {
296 Some(end) => end.date_time.is_some() || event.end_time_unspecified.unwrap_or_default(),
297 None => false,
298 };
299
300 let schedule = if has_start_time && has_end_time {
301 let local = match event
302 .end
303 .clone()
304 .unwrap()
305 .date_time
306 .unwrap()
307 .parse::<chrono::DateTime<chrono::Local>>()
308 {
309 Ok(p) => p.naive_local(),
310 Err(_) => return Err(anyhow!("Couldn't parse time").into()),
311 };
312
313 if (start_time.unwrap() + chrono::TimeDelta::try_days(1).unwrap_or_default()) == local {
314 RecordType::AllDay
315 } else {
316 RecordType::Schedule
317 }
318 } else if has_start_time {
319 RecordType::At
320 } else {
321 RecordType::AllDay
322 };
323
324 record.set_record_type(schedule.clone());
325
326 let now = now();
327 let start_time = start_time.unwrap_or(now.naive_local());
328 let date = date.unwrap_or(start_time.date());
329
330 if let Some(reminders) = event.reminders {
331 if reminders.use_default {
332 record.add_notification(chrono::TimeDelta::try_minutes(10).unwrap_or_default());
333 }
334
335 if let Some(overrides) = reminders.overrides {
336 for notification in overrides {
337 match notification.method {
338 gcal::ReminderMethod::PopUp => {
339 record.add_notification(
340 chrono::TimeDelta::try_minutes(notification.minutes.into())
341 .unwrap_or_default(),
342 );
343 }
344 _ => {}
345 }
346 }
347 }
348 }
349
350 match schedule {
351 RecordType::AllDay => {
352 record.set_all_day();
353 record.set_date(date);
354 }
355 RecordType::At => {
356 record.set_at(Some(start_time.time()));
357 record.set_date(start_time.date());
358 }
359 RecordType::Schedule => {
360 record.set_date(start_time.date());
361 record.set_scheduled(Some((
362 start_time.time(),
363 event.end.map_or_else(
364 || now.time(),
365 |x| {
366 x.date_time.map_or_else(
367 || now.time(),
368 |y| {
369 y.parse::<chrono::DateTime<chrono::Local>>()
370 .unwrap_or(now)
371 .time()
372 },
373 )
374 },
375 ),
376 )));
377 }
378 }
379
380 record.set_detail(event.summary.unwrap_or("No summary provided".to_string()));
381 if let Some(uid) = event.ical_uid {
382 if let Ok(uid) = uid.strip_prefix("UID:").unwrap_or_default().parse::<u64>() {
383 if let Some(id) = event.id.clone() {
384 self.ical_map.insert(id, uid);
385 }
386 }
387 }
388 Ok(record)
389 }
390}
391
392#[async_trait]
393impl RemoteClient for GoogleClient {
394 async fn delete(&mut self, calendar_id: String, event_id: String) -> Result<()> {
395 let events = EventClient::new(self.client());
396 let mut event = Event::default();
397 event.id = Some(event_id);
398 event.calendar_id = Some(calendar_id);
399
400 do_client!(self, { events.delete(event.clone()) })?;
401 Ok(())
402 }
403
404 async fn delete_recurrence(
405 &mut self,
406 calendar_id: String,
407 event_id: String,
408 ) -> Result<Vec<String>> {
409 let events = EventClient::new(self.client());
410 let mut event = Event::default();
411 event.id = Some(event_id);
412 event.calendar_id = Some(calendar_id.clone());
413
414 let list = events.instances(event.clone()).await?;
415
416 do_client!(self, { events.delete(event.clone()) })?;
417
418 Ok(list
419 .items
420 .iter()
421 .filter_map(|x| x.id.clone())
422 .collect::<Vec<String>>())
423 }
424
425 async fn record(&mut self, calendar_id: String, mut record: Record) -> Result<String> {
426 let event = self.record_to_event(calendar_id, &mut record).await;
427 let client = EventClient::new(self.client());
428
429 let event = do_client!(self, { client.insert(event.clone()) })?;
430
431 if let Some(uid) = event.ical_uid {
432 if let Ok(uid) = uid.strip_prefix("UID:").unwrap_or_default().parse::<u64>() {
433 if let Some(id) = event.id.clone() {
434 self.ical_map.insert(id, uid);
435 }
436 }
437 }
438
439 if let Some(id) = event.id {
440 Ok(id)
441 } else {
442 Err(anyhow!("Event could not be saved"))
443 }
444 }
445
446 async fn record_recurrence(
447 &mut self,
448 calendar_id: String,
449 mut record: RecurringRecord,
450 ) -> Result<(String, String)> {
451 if record.recurrence().duration() < chrono::TimeDelta::try_days(1).unwrap_or_default() {
452 return Err(anyhow!(
453 "Google Calendar supports a minimum granularity of 1 day"
454 ));
455 }
456
457 let mut event = self.record_to_event(calendar_id, record.record()).await;
458
459 if let Some(uid) = event.clone().ical_uid {
460 if let Ok(uid) = uid.strip_prefix("UID:").unwrap_or_default().parse::<u64>() {
461 if let Some(id) = event.id.clone() {
462 self.ical_map.insert(id, uid);
463 }
464 }
465 }
466
467 let mut recurrence = BTreeSet::default();
468 recurrence.insert(record.to_rrule());
469
470 event.recurrence = Some(recurrence);
471
472 let client = EventClient::new(self.client());
473 let event = do_client!(self, { client.insert(event.clone()) })?;
474
475 if let Some(id) = event.clone().id {
476 return Ok((id.clone(), id));
477 }
478
479 Err(anyhow!("Event could not be saved"))
480 }
481
482 async fn list_recurrence(&mut self, calendar_id: String) -> Result<Vec<RecurringRecord>> {
483 let list = EventClient::new(self.client());
484
485 let mut events = do_client!(self, {
486 let window = window(&self.config);
487 list.list(calendar_id.clone(), window.0, window.1)
488 })?;
489
490 let mut v = Vec::new();
491
492 for event in &mut events {
493 if let Some(recurrence) = &event.recurrence {
494 event.calendar_id = Some(calendar_id.clone());
495 let record = self.event_to_record(event.clone())?;
496 for recur in recurrence {
497 if let Ok(mut x) =
498 RecurringRecord::from_rrule(record.clone(), recur.to_string())
499 {
500 x.set_internal_key(event.id.clone());
501 if let Some(status) = event.status.clone() {
502 if !matches!(status, EventStatus::Cancelled) {
503 v.push(x);
504 }
505 } else {
506 v.push(x);
507 }
508 }
509 }
510 }
511 }
512
513 Ok(v)
514 }
515
516 async fn update_recurrence(&mut self, _calendar_id: String) -> Result<()> {
517 Ok(())
518 }
519
520 async fn list_today(
521 &mut self,
522 calendar_id: String,
523 _include_completed: bool,
524 ) -> Result<Vec<Record>> {
525 self.perform_list(
526 calendar_id,
527 now() - chrono::TimeDelta::try_days(1).unwrap_or_default(),
528 now() + chrono::TimeDelta::try_days(1).unwrap_or_default(),
529 )
530 .await
531 }
532
533 async fn list_all(
534 &mut self,
535 calendar_id: String,
536 _include_completed: bool, ) -> Result<Vec<Record>> {
538 let window = window(&self.config);
539 self.perform_list(calendar_id, window.0, window.1).await
540 }
541
542 async fn events_now(
543 &mut self,
544 calendar_id: String,
545 last: chrono::Duration,
546 _include_completed: bool,
547 ) -> Result<Vec<Record>> {
548 let window = window(&self.config);
549 let list = self.perform_list(calendar_id, window.0, window.1).await?;
550 let mut v = Vec::new();
551 for item in list {
552 let dt = item.datetime();
553 let n = now();
554 if dt > n && n > dt - last {
555 v.push(item);
556 } else if let Some(notifications) = item.notifications() {
557 for notification in notifications {
558 let dt_window = dt - notification.duration();
559 let dt_time = dt_window
560 .time()
561 .with_second(0)
562 .unwrap()
563 .with_nanosecond(0)
564 .unwrap();
565 let n_time = n.time().with_second(0).unwrap().with_nanosecond(0).unwrap();
566
567 if dt > n && dt_window.date_naive() == n.date_naive() && dt_time == n_time {
568 v.push(item);
569 break;
570 }
571 }
572 }
573 }
574
575 Ok(v)
576 }
577
578 async fn complete_task(&mut self, _calendar_id: String, _primary_key: u64) -> Result<()> {
579 Ok(())
580 }
581
582 async fn get(&mut self, calendar_id: String, event_id: String) -> Result<Record> {
583 let events = EventClient::new(self.client());
584 Ok(self.event_to_record(events.get(calendar_id, event_id).await?)?)
585 }
586
587 async fn get_recurring(
588 &mut self,
589 calendar_id: String,
590 event_id: String,
591 ) -> Result<RecurringRecord> {
592 let events = EventClient::new(self.client());
593 let event = events.get(calendar_id, event_id).await?;
594 let mut ret: Option<RecurringRecord> = None;
595
596 let record = self.event_to_record(event.clone())?;
597 for recur in &event
598 .recurrence
599 .ok_or(anyhow!("No recurrence data for this event"))?
600 {
601 if let Ok(rr) = RecurringRecord::from_rrule(record.clone(), recur.clone()) {
602 ret = Some(rr);
603 break;
604 }
605 }
606
607 let mut ret = ret.ok_or(anyhow!("No recurrence data found for event"))?;
608 ret.set_internal_key(event.id.clone());
609 Ok(ret)
610 }
611
612 async fn update(&mut self, calendar_id: String, mut record: Record) -> Result<()> {
613 let events = EventClient::new(self.client());
614 let event = self.record_to_event(calendar_id, &mut record).await;
615 events.update(event).await?;
616 Ok(())
617 }
618
619 async fn update_recurring(
620 &mut self,
621 calendar_id: String,
622 mut record: RecurringRecord,
623 ) -> Result<()> {
624 let events = EventClient::new(self.client());
625 let key = record.internal_key();
626 let r = record.record();
627 r.set_internal_key(key);
628 let mut event = self.record_to_event(calendar_id, r).await;
629 event.recurrence = Some(BTreeSet::from_iter(vec![record.to_rrule()]));
630 events.update(event).await?;
631 Ok(())
632 }
633}