request.go 8.71 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2
package commands

3
import (
Jeromy's avatar
Jeromy committed
4
	"bufio"
5
	"errors"
6
	"fmt"
7 8
	"io"
	"os"
9 10
	"reflect"
	"strconv"
Jeromy's avatar
Jeromy committed
11
	"time"
12

13 14 15
	"github.com/ipfs/go-ipfs/commands/files"
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/repo/config"
16
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
Jeromy's avatar
Jeromy committed
17
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
18 19
)

Henry's avatar
Henry committed
20
type OptMap map[string]interface{}
21

22
type Context struct {
23
	Online     bool
24
	ConfigRoot string
25
	ReqLog     *ReqLog
26 27 28 29

	config     *config.Config
	LoadConfig func(path string) (*config.Config, error)

30 31
	node          *core.IpfsNode
	ConstructNode func() (*core.IpfsNode, error)
32 33 34 35 36 37 38 39
}

// GetConfig returns the config of the current Command exection
// context. It may load it with the providied function.
func (c *Context) GetConfig() (*config.Config, error) {
	var err error
	if c.config == nil {
		if c.LoadConfig == nil {
40
			return nil, errors.New("nil LoadConfig function")
41 42 43 44 45 46 47
		}
		c.config, err = c.LoadConfig(c.ConfigRoot)
	}
	return c.config, err
}

// GetNode returns the node of the current Command exection
rht's avatar
rht committed
48
// context. It may construct it with the provided function.
49 50 51 52
func (c *Context) GetNode() (*core.IpfsNode, error) {
	var err error
	if c.node == nil {
		if c.ConstructNode == nil {
53
			return nil, errors.New("nil ConstructNode function")
54 55 56 57 58 59 60 61 62 63
		}
		c.node, err = c.ConstructNode()
	}
	return c.node, err
}

// NodeWithoutConstructing returns the underlying node variable
// so that clients may close it.
func (c *Context) NodeWithoutConstructing() *core.IpfsNode {
	return c.node
64 65
}

Matt Bell's avatar
Matt Bell committed
66
// Request represents a call to a command from a consumer
67 68
type Request interface {
	Path() []string
69
	Option(name string) *OptionValue
Henry's avatar
Henry committed
70
	Options() OptMap
71
	SetOption(name string, val interface{})
Henry's avatar
Henry committed
72
	SetOptions(opts OptMap) error
73 74
	Arguments() []string
	SetArguments([]string)
75 76
	Files() files.File
	SetFiles(files.File)
Jeromy's avatar
Jeromy committed
77 78 79 80
	Context() context.Context
	SetRootContext(context.Context) error
	InvocContext() *Context
	SetInvocContext(Context)
81
	Command() *Command
82
	Values() map[string]interface{}
83
	Stdin() io.Reader
Jeromy's avatar
Jeromy committed
84
	VarArgs(func(string) error) error
85

86
	ConvertOptions() error
87 88 89
}

type request struct {
90
	path       []string
Henry's avatar
Henry committed
91
	options    OptMap
92
	arguments  []string
93
	files      files.File
94 95
	cmd        *Command
	ctx        Context
Jeromy's avatar
Jeromy committed
96
	rctx       context.Context
97
	optionDefs map[string]Option
98
	values     map[string]interface{}
99
	stdin      io.Reader
Matt Bell's avatar
Matt Bell committed
100 101
}

102 103
// Path returns the command path of this request
func (r *request) Path() []string {
104 105 106
	return r.path
}

107
// Option returns the value of the option for given name.
108
func (r *request) Option(name string) *OptionValue {
109 110
	// find the option with the specified name
	option, found := r.optionDefs[name]
111 112 113 114 115
	if !found {
		return nil
	}

	// try all the possible names, break if we find a value
116 117
	for _, n := range option.Names() {
		val, found := r.options[n]
118
		if found {
119
			return &OptionValue{val, found, option}
120 121 122
		}
	}

123
	return &OptionValue{option.DefaultVal(), false, option}
Matt Bell's avatar
Matt Bell committed
124 125
}

