This repository has been archived by the owner on Mar 27, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
thunder.go
226 lines (199 loc) · 5.05 KB
/
thunder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// Package thunder povides a simple key-value
// file storage using encoding/gob for file
// encoding/decoding.
package thunder
import (
"encoding/gob"
"errors"
"io"
"os"
"path"
"sync"
)
type header struct {
Name string
Version int
}
type nodeMap map[interface{}]*Node
// DB is the struct that contains the name of the of the database file,
// the header containing database type and version and the
// data as map of data nodes.
type DB struct {
mx *sync.Mutex
locked bool
filename string
Header *header
Data nodeMap
}
// ------------------------ PRIVATE FUNCS ------------------------
// encode transmits the data of the DB instance
// to the passed file writer handler.
func encode(db *DB, fhandler io.Writer) error {
gobencoder := gob.NewEncoder(fhandler)
err := gobencoder.Encode(db)
return err
}
// decode reads the data from the passed file
// reader handler and parses th data to a new
// instance of DB.
func decode(fhandler io.Reader) (*DB, error) {
gobdecoder := gob.NewDecoder(fhandler)
obj := new(DB)
err := gobdecoder.Decode(obj)
return obj, err
}
// lock locks the mutex of the DB if it is
// not already locked.
func (db *DB) lock() {
if db.mx != nil && !db.locked {
db.mx.Lock()
db.locked = true
}
}
// unlock unlocks the mutex of the DB if
// it is locked.
func (db *DB) unlock() {
if db.mx != nil && db.locked {
db.locked = false
db.mx.Unlock()
}
}
// ------------------------ PUBLIC FUNCS ------------------------
// Register records one or more types passed by an
// instance of them to gob.
// See gob#Register for more information.
func Register(val ...interface{}) {
for _, v := range val {
gob.Register(v)
}
}
// Open creates a new instance of DB from database file.
// If the passed file does not exist, it will be created
// as empty database.
// The file name and location is passed as string.
// If no exceptions are occuring, the database instannce
// will be returned. Else, the error will be returned as
// second return value.
//
// Directories not existent will be created automatically
// with file permission mdoe 0750.
//
// If you get an error like:
// "gob: name not registered for interface: <type>"
// You need to register this type in gob before opening
// like following:
//
// import (
// "encoding/gob"
// "github.com/zekroTJA/thunder"
// )
//
// type User struct {
// UserName string
// UID int64
// }
//
// func main() {
// thunder.Register(*User)
// db, err := thunder.Open("myDb.th")
// }
func Open(filename string) (*DB, error) {
gob.Register(map[interface{}]*Node{})
dir := path.Dir(filename)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.MkdirAll(dir, 0750); err != nil {
return nil, err
}
}
fhandler, err := os.Open(filename)
if os.IsNotExist(err) {
obj := &DB{
mx: new(sync.Mutex),
Header: &header{
Name: headerName,
Version: headerVersion,
},
Data: make(nodeMap),
filename: filename,
}
fhandler, err = os.Create(filename)
if err != nil {
return nil, err
}
defer fhandler.Close()
err = encode(obj, fhandler)
return obj, err
} else if err != nil {
return nil, err
}
defer fhandler.Close()
obj, err := decode(fhandler)
if err != nil {
return nil, err
}
if obj.Header.Version > headerVersion {
return nil, errors.New("the database file version is newer than the package version")
}
obj.filename = filename
return obj, err
}
// CreateNode creates a new node inside data base.
// Notes are used to save key-value pair data.
// As first arguement, the key will be passed as interface type.
// Optional, the externally created node can be passed to
// insert prepared nodes into the data base.
// If no exceptions occure, the created node instance will be returned.
// Else, the error will be returned as second return value.
func (db *DB) CreateNode(key interface{}, node ...*Node) (*Node, error) {
db.lock()
defer db.unlock()
if _, ok := db.Data[key]; ok {
return nil, ErrNodeKeyExists
}
if len(node) > 0 {
db.Data[key] = node[0]
} else {
db.Data[key] = NewNode()
}
db.Save()
return db.Data[key], nil
}
// GetNode gets the node by key if exists.
// It returns the node instance of the key and,
// as bool, if the node key exists in the database.
func (db *DB) GetNode(key interface{}) (*Node, bool) {
db.lock()
defer db.unlock()
node, ok := db.Data[key]
return node, ok
}
// RemoveNode deletes the node by key in the database.
// If erros occure, they will be returned as error.
func (db *DB) RemoveNode(key interface{}) error {
db.lock()
defer db.unlock()
if _, ok := db.Data[key]; !ok {
return ErrNodeKeyNotExist
}
delete(db.Data, key)
db.Save()
return nil
}
// Save saves the current database state to file.
// If errors occure, they will be returned as error.
func (db *DB) Save() error {
db.lock()
defer db.unlock()
fhandler, err := os.OpenFile(db.filename, os.O_WRONLY, 771)
defer fhandler.Close()
if err != nil {
return err
}
err = encode(db, fhandler)
return err
}
// Close closes the database file and saves the current
// state of the database to the file.
func (db *DB) Close() {
db.Save()
}