Mercurial > code > home > repos > gcalendarwatch
changeset 52:5f7c393577e9
now gets sync updates (for 30 sec)
author | drewp@bigasterisk.com |
---|---|
date | Mon, 19 Aug 2024 19:19:13 -0700 |
parents | a9b720445bcf |
children | f248f018a663 |
files | calsync/event_sync.go calsync/gcalclient/gcalclient.go calsync/gcalclient/service.go |
diffstat | 3 files changed, 195 insertions(+), 35 deletions(-) [+] |
line wrap: on
line diff
--- a/calsync/event_sync.go Mon Aug 19 14:42:27 2024 -0700 +++ b/calsync/event_sync.go Mon Aug 19 19:19:13 2024 -0700 @@ -9,19 +9,44 @@ "bigasterisk.com/go/gcalendarwatch/mongoclient" ) +// Runs forever. func updateMongoEventsToMatchGoogle( mc *mongoclient.MongoClient, gc *gcalclient.GCalClient) error { t := time.Now() - events, err := gc.FindEvents(mc, t, 3) - if err != nil { - return err + eventUpdates := make(chan *gcalclient.FindEventsMessage) + + go updateRoutine(eventUpdates, gc, mc) + + for ev := range eventUpdates { + if ev.Event != nil { + mc.UpsertOneEvent( + convert.MongoEventFromGoogleEvent2( + ev.Event.CalendarUrl, + ev.Event, + /*modSince=*/ t, + ), + ) + } else { + log.Println("cal", ev.CalId, "ready for cleanup - todo") + log.Println("t=", t) + mc.DeleteEventsUpdatedBefore(t) + } } - log.Println("upserting", len(events), "events") - for _, ev := range events { - mc.UpsertOneEvent( - convert.MongoEventFromGoogleEvent2(ev.CalendarUrl, ev, t), - ) - } - mc.DeleteEventsUpdatedBefore(t) return nil } + +func updateRoutine( + eventUpdates chan *gcalclient.FindEventsMessage, + gc *gcalclient.GCalClient, + mc *mongoclient.MongoClient, +) { + defer close(eventUpdates) + + t := time.Now() + err := gc.FindEvents(mc, t.AddDate(0, 0, -3), t.AddDate(0, 0, 7), eventUpdates) + if err != nil { + log.Println(err) + return + } + log.Println("updateRoutine done") +}
--- a/calsync/gcalclient/gcalclient.go Mon Aug 19 14:42:27 2024 -0700 +++ b/calsync/gcalclient/gcalclient.go Mon Aug 19 19:19:13 2024 -0700 @@ -35,8 +35,8 @@ return MakeEventUrl("http://bigasterisk.com/calendar/"+ url.QueryEscape(googleCalId), evId) } + func New(ctx context.Context) (*GCalClient, error) { - // If modifying these scopes, delete your previously saved token.json. err, srv := newService(ctx) if err != nil { log.Fatalf("Unable to retrieve Calendar client: %v", err) @@ -58,36 +58,170 @@ return list.Items, nil } -// FindEvents considers all calendars -func (gc *GCalClient) FindEvents(mc *mongoclient.MongoClient, s time.Time, maxEventsPerCalendar int64) ([]*CalendarEvent, error) { +type FindEventsMessage struct { + // either non-nil this: + Event *CalendarEvent + // or these: + CalId string + OlderThanThisIsDeletable time.Time +} + +// FindEvents considers all calendars. It runs forever. +func (gc *GCalClient) FindEvents( + mc *mongoclient.MongoClient, + // For each calendar, after events in this time range have been sent to + // `out`, the chan will get the other kind of FindEventsMessage (CalId, + // ...). That message signals that the caller may cull old events on the given + // calendar. After that point, all events will be updates (including + // deletes). + initialFillStart, initialFillEnd time.Time, + out chan *FindEventsMessage, +) error { + cals, err := mc.GetAllCals() if err != nil { - return nil, err + return err } log.Println("reading", len(cals), "calendars") - ret := make([]*CalendarEvent, 0) - for _, cal := range cals { - calUrl := cal.Url - events, err := gc.srv. - Events.List(cal.GoogleId). - ShowDeleted(false). - SingleEvents(true). - TimeMin(s.Format(time.RFC3339)). - MaxResults(maxEventsPerCalendar). - OrderBy("startTime"). - Do() + for calNum, cal := range cals { + t := time.Now() + log.Println(" cal", calNum, cal.Url) + log.Println(" cal", calNum, "readEventsInRange", "from", initialFillStart, "to", initialFillEnd) + syncToken, err := gc.readEventsInRange(&cal, initialFillStart, initialFillEnd, out) + if err != nil { + return err + } + + out <- &FindEventsMessage{nil, cal.GoogleId, t} + + ew := gc.NewEventWatch(&cal, t, syncToken, out) + + for loop := 0; loop < 30; loop++ { + log.Println("") + log.Println("tail loop", loop, "for", cal.Url) + err := ew.GetMoreEvents() if err != nil { - return nil, err + return err } - log.Println(len(events.Items), "events from", calUrl) - for _, event := range events.Items { - ev := &CalendarEvent{ - Event: event, - CalendarUrl: calUrl, - EventUrl: MakeEventUrl(calUrl, event.Id), - } - ret = append(ret, ev) + time.Sleep(2 * time.Second) } } - return ret, nil + + return nil +} + +// Synchronous. +func (gc *GCalClient) readEventsInRange( + cal *mongoclient.MongoCal, + initialFillStart, initialFillEnd time.Time, + out chan *FindEventsMessage, +) (string, error) { + log.Println( + " get initial events for", cal.Url, "between", + initialFillStart, "and", initialFillEnd) + + pageToken := "" + syncToken := "" + + for { + log.Println(" getting another page", pageToken) + events, err := rangedEventsCall(gc.srv, cal.GoogleId, initialFillStart, initialFillEnd, pageToken).Do() + if err != nil { + return "", err + } + + log.Println(" got", len(events.Items), "events, sync=", events.NextSyncToken) + if len(events.Items) == 0 { + break + } + + sendEvents(events, cal, out) + + syncToken = events.NextSyncToken + if events.NextPageToken == "" { + break + } + pageToken = events.NextPageToken + } + return syncToken, nil +} + +// Send a page of calendar.Events over a channel, as CalendarEvent structs. +func sendEvents(events *calendar.Events, cal *mongoclient.MongoCal, out chan *FindEventsMessage) { + for _, event := range events.Items { + if event.Status == "cancelled" { + log.Fatal("todo") + } + out <- &FindEventsMessage{ + Event: &CalendarEvent{ + Event: event, + CalendarUrl: cal.Url, + EventUrl: MakeEventUrl(cal.Url, event.Id), + }} + } } + +type eventWatch struct { + gc *GCalClient + cal *mongoclient.MongoCal + nextSyncToken string + nextPageToken string + modSince time.Time + out chan *FindEventsMessage +} + +func (gc *GCalClient) NewEventWatch( + cal *mongoclient.MongoCal, + modSince time.Time, + syncToken string, + out chan *FindEventsMessage, +) *eventWatch { + ew := &eventWatch{gc, cal, syncToken, "", modSince, out} + return ew +} + +// Call this when there are likely new changes to sync. +func (w *eventWatch) GetMoreEvents() error { + call := syncEventsCall(w.gc.srv, w.cal.GoogleId) + log.Println("listing events on", w.cal.GoogleId, "with") + + if w.nextPageToken != "" { + call = call.PageToken(w.nextPageToken) + log.Println(" pageToken", w.nextPageToken) + } else if w.nextSyncToken != "" { + call = call.SyncToken(w.nextSyncToken) + log.Println(" syncToken", w.nextSyncToken) + } else { + call = call.UpdatedMin((w.modSince.Format(time.RFC3339))) + log.Println(" updatedMin", w.modSince.Format(time.RFC3339)) + } + ret, err := call.Do() + if err != nil { + return err + } + w.nextSyncToken = ret.NextSyncToken + w.nextPageToken = ret.NextPageToken + log.Println(len(ret.Items), "more events received") + sendEvents(ret, w.cal, w.out) + log.Println("got nextSyncToken=", w.nextSyncToken) + log.Println("got nextPageToken=", w.nextPageToken) + return err +} + +func rangedEventsCall(srv *calendar.Service, calGoogleId string, + initialFillStart, initialFillEnd time.Time, pageToken string) *calendar.EventsListCall { + return srv.Events.List(calGoogleId). + ShowDeleted(false). + SingleEvents(true). + TimeMin(initialFillStart.Format(time.RFC3339)). + TimeMax(initialFillEnd.Format(time.RFC3339)). + MaxResults(4). + PageToken(pageToken) +} + +func syncEventsCall(srv *calendar.Service, calGoogleId string) *calendar.EventsListCall { + return srv.Events.List(calGoogleId). + ShowDeleted(true). + SingleEvents(true). + MaxResults(4) +}
--- a/calsync/gcalclient/service.go Mon Aug 19 14:42:27 2024 -0700 +++ b/calsync/gcalclient/service.go Mon Aug 19 19:19:13 2024 -0700 @@ -75,6 +75,7 @@ log.Fatalf("Unable to read client secret file: %v", err) } + // If modifying these scopes, delete your previously saved token.json. config, err := google.ConfigFromJSON(b, calendar.CalendarReadonlyScope) if err != nil { log.Fatalf("Unable to parse client secret file to config: %v", err)