add.go 9.2 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{
Richard Littauer's avatar
Richard Littauer committed
40
		Tagline: "Add a file to ipfs.",
41
		ShortDescription: `
Richard Littauer's avatar
Richard Littauer committed
42 43 44
Adds contents of <path> to ipfs. Use -r to add directories.
Note that directories are added recursively, to form the ipfs
MerkleDAG.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
`,
		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
65 66 67
`,
	},

68
	Arguments: []cmds.Argument{
69
		cmds.FileArg("path", true, true, "The path to a file to be added to ipfs.").EnableRecursive().EnableStdin(),
70
	},
71 72
	Options: []cmds.Option{
		cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
Richard Littauer's avatar
Richard Littauer committed
73 74
		cmds.BoolOption(quietOptionName, "q", "Write minimal output.").Default(false),
		cmds.BoolOption(silentOptionName, "Write no output.").Default(false),
75
		cmds.BoolOption(progressOptionName, "p", "Stream progress data."),
Richard Littauer's avatar
Richard Littauer committed
76 77 78 79
		cmds.BoolOption(trickleOptionName, "t", "Use trickle-dag format for dag generation.").Default(false),
		cmds.BoolOption(onlyHashOptionName, "n", "Only chunk and hash - do not write to disk.").Default(false),
		cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object.").Default(false),
		cmds.BoolOption(hiddenOptionName, "H", "Include files that are hidden. Only takes effect on recursive add.").Default(false),
80
		cmds.StringOption(chunkerOptionName, "s", "Chunking algorithm to use."),
Richard Littauer's avatar
Richard Littauer committed
81
		cmds.BoolOption(pinOptionName, "Pin this object when adding.").Default(true),
82
		cmds.BoolOption(rawLeavesOptionName, "Use raw blocks for leaf nodes. (experimental)"),
83 84
	},
	PreRun: func(req cmds.Request) error {
gatesvp's avatar
gatesvp committed
85
		if quiet, _, _ := req.Option(quietOptionName).Bool(); quiet {
86 87 88
			return nil
		}

89 90 91 92 93
		_, found, _ := req.Option(progressOptionName).Bool()
		if !found {
			req.SetOption(progressOptionName, true)
		}

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

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

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

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

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

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

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

155 156 157 158 159 160 161 162
		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)
		}

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

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

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

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

			fileAdder.SetMfsRoot(mr)
		}

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

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

Stephen Whitmore's avatar
Stephen Whitmore committed
216 217 218 219
			if hash {
				return nil
			}

220
			return fileAdder.PinRoot()
221 222 223 224 225
		}

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

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

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

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

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

261 262
		if !quiet && !silent {
			progress = true
Jeromy's avatar
Jeromy committed
263
		}
264 265

		var bar *pb.ProgressBar
266
		if progress {
267
			bar = pb.New64(0).SetUnits(pb.U_BYTES)
268
			bar.ManualUpdate = true
Jeromy's avatar
Jeromy committed
269 270 271
			bar.ShowTimeLeft = false
			bar.ShowPercent = false
			bar.Output = res.Stderr()
272 273 274
			bar.Start()
		}

275 276 277 278 279 280
		var sizeChan chan int64
		s, found := req.Values()["size"]
		if found {
			sizeChan = s.(chan int64)
		}

281 282
		lastFile := ""
		var totalProgress, prevFiles, lastBytes int64
283

284 285 286 287 288 289
	LOOP:
		for {
			select {
			case out, ok := <-outChan:
				if !ok {
					break LOOP
290
				}
291 292
				output := out.(*coreunix.AddedObject)
				if len(output.Hash) > 0 {
293
					if progress {
294 295 296 297 298 299 300 301
						// 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)
					}
302

303 304 305
				} else {
					log.Debugf("add progress: %v %v\n", output.Name, output.Bytes)

306
					if !progress {
307 308 309 310 311 312 313 314 315 316 317 318 319
						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)
320 321
				}

322
				if progress {
323
					bar.Update()
324
				}
325
			case size := <-sizeChan:
326
				if progress {
Jeromy's avatar
Jeromy committed
327 328 329 330 331
					bar.Total = size
					bar.ShowPercent = true
					bar.ShowBar = true
					bar.ShowTimeLeft = true
				}
Jeromy's avatar
Jeromy committed
332 333 334
			case <-req.Context().Done():
				res.SetError(req.Context().Err(), cmds.ErrNormal)
				return
335 336
			}
		}
337
	},
338
	Type: coreunix.AddedObject{},
339
}