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
	"github.com/ipfs/go-ipfs/commands/files"
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/repo/config"
15
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/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
	return &OptionValue{option.DefaultVal(), false, option}
Matt Bell's avatar
Matt Bell committed
122 123
}

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

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

	r.rctx = ctx
	return nil
}

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

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

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

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

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

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

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

Jeromy's avatar
Jeromy committed
186 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
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 {
213 214 215
	return &r.ctx
}

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

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

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

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

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

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

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

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

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

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

	return nil
}

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

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

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

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