-
Notifications
You must be signed in to change notification settings - Fork 0
/
watcher.go
121 lines (102 loc) · 2.83 KB
/
watcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package watch
import (
"log"
"sync"
"time"
"github.com/maxim-nazarenko/nextshop-item-watcher/next"
"github.com/maxim-nazarenko/nextshop-item-watcher/next/shop"
"github.com/robfig/cron/v3"
)
// Watcher interface to be implemented by different watchers
type Watcher interface {
AddItem(*shop.Item) error
InStockChan() <-chan shop.Item
RemoveItem(shop.Item)
Start() error
Stop()
}
// ItemWatcher holds information about items to watch after
type ItemWatcher struct {
Client *next.Client
UpdateInterval time.Duration
cron *cron.Cron
items []*shop.Item
itemsLock sync.Locker
inStockChan chan shop.Item
}
// Start begins watcher's loop of checks
func (w *ItemWatcher) Start() error {
w.cron.Start()
return nil
}
// Stop terminates periodic checking
func (w *ItemWatcher) Stop() {
log.Println("[INFO] Stopping web watcher")
defer close(w.inStockChan)
w.cron.Stop()
}
// Run satisfies cron.Job interface
func (w *ItemWatcher) Run() {
w.onTimer()
}
func (w *ItemWatcher) onTimer() {
log.Println("ItemWatcher timer fired")
for _, item := range w.items {
go func(item shop.Item) {
shopItemInfo, err := w.Client.GetItemOption(item.Article, item.SizeID)
if err != nil {
log.Println("[ERROR] + " + err.Error())
return
}
w.processInStockItems(shopItemInfo)
}(*item)
}
}
// AddItem add given item to the list of watched items
func (w *ItemWatcher) AddItem(item *shop.Item) error {
w.items = append(w.items, item)
return nil
}
// RemoveItem removes watching item from the list so it will not be processed next time when cron fires
func (w *ItemWatcher) RemoveItem(item shop.Item) {
log.Printf("[DEBUG] watcher: removing item %v\n", item)
w.itemsLock.Lock()
defer w.itemsLock.Unlock()
for index, it := range w.items {
if item.Article == it.Article && item.SizeID == it.SizeID {
w.items = append(w.items[:index], w.items[index+1:]...)
return
}
}
}
// InStockChan returns channel where InStock items will appear
func (w ItemWatcher) InStockChan() <-chan shop.Item {
return w.inStockChan
}
func (w *ItemWatcher) processInStockItems(items ...shop.ItemOption) {
w.itemsLock.Lock()
defer w.itemsLock.Unlock()
for _, item := range items {
if item.StockStatusString != shop.ItemStatusInStock {
continue
}
w.inStockChan <- shop.Item{Article: item.Article, SizeID: item.Number}
}
}
// New constructs new ItemWatcher instance
func New(client *next.Client, config *Config) (*ItemWatcher, error) {
// TODO: add TZ support
watcher := ItemWatcher{
Client: client,
UpdateInterval: config.UpdateInterval,
cron: cron.New(),
itemsLock: &sync.Mutex{},
}
watcher.inStockChan = make(chan shop.Item, 20)
interval := "@every " + watcher.UpdateInterval.String()
_, err := watcher.cron.AddJob(interval, &watcher)
if err != nil {
return nil, err
}
return &watcher, nil
}