add.go 9.29 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/Qmb912gdngC1UWwTkhuW8knyRbcWeu5kqkxBpveLmW8bSr/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 {
gatesvp's avatar
gatesvp committed
83
		if quiet, _, _ := req.Option(quietOptionName).Bool(); quiet {
84 85 86
			return nil
		}

87
		// ipfs cli progress bar defaults to true
88 89 90 91 92
		_, found, _ := req.Option(progressOptionName).Bool()
		if !found {
			req.SetOption(progressOptionName, true)
		}

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

100 101
		sizeCh := make(chan int64, 1)
		req.Values()["size"] = sizeCh
rht's avatar
rht committed
102

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

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

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

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

141 142 143 144
		if !pin_found { // default
			dopin = true
		}

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

158 159 160 161 162 163 164 165
		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)
		}

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

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

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

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

			fileAdder.SetMfsRoot(mr)
		}

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

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

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

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

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

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

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

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

Jeromy's avatar
Jeromy committed
258 259 260 261 262 263
		silent, _, err := req.Option(silentOptionName).Bool()
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

264 265 266 267 268
		var showProgressBar bool
		if prgFound {
			showProgressBar = progress
		} else if !quiet && !silent {
			showProgressBar = true
Jeromy's avatar
Jeromy committed
269
		}
270 271

		var bar *pb.ProgressBar
272
		if showProgressBar {
273
			bar = pb.New64(0).SetUnits(pb.U_BYTES)
274
			bar.ManualUpdate = true
Jeromy's avatar
Jeromy committed
275 276 277
			bar.ShowTimeLeft = false
			bar.ShowPercent = false
			bar.Output = res.Stderr()
278 279 280
			bar.Start()
		}

281 282 283 284 285 286
		var sizeChan chan int64
		s, found := req.Values()["size"]
		if found {
			sizeChan = s.(chan int64)
		}

287 288
		lastFile := ""
		var totalProgress, prevFiles, lastBytes int64
289

290 291 292 293 294 295
	LOOP:
		for {
			select {
			case out, ok := <-outChan:
				if !ok {
					break LOOP
296
				}
297 298
				output := out.(*coreunix.AddedObject)
				if len(output.Hash) > 0 {
299
					if showProgressBar {
300 301 302 303 304 305 306 307
						// 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)
					}
308

309 310 311
				} else {
					log.Debugf("add progress: %v %v\n", output.Name, output.Bytes)

312
					if !showProgressBar {
313 314 315 316 317 318 319 320 321 322 323 324 325
						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)
326 327
				}

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