1
0
Fork 0
hswro-alarm-bot/data_store.go

166 lines
4.0 KiB
Go

package main
import (
"encoding/gob"
"errors"
"io/fs"
"log"
"os"
"sync"
"git.sr.ht/~michalr/go-satel"
)
var EXPECTED_PERSISTENCE_FILE_MAGIC = [...]byte{'H', 'S', 'W', 'R', 'O', 'A', 'L', 'A', 'R', 'M', 'B', 'O', 'T'}
const EXPECTED_PERSISTENCE_FILE_VERSION = 0
type EventKey struct {
ChangeType satel.ChangeType
Index int
}
type EventValue struct{ Value bool }
type LastSeenRecord struct {
Key EventKey
Value EventValue
}
type PersistenceData struct {
Magic [len(EXPECTED_PERSISTENCE_FILE_MAGIC)]byte
FileVersion uint32
LastSeen []LastSeenRecord
}
type DataStore struct {
mtx sync.Mutex
logger *log.Logger
persistenceFilePath string
allowedPartitions []int
lastSeen map[EventKey]EventValue
}
func loadSystemState(logger *log.Logger, persistenceFilePath string) map[EventKey]EventValue {
lastSeen := make(map[EventKey]EventValue)
f, err := os.OpenFile(persistenceFilePath, os.O_RDONLY|os.O_CREATE, 0600)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
// File not existing is fine, we'll create one later
return lastSeen
}
panic(err)
}
defer f.Close()
dec := gob.NewDecoder(f)
data := PersistenceData{}
err = dec.Decode(&data)
if err != nil {
logger.Println("Error reading persistence file", persistenceFilePath, "from disk:", err, ". Discarding and starting over.")
return lastSeen
}
if data.Magic != EXPECTED_PERSISTENCE_FILE_MAGIC {
logger.Println("Error reading persistence file", persistenceFilePath, "from disk: Wrong magic string. Discarding and starting over.")
return lastSeen
}
if data.FileVersion != EXPECTED_PERSISTENCE_FILE_VERSION {
logger.Println("Error reading persistence file", persistenceFilePath, "from disk: Wrong version: expected ",
EXPECTED_PERSISTENCE_FILE_VERSION, ", got ", data.FileVersion, ". Discarding and starting over.")
return lastSeen
}
for _, readData := range data.LastSeen {
lastSeen[readData.Key] = readData.Value
}
return lastSeen
}
func (self *DataStore) saveSystemState() {
f, err := os.OpenFile(self.persistenceFilePath, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
panic(err)
}
defer f.Close()
enc := gob.NewEncoder(f)
data := PersistenceData{
Magic: EXPECTED_PERSISTENCE_FILE_MAGIC,
FileVersion: EXPECTED_PERSISTENCE_FILE_VERSION,
LastSeen: make([]LastSeenRecord, len(self.lastSeen)),
}
i := 0
for k, v := range self.lastSeen {
data.LastSeen[i] = LastSeenRecord{Key: k, Value: v}
i += 1
}
err = enc.Encode(data)
if err != nil {
panic(err)
}
}
func MakeDataStore(logger *log.Logger, persistenceFilePath string, allowedPartitions []int) DataStore {
return DataStore{
logger: logger,
persistenceFilePath: persistenceFilePath,
lastSeen: loadSystemState(logger, persistenceFilePath),
}
}
func (self *DataStore) GetSystemState() map[EventKey]EventValue {
self.mtx.Lock()
defer self.mtx.Unlock()
copiedMap := make(map[EventKey]EventValue)
for key, value := range self.lastSeen {
copiedMap[key] = value
}
return copiedMap
}
func (self *DataStore) isMessageAllowed(key EventKey, allowedType satel.ChangeType) bool {
if key.ChangeType != allowedType {
return false
}
if len(self.allowedPartitions) == 0 {
// all partitions are allowed
return true
}
for _, allowedIndex := range self.allowedPartitions {
if key.Index == allowedIndex {
return true
}
}
return false
}
func (self *DataStore) GetGenericMessageOfAllowedPartitions(allowedType satel.ChangeType) GenericMessage {
self.mtx.Lock()
defer self.mtx.Unlock()
messages := make([]satel.BasicEventElement, 0)
for key, value := range self.lastSeen {
if self.isMessageAllowed(key, allowedType) {
messages = append(messages, satel.BasicEventElement{
Type: key.ChangeType,
Index: key.Index,
Value: value.Value,
})
}
}
return GenericMessage{
ChatIds: EmptyChatId{},
Messages: messages,
}
}
func (self *DataStore) SetSystemState(key EventKey, value EventValue) {
self.mtx.Lock()
self.lastSeen[key] = value
self.saveSystemState()
self.mtx.Unlock()
}