watch.go 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. // Package watch implements waiting/polling for new free game offers.
  2. package watch
  3. import (
  4. "context"
  5. "log"
  6. "time"
  7. "gogs.netdome.biz/paul/cheapo/internal/game"
  8. )
  9. // Notifier is called for every game that newly became free.
  10. type Notifier interface {
  11. Notify(games []game.Game)
  12. }
  13. // NotifierFunc adapts a function to Notifier.
  14. type NotifierFunc func(games []game.Game)
  15. func (f NotifierFunc) Notify(games []game.Game) { f(games) }
  16. // Watcher polls providers and reports games that were not seen before.
  17. type Watcher struct {
  18. Providers []game.Provider
  19. Interval time.Duration
  20. seen map[string]bool
  21. }
  22. // NewWatcher creates a watcher. Interval defaults to 15m.
  23. func NewWatcher(interval time.Duration, providers ...game.Provider) *Watcher {
  24. if interval <= 0 {
  25. interval = 15 * time.Minute
  26. }
  27. return &Watcher{Providers: providers, Interval: interval, seen: map[string]bool{}}
  28. }
  29. // Run polls all providers until ctx is done. On the first pass it reports all
  30. // current free games as "new" unless baselineOnly is true (useful to seed the
  31. // seen-set without notifying).
  32. func (w *Watcher) Run(ctx context.Context, baselineOnly bool, n Notifier) error {
  33. t := time.NewTicker(w.Interval)
  34. defer t.Stop()
  35. first := true
  36. for {
  37. var total int
  38. for _, p := range w.Providers {
  39. games, err := p.GetFreeGames(ctx)
  40. if err != nil {
  41. // One flaky provider shall not kill the watcher.
  42. log.Printf("watcher: %s: %v", p.Name(), err)
  43. continue
  44. }
  45. total += len(games)
  46. var fresh []game.Game
  47. for _, g := range games {
  48. if !w.seen[g.ID] {
  49. fresh = append(fresh, g)
  50. }
  51. w.seen[g.ID] = true
  52. }
  53. if len(fresh) > 0 && !(first && baselineOnly) {
  54. n.Notify(fresh)
  55. }
  56. }
  57. if first {
  58. log.Printf("watcher: baseline set with %d current free games across %d providers, polling every %s",
  59. total, len(w.Providers), w.Interval)
  60. first = false
  61. }
  62. select {
  63. case <-ctx.Done():
  64. return ctx.Err()
  65. case <-t.C:
  66. }
  67. }
  68. }