block.go 6.68 KB
Newer Older
1 2 3
package commands

import (
keks's avatar
keks committed
4
	"errors"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
	"fmt"
6
	"io"
Jan Winkelmann's avatar
Jan Winkelmann committed
7
	"os"
8

9 10
	files "github.com/ipfs/go-ipfs-files"

11
	util "github.com/ipfs/go-ipfs/blocks/blockstoreutil"
12
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
13

Jakub Sztandera's avatar
Jakub Sztandera committed
14 15
	cmds "github.com/ipfs/go-ipfs-cmds"
	options "github.com/ipfs/interface-go-ipfs-core/options"
16
	path "github.com/ipfs/interface-go-ipfs-core/path"
Jakub Sztandera's avatar
Jakub Sztandera committed
17
	mh "github.com/multiformats/go-multihash"
18 19
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
20 21 22 23 24 25 26
type BlockStat struct {
	Key  string
	Size int
}

func (bs BlockStat) String() string {
	return fmt.Sprintf("Key: %s\nSize: %d\n", bs.Key, bs.Size)
27 28
}

29
var BlockCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
30
	Helptext: cmds.HelpText{
31
		Tagline: "Interact with raw IPFS blocks.",
32
		ShortDescription: `
33
'ipfs block' is a plumbing command used to manipulate raw IPFS blocks.
34
Reads from stdin or writes to stdout, and <key> is a base58 encoded
35 36 37 38
multihash.
`,
	},

39
	Subcommands: map[string]*cmds.Command{
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
40 41 42
		"stat": blockStatCmd,
		"get":  blockGetCmd,
		"put":  blockPutCmd,
Kevin Atkinson's avatar
Kevin Atkinson committed
43
		"rm":   blockRmCmd,
44 45 46
	},
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
47
var blockStatCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
48
	Helptext: cmds.HelpText{
rht's avatar
rht committed
49
		Tagline: "Print information of a raw IPFS block.",
50
		ShortDescription: `
rht's avatar
rht committed
51
'ipfs block stat' is a plumbing command for retrieving information
52
on raw IPFS blocks. It outputs the following to stdout:
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
53 54 55 56

	Key  - the base58 encoded multihash
	Size - the size of the block in bytes

57 58
`,
	},
59

Steven Allen's avatar
Steven Allen committed
60 61
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, false, "The base58 multihash of an existing block to stat.").EnableStdin(),
62
	},
keks's avatar
keks committed
63
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
64
		api, err := cmdenv.GetApi(env, req)
Łukasz Magiera's avatar
Łukasz Magiera committed
65
		if err != nil {
keks's avatar
keks committed
66
			return err
Łukasz Magiera's avatar
Łukasz Magiera committed
67 68
		}

69
		b, err := api.Block().Stat(req.Context, path.New(req.Arguments[0]))
70
		if err != nil {
keks's avatar
keks committed
71
			return err
72
		}
73

keks's avatar
keks committed
74
		return cmds.EmitOnce(res, &BlockStat{
Łukasz Magiera's avatar
Łukasz Magiera committed
75 76
			Key:  b.Path().Cid().String(),
			Size: b.Size(),
77
		})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
78 79
	},
	Type: BlockStat{},
Jan Winkelmann's avatar
Jan Winkelmann committed
80
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
81
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, bs *BlockStat) error {
Jan Winkelmann's avatar
Jan Winkelmann committed
82 83 84
			_, err := fmt.Fprintf(w, "%s", bs)
			return err
		}),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
85 86
	},
}
87

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
88
var blockGetCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
89
	Helptext: cmds.HelpText{
rht's avatar
rht committed
90
		Tagline: "Get a raw IPFS block.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
91
		ShortDescription: `
92
'ipfs block get' is a plumbing command for retrieving raw IPFS blocks.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
93 94 95
It outputs to stdout, and <key> is a base58 encoded multihash.
`,
	},
96

Steven Allen's avatar
Steven Allen committed
97 98
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, false, "The base58 multihash of an existing block to get.").EnableStdin(),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
99
	},
keks's avatar
keks committed
100
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
101
		api, err := cmdenv.GetApi(env, req)
