-
Notifications
You must be signed in to change notification settings - Fork 0
/
roc.go
155 lines (130 loc) · 2.74 KB
/
roc.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
package renameonclose
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
)
// File is a wrapper around os.File, but can sync and then rename/remove on close.
type File struct {
*os.File
oname string
sync bool
rename bool
closed bool
}
// Create a new temporary file (via. ioutil.TempFile) that on close
// will either can rename over the path given or delete itself.
func Create(name string) (f *File, err error) {
base, fname := filepath.Split(name)
if base == "" {
base = "."
}
if fname == "" {
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrInvalid}
}
tmpfile, err := ioutil.TempFile(base, fname)
if err != nil {
return nil, err
}
return &File{File: tmpfile, oname: name}, nil
}
// Renamed returns the name of the file we will rename to
func (f *File) Renamed() string {
return f.oname
}
// Sync the file before the rename
func (f *File) Sync() error {
f.sync = true
return nil
}
// CloseRename closes the file, maybe after sync'ing, and then rename's it.
func (f *File) CloseRename() error {
f.rename = true
return f.Close()
}
const chunckSize = 4 * 1024
// IsDifferent is the new file different to the file it will replace.
// On errors it returns true for differences.
func (f *File) IsDifferent() (bool, error) {
nstat, err := f.Stat()
if err != nil {
return true, err
}
ostat, err := os.Stat(f.oname)
if err != nil {
return true, err
}
if nstat.Size() != ostat.Size() {
return true, nil
}
of, err := os.Open(f.oname)
if err != nil {
return true, err
}
defer of.Close()
var off int64
b1store := make([]byte, chunckSize)
b2store := make([]byte, chunckSize)
for {
n1, err1 := f.ReadAt(b1store, off)
off += int64(n1)
if n1 > 0 && err1 == io.EOF {
err1 = nil
}
n2, err2 := of.Read(b2store)
if err1 != nil || err2 != nil {
if err1 == io.EOF && err2 == io.EOF {
return false, nil
} else if err1 == io.EOF {
return true, nil
} else if err2 == io.EOF {
return true, nil
} else {
err := err1
if err != nil {
err = err2
}
return true, err
}
}
b1 := b1store[:n1]
b2 := b2store[:n2]
if n1 != n2 { // Bad ?
return false, nil
}
if !bytes.Equal(b1[:n1], b2[:n1]) {
return true, nil
}
}
}
// Close the file and delete
func (f *File) Close() error {
if f == nil {
return os.ErrInvalid
}
name := f.Name()
if f.closed {
return &os.PathError{Op: "close", Path: name, Err: os.ErrClosed}
}
f.closed = true
if f.sync && f.rename {
if err := f.Sync(); err != nil {
os.Remove(name)
return err
}
}
if err := f.File.Close(); err != nil {
os.Remove(name)
return err
}
if f.rename {
if err := os.Rename(name, f.oname); err != nil {
os.Remove(name)
return err
}
return nil
}
return os.Remove(name)
}