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

import (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
4
	"bytes"
5
	"errors"
6 7
	"fmt"
	"io"
8 9 10
	"io/ioutil"
	"os"
	"path/filepath"
11 12

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

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

26
type AddOutput struct {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
27 28
	Objects []*Object
	Names   []string
29 30
}

31 32
var addCmd = &cmds.Command{
	Options: []cmds.Option{
33
		cmds.BoolOption("recursive", "r", "Must be specified when adding directories"),
34 35
	},
	Arguments: []cmds.Argument{
36
		cmds.StringArg("file", true, true, "The path to a file to be added to IPFS"),
37
	},
38 39
	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
40 41 42
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
43
`,
44
	Run: func(req cmds.Request) (interface{}, error) {
45
		added := &AddOutput{}
46 47 48 49
		n, err := req.Context().GetNode()
		if err != nil {
			return nil, err
		}
50

51
		recursive, _, err := req.Option("r").Bool()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
52 53 54 55
		if err != nil {
			return nil, err
		}

56 57 58 59
		// THIS IS A HORRIBLE HACK -- FIXME!!!
		// see https://github.com/jbenet/go-ipfs/issues/309

		// returns the last one
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
60 61 62 63
		addDagnode := func(name string, dn *dag.Node) error {
			o, err := getOutput(dn)
			if err != nil {
				return err
64
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
65 66 67

			added.Objects = append(added.Objects, o)
			added.Names = append(added.Names, name)
68
			return nil
69
		}
70

71 72 73 74 75 76 77 78 79 80 81 82
		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
			}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
83
			log.Infof("adding file: %s", name)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
84
			if err := addDagnode(name, dns[len(dns)-1]); err != nil {
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
				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)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
113
			if err := addDagnode(name, tree); err != nil {
114 115 116
				return nil, err
			}
			return tree, nil
117 118
		}

119 120
		addPath = func(fpath string) (*dag.Node, error) {
			fi, err := os.Stat(fpath)
121
			if err != nil {
122
				return nil, err
123
			}
124

125
			if fi.IsDir() {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
126 127 128
				if !recursive {
					return nil, errors.New("use -r to recursively add directories")
				}
129 130 131 132 133 134 135
				return addDir(fpath)
			}
			return addFile(fpath)
		}

		paths, err := internal.CastToStrings(req.Arguments())
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
136
			panic(err)
137
			return nil, err
138 139
		}

140 141 142 143 144
		for _, f := range paths {
			if _, err := addPath(f); err != nil {
				return nil, err
			}
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
145
		return added, nil
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166

		// 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)
		// }
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
167 168
		//
		// return &AddOutput{added}, nil
169
	},
170 171
	Marshallers: map[cmds.EncodingType]cmds.Marshaller{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
172 173
			val, ok := res.Output().(*AddOutput)
			if !ok {
174
				return nil, u.ErrCast()
175
			}
176

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
177 178 179
			var buf bytes.Buffer
			for i, obj := range val.Objects {
				buf.Write([]byte(fmt.Sprintf("added %s %s\n", obj.Hash, val.Names[i])))
180
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
181
			return buf.Bytes(), nil
182
		},
183
	},
184
	Type: &AddOutput{},
185 186
}

187
func add(n *core.IpfsNode, readers []io.Reader) ([]*dag.Node, error) {
188 189 190 191 192
	mp, ok := n.Pinning.(pinning.ManualPinner)
	if !ok {
		return nil, errors.New("invalid pinner type! expected manual pinner")
	}

193
	dagnodes := make([]*dag.Node, 0)
194

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

197
	for _, reader := range readers {
198
		node, err := importer.BuildDagFromReader(reader, n.DAG, mp, chunk.DefaultSplitter)
199 200 201 202
		if err != nil {
			return nil, err
		}

203
		err = addNode(n, node)
204 205 206 207 208 209
		if err != nil {
			return nil, err
		}

		dagnodes = append(dagnodes, node)
	}
210

211
	return dagnodes, nil
212
}
213 214 215 216 217 218 219 220 221 222 223 224 225 226

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
}