database/db.go (view raw)
1package database
2
3import (
4 "bytes"
5 "errors"
6 "fmt"
7 "math"
8 "path"
9 "strconv"
10 "time"
11
12 "git.icyphox.sh/paprika/config"
13 "github.com/dgraph-io/badger/v3"
14)
15
16// Use this as a global DB handle.
17var DB database
18
19type database struct {
20 *badger.DB
21}
22
23// see: https://dgraph.io/docs/badger/get-started/#garbage-collection
24func gc() {
25 ticker := time.NewTicker(5 * time.Minute)
26 defer ticker.Stop()
27 for range ticker.C {
28 again:
29 err := DB.DB.RunValueLogGC(0.7)
30 if err == nil {
31 goto again
32 }
33 }
34 panic("Unreachable!")
35}
36
37func Open() (*badger.DB, error) {
38 db, err := badger.Open(
39 badger.DefaultOptions(path.Join(config.C.DbPath, "badger")),
40 )
41 if err != nil {
42 return nil, err
43 }
44 go gc()
45 return db, nil
46}
47
48// Wrapper function to simplify setting a key/val
49// in badger.
50func (d *database) Set(key, val []byte) error {
51 err := d.Update(func(txn *badger.Txn) error {
52 err := txn.Set(key, val)
53 return err
54 })
55
56 return err
57}
58
59// Wrapper function to simplify getting a key from badger.
60func (d *database) Get(key []byte) ([]byte, error) {
61 var val []byte
62 err := d.View(func(txn *badger.Txn) error {
63 item, err := txn.Get(key)
64 if err != nil {
65 return err
66 }
67
68 err = item.Value(func(v []byte) error {
69 val, err = item.ValueCopy(nil)
70 return err
71 })
72 return err
73 })
74 if err != nil {
75 return nil, err
76 }
77 return val, nil
78}
79
80var NumTooBig = errors.New("Number Too Big")
81var InvalidNumber = errors.New("Invalid Number")
82
83// encode number so it sorts lexicographically, while being semi readable.
84func EncodeNumber(n int) ([]byte, error) {
85 neg := false
86 num := n
87 if n < 0 {
88 neg = true
89 num = -num
90 }
91
92 digits := int(math.Trunc(math.Log10(float64(num))))+1
93 if digits > 93 {
94 return []byte{}, NumTooBig
95 }
96
97 if !neg {
98 lenCode := 33 + digits
99 return []byte(fmt.Sprintf("%c %d", lenCode, n)), nil
100 } else {
101 lenCode := 127 - digits
102 return []byte(fmt.Sprintf("!%c %d", lenCode, n)), nil
103 }
104}
105
106// encode number so it sorts lexicographically, while being semi readable.
107func DecodeNumber(n []byte) (int, error) {
108 if len(n) < 3 {
109 return 0, InvalidNumber
110 }
111
112 // No digit padding
113 if n[0] < 33 || n[0] > 126 {
114 return 0, InvalidNumber
115 }
116
117 num := bytes.SplitN(n, []byte{' '}, 2)
118 if len(num) != 2 {
119 return 0, InvalidNumber
120 }
121
122 number, err := strconv.Atoi(string(num[1]))
123 if err != nil {
124 return number, err
125 }
126
127 if n[0] == '!' {
128 return -number, nil
129 } else {
130 return number, nil
131 }
132}