kubo/unixfs/tar/reader.go
Henry 92d08db7a5 rewrote import paths of go.net/context to use golang.org/x/context
- updated go-ctxgroup and goprocess
ctxgroup: AddChildGroup was changed to AddChild. Used in two files:
- p2p/net/mock/mock_net.go
- routing/dht/dht.go

- updated context from hg repo to git
prev. commit in hg was ad01a6fcc8a19d3a4478c836895ffe883bd2ceab. (context: make parentCancelCtx iterative)
represents commit 84f8955a887232b6308d79c68b8db44f64df455c in git repo

- updated context to master (b6fdb7d8a4ccefede406f8fe0f017fb58265054c)

Aaron Jacobs (2):
net/context: Don't accept a context in the DoSomethingSlow example.
context: Be clear that users must cancel the result of WithCancel.

Andrew Gerrand (1):
go.net: use golang.org/x/... import paths

Bryan C. Mills (1):
net/context: Don't leak goroutines in Done example.

Damien Neil (1):
context: fix removal of cancelled timer contexts from parent

David Symonds (2):
context: Fix WithValue example code.
net: add import comments.

Sameer Ajmani (1):
context: fix TestAllocs to account for ints in interfaces
2015-02-25 11:58:19 +01:00

210 lines
3.8 KiB
Go

package tar
import (
"archive/tar"
"bytes"
"compress/gzip"
"io"
gopath "path"
"time"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
mdag "github.com/jbenet/go-ipfs/merkledag"
path "github.com/jbenet/go-ipfs/path"
uio "github.com/jbenet/go-ipfs/unixfs/io"
upb "github.com/jbenet/go-ipfs/unixfs/pb"
proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
)
type Reader struct {
buf bytes.Buffer
closed bool
signalChan chan struct{}
dag mdag.DAGService
resolver *path.Resolver
writer *tar.Writer
gzipWriter *gzip.Writer
err error
}
func NewReader(path path.Path, dag mdag.DAGService, resolver *path.Resolver, compression int) (*Reader, error) {
reader := &Reader{
signalChan: make(chan struct{}),
dag: dag,
resolver: resolver,
}
var err error
if compression != gzip.NoCompression {
reader.gzipWriter, err = gzip.NewWriterLevel(&reader.buf, compression)
if err != nil {
return nil, err
}
reader.writer = tar.NewWriter(reader.gzipWriter)
} else {
reader.writer = tar.NewWriter(&reader.buf)
}
dagnode, err := resolver.ResolvePath(path)
if err != nil {
return nil, err
}
// writeToBuf will write the data to the buffer, and will signal when there
// is new data to read
_, filename := gopath.Split(path.String())
go reader.writeToBuf(dagnode, filename, 0)
return reader, nil
}
func (r *Reader) writeToBuf(dagnode *mdag.Node, path string, depth int) {
pb := new(upb.Data)
err := proto.Unmarshal(dagnode.Data, pb)
if err != nil {
r.emitError(err)
return
}
if depth == 0 {
defer r.close()
}
if pb.GetType() == upb.Data_Directory {
err = r.writer.WriteHeader(&tar.Header{
Name: path,
Typeflag: tar.TypeDir,
Mode: 0777,
ModTime: time.Now(),
// TODO: set mode, dates, etc. when added to unixFS
})
if err != nil {
r.emitError(err)
return
}
r.flush()
ctx, _ := context.WithTimeout(context.TODO(), time.Second*60)
for i, ng := range r.dag.GetDAG(ctx, dagnode) {
childNode, err := ng.Get()
if err != nil {
r.emitError(err)
return
}
r.writeToBuf(childNode, gopath.Join(path, dagnode.Links[i].Name), depth+1)
}
return
}
err = r.writer.WriteHeader(&tar.Header{
Name: path,
Size: int64(pb.GetFilesize()),
Typeflag: tar.TypeReg,
Mode: 0644,
ModTime: time.Now(),
// TODO: set mode, dates, etc. when added to unixFS
})
if err != nil {
r.emitError(err)
return
}
r.flush()
reader, err := uio.NewDagReader(context.TODO(), dagnode, r.dag)
if err != nil {
r.emitError(err)
return
}
err = r.syncCopy(reader)
if err != nil {
r.emitError(err)
return
}
}
func (r *Reader) Read(p []byte) (int, error) {
// wait for the goroutine that is writing data to the buffer to tell us
// there is something to read
if !r.closed {
<-r.signalChan
}
if r.err != nil {
return 0, r.err
}
if !r.closed {
defer r.signal()
}
if r.buf.Len() == 0 {
if r.closed {
return 0, io.EOF
}
return 0, nil
}
n, err := r.buf.Read(p)
if err == io.EOF && !r.closed || r.buf.Len() > 0 {
return n, nil
}
return n, err
}
func (r *Reader) signal() {
r.signalChan <- struct{}{}
}
func (r *Reader) flush() {
r.signal()
<-r.signalChan
}
func (r *Reader) emitError(err error) {
r.err = err
r.signal()
}
func (r *Reader) close() {
r.closed = true
defer r.signal()
err := r.writer.Close()
if err != nil {
r.emitError(err)
return
}
if r.gzipWriter != nil {
err = r.gzipWriter.Close()
if err != nil {
r.emitError(err)
return
}
}
}
func (r *Reader) syncCopy(reader io.Reader) error {
buf := make([]byte, 32*1024)
for {
nr, err := reader.Read(buf)
if nr > 0 {
_, err := r.writer.Write(buf[:nr])
if err != nil {
return err
}
r.flush()
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
return nil
}