-
Notifications
You must be signed in to change notification settings - Fork 0
/
file_handling.go
167 lines (140 loc) · 3.95 KB
/
file_handling.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
package kvstore
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"time"
)
// getFlag is used to determine the state of the store file. If it is in use by
// other client or not
// 0 -> not in use by any other client
// 1 -> is in use by some other client
func getFlag(storeFile *os.File) (int, error) {
var err error
flag := make([]byte, 1)
_, err = storeFile.ReadAt(flag, 0)
if err != nil {
return -1, err
}
return strconv.Atoi(string(flag))
}
// toggleFlag is used to toggle the state of the store file. The state is used
// for determining whether the file is in use by other client or not
func toggleFlag(storeFile *os.File) error {
flag, err := getFlag(storeFile)
if err != nil {
return err
}
if flag == 0 {
// change the flag to 1 because this file is now used by the current client
_, err = storeFile.WriteAt([]byte{'1'}, 0)
} else {
// change the flag to 0 because this file is not used by any client
_, err = storeFile.WriteAt([]byte{'0'}, 0)
}
return err
}
// readStoreFile will read all the content in given store file and will
// unmarshal the content in the usable JSON Object as represented by
// map[string]interface{} in golang
func readStoreFile(fi *os.File) (*map[string]*KeyValue, error) {
content := []byte{}
content = append(content, byte('{'))
// make a read buffer
r := bufio.NewReader(fi)
buf := make([]byte, 1024)
flag := ""
for {
// read a chunk of key value pair
n, err := r.Read(buf)
if err != nil && err != io.EOF {
return nil, err
}
i := 0
for i < n {
if len(flag) == 0 && i == 0 {
flag = string(buf[i])
} else {
content = append(content, buf[i])
}
i++
}
if n == 0 {
break
}
}
// remove the extra invalid ","
if len(content) > 1 {
content = content[:len(content)-1]
}
content = append(content, byte('}'))
var result map[string]*KeyValue
if err := json.Unmarshal(content, &result); err != nil {
return nil, err
}
for key, keyValue := range result {
keyValue.Key = key
}
return &result, nil
}
// writeToStoreFile will be used to marshal the given key value pair
// and write to the store file for persistence
func writeToStoreFile(storeFile *os.File, keyValue *KeyValue) error {
info, err := storeFile.Stat()
if err != nil {
return err
}
// according to the requirement the store file size cannot exceed 1GB
if info.Size() > _GB {
return errors.New("size of store file cannot exceed 1GB")
}
// set the unix time stamp till which the key is valid
// otherwise the 0 timestamp means the key value pair is valid forever
if keyValue.Time != 0 {
keyValue.ValidTill = time.Now().Unix() + keyValue.Time
}
valueStrBytes, err := json.Marshal(keyValue.Value)
if err != nil {
return err
}
valueStr := string(valueStrBytes)
// 1 character takes 1 byte in file
if len(valueStr) > 16*_KB {
return errors.New("size of value cannot exceed 16KB")
}
_, err = storeFile.WriteString(
fmt.Sprintf("\"%v\":{\"value\":%v,\"time\":%v,\"validTill\":%v},",
keyValue.Key, valueStr, keyValue.Time, keyValue.ValidTill))
return err
}
// updateStoreFile will update the store file to a new state after removing the
// deleted keys. This function works by first removing the old store file. Then
// a new store file is created and the remaning key value pairs are written to
// that new store file. And then the old store file is replaced by the new file
func updateStoreFile(store *Store, storeMap *map[string]*KeyValue) error {
storeFile := store.StoreFile
storeFilePath, err := filepath.Abs(storeFile.Name())
if err != nil {
return err
}
if err = os.Remove(storeFile.Name()); err != nil {
return err
}
newStoreFile, err := os.OpenFile(storeFilePath, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return err
}
if _, err = newStoreFile.WriteAt([]byte{'1'}, 0); err != nil {
return err
}
for _, keyValue := range *storeMap {
writeToStoreFile(newStoreFile, keyValue)
}
store.StoreFile = newStoreFile
return nil
}