github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/core/commands/get.go (about)

     1  package commands
     2  
     3  import (
     4  	"compress/gzip"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	gopath "path"
    10  	"strings"
    11  
    12  	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb"
    13  
    14  	cmds "github.com/ipfs/go-ipfs/commands"
    15  	core "github.com/ipfs/go-ipfs/core"
    16  	path "github.com/ipfs/go-ipfs/path"
    17  	tar "github.com/ipfs/go-ipfs/thirdparty/tar"
    18  	uarchive "github.com/ipfs/go-ipfs/unixfs/archive"
    19  )
    20  
    21  var ErrInvalidCompressionLevel = errors.New("Compression level must be between 1 and 9")
    22  
    23  var GetCmd = &cmds.Command{
    24  	Helptext: cmds.HelpText{
    25  		Tagline: "Download IPFS objects",
    26  		ShortDescription: `
    27  Retrieves the object named by <ipfs-or-ipns-path> and stores the data to disk.
    28  
    29  By default, the output will be stored at ./<ipfs-path>, but an alternate path
    30  can be specified with '--output=<path>' or '-o=<path>'.
    31  
    32  To output a TAR archive instead of unpacked files, use '--archive' or '-a'.
    33  
    34  To compress the output with GZIP compression, use '--compress' or '-C'. You
    35  may also specify the level of compression by specifying '-l=<1-9>'.
    36  `,
    37  	},
    38  
    39  	Arguments: []cmds.Argument{
    40  		cmds.StringArg("ipfs-path", true, false, "The path to the IPFS object(s) to be outputted").EnableStdin(),
    41  	},
    42  	Options: []cmds.Option{
    43  		cmds.StringOption("output", "o", "The path where output should be stored"),
    44  		cmds.BoolOption("archive", "a", "Output a TAR archive"),
    45  		cmds.BoolOption("compress", "C", "Compress the output with GZIP compression"),
    46  		cmds.IntOption("compression-level", "l", "The level of compression (1-9)"),
    47  	},
    48  	PreRun: func(req cmds.Request) error {
    49  		_, err := getCompressOptions(req)
    50  		return err
    51  	},
    52  	Run: func(req cmds.Request, res cmds.Response) {
    53  		cmplvl, err := getCompressOptions(req)
    54  		if err != nil {
    55  			res.SetError(err, cmds.ErrClient)
    56  			return
    57  		}
    58  
    59  		node, err := req.InvocContext().GetNode()
    60  		if err != nil {
    61  			res.SetError(err, cmds.ErrNormal)
    62  			return
    63  		}
    64  		p := path.Path(req.Arguments()[0])
    65  		ctx := req.Context()
    66  		dn, err := core.Resolve(ctx, node, p)
    67  		if err != nil {
    68  			res.SetError(err, cmds.ErrNormal)
    69  			return
    70  		}
    71  
    72  		archive, _, _ := req.Option("archive").Bool()
    73  		reader, err := uarchive.DagArchive(ctx, dn, p.String(), node.DAG, archive, cmplvl)
    74  		if err != nil {
    75  			res.SetError(err, cmds.ErrNormal)
    76  			return
    77  		}
    78  		res.SetOutput(reader)
    79  	},
    80  	PostRun: func(req cmds.Request, res cmds.Response) {
    81  		if res.Output() == nil {
    82  			return
    83  		}
    84  		outReader := res.Output().(io.Reader)
    85  		res.SetOutput(nil)
    86  
    87  		outPath, _, _ := req.Option("output").String()
    88  		if len(outPath) == 0 {
    89  			_, outPath = gopath.Split(req.Arguments()[0])
    90  			outPath = gopath.Clean(outPath)
    91  		}
    92  
    93  		cmplvl, err := getCompressOptions(req)
    94  		if err != nil {
    95  			res.SetError(err, cmds.ErrClient)
    96  			return
    97  		}
    98  
    99  		archive, _, _ := req.Option("archive").Bool()
   100  
   101  		gw := getWriter{
   102  			Out:         os.Stdout,
   103  			Err:         os.Stderr,
   104  			Archive:     archive,
   105  			Compression: cmplvl,
   106  		}
   107  
   108  		if err := gw.Write(outReader, outPath); err != nil {
   109  			res.SetError(err, cmds.ErrNormal)
   110  			return
   111  		}
   112  	},
   113  }
   114  
   115  func progressBarForReader(out io.Writer, r io.Reader) (*pb.ProgressBar, *pb.Reader) {
   116  	// setup bar reader
   117  	// TODO: get total length of files
   118  	bar := pb.New(0).SetUnits(pb.U_BYTES)
   119  	bar.Output = out
   120  	barR := bar.NewProxyReader(r)
   121  	return bar, barR
   122  }
   123  
   124  type getWriter struct {
   125  	Out io.Writer // for output to user
   126  	Err io.Writer // for progress bar output
   127  
   128  	Archive     bool
   129  	Compression int
   130  }
   131  
   132  func (gw *getWriter) Write(r io.Reader, fpath string) error {
   133  	if gw.Archive || gw.Compression != gzip.NoCompression {
   134  		return gw.writeArchive(r, fpath)
   135  	}
   136  	return gw.writeExtracted(r, fpath)
   137  }
   138  
   139  func (gw *getWriter) writeArchive(r io.Reader, fpath string) error {
   140  	// adjust file name if tar
   141  	if gw.Archive {
   142  		if !strings.HasSuffix(fpath, ".tar") && !strings.HasSuffix(fpath, ".tar.gz") {
   143  			fpath += ".tar"
   144  		}
   145  	}
   146  
   147  	// adjust file name if gz
   148  	if gw.Compression != gzip.NoCompression {
   149  		if !strings.HasSuffix(fpath, ".gz") {
   150  			fpath += ".gz"
   151  		}
   152  	}
   153  
   154  	// create file
   155  	file, err := os.Create(fpath)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	defer file.Close()
   160  
   161  	fmt.Fprintf(gw.Out, "Saving archive to %s\n", fpath)
   162  	bar, barR := progressBarForReader(gw.Err, r)
   163  	bar.Start()
   164  	defer bar.Finish()
   165  
   166  	_, err = io.Copy(file, barR)
   167  	return err
   168  }
   169  
   170  func (gw *getWriter) writeExtracted(r io.Reader, fpath string) error {
   171  	fmt.Fprintf(gw.Out, "Saving file(s) to %s\n", fpath)
   172  	bar, barR := progressBarForReader(gw.Err, r)
   173  	bar.Start()
   174  	defer bar.Finish()
   175  
   176  	extractor := &tar.Extractor{fpath}
   177  	return extractor.Extract(barR)
   178  }
   179  
   180  func getCompressOptions(req cmds.Request) (int, error) {
   181  	cmprs, _, _ := req.Option("compress").Bool()
   182  	cmplvl, cmplvlFound, _ := req.Option("compression-level").Int()
   183  	switch {
   184  	case !cmprs:
   185  		return gzip.NoCompression, nil
   186  	case cmprs && !cmplvlFound:
   187  		return gzip.DefaultCompression, nil
   188  	case cmprs && cmplvlFound && (cmplvl < 1 || cmplvl > 9):
   189  		return gzip.NoCompression, ErrInvalidCompressionLevel
   190  	}
   191  	return gzip.NoCompression, nil
   192  }