1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-09-12 07:55:07 +08:00
Files
kubo/unixfs/tar/reader.go
gatesvp 31ff954539 Move IPNS resolutions into the core library
Move IPNS resolutions into the core library via the pathresolver.go
file. Fix the CLI commands to leverage this core component.
2015-04-20 01:04:30 -07:00

205 lines
3.8 KiB
Go

package tar
import (
"archive/tar"
"bytes"
"compress/gzip"
"io"
gopath "path"
"time"
"github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
mdag "github.com/ipfs/go-ipfs/merkledag"
path "github.com/ipfs/go-ipfs/path"
uio "github.com/ipfs/go-ipfs/unixfs/io"
upb "github.com/ipfs/go-ipfs/unixfs/pb"
proto "github.com/ipfs/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, dagnode *mdag.Node, compression int) (*Reader, error) {
reader := &Reader{
signalChan: make(chan struct{}),
dag: dag,
}
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)
}
// 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, cancel := context.WithTimeout(context.TODO(), time.Second*60)
defer cancel()
for i, ng := range r.dag.GetDAG(ctx, dagnode) {
childNode, err := ng.Get(ctx)
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
}