dag.go 6.49 KB
Newer Older
1 2 3 4 5
package dagcmd

import (
	"fmt"
	"io"
6
	"math"
7

Overbool's avatar
Overbool committed
8 9 10 11
	"github.com/ipfs/go-ipfs/core/commands/cmdenv"
	"github.com/ipfs/go-ipfs/core/commands/e"
	"github.com/ipfs/go-ipfs/core/coredag"
	"github.com/ipfs/go-ipfs/pin"
12

13
	cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
14
	mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
Overbool's avatar
Overbool committed
15
	path "gx/ipfs/QmRKuTyCzg7HFBcV1YUhzStroGtJSb8iWgyxfsDCwFhWTS/go-path"
16
	files "gx/ipfs/QmZMWMvWMVKCbHetJ4RgndbuEF1io2UpUxwQwtNjtYPzSC/go-ipfs-files"
17
	ipld "gx/ipfs/QmdDXJs4axxefSPgK6Y1QhpJWKuDPnGJiqgq4uncb4rFHL/go-ipld-format"
Overbool's avatar
Overbool committed
18
	cmds "gx/ipfs/QmdTmGruUz23vgzym3uWpnAEQdGdGifQqBvP8UXSRjG8gZ/go-ipfs-cmds"
19
	cmdkit "gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
20 21 22
)

var DagCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
23
	Helptext: cmdkit.HelpText{
Jeromy's avatar
Jeromy committed
24 25 26 27 28 29 30
		Tagline: "Interact with ipld dag objects.",
		ShortDescription: `
'ipfs dag' is used for creating and manipulating dag objects.

This subcommand is currently an experimental feature, but it is intended
to deprecate and replace the existing 'ipfs object' command moving forward.
		`,
31 32
	},
	Subcommands: map[string]*cmds.Command{
Łukasz Magiera's avatar
Łukasz Magiera committed
33 34 35
		"put":     DagPutCmd,
		"get":     DagGetCmd,
		"resolve": DagResolveCmd,
36 37 38
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
39
// OutputObject is the output type of 'dag put' command
40
type OutputObject struct {
41
	Cid cid.Cid
42 43
}

Łukasz Magiera's avatar
Łukasz Magiera committed
44
// ResolveOutput is the output type of 'dag resolve' command
Łukasz Magiera's avatar
Łukasz Magiera committed
45
type ResolveOutput struct {
46
	Cid     cid.Cid
Łukasz Magiera's avatar
Łukasz Magiera committed
47 48 49
	RemPath string
}

50
var DagPutCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
51
	Helptext: cmdkit.HelpText{
52
		Tagline: "Add a dag node to ipfs.",
Jeromy's avatar
Jeromy committed
53 54 55 56
		ShortDescription: `
'ipfs dag put' accepts input from a file or stdin and parses it
into an object of the specified format.
`,
57
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
58 59
	Arguments: []cmdkit.Argument{
		cmdkit.FileArg("object data", true, true, "The object to put").EnableStdin(),
60
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
61
	Options: []cmdkit.Option{
62 63
		cmdkit.StringOption("format", "f", "Format that the object will be added as.").WithDefault("cbor"),
		cmdkit.StringOption("input-enc", "Format that the input object will be.").WithDefault("json"),
64
		cmdkit.BoolOption("pin", "Pin this object when adding."),
65
		cmdkit.StringOption("hash", "Hash function to use").WithDefault(""),
66
	},
Overbool's avatar
Overbool committed
67 68
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
69
		if err != nil {
Overbool's avatar
Overbool committed
70
			return err
71 72
		}

Overbool's avatar
Overbool committed
73 74 75 76
		ienc, _ := req.Options["input-enc"].(string)
		format, _ := req.Options["format"].(string)
		hash, _ := req.Options["hash"].(string)
		dopin, _ := req.Options["pin"].(bool)
77

78 79 80 81 82 83 84 85
		// mhType tells inputParser which hash should be used. MaxUint64 means 'use
		// default hash' (sha256 for cbor, sha1 for git..)
		mhType := uint64(math.MaxUint64)

		if hash != "" {
			var ok bool
			mhType, ok = mh.Names[hash]
			if !ok {
Overbool's avatar
Overbool committed
86
				return fmt.Errorf("%s in not a valid multihash name", hash)
87 88 89
			}
		}

90 91 92
		outChan := make(chan interface{}, 8)

		addAllAndPin := func(f files.File) error {
Łukasz Magiera's avatar
Łukasz Magiera committed
93
			cids := cid.NewSet()
Overbool's avatar
Overbool committed
94
			b := ipld.NewBatch(req.Context, nd.DAG)
Łukasz Magiera's avatar
Łukasz Magiera committed
95

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
			for {
				file, err := f.NextFile()
				if err == io.EOF {
					// Finished the list of files.
					break
				} else if err != nil {
					return err
				}

				nds, err := coredag.ParseInputs(ienc, format, file, mhType, -1)
				if err != nil {
					return err
				}
				if len(nds) == 0 {
					return fmt.Errorf("no node returned from ParseInputs")
				}

Łukasz Magiera's avatar
Łukasz Magiera committed
113
				for _, nd := range nds {
114
					err := b.Add(nd)
Łukasz Magiera's avatar
Łukasz Magiera committed
115 116
					if err != nil {
						return err
117
					}
Łukasz Magiera's avatar
Łukasz Magiera committed
118
				}
119

Łukasz Magiera's avatar
Łukasz Magiera committed
120 121
				cid := nds[0].Cid()
				cids.Add(cid)
Alec Brickner's avatar
Alec Brickner committed
122 123 124

				select {
				case outChan <- &OutputObject{Cid: cid}:
Overbool's avatar
Overbool committed
125
				case <-req.Context.Done():
Alec Brickner's avatar
Alec Brickner committed
126 127
					return nil
				}
Łukasz Magiera's avatar
Łukasz Magiera committed
128
			}
129

Łukasz Magiera's avatar
Łukasz Magiera committed
130 131 132
			if err := b.Commit(); err != nil {
				return err
			}
133

Łukasz Magiera's avatar
Łukasz Magiera committed
134
			if dopin {
Overbool's avatar
Overbool committed
135
				defer nd.Blockstore.PinLock().Unlock()
Łukasz Magiera's avatar
Łukasz Magiera committed
136

137
				cids.ForEach(func(c cid.Cid) error {
Overbool's avatar
Overbool committed
138
					nd.Pinning.PinWithMode(c, pin.Recursive)
Łukasz Magiera's avatar
Łukasz Magiera committed
139
					return nil
Łukasz Magiera's avatar
Łukasz Magiera committed
140
				})
Łukasz Magiera's avatar
Łukasz Magiera committed
141

Overbool's avatar
Overbool committed
142
				err := nd.Pinning.Flush()
Łukasz Magiera's avatar
Łukasz Magiera committed
143 144 145
				if err != nil {
					return err
				}
Jeromy's avatar
Jeromy committed
146
			}
147

148
			return nil
149
		}
150

Overbool's avatar
Overbool committed
151
		errC := make(chan error)
152
		go func() {
Overbool's avatar
Overbool committed
153 154
			var err error
			defer func() { errC <- err }()
155
			defer close(outChan)
Overbool's avatar
Overbool committed
156
			err = addAllAndPin(req.Files)
157 158
		}()

Overbool's avatar
Overbool committed
159 160 161 162
		err = res.Emit(outChan)
		if err != nil {
			return err
		}
163

Overbool's avatar
Overbool committed
164 165 166 167 168 169 170 171
		return <-errC
	},
	Type: OutputObject{},
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *OutputObject) error {
			fmt.Fprintln(w, out.Cid.String())
			return nil
		}),
172 173 174 175
	},
}

var DagGetCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
176
	Helptext: cmdkit.HelpText{
177
		Tagline: "Get a dag node from ipfs.",
Jeromy's avatar
Jeromy committed
178
		ShortDescription: `
Łukasz Magiera's avatar
Łukasz Magiera committed
179
'ipfs dag get' fetches a dag node from ipfs and prints it out in the specified
180
format.
Jeromy's avatar
Jeromy committed
181
`,
182
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
183 184
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ref", true, false, "The object to get").EnableStdin(),
185
	},
