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() }