add.go 9.02 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
	blockservice "github.com/ipfs/go-ipfs/blockservice"
11 12 13
	cmds "github.com/ipfs/go-ipfs/commands"
	files "github.com/ipfs/go-ipfs/commands/files"
	core "github.com/ipfs/go-ipfs/core"
14 15
	offline "github.com/ipfs/go-ipfs/exchange/offline"
	dag "github.com/ipfs/go-ipfs/merkledag"
Jeromy's avatar
Jeromy committed
16 17
	dagtest "github.com/ipfs/go-ipfs/merkledag/test"
	mfs "github.com/ipfs/go-ipfs/mfs"
18
	ft "github.com/ipfs/go-ipfs/unixfs"
19
	u "gx/ipfs/QmZuY8aV7zbNXVy6DyN9SmnuH3o9nG852F4aTiSBpts8d1/go-ipfs-util"
20 21 22 23 24
)

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

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

38
var AddCmd = &cmds.Command{
39
	Helptext: cmds.HelpText{
40
		Tagline: "Add a file or directory to ipfs.",
41
		ShortDescription: `
42
Adds contents of <path> to ipfs. Use -r to add directories (recursively).
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
`,
		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
63 64 65
`,
	},

66
	Arguments: []cmds.Argument{
67
		cmds.FileArg("path", true, true, "The path to a file to be added to ipfs.").EnableRecursive().EnableStdin(),
68
	},
69 70
	Options: []cmds.Option{
		cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
71 72
		cmds.BoolOption(quietOptionName, "q", "Write minimal output."),
		cmds.BoolOption(silentOptionName, "Write no output."),
73
		cmds.BoolOption(progressOptionName, "p", "Stream progress data."),
74 75 76 77
		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."),
78
		cmds.StringOption(chunkerOptionName, "s", "Chunking algorithm to use."),
79
		cmds.BoolOption(pinOptionName, "Pin this object when adding.").Default(true),
80
		cmds.BoolOption(rawLeavesOptionName, "Use raw blocks for leaf nodes. (experimental)"),
81 82
	},
	PreRun: func(req cmds.Request) error {
83 84 85 86
		quiet, _, _ := req.Option(quietOptionName).Bool()
		silent, _, _ := req.Option(silentOptionName).Bool()

		if quiet || silent {
87 88 89
			return nil
		}

90
		// ipfs cli progress bar defaults to true unless quiet or silent is used
91 92 93 94 95
		_, found, _ := req.Option(progressOptionName).Bool()
		if !found {
			req.SetOption(progressOptionName, true)
		}

96 97 98
		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
99
			log.Warning("cannnot determine size of input file")
100 101 102
			return nil
		}

103 104
		sizeCh := make(chan int64, 1)
		req.Values()["size"] = sizeCh
rht's avatar
rht committed
105

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

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

		return nil
119
	},
120
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
121
		n, err := req.InvocContext().GetNode()
122
		if err != nil {
123 124
			res.SetError(err, cmds.ErrNormal)
			return
125
		}
rht's avatar
rht committed
126 127 128 129 130 131 132
		// 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
		//}
133

134
		progress, _, _ := req.Option(progressOptionName).Bool()
135
		trickle, _, _ := req.Option(trickleOptionName).Bool()
136
		wrap, _, _ := req.Option(wrapOptionName).Bool()
gatesvp's avatar
gatesvp committed
137 138
		hash, _, _ := req.Option(onlyHashOptionName).Bool()
		hidden, _, _ := req.Option(hiddenOptionName).Bool()
Jeromy's avatar
Jeromy committed
139
		silent, _, _ := req.Option(silentOptionName).Bool()
140
		chunker, _, _ := req.Option(chunkerOptionName).String()
141
		dopin, _, _ := req.Option(pinOptionName).Bool()
142
		rawblks, _, _ := req.Option(rawLeavesOptionName).Bool()
Jeromy's avatar
Jeromy committed
143 144

		if hash {
145 146 147
			nilnode, err := core.NewNode(n.Context(), &core.BuildCfg{
				//TODO: need this to be true or all files
				// hashed will be stored in memory!
148
				NilRepo: true,
149
			})
Jeromy's avatar
Jeromy committed
150 151 152 153 154 155
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			n = nilnode
		}
156

157 158 159 160 161 162 163 164
		dserv := n.DAG
		local, _, _ := req.Option("local").Bool()
		if local {
			offlineexch := offline.Exchange(n.Blockstore)
			bserv := blockservice.New(n.Blockstore, offlineexch)
			dserv = dag.NewDAGService(bserv)
		}

165
		outChan := make(chan interface{}, 8)
166
		res.SetOutput((<-chan interface{})(outChan))
167

168
		fileAdder, err := coreunix.NewAdder(req.Context(), n.Pinning, n.Blockstore, dserv)
Jeromy's avatar
Jeromy committed
169 170 171 172
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
173 174

		fileAdder.Out = outChan
175 176 177 178 179
		fileAdder.Chunker = chunker
		fileAdder.Progress = progress
		fileAdder.Hidden = hidden
		fileAdder.Trickle = trickle
		fileAdder.Wrap = wrap
180
		fileAdder.Pin = dopin
Jeromy's avatar
Jeromy committed
181
		fileAdder.Silent = silent
182
		fileAdder.RawLeaves = rawblks
183

Jeromy's avatar
Jeromy committed
184 185
		if hash {
			md := dagtest.Mock()
186
			mr, err := mfs.NewRoot(req.Context(), md, ft.EmptyDirNode(), nil)
Jeromy's avatar
Jeromy committed
187 188 189 190 191 192 193 194
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

			fileAdder.SetMfsRoot(mr)
		}

195
		addAllAndPin := func(f files.File) error {
196 197 198 199 200 201 202 203 204 205 206 207 208 209
			// 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
				}
210
			}
211

212
			// copy intermediary nodes from editor to our actual dagservice
Jeromy's avatar
Jeromy committed
213
			_, err := fileAdder.Finalize()
214 215
			if err != nil {
				return err
216 217
			}

Stephen Whitmore's avatar
Stephen Whitmore committed
218 219 220 221
			if hash {
				return nil
			}

222
			return fileAdder.PinRoot()
223 224 225 226 227
		}

		go func() {
			defer close(outChan)
			if err := addAllAndPin(req.Files()); err != nil {
228 229
				res.SetError(err, cmds.ErrNormal)
				return
230
			}
231

232
		}()
233
	},
234
	PostRun: func(req cmds.Request, res cmds.Response) {
235 236 237
		if res.Error() != nil {
			return
		}
238 239 240 241 242
		outChan, ok := res.Output().(<-chan interface{})
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
243
		res.SetOutput(nil)
244

245
		quiet, _, err := req.Option("quiet").Bool()
246 247 248 249
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
250

251
		progress, _, err := req.Option(progressOptionName).Bool()
Jeromy's avatar
Jeromy committed
252 253 254 255 256
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

257
		var bar *pb.ProgressBar
258
		if progress {
259
			bar = pb.New64(0).SetUnits(pb.U_BYTES)
260
			bar.ManualUpdate = true
Jeromy's avatar
Jeromy committed
261 262 263
			bar.ShowTimeLeft = false
			bar.ShowPercent = false
			bar.Output = res.Stderr()
264 265 266
			bar.Start()
		}

267 268 269 270 271 272
		var sizeChan chan int64
		s, found := req.Values()["size"]
		if found {
			sizeChan = s.(chan int64)
		}

273 274
		lastFile := ""
		var totalProgress, prevFiles, lastBytes int64
275

276 277 278 279 280 281
	LOOP:
		for {
			select {
			case out, ok := <-outChan:
				if !ok {
					break LOOP
282
				}
283 284
				output := out.(*coreunix.AddedObject)
				if len(output.Hash) > 0 {
285
					if progress {
286 287 288 289 290 291 292 293
						// 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)
					}
294

295 296 297
				} else {
					log.Debugf("add progress: %v %v\n", output.Name, output.Bytes)

298
					if !progress {
299 300 301 302 303 304 305 306 307 308 309 310 311
						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)
312 313
				}

314
				if progress {
315
					bar.Update()
316
				}
317
			case size := <-sizeChan:
318
				if progress {
Jeromy's avatar
Jeromy committed
319 320 321 322 323
					bar.Total = size
					bar.ShowPercent = true
					bar.ShowBar = true
					bar.ShowTimeLeft = true
				}
Jeromy's avatar
Jeromy committed
324 325 326
			case <-req.Context().Done():
				res.SetError(req.Context().Err(), cmds.ErrNormal)
				return
327 328
			}
		}
329
	},
330
	Type: coreunix.AddedObject{},
331
}