Łukasz Magiera's avatar
Łukasz Magiera committed
102
		if err != nil {
keks's avatar
keks committed
103
			return err
Łukasz Magiera's avatar
Łukasz Magiera committed
104 105
		}

106
		r, err := api.Block().Get(req.Context, path.New(req.Arguments[0]))
107
		if err != nil {
keks's avatar
keks committed
108
			return err
109 110
		}

keks's avatar
keks committed
111
		return res.Emit(r)
112 113 114
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
115 116 117 118 119 120
const (
	blockFormatOptionName = "format"
	mhtypeOptionName      = "mhtype"
	mhlenOptionName       = "mhlen"
)

121
var blockPutCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
122
	Helptext: cmds.HelpText{
123
		Tagline: "Store input as an IPFS block.",
124
		ShortDescription: `
125
'ipfs block put' is a plumbing command for storing raw IPFS blocks.
Dirk McCormick's avatar
Dirk McCormick committed
126
It reads from stdin, and outputs the block's CID to stdout.
127

Dirk McCormick's avatar
Dirk McCormick committed
128
Unless specified, this command returns dag-pb CIDv0 CIDs. Setting 'mhtype' to anything
Dirk McCormick's avatar
Dirk McCormick committed
129
other than 'sha2-256' or format to anything other than 'v0' will result in CIDv1.
130 131
`,
	},
132

Steven Allen's avatar
Steven Allen committed
133
	Arguments: []cmds.Argument{
134
		cmds.FileArg("data", true, true, "The data to be stored as an IPFS block.").EnableStdin(),
135
	},
Steven Allen's avatar
Steven Allen committed
136 137 138 139 140
	Options: []cmds.Option{
		cmds.StringOption(blockFormatOptionName, "f", "cid format for blocks to be created with."),
		cmds.StringOption(mhtypeOptionName, "multihash hash function").WithDefault("sha2-256"),
		cmds.IntOption(mhlenOptionName, "multihash hash length").WithDefault(-1),
		cmds.BoolOption(pinOptionName, "pin added blocks recursively").WithDefault(false),
141
	},
keks's avatar
keks committed
142
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
143
		api, err := cmdenv.GetApi(env, req)
144
		if err != nil {
keks's avatar
keks committed
145
			return err
146
		}
147

Kejie Zhang's avatar
Kejie Zhang committed
148
		mhtype, _ := req.Options[mhtypeOptionName].(string)
149 150
		mhtval, ok := mh.Names[mhtype]
		if !ok {
keks's avatar
keks committed
151
			return fmt.Errorf("unrecognized multihash function: %s", mhtype)
152 153
		}

Kejie Zhang's avatar
Kejie Zhang committed
154
		mhlen, ok := req.Options[mhlenOptionName].(int)
Łukasz Magiera's avatar
Łukasz Magiera committed
155
		if !ok {
keks's avatar
keks committed
156
			return errors.New("missing option \"mhlen\"")
Łukasz Magiera's avatar
Łukasz Magiera committed
157
		}
158

Kejie Zhang's avatar
Kejie Zhang committed
159
		format, formatSet := req.Options[blockFormatOptionName].(string)
160
		if !formatSet {
161
			if mhtval != mh.SHA2_256 || (mhlen != -1 && mhlen != 32) {
162
				format = "protobuf"
163 164
			} else {
				format = "v0"
165
			}
166
		}
167

Łukasz Magiera's avatar
Łukasz Magiera committed
168 169
		pin, _ := req.Options[pinOptionName].(bool)

170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
		it := req.Files.Entries()
		for it.Next() {
			file := files.FileFromEntry(it)
			if file == nil {
				return errors.New("expected a file")
			}

			p, err := api.Block().Put(req.Context, file,
				options.Block.Hash(mhtval, mhlen),
				options.Block.Format(format),
				options.Block.Pin(pin))
			if err != nil {
				return err
			}

			err = res.Emit(&BlockStat{
				Key:  p.Path().Cid().String(),
				Size: p.Size(),
			})
			if err != nil {
				return err
			}
192 193
		}

194
		return it.Err()
