-
Notifications
You must be signed in to change notification settings - Fork 1
/
extractor.go
141 lines (120 loc) · 3.06 KB
/
extractor.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
package tar
import (
"archive/tar"
"fmt"
"io"
"os"
gopath "path"
fp "path/filepath"
"strings"
)
type Extractor struct {
Path string
Progress func(int64) int64
}
func (te *Extractor) Extract(reader io.Reader) error {
tarReader := tar.NewReader(reader)
// Check if the output path already exists, so we know whether we should
// create our output with that name, or if we should put the output inside
// a preexisting directory
rootExists := true
rootIsDir := false
if stat, err := os.Stat(te.Path); err != nil && os.IsNotExist(err) {
rootExists = false
} else if err != nil {
return err
} else if stat.IsDir() {
rootIsDir = true
}
// files come recursively in order (i == 0 is root directory)
for i := 0; ; i++ {
header, err := tarReader.Next()
if err != nil && err != io.EOF {
return err
}
if header == nil || err == io.EOF {
break
}
switch header.Typeflag {
case tar.TypeDir:
if err := te.extractDir(header, i); err != nil {
return err
}
case tar.TypeReg:
if err := te.extractFile(header, tarReader, i, rootExists, rootIsDir); err != nil {
return err
}
case tar.TypeSymlink:
if err := te.extractSymlink(header); err != nil {
return err
}
default:
return fmt.Errorf("unrecognized tar header type: %d", header.Typeflag)
}
}
return nil
}
// outputPath returns the path at whicht o place tarPath
func (te *Extractor) outputPath(tarPath string) string {
elems := strings.Split(tarPath, "/") // break into elems
elems = elems[1:] // remove original root
path := fp.Join(elems...) // join elems
path = fp.Join(te.Path, path) // rebase on extractor root
return path
}
func (te *Extractor) extractDir(h *tar.Header, depth int) error {
path := te.outputPath(h.Name)
if depth == 0 {
// if this is the root root directory, use it as the output path for remaining files
te.Path = path
}
err := os.MkdirAll(path, 0755)
if err != nil {
return err
}
return nil
}
func (te *Extractor) extractSymlink(h *tar.Header) error {
return os.Symlink(h.Linkname, te.outputPath(h.Name))
}
func (te *Extractor) extractFile(h *tar.Header, r *tar.Reader, depth int, rootExists bool, rootIsDir bool) error {
path := te.outputPath(h.Name)
if depth == 0 { // if depth is 0, this is the only file (we aren't 'ipfs get'ing a directory)
if rootExists && rootIsDir {
// putting file inside of a root dir.
fnameo := gopath.Base(h.Name)
fnamen := fp.Base(path)
// add back original name if lost.
if fnameo != fnamen {
path = fp.Join(path, fnameo)
}
} // else if old file exists, just overwrite it.
}
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
err = copyWithProgress(file, r, te.Progress)
if err != nil {
return err
}
return nil
}
func copyWithProgress(to io.Writer, from io.Reader, cb func(int64) int64) error {
buf := make([]byte, 4096)
for {
n, err := from.Read(buf)
if err != nil {
if err == io.EOF {
return nil
}
return err
}
cb(int64(n))
_, err = to.Write(buf[:n])
if err != nil {
return err
}
}
}