use more effienet data storage
This commit is contained in:
203
v2/db/hiddify_db.go
Normal file
203
v2/db/hiddify_db.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
tmdb "github.com/tendermint/tm-db"
|
||||
)
|
||||
|
||||
// getDB initializes the database with retry logic. If it fails after 100 attempts, it returns nil.
|
||||
func getDB(name string, readOnly bool) tmdb.DB {
|
||||
const retryAttempts = 100
|
||||
const retryDelay = 100 * time.Microsecond
|
||||
|
||||
for i := 0; i < retryAttempts; i++ {
|
||||
db, err := tmdb.NewGoLevelDBWithOpts(name, "./data", &opt.Options{ReadOnly: readOnly})
|
||||
if err == nil {
|
||||
return db
|
||||
}
|
||||
log.Printf("Failed attempt %d to initialize the database: %v", i, err)
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTable returns a new Table instance for the generic type T, ensuring the struct has an "Id" field.
|
||||
func GetTable[T any]() *Table[T] {
|
||||
var t T
|
||||
typeName := reflect.TypeOf(t).Name()
|
||||
if !hasIdField(t) {
|
||||
panic(fmt.Sprintf("Table %s must have a field named 'Id'", typeName))
|
||||
}
|
||||
return &Table[T]{name: typeName}
|
||||
}
|
||||
|
||||
// hasIdField checks if the struct has a field named "Id".
|
||||
func hasIdField[T any](t T) bool {
|
||||
val := reflect.Indirect(reflect.ValueOf(t))
|
||||
|
||||
if val.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
|
||||
return val.FieldByName("Id").IsValid()
|
||||
}
|
||||
|
||||
// getIdBytes converts an ID to its byte representation for storage in the database.
|
||||
func getIdBytes(id any) []byte {
|
||||
res, err := SerializeKey(id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// if id == nil {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// var buf bytes.Buffer
|
||||
// switch v := id.(type) {
|
||||
// case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
// if err := binary.Write(&buf, binary.BigEndian, v); err == nil {
|
||||
// return buf.Bytes()
|
||||
// }
|
||||
// case string:
|
||||
// return []byte(v)
|
||||
// case []byte:
|
||||
// return v
|
||||
// default:
|
||||
// return []byte(fmt.Sprint(v))
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// getId retrieves the "Id" field from the struct T.
|
||||
func getId[T any](t T) any {
|
||||
val := reflect.Indirect(reflect.ValueOf(t))
|
||||
|
||||
if val.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
field := val.FieldByName("Id")
|
||||
if field.IsValid() {
|
||||
return field.Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Table represents a database table for generic type T.
|
||||
type Table[T any] struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// All retrieves all entries from the database and unmarshals them into a slice of T.
|
||||
func (tbl *Table[T]) All() ([]*T, error) {
|
||||
db := getDB(tbl.name, true)
|
||||
if db == nil {
|
||||
return nil, fmt.Errorf("failed to open database %s", tbl.name)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var items []*T
|
||||
iter, err := db.Iterator(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
|
||||
item, err := Deserialize[T](iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func Serialize(data any) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
err := enc.Encode(data)
|
||||
return buf.Bytes(), err
|
||||
|
||||
// return json.Marshal(data)
|
||||
}
|
||||
|
||||
func SerializeKey(data any) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
err := enc.Encode(data)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func Deserialize[T any](data []byte) (*T, error) {
|
||||
var obj T
|
||||
buf := bytes.NewBuffer(data)
|
||||
dec := gob.NewDecoder(buf)
|
||||
err := dec.Decode(&obj)
|
||||
return &obj, err
|
||||
|
||||
// return &obj, json.Unmarshal(data, &obj)
|
||||
}
|
||||
|
||||
// UpdateInsert inserts or updates multiple items in the database.
|
||||
func (tbl *Table[T]) UpdateInsert(items ...*T) error {
|
||||
db := getDB(tbl.name, false)
|
||||
if db == nil {
|
||||
return fmt.Errorf("failed to open database %s", tbl.name)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
for _, item := range items {
|
||||
// b, err := json.Marshal(item)
|
||||
b, err := Serialize(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Set(getIdBytes(getId(item)), b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes entries by their IDs.
|
||||
func (tbl *Table[T]) Delete(ids ...any) error {
|
||||
db := getDB(tbl.name, false)
|
||||
if db == nil {
|
||||
return fmt.Errorf("failed to open database %s", tbl.name)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
for _, id := range ids {
|
||||
if err := db.Delete(getIdBytes(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves a single item by its ID.
|
||||
func (tbl *Table[T]) Get(id any) (*T, error) {
|
||||
db := getDB(tbl.name, true)
|
||||
if db == nil {
|
||||
return nil, fmt.Errorf("failed to open database %s", tbl.name)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
b, err := db.Get(getIdBytes(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Deserialize[T](b)
|
||||
}
|
||||
Reference in New Issue
Block a user