88 lines
1.6 KiB
Go
88 lines
1.6 KiB
Go
package util
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/evcc-io/evcc/api"
|
|
"github.com/fatih/structs"
|
|
)
|
|
|
|
// Sharder splits data into chunks, omitting unmodified chunks
|
|
type Sharder interface {
|
|
Shards() []Shard
|
|
}
|
|
|
|
type Shard struct {
|
|
Key string
|
|
Value any
|
|
}
|
|
|
|
type sharderImpl struct {
|
|
prefix string
|
|
struc any
|
|
}
|
|
|
|
// shared shard cache
|
|
var (
|
|
shardCache = make(map[string][32]byte)
|
|
shardMu sync.Mutex
|
|
)
|
|
|
|
func (s *sharderImpl) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(s.struc)
|
|
}
|
|
|
|
func (s *sharderImpl) Shards() []Shard {
|
|
ff := structs.Fields(s.struc)
|
|
res := make([]Shard, 0, len(ff))
|
|
|
|
shardMu.Lock()
|
|
defer shardMu.Unlock()
|
|
|
|
for _, f := range ff {
|
|
key := f.Name()
|
|
if t := f.Tag("json"); t != "" {
|
|
if n := strings.Split(t, ",")[0]; n != "" {
|
|
key = n
|
|
}
|
|
}
|
|
|
|
// Use JSON for stable hashing (fmt.Append includes pointer addresses)
|
|
b, err := json.Marshal(f.Value())
|
|
if err != nil {
|
|
// Fallback to fmt.Append if JSON fails
|
|
b = fmt.Append(nil, f.Value())
|
|
}
|
|
|
|
hash := sha256.Sum256(b)
|
|
if cached, ok := shardCache[s.prefix+key]; ok && hash == cached {
|
|
continue
|
|
}
|
|
shardCache[s.prefix+key] = hash
|
|
|
|
res = append(res, Shard{
|
|
Key: key,
|
|
Value: f.Value(),
|
|
})
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
var _ api.StructMarshaler = (*sharderImpl)(nil)
|
|
|
|
func (s *sharderImpl) MarshalStruct() (any, error) {
|
|
return s.struc, nil
|
|
}
|
|
|
|
var _ Sharder = (*sharderImpl)(nil)
|
|
|
|
// NewSharder creates a Sharder that splits structs into sub-structs for space-efficient socket publishing
|
|
// Passing anything else than a struct will panic
|
|
func NewSharder(prefix string, struc any) Sharder {
|
|
return &sharderImpl{prefix, struc}
|
|
}
|