2024-02-28 20:26:20 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/gob"
|
|
|
|
"errors"
|
|
|
|
"io/fs"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
|
2024-03-04 20:11:34 +00:00
|
|
|
"git.sr.ht/~michalr/go-satel"
|
2024-02-28 20:26:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
2024-03-10 15:01:25 +00:00
|
|
|
allowedPartitions []int
|
2024-02-28 20:26:20 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-10 15:01:25 +00:00
|
|
|
func MakeDataStore(logger *log.Logger, persistenceFilePath string, allowedPartitions []int) DataStore {
|
2024-02-28 20:26:20 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-03-10 15:01:25 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-28 20:26:20 +00:00
|
|
|
func (self *DataStore) SetSystemState(key EventKey, value EventValue) {
|
|
|
|
self.mtx.Lock()
|
|
|
|
self.lastSeen[key] = value
|
|
|
|
self.saveSystemState()
|
|
|
|
self.mtx.Unlock()
|
|
|
|
}
|