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

import (
	"fmt"
5
	"io"
Jan Winkelmann's avatar
Jan Winkelmann committed
6
	"os"
7
	"strings"
8

Łukasz Magiera's avatar
Łukasz Magiera committed
9
	"github.com/ipfs/go-ipfs/core/commands/cmdenv"
10
	coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
Łukasz Magiera's avatar
Łukasz Magiera committed
11
	"github.com/ipfs/go-ipfs/core/coreapi/interface/options"
12

13
	pb "gx/ipfs/QmPtj12fdwuAqj9sBSTNUxBNu8kCGNp8b3o8yUzMm5GHpq/pb"
14
	cmds "gx/ipfs/QmaAP56JAwdjwisPTu4yx17whcjTr6y5JCSCF77Y1rahWV/go-ipfs-cmds"
15
	cmdkit "gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
16
	mh "gx/ipfs/QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW/go-multihash"
17 18
)

Łukasz Magiera's avatar
Łukasz Magiera committed
19
// ErrDepthLimitExceeded indicates that the max depth has been exceeded.
20 21
var ErrDepthLimitExceeded = fmt.Errorf("depth limit exceeded")

22
const (
23
	quietOptionName       = "quiet"
24
	quieterOptionName     = "quieter"
25 26 27 28
	silentOptionName      = "silent"
	progressOptionName    = "progress"
	trickleOptionName     = "trickle"
	wrapOptionName        = "wrap-with-directory"
29
	stdinPathName         = "stdin-name"
30 31 32 33 34 35 36
	hiddenOptionName      = "hidden"
	onlyHashOptionName    = "only-hash"
	chunkerOptionName     = "chunker"
	pinOptionName         = "pin"
	rawLeavesOptionName   = "raw-leaves"
	noCopyOptionName      = "nocopy"
	fstoreCacheOptionName = "fscache"
37
	cidVersionOptionName  = "cid-version"
38
	hashOptionName        = "hash"
39
	inlineOptionName      = "inline"
Kevin Atkinson's avatar
Kevin Atkinson committed
40
	inlineLimitOptionName = "inline-limit"
41
)
42

43 44
const adderOutChanSize = 8