Overbool's avatar
Overbool committed
186 187
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
188
		if err != nil {
Overbool's avatar
Overbool committed
189
			return err
190 191
		}

Overbool's avatar
Overbool committed
192
		p, err := path.ParsePath(req.Arguments[0])
193
		if err != nil {
Overbool's avatar
Overbool committed
194
			return err
195 196
		}

Overbool's avatar
Overbool committed
197
		lastCid, rem, err := nd.Resolver.ResolveToLastNode(req.Context, p)
198
		if err != nil {
Overbool's avatar
Overbool committed
199
			return err
200
		}
Overbool's avatar
Overbool committed
201
		obj, err := nd.DAG.Get(req.Context, lastCid)
202
		if err != nil {
Overbool's avatar
Overbool committed
203
			return err
204 205
		}

206 207 208 209
		var out interface{} = obj
		if len(rem) > 0 {
			final, _, err := obj.Resolve(rem)
			if err != nil {
Overbool's avatar
Overbool committed
210
				return err
211 212 213
			}
			out = final
		}
Overbool's avatar
Overbool committed
214
		return res.Emit(&out)
215 216
	},
}
Łukasz Magiera's avatar
Łukasz Magiera committed
217

Łukasz Magiera's avatar
Łukasz Magiera committed
218
// DagResolveCmd returns address of highest block within a path and a path remainder
Łukasz Magiera's avatar
Łukasz Magiera committed
219
var DagResolveCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
220
	Helptext: cmdkit.HelpText{
Łukasz Magiera's avatar
Łukasz Magiera committed
221 222 223 224 225
		Tagline: "Resolve ipld block",
		ShortDescription: `
'ipfs dag resolve' fetches a dag node from ipfs, prints it's address and remaining path.
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
226 227
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ref", true, false, "The path to resolve").EnableStdin(),
Łukasz Magiera's avatar
Łukasz Magiera committed
228
	},
Overbool's avatar
Overbool committed
229 230
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Łukasz Magiera's avatar
Łukasz Magiera committed
231
		if err != nil {
Overbool's avatar
Overbool committed
232
			return err
Łukasz Magiera's avatar
Łukasz Magiera committed
233 234
		}

Overbool's avatar
Overbool committed
235
		p, err := path.ParsePath(req.Arguments[0])
Łukasz Magiera's avatar
Łukasz Magiera committed
236
		if err != nil {
Overbool's avatar
Overbool committed
237
			return err
Łukasz Magiera's avatar
Łukasz Magiera committed
238 239
		}

Overbool's avatar
Overbool committed
240
		lastCid, rem, err := nd.Resolver.ResolveToLastNode(req.Context, p)
Łukasz Magiera's avatar
Łukasz Magiera committed
241
		if err != nil {
Overbool's avatar
Overbool committed
242
			return err
Łukasz Magiera's avatar
Łukasz Magiera committed
243 244
		}

Overbool's avatar
Overbool committed
245
		return res.Emit(&ResolveOutput{
246
			Cid:     lastCid,
Łukasz Magiera's avatar
Łukasz Magiera committed
247 248 249
			RemPath: path.Join(rem),
		})
	},
Overbool's avatar
Overbool committed
250 251 252 253 254
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *ResolveOutput) error {
			p := out.Cid.String()
			if out.RemPath != "" {
				p = path.Join([]string{p, out.RemPath})
Łukasz Magiera's avatar
Łukasz Magiera committed
255 256
			}

Overbool's avatar
Overbool committed
257 258 259
			fmt.Fprint(w, p)
			return nil
		}),
Łukasz Magiera's avatar
Łukasz Magiera committed
260 261 262
	},
	Type: ResolveOutput{},
}
Jan Winkelmann's avatar
Jan Winkelmann committed
263 264 265 266 267 268 269 270 271 272 273 274 275 276

// copy+pasted from ../commands.go
func unwrapOutput(i interface{}) (interface{}, error) {
	var (
		ch <-chan interface{}
		ok bool
	)

	if ch, ok = i.(<-chan interface{}); !ok {
		return nil, e.TypeErr(ch, i)
	}

	return <-ch, nil
}