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 }