GoDoxy/internal/jsonstore/jsonstore.go
2025-04-25 08:25:37 +08:00

148 lines
3.1 KiB
Go

package jsonstore
import (
"encoding/json"
"fmt"
"path/filepath"
"reflect"
"sync"
"maps"
"github.com/puzpuzpuz/xsync/v3"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/utils"
)
type namespace string
type MapStore[VT any] struct {
*xsync.MapOf[string, VT]
}
type ObjectStore[Pointer Initializer] struct {
ptr Pointer
}
type Initializer interface {
Initialize()
}
type storeByNamespace struct {
sync.RWMutex
m map[namespace]store
}
type store interface {
json.Marshaler
json.Unmarshaler
}
var stores = storeByNamespace{m: make(map[namespace]store)}
var storesPath = common.DataDir
func init() {
if err := load(); err != nil {
logging.Error().Err(err).Msg("failed to load stores")
}
task.OnProgramExit("save_stores", func() {
if err := save(); err != nil {
logging.Error().Err(err).Msg("failed to save stores")
}
})
}
func load() error {
stores.Lock()
defer stores.Unlock()
errs := gperr.NewBuilder("failed to load data stores")
for ns, obj := range stores.m {
if init, ok := obj.(Initializer); ok {
init.Initialize()
}
if err := utils.LoadJSONIfExist(filepath.Join(storesPath, string(ns)+".json"), &obj); err != nil {
errs.Add(err)
} else {
logging.Info().Str("name", string(ns)).Msg("store loaded")
}
stores.m[ns] = obj
}
return errs.Error()
}
func save() error {
stores.Lock()
defer stores.Unlock()
errs := gperr.NewBuilder("failed to save data stores")
for ns, store := range stores.m {
if err := utils.SaveJSON(filepath.Join(storesPath, string(ns)+".json"), &store, 0o644); err != nil {
errs.Add(err)
}
}
return errs.Error()
}
func Store[VT any](namespace namespace) MapStore[VT] {
stores.Lock()
defer stores.Unlock()
if s, ok := stores.m[namespace]; ok {
v, ok := s.(*MapStore[VT])
if !ok {
panic(fmt.Errorf("type mismatch: %T != %T", s, v))
}
return *v
}
m := &MapStore[VT]{MapOf: xsync.NewMapOf[string, VT]()}
stores.m[namespace] = m
return *m
}
func Object[Ptr Initializer](namespace namespace) Ptr {
stores.Lock()
defer stores.Unlock()
if s, ok := stores.m[namespace]; ok {
v, ok := s.(*ObjectStore[Ptr])
if !ok {
panic(fmt.Errorf("type mismatch: %T != %T", s, v))
}
return v.ptr
}
obj := &ObjectStore[Ptr]{}
obj.init()
stores.m[namespace] = obj
return obj.ptr
}
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
return json.Marshal(maps.Collect(s.Range))
}
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
tmp := make(map[string]VT)
if err := json.Unmarshal(data, &tmp); err != nil {
return err
}
s.MapOf = xsync.NewMapOf[string, VT](xsync.WithPresize(len(tmp)))
for k, v := range tmp {
s.MapOf.Store(k, v)
}
return nil
}
func (obj *ObjectStore[Ptr]) init() {
obj.ptr = reflect.New(reflect.TypeFor[Ptr]().Elem()).Interface().(Ptr)
obj.ptr.Initialize()
}
func (obj ObjectStore[Ptr]) MarshalJSON() ([]byte, error) {
return json.Marshal(obj.ptr)
}
func (obj *ObjectStore[Ptr]) UnmarshalJSON(data []byte) error {
obj.init()
return json.Unmarshal(data, obj.ptr)
}