request.go 7.13 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 16
	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
	"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"
17 18
)

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

21
type Context struct {
22 23 24 25
	// this Context is temporary. Will be replaced soon, as we get
	// rid of this variable entirely.
	Context context.Context

26
	Online     bool
27
	ConfigRoot string
28 29 30 31

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

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

// 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 {
42
			return nil, errors.New("nil LoadConfig function")
43 44 45 46 47 48 49 50 51 52 53 54
		}
		c.config, err = c.LoadConfig(c.ConfigRoot)
	}
	return c.config, err
}

// GetNode returns the node of the current Command exection
// context. It may construct it with the providied function.
func (c *Context) GetNode() (*core.IpfsNode, error) {
	var err error
	if c.node == nil {
		if c.ConstructNode == nil {
55
			return nil, errors.New("nil ConstructNode function")
56 57 58 59 60 61 62 63 64 65
		}
		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
66 67
}

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

85
	ConvertOptions() error
86 87 88
}

type request struct {
89
	path       []string
Henry's avatar
Henry committed
90
	options    OptMap
91
	arguments  []string
92
	files      files.File
93 94 95
	cmd        *Command
	ctx        Context
	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
}

134 135
// SetOption sets the value of the option for given name.
func (r *request) SetOption(name string, val interface{}) {
136 137 138 139 140 141 142
	// 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
143
	for _, n := range option.Names() {
144
		_, found := r.options[n]
145 146 147 148 149 150
		if found {
			r.options[n] = val
			return
		}
	}

151
	r.options[name] = val
152 153
}

Matt Bell's avatar
Matt Bell committed
154
// SetOptions sets the option values, unsetting any values that were previously set
Henry's avatar
Henry committed
155
func (r *request) SetOptions(opts OptMap) error {
Matt Bell's avatar
Matt Bell committed
156 157 158 159
	r.options = opts
	return r.ConvertOptions()
}

160
// Arguments returns the arguments slice
161
func (r *request) Arguments() []string {
Matt Bell's avatar
Matt Bell committed
162
	return r.arguments
163
}
Matt Bell's avatar
Matt Bell committed
164

165
func (r *request) SetArguments(args []string) {
166 167 168
	r.arguments = args
}

169
func (r *request) Files() files.File {
Matt Bell's avatar
Matt Bell committed
170 171 172
	return r.files
}

173
func (r *request) SetFiles(f files.File) {
174 175 176
	r.files = f
}

177 178 179 180
func (r *request) Context() *Context {
	return &r.ctx
}

181 182 183 184
func (r *request) SetContext(ctx Context) {
	r.ctx = ctx
}

185 186 187 188
func (r *request) Command() *Command {
	return r.cmd
}

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

191
var converters = map[reflect.Kind]converter{
Matt Bell's avatar
Matt Bell committed
192
	Bool: func(v string) (interface{}, error) {
193 194 195 196 197
		if v == "" {
			return true, nil
		}
		return strconv.ParseBool(v)
	},
Matt Bell's avatar
Matt Bell committed
198
	Int: func(v string) (interface{}, error) {
199 200 201 202 203
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
204
	},
Matt Bell's avatar
Matt Bell committed
205
	Uint: func(v string) (interface{}, error) {
206 207 208 209 210
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
211
	},
Matt Bell's avatar
Matt Bell committed
212
	Float: func(v string) (interface{}, error) {
213 214 215 216
		return strconv.ParseFloat(v, 64)
	},
}

217 218 219 220
func (r *request) Values() map[string]interface{} {
	return r.values
}

221 222 223 224
func (r *request) Stdin() io.Reader {
	return r.stdin
}

225
func (r *request) ConvertOptions() error {
226
	for k, v := range r.options {
227
		opt, ok := r.optionDefs[k]
228
		if !ok {
229
			continue
230 231 232
		}

		kind := reflect.TypeOf(v).Kind()
233
		if kind != opt.Type() {
234
			if kind == String {
235
				convert := converters[opt.Type()]
236 237
				str, ok := v.(string)
				if !ok {
238
					return u.ErrCast()
239 240
				}
				val, err := convert(str)
241
				if err != nil {
242 243 244 245 246
					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')",
247
						value, opt.Type().String(), k)
248
				}
249
				r.options[k] = val
250 251 252

			} else {
				return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
253
					k, opt.Type().String(), kind.String())
254 255
			}
		} else {
256
			r.options[k] = v
257 258
		}

259
		for _, name := range opt.Names() {
260 261 262 263 264 265 266 267 268 269
			if _, ok := r.options[name]; name != k && ok {
				return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
					k, name)
			}
		}
	}

	return nil
}

270
// NewEmptyRequest initializes an empty request
271
func NewEmptyRequest() (Request, error) {
Matt Bell's avatar
Matt Bell committed
272
	return NewRequest(nil, nil, nil, nil, nil, nil)
Matt Bell's avatar
Matt Bell committed
273 274
}

275
// NewRequest returns a request initialized with given arguments
276
// An non-nil error will be returned if the provided option values are invalid
Henry's avatar
Henry committed
277
func NewRequest(path []string, opts OptMap, args []string, file files.File, cmd *Command, optDefs map[string]Option) (Request, error) {
278
	if path == nil {
Matt Bell's avatar
Matt Bell committed
279
		path = make([]string, 0)
Matt Bell's avatar
Matt Bell committed
280
	}
281
	if opts == nil {
Henry's avatar
Henry committed
282
		opts = make(OptMap)
283 284
	}
	if args == nil {
285
		args = make([]string, 0)
286
	}
287 288 289
	if optDefs == nil {
		optDefs = make(map[string]Option)
	}
290

291
	ctx := Context{Context: context.TODO()}
292
	values := make(map[string]interface{})
293
	req := &request{path, opts, args, file, cmd, ctx, optDefs, values, os.Stdin}
294 295 296 297
	err := req.ConvertOptions()
	if err != nil {
		return nil, err
	}
298

299
	return req, nil
Matt Bell's avatar
Matt Bell committed
300
}
Jeromy's avatar
Jeromy committed
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322

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
}