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

import (
	"fmt"
5
	"io"
6

Jeromy's avatar
Jeromy committed
7
	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb"
8
	"github.com/ipfs/go-ipfs/core/coreunix"
Jeromy's avatar
Jeromy committed
9

10 11 12
	cmds "github.com/ipfs/go-ipfs/commands"
	files "github.com/ipfs/go-ipfs/commands/files"
	core "github.com/ipfs/go-ipfs/core"
13
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
14 15 16 17 18
)

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

19
const (
gatesvp's avatar
gatesvp committed
20
	quietOptionName    = "quiet"
Jeromy's avatar
Jeromy committed
21
	silentOptionName   = "silent"
22
	progressOptionName = "progress"
23
	trickleOptionName  = "trickle"
24
	wrapOptionName     = "wrap-with-directory"
gatesvp's avatar
gatesvp committed
25 26
	hiddenOptionName   = "hidden"
	onlyHashOptionName = "only-hash"
27
	chunkerOptionName  = "chunker"
28
	pinOptionName      = "pin"
29
)
30

31
var AddCmd = &cmds.Command{
32
	Helptext: cmds.HelpText{
Richard Littauer's avatar
Richard Littauer committed
33
		Tagline: "Add a file to ipfs.",
34 35 36
		ShortDescription: `
Adds contents of <path> to ipfs. Use -r to add directories.
Note that directories are added recursively, to form the ipfs
Richard Littauer's avatar
Richard Littauer committed
37
MerkleDAG.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
`,
		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
58 59 60
`,
	},

61
	Arguments: []cmds.Argument{
62
		cmds.FileArg("path", true, true, "The path to a file to be added to IPFS.").EnableRecursive().EnableStdin(),
63
	},
64 65
	Options: []cmds.Option{
		cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
66 67 68 69 70 71
		cmds.BoolOption(quietOptionName, "q", "Write minimal output."),
		cmds.BoolOption(silentOptionName, "Write no output."),
		cmds.BoolOption(progressOptionName, "p", "Stream progress data."),
		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."),
72
		cmds.BoolOption(hiddenOptionName, "H", "Include files that are hidden. Only takes effect on recursive add."),
73 74
		cmds.StringOption(chunkerOptionName, "s", "Chunking algorithm to use."),
		cmds.BoolOption(pinOptionName, "Pin this object when adding.  Default: true."),
75 76
	},
	PreRun: func(req cmds.Request) error {
gatesvp's avatar
gatesvp committed
77
		if quiet, _, _ := req.Option(quietOptionName).Bool(); quiet {
78 79 80
			return nil
		}

Jeromy's avatar
Jeromy committed
81 82 83 84 85 86 87
		// ipfs cli progress bar defaults to true
		progress, found, _ := req.Option(progressOptionName).Bool()
		if !found {
			progress = true
		}

		req.SetOption(progressOptionName, progress)
88 89 90 91

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

96 97
		sizeCh := make(chan int64, 1)
		req.Values()["size"] = sizeCh
rht's avatar
rht committed
98

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

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

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

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

		if !pin_found { // default
			dopin = true
		}
Jeromy's avatar
Jeromy committed
139 140

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

153
		outChan := make(chan interface{}, 8)
154
		res.SetOutput((<-chan interface{})(outChan))
155

Jeromy's avatar
Jeromy committed
156 157 158 159 160
		fileAdder, err := coreunix.NewAdder(req.Context(), n, outChan)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
161 162 163 164 165
		fileAdder.Chunker = chunker
		fileAdder.Progress = progress
		fileAdder.Hidden = hidden
		fileAdder.Trickle = trickle
		fileAdder.Wrap = wrap
166
		fileAdder.Pin = dopin
Jeromy's avatar
Jeromy committed
167
		fileAdder.Silent = silent
168

169
		addAllAndPin := func(f files.File) error {
170 171 172 173 174 175 176 177 178 179 180 181 182 183
			// 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
				}
184
			}
185

186
			// copy intermediary nodes from editor to our actual dagservice
Jeromy's avatar
Jeromy committed
187
			_, err := fileAdder.Finalize()
188 189
			if err != nil {
				return err
190 191
			}

Stephen Whitmore's avatar
Stephen Whitmore committed
192 193 194 195
			if hash {
				return nil
			}

196
			return fileAdder.PinRoot()
197 198 199 200 201
		}

		go func() {
			defer close(outChan)
			if err := addAllAndPin(req.Files()); err != nil {
202 203
				res.SetError(err, cmds.ErrNormal)
				return
204
			}
205

206
		}()
207
	},
208
	PostRun: func(req cmds.Request, res cmds.Response) {
209 210 211
		if res.Error() != nil {
			return
		}
212 213 214 215 216
		outChan, ok := res.Output().(<-chan interface{})
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
217
		res.SetOutput(nil)
218

219
		quiet, _, err := req.Option("quiet").Bool()
220 221 222 223
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
224

Jeromy's avatar
Jeromy committed
225
		progress, prgFound, err := req.Option(progressOptionName).Bool()
Jeromy's avatar
Jeromy committed
226 227 228 229 230
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

Jeromy's avatar
Jeromy committed
231 232 233 234 235 236
		silent, _, err := req.Option(silentOptionName).Bool()
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

Jeromy's avatar
Jeromy committed
237 238 239
		var showProgressBar bool
		if prgFound {
			showProgressBar = progress
Jeromy's avatar
Jeromy committed
240
		} else if !quiet && !silent {
Jeromy's avatar
Jeromy committed
241 242
			showProgressBar = true
		}
243 244 245 246

		var bar *pb.ProgressBar
		var terminalWidth int
		if showProgressBar {
247
			bar = pb.New64(0).SetUnits(pb.U_BYTES)
248 249 250 251 252 253 254 255 256
			bar.ManualUpdate = true
			bar.Start()

			// the progress bar lib doesn't give us a way to get the width of the output,
			// so as a hack we just use a callback to measure the output, then git rid of it
			terminalWidth = 0
			bar.Callback = func(line string) {
				terminalWidth = len(line)
				bar.Callback = nil
257
				bar.Output = res.Stderr()
258 259 260 261 262
				log.Infof("terminal width: %v\n", terminalWidth)
			}
			bar.Update()
		}

263 264 265 266 267 268
		var sizeChan chan int64
		s, found := req.Values()["size"]
		if found {
			sizeChan = s.(chan int64)
		}

269 270
		lastFile := ""
		var totalProgress, prevFiles, lastBytes int64
271

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

291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
				} else {
					log.Debugf("add progress: %v %v\n", output.Name, output.Bytes)

					if !showProgressBar {
						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)
308 309
				}

310 311
				if showProgressBar {
					bar.Update()
312
				}
313
			case size := <-sizeChan:
Jeromy's avatar
Jeromy committed
314 315 316 317 318 319
				if showProgressBar {
					bar.Total = size
					bar.ShowPercent = true
					bar.ShowBar = true
					bar.ShowTimeLeft = true
				}
320 321
			}
		}
322
	},
323
	Type: coreunix.AddedObject{},
324
}