This repository has been archived by the owner on Apr 23, 2020. It is now read-only.
/
dsfile.go
116 lines (104 loc) · 1.89 KB
/
dsfile.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
package dsfile
import (
crand "crypto/rand"
"encoding/binary"
"io"
"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{}
path string
codec Codec
locker sync.Locker
cbs chan func()
}
type Codec interface {
Decode(io.Reader, interface{}) error
Encode(io.Writer, interface{}) error
}
func New(obj interface{}, path string, codec Codec, locker sync.Locker) (*File, error) {
// check object
if reflect.TypeOf(obj).Kind() != reflect.Ptr {
return nil, makeErr(nil, "object must be a pointer")
}
// init
file := &File{
obj: obj,
path: path,
codec: codec,
locker: locker,
cbs: make(chan func()),
}
// try lock
done := make(chan struct{})
go func() {
locker.Lock()
close(done)
}()
select {
case <-time.NewTimer(time.Second * 1).C:
return nil, makeErr(nil, "lock fail")
case <-done:
}
// try load from file
dbFile, err := os.Open(path)
if err == nil {
defer dbFile.Close()
err = codec.Decode(dbFile, obj)
if err != nil {
return nil, makeErr(err, "decode error")
}
}
// loop
go func() {
for {
cb, ok := <-file.cbs
if !ok {
return
}
cb()
}
}()
return file, nil
}
func (f *File) Save() error {
done := make(chan struct{})
var err error
f.cbs <- func() {
defer close(done)
tmpPath := f.path + "." + strconv.FormatInt(rand.Int63(), 10) + ".tmp"
var tmpF *os.File
tmpF, err = os.Create(tmpPath)
if err != nil {
err = makeErr(err, "open temp file")
return
}
defer tmpF.Close()
err = f.codec.Encode(tmpF, f.obj)
if err != nil {
err = makeErr(err, "encode error")
return
}
err = os.Rename(tmpPath, f.path)
if err != nil {
err = makeErr(err, "rename temp file")
return
}
}
<-done
return err
}
func (f *File) Close() {
close(f.cbs)
f.locker.Unlock()
}