saturn_cli/db/
google.rs

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    // this should be safe? lol
52    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, // FIXME include tasks
537    ) -> 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}