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

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"strings"
    11  	"text/tabwriter"
    12  
    13  	mh "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
    14  
    15  	key "github.com/ipfs/go-ipfs/blocks/key"
    16  	cmds "github.com/ipfs/go-ipfs/commands"
    17  	core "github.com/ipfs/go-ipfs/core"
    18  	dag "github.com/ipfs/go-ipfs/merkledag"
    19  	dagutils "github.com/ipfs/go-ipfs/merkledag/utils"
    20  	path "github.com/ipfs/go-ipfs/path"
    21  	ft "github.com/ipfs/go-ipfs/unixfs"
    22  	u "github.com/ipfs/go-ipfs/util"
    23  )
    24  
    25  // ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
    26  var ErrObjectTooLarge = errors.New("input object was too large. limit is 512kbytes")
    27  
    28  const inputLimit = 512 * 1024
    29  
    30  type Node struct {
    31  	Links []Link
    32  	Data  string
    33  }
    34  
    35  type Link struct {
    36  	Name, Hash string
    37  	Size       uint64
    38  }
    39  
    40  type Object struct {
    41  	Hash  string
    42  	Links []Link
    43  }
    44  
    45  var ObjectCmd = &cmds.Command{
    46  	Helptext: cmds.HelpText{
    47  		Tagline: "Interact with ipfs objects",
    48  		ShortDescription: `
    49  'ipfs object' is a plumbing command used to manipulate DAG objects
    50  directly.`,
    51  		Synopsis: `
    52  ipfs object get <key>       - Get the DAG node named by <key>
    53  ipfs object put <data>      - Stores input, outputs its key
    54  ipfs object data <key>      - Outputs raw bytes in an object
    55  ipfs object links <key>     - Outputs links pointed to by object
    56  ipfs object stat <key>      - Outputs statistics of object
    57  ipfs object new <template>  - Create new ipfs objects
    58  ipfs object patch <args>    - Create new object from old ones
    59  `,
    60  	},
    61  
    62  	Subcommands: map[string]*cmds.Command{
    63  		"data":  objectDataCmd,
    64  		"links": objectLinksCmd,
    65  		"get":   objectGetCmd,
    66  		"put":   objectPutCmd,
    67  		"stat":  objectStatCmd,
    68  		"new":   objectNewCmd,
    69  		"patch": objectPatchCmd,
    70  	},
    71  }
    72  
    73  var objectDataCmd = &cmds.Command{
    74  	Helptext: cmds.HelpText{
    75  		Tagline: "Outputs the raw bytes in an IPFS object",
    76  		ShortDescription: `
    77  ipfs object data is a plumbing command for retreiving the raw bytes stored in
    78  a DAG node. It outputs to stdout, and <key> is a base58 encoded
    79  multihash.
    80  `,
    81  		LongDescription: `
    82  ipfs object data is a plumbing command for retreiving the raw bytes stored in
    83  a DAG node. It outputs to stdout, and <key> is a base58 encoded
    84  multihash.
    85  
    86  Note that the "--encoding" option does not affect the output, since the
    87  output is the raw data of the object.
    88  `,
    89  	},
    90  
    91  	Arguments: []cmds.Argument{
    92  		cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format").EnableStdin(),
    93  	},
    94  	Run: func(req cmds.Request, res cmds.Response) {
    95  		n, err := req.InvocContext().GetNode()
    96  		if err != nil {
    97  			res.SetError(err, cmds.ErrNormal)
    98  			return
    99  		}
   100  
   101  		fpath := path.Path(req.Arguments()[0])
   102  		node, err := core.Resolve(req.Context(), n, fpath)
   103  		if err != nil {
   104  			res.SetError(err, cmds.ErrNormal)
   105  			return
   106  		}
   107  		res.SetOutput(bytes.NewReader(node.Data))
   108  	},
   109  }
   110  
   111  var objectLinksCmd = &cmds.Command{
   112  	Helptext: cmds.HelpText{
   113  		Tagline: "Outputs the links pointed to by the specified object",
   114  		ShortDescription: `
   115  'ipfs object links' is a plumbing command for retreiving the links from
   116  a DAG node. It outputs to stdout, and <key> is a base58 encoded
   117  multihash.
   118  `,
   119  	},
   120  
   121  	Arguments: []cmds.Argument{
   122  		cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format").EnableStdin(),
   123  	},
   124  	Run: func(req cmds.Request, res cmds.Response) {
   125  		n, err := req.InvocContext().GetNode()
   126  		if err != nil {
   127  			res.SetError(err, cmds.ErrNormal)
   128  			return
   129  		}
   130  
   131  		fpath := path.Path(req.Arguments()[0])
   132  		node, err := core.Resolve(req.Context(), n, fpath)
   133  		if err != nil {
   134  			res.SetError(err, cmds.ErrNormal)
   135  			return
   136  		}
   137  		output, err := getOutput(node)
   138  		if err != nil {
   139  			res.SetError(err, cmds.ErrNormal)
   140  			return
   141  		}
   142  		res.SetOutput(output)
   143  	},
   144  	Marshalers: cmds.MarshalerMap{
   145  		cmds.Text: func(res cmds.Response) (io.Reader, error) {
   146  			object := res.Output().(*Object)
   147  			buf := new(bytes.Buffer)
   148  			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
   149  			fmt.Fprintln(w, "Hash\tSize\tName\t")
   150  			for _, link := range object.Links {
   151  				fmt.Fprintf(w, "%s\t%v\t%s\t\n", link.Hash, link.Size, link.Name)
   152  			}
   153  			w.Flush()
   154  			return buf, nil
   155  		},
   156  	},
   157  	Type: Object{},
   158  }
   159  
   160  var objectGetCmd = &cmds.Command{
   161  	Helptext: cmds.HelpText{
   162  		Tagline: "Get and serialize the DAG node named by <key>",
   163  		ShortDescription: `
   164  'ipfs object get' is a plumbing command for retreiving DAG nodes.
   165  It serializes the DAG node to the format specified by the "--encoding"
   166  flag. It outputs to stdout, and <key> is a base58 encoded multihash.
   167  `,
   168  		LongDescription: `
   169  'ipfs object get' is a plumbing command for retreiving DAG nodes.
   170  It serializes the DAG node to the format specified by the "--encoding"
   171  flag. It outputs to stdout, and <key> is a base58 encoded multihash.
   172  
   173  This command outputs data in the following encodings:
   174    * "protobuf"
   175    * "json"
   176    * "xml"
   177  (Specified by the "--encoding" or "-enc" flag)`,
   178  	},
   179  
   180  	Arguments: []cmds.Argument{
   181  		cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)").EnableStdin(),
   182  	},
   183  	Run: func(req cmds.Request, res cmds.Response) {
   184  		n, err := req.InvocContext().GetNode()
   185  		if err != nil {
   186  			res.SetError(err, cmds.ErrNormal)
   187  			return
   188  		}
   189  
   190  		fpath := path.Path(req.Arguments()[0])
   191  
   192  		object, err := core.Resolve(req.Context(), n, fpath)
   193  		if err != nil {
   194  			res.SetError(err, cmds.ErrNormal)
   195  			return
   196  		}
   197  
   198  		node := &Node{
   199  			Links: make([]Link, len(object.Links)),
   200  			Data:  string(object.Data),
   201  		}
   202  
   203  		for i, link := range object.Links {
   204  			node.Links[i] = Link{
   205  				Hash: link.Hash.B58String(),
   206  				Name: link.Name,
   207  				Size: link.Size,
   208  			}
   209  		}
   210  
   211  		res.SetOutput(node)
   212  	},
   213  	Type: Node{},
   214  	Marshalers: cmds.MarshalerMap{
   215  		cmds.EncodingType("protobuf"): func(res cmds.Response) (io.Reader, error) {
   216  			node := res.Output().(*Node)
   217  			object, err := deserializeNode(node)
   218  			if err != nil {
   219  				return nil, err
   220  			}
   221  
   222  			marshaled, err := object.Marshal()
   223  			if err != nil {
   224  				return nil, err
   225  			}
   226  			return bytes.NewReader(marshaled), nil
   227  		},
   228  	},
   229  }
   230  
   231  var objectStatCmd = &cmds.Command{
   232  	Helptext: cmds.HelpText{
   233  		Tagline: "Get stats for the DAG node named by <key>",
   234  		ShortDescription: `
   235  'ipfs object stat' is a plumbing command to print DAG node statistics.
   236  <key> is a base58 encoded multihash. It outputs to stdout:
   237  
   238  	NumLinks        int number of links in link table
   239  	BlockSize       int size of the raw, encoded data
   240  	LinksSize       int size of the links segment
   241  	DataSize        int size of the data segment
   242  	CumulativeSize  int cumulative size of object and its references
   243  `,
   244  	},
   245  
   246  	Arguments: []cmds.Argument{
   247  		cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)").EnableStdin(),
   248  	},
   249  	Run: func(req cmds.Request, res cmds.Response) {
   250  		n, err := req.InvocContext().GetNode()
   251  		if err != nil {
   252  			res.SetError(err, cmds.ErrNormal)
   253  			return
   254  		}
   255  
   256  		fpath := path.Path(req.Arguments()[0])
   257  
   258  		object, err := core.Resolve(req.Context(), n, fpath)
   259  		if err != nil {
   260  			res.SetError(err, cmds.ErrNormal)
   261  			return
   262  		}
   263  
   264  		ns, err := object.Stat()
   265  		if err != nil {
   266  			res.SetError(err, cmds.ErrNormal)
   267  			return
   268  		}
   269  
   270  		res.SetOutput(ns)
   271  	},
   272  	Type: dag.NodeStat{},
   273  	Marshalers: cmds.MarshalerMap{
   274  		cmds.Text: func(res cmds.Response) (io.Reader, error) {
   275  			ns := res.Output().(*dag.NodeStat)
   276  
   277  			buf := new(bytes.Buffer)
   278  			w := func(s string, n int) {
   279  				fmt.Fprintf(buf, "%s: %d\n", s, n)
   280  			}
   281  			w("NumLinks", ns.NumLinks)
   282  			w("BlockSize", ns.BlockSize)
   283  			w("LinksSize", ns.LinksSize)
   284  			w("DataSize", ns.DataSize)
   285  			w("CumulativeSize", ns.CumulativeSize)
   286  
   287  			return buf, nil
   288  		},
   289  	},
   290  }
   291  
   292  var objectPutCmd = &cmds.Command{
   293  	Helptext: cmds.HelpText{
   294  		Tagline: "Stores input as a DAG object, outputs its key",
   295  		ShortDescription: `
   296  'ipfs object put' is a plumbing command for storing DAG nodes.
   297  It reads from stdin, and the output is a base58 encoded multihash.
   298  `,
   299  		LongDescription: `
   300  'ipfs object put' is a plumbing command for storing DAG nodes.
   301  It reads from stdin, and the output is a base58 encoded multihash.
   302  
   303  Data should be in the format specified by the --inputenc flag.
   304  --inputenc may be one of the following:
   305  	* "protobuf"
   306  	* "json" (default)
   307  
   308  Examples:
   309  
   310  	echo '{ "Data": "abc" }' | ipfs object put
   311  
   312  This creates a node with the data "abc" and no links. For an object with links,
   313  create a file named node.json with the contents:
   314  
   315      {
   316          "Data": "another",
   317          "Links": [ {
   318              "Name": "some link",
   319              "Hash": "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V",
   320              "Size": 8
   321          } ]
   322      }
   323  
   324  and then run
   325  
   326  	ipfs object put node.json
   327  `,
   328  	},
   329  
   330  	Arguments: []cmds.Argument{
   331  		cmds.FileArg("data", true, false, "Data to be stored as a DAG object").EnableStdin(),
   332  	},
   333  	Options: []cmds.Option{
   334  		cmds.StringOption("inputenc", "Encoding type of input data, either \"protobuf\" or \"json\""),
   335  	},
   336  	Run: func(req cmds.Request, res cmds.Response) {
   337  		n, err := req.InvocContext().GetNode()
   338  		if err != nil {
   339  			res.SetError(err, cmds.ErrNormal)
   340  			return
   341  		}
   342  
   343  		input, err := req.Files().NextFile()
   344  		if err != nil && err != io.EOF {
   345  			res.SetError(err, cmds.ErrNormal)
   346  			return
   347  		}
   348  
   349  		inputenc, found, err := req.Option("inputenc").String()
   350  		if err != nil {
   351  			res.SetError(err, cmds.ErrNormal)
   352  			return
   353  		}
   354  		if !found {
   355  			inputenc = "json"
   356  		}
   357  
   358  		output, err := objectPut(n, input, inputenc)
   359  		if err != nil {
   360  			errType := cmds.ErrNormal
   361  			if err == ErrUnknownObjectEnc {
   362  				errType = cmds.ErrClient
   363  			}
   364  			res.SetError(err, errType)
   365  			return
   366  		}
   367  
   368  		res.SetOutput(output)
   369  	},
   370  	Marshalers: cmds.MarshalerMap{
   371  		cmds.Text: func(res cmds.Response) (io.Reader, error) {
   372  			object := res.Output().(*Object)
   373  			return strings.NewReader("added " + object.Hash), nil
   374  		},
   375  	},
   376  	Type: Object{},
   377  }
   378  
   379  var objectNewCmd = &cmds.Command{
   380  	Helptext: cmds.HelpText{
   381  		Tagline: "creates a new object from an ipfs template",
   382  		ShortDescription: `
   383  'ipfs object new' is a plumbing command for creating new DAG nodes.
   384  `,
   385  		LongDescription: `
   386  'ipfs object new' is a plumbing command for creating new DAG nodes.
   387  By default it creates and returns a new empty merkledag node, but
   388  you may pass an optional template argument to create a preformatted
   389  node.
   390  
   391  Available templates:
   392  	* unixfs-dir
   393  `,
   394  	},
   395  	Arguments: []cmds.Argument{
   396  		cmds.StringArg("template", false, false, "optional template to use"),
   397  	},
   398  	Run: func(req cmds.Request, res cmds.Response) {
   399  		n, err := req.InvocContext().GetNode()
   400  		if err != nil {
   401  			res.SetError(err, cmds.ErrNormal)
   402  			return
   403  		}
   404  
   405  		node := new(dag.Node)
   406  		if len(req.Arguments()) == 1 {
   407  			template := req.Arguments()[0]
   408  			var err error
   409  			node, err = nodeFromTemplate(template)
   410  			if err != nil {
   411  				res.SetError(err, cmds.ErrNormal)
   412  				return
   413  			}
   414  		}
   415  
   416  		k, err := n.DAG.Add(node)
   417  		if err != nil {
   418  			res.SetError(err, cmds.ErrNormal)
   419  			return
   420  		}
   421  		res.SetOutput(&Object{Hash: k.B58String()})
   422  	},
   423  	Marshalers: cmds.MarshalerMap{
   424  		cmds.Text: func(res cmds.Response) (io.Reader, error) {
   425  			object := res.Output().(*Object)
   426  			return strings.NewReader(object.Hash + "\n"), nil
   427  		},
   428  	},
   429  	Type: Object{},
   430  }
   431  
   432  var objectPatchCmd = &cmds.Command{
   433  	Helptext: cmds.HelpText{
   434  		Tagline: "Create a new merkledag object based on an existing one",
   435  		ShortDescription: `
   436  'ipfs object patch <root> [add-link|rm-link] <args>' is a plumbing command used to
   437  build custom DAG objects. It adds and removes links from objects, creating a new
   438  object as a result. This is the merkle-dag version of modifying an object.
   439  
   440  Examples:
   441  
   442      EMPTY_DIR=$(ipfs object new unixfs-dir)
   443      BAR=$(echo "bar" | ipfs add -q)
   444      ipfs object patch $EMPTY_DIR add-link foo $BAR
   445  
   446  This takes an empty directory, and adds a link named foo under it, pointing to
   447  a file containing 'bar', and returns the hash of the new object.
   448  
   449      ipfs object patch $FOO_BAR rm-link foo
   450  
   451  This removes the link named foo from the hash in $FOO_BAR and returns the
   452  resulting object hash.
   453  `,
   454  	},
   455  	Options: []cmds.Option{
   456  		cmds.BoolOption("create", "p", "create intermediate directories on add-link"),
   457  	},
   458  	Arguments: []cmds.Argument{
   459  		cmds.StringArg("root", true, false, "the hash of the node to modify"),
   460  		cmds.StringArg("command", true, false, "the operation to perform"),
   461  		cmds.StringArg("args", true, true, "extra arguments").EnableStdin(),
   462  	},
   463  	Type: Object{},
   464  	Run: func(req cmds.Request, res cmds.Response) {
   465  		nd, err := req.InvocContext().GetNode()
   466  		if err != nil {
   467  			res.SetError(err, cmds.ErrNormal)
   468  			return
   469  		}
   470  
   471  		rootarg := req.Arguments()[0]
   472  		if strings.HasPrefix(rootarg, "/ipfs/") {
   473  			rootarg = rootarg[6:]
   474  		}
   475  		rhash := key.B58KeyDecode(rootarg)
   476  		if rhash == "" {
   477  			res.SetError(fmt.Errorf("incorrectly formatted root hash: %s", req.Arguments()[0]), cmds.ErrNormal)
   478  			return
   479  		}
   480  
   481  		rnode, err := nd.DAG.Get(req.Context(), rhash)
   482  		if err != nil {
   483  			res.SetError(err, cmds.ErrNormal)
   484  			return
   485  		}
   486  
   487  		action := req.Arguments()[1]
   488  
   489  		switch action {
   490  		case "add-link":
   491  			k, err := addLinkCaller(req, rnode)
   492  			if err != nil {
   493  				res.SetError(err, cmds.ErrNormal)
   494  				return
   495  			}
   496  			res.SetOutput(&Object{Hash: k.B58String()})
   497  		case "rm-link":
   498  			k, err := rmLinkCaller(req, rnode)
   499  			if err != nil {
   500  				res.SetError(err, cmds.ErrNormal)
   501  				return
   502  			}
   503  			res.SetOutput(&Object{Hash: k.B58String()})
   504  		case "set-data":
   505  			k, err := setDataCaller(req, rnode)
   506  			if err != nil {
   507  				res.SetError(err, cmds.ErrNormal)
   508  				return
   509  			}
   510  			res.SetOutput(&Object{Hash: k.B58String()})
   511  		case "append-data":
   512  			k, err := appendDataCaller(req, rnode)
   513  			if err != nil {
   514  				res.SetError(err, cmds.ErrNormal)
   515  				return
   516  			}
   517  			res.SetOutput(&Object{Hash: k.B58String()})
   518  		default:
   519  			res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal)
   520  			return
   521  		}
   522  	},
   523  	Marshalers: cmds.MarshalerMap{
   524  		cmds.Text: func(res cmds.Response) (io.Reader, error) {
   525  			o, ok := res.Output().(*Object)
   526  			if !ok {
   527  				return nil, u.ErrCast()
   528  			}
   529  
   530  			return strings.NewReader(o.Hash + "\n"), nil
   531  		},
   532  	},
   533  }
   534  
   535  func appendDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
   536  	if len(req.Arguments()) < 3 {
   537  		return "", fmt.Errorf("not enough arguments for set-data")
   538  	}
   539  
   540  	nd, err := req.InvocContext().GetNode()
   541  	if err != nil {
   542  		return "", err
   543  	}
   544  
   545  	root.Data = append(root.Data, []byte(req.Arguments()[2])...)
   546  
   547  	newkey, err := nd.DAG.Add(root)
   548  	if err != nil {
   549  		return "", err
   550  	}
   551  
   552  	return newkey, nil
   553  }
   554  
   555  func setDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
   556  	if len(req.Arguments()) < 3 {
   557  		return "", fmt.Errorf("not enough arguments for set-data")
   558  	}
   559  
   560  	nd, err := req.InvocContext().GetNode()
   561  	if err != nil {
   562  		return "", err
   563  	}
   564  
   565  	root.Data = []byte(req.Arguments()[2])
   566  
   567  	newkey, err := nd.DAG.Add(root)
   568  	if err != nil {
   569  		return "", err
   570  	}
   571  
   572  	return newkey, nil
   573  }
   574  
   575  func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
   576  	if len(req.Arguments()) < 3 {
   577  		return "", fmt.Errorf("not enough arguments for rm-link")
   578  	}
   579  
   580  	nd, err := req.InvocContext().GetNode()
   581  	if err != nil {
   582  		return "", err
   583  	}
   584  
   585  	path := req.Arguments()[2]
   586  
   587  	e := dagutils.NewDagEditor(nd.DAG, root)
   588  
   589  	err = e.RmLink(req.Context(), path)
   590  	if err != nil {
   591  		return "", err
   592  	}
   593  
   594  	nnode := e.GetNode()
   595  
   596  	return nnode.Key()
   597  }
   598  
   599  func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
   600  	if len(req.Arguments()) < 4 {
   601  		return "", fmt.Errorf("not enough arguments for add-link")
   602  	}
   603  
   604  	nd, err := req.InvocContext().GetNode()
   605  	if err != nil {
   606  		return "", err
   607  	}
   608  
   609  	path := req.Arguments()[2]
   610  	childk := key.B58KeyDecode(req.Arguments()[3])
   611  
   612  	create, _, err := req.Option("create").Bool()
   613  	if err != nil {
   614  		return "", err
   615  	}
   616  
   617  	var createfunc func() *dag.Node
   618  	if create {
   619  		createfunc = func() *dag.Node {
   620  			return &dag.Node{Data: ft.FolderPBData()}
   621  		}
   622  	}
   623  
   624  	e := dagutils.NewDagEditor(nd.DAG, root)
   625  
   626  	childnd, err := nd.DAG.Get(req.Context(), childk)
   627  	if err != nil {
   628  		return "", err
   629  	}
   630  
   631  	err = e.InsertNodeAtPath(req.Context(), path, childnd, createfunc)
   632  	if err != nil {
   633  		return "", err
   634  	}
   635  
   636  	nnode := e.GetNode()
   637  
   638  	return nnode.Key()
   639  }
   640  
   641  func nodeFromTemplate(template string) (*dag.Node, error) {
   642  	switch template {
   643  	case "unixfs-dir":
   644  		nd := new(dag.Node)
   645  		nd.Data = ft.FolderPBData()
   646  		return nd, nil
   647  	default:
   648  		return nil, fmt.Errorf("template '%s' not found", template)
   649  	}
   650  }
   651  
   652  // ErrEmptyNode is returned when the input to 'ipfs object put' contains no data
   653  var ErrEmptyNode = errors.New("no data or links in this node")
   654  
   655  // objectPut takes a format option, serializes bytes from stdin and updates the dag with that data
   656  func objectPut(n *core.IpfsNode, input io.Reader, encoding string) (*Object, error) {
   657  
   658  	data, err := ioutil.ReadAll(io.LimitReader(input, inputLimit+10))
   659  	if err != nil {
   660  		return nil, err
   661  	}
   662  
   663  	if len(data) >= inputLimit {
   664  		return nil, ErrObjectTooLarge
   665  	}
   666  
   667  	var dagnode *dag.Node
   668  	switch getObjectEnc(encoding) {
   669  	case objectEncodingJSON:
   670  		node := new(Node)
   671  		err = json.Unmarshal(data, node)
   672  		if err != nil {
   673  			return nil, err
   674  		}
   675  
   676  		// check that we have data in the Node to add
   677  		// otherwise we will add the empty object without raising an error
   678  		if node.Data == "" && len(node.Links) == 0 {
   679  			return nil, ErrEmptyNode
   680  		}
   681  
   682  		dagnode, err = deserializeNode(node)
   683  		if err != nil {
   684  			return nil, err
   685  		}
   686  
   687  	case objectEncodingProtobuf:
   688  		dagnode, err = dag.Decoded(data)
   689  
   690  	default:
   691  		return nil, ErrUnknownObjectEnc
   692  	}
   693  
   694  	if err != nil {
   695  		return nil, err
   696  	}
   697  
   698  	_, err = n.DAG.Add(dagnode)
   699  	if err != nil {
   700  		return nil, err
   701  	}
   702  
   703  	return getOutput(dagnode)
   704  }
   705  
   706  // ErrUnknownObjectEnc is returned if a invalid encoding is supplied
   707  var ErrUnknownObjectEnc = errors.New("unknown object encoding")
   708  
   709  type objectEncoding string
   710  
   711  const (
   712  	objectEncodingJSON     objectEncoding = "json"
   713  	objectEncodingProtobuf                = "protobuf"
   714  )
   715  
   716  func getObjectEnc(o interface{}) objectEncoding {
   717  	v, ok := o.(string)
   718  	if !ok {
   719  		// chosen as default because it's human readable
   720  		log.Warning("option is not a string - falling back to json")
   721  		return objectEncodingJSON
   722  	}
   723  
   724  	return objectEncoding(v)
   725  }
   726  
   727  func getOutput(dagnode *dag.Node) (*Object, error) {
   728  	key, err := dagnode.Key()
   729  	if err != nil {
   730  		return nil, err
   731  	}
   732  
   733  	output := &Object{
   734  		Hash:  key.Pretty(),
   735  		Links: make([]Link, len(dagnode.Links)),
   736  	}
   737  
   738  	for i, link := range dagnode.Links {
   739  		output.Links[i] = Link{
   740  			Name: link.Name,
   741  			Hash: link.Hash.B58String(),
   742  			Size: link.Size,
   743  		}
   744  	}
   745  
   746  	return output, nil
   747  }
   748  
   749  // converts the Node object into a real dag.Node
   750  func deserializeNode(node *Node) (*dag.Node, error) {
   751  	dagnode := new(dag.Node)
   752  	dagnode.Data = []byte(node.Data)
   753  	dagnode.Links = make([]*dag.Link, len(node.Links))
   754  	for i, link := range node.Links {
   755  		hash, err := mh.FromB58String(link.Hash)
   756  		if err != nil {
   757  			return nil, err
   758  		}
   759  		dagnode.Links[i] = &dag.Link{
   760  			Name: link.Name,
   761  			Size: link.Size,
   762  			Hash: hash,
   763  		}
   764  	}
   765  
   766  	return dagnode, nil
   767  }