get.go 4.25 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2 3
package commands

import (
4
	"compress/gzip"
5
	"errors"
6
	"fmt"
Matt Bell's avatar
Matt Bell committed
7
	"io"
8 9
	"os"
	"strings"
Matt Bell's avatar
Matt Bell committed
10 11 12

	cmds "github.com/jbenet/go-ipfs/commands"
	core "github.com/jbenet/go-ipfs/core"
13 14
	tar "github.com/jbenet/go-ipfs/thirdparty/tar"
	utar "github.com/jbenet/go-ipfs/unixfs/tar"
Matt Bell's avatar
Matt Bell committed
15

16
	"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb"
Matt Bell's avatar
Matt Bell committed
17 18
)

19 20
var ErrInvalidCompressionLevel = errors.New("Compression level must be between 1 and 9")

Matt Bell's avatar
Matt Bell committed
21 22 23 24 25 26 27 28 29 30
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'.
31 32 33

To compress the output with GZIP compression, use '--compress' or '-C'. You
may also specify the level of compression by specifying '-l=<1-9>'.
Matt Bell's avatar
Matt Bell committed
34 35 36 37
`,
	},

	Arguments: []cmds.Argument{
38
		cmds.StringArg("ipfs-path", true, false, "The path to the IPFS object(s) to be outputted").EnableStdin(),
Matt Bell's avatar
Matt Bell committed
39 40 41 42
	},
	Options: []cmds.Option{
		cmds.StringOption("output", "o", "The path where output should be stored"),
		cmds.BoolOption("archive", "a", "Output a TAR archive"),
43 44
		cmds.BoolOption("compress", "C", "Compress the output with GZIP compression"),
		cmds.IntOption("compression-level", "l", "The level of compression (an int between 1 and 9)"),
Matt Bell's avatar
Matt Bell committed
45
	},
46
	PreRun: getCheckOptions,
Matt Bell's avatar
Matt Bell committed
47
	Run: func(req cmds.Request, res cmds.Response) {
48 49 50 51 52 53
		err := getCheckOptions(req)
		if err != nil {
			res.SetError(err, cmds.ErrClient)
			return
		}

Matt Bell's avatar
Matt Bell committed
54 55 56 57 58 59
		node, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

60 61 62 63 64 65 66 67 68 69
		cmprs, _, _ := req.Option("compress").Bool()
		cmplvl, cmplvlFound, _ := req.Option("compression-level").Int()
		switch {
		case !cmprs:
			cmplvl = gzip.NoCompression
		case cmprs && !cmplvlFound:
			cmplvl = gzip.DefaultCompression
		case cmprs && cmplvlFound && (cmplvl < 1 || cmplvl > 9):
			res.SetError(ErrInvalidCompressionLevel, cmds.ErrClient)
			return
70 71
		}

72
		reader, err := get(node, req.Arguments()[0], cmplvl)
Matt Bell's avatar
Matt Bell committed
73 74 75 76 77 78
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(reader)
	},
79
	PostRun: func(req cmds.Request, res cmds.Response) {
80
		outReader := res.Output().(io.Reader)
81 82
		res.SetOutput(nil)

83
		outPath, _, _ := req.Option("output").String()
84
		if len(outPath) == 0 {
85
			outPath = req.Arguments()[0]
86 87
		}

88 89 90 91 92
		cmprs, _, _ := req.Option("compress").Bool()
		cmplvl, _, _ := req.Option("compression-level").Int()
		if !cmprs {
			cmprs = cmplvl > 0
		}
93 94

		if archive, _, _ := req.Option("archive").Bool(); archive {
95 96 97
			if !strings.HasSuffix(outPath, ".tar") {
				outPath += ".tar"
			}
98
			if cmprs {
99 100
				outPath += ".gz"
			}
101 102 103 104 105 106 107 108 109
			fmt.Printf("Saving archive to %s\n", outPath)

			file, err := os.Create(outPath)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			defer file.Close()

110 111
			bar := pb.New(0).SetUnits(pb.U_BYTES)
			bar.Output = os.Stderr
112
			pbReader := bar.NewProxyReader(outReader)
113 114 115 116
			bar.Start()
			defer bar.Finish()

			_, err = io.Copy(file, pbReader)
117 118 119 120 121 122 123 124 125 126
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

			return
		}

		fmt.Printf("Saving file(s) to %s\n", outPath)

127 128 129 130
		// TODO: get total length of files
		bar := pb.New(0).SetUnits(pb.U_BYTES)
		bar.Output = os.Stderr

131 132 133 134 135
		// wrap the reader with the progress bar proxy reader
		// if the output is compressed, also wrap it in a gzip.Reader
		var reader io.Reader
		if cmprs {
			gzipReader, err := gzip.NewReader(outReader)
136 137 138 139 140
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			defer gzipReader.Close()
141
			reader = bar.NewProxyReader(gzipReader)
142
		} else {
143
			reader = bar.NewProxyReader(outReader)
144
		}
145

146 147 148
		bar.Start()
		defer bar.Finish()

149 150 151 152
		extractor := &tar.Extractor{outPath}
		err := extractor.Extract(reader)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
153 154
		}
	},
Matt Bell's avatar
Matt Bell committed
155 156
}

157
func getCheckOptions(req cmds.Request) error {
158 159
	cmplvl, found, _ := req.Option("compression-level").Int()
	if found && (cmplvl < 1 || cmplvl > 9) {
160 161 162 163 164
		return ErrInvalidCompressionLevel
	}
	return nil
}

165
func get(node *core.IpfsNode, path string, compression int) (io.Reader, error) {
166
	return utar.NewReader(path, node.DAG, node.Resolver, compression)
Matt Bell's avatar
Matt Bell committed
167
}