add.go 4.25 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
	"path"
9 10

	cmds "github.com/jbenet/go-ipfs/commands"
11 12
	core "github.com/jbenet/go-ipfs/core"
	importer "github.com/jbenet/go-ipfs/importer"
13
	"github.com/jbenet/go-ipfs/importer/chunk"
14
	dag "github.com/jbenet/go-ipfs/merkledag"
15
	pinning "github.com/jbenet/go-ipfs/pin"
16
	ft "github.com/jbenet/go-ipfs/unixfs"
17
	u "github.com/jbenet/go-ipfs/util"
18 19 20 21 22
)

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

23
type AddOutput struct {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
24 25
	Objects []*Object
	Names   []string
26
	Quiet   bool
27 28
}

29
var AddCmd = &cmds.Command{
30 31 32 33 34 35 36 37 38 39
	Helptext: cmds.HelpText{
		Tagline: "Add an object to ipfs.",
		ShortDescription: `
Adds contents of <path> to ipfs. Use -r to add directories.
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.
`,
	},

40
	Arguments: []cmds.Argument{
41
		cmds.FileArg("path", true, true, "The path to a file to be added to IPFS").EnableRecursive(),
42
	},
43 44
	Options: []cmds.Option{
		cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
45
		cmds.BoolOption("quiet", "q", "Write minimal output"),
46
	},
47
	Run: func(req cmds.Request) (interface{}, error) {
48
		added := &AddOutput{}
49 50 51 52
		n, err := req.Context().GetNode()
		if err != nil {
			return nil, err
		}
53

54 55 56
		for {
			file, err := req.Files().NextFile()
			if err != nil && err != io.EOF {
57 58
				return nil, err
			}
59 60
			if file == nil {
				break
61 62
			}

63
			_, err = addFile(n, file, added)
64
			if err != nil {
65
				return nil, err
66
			}
67 68
		}

69 70 71 72 73 74 75
		quiet, _, err := req.Option("quiet").Bool()
		if err != nil {
			return nil, err
		}

		added.Quiet = quiet

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
76
		return added, nil
77
	},
78
	Marshalers: cmds.MarshalerMap{
79
		cmds.Text: func(res cmds.Response) ([]byte, error) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
80 81
			val, ok := res.Output().(*AddOutput)
			if !ok {
82
				return nil, u.ErrCast()
83
			}
84

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
85 86
			var buf bytes.Buffer
			for i, obj := range val.Objects {
87 88 89 90 91
				if val.Quiet {
					buf.Write([]byte(fmt.Sprintf("%s\n", obj.Hash)))
				} else {
					buf.Write([]byte(fmt.Sprintf("added %s %s\n", obj.Hash, val.Names[i])))
				}
92
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
93
			return buf.Bytes(), nil
94
		},
95
	},
96
	Type: &AddOutput{},
97 98
}

99
func add(n *core.IpfsNode, readers []io.Reader) ([]*dag.Node, error) {
100 101 102 103 104
	mp, ok := n.Pinning.(pinning.ManualPinner)
	if !ok {
		return nil, errors.New("invalid pinner type! expected manual pinner")
	}

105
	dagnodes := make([]*dag.Node, 0)
106

107
	for _, reader := range readers {
108
		node, err := importer.BuildDagFromReader(reader, n.DAG, mp, chunk.DefaultSplitter)
109 110 111 112 113
		if err != nil {
			return nil, err
		}
		dagnodes = append(dagnodes, node)
	}
114

115
	return dagnodes, nil
116
}
117 118 119 120 121 122 123 124 125 126 127 128 129 130

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
}
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167

func addFile(n *core.IpfsNode, file cmds.File, added *AddOutput) (*dag.Node, error) {
	if file.IsDirectory() {
		return addDir(n, file, added)
	}

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

	log.Infof("adding file: %s", file.FileName())
	if err := addDagnode(added, file.FileName(), dns[len(dns)-1]); err != nil {
		return nil, err
	}
	return dns[len(dns)-1], nil // last dag node is the file.
}

func addDir(n *core.IpfsNode, dir cmds.File, added *AddOutput) (*dag.Node, error) {
	log.Infof("adding directory: %s", dir.FileName())

	tree := &dag.Node{Data: ft.FolderPBData()}

	for {
		file, err := dir.NextFile()
		if err != nil && err != io.EOF {
			return nil, err
		}
		if file == nil {
			break
		}

		node, err := addFile(n, file, added)
		if err != nil {
			return nil, err
		}

168
		_, name := path.Split(file.FileName())
169 170 171 172 173 174 175

		err = tree.AddNodeLink(name, node)
		if err != nil {
			return nil, err
		}
	}

176 177 178 179 180 181 182 183 184
	err := addDagnode(added, dir.FileName(), tree)
	if err != nil {
		return nil, err
	}

	err = addNode(n, tree)
	if err != nil {
		return nil, err
	}
185

186
	return tree, nil
187 188 189 190 191 192 193 194 195 196 197 198 199
}

// addDagnode adds dagnode info to an output object
func addDagnode(output *AddOutput, name string, dn *dag.Node) error {
	o, err := getOutput(dn)
	if err != nil {
		return err
	}

	output.Objects = append(output.Objects, o)
	output.Names = append(output.Names, name)
	return nil
}