This repository has been archived by the owner on Apr 23, 2020. It is now read-only.
/
jsonfile.go
120 lines (109 loc) · 1.88 KB
/
jsonfile.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
package jsonfile
import (
"bytes"
crand "crypto/rand"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math/rand"
"os"
"reflect"
"strconv"
"sync"
"time"
)
func init() {
var seed int64
binary.Read(crand.Reader, binary.LittleEndian, &seed)
rand.Seed(seed)
}
type File struct {
Obj interface{}
locker sync.Locker
cbs chan func()
path string
}
func New(obj interface{}, path string, locker sync.Locker) (*File, error) {
// check object
if reflect.TypeOf(obj).Kind() != reflect.Ptr {
return nil, errors.New("object must be a pointer")
}
// init
file := &File{
Obj: obj,
locker: locker,
cbs: make(chan func()),
path: path,
}
// try lock
done := make(chan struct{})
go func() {
locker.Lock()
close(done)
}()
select {
case <-time.NewTimer(time.Second * 1).C:
return nil, fmt.Errorf("lock fail")
case <-done:
}
// try load from file
dbFile, err := os.Open(path)
if err == nil {
defer dbFile.Close()
err = json.NewDecoder(dbFile).Decode(file.Obj)
if err != nil {
return nil, err
}
}
// loop
go func() {
for {
cb, ok := <-file.cbs
if !ok {
return
}
cb()
}
}()
return file, nil
}
func (f *File) Save() (err error) {
var done sync.Mutex
done.Lock()
f.cbs <- func() {
defer done.Unlock()
tmpPath := f.path + "." + strconv.FormatInt(rand.Int63(), 10)
var tmpF *os.File
tmpF, err = os.Create(tmpPath)
if err != nil {
return
}
defer tmpF.Close()
buf := new(bytes.Buffer)
err = json.NewEncoder(buf).Encode(f.Obj)
if err != nil {
return
}
// indent
indentBuf := new(bytes.Buffer)
err = json.Indent(indentBuf, buf.Bytes(), "", " ")
if err != nil {
return
}
_, err = tmpF.Write(indentBuf.Bytes())
if err != nil {
return
}
err = os.Rename(tmpPath, f.path)
if err != nil {
return
}
}
done.Lock()
return
}
func (f *File) Close() {
close(f.cbs)
f.locker.Unlock()
}