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

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

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

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

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

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

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

// 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 {
39
			return nil, errors.New("nil LoadConfig function")
40 41 42 43 44 45 46
		}
		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
47
// context. It may construct it with the provided function.
48 49 50 51
func (c *Context) GetNode() (*core.IpfsNode, error) {
	var err error
	if c.node == nil {
		if c.ConstructNode == nil {
52
			return nil, errors.New("nil ConstructNode function")
53 54 55 56 57 58 59 60 61 62
		}
		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
63 64
}

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

84
	ConvertOptions() error
85 86 87
}

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

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

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

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

121
	// MAYBE_TODO: use default value instead of nil
122
	return &OptionValue{nil, false, option}
Matt Bell's avatar
Matt Bell committed
123 124
}

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

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

	r.rctx = ctx
	return nil
}

144 145
// SetOption sets the value of the option for given name.
func (r *request) SetOption(name string, val interface{}) {
146 147 148 149 150 151 152
	// 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
153
	for _, n := range option.Names() {
154
		_, found := r.options[n]
155 156 157 158 159 160
		if found {
			r.options[n] = val
			return
		}
	}

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

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

170
// Arguments returns the arguments slice
171
func (r *request) Arguments() []string {
Matt Bell's avatar
Matt Bell committed
172
	return r.arguments
173
}
Matt Bell's avatar
Matt Bell committed
174

175
func (r *request) SetArguments(args []string) {
176 177 178
	r.arguments = args
}

179
func (r *request) Files() files.File {
Matt Bell's avatar
Matt Bell committed
180 181 182
	return r.files
}

183
func (r *request) SetFiles(f files.File) {
184 185 186
	r.files = f
}

Jeromy's avatar
Jeromy committed
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
func (r *request) Context() context.Context {
	return r.rctx
}

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 {
214 215 216
	return &r.ctx
}

Jeromy's avatar
Jeromy committed
217
func (r *request) SetInvocContext(ctx Context) {
218 219 220
	r.ctx = ctx
}

221 222 223 224
func (r *request) Command() *Command {
	return r.cmd
}

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

227
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
228
	Bool: func(v string) (interface{}, error) {
229 230 231 232 233
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
234
	Int: func(v string) (interface{}, error) {
235 236 237 238 239
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
240
	},
Matt Bell's avatar
Matt Bell committed
241
	Uint: func(v string) (interface{}, error) {
242 243 244 245 246
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
247
	},
Matt Bell's avatar
Matt Bell committed
248
	Float: func(v string) (interface{}, error) {
249 250 251 252
		return strconv.ParseFloat(v, 64)
	},
}

253 254 255 256
func (r *request) Values() map[string]interface{} {
	return r.values
}

257 258 259 260
func (r *request) Stdin() io.Reader {
	return r.stdin
}

261
func (r *request) ConvertOptions() error {
262
	for k, v := range r.options {
263
		opt, ok := r.optionDefs[k]
264
		if !ok {
265
			continue
266 267 268
		}

		kind := reflect.TypeOf(v).Kind()
269
		if kind != opt.Type() {
270
			if kind == String {
271
				convert := converters[opt.Type()]
272 273
				str, ok := v.(string)
				if !ok {
274
					return u.ErrCast()
275 276
				}
				val, err := convert(str)
277
				if err != nil {
278 279 280 281 282
					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')",
283
						value, opt.Type().String(), k)
284
				}
285
				r.options[k] = val
286 287 288

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
289
					k, opt.Type().String(), kind.String())
290 291
			}
		} else {
292
			r.options[k] = v
293 294
		}

295
		for _, name := range opt.Names() {
296 297 298 299 300 301 302 303 304 305
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}
		}
	}

	return nil
}

306
// NewEmptyRequest initializes an empty request
307
func NewEmptyRequest() (Request, error) {
Matt Bell's avatar
Matt Bell committed
308
	return NewRequest(nil, nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
309 310
}

311
// NewRequest returns a request initialized with given arguments
312
// An non-nil error will be returned if the provided option values are invalid
Henry's avatar
Henry committed
313
func NewRequest(path []string, opts OptMap, args []string, file files.File, cmd *Command, optDefs map[string]Option) (Request, error) {
314
	if opts == nil {
Henry's avatar
Henry committed
315
		opts = make(OptMap)
316
	}
317 318 319
	if optDefs == nil {
		optDefs = make(map[string]Option)
	}
320

Jeromy's avatar
Jeromy committed
321
	ctx := Context{}
322
	values := make(map[string]interface{})
Jeromy's avatar
Jeromy committed
323 324 325 326 327 328 329 330 331 332 333
	req := &request{
		path:       path,
		options:    opts,
		arguments:  args,
		files:      file,
		cmd:        cmd,
		ctx:        ctx,
		optionDefs: optDefs,
		values:     values,
		stdin:      os.Stdin,
	}
334 335 336 337
	err := req.ConvertOptions()
	if err != nil {
		return nil, err
	}
338

339
	return req, nil
Matt Bell's avatar
Matt Bell committed
340
}