45
var AddCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
46
	Helptext: cmdkit.HelpText{
47
		Tagline: "Add a file or directory to ipfs.",
48
		ShortDescription: `
49
Adds contents of <path> to ipfs. Use -r to add directories (recursively).
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
`,
		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
70

71 72
The chunker option, '-s', specifies the chunking strategy that dictates
how to break files into blocks. Blocks with same content can
73
be deduplicated. The default is a fixed block size of
74 75
256 * 1024 bytes, 'size-262144'. Alternatively, you can use the
rabin chunker for content defined chunking by specifying
76
rabin-[min]-[avg]-[max] (where min/avg/max refer to the resulting
77 78
chunk sizes). Using other chunking strategies will produce
different hashes for the same file.
79

80 81 82 83
  > ipfs add --chunker=size-2048 ipfs-logo.svg
  added QmafrLBfzRLV4XSH1XcaMMeaXEUhDJjmtDfsYU95TrWG87 ipfs-logo.svg
  > ipfs add --chunker=rabin-512-1024-2048 ipfs-logo.svg
  added Qmf1hDN65tR55Ubh2RN1FPxr69xq3giVBz1KApsresY8Gn ipfs-logo.svg
84 85 86

You can now check what blocks have been created by:

87 88 89 90 91 92
  > ipfs object links QmafrLBfzRLV4XSH1XcaMMeaXEUhDJjmtDfsYU95TrWG87
  QmY6yj1GsermExDXoosVE3aSPxdMNYr6aKuw3nA8LoWPRS 2059
  Qmf7ZQeSxq2fJVJbCmgTrLLVN9tDR9Wy5k75DxQKuz5Gyt 1195
  > ipfs object links Qmf1hDN65tR55Ubh2RN1FPxr69xq3giVBz1KApsresY8Gn
  QmY6yj1GsermExDXoosVE3aSPxdMNYr6aKuw3nA8LoWPRS 2059
  QmerURi9k4XzKCaaPbsK6BL5pMEjF7PGphjDvkkjDtsVf3 868
93
  QmQB28iwSriSUSMqG2nXDTLtdPHgWb4rebBrU7Q1j4vxPv 338
94 95 96
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
97 98
	Arguments: []cmdkit.Argument{
		cmdkit.FileArg("path", true, true, "The path to a file to be added to ipfs.").EnableRecursive().EnableStdin(),
99
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
100
	Options: []cmdkit.Option{
101
		cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
102
		cmds.OptionDerefArgs,     // a builtin option that resolves passed in filesystem links (--dereference-args)
Jan Winkelmann's avatar
Jan Winkelmann committed
103 104 105 106 107 108 109
		cmdkit.BoolOption(quietOptionName, "q", "Write minimal output."),
		cmdkit.BoolOption(quieterOptionName, "Q", "Write only final hash."),
		cmdkit.BoolOption(silentOptionName, "Write no output."),
		cmdkit.BoolOption(progressOptionName, "p", "Stream progress data."),
		cmdkit.BoolOption(trickleOptionName, "t", "Use trickle-dag format for dag generation."),
		cmdkit.BoolOption(onlyHashOptionName, "n", "Only chunk and hash - do not write to disk."),
		cmdkit.BoolOption(wrapOptionName, "w", "Wrap files with a directory object."),
110
		cmdkit.StringOption(stdinPathName, "Assign a name if the file source is stdin."),
Jan Winkelmann's avatar
Jan Winkelmann committed
111
		cmdkit.BoolOption(hiddenOptionName, "H", "Include files that are hidden. Only takes effect on recursive add."),
112 113
		cmdkit.StringOption(chunkerOptionName, "s", "Chunking algorithm, size-[bytes] or rabin-[min]-[avg]-[max]").WithDefault("size-262144"),
		cmdkit.BoolOption(pinOptionName, "Pin this object when adding.").WithDefault(true),
114
		cmdkit.BoolOption(rawLeavesOptionName, "Use raw blocks for leaf nodes. (experimental)"),
115
		cmdkit.BoolOption(noCopyOptionName, "Add the file using filestore. Implies raw-leaves. (experimental)"),
Jan Winkelmann's avatar
Jan Winkelmann committed
116
		cmdkit.BoolOption(fstoreCacheOptionName, "Check the filestore for pre-existing blocks. (experimental)"),
117 118
		cmdkit.IntOption(cidVersionOptionName, "CID version. Defaults to 0 unless an option that depends on CIDv1 is passed. (experimental)"),
		cmdkit.StringOption(hashOptionName, "Hash function to use. Implies CIDv1 if not sha2-256. (experimental)").WithDefault("sha2-256"),
Kevin Atkinson's avatar
Kevin Atkinson committed
119
		cmdkit.BoolOption(inlineOptionName, "Inline small blocks into CIDs. (experimental)"),
120
		cmdkit.IntOption(inlineLimitOptionName, "Maximum block size to inline. (experimental)").WithDefault(32),
121
	},
Jeromy's avatar
Jeromy committed
122
	PreRun: func(req *cmds.Request, env cmds.Environment) error {
123 124
		quiet, _ := req.Options[quietOptionName].(bool)
		quieter, _ := req.Options[quieterOptionName].(bool)
125 126
		quiet = quiet || quieter

127
		silent, _ := req.Options[silentOptionName].(bool)
128 129

		if quiet || silent {
130 131 132
			return nil
		}

133
		// ipfs cli progress bar defaults to true unless quiet or silent is used
134
		_, found := req.Options[progressOptionName].(bool)
135
		if !found {
136
			req.Options[progressOptionName] = true
137 138
		}

139
		return nil
140
	},
keks's avatar
keks committed
141
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
Łukasz Magiera's avatar
Łukasz Magiera committed
142
		api, err := cmdenv.GetApi(env)
143
		if err != nil {
keks's avatar
keks committed
144
			return err
145
		}
146

147 148 149 150 151 152 153 154 155 156 157
		progress, _ := req.Options[progressOptionName].(bool)
		trickle, _ := req.Options[trickleOptionName].(bool)
		wrap, _ := req.Options[wrapOptionName].(bool)
		hash, _ := req.Options[onlyHashOptionName].(bool)
		hidden, _ := req.Options[hiddenOptionName].(bool)
		silent, _ := req.Options[silentOptionName].(bool)
		chunker, _ := req.Options[chunkerOptionName].(string)
		dopin, _ := req.Options[pinOptionName].(bool)
		rawblks, rbset := req.Options[rawLeavesOptionName].(bool)
		nocopy, _ := req.Options[noCopyOptionName].(bool)
		fscache, _ := req.Options[fstoreCacheOptionName].(bool)
158 159
		cidVer, cidVerSet := req.Options[cidVersionOptionName].(int)
		hashFunStr, _ := req.Options[hashOptionName].(string)
160
		inline, _ := req.Options[inlineOptionName].(bool)
Kevin Atkinson's avatar
Kevin Atkinson committed
161
		inlineLimit, _ := req.Options[inlineLimitOptionName].(int)
162
		pathName, _ := req.Options[stdinPathName].(string)
Łukasz Magiera's avatar
Łukasz Magiera committed
163
		local, _ := req.Options["local"].(bool)
164

165 166
		hashFunCode, ok := mh.Names[strings.ToLower(hashFunStr)]
		if !ok {
keks's avatar
keks committed
167
			return fmt.Errorf("unrecognized hash function: %s", strings.ToLower(hashFunStr))
168 169
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
170
		events := make(chan interface{}, adderOutChanSize)
171

Łukasz Magiera's avatar
Łukasz Magiera committed
172 173
		opts := []options.UnixfsAddOption{
			options.Unixfs.Hash(hashFunCode),
174

Łukasz Magiera's avatar
Łukasz Magiera committed
175 176
			options.Unixfs.Inline(inline),
			options.Unixfs.InlineLimit(inlineLimit),
177

Łukasz Magiera's avatar
Łukasz Magiera committed
178
			options.Unixfs.Chunker(chunker),
179

Łukasz Magiera's avatar
Łukasz Magiera committed
180 181 182 183 184
			options.Unixfs.Pin(dopin),
			options.Unixfs.HashOnly(hash),
			options.Unixfs.Local(local),
			options.Unixfs.FsCache(fscache),
			options.Unixfs.Nocopy(nocopy),
Jan Winkelmann's avatar
Jan Winkelmann committed
185

Łukasz Magiera's avatar
Łukasz Magiera committed
186 187 188 189 190 191 192 193 194 195 196
			options.Unixfs.Wrap(wrap),
			options.Unixfs.Hidden(hidden),
			options.Unixfs.StdinName(pathName),

			options.Unixfs.Progress(progress),
			options.Unixfs.Silent(silent),
			options.Unixfs.Events(events),
		}

		if cidVerSet {
			opts = append(opts, options.Unixfs.CidVersion(cidVer))
Jeromy's avatar
Jeromy committed
197
		}
198

Łukasz Magiera's avatar
Łukasz Magiera committed
199 200
		if rbset {
			opts = append(opts, options.Unixfs.RawLeaves(rawblks))
201
		}
202

Łukasz Magiera's avatar
Łukasz Magiera committed
203 204
		if trickle {
			opts = append(opts, options.Unixfs.Layout(options.TrickleLayout))
Jeromy's avatar
Jeromy committed
205 206
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
207
		errCh := make(chan error)
208
		go func() {
Jan Winkelmann's avatar
Jan Winkelmann committed
209 210
			var err error
			defer func() { errCh <- err }()
Łukasz Magiera's avatar
Łukasz Magiera committed
211 212
			defer close(events)
			_, err = api.Unixfs().Add(req.Context, req.Files, opts...)
213
		}()
Jan Winkelmann's avatar
Jan Winkelmann committed
214

Łukasz Magiera's avatar
Łukasz Magiera committed
215
		err = res.Emit(events)
Jan Winkelmann's avatar
Jan Winkelmann committed
216
		if err != nil {
keks's avatar
keks committed
217
			return err
218
		}
keks's avatar
keks committed
219 220

		return <-errCh
Jan Winkelmann's avatar
Jan Winkelmann committed
221
	},
222
	PostRun: cmds.PostRunMap{
keks's avatar
keks committed
223
		cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
Steven Allen's avatar
Steven Allen committed
224
			sizeChan := make(chan int64, 1)
keks's avatar
keks committed
225 226
			outChan := make(chan interface{})
			req := res.Request()
Steven Allen's avatar
Steven Allen committed
227

Łukasz Magiera's avatar
Łukasz Magiera committed
228 229 230 231 232 233 234 235 236 237 238
			// Could be slow.
			go func() {
				size, err := req.Files.Size()
				if err != nil {
					log.Warningf("error getting files size: %s", err)
					// see comment above
					return
				}

				sizeChan <- size
			}()
Steven Allen's avatar
Steven Allen committed
239

Jan Winkelmann's avatar
Jan Winkelmann committed
240 241
			progressBar := func(wait chan struct{}) {
				defer close(wait)
242

243 244
				quiet, _ := req.Options[quietOptionName].(bool)
				quieter, _ := req.Options[quieterOptionName].(bool)
Jan Winkelmann's avatar
Jan Winkelmann committed
245
				quiet = quiet || quieter
Jeromy's avatar
Jeromy committed
246

247
				progress, _ := req.Options[progressOptionName].(bool)
248

Jan Winkelmann's avatar
Jan Winkelmann committed
249 250 251 252 253 254 255 256 257
				var bar *pb.ProgressBar
				if progress {
					bar = pb.New64(0).SetUnits(pb.U_BYTES)
					bar.ManualUpdate = true
					bar.ShowTimeLeft = false
					bar.ShowPercent = false
					bar.Output = os.Stderr
					bar.Start()
				}
258

Jan Winkelmann's avatar
Jan Winkelmann committed
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
				lastFile := ""
				lastHash := ""
				var totalProgress, prevFiles, lastBytes int64

			LOOP:
				for {
					select {
					case out, ok := <-outChan:
						if !ok {
							if quieter {
								fmt.Fprintln(os.Stdout, lastHash)
							}

							break LOOP
						}
274
						output := out.(*coreiface.AddEvent)
Jan Winkelmann's avatar
Jan Winkelmann committed
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
						if len(output.Hash) > 0 {
							lastHash = output.Hash
							if quieter {
								continue
							}

							if progress {
								// clear progress bar line before we print "added x" output
								fmt.Fprintf(os.Stderr, "\033[2K\r")
							}
							if quiet {
								fmt.Fprintf(os.Stdout, "%s\n", output.Hash)
							} else {
								fmt.Fprintf(os.Stdout, "added %s %s\n", output.Hash, output.Name)
							}

						} else {
							if !progress {
								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)
						}

						if progress {
							bar.Update()
						}
					case size := <-sizeChan:
						if progress {
							bar.Total = size
							bar.ShowPercent = true
							bar.ShowBar = true
							bar.ShowTimeLeft = true
						}
318
					case <-req.Context.Done():
keks's avatar
cleanup  
keks committed
319
						// don't set or print error here, that happens in the goroutine below
320
						return
321
					}
Jan Winkelmann's avatar
Jan Winkelmann committed
322
				}
323 324 325 326 327 328 329 330

				if progress && bar.Total == 0 && bar.Get() != 0 {
					bar.Total = bar.Get()
					bar.ShowPercent = true
					bar.ShowBar = true
					bar.ShowTimeLeft = true
					bar.Update()
				}
Jan Winkelmann's avatar
Jan Winkelmann committed
331
			}
332

keks's avatar
keks committed
333 334 335 336
			if e := res.Error(); e != nil {
				close(outChan)
				return e
			}
337

keks's avatar
keks committed
338 339
			wait := make(chan struct{})
			go progressBar(wait)
Jan Winkelmann's avatar
Jan Winkelmann committed
340

keks's avatar
keks committed
341 342
			defer func() { <-wait }()
			defer close(outChan)
Jan Winkelmann's avatar
Jan Winkelmann committed
343

keks's avatar
keks committed
344 345 346 347 348
			for {
				v, err := res.Next()
				if err != nil {
					if err == io.EOF {
						return nil
Jan Winkelmann's avatar
Jan Winkelmann committed
349 350
					}

keks's avatar
keks committed
351
					return err
Jeromy's avatar
Jeromy committed
352
				}
Jan Winkelmann's avatar
Jan Winkelmann committed
353

keks's avatar
keks committed
354 355 356 357 358 359
				select {
				case outChan <- v:
				case <-req.Context.Done():
					return req.Context.Err()
				}
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
360
		},
361
	},
362
	Type: coreiface.AddEvent{},
363
}