126
// Options returns a copy of the option map
Henry's avatar
Henry committed
127 128
func (r *request) Options() OptMap {
	output := make(OptMap)
129 130 131 132 133 134
	for k, v := range r.options {
		output[k] = v
	}
	return output
}

Jeromy's avatar
Jeromy committed
135 136 137 138 139 140 141 142 143 144
func (r *request) SetRootContext(ctx context.Context) error {
	ctx, err := getContext(ctx, r)
	if err != nil {
		return err
	}

	r.rctx = ctx
	return nil
}

145 146
// SetOption sets the value of the option for given name.
func (r *request) SetOption(name string, val interface{}) {
147 148 149 150 151 152 153
	// find the option with the specified name
	option, found := r.optionDefs[name]
	if !found {
		return
	}

	// try all the possible names, if we already have a value then set over it
154
	for _, n := range option.Names() {
155
		_, found := r.options[n]
156 157 158 159 160 161
		if found {
			r.options[n] = val
			return
		}
	}

162
	r.options[name] = val
163 164
}

Matt Bell's avatar
Matt Bell committed
165
// SetOptions sets the option values, unsetting any values that were previously set
Henry's avatar
Henry committed
166
func (r *request) SetOptions(opts OptMap) error {
Matt Bell's avatar
Matt Bell committed
167 168 169 170
	r.options = opts
	return r.ConvertOptions()
}

171
// Arguments returns the arguments slice
172
func (r *request) Arguments() []string {
173 174 175 176 177 178 179 180 181 182
	if r.haveVarArgsFromStdin() {
		err := r.VarArgs(func(s string) error {
			r.arguments = append(r.arguments, s)
			return nil
		})
		if err != nil && err != io.EOF {
			log.Error(err)
		}
	}

Matt Bell's avatar
Matt Bell committed
183
	return r.arguments
184
}
Matt Bell's avatar
Matt Bell committed
185

186
func (r *request) SetArguments(args []string) {
187 188 189
	r.arguments = args
}

190
func (r *request) Files() files.File {
Matt Bell's avatar
Matt Bell committed
191 192 193
	return r.files
}

194
func (r *request) SetFiles(f files.File) {
195 196 197
	r.files = f
}

Jeromy's avatar
Jeromy committed
198 199 200 201
func (r *request) Context() context.Context {
	return r.rctx
}

202 203 204 205 206 207 208 209 210 211 212 213
func (r *request) haveVarArgsFromStdin() bool {
	// we expect varargs if we have a variadic required argument and no arguments to
	// fill it
	if len(r.cmd.Arguments) == 0 {
		return false
	}

	last := r.cmd.Arguments[len(r.cmd.Arguments)-1]
	return last.SupportsStdin && last.Type == ArgString &&
		len(r.arguments) < len(r.cmd.Arguments)
}

Jeromy's avatar
Jeromy committed
214 215 216
func (r *request) VarArgs(f func(string) error) error {
	var i int
	for i = 0; i < len(r.cmd.Arguments); i++ {
217
		if r.cmd.Arguments[i].Variadic || r.cmd.Arguments[i].SupportsStdin {
Jeromy's avatar
Jeromy committed
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
			break
		}
	}

	args := r.arguments[i:]
	if len(args) > 0 {
		for _, arg := range args {
			err := f(arg)
			if err != nil {
				return err
			}
		}

		return nil
	} else {
233 234
		if r.files != nil {
			fi, err := r.files.NextFile()
Jeromy's avatar
Jeromy committed
235 236 237
			if err != nil {
				return err
			}
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252

			if fi.FileName() == "*stdin*" {
				fmt.Fprintln(os.Stderr, "ipfs: Reading from stdin; send Ctrl-d to stop.")
			}

			scan := bufio.NewScanner(fi)
			for scan.Scan() {
				err := f(scan.Text())
				if err != nil {
					return err
				}
			}
			return nil
		} else {
			return fmt.Errorf("expected more arguments from stdin")
Jeromy's avatar
Jeromy committed
253 254 255 256
		}
	}
}

