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

import (
	"fmt"
5
	"io"
6

7
	"github.com/ipfs/go-ipfs/core/coreunix"
Jakub Sztandera's avatar
Jakub Sztandera committed
8
	"gx/ipfs/QmeWjRodbcZFKe5tMN7poEx3izym6osrLSnTLf9UjJZBbs/pb"
Jeromy's avatar
Jeromy committed
9

10
	bstore "github.com/ipfs/go-ipfs/blocks/blockstore"
11
	blockservice "github.com/ipfs/go-ipfs/blockservice"
12 13 14
	cmds "github.com/ipfs/go-ipfs/commands"
	files "github.com/ipfs/go-ipfs/commands/files"
	core "github.com/ipfs/go-ipfs/core"
15 16
	offline "github.com/ipfs/go-ipfs/exchange/offline"
	dag "github.com/ipfs/go-ipfs/merkledag"
Jeromy's avatar
Jeromy committed
17 18
	dagtest "github.com/ipfs/go-ipfs/merkledag/test"
	mfs "github.com/ipfs/go-ipfs/mfs"
19
	ft "github.com/ipfs/go-ipfs/unixfs"
20
	u "gx/ipfs/QmZuY8aV7zbNXVy6DyN9SmnuH3o9nG852F4aTiSBpts8d1/go-ipfs-util"
21 22 23 24 25
)

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

26
const (
27 28 29 30 31 32 33 34 35 36 37 38
	quietOptionName       = "quiet"
	silentOptionName      = "silent"
	progressOptionName    = "progress"
	trickleOptionName     = "trickle"
	wrapOptionName        = "wrap-with-directory"
	hiddenOptionName      = "hidden"
	onlyHashOptionName    = "only-hash"
	chunkerOptionName     = "chunker"
	pinOptionName         = "pin"
	rawLeavesOptionName   = "raw-leaves"
	noCopyOptionName      = "nocopy"
	fstoreCacheOptionName = "fscache"
39
)
40

41
var AddCmd = &cmds.Command{
42
	Helptext: cmds.HelpText{
43
		Tagline: "Add a file or directory to ipfs.",
44
		ShortDescription: `
45
Adds contents of <path> to ipfs. Use -r to add directories (recursively).
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
`,
		LongDescription: `
Adds contents of <path> to ipfs. Use -r to add directories.
Note that directories are added recursively, to form the ipfs
MerkleDAG.

The wrap option, '-w', wraps the file (or files, if using the
recursive option) in a directory. This directory contains only
the files which have been added, and means that the file retains
its filename. For example:

  > ipfs add example.jpg
  added QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH example.jpg
  > ipfs add example.jpg -w
  added QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH example.jpg
  added QmaG4FuMqEBnQNn3C8XJ5bpW8kLs7zq2ZXgHptJHbKDDVx

You can now refer to the added file in a gateway, like so:

  /ipfs/QmaG4FuMqEBnQNn3C8XJ5bpW8kLs7zq2ZXgHptJHbKDDVx/example.jpg
66 67 68
`,
	},

69
	Arguments: []cmds.Argument{
70
		cmds.FileArg("path", true, true, "The path to a file to be added to ipfs.").EnableRecursive().EnableStdin(),
71
	},
72 73
	Options: []cmds.Option{
		cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
74 75
		cmds.BoolOption(quietOptionName, "q", "Write minimal output."),
		cmds.BoolOption(silentOptionName, "Write no output."),
76
		cmds.BoolOption(progressOptionName, "p", "Stream progress data."),
77 78 79 80
		cmds.BoolOption(trickleOptionName, "t", "Use trickle-dag format for dag generation."),
		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, "H", "Include files that are hidden. Only takes effect on recursive add."),
81
		cmds.StringOption(chunkerOptionName, "s", "Chunking algorithm to use."),
82
		cmds.BoolOption(pinOptionName, "Pin this object when adding.").Default(true),
83
		cmds.BoolOption(rawLeavesOptionName, "Use raw blocks for leaf nodes. (experimental)"),
84 85
		cmds.BoolOption(noCopyOptionName, "Add the file using filestore. (experimental)"),
		cmds.BoolOption(fstoreCacheOptionName, "Check the filestore for pre-existing blocks. (experimental)"),
86 87
	},
	PreRun: func(req cmds.Request) error {
88 89 90 91
		quiet, _, _ := req.Option(quietOptionName).Bool()
		silent, _, _ := req.Option(silentOptionName).Bool()

		if quiet || silent {
92 93 94
			return nil
		}

95
		// ipfs cli progress bar defaults to true unless quiet or silent is used
96 97 98 99 100
		_, found, _ := req.Option(progressOptionName).Bool()
		if !found {
			req.SetOption(progressOptionName, true)
		}

101 102 103
		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
Jeromy's avatar
Jeromy committed
104
			log.Warning("cannnot determine size of input file")
105 106 107
			return nil
		}

