view calsync/gcalclient/gcalclient.go @ 57:24f662799710

WIP incremental sync now runs
author drewp@bigasterisk.com
date Thu, 05 Sep 2024 15:03:05 -0700
parents 635ff76f867c
children 6c7151126a0b
line wrap: on
line source

package gcalclient

/*
Note: this module keeps gcal *paging* private (we fetch all the pages), but
*sync* is public. At least, callers may receive a syncToken and have to pass it
back in.
*/
import (
	"context"
	"crypto/md5"
	"fmt"
	"log"
	"net/url"
	"strings"
	"time"

	"bigasterisk.com/go/gcalendarwatch/mongoclient"
	"google.golang.org/api/calendar/v3"
)

const urlBase = "http://bigasterisk.com/calendar/"

type GCalClient struct {
	ctx context.Context
	srv *calendar.Service
}

// Same as calendar.Event, but includes our urls
type CalendarEvent struct {
	*calendar.Event
	CalendarUrl string
	EventUrl    string
}

func MakeCalUrl(calId string) string {
	return urlBase + url.QueryEscape(calId)
}

func MakeEventUrl(calUrl string, evId string) string {
	return calUrl + "/" + url.QueryEscape(evId)
}

func New(ctx context.Context) (*GCalClient, error) {
	err, srv := newService(ctx)
	if err != nil {
		log.Fatalf("Unable to retrieve Calendar client: %v", err)
	}
	return &GCalClient{ctx, srv}, nil
}

func (gc *GCalClient) Close() {
	// todo: disconnect watches if possible
}

func (gc *GCalClient) AllCalendars() ([]*calendar.CalendarListEntry, error) {
	// todo: pagination
	list, err := gc.srv.CalendarList.List().MaxResults( /*maxResults*/ 100).Do()
	if err != nil {
		return nil, err
	}

	// do not submit
	debugFilterCals(list)

	return list.Items, nil
}

func debugFilterCals(list *calendar.CalendarList) {
	log.Println("filtering cal list")
	ret := make([]*calendar.CalendarListEntry, 0)
	for _, cal := range list.Items {
		if strings.Contains(cal.Id, "drewp") || strings.Contains(cal.Id, "east") {
			ret = append(ret, cal)
		}
	}
	list.Items = ret
}


func shortDebugHash(pageToken string) string {
	if pageToken == "" {
		return "(empty)"
	}
	return fmt.Sprintf("%x", md5.Sum([]byte(pageToken)))
}

func (gc *GCalClient) ListEventsInRange(cal mongoclient.MongoCal, t1, t2 time.Time) (
	events []CalendarEvent, nextSyncToken string, err error) {
	mongoclient.LogWithCal(cal, "ListEventsInRange", t1, "to", t2)

	call := func(pageToken string) *calendar.EventsListCall {
		return rangedEventsCall(gc.srv, cal.GoogleId, t1, t2, pageToken)
	}
	events, nextSyncToken, err = readEventsPages(cal, call)
	if err != nil {
		return nil, "", err
	}

	return events, nextSyncToken, nil
}

func (gc *GCalClient) ListEventUpdates(cal mongoclient.MongoCal, syncToken string) (
	events []CalendarEvent, nextSyncToken string, err error) {
	mongoclient.LogWithCal(cal, "ListEventUpdates", syncToken)

	call := func(pageToken string) *calendar.EventsListCall {
		return syncEventsCall(gc.srv, cal.GoogleId, syncToken, pageToken)
	}
	events, nextSyncToken, err = readEventsPages(cal, call)
	if err != nil {
		return nil, "", err
	}

	return events, nextSyncToken, nil
}

func readEventsPages(cal mongoclient.MongoCal, call func(string) *calendar.EventsListCall) (
	events []CalendarEvent, syncToken string, err error) {

	events = make([]CalendarEvent, 0)
	err = nil

	pageToken := ""
	for {
		mongoclient.LogWithCal(cal, "getting another page", shortDebugHash(pageToken))
		pageResult, err2 := call(pageToken).Do()
		if err2 != nil {
			log.Fatal(err2)
			return nil, "", err2
		}

		// Placement is important! This must run even if the result set is empty.
		syncToken = pageResult.NextSyncToken

		mongoclient.LogWithCal(cal, "got page with", len(pageResult.Items), "events")
		if len(pageResult.Items) == 0 {
			break
		}

		for _, ev := range pageResult.Items {
			if ev.Status == "cancelled" {
				log.Fatal("todo")
			}
			events = append(events, CalendarEvent{
				Event:       ev,
				CalendarUrl: cal.Url,
				EventUrl:    MakeEventUrl(cal.Url, ev.Id),
			})
		}



		if pageResult.NextPageToken == "" {
			break
		}
		pageToken = pageResult.NextPageToken
	}
	mongoclient.LogWithCal(cal, "total events read: ", len(events), "with syncToken", syncToken)
	return events, syncToken, nil
}