add.go 10.3 KB
Newer Older
1 2 3 4 5
package commands

import (
	"fmt"
	"io"
6
	"path"
7

Jeromy's avatar
Jeromy committed
8 9
	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb"

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

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

24 25 26
// how many bytes of progress to wait before sending a progress update message
const progressReaderIncrement = 1024 * 256

27
const (
gatesvp's avatar
gatesvp committed
28
	quietOptionName    = "quiet"
29
	progressOptionName = "progress"
30
	trickleOptionName  = "trickle"
31
	wrapOptionName     = "wrap-with-directory"
gatesvp's avatar
gatesvp committed
32 33
	hiddenOptionName   = "hidden"
	onlyHashOptionName = "only-hash"
34
	chunkerOptionName  = "chunker"
35
)
36

37
type AddedObject struct {
38 39 40
	Name  string
	Hash  string `json:",omitempty"`
	Bytes int64  `json:",omitempty"`
41 42
}

43
var AddCmd = &cmds.Command{
44 45 46 47 48 49 50 51 52 53
	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.
`,
	},

54
	Arguments: []cmds.Argument{
55
		cmds.FileArg("path", true, true, "The path to a file to be added to IPFS").EnableRecursive().EnableStdin(),
56
	},
57 58
	Options: []cmds.Option{
		cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
gatesvp's avatar
gatesvp committed
59
		cmds.BoolOption(quietOptionName, "q", "Write minimal output"),
60
		cmds.BoolOption(progressOptionName, "p", "Stream progress data"),
61
		cmds.BoolOption(trickleOptionName, "t", "Use trickle-dag format for dag generation"),
gatesvp's avatar
gatesvp committed
62 63 64
		cmds.BoolOption(onlyHashOptionName, "n", "Only chunk and hash - do not write to disk"),
		cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object"),
		cmds.BoolOption(hiddenOptionName, "Include files that are hidden"),
65
		cmds.StringOption(chunkerOptionName, "s", "chunking algorithm to use"),
66 67
	},
	PreRun: func(req cmds.Request) error {
gatesvp's avatar
gatesvp committed
68
		if quiet, _, _ := req.Option(quietOptionName).Bool(); quiet {
69 70 71
			return nil
		}

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
		req.SetOption(progressOptionName, true)

		sizeFile, ok := req.Files().(files.SizeFile)
		if !ok {
			// we don't need to error, the progress bar just won't know how big the files are
			return nil
		}

		size, err := sizeFile.Size()
		if err != nil {
			// see comment above
			return nil
		}
		log.Debugf("Total size of file being added: %v\n", size)
		req.Values()["size"] = size

		return nil
89
	},
90
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
91
		n, err := req.InvocContext().GetNode()
92
		if err != nil {
93 94
			res.SetError(err, cmds.ErrNormal)
			return
95
		}
96

97
		progress, _, _ := req.Option(progressOptionName).Bool()
98
		trickle, _, _ := req.Option(trickleOptionName).Bool()
99
		wrap, _, _ := req.Option(wrapOptionName).Bool()
gatesvp's avatar
gatesvp committed
100 101
		hash, _, _ := req.Option(onlyHashOptionName).Bool()
		hidden, _, _ := req.Option(hiddenOptionName).Bool()
102
		chunker, _, _ := req.Option(chunkerOptionName).String()
Jeromy's avatar
Jeromy committed
103 104 105 106 107 108 109 110 111

		if hash {
			nilnode, err := core.NewNodeBuilder().NilRepo().Build(n.Context())
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			n = nilnode
		}
112

113
		outChan := make(chan interface{}, 8)
114
		res.SetOutput((<-chan interface{})(outChan))
115

116 117 118 119 120 121 122 123
		// addSingleFile is a function that adds a file given as a param.
		addSingleFile := func(file files.File) error {
			addParams := adder{
				node:     n,
				out:      outChan,
				progress: progress,
				hidden:   hidden,
				trickle:  trickle,
124
				chunker:  chunker,
125
			}
126

127 128 129 130
			rootnd, err := addParams.addFile(file)
			if err != nil {
				return err
			}
131

132 133 134 135
			rnk, err := rootnd.Key()
			if err != nil {
				return err
			}
136

137 138 139 140 141
			mp := n.Pinning.GetManual()
			mp.RemovePinWithMode(rnk, pin.Indirect)
			mp.PinWithMode(rnk, pin.Recursive)
			return n.Pinning.Flush()
		}
142

143 144 145 146 147 148 149 150 151 152
		// addFilesSeparately loops over a convenience slice file to
		// add each file individually. e.g. 'ipfs add a b c'
		addFilesSeparately := func(sliceFile files.File) error {
			for {
				file, err := sliceFile.NextFile()
				if err != nil && err != io.EOF {
					return err
				}
				if file == nil {
					return nil // done
Jeromy's avatar
Jeromy committed
153 154
				}

155 156
				if err := addSingleFile(file); err != nil {
					return err
Jeromy's avatar
Jeromy committed
157
				}
158 159
			}
		}
Jeromy's avatar
Jeromy committed
160

161 162
		go func() {
			defer close(outChan)
163

164 165 166 167 168 169 170 171 172 173 174 175
			// really, we're unrapping, if !wrap, because
			// req.Files() is already a SliceFile() with all of them,
			// so can just use that slice as the wrapper.
			var err error
			if wrap {
				err = addSingleFile(req.Files())
			} else {
				err = addFilesSeparately(req.Files())
			}
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
176 177
			}
		}()
178
	},
179
	PostRun: func(req cmds.Request, res cmds.Response) {
180 181 182
		if res.Error() != nil {
			return
		}
183 184 185 186 187
		outChan, ok := res.Output().(<-chan interface{})
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
188
		res.SetOutput(nil)
189

190
		quiet, _, err := req.Option("quiet").Bool()
191 192 193 194
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
195 196

		size := int64(0)
197
		s, found := req.Values()["size"]
198 199 200
		if found {
			size = s.(int64)
		}
201
		showProgressBar := !quiet && size >= progressBarMinSize
202 203 204 205 206 207 208 209 210 211 212 213 214 215

		var bar *pb.ProgressBar
		var terminalWidth int
		if showProgressBar {
			bar = pb.New64(size).SetUnits(pb.U_BYTES)
			bar.ManualUpdate = true
			bar.Start()

			// the progress bar lib doesn't give us a way to get the width of the output,
			// so as a hack we just use a callback to measure the output, then git rid of it
			terminalWidth = 0
			bar.Callback = func(line string) {
				terminalWidth = len(line)
				bar.Callback = nil
216
				bar.Output = res.Stderr()
217 218 219 220 221
				log.Infof("terminal width: %v\n", terminalWidth)
			}
			bar.Update()
		}

222 223
		lastFile := ""
		var totalProgress, prevFiles, lastBytes int64
224

225 226 227
		for out := range outChan {
			output := out.(*AddedObject)
			if len(output.Hash) > 0 {
228
				if showProgressBar {
229
					// clear progress bar line before we print "added x" output
rht's avatar
rht committed
230
					fmt.Fprintf(res.Stderr(), "\033[2K\r")
231 232
				}
				if quiet {
233
					fmt.Fprintf(res.Stdout(), "%s\n", output.Hash)
234
				} else {
235
					fmt.Fprintf(res.Stdout(), "added %s %s\n", output.Hash, output.Name)
236
				}
237

238 239
			} else {
				log.Debugf("add progress: %v %v\n", output.Name, output.Bytes)
240

241 242
				if !showProgressBar {
					continue
243 244
				}

245 246 247 248 249 250
				if len(lastFile) == 0 {
					lastFile = output.Name
				}
				if output.Name != lastFile || output.Bytes < lastBytes {
					prevFiles += lastBytes
					lastFile = output.Name
251
				}
252 253 254
				lastBytes = output.Bytes
				delta := prevFiles + lastBytes - totalProgress
				totalProgress = bar.Add64(delta)
255
			}
256

257 258 259 260
			if showProgressBar {
				bar.Update()
			}
		}
261
	},
262
	Type: AddedObject{},
263 264
}

gatesvp's avatar
gatesvp committed
265 266 267 268 269 270 271
// Internal structure for holding the switches passed to the `add` call
type adder struct {
	node     *core.IpfsNode
	out      chan interface{}
	progress bool
	hidden   bool
	trickle  bool
272
	chunker  string
gatesvp's avatar
gatesvp committed
273 274 275
}

// Perform the actual add & pin locally, outputting results to reader
276 277 278 279 280 281
func add(n *core.IpfsNode, reader io.Reader, useTrickle bool, chunker string) (*dag.Node, error) {
	chnk, err := chunk.FromString(reader, chunker)
	if err != nil {
		return nil, err
	}

282 283 284 285
	var node *dag.Node
	if useTrickle {
		node, err = importer.BuildTrickleDagFromReader(
			n.DAG,
286
			chnk,
287 288 289 290 291
			importer.PinIndirectCB(n.Pinning.GetManual()),
		)
	} else {
		node, err = importer.BuildDagFromReader(
			n.DAG,
292
			chnk,
293 294 295 296
			importer.PinIndirectCB(n.Pinning.GetManual()),
		)
	}

297 298
	if err != nil {
		return nil, err
299
	}
300

301
	return node, nil
302
}
303

gatesvp's avatar
gatesvp committed
304 305 306 307 308 309 310 311 312
// Add the given file while respecting the params.
func (params *adder) addFile(file files.File) (*dag.Node, error) {
	// Check if file is hidden
	if fileIsHidden := files.IsHidden(file); fileIsHidden && !params.hidden {
		log.Debugf("%s is hidden, skipping", file.FileName())
		return nil, &hiddenFileError{file.FileName()}
	}

	// Check if "file" is actually a directory
313
	if file.IsDirectory() {
gatesvp's avatar
gatesvp committed
314
		return params.addDir(file)
315 316 317 318 319
	}

	// if the progress flag was specified, wrap the file so that we can send
	// progress updates to the client (over the output channel)
	var reader io.Reader = file
gatesvp's avatar
gatesvp committed
320 321
	if params.progress {
		reader = &progressReader{file: file, out: params.out}
322 323
	}

324
	dagnode, err := add(params.node, reader, params.trickle, params.chunker)
325 326 327 328 329
	if err != nil {
		return nil, err
	}

	log.Infof("adding file: %s", file.FileName())
gatesvp's avatar
gatesvp committed
330
	if err := outputDagnode(params.out, file.FileName(), dagnode); err != nil {
331 332
		return nil, err
	}
333
	return dagnode, nil
334 335
}

gatesvp's avatar
gatesvp committed
336
func (params *adder) addDir(file files.File) (*dag.Node, error) {
337
	tree := &dag.Node{Data: ft.FolderPBData()}
gatesvp's avatar
gatesvp committed
338
	log.Infof("adding directory: %s", file.FileName())
339 340

	for {
gatesvp's avatar
gatesvp committed
341
		file, err := file.NextFile()
342 343 344 345 346 347 348
		if err != nil && err != io.EOF {
			return nil, err
		}
		if file == nil {
			break
		}

gatesvp's avatar
gatesvp committed
349 350 351 352 353
		node, err := params.addFile(file)
		if _, ok := err.(*hiddenFileError); ok {
			// hidden file error, set the node to nil for below
			node = nil
		} else if err != nil {
354 355 356
			return nil, err
		}

gatesvp's avatar
gatesvp committed
357 358
		if node != nil {
			_, name := path.Split(file.FileName())
359

gatesvp's avatar
gatesvp committed
360 361 362 363
			err = tree.AddNodeLink(name, node)
			if err != nil {
				return nil, err
			}
364 365 366
		}
	}

gatesvp's avatar
gatesvp committed
367
	err := outputDagnode(params.out, file.FileName(), tree)
368 369 370 371
	if err != nil {
		return nil, err
	}

gatesvp's avatar
gatesvp committed
372
	k, err := params.node.DAG.Add(tree)
373 374 375
	if err != nil {
		return nil, err
	}
376

gatesvp's avatar
gatesvp committed
377
	params.node.Pinning.GetManual().PinWithMode(k, pin.Indirect)
378

379
	return tree, nil
380 381
}

382 383
// outputDagnode sends dagnode info over the output channel
func outputDagnode(out chan interface{}, name string, dn *dag.Node) error {
384 385 386 387 388
	o, err := getOutput(dn)
	if err != nil {
		return err
	}

389 390 391 392
	out <- &AddedObject{
		Hash: o.Hash,
		Name: name,
	}
393

394
	return nil
395
}
396

gatesvp's avatar
gatesvp committed
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
type hiddenFileError struct {
	fileName string
}

func (e *hiddenFileError) Error() string {
	return fmt.Sprintf("%s is a hidden file", e.fileName)
}

type ignoreFileError struct {
	fileName string
}

func (e *ignoreFileError) Error() string {
	return fmt.Sprintf("%s is an ignored file", e.fileName)
}

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
type progressReader struct {
	file         files.File
	out          chan interface{}
	bytes        int64
	lastProgress int64
}

func (i *progressReader) Read(p []byte) (int, error) {
	n, err := i.file.Read(p)

	i.bytes += int64(n)
	if i.bytes-i.lastProgress >= progressReaderIncrement || err == io.EOF {
		i.lastProgress = i.bytes
		i.out <- &AddedObject{
			Name:  i.file.FileName(),
			Bytes: i.bytes,
		}
	}

	return n, err
}