get.go 4.32 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2 3 4 5 6 7
package commands

import (
	"archive/tar"
	"bytes"
	"io"
	p "path"
8
	"sync"
Matt Bell's avatar
Matt Bell committed
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

	cmds "github.com/jbenet/go-ipfs/commands"
	core "github.com/jbenet/go-ipfs/core"
	dag "github.com/jbenet/go-ipfs/merkledag"
	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"
)

var GetCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Download IPFS objects",
		ShortDescription: `
Retrieves the object named by <ipfs-path> and stores the data to disk.

By default, the output will be stored at ./<ipfs-path>, but an alternate path
can be specified with '--output=<path>' or '-o=<path>'.

To output a TAR archive instead of unpacked files, use '--archive' or '-a'.
`,
	},

	Arguments: []cmds.Argument{
		cmds.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to be outputted").EnableStdin(),
	},
	Options: []cmds.Option{
		cmds.StringOption("output", "o", "The path where output should be stored"),
		cmds.BoolOption("archive", "a", "Output a TAR archive"),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		node, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

46
		reader, err := get(node, req.Arguments()[0])
Matt Bell's avatar
Matt Bell committed
47 48 49 50 51 52 53 54
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(reader)
	},
}

55 56
func get(node *core.IpfsNode, path string) (io.Reader, error) {
	buf := NewBufReadWriter()
Matt Bell's avatar
Matt Bell committed
57 58

	go func() {
59
		err := copyFilesAsTar(node, buf, path)
Matt Bell's avatar
Matt Bell committed
60 61 62 63 64 65
		if err != nil {
			log.Error(err)
			return
		}
	}()

66
	return buf, nil
Matt Bell's avatar
Matt Bell committed
67 68
}

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
func copyFilesAsTar(node *core.IpfsNode, buf *bufReadWriter, path string) error {
	writer := tar.NewWriter(buf)

	err := _copyFilesAsTar(node, writer, buf, path, nil)
	if err != nil {
		return err
	}

	err = writer.Flush()
	if err != nil {
		return err
	}
	buf.Close()
	buf.Signal()
	return nil
}

func _copyFilesAsTar(node *core.IpfsNode, writer *tar.Writer, buf *bufReadWriter, path string, dagnode *dag.Node) error {
Matt Bell's avatar
Matt Bell committed
87 88 89 90
	var err error
	if dagnode == nil {
		dagnode, err = node.Resolver.ResolvePath(path)
		if err != nil {
91
			return err
Matt Bell's avatar
Matt Bell committed
92 93 94 95 96 97
		}
	}

	pb := new(upb.Data)
	err = proto.Unmarshal(dagnode.Data, pb)
	if err != nil {
98
		return err
Matt Bell's avatar
Matt Bell committed
99 100 101
	}

	if pb.GetType() == upb.Data_Directory {
102
		buf.mutex.Lock()
Matt Bell's avatar
Matt Bell committed
103 104 105 106 107 108
		err = writer.WriteHeader(&tar.Header{
			Name:     path,
			Typeflag: tar.TypeDir,
			Mode:     0777,
			// TODO: set mode, dates, etc. when added to unixFS
		})
109
		buf.mutex.Unlock()
Matt Bell's avatar
Matt Bell committed
110
		if err != nil {
111
			return err
Matt Bell's avatar
Matt Bell committed
112 113 114
		}

		for _, link := range dagnode.Links {
115
			err := _copyFilesAsTar(node, writer, buf, p.Join(path, link.Name), link.Node)
Matt Bell's avatar
Matt Bell committed
116
			if err != nil {
117
				return err
Matt Bell's avatar
Matt Bell committed
118 119 120
			}
		}

121 122
		return nil
	}
Matt Bell's avatar
Matt Bell committed
123

124 125 126 127 128 129 130 131 132 133 134 135
	buf.mutex.Lock()
	err = writer.WriteHeader(&tar.Header{
		Name:     path,
		Size:     int64(pb.GetFilesize()),
		Typeflag: tar.TypeReg,
		Mode:     0644,
		// TODO: set mode, dates, etc. when added to unixFS
	})
	buf.mutex.Unlock()
	if err != nil {
		return err
	}
Matt Bell's avatar
Matt Bell committed
136

137 138 139 140 141 142 143 144
	reader, err := uio.NewDagReader(dagnode, node.DAG)
	if err != nil {
		return err
	}

	_, err = syncCopy(writer, reader, buf)
	if err != nil {
		return err
Matt Bell's avatar
Matt Bell committed
145
	}
146 147

	return nil
Matt Bell's avatar
Matt Bell committed
148 149
}

150
type bufReadWriter struct {
Matt Bell's avatar
Matt Bell committed
151 152 153
	buf        bytes.Buffer
	closed     bool
	signalChan chan struct{}
154 155 156 157 158 159 160 161
	mutex      *sync.Mutex
}

func NewBufReadWriter() *bufReadWriter {
	return &bufReadWriter{
		signalChan: make(chan struct{}),
		mutex:      &sync.Mutex{},
	}
Matt Bell's avatar
Matt Bell committed
162 163
}

164
func (i *bufReadWriter) Read(p []byte) (int, error) {
Matt Bell's avatar
Matt Bell committed
165
	<-i.signalChan
166 167 168 169 170 171 172 173 174 175
	i.mutex.Lock()
	defer i.mutex.Unlock()

	if i.buf.Len() == 0 {
		if i.closed {
			return 0, io.EOF
		}
		return 0, nil
	}

Matt Bell's avatar
Matt Bell committed
176
	n, err := i.buf.Read(p)
177
	if err == io.EOF && !i.closed || i.buf.Len() > 0 {
Matt Bell's avatar
Matt Bell committed
178 179 180 181 182
		return n, nil
	}
	return n, err
}

183 184 185 186 187
func (i *bufReadWriter) Write(p []byte) (int, error) {
	return i.buf.Write(p)
}

func (i *bufReadWriter) Signal() {
Matt Bell's avatar
Matt Bell committed
188 189 190
	i.signalChan <- struct{}{}
}

191
func (i *bufReadWriter) Close() error {
Matt Bell's avatar
Matt Bell committed
192
	i.closed = true
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
	return nil
}

func syncCopy(writer io.Writer, reader io.Reader, buf *bufReadWriter) (int64, error) {
	written := int64(0)
	copyBuf := make([]byte, 32*1024)
	for {
		nr, err := reader.Read(copyBuf)
		if nr > 0 {
			buf.mutex.Lock()
			nw, err := writer.Write(copyBuf[:nr])
			buf.mutex.Unlock()
			if err != nil {
				return written, err
			}
			written += int64(nw)
			buf.Signal()
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return written, err
		}
	}
	return written, nil
Matt Bell's avatar
Matt Bell committed
219
}