diff calsync/gcalclient/gcalclient.go @ 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
line wrap: on
line diff
--- 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)
+}