add kv fs store and tests
This commit is contained in:
parent
9de2f796db
commit
557f9085f6
|
@ -0,0 +1,131 @@
|
||||||
|
package kvdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KVDB struct {
|
||||||
|
Prefix string
|
||||||
|
Ext string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *KVDB) Load(
|
||||||
|
keyif interface{},
|
||||||
|
typ ...interface{},
|
||||||
|
) (value interface{}, ok bool, err error) {
|
||||||
|
key, _ := keyif.(string)
|
||||||
|
if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#!:| \n") {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userFile := filepath.Join(kv.Prefix, key+"."+kv.Ext)
|
||||||
|
fmt.Println("Debug user file:", userFile)
|
||||||
|
b, err := ioutil.ReadFile(userFile)
|
||||||
|
if nil != err {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
fmt.Println("kvdb debug read:", err)
|
||||||
|
return nil, false, errors.New("database read failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
value = b
|
||||||
|
if 1 == len(typ) {
|
||||||
|
err := json.Unmarshal(b, typ[0])
|
||||||
|
if nil != err {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
value = typ[0]
|
||||||
|
} else if len(b) > 0 && '"' == b[0] {
|
||||||
|
var str string
|
||||||
|
err := json.Unmarshal(b, &str)
|
||||||
|
if nil == err {
|
||||||
|
value = str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *KVDB) Store(keyif interface{}, value interface{}) (err error) {
|
||||||
|
key, _ := keyif.(string)
|
||||||
|
if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#! \n") {
|
||||||
|
return errors.New("invalid key name")
|
||||||
|
}
|
||||||
|
|
||||||
|
keypath := filepath.Join(kv.Prefix, key+"."+kv.Ext)
|
||||||
|
f, err := os.Open(keypath)
|
||||||
|
if nil == err {
|
||||||
|
s, err := f.Stat()
|
||||||
|
if nil != err {
|
||||||
|
// if we can open, we should be able to stat
|
||||||
|
return errors.New("database connection failure")
|
||||||
|
}
|
||||||
|
ts := strconv.FormatInt(s.ModTime().Unix(), 10)
|
||||||
|
bakpath := filepath.Join(kv.Prefix, key+"."+ts+"."+kv.Ext)
|
||||||
|
if err := os.Rename(keypath, bakpath); nil != err {
|
||||||
|
// keep the old record as a backup
|
||||||
|
return errors.New("database write failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
b = v
|
||||||
|
case string:
|
||||||
|
b, _ = json.Marshal(v)
|
||||||
|
default:
|
||||||
|
fmt.Println("kvdb: not []byte or string:", v)
|
||||||
|
jsonb, err := json.Marshal(v)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b = jsonb
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(
|
||||||
|
keypath,
|
||||||
|
b,
|
||||||
|
os.FileMode(0600),
|
||||||
|
); nil != err {
|
||||||
|
fmt.Println("write failure:", err)
|
||||||
|
return errors.New("database write failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *KVDB) Delete(keyif interface{}) (err error) {
|
||||||
|
key, _ := keyif.(string)
|
||||||
|
if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#! \n") {
|
||||||
|
return errors.New("invalid key name")
|
||||||
|
}
|
||||||
|
|
||||||
|
keypath := filepath.Join(kv.Prefix, key+"."+kv.Ext)
|
||||||
|
f, err := os.Open(keypath)
|
||||||
|
if nil == err {
|
||||||
|
s, err := f.Stat()
|
||||||
|
if nil != err {
|
||||||
|
return errors.New("database connection failure")
|
||||||
|
}
|
||||||
|
ts := strconv.FormatInt(s.ModTime().Unix(), 64)
|
||||||
|
if err := os.Rename(keypath, filepath.Join(kv.Prefix, key+"."+ts+"."+kv.Ext)); nil != err {
|
||||||
|
return errors.New("database connection failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv *KVDB) Vacuum() (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package kvdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestEntry struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Subjects []string `json:"subjects"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var email = "john@example.com"
|
||||||
|
var sub = "id123"
|
||||||
|
var dbPrefix = "../testdb"
|
||||||
|
var testKV = &KVDB{
|
||||||
|
Prefix: dbPrefix + "/test-entries",
|
||||||
|
Ext: "eml.json",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStore(t *testing.T) {
|
||||||
|
entry := &TestEntry{
|
||||||
|
Email: email,
|
||||||
|
Subjects: []string{sub},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testKV.Store(email, entry); nil != err {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value, ok, err := testKV.Load(email, &(TestEntry{}))
|
||||||
|
if nil != err {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("test entry not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := value.(*TestEntry)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("test entry not of type TestEntry")
|
||||||
|
}
|
||||||
|
|
||||||
|
if email != v.Email || sub != strings.Join(v.Subjects, ",") {
|
||||||
|
t.Fatalf("value: %#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoExist(t *testing.T) {
|
||||||
|
value, ok, err := testKV.Load("not"+email, &(TestEntry{}))
|
||||||
|
if nil != err {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
t.Fatal("found entry that doesn't exist")
|
||||||
|
}
|
||||||
|
if value != nil {
|
||||||
|
t.Fatal("had value for entry that doesn't exist")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue