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>
188 lines
3.9 KiB
Go
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(),
|
|
}}
|
|
}
|