feat(qs2) add qs2 framework

This commit is contained in:
hexxa 2020-12-05 10:30:03 +08:00
parent 6ae65fe09b
commit 83100007e3
33 changed files with 2934 additions and 60 deletions

View file

@ -0,0 +1,225 @@
package boltdbpvd
import (
"encoding/binary"
"fmt"
"math"
"path"
"time"
"github.com/boltdb/bolt"
"github.com/ihexxa/quickshare/src/kvstore"
)
type BoltPvd struct {
dbPath string
db *bolt.DB
maxStrLen int
}
func New(dbPath string, maxStrLen int) *BoltPvd {
boltPath := path.Join(path.Clean(dbPath), "quickshare.db")
db, err := bolt.Open(boltPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
if err != nil {
panic(err)
}
buckets := []string{"bools", "ints", "int64s", "floats", "strings", "locks"}
for _, bucketName := range buckets {
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketName))
if b != nil {
return nil
}
_, err := tx.CreateBucket([]byte(bucketName))
if err != nil {
panic(err)
}
return nil
})
}
return &BoltPvd{
dbPath: dbPath,
db: db,
maxStrLen: maxStrLen,
}
}
func (bp *BoltPvd) Close() error {
return bp.db.Close()
}
func (bp *BoltPvd) GetBool(key string) (bool, bool) {
buf, ok := make([]byte, 1), false
bp.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("bools"))
v := b.Get([]byte(key))
copy(buf, v)
ok = v != nil
return nil
})
// 1 means true, 0 means false
return buf[0] == 1, ok
}
func (bp *BoltPvd) SetBool(key string, val bool) error {
var bVal byte = 0
if val {
bVal = 1
}
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("bools"))
return b.Put([]byte(key), []byte{bVal})
})
}
func (bp *BoltPvd) DelBool(key string) error {
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("bools"))
return b.Delete([]byte(key))
})
}
func (bp *BoltPvd) GetInt(key string) (int, bool) {
x, ok := bp.GetInt64(key)
return int(x), ok
}
func (bp *BoltPvd) SetInt(key string, val int) error {
return bp.SetInt64(key, int64(val))
}
func (bp *BoltPvd) DelInt(key string) error {
return bp.DelInt64(key)
}
func (bp *BoltPvd) GetInt64(key string) (int64, bool) {
buf, ok := make([]byte, binary.MaxVarintLen64), false
bp.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("int64s"))
v := b.Get([]byte(key))
copy(buf, v)
ok = v != nil
return nil
})
if !ok {
return 0, false
}
x, n := binary.Varint(buf)
if n < 0 {
return 0, false
}
return x, true
}
func (bp *BoltPvd) SetInt64(key string, val int64) error {
buf := make([]byte, binary.MaxVarintLen64)
n := binary.PutVarint(buf, val)
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("int64s"))
return b.Put([]byte(key), buf[:n])
})
}
func (bp *BoltPvd) DelInt64(key string) error {
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("int64s"))
return b.Delete([]byte(key))
})
}
func float64ToBytes(num float64) []byte {
buf := make([]byte, 64)
binary.PutUvarint(buf, math.Float64bits(num))
return buf
}
func bytesToFloat64(buf []byte) float64 {
uintVal, _ := binary.Uvarint(buf[:64])
return math.Float64frombits(uintVal)
}
func (bp *BoltPvd) GetFloat(key string) (float64, bool) {
buf, ok := make([]byte, 64), false
bp.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("floats"))
v := b.Get([]byte(key))
copy(buf, v)
ok = v != nil
return nil
})
if !ok {
return 0.0, false
}
return bytesToFloat64(buf), true
}
func (bp *BoltPvd) SetFloat(key string, val float64) error {
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("floats"))
return b.Put([]byte(key), float64ToBytes(val))
})
}
func (bp *BoltPvd) DelFloat(key string) error {
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("floats"))
return b.Delete([]byte(key))
})
}
func (bp *BoltPvd) GetString(key string) (string, bool) {
buf, ok, length := make([]byte, bp.maxStrLen), false, bp.maxStrLen
bp.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("strings"))
v := b.Get([]byte(key))
length = copy(buf, v)
ok = v != nil
return nil
})
return string(buf[:length]), ok
}
func (bp *BoltPvd) SetString(key string, val string) error {
if len(val) > bp.maxStrLen {
return fmt.Errorf("can not set string value longer than %d", bp.maxStrLen)
}
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("strings"))
return b.Put([]byte(key), []byte(val))
})
}
func (bp *BoltPvd) DelString(key string) error {
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("strings"))
return b.Delete([]byte(key))
})
}
func (bp *BoltPvd) TryLock(key string) error {
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("locks"))
if b.Get([]byte(key)) != nil {
return kvstore.ErrLocked
}
return b.Put([]byte(key), []byte{})
})
}
func (bp *BoltPvd) Unlock(key string) error {
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("locks"))
if b.Get([]byte(key)) != nil {
return b.Delete([]byte(key))
}
return kvstore.ErrNoLock
})
}