Jeromy's avatar
Jeromy committed
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
func getContext(base context.Context, req Request) (context.Context, error) {
	tout, found, err := req.Option("timeout").String()
	if err != nil {
		return nil, fmt.Errorf("error parsing timeout option: %s", err)
	}

	var ctx context.Context
	if found {
		duration, err := time.ParseDuration(tout)
		if err != nil {
			return nil, fmt.Errorf("error parsing timeout option: %s", err)
		}

		tctx, _ := context.WithTimeout(base, duration)
		ctx = tctx
	} else {
		cctx, _ := context.WithCancel(base)
		ctx = cctx
	}
	return ctx, nil
}

func (r *request) InvocContext() *Context {
280 281 282
	return &r.ctx
}

Jeromy's avatar
Jeromy committed
283
func (r *request) SetInvocContext(ctx Context) {
284 285 286
	r.ctx = ctx
}

287 288 289 290
func (r *request) Command() *Command {
	return r.cmd
}

Matt Bell's avatar
Matt Bell committed
291 292
type converter func(string) (interface{}, error)

293
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
294
	Bool: func(v string) (interface{}, error) {
295 296 297 298 299
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
300
	Int: func(v string) (interface{}, error) {
301 302 303 304 305
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
306
	},
Matt Bell's avatar
Matt Bell committed
307
	Uint: func(v string) (interface{}, error) {
308 309 310 311 312
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
313
	},
Matt Bell's avatar
Matt Bell committed
314
	Float: func(v string) (interface{}, error) {
315 316 317 318
		return strconv.ParseFloat(v, 64)
	},
}

319 320 321 322
func (r *request) Values() map[string]interface{} {
	return r.values
}

323 324 325 326
func (r *request) Stdin() io.Reader {
	return r.stdin
}

327
func (r *request) ConvertOptions() error {
328
	for k, v := range r.options {
329
		opt, ok := r.optionDefs[k]
330
		if !ok {
331
			continue
332 333 334
		}

		kind := reflect.TypeOf(v).Kind()
335
		if kind != opt.Type() {
336
			if kind == String {
337
				convert := converters[opt.Type()]
338 339
				str, ok := v.(string)
				if !ok {
340
					return u.ErrCast()
341 342
				}
				val, err := convert(str)
343
				if err != nil {
344 345 346 347 348
					value := fmt.Sprintf("value '%v'", v)
					if len(str) == 0 {
						value = "empty value"
					}
					return fmt.Errorf("Could not convert %s to type '%s' (for option '-%s')",
349
						value, opt.Type().String(), k)
350
				}
351
				r.options[k] = val
352 353 354

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
355
					k, opt.Type().String(), kind.String())
356 357
			}
		} else {
358
			r.options[k] = v
359 360
		}

361
		for _, name := range opt.Names() {
362 363 364 365 366 367 368 369 370 371
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}
		}
	}

	return nil
}

372
// NewEmptyRequest initializes an empty request
373
func NewEmptyRequest() (Request, error) {
Matt Bell's avatar
Matt Bell committed
374
	return NewRequest(nil, nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
375 376
}

377
// NewRequest returns a request initialized with given arguments
378
// An non-nil error will be returned if the provided option values are invalid
Henry's avatar
Henry committed
379
func NewRequest(path []string, opts OptMap, args []string, file files.File, cmd *Command, optDefs map[string]Option) (Request, error) {
380
	if opts == nil {
Henry's avatar
Henry committed
381
		opts = make(OptMap)
382
	}
383 384 385
	if optDefs == nil {
		optDefs = make(map[string]Option)
	}
386

Jeromy's avatar
Jeromy committed
387
	ctx := Context{}
388
	values := make(map[string]interface{})
Jeromy's avatar
Jeromy committed
389 390 391 392 393 394 395 396 397 398 399
	req := &request{
		path:       path,
		options:    opts,
		arguments:  args,
		files:      file,
		cmd:        cmd,
		ctx:        ctx,
		optionDefs: optDefs,
		values:     values,
		stdin:      os.Stdin,
	}
400 401 402 403
	err := req.ConvertOptions()
	if err != nil {
		return nil, err
	}
404

405
	return req, nil
Matt Bell's avatar
Matt Bell committed
406
}