writer.go 2.74 KB
Newer Older
1 2 3 4 5 6 7 8 9
package tar

import (
	"archive/tar"
	"io"
	"path"
	"time"

	proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
10
	cxt "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
11 12

	mdag "github.com/ipfs/go-ipfs/merkledag"
rht's avatar
rht committed
13
	ft "github.com/ipfs/go-ipfs/unixfs"
14 15 16 17 18 19 20 21 22 23
	uio "github.com/ipfs/go-ipfs/unixfs/io"
	upb "github.com/ipfs/go-ipfs/unixfs/pb"
)

// Writer is a utility structure that helps to write
// unixfs merkledag nodes as a tar archive format.
// It wraps any io.Writer.
type Writer struct {
	Dag  mdag.DAGService
	TarW *tar.Writer
rht's avatar
rht committed
24 25

	ctx cxt.Context
26 27 28
}

// NewWriter wraps given io.Writer.
rht's avatar
rht committed
29
func NewWriter(ctx cxt.Context, dag mdag.DAGService, archive bool, compression int, w io.Writer) (*Writer, error) {
30 31 32
	return &Writer{
		Dag:  dag,
		TarW: tar.NewWriter(w),
rht's avatar
rht committed
33
		ctx:  ctx,
34 35 36
	}, nil
}

rht's avatar
rht committed
37
func (w *Writer) writeDir(nd *mdag.Node, fpath string) error {
38 39 40 41
	if err := writeDirHeader(w.TarW, fpath); err != nil {
		return err
	}

rht's avatar
rht committed
42 43
	for i, ng := range w.Dag.GetDAG(w.ctx, nd) {
		child, err := ng.Get(w.ctx)
44 45 46 47 48
		if err != nil {
			return err
		}

		npath := path.Join(fpath, nd.Links[i].Name)
rht's avatar
rht committed
49
		if err := w.WriteNode(child, npath); err != nil {
50 51 52 53 54 55 56
			return err
		}
	}

	return nil
}

rht's avatar
rht committed
57
func (w *Writer) writeFile(nd *mdag.Node, pb *upb.Data, fpath string) error {
58 59 60 61
	if err := writeFileHeader(w.TarW, fpath, pb.GetFilesize()); err != nil {
		return err
	}

rht's avatar
rht committed
62
	dagr := uio.NewDataFileReader(w.ctx, nd, pb, w.Dag)
rht's avatar
rht committed
63 64 65 66 67
	if _, err := dagr.WriteTo(w.TarW); err != nil {
		return err
	}
	w.TarW.Flush()
	return nil
68 69
}

rht's avatar
rht committed
70
func (w *Writer) WriteNode(nd *mdag.Node, fpath string) error {
71 72 73 74 75 76
	pb := new(upb.Data)
	if err := proto.Unmarshal(nd.Data, pb); err != nil {
		return err
	}

	switch pb.GetType() {
rht's avatar
rht committed
77 78
	case upb.Data_Metadata:
		fallthrough
79
	case upb.Data_Directory:
rht's avatar
rht committed
80 81 82
		return w.writeDir(nd, fpath)
	case upb.Data_Raw:
		fallthrough
83
	case upb.Data_File:
rht's avatar
rht committed
84
		return w.writeFile(nd, pb, fpath)
Jeromy's avatar
Jeromy committed
85 86
	case upb.Data_Symlink:
		return writeSymlinkHeader(w.TarW, string(pb.GetData()), fpath)
87
	default:
rht's avatar
rht committed
88
		return ft.ErrUnrecognizedType
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
	}
}

func (w *Writer) Close() error {
	return w.TarW.Close()
}

func writeDirHeader(w *tar.Writer, fpath string) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Typeflag: tar.TypeDir,
		Mode:     0777,
		ModTime:  time.Now(),
		// TODO: set mode, dates, etc. when added to unixFS
	})
}

func writeFileHeader(w *tar.Writer, fpath string, size uint64) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Size:     int64(size),
		Typeflag: tar.TypeReg,
		Mode:     0644,
		ModTime:  time.Now(),
		// TODO: set mode, dates, etc. when added to unixFS
	})
}
Jeromy's avatar
Jeromy committed
116 117 118 119 120 121 122 123 124

func writeSymlinkHeader(w *tar.Writer, target, fpath string) error {
	return w.WriteHeader(&tar.Header{
		Name:     fpath,
		Linkname: target,
		Mode:     0777,
		Typeflag: tar.TypeSymlink,
	})
}