block.go 6.67 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 a multihash to stdout.
127

Dirk McCormick's avatar
Dirk McCormick committed
128 129
By default the multihash is output in CIDv0 format. Setting 'mhtype' to anything
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
}