github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/core/commands/add.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "io" 6 "path" 7 8 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb" 9 ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore" 10 syncds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore/sync" 11 cxt "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" 12 13 bstore "github.com/ipfs/go-ipfs/blocks/blockstore" 14 bserv "github.com/ipfs/go-ipfs/blockservice" 15 cmds "github.com/ipfs/go-ipfs/commands" 16 files "github.com/ipfs/go-ipfs/commands/files" 17 core "github.com/ipfs/go-ipfs/core" 18 offline "github.com/ipfs/go-ipfs/exchange/offline" 19 importer "github.com/ipfs/go-ipfs/importer" 20 "github.com/ipfs/go-ipfs/importer/chunk" 21 dag "github.com/ipfs/go-ipfs/merkledag" 22 dagutils "github.com/ipfs/go-ipfs/merkledag/utils" 23 pin "github.com/ipfs/go-ipfs/pin" 24 ft "github.com/ipfs/go-ipfs/unixfs" 25 u "github.com/ipfs/go-ipfs/util" 26 ) 27 28 // Error indicating the max depth has been exceded. 29 var ErrDepthLimitExceeded = fmt.Errorf("depth limit exceeded") 30 31 // how many bytes of progress to wait before sending a progress update message 32 const progressReaderIncrement = 1024 * 256 33 34 const ( 35 quietOptionName = "quiet" 36 progressOptionName = "progress" 37 trickleOptionName = "trickle" 38 wrapOptionName = "wrap-with-directory" 39 hiddenOptionName = "hidden" 40 onlyHashOptionName = "only-hash" 41 chunkerOptionName = "chunker" 42 ) 43 44 type AddedObject struct { 45 Name string 46 Hash string `json:",omitempty"` 47 Bytes int64 `json:",omitempty"` 48 } 49 50 var AddCmd = &cmds.Command{ 51 Helptext: cmds.HelpText{ 52 Tagline: "Add an object to ipfs.", 53 ShortDescription: ` 54 Adds contents of <path> to ipfs. Use -r to add directories. 55 Note that directories are added recursively, to form the ipfs 56 MerkleDAG. A smarter partial add with a staging area (like git) 57 remains to be implemented. 58 `, 59 }, 60 61 Arguments: []cmds.Argument{ 62 cmds.FileArg("path", true, true, "The path to a file to be added to IPFS").EnableRecursive().EnableStdin(), 63 }, 64 Options: []cmds.Option{ 65 cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive) 66 cmds.BoolOption(quietOptionName, "q", "Write minimal output"), 67 cmds.BoolOption(progressOptionName, "p", "Stream progress data"), 68 cmds.BoolOption(trickleOptionName, "t", "Use trickle-dag format for dag generation"), 69 cmds.BoolOption(onlyHashOptionName, "n", "Only chunk and hash - do not write to disk"), 70 cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object"), 71 cmds.BoolOption(hiddenOptionName, "Include files that are hidden"), 72 cmds.StringOption(chunkerOptionName, "s", "chunking algorithm to use"), 73 }, 74 PreRun: func(req cmds.Request) error { 75 if quiet, _, _ := req.Option(quietOptionName).Bool(); quiet { 76 return nil 77 } 78 79 req.SetOption(progressOptionName, true) 80 81 sizeFile, ok := req.Files().(files.SizeFile) 82 if !ok { 83 // we don't need to error, the progress bar just won't know how big the files are 84 return nil 85 } 86 87 size, err := sizeFile.Size() 88 if err != nil { 89 // see comment above 90 return nil 91 } 92 log.Debugf("Total size of file being added: %v\n", size) 93 req.Values()["size"] = size 94 95 return nil 96 }, 97 Run: func(req cmds.Request, res cmds.Response) { 98 n, err := req.InvocContext().GetNode() 99 if err != nil { 100 res.SetError(err, cmds.ErrNormal) 101 return 102 } 103 104 progress, _, _ := req.Option(progressOptionName).Bool() 105 trickle, _, _ := req.Option(trickleOptionName).Bool() 106 wrap, _, _ := req.Option(wrapOptionName).Bool() 107 hash, _, _ := req.Option(onlyHashOptionName).Bool() 108 hidden, _, _ := req.Option(hiddenOptionName).Bool() 109 chunker, _, _ := req.Option(chunkerOptionName).String() 110 111 e := dagutils.NewDagEditor(NewMemoryDagService(), newDirNode()) 112 if hash { 113 nilnode, err := core.NewNode(n.Context(), &core.BuildCfg{ 114 //TODO: need this to be true or all files 115 // hashed will be stored in memory! 116 NilRepo: true, 117 }) 118 if err != nil { 119 res.SetError(err, cmds.ErrNormal) 120 return 121 } 122 n = nilnode 123 } 124 125 outChan := make(chan interface{}, 8) 126 res.SetOutput((<-chan interface{})(outChan)) 127 128 fileAdder := adder{ 129 ctx: req.Context(), 130 node: n, 131 editor: e, 132 out: outChan, 133 chunker: chunker, 134 progress: progress, 135 hidden: hidden, 136 trickle: trickle, 137 wrap: wrap, 138 } 139 140 // addAllFiles loops over a convenience slice file to 141 // add each file individually. e.g. 'ipfs add a b c' 142 addAllFiles := func(sliceFile files.File) error { 143 for { 144 file, err := sliceFile.NextFile() 145 if err != nil && err != io.EOF { 146 return err 147 } 148 if file == nil { 149 return nil // done 150 } 151 152 if _, err := fileAdder.addFile(file); err != nil { 153 return err 154 } 155 } 156 } 157 158 pinRoot := func(rootnd *dag.Node) error { 159 rnk, err := rootnd.Key() 160 if err != nil { 161 return err 162 } 163 164 mp := n.Pinning.GetManual() 165 mp.RemovePinWithMode(rnk, pin.Indirect) 166 mp.PinWithMode(rnk, pin.Recursive) 167 return n.Pinning.Flush() 168 } 169 170 addAllAndPin := func(f files.File) error { 171 if err := addAllFiles(f); err != nil { 172 return err 173 } 174 175 if !hash { 176 // copy intermediary nodes from editor to our actual dagservice 177 err := e.WriteOutputTo(n.DAG) 178 if err != nil { 179 log.Error("WRITE OUT: ", err) 180 return err 181 } 182 } 183 184 rootnd, err := fileAdder.RootNode() 185 if err != nil { 186 return err 187 } 188 189 return pinRoot(rootnd) 190 } 191 192 go func() { 193 defer close(outChan) 194 if err := addAllAndPin(req.Files()); err != nil { 195 res.SetError(err, cmds.ErrNormal) 196 return 197 } 198 199 }() 200 }, 201 PostRun: func(req cmds.Request, res cmds.Response) { 202 if res.Error() != nil { 203 return 204 } 205 outChan, ok := res.Output().(<-chan interface{}) 206 if !ok { 207 res.SetError(u.ErrCast(), cmds.ErrNormal) 208 return 209 } 210 res.SetOutput(nil) 211 212 quiet, _, err := req.Option("quiet").Bool() 213 if err != nil { 214 res.SetError(u.ErrCast(), cmds.ErrNormal) 215 return 216 } 217 218 size := int64(0) 219 s, found := req.Values()["size"] 220 if found { 221 size = s.(int64) 222 } 223 showProgressBar := !quiet && size >= progressBarMinSize 224 225 var bar *pb.ProgressBar 226 var terminalWidth int 227 if showProgressBar { 228 bar = pb.New64(size).SetUnits(pb.U_BYTES) 229 bar.ManualUpdate = true 230 bar.Start() 231 232 // the progress bar lib doesn't give us a way to get the width of the output, 233 // so as a hack we just use a callback to measure the output, then git rid of it 234 terminalWidth = 0 235 bar.Callback = func(line string) { 236 terminalWidth = len(line) 237 bar.Callback = nil 238 bar.Output = res.Stderr() 239 log.Infof("terminal width: %v\n", terminalWidth) 240 } 241 bar.Update() 242 } 243 244 lastFile := "" 245 var totalProgress, prevFiles, lastBytes int64 246 247 for out := range outChan { 248 output := out.(*AddedObject) 249 if len(output.Hash) > 0 { 250 if showProgressBar { 251 // clear progress bar line before we print "added x" output 252 fmt.Fprintf(res.Stderr(), "\033[2K\r") 253 } 254 if quiet { 255 fmt.Fprintf(res.Stdout(), "%s\n", output.Hash) 256 } else { 257 fmt.Fprintf(res.Stdout(), "added %s %s\n", output.Hash, output.Name) 258 } 259 260 } else { 261 log.Debugf("add progress: %v %v\n", output.Name, output.Bytes) 262 263 if !showProgressBar { 264 continue 265 } 266 267 if len(lastFile) == 0 { 268 lastFile = output.Name 269 } 270 if output.Name != lastFile || output.Bytes < lastBytes { 271 prevFiles += lastBytes 272 lastFile = output.Name 273 } 274 lastBytes = output.Bytes 275 delta := prevFiles + lastBytes - totalProgress 276 totalProgress = bar.Add64(delta) 277 } 278 279 if showProgressBar { 280 bar.Update() 281 } 282 } 283 }, 284 Type: AddedObject{}, 285 } 286 287 func NewMemoryDagService() dag.DAGService { 288 // build mem-datastore for editor's intermediary nodes 289 bs := bstore.NewBlockstore(syncds.MutexWrap(ds.NewMapDatastore())) 290 bsrv := bserv.New(bs, offline.Exchange(bs)) 291 return dag.NewDAGService(bsrv) 292 } 293 294 // Internal structure for holding the switches passed to the `add` call 295 type adder struct { 296 ctx cxt.Context 297 node *core.IpfsNode 298 editor *dagutils.Editor 299 out chan interface{} 300 progress bool 301 hidden bool 302 trickle bool 303 wrap bool 304 chunker string 305 306 nextUntitled int 307 } 308 309 // Perform the actual add & pin locally, outputting results to reader 310 func add(n *core.IpfsNode, reader io.Reader, useTrickle bool, chunker string) (*dag.Node, error) { 311 chnk, err := chunk.FromString(reader, chunker) 312 if err != nil { 313 return nil, err 314 } 315 316 var node *dag.Node 317 if useTrickle { 318 node, err = importer.BuildTrickleDagFromReader( 319 n.DAG, 320 chnk, 321 importer.PinIndirectCB(n.Pinning.GetManual()), 322 ) 323 } else { 324 node, err = importer.BuildDagFromReader( 325 n.DAG, 326 chnk, 327 importer.PinIndirectCB(n.Pinning.GetManual()), 328 ) 329 } 330 331 if err != nil { 332 return nil, err 333 } 334 335 return node, nil 336 } 337 338 func (params *adder) RootNode() (*dag.Node, error) { 339 r := params.editor.GetNode() 340 341 // if not wrapping, AND one root file, use that hash as root. 342 if !params.wrap && len(r.Links) == 1 { 343 var err error 344 r, err = r.Links[0].GetNode(params.ctx, params.editor.GetDagService()) 345 // no need to output, as we've already done so. 346 return r, err 347 } 348 349 // otherwise need to output, as we have not. 350 err := outputDagnode(params.out, "", r) 351 return r, err 352 } 353 354 func (params *adder) addNode(node *dag.Node, path string) error { 355 // patch it into the root 356 if path == "" { 357 key, err := node.Key() 358 if err != nil { 359 return err 360 } 361 362 path = key.Pretty() 363 } 364 365 if err := params.editor.InsertNodeAtPath(params.ctx, path, node, newDirNode); err != nil { 366 return err 367 } 368 369 return outputDagnode(params.out, path, node) 370 } 371 372 // Add the given file while respecting the params. 373 func (params *adder) addFile(file files.File) (*dag.Node, error) { 374 // Check if file is hidden 375 if fileIsHidden := files.IsHidden(file); fileIsHidden && !params.hidden { 376 log.Debugf("%s is hidden, skipping", file.FileName()) 377 return nil, &hiddenFileError{file.FileName()} 378 } 379 380 // Check if "file" is actually a directory 381 if file.IsDirectory() { 382 return params.addDir(file) 383 } 384 385 if s, ok := file.(*files.Symlink); ok { 386 sdata, err := ft.SymlinkData(s.Target) 387 if err != nil { 388 return nil, err 389 } 390 391 dagnode := &dag.Node{Data: sdata} 392 _, err = params.node.DAG.Add(dagnode) 393 if err != nil { 394 return nil, err 395 } 396 397 err = params.addNode(dagnode, s.FileName()) 398 return dagnode, err 399 } 400 401 // if the progress flag was specified, wrap the file so that we can send 402 // progress updates to the client (over the output channel) 403 var reader io.Reader = file 404 if params.progress { 405 reader = &progressReader{file: file, out: params.out} 406 } 407 408 dagnode, err := add(params.node, reader, params.trickle, params.chunker) 409 if err != nil { 410 return nil, err 411 } 412 413 // patch it into the root 414 log.Infof("adding file: %s", file.FileName()) 415 err = params.addNode(dagnode, file.FileName()) 416 return dagnode, err 417 } 418 419 func (params *adder) addDir(file files.File) (*dag.Node, error) { 420 tree := &dag.Node{Data: ft.FolderPBData()} 421 log.Infof("adding directory: %s", file.FileName()) 422 423 for { 424 file, err := file.NextFile() 425 if err != nil && err != io.EOF { 426 return nil, err 427 } 428 if file == nil { 429 break 430 } 431 432 node, err := params.addFile(file) 433 if _, ok := err.(*hiddenFileError); ok { 434 // hidden file error, set the node to nil for below 435 node = nil 436 } else if err != nil { 437 return nil, err 438 } 439 440 if node != nil { 441 _, name := path.Split(file.FileName()) 442 443 err = tree.AddNodeLink(name, node) 444 if err != nil { 445 return nil, err 446 } 447 } 448 } 449 450 if err := params.addNode(tree, file.FileName()); err != nil { 451 return nil, err 452 } 453 454 k, err := params.node.DAG.Add(tree) 455 if err != nil { 456 return nil, err 457 } 458 459 params.node.Pinning.GetManual().PinWithMode(k, pin.Indirect) 460 461 return tree, nil 462 } 463 464 // outputDagnode sends dagnode info over the output channel 465 func outputDagnode(out chan interface{}, name string, dn *dag.Node) error { 466 o, err := getOutput(dn) 467 if err != nil { 468 return err 469 } 470 471 out <- &AddedObject{ 472 Hash: o.Hash, 473 Name: name, 474 } 475 476 return nil 477 } 478 479 type hiddenFileError struct { 480 fileName string 481 } 482 483 func (e *hiddenFileError) Error() string { 484 return fmt.Sprintf("%s is a hidden file", e.fileName) 485 } 486 487 type ignoreFileError struct { 488 fileName string 489 } 490 491 func (e *ignoreFileError) Error() string { 492 return fmt.Sprintf("%s is an ignored file", e.fileName) 493 } 494 495 type progressReader struct { 496 file files.File 497 out chan interface{} 498 bytes int64 499 lastProgress int64 500 } 501 502 func (i *progressReader) Read(p []byte) (int, error) { 503 n, err := i.file.Read(p) 504 505 i.bytes += int64(n) 506 if i.bytes-i.lastProgress >= progressReaderIncrement || err == io.EOF { 507 i.lastProgress = i.bytes 508 i.out <- &AddedObject{ 509 Name: i.file.FileName(), 510 Bytes: i.bytes, 511 } 512 } 513 514 return n, err 515 } 516 517 // TODO: generalize this to more than unix-fs nodes. 518 func newDirNode() *dag.Node { 519 return &dag.Node{Data: ft.FolderPBData()} 520 }