View file

@ -0,0 +1,26 @@
package kvstore
import "errors"
var ErrLocked = errors.New("already locked")
var ErrNoLock = errors.New("no lock to unlock")
type IKVStore interface {
GetBool(key string) (bool, bool)
SetBool(key string, val bool) error
DelBool(key string) error
GetInt(key string) (int, bool)
SetInt(key string, val int) error
DelInt(key string) error
GetInt64(key string) (int64, bool)
SetInt64(key string, val int64) error
DelInt64(key string) error
GetFloat(key string) (float64, bool)
SetFloat(key string, val float64) error
DelFloat(key string) error
GetString(key string) (string, bool)
SetString(key string, val string) error
DelString(key string) error
TryLock(key string) error
Unlock(key string) error
}

View file

@ -0,0 +1,166 @@
package memstore
import (
"sync"
"github.com/ihexxa/quickshare/src/kvstore"
)
type MemStore struct {
bools map[string]bool
boolsMtx *sync.Mutex
ints map[string]int
intsMtx *sync.Mutex
int64s map[string]int64
int64sMtx *sync.Mutex
floats map[string]float64
floatsMtx *sync.Mutex
strings map[string]string
stringsMtx *sync.Mutex
locks map[string]bool
locksMtx *sync.Mutex
}
func New() *MemStore {
return &MemStore{
bools: map[string]bool{},
boolsMtx: &sync.Mutex{},
ints: map[string]int{},
intsMtx: &sync.Mutex{},
int64s: map[string]int64{},
int64sMtx: &sync.Mutex{},
floats: map[string]float64{},
floatsMtx: &sync.Mutex{},
strings: map[string]string{},
stringsMtx: &sync.Mutex{},
locks: map[string]bool{},
locksMtx: &sync.Mutex{},
}
}
func (st *MemStore) GetBool(key string) (bool, bool) {
st.boolsMtx.Lock()
defer st.boolsMtx.Unlock()
val, ok := st.bools[key]
return val, ok
}
func (st *MemStore) SetBool(key string, val bool) error {
st.boolsMtx.Lock()
defer st.boolsMtx.Unlock()
st.bools[key] = val
return nil
}
func (st *MemStore) GetInt(key string) (int, bool) {
st.intsMtx.Lock()
defer st.intsMtx.Unlock()
val, ok := st.ints[key]
return val, ok
}
func (st *MemStore) SetInt(key string, val int) error {
st.intsMtx.Lock()
defer st.intsMtx.Unlock()
st.ints[key] = val
return nil
}
func (st *MemStore) GetInt64(key string) (int64, bool) {
st.int64sMtx.Lock()
defer st.int64sMtx.Unlock()
val, ok := st.int64s[key]
return val, ok
}
func (st *MemStore) SetInt64(key string, val int64) error {
st.int64sMtx.Lock()
defer st.int64sMtx.Unlock()
st.int64s[key] = val
return nil
}
func (st *MemStore) GetFloat(key string) (float64, bool) {
st.floatsMtx.Lock()
defer st.floatsMtx.Unlock()
val, ok := st.floats[key]
return val, ok
}
func (st *MemStore) SetFloat(key string, val float64) error {
st.floatsMtx.Lock()
defer st.floatsMtx.Unlock()
st.floats[key] = val
return nil
}
func (st *MemStore) GetString(key string) (string, bool) {
st.stringsMtx.Lock()
defer st.stringsMtx.Unlock()
val, ok := st.strings[key]
return val, ok
}
func (st *MemStore) SetString(key string, val string) error {
st.stringsMtx.Lock()
defer st.stringsMtx.Unlock()
st.strings[key] = val
return nil
}
func (st *MemStore) DelBool(key string) error {
st.boolsMtx.Lock()
defer st.boolsMtx.Unlock()
delete(st.bools, key)
return nil
}
func (st *MemStore) DelInt(key string) error {
st.intsMtx.Lock()
defer st.intsMtx.Unlock()
delete(st.ints, key)
return nil
}
func (st *MemStore) DelInt64(key string) error {
st.int64sMtx.Lock()
defer st.int64sMtx.Unlock()
delete(st.int64s, key)
return nil
}
func (st *MemStore) DelFloat(key string) error {
st.floatsMtx.Lock()
defer st.floatsMtx.Unlock()
delete(st.floats, key)
return nil
}
func (st *MemStore) DelString(key string) error {
st.stringsMtx.Lock()
defer st.stringsMtx.Unlock()
delete(st.strings, key)
return nil
}
func (st *MemStore) TryLock(key string) error {
st.stringsMtx.Lock()
defer st.stringsMtx.Unlock()
_, ok := st.locks[key]
if ok {
return kvstore.ErrLocked
}
st.locks[key] = true
return nil
}
func (st *MemStore) Unlock(key string) error {
st.stringsMtx.Lock()
defer st.stringsMtx.Unlock()
_, ok := st.locks[key]
if !ok {
return kvstore.ErrNoLock
}
delete(st.locks, key)
return nil
}