108 109
		sizeCh := make(chan int64, 1)
		req.Values()["size"] = sizeCh
rht's avatar
rht committed
110

111 112 113
		go func() {
			size, err := sizeFile.Size()
			if err != nil {
Jeromy's avatar
Jeromy committed
114
				log.Warningf("error getting files size: %s", err)
115 116 117 118 119 120 121
				// see comment above
				return
			}

			log.Debugf("Total size of file being added: %v\n", size)
			sizeCh <- size
		}()
122 123

		return nil
124
	},
125
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
126
		n, err := req.InvocContext().GetNode()
127
		if err != nil {
128 129
			res.SetError(err, cmds.ErrNormal)
			return
130
		}
rht's avatar
rht committed
131 132 133 134 135 136 137
		// check if repo will exceed storage limit if added
		// TODO: this doesn't handle the case if the hashed file is already in blocks (deduplicated)
		// TODO: conditional GC is disabled due to it is somehow not possible to pass the size to the daemon
		//if err := corerepo.ConditionalGC(req.Context(), n, uint64(size)); err != nil {
		//	res.SetError(err, cmds.ErrNormal)
		//	return
		//}
138

139
		progress, _, _ := req.Option(progressOptionName).Bool()
140
		trickle, _, _ := req.Option(trickleOptionName).Bool()
141
		wrap, _, _ := req.Option(wrapOptionName).Bool()
gatesvp's avatar
gatesvp committed
142 143
		hash, _, _ := req.Option(onlyHashOptionName).Bool()
		hidden, _, _ := req.Option(hiddenOptionName).Bool()
Jeromy's avatar
Jeromy committed
144
		silent, _, _ := req.Option(silentOptionName).Bool()
145
		chunker, _, _ := req.Option(chunkerOptionName).String()
146
		dopin, _, _ := req.Option(pinOptionName).Bool()
147
		rawblks, _, _ := req.Option(rawLeavesOptionName).Bool()
148 149 150 151 152 153 154
		nocopy, _, _ := req.Option(noCopyOptionName).Bool()
		fscache, _, _ := req.Option(fstoreCacheOptionName).Bool()

		if nocopy && !rawblks {
			res.SetError(fmt.Errorf("nocopy option requires '--raw-leaves' to be enabled as well"), cmds.ErrNormal)
			return
		}
Jeromy's avatar
Jeromy committed
155 156

		if hash {
157 158 159
			nilnode, err := core.NewNode(n.Context(), &core.BuildCfg{
				//TODO: need this to be true or all files
				// hashed will be stored in memory!
160
				NilRepo: true,
161
			})
Jeromy's avatar
Jeromy committed
162 163 164 165 166 167
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			n = nilnode
		}
168

169 170 171 172 173 174
		addblockstore := n.Blockstore
		if !fscache && !nocopy {
			addblockstore = bstore.NewGCBlockstore(n.BaseBlocks, n.GCLocker)
		}

		exch := n.Exchange
175 176
		local, _, _ := req.Option("local").Bool()
		if local {
177
			exch = offline.Exchange(addblockstore)
178 179
		}

180 181 182
		bserv := blockservice.New(addblockstore, exch)
		dserv := dag.NewDAGService(bserv)

183
		outChan := make(chan interface{}, 8)
184
		res.SetOutput((<-chan interface{})(outChan))
185

186
		fileAdder, err := coreunix.NewAdder(req.Context(), n.Pinning, n.Blockstore, dserv)
Jeromy's avatar
Jeromy committed
187 188 189 190
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
191 192

		fileAdder.Out = outChan
193 194 195 196 197
		fileAdder.Chunker = chunker
		fileAdder.Progress = progress
		fileAdder.Hidden = hidden
		fileAdder.Trickle = trickle
		fileAdder.Wrap = wrap
198
		fileAdder.Pin = dopin
Jeromy's avatar
Jeromy committed
199
		fileAdder.Silent = silent
200
		fileAdder.RawLeaves = rawblks
201
		fileAdder.NoCopy = nocopy
