This repository has been archived by the owner on Sep 21, 2023. It is now read-only.
/
util.go
144 lines (129 loc) · 3.22 KB
/
util.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
package cdb
import (
"crypto/md5"
"encoding/base64"
"encoding/json"
"io"
"path/filepath"
"strings"
"sync"
"github.com/go-kivik/fsdb/v3/filesystem"
)
var reservedKeys = map[string]struct{}{
"_id": {},
"_rev": {},
"_attachments": {},
"_revisions": {},
"_revs_info": {}, // *
"_deleted": {},
"_conflicts": {}, // *
"_deleted_conflicts": {}, // *
"_local_seq": {}, // *
// * Can these be PUT?
}
// EscapeID escapes a document or database ID to be a safe filename.
func EscapeID(s string) string {
if s == "" {
return s
}
if strings.Contains(s, "/") || strings.Contains(s, "%2F") || strings.Contains(s, "%2f") {
s = strings.Replace(s, "%", "%25", -1)
s = strings.Replace(s, "/", "%2F", -1)
}
return s
}
// UnescapeID unescapes a filename into a document or database ID.
func UnescapeID(s string) string {
if s == "" {
return s
}
if strings.Contains(s, "%2F") || strings.Contains(s, "%2f") || strings.Contains(s, "%25") {
s = strings.Replace(s, "%2F", "/", -1)
s = strings.Replace(s, "%2f", "/", -1)
s = strings.Replace(s, "%25", "%", -1)
}
return s
}
// copyDigest works the same as io.Copy, but also returns the md5sum
// digest of the copied file.
func copyDigest(tgt io.Writer, dst io.Reader) (int64, string, error) {
h := md5.New()
tee := io.TeeReader(dst, h)
wg := sync.WaitGroup{}
wg.Add(1)
var written int64
var err error
go func() {
written, err = io.Copy(tgt, tee)
wg.Done()
}()
wg.Wait()
return written, "md5-" + base64.StdEncoding.EncodeToString(h.Sum(nil)), err
}
func digest(r io.Reader) (int64, string) {
h := md5.New()
written, _ := io.Copy(h, r)
return written, "md5-" + base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func joinJSON(objects ...json.RawMessage) []byte {
var size int
for _, obj := range objects {
size += len(obj)
}
result := make([]byte, 0, size)
result = append(result, '{')
for _, obj := range objects {
if len(obj) == 4 && string(obj) == "null" {
continue
}
result = append(result, obj[1:len(obj)-1]...)
result = append(result, ',')
}
result[len(result)-1] = '}'
return result
}
func atomicWriteFile(fs filesystem.Filesystem, path string, r io.Reader) error {
f, err := fs.TempFile(filepath.Dir(path), ".tmp."+filepath.Base(path)+"-")
if err != nil {
return err
}
if _, err := io.Copy(f, r); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return fs.Rename(f.Name(), path)
}
type atomicWriter struct {
fs filesystem.Filesystem
path string
f filesystem.File
err error
}
func (w *atomicWriter) Write(p []byte) (int, error) {
if w.err != nil {
return 0, w.err
}
return w.f.Write(p)
}
func (w *atomicWriter) Close() error {
if w.err != nil {
return w.err
}
if err := w.f.Close(); err != nil {
return err
}
return w.fs.Rename(w.f.Name(), w.path)
}
// atomicFileWriter returns an io.WriteCloser, which writes to a temp file, then
// when Close() is called, it renames to the originally requested path.
func atomicFileWriter(fs filesystem.Filesystem, path string) io.WriteCloser {
f, err := fs.TempFile(filepath.Dir(path), ".tmp."+filepath.Base(path)+"-")
return &atomicWriter{
fs: fs,
path: path,
f: f,
err: err,
}
}