add.go 9.23 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, _, _ := req.Option(pinOptionName).Bool()
139
		rawblks, _, _ := req.Option(rawLeavesOptionName).Bool()
Jeromy's avatar
Jeromy committed
140 141

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

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

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

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

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

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

			fileAdder.SetMfsRoot(mr)
		}

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

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

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

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

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

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

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

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

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

260 261 262 263 264
		var showProgressBar bool
		if prgFound {
			showProgressBar = progress
		} else if !quiet && !silent {
			showProgressBar = true
Jeromy's avatar
Jeromy committed
265
		}
266 267

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

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

283 284
		lastFile := ""
		var totalProgress, prevFiles, lastBytes int64
285

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

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

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

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