diff --git a/impl/file/file.go b/impl/file/file.go new file mode 100644 index 0000000..d5f6f13 --- /dev/null +++ b/impl/file/file.go @@ -0,0 +1,331 @@ +package file + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/shimmeringbee/persistence" + "github.com/shimmeringbee/persistence/impl/memory" + "os" + "strconv" + "strings" + "sync" + "time" +) + +func New(dir string) persistence.Section { + f := &file{m: &sync.RWMutex{}, cache: memory.New(), sections: make(map[string]*file)} + + dirWithoutPathSep, _ := strings.CutSuffix(dir, string(os.PathSeparator)) + f.dir = fmt.Sprintf("%s%c", dirWithoutPathSep, os.PathSeparator) + + f.load() + + return f +} + +type file struct { + dir string + cache persistence.Section + + m *sync.RWMutex + sections map[string]*file + + dirtyTimer *time.Timer +} + +var _ persistence.Section = (*file)(nil) + +func (f *file) Section(key ...string) persistence.Section { + f.m.Lock() + defer f.m.Unlock() + + s, ok := f.sections[key[0]] + + if !ok { + dir := fmt.Sprintf("%s%s", f.dir, key[0]) + _ = os.MkdirAll(dir, 600) + + s, _ = New(dir).(*file) + f.sections[key[0]] = s + } + + if len(key) > 1 { + return s.Section(key[1:]...) + } else { + return s + } +} + +func (f *file) SectionKeys() []string { + f.m.RLock() + defer f.m.RUnlock() + + var keys = make([]string, 0, len(f.sections)) + + for k := range f.sections { + keys = append(keys, k) + } + + return keys +} + +func (f *file) SectionExists(key string) bool { + f.m.RLock() + defer f.m.RUnlock() + + _, found := f.sections[key] + + return found +} + +func (f *file) SectionDelete(key string) bool { + f.m.Lock() + defer f.m.Unlock() + + if s, ok := f.sections[key]; ok { + for _, k := range s.SectionKeys() { + s.SectionDelete(k) + } + + s.sectionDeleteSelf() + delete(f.sections, key) + return true + } else { + return false + } +} + +func (f *file) sectionDeleteSelf() { + _ = os.RemoveAll(f.dir) +} + +func (f *file) Keys() []string { + return f.cache.Keys() +} + +func (f *file) Exists(key string) bool { + return f.cache.Exists(key) +} + +func (f *file) Type(key string) persistence.ValueType { + return f.cache.Type(key) +} + +func (f *file) Int(key string, defValue ...int64) (int64, bool) { + return f.cache.Int(key, defValue...) +} + +func (f *file) UInt(key string, defValue ...uint64) (uint64, bool) { + return f.cache.UInt(key, defValue...) +} + +func (f *file) String(key string, defValue ...string) (string, bool) { + return f.cache.String(key, defValue...) +} + +func (f *file) Bool(key string, defValue ...bool) (bool, bool) { + return f.cache.Bool(key, defValue...) +} + +func (f *file) Float(key string, defValue ...float64) (float64, bool) { + return f.cache.Float(key, defValue...) +} + +func (f *file) Bytes(key string, defValue ...[]byte) ([]byte, bool) { + return f.cache.Bytes(key, defValue...) +} + +func (f *file) Set(key string, value interface{}) { + f.cache.Set(key, value) + f.dirty() +} + +func (f *file) Delete(key string) bool { + ok := f.cache.Delete(key) + f.dirty() + return ok +} + +const dataFile = "data.json" + +type Value struct { + Value any + Type persistence.ValueType +} + +func (f *file) load() { + var sections []string + dataPresent := false + + entries, err := os.ReadDir(f.dir) + if err != nil { + panic(err) + } + + for _, ent := range entries { + if ent.IsDir() { + sections = append(sections, ent.Name()) + } else if ent.Name() == dataFile { + dataPresent = true + } + } + + if dataPresent { + var d map[string]Value + + b, err := os.ReadFile(fmt.Sprintf("%s%s", f.dir, dataFile)) + if err != nil { + panic(err) + } + + dec := json.NewDecoder(bytes.NewReader(b)) + dec.UseNumber() + + if err := dec.Decode(&d); err != nil { + panic(err) + } + + for k, v := range d { + f.loadValue(k, v) + } + } + + for _, subsection := range sections { + s := New(fmt.Sprintf("%s%s", f.dir, subsection)).(*file) + f.sections[subsection] = s + } +} + +const dirtyDelay = 500 * time.Millisecond + +func (f *file) dirty() { + f.m.Lock() + defer f.m.Unlock() + + if f.dirtyTimer != nil { + f.dirtyTimer.Stop() + } + + f.dirtyTimer = time.AfterFunc(dirtyDelay, f.dirtySync) +} + +func (f *file) dirtySync() { + f.m.Lock() + defer f.m.Unlock() + + if f.dirtyTimer != nil { + f.dirtyTimer.Stop() + f.dirtyTimer = nil + } + + f.sync(false) +} + +func (f *file) loadValue(k string, v Value) { + switch v.Type { + case persistence.Int: + if jn, ok := v.Value.(json.Number); ok { + if n, err := strconv.Atoi(string(jn)); err == nil { + f.cache.Set(k, int64(n)) + } + } + case persistence.UnsignedInt: + if jn, ok := v.Value.(json.Number); ok { + if n, err := strconv.Atoi(string(jn)); err == nil { + f.cache.Set(k, uint64(n)) + } + } + case persistence.String: + if s, ok := v.Value.(string); ok { + f.cache.Set(k, s) + } + case persistence.Bool: + if b, ok := v.Value.(bool); ok { + f.cache.Set(k, b) + } + case persistence.Float: + if jn, ok := v.Value.(json.Number); ok { + if n, err := strconv.ParseFloat(string(jn), 64); err == nil { + f.cache.Set(k, n) + } + } + case persistence.Bytes: + if ba, ok := v.Value.(string); ok { + var data []byte + + for ; len(ba) > 0; ba = ba[2:] { + if b, err := strconv.ParseInt(ba[:2], 16, 8); err == nil { + data = append(data, byte(b)) + } else { + return + } + } + + f.cache.Set(k, data) + } + } +} + +func (f *file) Sync() { + f.sync(true) +} + +func (f *file) sync(recursive bool) { + f.m.RLock() + defer f.m.RUnlock() + + data := make(map[string]Value) + + for _, k := range f.cache.Keys() { + var v any + t := f.cache.Type(k) + + switch t { + case persistence.Int: + v, _ = f.cache.Int(k) + case persistence.UnsignedInt: + v, _ = f.cache.UInt(k) + case persistence.String: + v, _ = f.cache.String(k) + case persistence.Bool: + v, _ = f.cache.Bool(k) + case persistence.Float: + v, _ = f.cache.Float(k) + case persistence.Bytes: + bs, _ := f.cache.Bytes(k) + + var bytesOut []string + + for _, b := range bs { + bytesOut = append(bytesOut, fmt.Sprintf("%02x", b)) + } + + v = strings.Join(bytesOut, "") + } + + data[k] = Value{ + Value: v, + Type: t, + } + } + + r, err := os.Create(fmt.Sprintf("%s%s", f.dir, dataFile)) + if err != nil { + panic(err) + } + defer r.Close() + + enc := json.NewEncoder(r) + enc.SetIndent("", " ") + + if err := enc.Encode(data); err != nil { + panic(err) + } + + if recursive { + for _, v := range f.sections { + v.sync(recursive) + } + } +} diff --git a/impl/file/file_test.go b/impl/file/file_test.go new file mode 100644 index 0000000..2ca3fa6 --- /dev/null +++ b/impl/file/file_test.go @@ -0,0 +1,70 @@ +package file + +import ( + "github.com/shimmeringbee/persistence" + "github.com/shimmeringbee/persistence/impl/test" + "os" + "sync" + "testing" +) + +type tracker struct { + m *sync.Mutex + db map[persistence.Section]string +} + +func (t *tracker) New() persistence.Section { + t.m.Lock() + defer t.m.Unlock() + + dir, err := os.MkdirTemp("", "*") + if err != nil { + panic(err) + } + + return t.new(dir) +} + +func (t *tracker) new(dir string) persistence.Section { + p := New(dir) + t.db[p] = dir + + return p +} + +func (t *tracker) Switch(p persistence.Section) persistence.Section { + t.m.Lock() + defer t.m.Unlock() + + dir, ok := t.db[p] + if !ok { + panic("switch called on non existent persistence") + } + + if f, ok := p.(*file); ok { + f.Sync() + } + + return t.new(dir) +} + +func (t *tracker) Done(p persistence.Section) { + t.m.Lock() + defer t.m.Unlock() + + dir, ok := t.db[p] + if !ok { + panic("switch called on non existent persistence") + } + + delete(t.db, p) + + if err := os.RemoveAll(dir); err != nil { + panic(err) + } +} + +func TestFile(t *testing.T) { + tr := tracker{m: &sync.Mutex{}, db: make(map[persistence.Section]string)} + test.Impl{New: tr.New, Switch: tr.Switch, Done: tr.Done}.Test(t) +} diff --git a/impl/memory/memory.go b/impl/memory/memory.go index 78ada46..4e7444a 100644 --- a/impl/memory/memory.go +++ b/impl/memory/memory.go @@ -16,6 +16,33 @@ type memory struct { sections map[string]persistence.Section } +func (m *memory) Type(key string) persistence.ValueType { + m.m.RLock() + v, ok := m.kv[key] + m.m.RUnlock() + + if ok { + switch v.(type) { + case int64: + return persistence.Int + case uint64: + return persistence.UnsignedInt + case string: + return persistence.String + case float64: + return persistence.Float + case bool: + return persistence.Bool + case []byte: + return persistence.Bytes + } + } + + return persistence.None +} + +var _ persistence.Section = (*memory)(nil) + func (m *memory) SectionExists(key string) bool { m.m.RLock() defer m.m.RUnlock() @@ -136,7 +163,7 @@ func (m *memory) Bytes(key string, defValue ...[]byte) ([]byte, bool) { return genericRetrieve(m, key, defValue...) } -func (m *memory) Set(key string, value interface{}) error { +func (m *memory) Set(key string, value interface{}) { var sV interface{} switch v := value.(type) { @@ -171,15 +198,13 @@ func (m *memory) Set(key string, value interface{}) error { case []byte: sV = v default: - return fmt.Errorf("section set: unknown type: %T", v) + panic(fmt.Errorf("section set: unknown type: %T", v)) } m.m.Lock() defer m.m.Unlock() m.kv[key] = sV - - return nil } func (m *memory) Delete(key string) bool { @@ -194,5 +219,3 @@ func (m *memory) Delete(key string) bool { return found } - -var _ persistence.Section = (*memory)(nil) diff --git a/impl/memory/memory_test.go b/impl/memory/memory_test.go index dc1a0a8..22800ee 100644 --- a/impl/memory/memory_test.go +++ b/impl/memory/memory_test.go @@ -6,5 +6,9 @@ import ( ) func TestMemory(t *testing.T) { - test.Impl{New: New}.Test(t) + test.Impl{ + New: New, + Done: test.EmptyDone, + Switch: test.EmptySwitch, + }.Test(t) } diff --git a/impl/test/testing.go b/impl/test/testing.go index 95ef1f4..3b5334b 100644 --- a/impl/test/testing.go +++ b/impl/test/testing.go @@ -6,13 +6,22 @@ import ( "testing" ) +func EmptySwitch(p persistence.Section) persistence.Section { + return p +} + +func EmptyDone(_ persistence.Section) {} + type Impl struct { - New func() persistence.Section + New func() persistence.Section + Switch func(persistence.Section) persistence.Section + Done func(persistence.Section) } func (tt Impl) Test(t *testing.T) { for name, test := range map[string]func(*testing.T){ "Keys": tt.Keys, + "Type": tt.Type, "Delete": tt.Delete, "Bool": tt.Bool, "Bytes": tt.Bytes, @@ -36,9 +45,10 @@ func (tt Impl) Test(t *testing.T) { func (tt Impl) Keys(t *testing.T) { t.Run("added keys are returned", func(t *testing.T) { s := tt.New() + defer tt.Done(s) - _ = s.Set("a", "one") - _ = s.Set("b", "two") + s.Set("a", "one") + s.Set("b", "two") keys := s.Keys() assert.Len(t, keys, 2) @@ -47,11 +57,37 @@ func (tt Impl) Keys(t *testing.T) { }) } +func (tt Impl) Type(t *testing.T) { + t.Run("types return correct values", func(t *testing.T) { + s := tt.New() + defer tt.Done(s) + + s.Set("int", 1) + s.Set("uint", uint(1)) + s.Set("string", "Hello World") + s.Set("float", 1.0) + s.Set("bool", true) + s.Set("bytes", []byte("data")) + + s2 := tt.Switch(s) + defer tt.Done(s2) + + assert.Equal(t, persistence.Int, s2.Type("int")) + assert.Equal(t, persistence.UnsignedInt, s2.Type("uint")) + assert.Equal(t, persistence.String, s2.Type("string")) + assert.Equal(t, persistence.Float, s2.Type("float")) + assert.Equal(t, persistence.Bool, s2.Type("bool")) + assert.Equal(t, persistence.Bytes, s2.Type("bytes")) + assert.Equal(t, persistence.None, s2.Type("missing")) + }) +} + func (tt Impl) Delete(t *testing.T) { t.Run("deleting a key removes it", func(t *testing.T) { s := tt.New() + defer tt.Done(s) - _ = s.Set("a", "one") + s.Set("a", "one") assert.Contains(t, s.Keys(), "a") @@ -62,6 +98,7 @@ func (tt Impl) Delete(t *testing.T) { t.Run("returns false if key not present", func(t *testing.T) { s := tt.New() + defer tt.Done(s) assert.False(t, s.Delete("a")) }) @@ -70,6 +107,7 @@ func (tt Impl) Delete(t *testing.T) { func (tt Impl) Bool(t *testing.T) { t.Run("can be set and retrieved", func(t *testing.T) { s := tt.New() + defer tt.Done(s) val, found := s.Bool("boolKey") assert.False(t, val) @@ -79,9 +117,12 @@ func (tt Impl) Bool(t *testing.T) { assert.True(t, val) assert.False(t, found) - assert.NoError(t, s.Set("boolKey", true)) + s.Set("boolKey", true) - val, found = s.Bool("boolKey", true) + s2 := tt.Switch(s) + defer tt.Done(s2) + + val, found = s2.Bool("boolKey", true) assert.True(t, val) assert.True(t, found) }) @@ -90,6 +131,7 @@ func (tt Impl) Bool(t *testing.T) { func (tt Impl) Bytes(t *testing.T) { t.Run("can be set and retrieved", func(t *testing.T) { s := tt.New() + defer tt.Done(s) val, found := s.Bytes("bytesKey") assert.Nil(t, val) @@ -99,9 +141,12 @@ func (tt Impl) Bytes(t *testing.T) { assert.Equal(t, []byte{}, val) assert.False(t, found) - assert.NoError(t, s.Set("bytesKey", []byte{0x01})) + s.Set("bytesKey", []byte{0x01}) + + s2 := tt.Switch(s) + defer tt.Done(s2) - val, found = s.Bytes("bytesKey", nil) + val, found = s2.Bytes("bytesKey", nil) assert.Equal(t, []byte{0x01}, val) assert.True(t, found) }) @@ -110,6 +155,7 @@ func (tt Impl) Bytes(t *testing.T) { func (tt Impl) String(t *testing.T) { t.Run("can be set and retrieved", func(t *testing.T) { s := tt.New() + defer tt.Done(s) val, found := s.String("stringKey") assert.Equal(t, "", val) @@ -119,9 +165,12 @@ func (tt Impl) String(t *testing.T) { assert.Equal(t, "none", val) assert.False(t, found) - assert.NoError(t, s.Set("stringKey", "test")) + s.Set("stringKey", "test") - val, found = s.String("stringKey", "other") + s2 := tt.Switch(s) + defer tt.Done(s2) + + val, found = s2.String("stringKey", "other") assert.Equal(t, "test", val) assert.True(t, found) }) @@ -130,6 +179,7 @@ func (tt Impl) String(t *testing.T) { func (tt Impl) Float(t *testing.T) { t.Run("can be set and retrieved", func(t *testing.T) { s := tt.New() + defer tt.Done(s) val, found := s.Float("float64Key") assert.Equal(t, 0.0, val) @@ -139,15 +189,18 @@ func (tt Impl) Float(t *testing.T) { assert.Equal(t, 0.1, val) assert.False(t, found) - assert.NoError(t, s.Set("float64Key", 0.2)) + s.Set("float64Key", 0.2) val, found = s.Float("float64Key", 0.1) assert.Equal(t, 0.2, val) assert.True(t, found) - assert.NoError(t, s.Set("float32Key", float32(0.2))) + s.Set("float32Key", float32(0.2)) + + s2 := tt.Switch(s) + defer tt.Done(s2) - val, found = s.Float("float32Key", 0.1) + val, found = s2.Float("float32Key", 0.1) assert.InDelta(t, 0.2, val, 0.0001) assert.True(t, found) }) @@ -156,6 +209,7 @@ func (tt Impl) Float(t *testing.T) { func (tt Impl) Int(t *testing.T) { t.Run("can be set and retrieved", func(t *testing.T) { s := tt.New() + defer tt.Done(s) val, found := s.Int("intKey") assert.Equal(t, int64(0), val) @@ -165,33 +219,33 @@ func (tt Impl) Int(t *testing.T) { assert.Equal(t, int64(1), val) assert.False(t, found) - assert.NoError(t, s.Set("intKey", 2)) + s.Set("intKey", 2) val, found = s.Int("intKey", 1) assert.Equal(t, int64(2), val) assert.True(t, found) - assert.NoError(t, s.Set("int8Key", int8(2))) + s.Set("int8Key", int8(2)) + s.Set("int16Key", int16(2)) + s.Set("int32Key", int32(2)) + s.Set("int64Key", int64(2)) - val, found = s.Int("int8Key", 1) + s2 := tt.Switch(s) + defer tt.Done(s2) + + val, found = s2.Int("int8Key", 1) assert.Equal(t, int64(2), val) assert.True(t, found) - assert.NoError(t, s.Set("int16Key", int16(2))) - - val, found = s.Int("int16Key", 1) + val, found = s2.Int("int16Key", 1) assert.Equal(t, int64(2), val) assert.True(t, found) - assert.NoError(t, s.Set("int32Key", int32(2))) - - val, found = s.Int("int32Key", 1) + val, found = s2.Int("int32Key", 1) assert.Equal(t, int64(2), val) assert.True(t, found) - assert.NoError(t, s.Set("int64Key", int64(2))) - - val, found = s.Int("int64Key", 1) + val, found = s2.Int("int64Key", 1) assert.Equal(t, int64(2), val) assert.True(t, found) }) @@ -200,6 +254,7 @@ func (tt Impl) Int(t *testing.T) { func (tt Impl) UInt(t *testing.T) { t.Run("can be set and retrieved", func(t *testing.T) { s := tt.New() + defer tt.Done(s) val, found := s.UInt("intKey") assert.Equal(t, uint64(0), val) @@ -209,33 +264,33 @@ func (tt Impl) UInt(t *testing.T) { assert.Equal(t, uint64(1), val) assert.False(t, found) - assert.NoError(t, s.Set("intKey", uint(2))) + s.Set("intKey", uint(2)) val, found = s.UInt("intKey", 1) assert.Equal(t, uint64(2), val) assert.True(t, found) - assert.NoError(t, s.Set("int8Key", uint8(2))) + s.Set("int8Key", uint8(2)) + s.Set("int16Key", uint16(2)) + s.Set("int32Key", uint32(2)) + s.Set("int64Key", uint64(2)) + + s2 := tt.Switch(s) + defer tt.Done(s2) - val, found = s.UInt("int8Key", 1) + val, found = s2.UInt("int8Key", 1) assert.Equal(t, uint64(2), val) assert.True(t, found) - assert.NoError(t, s.Set("int16Key", uint16(2))) - - val, found = s.UInt("int16Key", 1) + val, found = s2.UInt("int16Key", 1) assert.Equal(t, uint64(2), val) assert.True(t, found) - assert.NoError(t, s.Set("int32Key", uint32(2))) - - val, found = s.UInt("int32Key", 1) + val, found = s2.UInt("int32Key", 1) assert.Equal(t, uint64(2), val) assert.True(t, found) - assert.NoError(t, s.Set("int64Key", uint64(2))) - - val, found = s.UInt("int64Key", 1) + val, found = s2.UInt("int64Key", 1) assert.Equal(t, uint64(2), val) assert.True(t, found) }) @@ -244,13 +299,17 @@ func (tt Impl) UInt(t *testing.T) { func (tt Impl) Section(t *testing.T) { t.Run("a chained section can be created and persists upon retrieval", func(t *testing.T) { s := tt.New() + defer tt.Done(s) cs := s.Section("tier1", "tier2") - _ = cs.Set("key", "value") + cs.Set("key", "value") + + s2 := tt.Switch(s) + defer tt.Done(s2) - assert.Contains(t, s.SectionKeys(), "tier1") + assert.Contains(t, s2.SectionKeys(), "tier1") - t1 := s.Section("tier1") + t1 := s2.Section("tier1") assert.Contains(t, t1.SectionKeys(), "tier2") @@ -264,50 +323,70 @@ func (tt Impl) Section(t *testing.T) { func (tt Impl) SectionKeys(t *testing.T) { t.Run("seconds can be listed", func(t *testing.T) { s := tt.New() + defer tt.Done(s) s.Section("one") s.Section("two") - assert.Contains(t, s.SectionKeys(), "one") - assert.Contains(t, s.SectionKeys(), "two") + s2 := tt.Switch(s) + defer tt.Done(s2) + + assert.Contains(t, s2.SectionKeys(), "one") + assert.Contains(t, s2.SectionKeys(), "two") }) } func (tt Impl) SectionDelete(t *testing.T) { t.Run("seconds can be deleted", func(t *testing.T) { s := tt.New() + defer tt.Done(s) s.Section("one") assert.Contains(t, s.SectionKeys(), "one") s.SectionDelete("one") - assert.NotContains(t, s.SectionKeys(), "one") + + s2 := tt.Switch(s) + defer tt.Done(s2) + + assert.NotContains(t, s2.SectionKeys(), "one") }) } func (tt Impl) Exists(t *testing.T) { t.Run("returns if a key exists", func(t *testing.T) { s := tt.New() + defer tt.Done(s) + + s.Set("key", "value") + + s2 := tt.Switch(s) + defer tt.Done(s2) - _ = s.Set("key", "value") - assert.True(t, s.Exists("key")) - assert.False(t, s.Exists("otherKey")) + assert.True(t, s2.Exists("key")) + assert.False(t, s2.Exists("otherKey")) }) } func (tt Impl) SectionExists(t *testing.T) { t.Run("returns if a section exists", func(t *testing.T) { s := tt.New() + defer tt.Done(s) _ = s.Section("key") - assert.True(t, s.SectionExists("key")) - assert.False(t, s.SectionExists("otherKey")) + + s2 := tt.Switch(s) + defer tt.Done(s2) + + assert.True(t, s2.SectionExists("key")) + assert.False(t, s2.SectionExists("otherKey")) }) } func (tt Impl) SectionKeyNotClash(t *testing.T) { t.Run("ensure that keys and sections dont shared the same name space", func(t *testing.T) { s := tt.New() + defer tt.Done(s) s.Section("key") s.Section("key2") @@ -317,12 +396,15 @@ func (tt Impl) SectionKeyNotClash(t *testing.T) { actualKeyInt, _ := s.Int("key") assert.Equal(t, int64(42), actualKeyInt) - assert.Contains(t, s.Keys(), "key") - assert.NotContains(t, s.Keys(), "key2") - assert.Contains(t, s.Keys(), "key3") + s2 := tt.Switch(s) + defer tt.Done(s2) + + assert.Contains(t, s2.Keys(), "key") + assert.NotContains(t, s2.Keys(), "key2") + assert.Contains(t, s2.Keys(), "key3") - assert.Contains(t, s.SectionKeys(), "key") - assert.Contains(t, s.SectionKeys(), "key2") - assert.NotContains(t, s.SectionKeys(), "key3") + assert.Contains(t, s2.SectionKeys(), "key") + assert.Contains(t, s2.SectionKeys(), "key2") + assert.NotContains(t, s2.SectionKeys(), "key3") }) } diff --git a/interface.go b/interface.go index 7e0d5c1..d703944 100644 --- a/interface.go +++ b/interface.go @@ -8,6 +8,7 @@ type Section interface { Keys() []string Exists(key string) bool + Type(key string) ValueType Int(key string, defValue ...int64) (int64, bool) UInt(key string, defValue ...uint64) (uint64, bool) @@ -16,7 +17,19 @@ type Section interface { Float(key string, defValue ...float64) (float64, bool) Bytes(key string, defValue ...[]byte) ([]byte, bool) - Set(key string, value interface{}) error + Set(key string, value interface{}) Delete(key string) bool } + +type ValueType uint8 + +const ( + Int ValueType = 0 + UnsignedInt ValueType = 1 + String ValueType = 2 + Bool ValueType = 3 + Float ValueType = 4 + Bytes ValueType = 5 + None ValueType = 255 +)