package main import ( "encoding/gob" "errors" "fmt" "log" "os" "time" ) type DiskStorage struct { stateFile string lastSave int64 LastSaves map[CartId]int64 } func NewDiskStorage(stateFile string) (*DiskStorage, error) { ret := &DiskStorage{ stateFile: stateFile, LastSaves: make(map[CartId]int64), } err := ret.loadState() return ret, err } func saveMessages(messages []StorableMessage, id CartId) error { if len(messages) == 0 { return nil } log.Printf("%d messages to save for grain id %s", len(messages), id) var file *os.File var err error path := getCartPath(id.String()) file, err = os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } defer file.Close() for _, m := range messages { err := m.Write(file) if err != nil { return err } } return err } func getCartPath(id string) string { return fmt.Sprintf("data/%s.prot", id) } func loadMessages(grain Grain, id CartId) error { var err error path := getCartPath(id.String()) if _, err = os.Stat(path); errors.Is(err, os.ErrNotExist) { return nil } file, err := os.Open(path) if err != nil { return err } defer file.Close() for err == nil { var msg Message err = ReadMessage(file, &msg) if err == nil { grain.HandleMessage(&msg, true) } } if err.Error() == "EOF" { return nil } return err } func (s *DiskStorage) saveState() error { tmpFile := s.stateFile + "_tmp" file, err := os.Create(tmpFile) if err != nil { return err } defer file.Close() err = gob.NewEncoder(file).Encode(s.LastSaves) if err != nil { return err } os.Remove(s.stateFile + ".bak") os.Rename(s.stateFile, s.stateFile+".bak") return os.Rename(tmpFile, s.stateFile) } func (s *DiskStorage) loadState() error { file, err := os.Open(s.stateFile) if err != nil { return err } defer file.Close() return gob.NewDecoder(file).Decode(&s.LastSaves) } func (s *DiskStorage) Store(id CartId, grain *CartGrain) error { lastSavedMessage, ok := s.LastSaves[id] if ok && lastSavedMessage > grain.GetLastChange() { return nil } err := saveMessages(grain.GetStorageMessage(lastSavedMessage), id) if err != nil { return err } ts := time.Now().Unix() s.LastSaves[id] = ts s.lastSave = ts return nil }