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 } }