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  }