watch.go 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  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/epic"
  8. )
  9. // Notifier is called for every game that newly became free.
  10. type Notifier interface {
  11. Notify(games []epic.Game)
  12. }
  13. // NotifierFunc adapts a function to Notifier.
  14. type NotifierFunc func(games []epic.Game)
  15. func (f NotifierFunc) Notify(games []epic.Game) { f(games) }
  16. // Watcher polls the client and reports games that were not seen before.
  17. type Watcher struct {
  18. Client *epic.Client
  19. Interval time.Duration
  20. seen map[string]bool
  21. }
  22. // NewWatcher creates a watcher. Interval defaults to 15m.
  23. func NewWatcher(c *epic.Client, interval time.Duration) *Watcher {
  24. if interval <= 0 {
  25. interval = 15 * time.Minute
  26. }
  27. return &Watcher{Client: c, Interval: interval, seen: map[string]bool{}}
  28. }
  29. // Run polls until ctx is done. On the first pass it reports all current free
  30. // games as "new" unless baselineOnly is true (useful to seed the seen-set
  31. // 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. games, err := w.Client.GetFreeGames(ctx)
  38. if err != nil {
  39. return err
  40. }
  41. var fresh []epic.Game
  42. for _, g := range games {
  43. if !w.seen[g.ID] {
  44. fresh = append(fresh, g)
  45. }
  46. }
  47. for _, g := range games {
  48. w.seen[g.ID] = true
  49. }
  50. if len(fresh) > 0 && !(first && baselineOnly) {
  51. n.Notify(fresh)
  52. }
  53. if first {
  54. log.Printf("watcher: baseline set with %d current free games, polling every %s", len(games), w.Interval)
  55. first = false
  56. }
  57. select {
  58. case <-ctx.Done():
  59. return ctx.Err()
  60. case <-t.C:
  61. }
  62. }
  63. }