Files
virtual-kubelet/vendor/github.com/hyperhq/hypercli/api/client/tarfile.go
2017-12-05 17:53:58 -06:00

247 lines
4.5 KiB
Go

package client
import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"github.com/cheggaaa/pb"
)
const (
TARINFO_HEADER = iota
TARINFO_FILE
TARINFO_PAD
TARINFO_UPLOADED
TARINFO_FINISHED
)
type tarInfo struct {
info os.FileInfo
relPath string
linkName string
path string
pad int // number of zeros to pad at the end of the file entry
headerBuf *bytes.Buffer
pos int64
state int
}
type TarFile struct {
fileList []*tarInfo
blockSize int
endPad int
padding []byte
closed bool
source string
progress *pb.ProgressBar
}
func (t *TarFile) writeHeader(p []byte, info *tarInfo) (int, error) {
if info.headerBuf == nil {
header, err := tar.FileInfoHeader(info.info, info.linkName)
if err != nil {
return 0, err
}
header.Name = info.relPath
buf := &bytes.Buffer{}
tarWriter := tar.NewWriter(buf)
defer tarWriter.Close()
err = tarWriter.WriteHeader(header)
if err != nil {
return 0, err
}
info.headerBuf = &bytes.Buffer{}
io.Copy(info.headerBuf, buf)
}
size, err := info.headerBuf.Read(p)
if err != nil && err != io.EOF {
return 0, err
}
if info.headerBuf.Len() == 0 {
info.headerBuf.Truncate(0)
}
return size, nil
}
func (t *TarFile) writeFile(p []byte, info *tarInfo) (int, error) {
var f *os.File
var err error
if f, err = os.Open(info.path); err != nil {
return 0, err
}
defer f.Close()
// resuming
if info.pos != 0 {
if _, err = f.Seek(info.pos, os.SEEK_SET); err != nil {
return 0, err
}
}
if ret, err := f.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
return ret, nil
}
}
func (t *TarFile) writePad(p []byte, info *tarInfo) (int, error) {
buf := bytes.NewBuffer(t.padding[0:info.pad])
if ret, err := buf.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
return ret, nil
}
}
// write the trailing zeros of a tar file
func (t *TarFile) writeClose(p []byte) (n int, err error) {
size := t.endPad
buf := bytes.NewBuffer(make([]byte, size))
if ret, err := buf.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
t.endPad -= ret
}
if t.endPad <= 0 {
if t.progress != nil {
t.progress.Finish()
}
t.closed = true
return size, io.EOF
}
return size - t.endPad, nil
}
func (t *TarFile) AllocBar(pool *pb.Pool) *pb.ProgressBar {
if t.progress == nil {
t.progress = pb.New(len(t.fileList)).Prefix(fmt.Sprintf("Sending %s", t.source))
pool.Add(t.progress)
}
return t.progress
}
func (t *TarFile) AddFile(info os.FileInfo, relPath, linkName, path string) {
t.fileList = append(t.fileList, &tarInfo{
info: info,
relPath: relPath,
linkName: linkName,
path: path,
pad: (t.blockSize - (int(info.Size()) % t.blockSize)) % t.blockSize,
})
}
func (t *TarFile) Close() error {
if !t.closed {
if t.progress != nil {
t.progress.Finish()
}
}
t.closed = true
return nil
}
func (t *TarFile) Read(p []byte) (n int, err error) {
var (
file *tarInfo
idx int
length = len(p)
)
if length == 0 {
return
}
if t.closed {
return 0, io.EOF
}
for idx, file = range t.fileList {
if file.state != TARINFO_FINISHED {
break
}
if idx == len(t.fileList)-1 {
file = nil
break
}
}
defer func() {
if err != nil && err != io.EOF {
t.Close()
}
}()
if file == nil {
return t.writeClose(p)
}
for n < length && file.state != TARINFO_FINISHED {
switch file.state {
case TARINFO_HEADER:
if ret, err := t.writeHeader(p, file); err != nil {
return 0, err
} else {
n += ret
p = p[n:]
if file.headerBuf.Len() == 0 {
if file.info.Mode().IsRegular() {
file.state = TARINFO_FILE
} else {
file.state = TARINFO_UPLOADED
}
}
}
case TARINFO_FILE:
if ret, err := t.writeFile(p, file); err != nil {
return 0, err
} else {
file.pos += int64(ret)
n += ret
p = p[ret:]
if file.pos >= file.info.Size() {
file.pos = 0
file.state = TARINFO_PAD
}
}
case TARINFO_PAD:
if file.pad == 0 {
file.state = TARINFO_UPLOADED
} else if ret, err := t.writePad(p, file); err != nil {
return 0, err
} else {
file.pos += int64(ret)
n += ret
p = p[ret:]
if file.pos >= int64(file.pad) {
file.state = TARINFO_UPLOADED
}
}
case TARINFO_UPLOADED:
file.state = TARINFO_FINISHED
if t.progress != nil {
t.progress.Increment()
}
}
}
return n, nil
}
func NewTarFile(path string, blockSize int) *TarFile {
return &TarFile{
blockSize: blockSize,
endPad: blockSize * 2,
source: filepath.Base(path),
padding: make([]byte, blockSize),
}
}