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

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

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

Jakub Sztandera's avatar
Jakub Sztandera committed
12 13 14 15 16 17
	cid "github.com/ipfs/go-cid"
	cidenc "github.com/ipfs/go-cidutil/cidenc"
	cmdkit "github.com/ipfs/go-ipfs-cmdkit"
	cmds "github.com/ipfs/go-ipfs-cmds"
	files "github.com/ipfs/go-ipfs-files"
	ipld "github.com/ipfs/go-ipld-format"
18
	path "github.com/ipfs/interface-go-ipfs-core/path"
Jakub Sztandera's avatar
Jakub Sztandera committed
19
	mh "github.com/multiformats/go-multihash"
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
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
68
		api, err := cmdenv.GetApi(env, req)
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
		var adder ipld.NodeAdder = api.Dag()
Steven Allen's avatar
Steven Allen committed
91
		if dopin {
92
			adder = api.Dag().Pinning()
Steven Allen's avatar
Steven Allen committed
93
		}
94
		b := ipld.NewBatch(req.Context, adder)
Steven Allen's avatar
Steven Allen committed
95

96
		it := req.Files.Entries()
97
		for it.Next() {
98 99
			file := files.FileFromEntry(it)
			if file == nil {
100
				return fmt.Errorf("expected a regular file")
Łukasz Magiera's avatar
Łukasz Magiera committed
101
			}
102
			nds, err := coredag.ParseInputs(ienc, format, file, mhType, -1)
Steven Allen's avatar
Steven Allen committed
103
			if err != nil {
Łukasz Magiera's avatar
Łukasz Magiera committed
104 105
				return err
			}
Steven Allen's avatar
Steven Allen committed
106 107 108
			if len(nds) == 0 {
				return fmt.Errorf("no node returned from ParseInputs")
			}
109

Steven Allen's avatar
Steven Allen committed
110
			for _, nd := range nds {
Hector Sanjuan's avatar
Hector Sanjuan committed
111
				err := b.Add(req.Context, nd)
Łukasz Magiera's avatar
Łukasz Magiera committed
112 113 114
				if err != nil {
					return err
				}
Jeromy's avatar
Jeromy committed
115
			}
116

Steven Allen's avatar
Steven Allen committed
117 118 119 120
			cid := nds[0].Cid()
			if err := res.Emit(&OutputObject{Cid: cid}); err != nil {
				return err
			}
121
		}
122
		if it.Err() != nil {
123
			return it.Err()
124
		}
125

Steven Allen's avatar
Steven Allen committed
126
		if err := b.Commit(); err != nil {
Overbool's avatar
Overbool committed
127 128
			return err
		}
129

Steven Allen's avatar
Steven Allen committed
130
		return nil
Overbool's avatar
Overbool committed
131 132 133 134
	},
	Type: OutputObject{},
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *OutputObject) error {
135 136 137 138 139
			enc, err := cmdenv.GetLowLevelCidEncoder(req)
			if err != nil {
				return err
			}
			fmt.Fprintln(w, enc.Encode(out.Cid))
Overbool's avatar
Overbool committed
140 141
			return nil
		}),
142 143 144 145
	},
}

var DagGetCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
146
	Helptext: cmdkit.HelpText{
147
		Tagline: "Get a dag node from ipfs.",
Jeromy's avatar
Jeromy committed
148
		ShortDescription: `
Łukasz Magiera's avatar
Łukasz Magiera committed
149
'ipfs dag get' fetches a dag node from ipfs and prints it out in the specified
150
format.
Jeromy's avatar
Jeromy committed
151
`,
152
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
153 154
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ref", true, false, "The object to get").EnableStdin(),
155
	},
Overbool's avatar
Overbool committed
156
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
157
		api, err := cmdenv.GetApi(env, req)
158
		if err != nil {
Overbool's avatar
Overbool committed
159
			return err
160 161
		}

162
		rp, err := api.ResolvePath(req.Context, path.New(req.Arguments[0]))
163
		if err != nil {
Overbool's avatar
Overbool committed
164
			return err
165
		}
166 167

		obj, err := api.Dag().Get(req.Context, rp.Cid())
168
		if err != nil {
Overbool's avatar
Overbool committed
169
			return err
170 171
		}

172
		var out interface{} = obj
173 174
		if len(rp.Remainder()) > 0 {
			rem := strings.Split(rp.Remainder(), "/")
175 176
			final, _, err := obj.Resolve(rem)
			if err != nil {
Overbool's avatar
Overbool committed
177
				return err
178 179 180
			}
			out = final
		}
181
		return cmds.EmitOnce(res, &out)
182 183
	},
}
Łukasz Magiera's avatar
Łukasz Magiera committed
184

Łukasz Magiera's avatar
Łukasz Magiera committed
185
// DagResolveCmd returns address of highest block within a path and a path remainder
Łukasz Magiera's avatar
Łukasz Magiera committed
186
var DagResolveCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
187
	Helptext: cmdkit.HelpText{
Łukasz Magiera's avatar
Łukasz Magiera committed
188 189 190 191 192
		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
193 194
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ref", true, false, "The path to resolve").EnableStdin(),
Łukasz Magiera's avatar
Łukasz Magiera committed
195
	},
Overbool's avatar
Overbool committed
196
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
197
		api, err := cmdenv.GetApi(env, req)
Łukasz Magiera's avatar
Łukasz Magiera committed
198
		if err != nil {
Overbool's avatar
Overbool committed
199
			return err
Łukasz Magiera's avatar
Łukasz Magiera committed
200 201
		}

202
		rp, err := api.ResolvePath(req.Context, path.New(req.Arguments[0]))
Łukasz Magiera's avatar
Łukasz Magiera committed
203
		if err != nil {
Overbool's avatar
Overbool committed
204
			return err
Łukasz Magiera's avatar
Łukasz Magiera committed
205 206
		}

207
		return cmds.EmitOnce(res, &ResolveOutput{
208 209
			Cid:     rp.Cid(),
			RemPath: rp.Remainder(),
Łukasz Magiera's avatar
Łukasz Magiera committed
210 211
		})
	},
Overbool's avatar
Overbool committed
212 213
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *ResolveOutput) error {
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
			var (
				enc cidenc.Encoder
				err error
			)
			switch {
			case !cmdenv.CidBaseDefined(req):
				// Not specified, check the path.
				enc, err = cmdenv.CidEncoderFromPath(req.Arguments[0])
				if err == nil {
					break
				}
				// Nope, fallback on the default.
				fallthrough
			default:
				enc, err = cmdenv.GetLowLevelCidEncoder(req)
				if err != nil {
					return err
				}
232 233
			}
			p := enc.Encode(out.Cid)
Overbool's avatar
Overbool committed
234 235
			if out.RemPath != "" {
				p = path.Join([]string{p, out.RemPath})
Łukasz Magiera's avatar
Łukasz Magiera committed
236 237
			}

Overbool's avatar
Overbool committed
238 239 240
			fmt.Fprint(w, p)
			return nil
		}),
Łukasz Magiera's avatar
Łukasz Magiera committed
241 242 243
	},
	Type: ResolveOutput{},
}