mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 12:42:34 +02:00
148 lines
3.1 KiB
Go
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)
|
|
}
|