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

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

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

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

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

84 85 86 87 88
		_, found, _ := req.Option(progressOptionName).Bool()
		if !found {
			req.SetOption(progressOptionName, true)
		}

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()
Richard Littauer's avatar
Richard Littauer committed
134
		dopin, _, _ := req.Option(pinOptionName).Bool()
Jeromy's avatar
Jeromy committed
135 136

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

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

152
		fileAdder, err := coreunix.NewAdder(req.Context(), n.Pinning, n.Blockstore, n.DAG)
Jeromy's avatar
Jeromy committed
153 154 155 156
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
157 158

		fileAdder.Out = outChan
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

Jeromy's avatar
Jeromy committed
167 168
		if hash {
			md := dagtest.Mock()
169
			mr, err := mfs.NewRoot(req.Context(), md, ft.EmptyDirNode(), nil)
Jeromy's avatar
Jeromy committed
170 171 172 173 174 175 176 177
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

			fileAdder.SetMfsRoot(mr)
		}

178
		addAllAndPin := func(f files.File) error {
179 180 181 182 183 184 185 186 187 188 189 190 191 192
			// 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
				}
193
			}
194

195
			// copy intermediary nodes from editor to our actual dagservice
Jeromy's avatar
Jeromy committed
196
			_, err := fileAdder.Finalize()
197 198
			if err != nil {
				return err
199 200
			}

Stephen Whitmore's avatar
Stephen Whitmore committed
201 202 203 204
			if hash {
				return nil
			}

205
			return fileAdder.PinRoot()
206 207 208 209 210
		}

		go func() {
			defer close(outChan)
			if err := addAllAndPin(req.Files()); err != nil {
211 212
				res.SetError(err, cmds.ErrNormal)
				return
213
			}
214

215
		}()
216
	},
217
	PostRun: func(req cmds.Request, res cmds.Response) {
218 219 220
		if res.Error() != nil {
			return
		}
221 222 223 224 225
		outChan, ok := res.Output().(<-chan interface{})
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
226
		res.SetOutput(nil)
227

228
		quiet, _, err := req.Option("quiet").Bool()
229 230 231 232
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}
233

Richard Littauer's avatar
Richard Littauer committed
234
		progress, _, err := req.Option(progressOptionName).Bool()
Jeromy's avatar
Jeromy committed
235 236 237 238 239
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

Jeromy's avatar
Jeromy committed
240 241 242 243 244 245
		silent, _, err := req.Option(silentOptionName).Bool()
		if err != nil {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

246 247
		if !quiet && !silent {
			progress = true
Jeromy's avatar
Jeromy committed
248
		}
249 250

		var bar *pb.ProgressBar
251
		if progress {
252
			bar = pb.New64(0).SetUnits(pb.U_BYTES)
253
			bar.ManualUpdate = true
Jeromy's avatar
Jeromy committed
254 255 256
			bar.ShowTimeLeft = false
			bar.ShowPercent = false
			bar.Output = res.Stderr()
257 258 259
			bar.Start()
		}

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

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

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

288 289 290
				} else {
					log.Debugf("add progress: %v %v\n", output.Name, output.Bytes)

291
					if !progress {
292 293 294 295 296 297 298 299 300 301 302 303 304
						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)
305 306
				}

307
				if progress {
308
					bar.Update()
309
				}
310
			case size := <-sizeChan:
311
				if progress {
Jeromy's avatar
Jeromy committed
312 313 314 315 316
					bar.Total = size
					bar.ShowPercent = true
					bar.ShowBar = true
					bar.ShowTimeLeft = true
				}
Jeromy's avatar
Jeromy committed
317 318 319
			case <-req.Context().Done():
				res.SetError(req.Context().Err(), cmds.ErrNormal)
				return
320 321
			}
		}
322
	},
323
	Type: coreunix.AddedObject{},
324
}