This repository has been archived by the owner on Nov 10, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
event.go
173 lines (144 loc) · 5.64 KB
/
event.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package event
import (
"fmt"
"sync"
"time"
)
// Event stores a time window for when the event can be triggered (`from` up to `upTo`),
// how long it should take to cooldown before being able to be re-triggered, which
// action should be performed when triggered, when it was last triggered, a mutex
// and a boolean variable for keeping track of if the action is still ongoing or not.
type Event struct {
from time.Time
upTo time.Time
cooldown time.Duration // How long to cool down before retriggering
actionFunc func() // Action takes no arguments
triggered time.Time // When was the event last triggered
mutex *sync.RWMutex
ongoing bool
clockOnly bool // Is the triggering based on the clock or date?
oneTimeEvent bool
triggerCounter uint64
}
// New creates a new Event, that should happen at the given "when" time,
// within the given time window, with an associated cooldown period after the
// event has been triggered. The event can be retriggered after every cooldown,
// within the time window. Only hour/minute/second will be considered.
func New(when time.Time, window, cooldown time.Duration, action func()) *Event {
return &Event{when, when.Add(window), cooldown, action, time.Time{}, &sync.RWMutex{}, false, true, false, 0}
}
// A mutex to make sure "once" events are only run once
var onceMutex = &sync.RWMutex{}
// NewOnce creates a new one-time Event, that should happen at the given "when" time,
// within the given time window, with an associated cooldown period after the
// event has been triggered. The event can be retriggered after every cooldown,
// within the time window. Only hour/minute/second will be considered.
func NewOnce(when time.Time, window, cooldown time.Duration, action func()) *Event {
return &Event{when, when.Add(window), cooldown, action, time.Time{}, onceMutex, false, true, true, 0}
}
// NewDateEvent is like New, but the date will also be considered when triggering events.
func NewDateEvent(when time.Time, window, cooldown time.Duration, action func()) *Event {
return &Event{when, when.Add(window), cooldown, action, time.Time{}, &sync.RWMutex{}, false, false, false, 0}
}
// From is the time from when the event should be able to be triggered.
func (e *Event) From() time.Time {
if e.clockOnly {
return ToToday(e.from)
}
return e.from
}
// UpTo is the time where the event should no longer be able to be triggered.
func (e *Event) UpTo() time.Time {
if e.clockOnly {
return ToToday(e.upTo)
}
return e.upTo
}
// Cooldown is how long to wait after the event has been triggered, before
// being possible to trigger again.
func (e *Event) Cooldown() time.Duration {
return e.cooldown
}
// Duration is for how long the window that this event can be triggered is
func (e *Event) Duration() time.Duration {
if e.clockOnly {
// Choose the non-negative difference if the two timestamps crosses midnight
a := ToToday(e.upTo).Sub(ToToday(e.from))
if a >= 0 {
return a
}
return ToToday(e.upTo).Add(24 * time.Hour).Sub(ToToday(e.from))
}
return e.upTo.Sub(e.from)
}
// Between returns true if the given time t is between the two timestamps
// a (inclusive) and b (exclusive)
func Between(t, a, b time.Time) bool {
return (t.Sub(a) >= 0) && (t.Sub(b) < 0)
}
// ToToday moves the date of a given time.Time to today's date.
// The hour/minute/second is kept as it is.
func ToToday(d time.Time) time.Time {
// Get hour, minute and second from the event
hour, min, sec := d.Clock()
// Get the current time and date
now := time.Now()
// Return a new time.Time
return time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, now.Nanosecond(), now.Location())
}
// ToTomorrow moves the date of a given time.Time to today's date.
// The hour/minute/second is kept as it is.
func ToTomorrow(d time.Time) time.Time {
// Add 24 hours to today
return ToToday(d).Add(time.Hour * 24)
}
// Has checks if the Event has time t in its interval:
// from p.From() and up to but not including p.UpTo()
func (e *Event) Has(t time.Time) bool {
// If only the hour/minute/second matters, use BetweenClock
if e.clockOnly {
fromToday := ToToday(e.From())
uptoToday := ToToday(e.UpTo())
uptoTomorrow := ToTomorrow(e.UpTo())
return Between(t, fromToday, uptoToday) || Between(t, fromToday, uptoTomorrow)
}
return Between(t, e.From(), e.UpTo())
}
// ShouldTrigger returns true if the current time is in the interval
// of the event AND it is not ongoing AND it is not in the cooldown period.
func (e *Event) ShouldTrigger() (retval bool) {
t := time.Now()
// Safely read the status
e.mutex.RLock()
retval = !e.ongoing && e.Has(t) && !Between(t, e.triggered, e.triggered.Add(e.cooldown))
e.mutex.RUnlock()
return
}
// Trigger triggers this event. The trigger time is noted, the associated
// action is performed and a cooldown period is initiated with time.Sleep.
// It is expected that this function will be called as a goroutine.
func (e *Event) Trigger() {
// Safely update the status
e.mutex.Lock()
defer e.mutex.Unlock()
e.triggered = time.Now()
e.triggerCounter++
// Don't run one-time events twice!
if e.oneTimeEvent && (e.ongoing || e.triggerCounter > 1) {
return
}
e.ongoing = true
e.mutex.Unlock()
// Perform the action
e.actionFunc()
e.mutex.Lock()
// If there is time left, sleep some
passed := time.Now().Sub(e.triggered)
time.Sleep(e.cooldown - passed)
// Safely update the status
e.ongoing = false
}
// String returns a string with information about this event
func (e *Event) String() string {
return fmt.Sprintf("Event from %s up to %s. Cooldown: %v. Should trigger: %v", e.from.Format("15:04:05"), e.upTo.Format("15:04:05"), e.cooldown, e.ShouldTrigger())
}