Files
aego/save/store.go
T

157 lines
3.3 KiB
Go

package save
import (
"errors"
"io/fs"
"path/filepath"
"sort"
"strings"
"time"
"aego/core/errs"
"aego/core/log"
"aego/platform"
)
type SlotInfo struct {
Slot string
Path string
Size int64
Mod time.Time
Meta Meta
Preview string
Err error
}
type Store struct {
dir string
files platform.UserFS
}
func NewStore(dir string) *Store {
return NewStoreFS(dir, platform.UserFiles())
}
func NewStoreFS(dir string, files platform.UserFS) *Store {
return &Store{dir: dir, files: files}
}
func ValidSlot(name string) bool {
if name == "" || len(name) > 64 {
return false
}
for i := 0; i < len(name); i++ {
c := name[i]
switch {
case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9', c == '-', c == '_':
default:
return false
}
}
return true
}
func (s *Store) Dir() string {
return s.dir
}
func (s *Store) Path(slot string) (string, error) {
if !ValidSlot(slot) {
return "", errs.New(errs.PlatformConfig, "slot name may use letters, digits, - and _", log.F("slot", slot))
}
return filepath.Join(s.dir, slot+Ext), nil
}
func previewOf(path string) string {
return strings.TrimSuffix(path, Ext) + PreviewExt
}
func (s *Store) Write(slot string, data, preview []byte) error {
path, err := s.Path(slot)
if err != nil {
return err
}
if err := s.files.WriteFile(path, data); err != nil {
return errs.Wrap(err, errs.VFSIO, "cannot write the save").With(log.F("slot", slot))
}
png := previewOf(path)
if len(preview) == 0 {
s.files.Remove(png)
return nil
}
if err := s.files.WriteFile(png, preview); err != nil {
return errs.Wrap(err, errs.VFSIO, "cannot write the save preview").With(log.F("slot", slot))
}
return nil
}
func (s *Store) Read(slot string) ([]byte, error) {
path, err := s.Path(slot)
if err != nil {
return nil, err
}
data, err := s.files.ReadFile(path)
if err != nil {
return nil, errs.Wrap(err, errs.VFSIO, "cannot read the save").With(log.F("slot", slot))
}
return data, nil
}
func (s *Store) Load(slot string) (*Doc, error) {
data, err := s.Read(slot)
if err != nil {
return nil, err
}
return Decode(data, slot+Ext)
}
func (s *Store) Exists(slot string) bool {
path, err := s.Path(slot)
if err != nil {
return false
}
_, err = s.files.Stat(path)
return err == nil
}
func (s *Store) Delete(slot string) error {
path, err := s.Path(slot)
if err != nil {
return err
}
if err := s.files.Remove(path); err != nil && !errors.Is(err, fs.ErrNotExist) {
return errs.Wrap(err, errs.VFSIO, "cannot delete the save").With(log.F("slot", slot))
}
s.files.Remove(previewOf(path))
return nil
}
func (s *Store) List() []SlotInfo {
entries, err := s.files.List(s.dir)
if err != nil {
return nil
}
var out []SlotInfo
for _, e := range entries {
if e.Dir || !strings.HasSuffix(e.Name, Ext) {
continue
}
slot := strings.TrimSuffix(e.Name, Ext)
path := filepath.Join(s.dir, e.Name)
info := SlotInfo{Slot: slot, Path: path, Size: e.Size, Mod: e.Mod}
if data, err := s.files.ReadFile(path); err != nil {
info.Err = err
} else if d, err := Decode(data, e.Name); err != nil {
info.Err = err
} else {
info.Meta = d.Meta
}
if _, err := s.files.Stat(previewOf(path)); err == nil {
info.Preview = previewOf(path)
}
out = append(out, info)
}
sort.Slice(out, func(i, j int) bool { return out[i].Mod.After(out[j].Mod) })
return out
}