dyzur-bot/sync.go

60 lines
1.5 KiB
Go

package dyzurbot
import (
"context"
"log"
"time"
)
// FetchFunc retrieves and parses the current rota from upstream (the Sheet).
type FetchFunc func(ctx context.Context) (Rota, error)
// Syncer refreshes a Store from an upstream fetch.
type Syncer struct {
store *Store
fetch FetchFunc
now func() time.Time
}
// NewSyncer wires a Store to a fetch function.
func NewSyncer(store *Store, fetch FetchFunc) *Syncer {
return &Syncer{store: store, fetch: fetch, now: time.Now}
}
// Sync fetches once. On success it replaces the cache; on failure it logs a
// warning, leaves the last good cache in place, and returns the error so a
// caller (e.g. a manual /sync command) can report it.
func (sy *Syncer) Sync(ctx context.Context) error {
rota, err := sy.fetch(ctx)
if err != nil {
log.Printf("sync: fetch failed, keeping cached data: %v", err)
return err
}
return sy.store.Replace(rota, sy.now())
}
// Run polls Sync on the given interval until ctx is canceled.
func (sy *Syncer) Run(ctx context.Context, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
_ = sy.Sync(ctx)
}
}
}
// SheetFetcher builds a FetchFunc that reads the rota from Google Sheets.
func SheetFetcher(spreadsheetID, readRange string) FetchFunc {
return func(ctx context.Context) (Rota, error) {
rows, err := ReadSheet(ctx, spreadsheetID, readRange)
if err != nil {
return Rota{}, err
}
return ParseRota(rows), nil
}
}