View file

@ -0,0 +1,185 @@
package test
import (
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/ihexxa/quickshare/src/kvstore"
"github.com/ihexxa/quickshare/src/kvstore/boltdbpvd"
"github.com/ihexxa/quickshare/src/kvstore/memstore"
)
func TestKVStoreProviders(t *testing.T) {
var err error
var ok bool
key, boolV, intV, int64V, floatV, stringV := "key", true, 2027, int64(2027), 3.1415, "foobar"
kvstoreTest := func(store kvstore.IKVStore, t *testing.T) {
// test bools
_, ok = store.GetBool(key)
if ok {
t.Error("value should not exist")
}
err = store.SetBool(key, boolV)
if err != nil {
t.Errorf("there should be no error %v", err)
}
boolVGot, ok := store.GetBool(key)
if !ok {
t.Error("value should exit")
} else if boolVGot != boolV {
t.Error(fmt.Sprintln("value not equal", boolVGot, boolV))
}
err = store.DelBool(key)
if err != nil {
t.Errorf("there should be no error %v", err)
}
_, ok = store.GetBool(key)
if ok {
t.Error("value should not exist")
}
// test ints
_, ok = store.GetInt(key)
if ok {
t.Error("value should not exist")
}
err = store.SetInt(key, intV)
if err != nil {
t.Errorf("there should be no error %v", err)
}
intVGot, ok := store.GetInt(key)
if !ok {
t.Error("value should exit")
} else if intVGot != intV {
t.Error(fmt.Sprintln("value not equal", intVGot, intV))
}
err = store.DelInt(key)
if err != nil {
t.Errorf("there should be no error %v", err)
}
_, ok = store.GetInt(key)
if ok {
t.Error("value should not exist")
}
// test int64s
_, ok = store.GetInt64(key)
if ok {
t.Error("value should not exist")
}
err = store.SetInt64(key, int64V)
if err != nil {
t.Errorf("there should be no error %v", err)
}
int64VGot, ok := store.GetInt64(key)
if !ok {
t.Error("value should exit")
} else if int64VGot != int64V {
t.Error(fmt.Sprintln("value not equal", int64VGot, int64V))
}
err = store.DelInt64(key)
if err != nil {
t.Errorf("there should be no error %v", err)
}
_, ok = store.GetInt64(key)
if ok {
t.Error("value should not exist")
}
// test floats
_, ok = store.GetFloat(key)
if ok {
t.Error("value should not exist")
}
err = store.SetFloat(key, floatV)
if err != nil {
t.Errorf("there should be no error %v", err)
}
floatVGot, ok := store.GetFloat(key)
if !ok {
t.Error("value should exit")
} else if floatVGot != floatV {
t.Error(fmt.Sprintln("value not equal", floatVGot, floatV))
}
err = store.DelFloat(key)
if err != nil {
t.Errorf("there should be no error %v", err)
}
_, ok = store.GetFloat(key)
if ok {
t.Error("value should not exist")
}
// test strings
_, ok = store.GetString(key)
if ok {
t.Error("value should not exist")
}
err = store.SetString(key, stringV)
if err != nil {
t.Errorf("there should be no error %v", err)
}
stringVGot, ok := store.GetString(key)
if !ok {
t.Error("value should exit")
} else if stringVGot != stringV {
t.Error(fmt.Sprintln("value not equal", stringVGot, stringV))
}
err = store.DelString(key)
if err != nil {
t.Errorf("there should be no error %v", err)
}
_, ok = store.GetString(key)
if ok {
t.Error("value should not exist")
}
// test locks
err = store.TryLock(key)
if err != nil {
t.Errorf("there should be no error %v", err)
}
err = store.TryLock(key)
if err == nil || err != kvstore.ErrLocked {
t.Error("there should be locked")
}
err = store.TryLock("key2")
if err != nil {
t.Errorf("there should be no error %v", err)
}
err = store.Unlock(key)
if err != nil {
t.Errorf("there should be no error %v", err)
}
err = store.Unlock("key2")
if err != nil {
t.Errorf("there should be no error %v", err)
}
}
t.Run("test bolt provider", func(t *testing.T) {
rootPath, err := ioutil.TempDir("./", "quickshare_kvstore_test_")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rootPath)
store := boltdbpvd.New(rootPath, 1024)
defer store.Close()
kvstoreTest(store, t)
})
t.Run("test in-memory provider", func(t *testing.T) {
rootPath, err := ioutil.TempDir("./", "quickshare_kvstore_test_")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rootPath)
store := memstore.New()
kvstoreTest(store, t)
})
}