add.go 8.6 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{
33
		Tagline: "Add a file or directory to ipfs.",
34
		ShortDescription: `
35
Adds contents of <path> to ipfs. Use -r to add directories (recursively).
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
`,
		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
56 57 58
`,
	},

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

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

		req.SetOption(progressOptionName, progress)
86 87 88 89

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

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

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

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

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

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

		if !pin_found { // default
			dopin = true
		}
Jeromy's avatar
Jeromy committed
137 138

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

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

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

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

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

Stephen Whitmore's avatar
Stephen Whitmore committed
190 191 192 193
			if hash {
				return nil
			}

194
			return fileAdder.PinRoot()
195 196 197 198 199
		}

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

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

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

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

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

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

		var bar *pb.ProgressBar
		var terminalWidth int
		if showProgressBar {
245
			bar = pb.New64(0).SetUnits(pb.U_BYTES)
246 247 248 249 250 251 252 253 254
			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
255
				bar.Output = res.Stderr()
256 257 258 259 260
				log.Infof("terminal width: %v\n", terminalWidth)
			}
			bar.Update()
		}

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

267 268
		lastFile := ""
		var totalProgress, prevFiles, lastBytes int64
269

270 271 272 273 274 275
	LOOP:
		for {
			select {
			case out, ok := <-outChan:
				if !ok {
					break LOOP
276
				}
277 278 279 280 281 282 283 284 285 286 287
				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)
					}
288

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
				} 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)
306 307
				}

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