mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-11-14 02:52:42 +00:00
Move timers to storage to persist them
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
8a37343127
commit
ac83a0a5e9
5 changed files with 73 additions and 49 deletions
3
main.go
3
main.go
|
@ -31,7 +31,7 @@ var (
|
||||||
config *configFile
|
config *configFile
|
||||||
configLock = new(sync.RWMutex)
|
configLock = new(sync.RWMutex)
|
||||||
|
|
||||||
store = newStorageFile()
|
store = newStorageFile(false)
|
||||||
|
|
||||||
version = "dev"
|
version = "dev"
|
||||||
)
|
)
|
||||||
|
@ -40,6 +40,7 @@ func init() {
|
||||||
for _, a := range os.Args {
|
for _, a := range os.Args {
|
||||||
if strings.HasPrefix(a, "-test.") {
|
if strings.HasPrefix(a, "-test.") {
|
||||||
// Skip initialize for test run
|
// Skip initialize for test run
|
||||||
|
store = newStorageFile(true) // Use in-mem-store for tests
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
rule.go
12
rule.go
|
@ -91,15 +91,15 @@ func (r *Rule) matches(m *irc.Message, event *string) bool {
|
||||||
|
|
||||||
func (r *Rule) setCooldown(m *irc.Message) {
|
func (r *Rule) setCooldown(m *irc.Message) {
|
||||||
if r.Cooldown != nil {
|
if r.Cooldown != nil {
|
||||||
timerStore.AddCooldown(timerTypeCooldown, "", r.MatcherID())
|
timerStore.AddCooldown(timerTypeCooldown, "", r.MatcherID(), time.Now().Add(*r.Cooldown))
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.ChannelCooldown != nil && len(m.Params) > 0 {
|
if r.ChannelCooldown != nil && len(m.Params) > 0 {
|
||||||
timerStore.AddCooldown(timerTypeCooldown, m.Params[0], r.MatcherID())
|
timerStore.AddCooldown(timerTypeCooldown, m.Params[0], r.MatcherID(), time.Now().Add(*r.ChannelCooldown))
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.UserCooldown != nil {
|
if r.UserCooldown != nil {
|
||||||
timerStore.AddCooldown(timerTypeCooldown, m.User, r.MatcherID())
|
timerStore.AddCooldown(timerTypeCooldown, m.User, r.MatcherID(), time.Now().Add(*r.UserCooldown))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ func (r *Rule) allowExecuteChannelCooldown(logger *log.Entry, m *irc.Message, ev
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !timerStore.InCooldown(timerTypeCooldown, m.Params[0], r.MatcherID(), *r.ChannelCooldown) {
|
if !timerStore.InCooldown(timerTypeCooldown, m.Params[0], r.MatcherID()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,7 +299,7 @@ func (r *Rule) allowExecuteRuleCooldown(logger *log.Entry, m *irc.Message, event
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !timerStore.InCooldown(timerTypeCooldown, "", r.MatcherID(), *r.Cooldown) {
|
if !timerStore.InCooldown(timerTypeCooldown, "", r.MatcherID()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +318,7 @@ func (r *Rule) allowExecuteUserCooldown(logger *log.Entry, m *irc.Message, event
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !timerStore.InCooldown(timerTypeCooldown, m.User, r.MatcherID(), *r.UserCooldown) {
|
if !timerStore.InCooldown(timerTypeCooldown, m.User, r.MatcherID()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ func TestAllowExecuteChannelCooldown(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cooldown
|
// Add cooldown
|
||||||
timerStore.AddCooldown(timerTypeCooldown, c1.Params[0], r.MatcherID())
|
timerStore.AddCooldown(timerTypeCooldown, c1.Params[0], r.MatcherID(), time.Now().Add(*r.ChannelCooldown))
|
||||||
|
|
||||||
if r.allowExecuteChannelCooldown(testLogger, c1, nil, badgeCollection{}) {
|
if r.allowExecuteChannelCooldown(testLogger, c1, nil, badgeCollection{}) {
|
||||||
t.Error("Call after cooldown added was allowed")
|
t.Error("Call after cooldown added was allowed")
|
||||||
|
@ -189,7 +189,7 @@ func TestAllowExecuteRuleCooldown(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cooldown
|
// Add cooldown
|
||||||
timerStore.AddCooldown(timerTypeCooldown, "", r.MatcherID())
|
timerStore.AddCooldown(timerTypeCooldown, "", r.MatcherID(), time.Now().Add(*r.Cooldown))
|
||||||
|
|
||||||
if r.allowExecuteRuleCooldown(testLogger, nil, nil, badgeCollection{}) {
|
if r.allowExecuteRuleCooldown(testLogger, nil, nil, badgeCollection{}) {
|
||||||
t.Error("Call after cooldown added was allowed")
|
t.Error("Call after cooldown added was allowed")
|
||||||
|
@ -210,7 +210,7 @@ func TestAllowExecuteUserCooldown(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cooldown
|
// Add cooldown
|
||||||
timerStore.AddCooldown(timerTypeCooldown, c1.User, r.MatcherID())
|
timerStore.AddCooldown(timerTypeCooldown, c1.User, r.MatcherID(), time.Now().Add(*r.UserCooldown))
|
||||||
|
|
||||||
if r.allowExecuteUserCooldown(testLogger, c1, nil, badgeCollection{}) {
|
if r.allowExecuteUserCooldown(testLogger, c1, nil, badgeCollection{}) {
|
||||||
t.Error("Call after cooldown added was allowed")
|
t.Error("Call after cooldown added was allowed")
|
||||||
|
|
54
store.go
54
store.go
|
@ -5,21 +5,26 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type storageFile struct {
|
type storageFile struct {
|
||||||
Counters map[string]int64 `json:"counters"`
|
Counters map[string]int64 `json:"counters"`
|
||||||
|
Timers map[string]timerEntry `json:"timers"`
|
||||||
|
|
||||||
lock *sync.RWMutex
|
inMem bool
|
||||||
|
lock *sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStorageFile() *storageFile {
|
func newStorageFile(inMemStore bool) *storageFile {
|
||||||
return &storageFile{
|
return &storageFile{
|
||||||
Counters: map[string]int64{},
|
Counters: map[string]int64{},
|
||||||
|
Timers: map[string]timerEntry{},
|
||||||
|
|
||||||
lock: new(sync.RWMutex),
|
inMem: inMemStore,
|
||||||
|
lock: new(sync.RWMutex),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,10 +35,23 @@ func (s *storageFile) GetCounterValue(counter string) int64 {
|
||||||
return s.Counters[counter]
|
return s.Counters[counter]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *storageFile) HasTimer(id string) bool {
|
||||||
|
s.lock.RLock()
|
||||||
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
return s.Timers[id].Time.After(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *storageFile) Load() error {
|
func (s *storageFile) Load() error {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
if s.inMem {
|
||||||
|
// In-Memory store is active, do not load from disk
|
||||||
|
// for testing purposes only!
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
f, err := os.Open(cfg.StorageFile)
|
f, err := os.Open(cfg.StorageFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
@ -60,6 +78,25 @@ func (s *storageFile) Save() error {
|
||||||
// NOTE(kahlers): DO NOT LOCK THIS, all calling functions are
|
// NOTE(kahlers): DO NOT LOCK THIS, all calling functions are
|
||||||
// modifying functions and must have locks in place
|
// modifying functions and must have locks in place
|
||||||
|
|
||||||
|
if s.inMem {
|
||||||
|
// In-Memory store is active, do not store to disk
|
||||||
|
// for testing purposes only!
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup timers
|
||||||
|
var timerIDs []string
|
||||||
|
for id := range s.Timers {
|
||||||
|
timerIDs = append(timerIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range timerIDs {
|
||||||
|
if s.Timers[i].Time.Before(time.Now()) {
|
||||||
|
delete(s.Timers, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write store to disk
|
||||||
f, err := os.Create(cfg.StorageFile)
|
f, err := os.Create(cfg.StorageFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "create storage file")
|
return errors.Wrap(err, "create storage file")
|
||||||
|
@ -75,6 +112,15 @@ func (s *storageFile) Save() error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *storageFile) SetTimer(kind timerType, id string, expiry time.Time) error {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.Timers[id] = timerEntry{Kind: kind, Time: expiry}
|
||||||
|
|
||||||
|
return errors.Wrap(s.Save(), "saving store")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *storageFile) UpdateCounter(counter string, value int64, absolute bool) error {
|
func (s *storageFile) UpdateCounter(counter string, value int64, absolute bool) error {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
47
timers.go
47
timers.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,33 +17,27 @@ const (
|
||||||
var timerStore = newTimer()
|
var timerStore = newTimer()
|
||||||
|
|
||||||
type timerEntry struct {
|
type timerEntry struct {
|
||||||
kind timerType
|
Kind timerType `json:"kind"`
|
||||||
time time.Time
|
Time time.Time `json:"time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type timer struct {
|
type timer struct{}
|
||||||
timers map[string]timerEntry
|
|
||||||
lock *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTimer() *timer {
|
func newTimer() *timer {
|
||||||
return &timer{
|
return &timer{}
|
||||||
timers: map[string]timerEntry{},
|
|
||||||
lock: new(sync.RWMutex),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cooldown timer
|
// Cooldown timer
|
||||||
|
|
||||||
func (t *timer) AddCooldown(tt timerType, limiter, ruleID string) {
|
func (t *timer) AddCooldown(tt timerType, limiter, ruleID string, expiry time.Time) {
|
||||||
t.add(timerTypeCooldown, t.getCooldownTimerKey(tt, limiter, ruleID))
|
store.SetTimer(timerTypeCooldown, t.getCooldownTimerKey(tt, limiter, ruleID), expiry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *timer) InCooldown(tt timerType, limiter, ruleID string, cooldown time.Duration) bool {
|
func (t *timer) InCooldown(tt timerType, limiter, ruleID string) bool {
|
||||||
return t.has(t.getCooldownTimerKey(tt, limiter, ruleID), cooldown)
|
return store.HasTimer(t.getCooldownTimerKey(tt, limiter, ruleID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t timer) getCooldownTimerKey(tt timerType, limiter, ruleID string) string {
|
func (timer) getCooldownTimerKey(tt timerType, limiter, ruleID string) string {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
fmt.Fprintf(h, "%d:%s:%s", tt, limiter, ruleID)
|
fmt.Fprintf(h, "%d:%s:%s", tt, limiter, ruleID)
|
||||||
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||||
|
@ -53,31 +46,15 @@ func (t timer) getCooldownTimerKey(tt timerType, limiter, ruleID string) string
|
||||||
// Permit timer
|
// Permit timer
|
||||||
|
|
||||||
func (t *timer) AddPermit(channel, username string) {
|
func (t *timer) AddPermit(channel, username string) {
|
||||||
t.add(timerTypePermit, t.getPermitTimerKey(channel, username))
|
store.SetTimer(timerTypePermit, t.getPermitTimerKey(channel, username), time.Now().Add(config.PermitTimeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *timer) HasPermit(channel, username string) bool {
|
func (t *timer) HasPermit(channel, username string) bool {
|
||||||
return t.has(t.getPermitTimerKey(channel, username), config.PermitTimeout)
|
return store.HasTimer(t.getPermitTimerKey(channel, username))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t timer) getPermitTimerKey(channel, username string) string {
|
func (timer) getPermitTimerKey(channel, username string) string {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
fmt.Fprintf(h, "%d:%s:%s", timerTypePermit, channel, strings.ToLower(strings.TrimLeft(username, "@")))
|
fmt.Fprintf(h, "%d:%s:%s", timerTypePermit, channel, strings.ToLower(strings.TrimLeft(username, "@")))
|
||||||
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
return fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic
|
|
||||||
|
|
||||||
func (t *timer) add(kind timerType, id string) {
|
|
||||||
t.lock.Lock()
|
|
||||||
defer t.lock.Unlock()
|
|
||||||
|
|
||||||
t.timers[id] = timerEntry{kind: kind, time: time.Now()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *timer) has(id string, validity time.Duration) bool {
|
|
||||||
t.lock.RLock()
|
|
||||||
defer t.lock.RUnlock()
|
|
||||||
|
|
||||||
return time.Since(t.timers[id].time) < validity
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue