-
Notifications
You must be signed in to change notification settings - Fork 0
/
save.go
162 lines (135 loc) · 3.09 KB
/
save.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
package bacom
import (
"bytes"
"io"
"math"
"net/http"
"os"
"path/filepath"
)
// Saver is used to handle saving or requests and responses to a new version
type Saver struct {
dir, fname, reqName string
}
// NewSaver returns a *Saver. `dir` is the output folder and `fname` is the name of the
// original request file
func NewSaver(dir, fname string) *Saver {
return &Saver{
dir: dir,
fname: fname,
reqName: filepath.Base(fname),
}
}
// SaveRequest moves the request file to the new folder (as specified in NewSaver).
// The file name is adjusted if file with the same name already exists at that location
// and if the files are not identical.
func (s *Saver) SaveRequest() error {
reqName, err := nameFromReqFileName(filepath.Base(s.fname))
if err != nil {
return err
}
dst := filepath.Join(s.dir, s.reqName)
if !fileExists(dst) {
return copyFile(s.fname, dst)
}
ok, err := compareFiles(s.fname, dst)
if err != nil {
return err
}
if ok {
return nil
}
dst = ReqFileName(reqName, s.dir)
s.reqName = filepath.Base(dst)
return copyFile(s.fname, dst)
}
// SaveResponse saves the response to the appropriate file in the output
// directory. The name generated by SaveRequest is used to ensure the response
// has a matching file name.
func (s *Saver) SaveResponse(resp *http.Response) error {
respName, err := GetResponseFilename(s.reqName)
if err != nil {
return err
}
f, err := os.Create(filepath.Join(s.dir, respName))
if err != nil {
return err
}
defer handleClose(&err, f)
return resp.Write(f)
}
func fileExists(fname string) bool {
_, err := os.Stat(fname)
return !os.IsNotExist(err)
}
func compareFiles(lhsFname, rhsFname string) (identical bool, err error) {
lhsF, err := os.Open(lhsFname)
if err != nil {
return false, err
}
defer handleClose(&err, lhsF)
rhsF, err := os.Open(rhsFname)
if err != nil {
return false, err
}
defer handleClose(&err, rhsF)
return compareReaders(lhsF, rhsF)
}
func copyFile(srcFname, dstFname string) (err error) {
src, err := os.Open(srcFname)
if err != nil {
return err
}
defer handleClose(&err, src)
dst, err := os.Create(dstFname)
if err != nil {
return err
}
defer handleClose(&err, dst)
_, err = io.Copy(dst, src)
return err
}
func handleClose(err *error, closer io.Closer) {
errClose := closer.Close()
if *err == nil {
*err = errClose
}
}
func compareReaders(lhs, rhs io.Reader) (bool, error) {
var lhsN, rhsN, n int
var lhsErr, rhsErr error
lhsB := make([]byte, 4096)
rhsB := make([]byte, 4096)
for {
n, lhsErr = lhs.Read(lhsB[lhsN:])
if lhsErr != nil && lhsErr != io.EOF {
return false, lhsErr
}
lhsN += n
n, rhsErr = rhs.Read(rhsB[rhsN:])
if rhsErr != nil && rhsErr != io.EOF {
return false, rhsErr
}
rhsN += n
n = minInt(lhsN, rhsN)
if !bytes.Equal(lhsB[:n], rhsB[:n]) {
return false, nil
}
if lhsErr == io.EOF || rhsErr == io.EOF {
return lhsErr == rhsErr, nil
}
copy(lhsB, lhsB[n:])
lhsN -= n
copy(rhsB, rhsB[n:])
rhsN -= n
}
}
func minInt(vals ...int) int {
min := math.MaxInt32
for _, v := range vals {
if v < min {
min = v
}
}
return min
}