bingobot/internal/state/events.go
Ava Affine 2515d396a0 Event replay and cleanup
This commit introduces the ability to reconfigure modules/event subscribers by
replaying previously stored events at startup. This way modules/subscribers can
always come up with the latest activity and data that was present at close at the
last run of the program. This process also includes the disposal of unneeded events
to minimize disk use over time. The changes include the following:

1. Event interface is extended with the Disposable() function
   unimplemented stubs for existing interface implementations
2. state.Init() is split into Init() and Start()
   Init() now initialized the file used by eventStream, and puts in place a 0 size
   memory cache as a temporary measure on top of the file.
   Start() reads Pop()s documents one by one from the temp nil-cache eventStream,
   it attempts to dispose of each event and if the event is not disposable it is
   pushed onto a second temporary no-memory-cache buffer which is then reverse ordered.
   The underlying file is truncated and reopened.
   Finally, the real eventStream is allocated, with in memory cache. All events in the
   second buffer are published (sent to subscribers as well as added to the new eventStream).
3. Updates to main() to support Init() vs Start()

Signed-off-by: Ava Affine <ava@sunnypup.io>
2024-12-06 13:00:50 -08:00

188 lines
3.9 KiB
Go

package state
import (
"errors"
"time"
"fmt"
"gitlab.com/whom/bingobot/internal/logging"
)
type VoteEvent map[string]string
const (
VoteMissingKeyError = "vote data not found: "
VoteCreatedKey = "created"
VoteRequesterKey = "requester"
VoteActionKey = "action"
VoteResultKey = "result"
VoteResultPass = "pass"
VoteResultFail = "fail"
VoteResultTie = "tie"
VoteResultTimeout = "timeout"
VoteBadResultError = "vote has invalid result: "
VoteNotFinishedError = "vote has result but isnt finished"
VoteMissingResultError = "vote finished but missing result"
VoteStatusKey = "status"
VoteStatusInProgress = "in_progress"
VoteStatusFinalized = "finalized"
VoteStatusTimeout = "timed_out"
VoteBadStatusError = "vote has invalid status: "
VeryOldVote = "1990-01-01T00:00:00Z"
)
func (ve VoteEvent) Type() EventType {
return Vote
}
func (ve VoteEvent) Time() time.Time {
t, e := time.Parse(time.RFC3339, ve[VoteCreatedKey])
if e != nil {
// we have a corrupted event
// return old time so that this event gets
// pruned from cache
logging.Warn(
"pruning corrupted vote event",
"event",
fmt.Sprintf("%+v", ve),
)
tooOld, _ := time.Parse(
time.RFC3339,
VeryOldVote,
)
return tooOld
}
return t
}
func (ve VoteEvent) Data() map[string]string {
return map[string]string(ve)
}
func (ve VoteEvent) Validate() error {
// make sure action, requester, and created are set
for _, key := range []string{
VoteActionKey,
VoteRequesterKey,
VoteCreatedKey,
VoteStatusKey,
} {
if _, found := ve[key]; !found {
return errors.New(VoteMissingKeyError + key)
}
}
status := ve[VoteStatusKey]
if status != VoteStatusTimeout &&
status != VoteStatusInProgress &&
status != VoteStatusFinalized {
return errors.New(VoteBadStatusError + status)
}
result, hasResult := ve[VoteResultKey]
if hasResult && status == VoteStatusInProgress {
return errors.New(VoteNotFinishedError)
}
if status != VoteStatusInProgress && !hasResult {
return errors.New(VoteMissingResultError)
}
if hasResult &&
(result != VoteResultPass &&
result != VoteResultFail &&
result != VoteResultTie &&
result != VoteResultTimeout) {
return errors.New(VoteBadResultError + result)
}
return nil
}
func (ve VoteEvent) Disposable() bool {
// will be implemented with democracy module
logging.Warn("unimplemented: VoteEvent::Disposable")
return false
}
type UserEvent struct {
uid string
created time.Time
}
const (
UserEventUserKey = "user"
UserEventCreatedKey = "created"
UserEventBadUserError = "event has bad user"
)
func (ue UserEvent) Time() time.Time {
return ue.created
}
func (ue UserEvent) Data() map[string]string {
return map[string]string{
UserEventUserKey: ue.uid,
UserEventCreatedKey: ue.created.Format(time.RFC3339),
}
}
func (ue UserEvent) Validate() error {
// empty for now, we may do some validation later.
return nil
}
func (ue UserEvent) Disposable() bool {
// when I make a module for challenges, restorations, and UserActives
// then I should implement this
logging.Warn("unimplemented: UserEvent::Disposable")
return false
}
type ChallengeEvent struct {
UserEvent
}
func (ce ChallengeEvent) Type() EventType {
return Challenge
}
func NewChallengeEvent(user string) ChallengeEvent {
return ChallengeEvent{UserEvent{
uid: user,
created: time.Now(),
}}
}
type RestorationEvent struct {
UserEvent
}
func (re RestorationEvent) Type() EventType {
return Restoration
}
func NewRestorationEvent(user string) RestorationEvent {
return RestorationEvent{UserEvent{
uid: user,
created: time.Now(),
}}
}
type UserActiveEvent struct {
UserEvent
}
func (ua UserActiveEvent) Type() EventType {
return UserActive
}
func NewUserActiveEvent(user string) UserActiveEvent {
return UserActiveEvent{UserEvent{
uid: user,
created: time.Now(),
}}
}