AJ ONeal
4 years ago
2 changed files with 194 additions and 0 deletions
@ -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