add.go 4.86 KB
Newer Older
1 2 3
package commands

import (
4
	"errors"
5 6
	"fmt"
	"io"
7 8 9
	"io/ioutil"
	"os"
	"path/filepath"
10 11

	cmds "github.com/jbenet/go-ipfs/commands"
12
	core "github.com/jbenet/go-ipfs/core"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
13
	internal "github.com/jbenet/go-ipfs/core/commands2/internal"
14
	importer "github.com/jbenet/go-ipfs/importer"
15
	"github.com/jbenet/go-ipfs/importer/chunk"
16
	dag "github.com/jbenet/go-ipfs/merkledag"
17
	pinning "github.com/jbenet/go-ipfs/pin"
18
	ft "github.com/jbenet/go-ipfs/unixfs"
19 20 21 22 23
)

// Error indicating the max depth has been exceded.
var ErrDepthLimitExceeded = fmt.Errorf("depth limit exceeded")

24
type AddOutput struct {
25
	Added []*Object
26 27
}

28 29
var addCmd = &cmds.Command{
	Options: []cmds.Option{
30
		cmds.BoolOption("recursive", "r", "Must be specified when adding directories"),
31 32
	},
	Arguments: []cmds.Argument{
33
		cmds.StringArg("file", true, true, "The path to a file to be added to IPFS"),
34
	},
35 36
	Description: "Add an object to ipfs.",
	Help: `Adds contents of <path> to ipfs. Use -r to add directories.
Matt Bell's avatar
Matt Bell committed
37 38 39
Note that directories are added recursively, to form the ipfs
MerkleDAG. A smarter partial add with a staging area (like git)
remains to be implemented.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
40
`,
41
	Run: func(req cmds.Request) (interface{}, error) {
42 43
		n := req.Context().Node

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
		// THIS IS A HORRIBLE HACK -- FIXME!!!
		// see https://github.com/jbenet/go-ipfs/issues/309
		var added []*Object

		// returns the last one
		addDagnodes := func(dns []*dag.Node) error {
			for _, dn := range dns {
				o, err := getOutput(dn)
				if err != nil {
					return err
				}

				added = append(added, o)
			}
			return nil
59
		}
60

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
		addFile := func(name string) (*dag.Node, error) {
			f, err := os.Open(name)
			if err != nil {
				return nil, err
			}
			defer f.Close()

			dns, err := add(n, []io.Reader{f})
			if err != nil {
				return nil, err
			}

			if err := addDagnodes(dns); err != nil {
				return nil, err
			}
			return dns[len(dns)-1], nil // last dag node is the file.
		}

		var addPath func(name string) (*dag.Node, error)
		addDir := func(name string) (*dag.Node, error) {
			tree := &dag.Node{Data: ft.FolderPBData()}

			entries, err := ioutil.ReadDir(name)
			if err != nil {
				return nil, err
			}

			// construct nodes for containing files.
			for _, e := range entries {
				fp := filepath.Join(name, e.Name())
				nd, err := addPath(fp)
				if err != nil {
					return nil, err
				}

				if err = tree.AddNodeLink(e.Name(), nd); err != nil {
					return nil, err
				}
			}

			log.Infof("adding dir: %s", name)

			if err := addDagnodes([]*dag.Node{tree}); err != nil {
				return nil, err
			}
			return tree, nil
107 108
		}

109 110
		addPath = func(fpath string) (*dag.Node, error) {
			fi, err := os.Stat(fpath)
111
			if err != nil {
112
				return nil, err
113
			}
114

115 116 117 118 119 120 121 122 123
			if fi.IsDir() {
				return addDir(fpath)
			}
			return addFile(fpath)
		}

		paths, err := internal.CastToStrings(req.Arguments())
		if err != nil {
			return nil, err
124 125
		}

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
		for _, f := range paths {
			if _, err := addPath(f); err != nil {
				return nil, err
			}
		}

		// readers, err := internal.CastToReaders(req.Arguments())
		// if err != nil {
		// 	return nil, err
		// }
		//
		// dagnodes, err := add(n, readers)
		// if err != nil {
		// 	return nil, err
		// }
		//
		// // TODO: include fs paths in output (will need a way to specify paths in underlying filearg system)
		// added := make([]*Object, 0, len(req.Arguments()))
		// for _, dagnode := range dagnodes {
		// 	object, err := getOutput(dagnode)
		// 	if err != nil {
		// 		return nil, err
		// 	}
		//
		// 	added = append(added, object)
		// }

153
		return &AddOutput{added}, nil
154
	},
155 156
	Marshallers: map[cmds.EncodingType]cmds.Marshaller{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
157 158 159 160 161 162 163
			val, ok := res.Output().(*AddOutput)
			if !ok {
				return nil, errors.New("cast err")
			}
			added := val.Added
			if len(added) == 1 {
				s := fmt.Sprintf("Added object: %s\n", added[0].Hash)
164 165
				return []byte(s), nil
			}
166

Brian Tiger Chow's avatar
Brian Tiger Chow committed
167 168
			s := fmt.Sprintf("Added %v objects:\n", len(added))
			for _, obj := range added {
169 170 171 172
				s += fmt.Sprintf("- %s\n", obj.Hash)
			}
			return []byte(s), nil
		},
173
	},
174
	Type: &AddOutput{},
175 176
}

177
func add(n *core.IpfsNode, readers []io.Reader) ([]*dag.Node, error) {
178 179 180 181 182
	mp, ok := n.Pinning.(pinning.ManualPinner)
	if !ok {
		return nil, errors.New("invalid pinner type! expected manual pinner")
	}

183
	dagnodes := make([]*dag.Node, 0)
184

185 186
	// TODO: allow adding directories (will need support for multiple files in filearg system)

187
	for _, reader := range readers {
188
		node, err := importer.BuildDagFromReader(reader, n.DAG, mp, chunk.DefaultSplitter)
189 190 191 192
		if err != nil {
			return nil, err
		}

193
		err = addNode(n, node)
194 195 196 197 198 199
		if err != nil {
			return nil, err
		}

		dagnodes = append(dagnodes, node)
	}
200

201
	return dagnodes, nil
202
}
203 204 205 206 207 208 209 210 211 212 213 214 215 216

func addNode(n *core.IpfsNode, node *dag.Node) error {
	err := n.DAG.AddRecursive(node) // add the file to the graph + local storage
	if err != nil {
		return err
	}

	err = n.Pinning.Pin(node, true) // ensure we keep it
	if err != nil {
		return err
	}

	return nil
}