202

Jeromy's avatar
Jeromy committed
203 204
		if hash {
			md := dagtest.Mock()
205
			mr, err := mfs.NewRoot(req.Context(), md, ft.EmptyDirNode(), nil)
Jeromy's avatar
Jeromy committed
206 207 208 209 210 211 212 213
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

			fileAdder.SetMfsRoot(mr)
		}

214
		addAllAndPin := func(f files.File) error {
215 216 217 218 219 220 221 222 223 224 225 226 227 228
			// Iterate over each top-level file and add individually. Otherwise the
			// single files.File f is treated as a directory, affecting hidden file
			// semantics.
			for {
				file, err := f.NextFile()
				if err == io.EOF {
					// Finished the list of files.
					break
				} else if err != nil {
					return err
				}
				if err := fileAdder.AddFile(file); err != nil {
					return err
				}
229
			}
230

231
			// copy intermediary nodes from editor to our actual dagservice
Jeromy's avatar
Jeromy committed
232
			_, err := fileAdder.Finalize()
233 234
			if err != nil {
				return err
235 236
			}

Stephen Whitmore's avatar
Stephen Whitmore committed
237 238 239 240
			if hash {
				return nil
			}

241
			return fileAdder.PinRoot()
242 243 244 245 246
		}

		go func() {
			defer close(outChan)
			if err := addAllAndPin(req.Files()); err != nil {
247 248
				res.SetError(err, cmds.ErrNormal)
				return
249
			}
250

251
		}()
252
	},
253
	PostRun: func(req cmds.Request, res cmds.Response) {
254 255 256
		if res.Error() != nil {
			return
		}
257 258 259 260 261
		outChan, ok := res.Output().(<-chan interface{})
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
262
		res.SetOutput(nil)
263

264
		quiet, _, err := req.Option("quiet").Bool()
265 266 267 268
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
269

270
		progress, _, err := req.Option(progressOptionName).Bool()
Jeromy's avatar
Jeromy committed
271 272 273 274 275
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

276
		var bar *pb.ProgressBar
277
		if progress {
278
			bar = pb.New64(0).SetUnits(pb.U_BYTES)
279
			bar.ManualUpdate = true
Jeromy's avatar
Jeromy committed
280 281 282
			bar.ShowTimeLeft = false
			bar.ShowPercent = false
			bar.Output = res.Stderr()
283 284 285
			bar.Start()
		}

286 287 288 289 290 291
		var sizeChan chan int64
		s, found := req.Values()["size"]
		if found {
			sizeChan = s.(chan int64)
		}

292 293
		lastFile := ""
		var totalProgress, prevFiles, lastBytes int64
294

295 296 297 298 299 300
	LOOP:
		for {
			select {
			case out, ok := <-outChan:
				if !ok {
					break LOOP
301
				}
302 303
				output := out.(*coreunix.AddedObject)
				if len(output.Hash) > 0 {
304
					if progress {
305 306 307 308 309 310 311 312
						// clear progress bar line before we print "added x" output
						fmt.Fprintf(res.Stderr(), "\033[2K\r")
					}
					if quiet {
						fmt.Fprintf(res.Stdout(), "%s\n", output.Hash)
					} else {
						fmt.Fprintf(res.Stdout(), "added %s %s\n", output.Hash, output.Name)
					}
313

314 315 316
				} else {
					log.Debugf("add progress: %v %v\n", output.Name, output.Bytes)

317
					if !progress {
318 319 320 321 322 323 324 325 326 327 328 329 330
						continue
					}

					if len(lastFile) == 0 {
						lastFile = output.Name
					}
					if output.Name != lastFile || output.Bytes < lastBytes {
						prevFiles += lastBytes
						lastFile = output.Name
					}
					lastBytes = output.Bytes
					delta := prevFiles + lastBytes - totalProgress
					totalProgress = bar.Add64(delta)
331 332
				}

333
				if progress {
334
					bar.Update()
335
				}
336
			case size := <-sizeChan:
337
				if progress {
Jeromy's avatar
Jeromy committed
338 339 340 341 342
					bar.Total = size
					bar.ShowPercent = true
					bar.ShowBar = true
					bar.ShowTimeLeft = true
				}
Jeromy's avatar
Jeromy committed
343 344 345
			case <-req.Context().Done():
				res.SetError(req.Context().Err(), cmds.ErrNormal)
				return
346 347
			}
		}
348
	},
349
	Type: coreunix.AddedObject{},
350
}