195
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
196
	Encoders: cmds.EncoderMap{
Overbool's avatar
Overbool committed
197
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, bs *BlockStat) error {
Jan Winkelmann's avatar
Jan Winkelmann committed
198 199 200
			_, err := fmt.Fprintf(w, "%s\n", bs.Key)
			return err
		}),
201
	},
Matt Bell's avatar
Matt Bell committed
202
	Type: BlockStat{},
203
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
204

Kejie Zhang's avatar
Kejie Zhang committed
205 206 207 208 209
const (
	forceOptionName      = "force"
	blockQuietOptionName = "quiet"
)

Kevin Atkinson's avatar
Kevin Atkinson committed
210
var blockRmCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
211
	Helptext: cmds.HelpText{
Kevin Atkinson's avatar
Kevin Atkinson committed
212 213 214
		Tagline: "Remove IPFS block(s).",
		ShortDescription: `
'ipfs block rm' is a plumbing command for removing raw ipfs blocks.
Łukasz Magiera's avatar
Łukasz Magiera committed
215
It takes a list of base58 encoded multihashes to remove.
Kevin Atkinson's avatar
Kevin Atkinson committed
216 217
`,
	},
Steven Allen's avatar
Steven Allen committed
218 219
	Arguments: []cmds.Argument{
		cmds.StringArg("hash", true, true, "Bash58 encoded multihash of block(s) to remove."),
Kevin Atkinson's avatar
Kevin Atkinson committed
220
	},
Steven Allen's avatar
Steven Allen committed
221 222 223
	Options: []cmds.Option{
		cmds.BoolOption(forceOptionName, "f", "Ignore nonexistent blocks."),
		cmds.BoolOption(blockQuietOptionName, "q", "Write minimal output."),
224
	},
keks's avatar
keks committed
225
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
226
		api, err := cmdenv.GetApi(env, req)
Kevin Atkinson's avatar
Kevin Atkinson committed
227
		if err != nil {
keks's avatar
keks committed
228
			return err
Kevin Atkinson's avatar
Kevin Atkinson committed
229
		}
Łukasz Magiera's avatar
Łukasz Magiera committed
230

Kejie Zhang's avatar
Kejie Zhang committed
231 232
		force, _ := req.Options[forceOptionName].(bool)
		quiet, _ := req.Options[blockQuietOptionName].(bool)
Łukasz Magiera's avatar
Łukasz Magiera committed
233 234 235

		// TODO: use batching coreapi when done
		for _, b := range req.Arguments {
236
			rp, err := api.ResolvePath(req.Context, path.New(b))
Steven Allen's avatar
Steven Allen committed
237 238 239
			if err != nil {
				return err
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
240

Łukasz Magiera's avatar
Łukasz Magiera committed
241
			err = api.Block().Rm(req.Context, rp, options.Block.Force(force))
242
			if err != nil {
Overbool's avatar
Overbool committed
243
				if err := res.Emit(&util.RemovedBlock{
Łukasz Magiera's avatar
Łukasz Magiera committed
244 245
					Hash:  rp.Cid().String(),
					Error: err.Error(),
Overbool's avatar
Overbool committed
246
				}); err != nil {
keks's avatar
keks committed
247 248
					return err
				}
Overbool's avatar
Overbool committed
249
				continue
Łukasz Magiera's avatar
Łukasz Magiera committed
250
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
251

252
			if !quiet {
keks's avatar
keks committed
253
				err := res.Emit(&util.RemovedBlock{
254 255
					Hash: rp.Cid().String(),
				})
keks's avatar
keks committed
256 257 258
				if err != nil {
					return err
				}
259
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
260
		}
keks's avatar
keks committed
261 262

		return nil
Kevin Atkinson's avatar
Kevin Atkinson committed
263
	},
264
	PostRun: cmds.PostRunMap{
keks's avatar
keks committed
265 266
		cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
			return util.ProcRmOutput(res.Next, os.Stdout, os.Stderr)
267
		},
Kevin Atkinson's avatar
Kevin Atkinson committed
268
	},
269
	Type: util.RemovedBlock{},
Kevin Atkinson's avatar
Kevin Atkinson committed
270
}