<commit_before>/*
RpcClient for Go RPC Servers
Copyright (C) ITsysCOM GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package rpcclient

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log/syslog"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
	"reflect"
	"strings"
	"sync"
	"syscall"
	"time"
)

const (
	JSON_RPC       = "json"
	JSON_HTTP      = "http_jsonrpc"
	GOB_RPC        = "gob"
	INTERNAL_RPC   = "*internal"
	POOL_FIRST     = "first"
	POOL_RANDOM    = "random"
	POOL_NEXT      = "next"
	POOL_BROADCAST = "broadcast"
)

var (
	ErrReqUnsynchronized       = errors.New("REQ_UNSYNCHRONIZED")
	ErrUnsupporteServiceMethod = errors.New("UNSUPPORTED_SERVICE_METHOD")
	ErrWrongArgsType           = errors.New("WRONG_ARGS_TYPE")
	ErrWrongReplyType          = errors.New("WRONG_REPLY_TYPE")
	ErrDisconnected            = errors.New("DISCONNECTED")
	ErrReplyTimeout            = errors.New("REPLY_TIMEOUT")
	ErrFailedReconnect         = errors.New("FAILED_RECONNECT")
	ErrInternallyDisconnected  = errors.New("INTERNALLY_DISCONNECTED")
	ErrUnsupportedCodec        = errors.New("UNSUPPORTED_CODEC")
	ErrSessionNotFound         = errors.New("SESSION_NOT_FOUND")
	logger                     *syslog.Writer
)

func init() {
	logger, _ = syslog.New(syslog.LOG_INFO, "RPCClient") // If we need to report anything to syslog
}

// successive Fibonacci numbers.
func Fib() func() time.Duration {
	a, b := 0, 1
	return func() time.Duration {
		a, b = b, a+b
		return time.Duration(a*10) * time.Millisecond
	}
}

func NewRpcClient(transport, addr, key_path, cert_path string, connectAttempts, reconnects int,
	connTimeout, replyTimeout time.Duration, codec string,
	internalConn RpcClientConnection, lazyConnect bool) (rpcClient *RpcClient, err error) {
	if codec != INTERNAL_RPC && codec != JSON_RPC && codec != JSON_HTTP && codec != GOB_RPC {
		return nil, ErrUnsupportedCodec
	}
	if codec == INTERNAL_RPC && reflect.ValueOf(internalConn).IsNil() {
		return nil, ErrInternallyDisconnected
	}
	rpcClient = &RpcClient{transport: transport, tls: (key_path != "" && cert_path != ""),
		address: addr, key_path: key_path,
		cert_path: cert_path, reconnects: reconnects,
		connTimeout: connTimeout, replyTimeout: replyTimeout,
		codec: codec, connection: internalConn}
	if lazyConnect {
		return
	}
	delay := Fib()
	for i := 0; i < connectAttempts; i++ {
		err = rpcClient.connect()
		if err == nil { //Connected so no need to reiterate
			break
		}
		time.Sleep(delay())
	}
	return rpcClient, err
}

type RpcClient struct {
	transport    string
	tls          bool
	address      string
	key_path     string
	cert_path    string
	reconnects   int
	connTimeout  time.Duration
	replyTimeout time.Duration
	codec        string // JSON_RPC or GOB_RPC
	connection   RpcClientConnection
	connMux      sync.RWMutex // protects connection
}

func (self *RpcClient) connect() (err error) {
	self.connMux.Lock()
	defer self.connMux.Unlock()
	if self.codec == INTERNAL_RPC {
		if self.connection == nil {
			return ErrDisconnected
		}
		return
	} else if self.codec == JSON_HTTP {
		self.connection = &HttpJsonRpcClient{httpClient: new(http.Client), url: self.address}
		return
	}
	var netconn io.ReadWriteCloser
	if self.tls {
		cert, err := tls.LoadX509KeyPair(self.cert_path, self.key_path)
		if err != nil {
			logger.Crit(fmt.Sprintf("Error: %s when load client keys", err))
			return err
		}
		if len(cert.Certificate) != 2 {
			logger.Crit(fmt.Sprintf("%s should have 2 concatenated certificates: client + CA", self.cert_path))
			return err
		}
		ca, err := x509.ParseCertificate(cert.Certificate[1])
		if err != nil {
			logger.Crit(err.Error())
			return err
		}
		certPool := x509.NewCertPool()
		certPool.AddCert(ca)
		config := tls.Config{
			Certificates: []tls.Certificate{cert},
			RootCAs:      certPool,
		}
		netconn, err = tls.Dial(self.transport, self.address, &config)
		if err != nil {
			logger.Crit(fmt.Sprintf("Error: %s when dialing", err.Error()))
			return err
		}
	} else {
		// RPC compliant connections here, manually create connection to timeout
		netconn, err = net.DialTimeout(self.transport, self.address, self.connTimeout)
		if err != nil {
			self.connection = nil // So we don't wrap nil into the interface
			return err
		}
	}

	if self.codec == JSON_RPC {
		self.connection = jsonrpc.NewClient(netconn)
	} else {
		self.connection = rpc.NewClient(netconn)
	}
	return
}

func (self *RpcClient) isConnected() bool {
	self.connMux.RLock()
	defer self.connMux.RUnlock()
	return self.connection != nil
}

func (self *RpcClient) disconnect() (err error) {
	switch self.codec {
	case INTERNAL_RPC, JSON_HTTP:
	default:
		self.connMux.Lock()
		if self.connection != nil {
			self.connection.(*rpc.Client).Close()
			self.connection = nil
		}
		self.connMux.Unlock()
	}
	return nil
}

func (self *RpcClient) reconnect() (err error) {
	self.disconnect()            // make sure we have cleared the connection so it can be garbage collected
	if self.codec == JSON_HTTP { // http client has automatic reconnects in place
		return self.connect()
	}
	i := 0
	delay := Fib()
	for {
		i++
		if self.reconnects != -1 && i > self.reconnects { // Maximum reconnects reached, -1 for infinite reconnects
			break
		}
		if err = self.connect(); err == nil { // No error on connect, succcess
			return nil
		}
		time.Sleep(delay()) // Cound not reconnect, retry
	}
	return ErrFailedReconnect
}

func (self *RpcClient) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
	if args == nil {
		return fmt.Errorf("nil rpc in argument method: %s in: %v out: %v", serviceMethod, args, reply)
	}
	rpl := reflect.New(reflect.TypeOf(reflect.ValueOf(reply).Elem().Interface())).Interface() // clone to avoid concurrency
	errChan := make(chan error, 1)
	go func(serviceMethod string, args interface{}, reply interface{}) {
		self.connMux.RLock()
		if self.connection == nil {
			errChan <- ErrDisconnected
		} else {
			if argsClnIface, clnable := args.(RPCCloner); clnable { // try cloning to avoid concurrency
				if argsCloned, err := argsClnIface.RPCClone(); err != nil {
					errChan <- err
					return
				} else {
					args = argsCloned
				}
			}
			errChan <- self.connection.Call(serviceMethod, args, reply)
		}
		self.connMux.RUnlock()
	}(serviceMethod, args, rpl)
	select {
	case err = <-errChan:
	case <-time.After(self.replyTimeout):
		err = ErrReplyTimeout
	}
	if isNetworkError(err) && err != ErrReplyTimeout &&
		err.Error() != ErrSessionNotFound.Error() &&
		self.reconnects != 0 { // ReplyTimeout should not reconnect since it creates loop
		if errReconnect := self.reconnect(); errReconnect != nil {
			return err
		}
		self.connMux.RLock()
		defer self.connMux.RUnlock()
		return self.connection.Call(serviceMethod, args, reply)
	}
	reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(rpl).Elem()) // no errors, copy the reply from clone
	return
}

// Connection used in RpcClient, as interface so we can combine the rpc.RpcClient with http one or websocket
type RpcClientConnection interface {
	Call(string, interface{}, interface{}) error
}

// RPCCloner is an interface for objects to clone parts of themselves which are affected by concurrency at the time of RPC call
type RPCCloner interface {
	RPCClone() (interface{}, error)
}

// Response received for
type JsonRpcResponse struct {
	Id     uint64
	Result *json.RawMessage
	Error  interface{}
}

type HttpJsonRpcClient struct {
	httpClient *http.Client
	id         uint64
	url        string
}

func (self *HttpJsonRpcClient) Call(serviceMethod string, args interface{}, reply interface{}) error {
	self.id += 1
	id := self.id
	data, err := json.Marshal(map[string]interface{}{
		"method": serviceMethod,
		"id":     self.id,
		"params": [1]interface{}{args},
	})
	if err != nil {
		return err
	}
	resp, err := self.httpClient.Post(self.url, "application/json", ioutil.NopCloser(strings.NewReader(string(data)))) // Closer so we automatically have close after response
	if err != nil {
		return err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var jsonRsp JsonRpcResponse
	err = json.Unmarshal(body, &jsonRsp)
	if err != nil {
		return err
	}
	if jsonRsp.Id != id {
		return ErrReqUnsynchronized
	}
	if jsonRsp.Error != nil || jsonRsp.Result == nil {
		x, ok := jsonRsp.Error.(string)
		if !ok {
			return fmt.Errorf("invalid error %v", jsonRsp.Error)
		}
		if x == "" {
			x = "unspecified error"
		}
		return errors.New(x)
	}
	return json.Unmarshal(*jsonRsp.Result, reply)
}

type RpcClientPool struct {
	transmissionType string
	connections      []RpcClientConnection
	counter          int
	replyTimeout     time.Duration
}

func NewRpcClientPool(transmissionType string, replyTimeout time.Duration) *RpcClientPool {
	return &RpcClientPool{transmissionType: transmissionType, replyTimeout: replyTimeout}
}

func (pool *RpcClientPool) AddClient(rcc RpcClientConnection) {
	if rcc != nil && !reflect.ValueOf(rcc).IsNil() {
		pool.connections = append(pool.connections, rcc)
	}
}

func (pool *RpcClientPool) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
	switch pool.transmissionType {
	case POOL_BROADCAST:
		replyChan := make(chan *rpcReplyError, len(pool.connections))
		for _, rc := range pool.connections {
			go func(conn RpcClientConnection) {
				// make a new pointer of the same type
				rpl := reflect.New(reflect.TypeOf(reflect.ValueOf(reply).Elem().Interface()))
				err := conn.Call(serviceMethod, args, rpl.Interface())
				if !isNetworkError(err) {
					replyChan <- &rpcReplyError{reply: rpl.Interface(), err: err}
				}
			}(rc)
		}
		//get first response with timeout
		var re *rpcReplyError
		select {
		case re = <-replyChan:
		case <-time.After(pool.replyTimeout):
			return ErrReplyTimeout
		}
		// put received value in the orig reply
		reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(re.reply).Elem())
		return re.err
	case POOL_FIRST:
		for _, rc := range pool.connections {
			err = rc.Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	case POOL_NEXT:
		ln := len(pool.connections)
		rrIndexes := roundIndex(int(math.Mod(float64(pool.counter), float64(ln))), ln)
		pool.counter++
		for _, index := range rrIndexes {
			err = pool.connections[index].Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	case POOL_RANDOM:
		rand.Seed(time.Now().UnixNano())
		randomIndex := rand.Perm(len(pool.connections))
		for _, index := range randomIndex {
			err = pool.connections[index].Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	}
	return
}

type rpcReplyError struct {
	reply interface{}
	err   error
}

// generates round robin indexes for a slice of length max
// starting from index start
func roundIndex(start, max int) []int {
	if start < 0 {
		start = 0
	}
	result := make([]int, max)
	for i := 0; i < max; i++ {
		if start+i < max {
			result[i] = start + i
		} else {
			result[i] = int(math.Abs(float64(max - (start + i))))
		}
	}
	return result
}

func isNetworkError(err error) bool {
	if err == nil {
		return false
	}
	if operr, ok := err.(*net.OpError); ok && strings.HasSuffix(operr.Err.Error(), syscall.ECONNRESET.Error()) { // connection reset
		return true
	}
	return err == rpc.ErrShutdown ||
		err == ErrReqUnsynchronized ||
		err == ErrDisconnected ||
		err == ErrReplyTimeout ||
		err.Error() == ErrSessionNotFound.Error() ||
		strings.HasPrefix(err.Error(), "rpc: can't find service")
}
<commit_msg>Add tls support for http<commit_after>/*
RpcClient for Go RPC Servers
Copyright (C) ITsysCOM GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package rpcclient

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log/syslog"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
	"reflect"
	"strings"
	"sync"
	"syscall"
	"time"
)

const (
	JSON_RPC       = "json"
	JSON_HTTP      = "http_jsonrpc"
	GOB_RPC        = "gob"
	INTERNAL_RPC   = "*internal"
	POOL_FIRST     = "first"
	POOL_RANDOM    = "random"
	POOL_NEXT      = "next"
	POOL_BROADCAST = "broadcast"
)

var (
	ErrReqUnsynchronized       = errors.New("REQ_UNSYNCHRONIZED")
	ErrUnsupporteServiceMethod = errors.New("UNSUPPORTED_SERVICE_METHOD")
	ErrWrongArgsType           = errors.New("WRONG_ARGS_TYPE")
	ErrWrongReplyType          = errors.New("WRONG_REPLY_TYPE")
	ErrDisconnected            = errors.New("DISCONNECTED")
	ErrReplyTimeout            = errors.New("REPLY_TIMEOUT")
	ErrFailedReconnect         = errors.New("FAILED_RECONNECT")
	ErrInternallyDisconnected  = errors.New("INTERNALLY_DISCONNECTED")
	ErrUnsupportedCodec        = errors.New("UNSUPPORTED_CODEC")
	ErrSessionNotFound         = errors.New("SESSION_NOT_FOUND")
	logger                     *syslog.Writer
)

func init() {
	logger, _ = syslog.New(syslog.LOG_INFO, "RPCClient") // If we need to report anything to syslog
}

// successive Fibonacci numbers.
func Fib() func() time.Duration {
	a, b := 0, 1
	return func() time.Duration {
		a, b = b, a+b
		return time.Duration(a*10) * time.Millisecond
	}
}

func NewRpcClient(transport, addr, key_path, cert_path string, connectAttempts, reconnects int,
	connTimeout, replyTimeout time.Duration, codec string,
	internalConn RpcClientConnection, lazyConnect bool) (rpcClient *RpcClient, err error) {
	if codec != INTERNAL_RPC && codec != JSON_RPC && codec != JSON_HTTP && codec != GOB_RPC {
		return nil, ErrUnsupportedCodec
	}
	if codec == INTERNAL_RPC && reflect.ValueOf(internalConn).IsNil() {
		return nil, ErrInternallyDisconnected
	}
	rpcClient = &RpcClient{transport: transport, tls: (key_path != "" && cert_path != ""),
		address: addr, key_path: key_path,
		cert_path: cert_path, reconnects: reconnects,
		connTimeout: connTimeout, replyTimeout: replyTimeout,
		codec: codec, connection: internalConn}
	if lazyConnect {
		return
	}
	delay := Fib()
	for i := 0; i < connectAttempts; i++ {
		err = rpcClient.connect()
		if err == nil { //Connected so no need to reiterate
			break
		}
		time.Sleep(delay())
	}
	return rpcClient, err
}

type RpcClient struct {
	transport    string
	tls          bool
	address      string
	key_path     string
	cert_path    string
	reconnects   int
	connTimeout  time.Duration
	replyTimeout time.Duration
	codec        string // JSON_RPC or GOB_RPC
	connection   RpcClientConnection
	connMux      sync.RWMutex // protects connection
}

func loadTLSConfig(serverCrt, serverKey string) (config tls.Config, err error) {
	cert, err := tls.LoadX509KeyPair(serverCrt, serverKey)
	if err != nil {
		logger.Crit(fmt.Sprintf("Error: %s when load client keys", err))
		return
	}
	if len(cert.Certificate) != 2 {
		logger.Crit(fmt.Sprintf("%s should have 2 concatenated certificates: client + CA", serverCrt))
		return
	}
	ca, err := x509.ParseCertificate(cert.Certificate[1])
	if err != nil {
		logger.Crit(err.Error())
		return
	}
	certPool := x509.NewCertPool()
	certPool.AddCert(ca)
	config = tls.Config{
		Certificates: []tls.Certificate{cert},
		RootCAs:      certPool,
	}
	return
}

func (self *RpcClient) connect() (err error) {
	self.connMux.Lock()
	defer self.connMux.Unlock()
	if self.codec == INTERNAL_RPC {
		if self.connection == nil {
			return ErrDisconnected
		}
		return
	} else if self.codec == JSON_HTTP {
		if self.tls {
			config, err := loadTLSConfig(self.cert_path, self.key_path)
			if err != nil {
				return err
			}
			transport := &http.Transport{TLSClientConfig: &config}
			client := &http.Client{Transport: transport}
			self.connection = &HttpJsonRpcClient{httpClient: client, url: self.address}
		} else {
			self.connection = &HttpJsonRpcClient{httpClient: new(http.Client), url: self.address}
		}
		return
	}
	var netconn io.ReadWriteCloser
	if self.tls {
		config, err := loadTLSConfig(self.cert_path, self.key_path)
		if err != nil {
			return err
		}
		netconn, err = tls.Dial(self.transport, self.address, &config)
		if err != nil {
			logger.Crit(fmt.Sprintf("Error: %s when dialing", err.Error()))
			return err
		}
	} else {
		// RPC compliant connections here, manually create connection to timeout
		netconn, err = net.DialTimeout(self.transport, self.address, self.connTimeout)
		if err != nil {
			self.connection = nil // So we don't wrap nil into the interface
			return err
		}
	}

	if self.codec == JSON_RPC {
		self.connection = jsonrpc.NewClient(netconn)
	} else {
		self.connection = rpc.NewClient(netconn)
	}
	return
}

func (self *RpcClient) isConnected() bool {
	self.connMux.RLock()
	defer self.connMux.RUnlock()
	return self.connection != nil
}

func (self *RpcClient) disconnect() (err error) {
	switch self.codec {
	case INTERNAL_RPC, JSON_HTTP:
	default:
		self.connMux.Lock()
		if self.connection != nil {
			self.connection.(*rpc.Client).Close()
			self.connection = nil
		}
		self.connMux.Unlock()
	}
	return nil
}

func (self *RpcClient) reconnect() (err error) {
	self.disconnect()            // make sure we have cleared the connection so it can be garbage collected
	if self.codec == JSON_HTTP { // http client has automatic reconnects in place
		return self.connect()
	}
	i := 0
	delay := Fib()
	for {
		i++
		if self.reconnects != -1 && i > self.reconnects { // Maximum reconnects reached, -1 for infinite reconnects
			break
		}
		if err = self.connect(); err == nil { // No error on connect, succcess
			return nil
		}
		time.Sleep(delay()) // Cound not reconnect, retry
	}
	return ErrFailedReconnect
}

func (self *RpcClient) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
	if args == nil {
		return fmt.Errorf("nil rpc in argument method: %s in: %v out: %v", serviceMethod, args, reply)
	}
	rpl := reflect.New(reflect.TypeOf(reflect.ValueOf(reply).Elem().Interface())).Interface() // clone to avoid concurrency
	errChan := make(chan error, 1)
	go func(serviceMethod string, args interface{}, reply interface{}) {
		self.connMux.RLock()
		if self.connection == nil {
			errChan <- ErrDisconnected
		} else {
			if argsClnIface, clnable := args.(RPCCloner); clnable { // try cloning to avoid concurrency
				if argsCloned, err := argsClnIface.RPCClone(); err != nil {
					errChan <- err
					return
				} else {
					args = argsCloned
				}
			}
			errChan <- self.connection.Call(serviceMethod, args, reply)
		}
		self.connMux.RUnlock()
	}(serviceMethod, args, rpl)
	select {
	case err = <-errChan:
	case <-time.After(self.replyTimeout):
		err = ErrReplyTimeout
	}
	if isNetworkError(err) && err != ErrReplyTimeout &&
		err.Error() != ErrSessionNotFound.Error() &&
		self.reconnects != 0 { // ReplyTimeout should not reconnect since it creates loop
		if errReconnect := self.reconnect(); errReconnect != nil {
			return err
		}
		self.connMux.RLock()
		defer self.connMux.RUnlock()
		return self.connection.Call(serviceMethod, args, reply)
	}
	reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(rpl).Elem()) // no errors, copy the reply from clone
	return
}

// Connection used in RpcClient, as interface so we can combine the rpc.RpcClient with http one or websocket
type RpcClientConnection interface {
	Call(string, interface{}, interface{}) error
}

// RPCCloner is an interface for objects to clone parts of themselves which are affected by concurrency at the time of RPC call
type RPCCloner interface {
	RPCClone() (interface{}, error)
}

// Response received for
type JsonRpcResponse struct {
	Id     uint64
	Result *json.RawMessage
	Error  interface{}
}

type HttpJsonRpcClient struct {
	httpClient *http.Client
	id         uint64
	url        string
}

func (self *HttpJsonRpcClient) Call(serviceMethod string, args interface{}, reply interface{}) error {
	self.id += 1
	id := self.id
	data, err := json.Marshal(map[string]interface{}{
		"method": serviceMethod,
		"id":     self.id,
		"params": [1]interface{}{args},
	})
	if err != nil {
		return err
	}
	resp, err := self.httpClient.Post(self.url, "application/json", ioutil.NopCloser(strings.NewReader(string(data)))) // Closer so we automatically have close after response
	if err != nil {
		return err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var jsonRsp JsonRpcResponse
	err = json.Unmarshal(body, &jsonRsp)
	if err != nil {
		return err
	}
	if jsonRsp.Id != id {
		return ErrReqUnsynchronized
	}
	if jsonRsp.Error != nil || jsonRsp.Result == nil {
		x, ok := jsonRsp.Error.(string)
		if !ok {
			return fmt.Errorf("invalid error %v", jsonRsp.Error)
		}
		if x == "" {
			x = "unspecified error"
		}
		return errors.New(x)
	}
	return json.Unmarshal(*jsonRsp.Result, reply)
}

type RpcClientPool struct {
	transmissionType string
	connections      []RpcClientConnection
	counter          int
	replyTimeout     time.Duration
}

func NewRpcClientPool(transmissionType string, replyTimeout time.Duration) *RpcClientPool {
	return &RpcClientPool{transmissionType: transmissionType, replyTimeout: replyTimeout}
}

func (pool *RpcClientPool) AddClient(rcc RpcClientConnection) {
	if rcc != nil && !reflect.ValueOf(rcc).IsNil() {
		pool.connections = append(pool.connections, rcc)
	}
}

func (pool *RpcClientPool) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
	switch pool.transmissionType {
	case POOL_BROADCAST:
		replyChan := make(chan *rpcReplyError, len(pool.connections))
		for _, rc := range pool.connections {
			go func(conn RpcClientConnection) {
				// make a new pointer of the same type
				rpl := reflect.New(reflect.TypeOf(reflect.ValueOf(reply).Elem().Interface()))
				err := conn.Call(serviceMethod, args, rpl.Interface())
				if !isNetworkError(err) {
					replyChan <- &rpcReplyError{reply: rpl.Interface(), err: err}
				}
			}(rc)
		}
		//get first response with timeout
		var re *rpcReplyError
		select {
		case re = <-replyChan:
		case <-time.After(pool.replyTimeout):
			return ErrReplyTimeout
		}
		// put received value in the orig reply
		reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(re.reply).Elem())
		return re.err
	case POOL_FIRST:
		for _, rc := range pool.connections {
			err = rc.Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	case POOL_NEXT:
		ln := len(pool.connections)
		rrIndexes := roundIndex(int(math.Mod(float64(pool.counter), float64(ln))), ln)
		pool.counter++
		for _, index := range rrIndexes {
			err = pool.connections[index].Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	case POOL_RANDOM:
		rand.Seed(time.Now().UnixNano())
		randomIndex := rand.Perm(len(pool.connections))
		for _, index := range randomIndex {
			err = pool.connections[index].Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	}
	return
}

type rpcReplyError struct {
	reply interface{}
	err   error
}

// generates round robin indexes for a slice of length max
// starting from index start
func roundIndex(start, max int) []int {
	if start < 0 {
		start = 0
	}
	result := make([]int, max)
	for i := 0; i < max; i++ {
		if start+i < max {
			result[i] = start + i
		} else {
			result[i] = int(math.Abs(float64(max - (start + i))))
		}
	}
	return result
}

func isNetworkError(err error) bool {
	if err == nil {
		return false
	}
	if operr, ok := err.(*net.OpError); ok && strings.HasSuffix(operr.Err.Error(), syscall.ECONNRESET.Error()) { // connection reset
		return true
	}
	return err == rpc.ErrShutdown ||
		err == ErrReqUnsynchronized ||
		err == ErrDisconnected ||
		err == ErrReplyTimeout ||
		err.Error() == ErrSessionNotFound.Error() ||
		strings.HasPrefix(err.Error(), "rpc: can't find service")
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"os"
	"strings"
)

type SecretManager struct {
	secretMap map[string]string
}

func NewSecretManager(secretFile string) (*SecretManager, error) {
	sm := &SecretManager{}
	sm.secretMap = make(map[string]string)

	f, err := os.Open(secretFile)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" || strings.HasPrefix(line, "#") {
			// ignore a empty line or a comment line
			continue
		}
		words := strings.SplitN(line, " ", 2)

		name := strings.TrimSpace(words[0])
		secret := strings.TrimSpace(words[1])
		sm.secretMap[name] = secret
	}
	return sm, nil
}

func (sm *SecretManager) hasAuth(hostid, secret string) bool {
	return secret != "" && sm.secretMap[hostid] == secret
}
<commit_msg>remove a unused module<commit_after><|endoftext|>


<commit_before>/*
 * Copyright 2015 Xuyuan Pang
 * Author: Pang Xuyuan <xuyuanp # gmail dot com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package hador

import (
	"container/list"
	"fmt"
	"net/http"
	"strings"
	"sync"

	"github.com/Xuyuanp/hador/swagger"
)

// Hador struct
type Hador struct {
	Router
	*FilterChain
	Logger Logger
	root   *node

	ctxPool  sync.Pool
	respPool sync.Pool

	Document *swagger.Document
}

// New creates new Hador instance
func New() *Hador {
	h := &Hador{
		Logger: defaultLogger,
		Document: &swagger.Document{
			Swagger:     "2.0.0",
			Definitions: swagger.GlobalDefinitions,
			Tags:        []swagger.Tag{},
			Responses:   swagger.Responses{},
			Parameters:  map[string]swagger.Parameter{},
			Consumes:    []string{},
			Produces:    []string{},
		},
	}
	h.root = &node{}
	h.Router = RouterFunc(h.root.AddRoute)
	h.FilterChain = NewFilterChain(h)

	h.ctxPool.New = func() interface{} {
		ctx := newContext(h.Logger)
		ctx.params = make(Params, h.root.findMaxParams())
		return ctx
	}
	h.respPool.New = func() interface{} {
		return NewResponseWriter(nil)
	}

	return h
}

// Default creates Hador instance with default filters(LogFilter, RecoveryFilter)
func Default() *Hador {
	h := New()
	h.Before(NewLogFilter(h.Logger))
	h.Before(NewRecoveryFilter(h.Logger))
	return h
}

// Run starts serving HTTP request
func (h *Hador) Run(addr string) error {
	h.Logger.Info("Listening on %s", addr)
	return http.ListenAndServe(addr, h)
}

// RunTLS starts serving HTTPS request.
func (h *Hador) RunTLS(addr, sertFile, keyFile string) error {
	h.Logger.Info("Listening on %s", addr)
	return http.ListenAndServeTLS(addr, sertFile, keyFile, h)
}

func (h *Hador) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	resp := h.respPool.Get().(*responseWriter)
	resp.reset(w)
	defer h.respPool.Put(resp)

	ctx := h.ctxPool.Get().(*Context)
	ctx.reset(resp, req)
	defer h.ctxPool.Put(ctx)

	h.FilterChain.Serve(ctx)
}

// Serve implements Handler interface
func (h *Hador) Serve(ctx *Context) {
	method := Method(ctx.Request.Method)
	path := ctx.Request.URL.Path
	if len(path) > 1 && path[len(path)-1] == '/' {
		path = path[:len(path)-1]
	}
	params, leaf, err := h.root.match(method, path, ctx.Params())
	if err != nil {
		status := http.StatusNotFound
		if e, ok := err.(HTTPError); ok {
			status = int(e)
		} else {
			h.Logger.Error("unexpected error: %s", err)
		}
		ctx.OnError(status)
		return
	}
	ctx.params = params
	leaf.Serve(ctx)
}

// AddFilters reuses FilterChain's AddFilters method and returns self
func (h *Hador) AddFilters(filters ...Filter) *Hador {
	h.FilterChain.AddFilters(filters...)
	return h
}

func (h *Hador) travel() []*Leaf {
	llist := list.New()
	h.root.travel(llist)

	leaves := make([]*Leaf, llist.Len())
	i := 0
	for e := llist.Front(); e != nil; e = e.Next() {
		leaves[i] = e.Value.(*Leaf)
		i++
	}
	return leaves
}

func (h *Hador) travelPaths() swagger.Paths {
	spaths := make(swagger.Paths)
	leaves := h.travel()
	for _, leaf := range leaves {
		if leaf.DocIgnored || leaf.method == "ANY" {
			continue
		}

		spath, ok := spaths[leaf.Path()]
		if !ok {
			spath = make(swagger.Path)
			spaths[leaf.Path()] = spath
		}

		spath[strings.ToLower(leaf.Method().String())] = *leaf.SwaggerOperation()
	}
	return spaths
}

// SwaggerHandler returns swagger json api handler
func (h *Hador) SwaggerHandler() Handler {
	return HandlerFunc(func(ctx *Context) {
		h.Document.Paths = h.travelPaths()
		ctx.RenderJSON(h.Document)
	})
}

// Swagger setups swagger config, returns json API path Leaf
func (h *Hador) Swagger(config SwaggerConfig) *Leaf {
	// handle API path
	leaf := h.Get(config.APIPath, h.SwaggerHandler()).
		DocIgnore(!config.SelfDocEnabled)

	// serve swagger-ui file
	if config.UIFilePath != "" {
		s := NewStatic(http.Dir(config.UIFilePath))
		s.Prefix = config.UIPrefix
		h.Before(s)
	}

	return leaf
}

// SwaggerDocument returns swagger.Document of this Hador.
func (h *Hador) SwaggerDocument() *swagger.Document {
	return h.Document
}

func (h *Hador) showGraph() {
	leaves := h.travel()
	for _, l := range leaves {
		fmt.Println(l.Path())
	}
}

func (h *Hador) _showGraph() {
	h.root._travel("")
}
<commit_msg>Avoid defer<commit_after>/*
 * Copyright 2015 Xuyuan Pang
 * Author: Pang Xuyuan <xuyuanp # gmail dot com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package hador

import (
	"container/list"
	"fmt"
	"net/http"
	"strings"
	"sync"

	"github.com/Xuyuanp/hador/swagger"
)

// Hador struct
type Hador struct {
	Router
	*FilterChain
	Logger Logger
	root   *node

	ctxPool  sync.Pool
	respPool sync.Pool

	Document *swagger.Document
}

// New creates new Hador instance
func New() *Hador {
	h := &Hador{
		Logger: defaultLogger,
		Document: &swagger.Document{
			Swagger:     "2.0.0",
			Definitions: swagger.GlobalDefinitions,
			Tags:        []swagger.Tag{},
			Responses:   swagger.Responses{},
			Parameters:  map[string]swagger.Parameter{},
			Consumes:    []string{},
			Produces:    []string{},
		},
	}
	h.root = &node{}
	h.Router = RouterFunc(h.root.AddRoute)
	h.FilterChain = NewFilterChain(h)

	h.ctxPool.New = func() interface{} {
		ctx := newContext(h.Logger)
		ctx.params = make(Params, h.root.findMaxParams())
		return ctx
	}
	h.respPool.New = func() interface{} {
		return NewResponseWriter(nil)
	}

	return h
}

// Default creates Hador instance with default filters(LogFilter, RecoveryFilter)
func Default() *Hador {
	h := New()
	h.Before(NewLogFilter(h.Logger))
	h.Before(NewRecoveryFilter(h.Logger))
	return h
}

// Run starts serving HTTP request
func (h *Hador) Run(addr string) error {
	h.Logger.Info("Listening on %s", addr)
	return http.ListenAndServe(addr, h)
}

// RunTLS starts serving HTTPS request.
func (h *Hador) RunTLS(addr, sertFile, keyFile string) error {
	h.Logger.Info("Listening on %s", addr)
	return http.ListenAndServeTLS(addr, sertFile, keyFile, h)
}

func (h *Hador) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	resp := h.respPool.Get().(*responseWriter)
	resp.reset(w)

	ctx := h.ctxPool.Get().(*Context)
	ctx.reset(resp, req)

	h.FilterChain.Serve(ctx)

	h.ctxPool.Put(ctx)
	h.respPool.Put(resp)
}

// Serve implements Handler interface
func (h *Hador) Serve(ctx *Context) {
	method := Method(ctx.Request.Method)
	path := ctx.Request.URL.Path
	if len(path) > 1 && path[len(path)-1] == '/' {
		path = path[:len(path)-1]
	}
	params, leaf, err := h.root.match(method, path, ctx.Params())
	if err != nil {
		status := http.StatusNotFound
		if e, ok := err.(HTTPError); ok {
			status = int(e)
		} else {
			h.Logger.Error("unexpected error: %s", err)
		}
		ctx.OnError(status)
		return
	}
	ctx.params = params
	leaf.Serve(ctx)
}

// AddFilters reuses FilterChain's AddFilters method and returns self
func (h *Hador) AddFilters(filters ...Filter) *Hador {
	h.FilterChain.AddFilters(filters...)
	return h
}

func (h *Hador) travel() []*Leaf {
	llist := list.New()
	h.root.travel(llist)

	leaves := make([]*Leaf, llist.Len())
	i := 0
	for e := llist.Front(); e != nil; e = e.Next() {
		leaves[i] = e.Value.(*Leaf)
		i++
	}
	return leaves
}

func (h *Hador) travelPaths() swagger.Paths {
	spaths := make(swagger.Paths)
	leaves := h.travel()
	for _, leaf := range leaves {
		if leaf.DocIgnored || leaf.method == "ANY" {
			continue
		}

		spath, ok := spaths[leaf.Path()]
		if !ok {
			spath = make(swagger.Path)
			spaths[leaf.Path()] = spath
		}

		spath[strings.ToLower(leaf.Method().String())] = *leaf.SwaggerOperation()
	}
	return spaths
}

// SwaggerHandler returns swagger json api handler
func (h *Hador) SwaggerHandler() Handler {
	return HandlerFunc(func(ctx *Context) {
		h.Document.Paths = h.travelPaths()
		ctx.RenderJSON(h.Document)
	})
}

// Swagger setups swagger config, returns json API path Leaf
func (h *Hador) Swagger(config SwaggerConfig) *Leaf {
	// handle API path
	leaf := h.Get(config.APIPath, h.SwaggerHandler()).
		DocIgnore(!config.SelfDocEnabled)

	// serve swagger-ui file
	if config.UIFilePath != "" {
		s := NewStatic(http.Dir(config.UIFilePath))
		s.Prefix = config.UIPrefix
		h.Before(s)
	}

	return leaf
}

// SwaggerDocument returns swagger.Document of this Hador.
func (h *Hador) SwaggerDocument() *swagger.Document {
	return h.Document
}

func (h *Hador) showGraph() {
	leaves := h.travel()
	for _, l := range leaves {
		fmt.Println(l.Path())
	}
}

func (h *Hador) _showGraph() {
	h.root._travel("")
}
<|endoftext|>


<commit_before>package task

import (
	"fmt"
	"github.com/c4s4/neon/build"
	"reflect"
	"github.com/c4s4/changelog/lib"
)

func init() {
	build.AddTask(build.TaskDesc{
		Name: "changelog",
		Func: changelog,
		Args: reflect.TypeOf(changelogArgs{}),
		Help: `Load information from semantic changelog file.

Arguments:

- changelog: the name of the changelog file (look for changelog in current
  directory if omitted).

Note:

- The release version is stored in property _changelog_version.
- The release date is stored in property _changelog_date.
- The release summary is stored in property _changelog_summary.

Examples:

    # get changelog information in file 'test.yml':
    - changelog: "test.yml"`,
	})
}

type changelogArgs struct {
	Changelog string `neon:"file,optional"`
}

func changelog(context *build.Context, args interface{}) error {
	params := args.(changelogArgs)
	var file string
	if params.Changelog == "" {
		var err error
		file, err = lib.FindChangelog()
		if err != nil {
			return err
		}
	} else {
		file = params.Changelog
	}
	source, err := lib.ReadChangelog(file)
	if err != nil {
		return err
	}
	changelog, err := lib.ParseChangelog(source)
	if err != nil {
		return err
	}
	if len(changelog) < 1 {
		return fmt.Errorf("the changelog is empty")
	}
	release := changelog[0]
	context.SetProperty("_changelog_version", release.Version)
	context.SetProperty("_changelog_date", release.Date)
	context.SetProperty("_changelog_summary", release.Summary)
	return nil
}
<commit_msg>Fmted<commit_after>package task

import (
	"fmt"
	"github.com/c4s4/changelog/lib"
	"github.com/c4s4/neon/build"
	"reflect"
)

func init() {
	build.AddTask(build.TaskDesc{
		Name: "changelog",
		Func: changelog,
		Args: reflect.TypeOf(changelogArgs{}),
		Help: `Load information from semantic changelog file.

Arguments:

- changelog: the name of the changelog file (look for changelog in current
  directory if omitted).

Note:

- The release version is stored in property _changelog_version.
- The release date is stored in property _changelog_date.
- The release summary is stored in property _changelog_summary.

Examples:

    # get changelog information in file 'test.yml':
    - changelog: "test.yml"`,
	})
}

type changelogArgs struct {
	Changelog string `neon:"file,optional"`
}

func changelog(context *build.Context, args interface{}) error {
	params := args.(changelogArgs)
	var file string
	if params.Changelog == "" {
		var err error
		file, err = lib.FindChangelog()
		if err != nil {
			return err
		}
	} else {
		file = params.Changelog
	}
	source, err := lib.ReadChangelog(file)
	if err != nil {
		return err
	}
	changelog, err := lib.ParseChangelog(source)
	if err != nil {
		return err
	}
	if len(changelog) < 1 {
		return fmt.Errorf("the changelog is empty")
	}
	release := changelog[0]
	context.SetProperty("_changelog_version", release.Version)
	context.SetProperty("_changelog_date", release.Date)
	context.SetProperty("_changelog_summary", release.Summary)
	return nil
}
<|endoftext|>


<commit_before>package gmock

import (
	"reflect"
)

type GMock struct {
	target   reflect.Value
	original reflect.Value
}

func CreateMockWithTarget(targetPtr interface{}) *GMock {
	targetValue := reflect.ValueOf(targetPtr)
	if targetValue.Kind() != reflect.Ptr {
		panic("Target needs to be a pointer")
	}

	mock := &GMock{}
	mock.target = targetValue.Elem()
	mock.original = reflect.New(mock.target.Type()).Elem()
	mock.original.Set(mock.target)
	return mock
}

func MockTargetWithValue(targetVar interface{}, mockValue interface{}) *GMock {
	mock := CreateMockWithTarget(targetVar)
	mock.Replace(mockValue)
	return mock
}

func (self *GMock) Replace(mockValue interface{}) {
	replacement := reflect.ValueOf(mockValue)

	if !replacement.IsValid() {
		replacement = reflect.Zero(self.target.Type())
	}

	self.target.Set(replacement)
}

func (self *GMock) Restore() {
	self.target.Set(self.original)
}

func (self *GMock) GetTarget() reflect.Value {
	return self.target
}

func (self *GMock) GetOriginal() reflect.Value {
	return self.original
}
<commit_msg>RS - Minor change to panic message when failing to create a mock.<commit_after>package gmock

import (
	"reflect"
)

type GMock struct {
	target   reflect.Value
	original reflect.Value
}

func CreateMockWithTarget(targetPtr interface{}) *GMock {
	targetValue := reflect.ValueOf(targetPtr)
	if targetValue.Kind() != reflect.Ptr {
		panic("GMock: Target needs to be a pointer")
	}

	mock := &GMock{}
	mock.target = targetValue.Elem()
	mock.original = reflect.New(mock.target.Type()).Elem()
	mock.original.Set(mock.target)
	return mock
}

func MockTargetWithValue(targetVar interface{}, mockValue interface{}) *GMock {
	mock := CreateMockWithTarget(targetVar)
	mock.Replace(mockValue)
	return mock
}

func (self *GMock) Replace(mockValue interface{}) {
	replacement := reflect.ValueOf(mockValue)

	if !replacement.IsValid() {
		replacement = reflect.Zero(self.target.Type())
	}

	self.target.Set(replacement)
}

func (self *GMock) Restore() {
	self.target.Set(self.original)
}

func (self *GMock) GetTarget() reflect.Value {
	return self.target
}

func (self *GMock) GetOriginal() reflect.Value {
	return self.original
}
<|endoftext|>


<commit_before>package semver

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"
)

const (
	numbers    string = "0123456789"
	letters           = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
	alphanum          = letters + numbers
	dot               = "."
	hyphen            = "-"
	plus              = "+"
	delimiters        = dot + hyphen + plus
	allchars          = alphanum + delimiters
)

type VersionGetter interface {
	Major() uint64
	Minor() uint64
	Patch() uint64
	Prerelease() string
	PrerelaseIdentifiers() []string
	Metadata() string
	MetadataIdentifiers() []string
}

type VersionSetter interface {
	SetPrerelease(...string) error
	SetMetadata(...string) error
	SetMajor(uint64)
	SetMinor(uint64)
	SetPatch(uint64)
}

type VersionGetterSetter interface {
	VersionGetter
	VersionSetter
}

type Version struct {
	major      uint64
	minor      uint64
	patch      uint64
	prerelease *prereleases
	metadata   []string
}

type prereleases struct {
	values  []string
	numbers map[int]uint64
}

func Build(major, minor, patch uint64, extra ...[]string) *Version {
	if len(extra) == 1 {
		ver := &Version{major, minor, patch, nil, nil}
		ver.SetPrerelease(extra[0]...)
		return ver
	}
	if len(extra) > 1 {
		ver := &Version{major, minor, patch, nil, extra[1]}
		ver.SetPrerelease(extra[0]...)
		return ver
	}
	return &Version{major, minor, patch, nil, nil}
}

func New(version string) (*Version, error) {
	var versions []string
	var prereleases []string
	var metadatas []string

	result := new(Version)

	if strings.Contains(version, plus) {
		metadata := strings.Split(version, plus)
		metadatas = strings.Split(metadata[1], dot)

		if err := result.SetMetadata(metadatas...); err != nil {
			return nil, err
		}

		version = metadata[0]
	}

	if strings.Contains(version, hyphen) {
		prerelease := strings.Split(version, hyphen)
		prereleases = strings.Split(prerelease[1], dot)

		if err := result.SetPrerelease(prereleases...); err != nil {
			return nil, err
		}

		version = prerelease[0]
	}

	versions = strings.Split(version, dot)
	if len(versions) != 3 {
		return nil, errors.New("major.minor.patch pattern not found")
	}

	var versionNumbers [3]uint64
	for i, partial := range versions {

		if num, err := strconv.ParseUint(partial, 10, 0); err != nil {
			return nil, errors.New(fmt.Sprint("expected unsigned integer: ", partial))
		} else {
			if hasLeadingZero(partial) {
				return nil, errors.New(fmt.Sprint("leading zeroes in version number: ", partial))
			}
			versionNumbers[i] = num
		}
	}

	result.major = versionNumbers[0]
	result.minor = versionNumbers[1]
	result.patch = versionNumbers[2]

	return result, nil
}

func (v Version) String() string {
	var buffer bytes.Buffer
	w := bufio.NewWriter(&buffer)

	fmt.Fprintf(w, "%d.%d.%d", v.major, v.minor, v.patch)

	if v.prerelease != nil {
		fmt.Fprintf(w, "%v%v", hyphen, v.Prerelease())
	}

	if v.metadata != nil {
		fmt.Fprintf(w, "%v%v", plus, v.Metadata())
	}

	w.Flush()
	return buffer.String()
}

func (v Version) Major() uint64 {
	return v.major
}

func (v *Version) SetMajor(major uint64) {
	v.major = major
}

func (v Version) Minor() uint64 {
	return v.minor
}

func (v *Version) SetMinor(minor uint64) {
	v.minor = minor
}

func (v Version) Patch() uint64 {
	return v.patch
}

func (v *Version) SetPatch(patch uint64) {
	v.patch = patch
}

func (v Version) Prerelease() string {
	return strings.Join(v.prerelease.values, dot)
}

func (v *Version) SetPrerelease(identifiers ...string) error {
	var result []string
	numbers := make(map[int]uint64)

	for i, ident := range identifiers {
		if len(ident) < 1 {
			return errors.New("identifier is empty")
		}

		if num, err := strconv.ParseUint(ident, 10, 0); err == nil {
			if hasLeadingZero(ident) {
				return errors.New(fmt.Sprint("leading zeroes in numerical identifier: ", ident))
			}
			numbers[i] = num
			result = append(result, ident)
		} else {
			if !containsOnly(ident, alphanum) {
				return errors.New(fmt.Sprint("not alphanumerical: ", ident))
			}
			result = append(result, ident)
		}
	}
	pre := &prereleases{result, numbers}
	v.prerelease = pre
	return nil
}

func (v Version) Metadata() string {
	return strings.Join(v.metadata, dot)
}

func (v *Version) SetMetadata(identifiers ...string) error {
	var result []string

	for _, ident := range identifiers {
		if len(ident) < 1 {
			return errors.New("identifier is empty")
		}

		if !containsOnly(ident, alphanum) {
			return errors.New(fmt.Sprint("not alphanumerical: ", ident))
		}
		result = append(result, ident)
	}

	v.metadata = result
	return nil
}

func (v Version) Satifies(other string) bool {
	return true
}
<commit_msg>Removed alphanumeric strings.<commit_after>package semver

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"
)

const (
	dot        = "."
	hyphen     = "-"
	plus       = "+"
	delimiters = dot + hyphen + plus
)

type VersionGetter interface {
	Major() uint64
	Minor() uint64
	Patch() uint64
	Prerelease() string
	PrerelaseIdentifiers() []string
	Metadata() string
	MetadataIdentifiers() []string
}

type VersionSetter interface {
	SetPrerelease(...string) error
	SetMetadata(...string) error
	SetMajor(uint64)
	SetMinor(uint64)
	SetPatch(uint64)
}

type VersionGetterSetter interface {
	VersionGetter
	VersionSetter
}

type Version struct {
	major      uint64
	minor      uint64
	patch      uint64
	prerelease *prereleases
	metadata   []string
}

type prereleases struct {
	values  []string
	numbers map[int]uint64
}

func Build(major, minor, patch uint64, extra ...[]string) *Version {
	if len(extra) == 1 {
		ver := &Version{major, minor, patch, nil, nil}
		ver.SetPrerelease(extra[0]...)
		return ver
	}
	if len(extra) > 1 {
		ver := &Version{major, minor, patch, nil, extra[1]}
		ver.SetPrerelease(extra[0]...)
		return ver
	}
	return &Version{major, minor, patch, nil, nil}
}

func New(version string) (*Version, error) {
	var versions []string
	var prereleases []string
	var metadatas []string

	result := new(Version)

	if strings.Contains(version, plus) {
		metadata := strings.Split(version, plus)
		metadatas = strings.Split(metadata[1], dot)

		if err := result.SetMetadata(metadatas...); err != nil {
			return nil, err
		}

		version = metadata[0]
	}

	if strings.Contains(version, hyphen) {
		prerelease := strings.Split(version, hyphen)
		prereleases = strings.Split(prerelease[1], dot)

		if err := result.SetPrerelease(prereleases...); err != nil {
			return nil, err
		}

		version = prerelease[0]
	}

	versions = strings.Split(version, dot)
	if len(versions) != 3 {
		return nil, errors.New("major.minor.patch pattern not found")
	}

	var versionNumbers [3]uint64
	for i, partial := range versions {

		if num, err := strconv.ParseUint(partial, 10, 0); err != nil {
			return nil, errors.New(fmt.Sprint("expected unsigned integer: ", partial))
		} else {
			if hasLeadingZero(partial) {
				return nil, errors.New(fmt.Sprint("leading zeroes in version number: ", partial))
			}
			versionNumbers[i] = num
		}
	}

	result.major = versionNumbers[0]
	result.minor = versionNumbers[1]
	result.patch = versionNumbers[2]

	return result, nil
}

func (v Version) String() string {
	var buffer bytes.Buffer
	w := bufio.NewWriter(&buffer)

	fmt.Fprintf(w, "%d.%d.%d", v.major, v.minor, v.patch)

	if v.prerelease != nil {
		fmt.Fprintf(w, "%v%v", hyphen, v.Prerelease())
	}

	if v.metadata != nil {
		fmt.Fprintf(w, "%v%v", plus, v.Metadata())
	}

	w.Flush()
	return buffer.String()
}

func (v Version) Major() uint64 {
	return v.major
}

func (v *Version) SetMajor(major uint64) {
	v.major = major
}

func (v Version) Minor() uint64 {
	return v.minor
}

func (v *Version) SetMinor(minor uint64) {
	v.minor = minor
}

func (v Version) Patch() uint64 {
	return v.patch
}

func (v *Version) SetPatch(patch uint64) {
	v.patch = patch
}

func (v Version) Prerelease() string {
	return strings.Join(v.prerelease.values, dot)
}

func (v *Version) SetPrerelease(identifiers ...string) error {
	var result []string
	numbers := make(map[int]uint64)

	for i, ident := range identifiers {
		if len(ident) < 1 {
			return errors.New("identifier is empty")
		}

		if num, err := strconv.ParseUint(ident, 10, 0); err == nil {
			if hasLeadingZero(ident) {
				return errors.New(fmt.Sprint("leading zeroes in numerical identifier: ", ident))
			}
			numbers[i] = num
			result = append(result, ident)
		} else {
			if !containsOnly(ident, alphanum) {
				return errors.New(fmt.Sprint("not alphanumerical: ", ident))
			}
			result = append(result, ident)
		}
	}
	pre := &prereleases{result, numbers}
	v.prerelease = pre
	return nil
}

func (v Version) Metadata() string {
	return strings.Join(v.metadata, dot)
}

func (v *Version) SetMetadata(identifiers ...string) error {
	var result []string

	for _, ident := range identifiers {
		if len(ident) < 1 {
			return errors.New("identifier is empty")
		}

		if !containsOnly(ident, alphanum) {
			return errors.New(fmt.Sprint("not alphanumerical: ", ident))
		}
		result = append(result, ident)
	}

	v.metadata = result
	return nil
}

func (v Version) Satifies(other string) bool {
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"image/color"
	"log"
	"os"

	"gonum.org/v1/plot"
	"gonum.org/v1/plot/plotter"
	"gonum.org/v1/plot/vg/draw"
)

func main() {
	xys, err := readData("data.txt")
	if err != nil {
		log.Fatalf("could not read data.txt: %v", err)
	}
	_ = xys

	err = plotData("out.png", xys)
	if err != nil {
		log.Fatalf("could not plot data: %v", err)
	}
}

type xy struct{ x, y float64 }

func readData(path string) (plotter.XYs, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	var xys plotter.XYs
	s := bufio.NewScanner(f)
	for s.Scan() {
		var x, y float64
		_, err := fmt.Sscanf(s.Text(), "%f,%f", &x, &y)
		if err != nil {
			log.Printf("discarding bad data point %q: %v", s.Text(), err)
		}
		xys = append(xys, struct{ X, Y float64 }{x, y})
	}
	if err := s.Err(); err != nil {
		return nil, fmt.Errorf("could not scan: %v", err)
	}
	return xys, nil
}

func plotData(path string, xys plotter.XYs) error {
	f, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("could not create %s: %v", path, err)
	}

	p, err := plot.New()
	if err != nil {
		return fmt.Errorf("could not create plot: %v", err)
	}

	// create scatter with all data points
	s, err := plotter.NewScatter(xys)
	if err != nil {
		return fmt.Errorf("could not create scatter: %v", err)
	}
	s.GlyphStyle.Shape = draw.CrossGlyph{}
	s.Color = color.RGBA{R: 255, A: 255}
	p.Add(s)

	var x, c float64
	x = 1.2
	c = -3

	// create fake linear regression result
	l, err := plotter.NewLine(plotter.XYs{
		{3, 3*x + c}, {20, 20*x + c},
	})
	if err != nil {
		return fmt.Errorf("could not create line: %v", err)
	}
	p.Add(l)

	wt, err := p.WriterTo(256, 256, "png")
	if err != nil {
		return fmt.Errorf("could not create writer: %v", err)
	}
	_, err = wt.WriteTo(f)
	if err != nil {
		return fmt.Errorf("could not write to %s: %v", path, err)
	}

	if err := f.Close(); err != nil {
		return fmt.Errorf("could not close %s: %v", path, err)
	}
	return nil
}
<commit_msg>bad data points should not be added to the resulting set (#68)<commit_after>package main

import (
	"bufio"
	"fmt"
	"image/color"
	"log"
	"os"

	"gonum.org/v1/plot"
	"gonum.org/v1/plot/plotter"
	"gonum.org/v1/plot/vg/draw"
)

func main() {
	xys, err := readData("data.txt")
	if err != nil {
		log.Fatalf("could not read data.txt: %v", err)
	}
	_ = xys

	err = plotData("out.png", xys)
	if err != nil {
		log.Fatalf("could not plot data: %v", err)
	}
}

type xy struct{ x, y float64 }

func readData(path string) (plotter.XYs, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	var xys plotter.XYs
	s := bufio.NewScanner(f)
	for s.Scan() {
		var x, y float64
		_, err := fmt.Sscanf(s.Text(), "%f,%f", &x, &y)
		if err != nil {
			log.Printf("discarding bad data point %q: %v", s.Text(), err)
			continue
		}
		xys = append(xys, struct{ X, Y float64 }{x, y})
	}
	if err := s.Err(); err != nil {
		return nil, fmt.Errorf("could not scan: %v", err)
	}
	return xys, nil
}

func plotData(path string, xys plotter.XYs) error {
	f, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("could not create %s: %v", path, err)
	}

	p, err := plot.New()
	if err != nil {
		return fmt.Errorf("could not create plot: %v", err)
	}

	// create scatter with all data points
	s, err := plotter.NewScatter(xys)
	if err != nil {
		return fmt.Errorf("could not create scatter: %v", err)
	}
	s.GlyphStyle.Shape = draw.CrossGlyph{}
	s.Color = color.RGBA{R: 255, A: 255}
	p.Add(s)

	var x, c float64
	x = 1.2
	c = -3

	// create fake linear regression result
	l, err := plotter.NewLine(plotter.XYs{
		{3, 3*x + c}, {20, 20*x + c},
	})
	if err != nil {
		return fmt.Errorf("could not create line: %v", err)
	}
	p.Add(l)

	wt, err := p.WriterTo(256, 256, "png")
	if err != nil {
		return fmt.Errorf("could not create writer: %v", err)
	}
	_, err = wt.WriteTo(f)
	if err != nil {
		return fmt.Errorf("could not write to %s: %v", path, err)
	}

	if err := f.Close(); err != nil {
		return fmt.Errorf("could not close %s: %v", path, err)
	}
	return nil
}
<|endoftext|>


<commit_before>package gobls

// DefaultBufferSize specifies the initial bytes size each gobls scanner will allocate to be used
// for aggregation of line fragments.
const DefaultBufferSize = 16 * 1024

// Scanner provides an interface for reading newline-delimited lines
// of text. It is similar to `bufio.Scanner`, but wraps
// `bufio.ReadLine` so lines of arbitrary length can be
// scanned. Successive calls to the Scan method will step through the
// lines of a file, skipping the newline whitespace between lines.
//
// Scanning stops unrecoverably at EOF, or at the first I/O
// error. Unlike `bufio.Scanner`, howver, attempting to scan a line
// longer than `bufio.MaxScanTokenSize` will not result in an error,
// but will return the long line.
//
// It is not necessary to check for errors by calling the Err method
// until after scanning stops, when the Scan method returns false.
type Scanner interface {
	Bytes() []byte
	Err() error
	Scan() bool
	Text() string
}
<commit_msg>documentation<commit_after>package gobls

// DefaultBufferSize specifies the initial bytes size each gobls scanner will allocate to be used
// for aggregation of line fragments.
const DefaultBufferSize = 16 * 1024

// Scanner provides an interface for reading newline-delimited lines of text.  It is similar to
// `bufio.Scanner`, but wraps the ReadLine method of `bufio.Reader` so lines of arbitrary length can
// be scanned.  Successive calls to the Scan method will step through the lines of a file, skipping
// the newline whitespace between lines.
//
// Scanning stops unrecoverably at EOF, or at the first I/O error.  Unlike `bufio.Scanner`, however,
// attempting to scan a line longer than `bufio.MaxScanTokenSize` will not result in an error, but
// will return the long line.
//
// It is not necessary to check for errors by calling the Err method until after scanning stops,
// when the Scan method returns false.
//
// Gobls Scanner ought behave exactly like `bufio.Scanner`.  All methods ought to have the exact
// same return values while stepping through the given `io.Reader`.
type Scanner interface {
	Bytes() []byte
	Err() error
	Scan() bool
	Text() string
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
)

func main() {
	var acct = flag.String("acct", "", "Character account for initial import. Ex: Krimic")
	var char = flag.String("char", "", "Character name for DB update or import. Ex: Rynshana")
	var lvl = flag.Int("lvl", 0, "Character level for DB update. Ex: 50")
	var class = flag.String("class", "", "Character class for initial import. Ex: \"Cleric\"")
	var race = flag.String("race", "", "Character race for initial import. Ex: \"Moon Elf\"")
	var file = flag.String("import", "", "Parse file for identify stats, import to DB. Ex: newstats.txt")
	var time = flag.String("time", "", "Parse uptime for boot tracking. Ex: 58:10:26")
	var cmd = flag.String("cmd", "", "Command from tell. Ex: stat")
	var oper = flag.String("oper", "", "Operant from tell, to be operated on by cmd. Ex: \"a longsword\"")
	flag.Parse()
	*acct += *class + *race + *cmd + *oper

	if *char != "" && *lvl != 0 {
		Who(*lvl, *char)
	}
	if *file != "" {
		Identify(*file)
	}
	if *time != "" {
		Uptime(*time)
	}
}
<commit_msg>Changed to if/else, use new who functions<commit_after>package main

import (
	"flag"
)

func main() {
	var acct = flag.String("acct", "", "Character account for initial import. Ex: Krimic")
	var char = flag.String("char", "", "Character name for DB update or import. Ex: Rynshana")
	var lvl = flag.Int("lvl", 0, "Character level for DB update. Ex: 50")
	var class = flag.String("class", "", "Character class for initial import. Ex: \"Cleric\"")
	var race = flag.String("race", "", "Character race for initial import. Ex: \"Moon Elf\"")
	var file = flag.String("import", "", "Parse file for identify stats, import to DB. Ex: newstats.txt")
	var time = flag.String("time", "", "Parse uptime for boot tracking. Ex: 58:10:26")
	var cmd = flag.String("cmd", "", "Command from tell. Ex: stat")
	var oper = flag.String("oper", "", "Operant from tell, to be operated on by cmd. Ex: \"a longsword\"")
	flag.Parse()
	*cmd += *oper

	if *char != "" && *lvl != 0 && *class != "" && *race != "" && *acct != "" {
		WhoChar(*char, *lvl, *class, *race, *acct)
	} else if *char != "" && *lvl != 0 {
		Who(*char, *lvl)
	} else if *file != "" {
		Identify(*file)
	} else if *time != "" {
		Uptime(*time)
	}
}
<|endoftext|>


<commit_before>package gocsv

import (
	"fmt"
	"encoding/csv"
	"errors"
	"strconv"
	"os"
	"golang.org/x/text/transform"
	"golang.org/x/text/encoding/simplifiedchinese"
	"reflect"
	"strings"
)

//Field field info
type Field struct {
	Name  string
	Value string
	Kind  string
}

//Read read for map array
func Read(file string, isUtf8 bool) ([]map[string]interface{}, error) {
	list := make([]map[string]interface{}, 0);
	err := ReadRaw(file, isUtf8, func(fields []Field) error {
		item := make(map[string]interface{})
		for _, f := range fields {
			if len(f.Name) <= 0 {
				continue
			}
			var itemValue interface{}
			var innerr error
			switch f.Kind {
			case "int":
				itemValue, innerr = strconv.ParseInt(f.Value, 10, 64)
				if innerr != nil {
					itemValue = 0
				}
			case "float":
				itemValue, innerr = strconv.ParseFloat(f.Value, 64)
				if innerr != nil {
					itemValue = 0
				}
			default:
				itemValue = f.Value
			}
			item[f.Name] = itemValue
		}
		list = append(list, item)
		return nil
	})
	return list, err
}

//ReadList read for []struct
func ReadList(file string, isUtf8 bool, out interface{}) error {

	if out == nil {
		return errors.New("Cannot remake from <nil>")
	}

	outv := reflect.ValueOf(out)

	outt := outv.Type()
	outk := outt.Kind()

	if outk != reflect.Ptr {
		return errors.New("Cannot reflect into non-pointer")
	}
	slicev := outv.Elem()
	slicet := slicev.Type()
	slicek := slicev.Kind()

	if slicek != reflect.Slice {
		return errors.New("Pointer must point to a slice")
	}

	elmt := slicet.Elem()

	//map field => value
	idxs := make(map[string]int)
	for i := 0; i < elmt.NumField(); i++ {
		name := elmt.Field(i).Tag.Get("csv")
		if len(name) <= 0 {
			name = elmt.Field(i).Name
		}
		idxs[format(name)] = i
	}

	err := ReadRaw(file, isUtf8, func(fields []Field) error {
		elmv := reflect.Indirect(reflect.New(elmt))
		for _, f := range fields {
			if len(f.Name) <= 0 {
				continue
			}
			idx, ok := idxs[format(f.Name)]
			if !ok {
				continue
			}
			switch f.Kind {
			case "int":
				itemValue, innerr := strconv.ParseInt(f.Value, 10, 64)
				if innerr != nil {
					itemValue = 0
				}
				elmv.Field(idx).SetInt(itemValue)
			case "float":
				itemValue, innerr := strconv.ParseFloat(f.Value, 64)
				if innerr != nil {
					itemValue = 0
				}
				elmv.Field(idx).SetFloat(itemValue)
			default:
				itemValue := f.Value
				elmv.Field(idx).SetString(itemValue)
			}
		}
		slicev.Set(reflect.Append(slicev, elmv))
		return nil
	})

	return err
}

//ReadList read for map[interface{}]struct
func ReadMap(file string, isUtf8 bool, keyField string, out interface{}) error {

	if out == nil {
		return errors.New("Cannot remake from <nil>")
	}

	outv := reflect.ValueOf(out)

	outt := outv.Type()
	outk := outt.Kind()

	if outk != reflect.Ptr {
		return errors.New("Cannot reflect into non-pointer")
	}
	mapv := outv.Elem()
	mapt := mapv.Type()
	mapk := mapv.Kind()

	if mapk != reflect.Map {
		return errors.New("Pointer must point to a slice")
	}

	//make map
	if mapv.IsNil() {
		mapv.Set(reflect.MakeMap(mapt))
	}

	elmt := mapt.Elem()

	//map field => value
	idxs := make(map[string]int)
	for i := 0; i < elmt.NumField(); i++ {
		name := elmt.Field(i).Tag.Get("csv")
		if len(name) <= 0 {
			name = elmt.Field(i).Name
		}
		idxs[format(name)] = i
	}

	err := ReadRaw(file, isUtf8, func(fields []Field) error {
		elmv := reflect.Indirect(reflect.New(elmt))
		keyi := 0
		for _, f := range fields {
			if len(f.Name) <= 0 {
				continue
			}
			idx, ok := idxs[format(f.Name)]
			if !ok {
				continue
			}
			if f.Name == keyField {
				keyi = idx
			}
			switch f.Kind {
			case "int":
				itemValue, innerr := strconv.ParseInt(f.Value, 10, 64)
				if innerr != nil {
					itemValue = 0
				}
				elmv.Field(idx).SetInt(itemValue)
			case "float":
				itemValue, innerr := strconv.ParseFloat(f.Value, 64)
				if innerr != nil {
					itemValue = 0
				}
				elmv.Field(idx).SetFloat(itemValue)
			default:
				itemValue := f.Value
				elmv.Field(idx).SetString(itemValue)
			}
		}
		mapv.SetMapIndex(elmv.Field(keyi), elmv)
		return nil
	})

	return err
}

//Read read csv for handle
func ReadRaw(file string, isUtf8 bool, handle func([]Field) error) error {
	//open file
	fi, err := os.Open(file)
	defer fi.Close()
	//get reader
	var reader *csv.Reader
	if isUtf8 {
		reader = csv.NewReader(fi)
	} else {
		//transform gbk to utf8
		r := transform.NewReader(fi, simplifiedchinese.GBK.NewDecoder())
		reader = csv.NewReader(r)
	}

	lines, err := reader.ReadAll()
	if err != nil {
		return err
	}
	lineNum := len(lines)
	if (lineNum < 3) {
		return errors.New(fmt.Sprintf("Csv %v is invalid"))
	}
	names, kinds := lines[1], lines[2]
	fieldNum := len(names)
	//从第三行开始
	for i := 3; i < lineNum; i++ {
		line := lines[i]
		itemFields := make([]Field, fieldNum, fieldNum)
		for j := 0; j < fieldNum; j++ {
			itemField := Field{
				Name: names[j],
				Value: line[j],
				Kind: kinds[j],

			}
			itemFields[j] = itemField
		}
		perr := handle(itemFields)
		//如果返回解析错误，则跳过，直接返回
		if perr != nil {
			return perr
		}
	}
	return nil
}

//format format name
func format(name string) string {
	return fmt.Sprintf("%v%v", strings.ToLower(name[0:1]), name[1:])
}<commit_msg>add catch error<commit_after>package gocsv

import (
	"fmt"
	"encoding/csv"
	"errors"
	"strconv"
	"os"
	"golang.org/x/text/transform"
	"golang.org/x/text/encoding/simplifiedchinese"
	"reflect"
	"strings"
	"log"
)

//Field field info
type Field struct {
	Name  string
	Value string
	Kind  string
}

//Read read for map array
func Read(file string, isUtf8 bool) ([]map[string]interface{}, error) {
	defer catch(file)

	list := make([]map[string]interface{}, 0);
	err := ReadRaw(file, isUtf8, func(fields []Field) error {
		item := make(map[string]interface{})
		for _, f := range fields {
			if len(f.Name) <= 0 {
				continue
			}
			var itemValue interface{}
			var innerr error
			switch f.Kind {
			case "int":
				itemValue, innerr = strconv.ParseInt(f.Value, 10, 64)
				if innerr != nil {
					itemValue = 0
				}
			case "float":
				itemValue, innerr = strconv.ParseFloat(f.Value, 64)
				if innerr != nil {
					itemValue = 0
				}
			default:
				itemValue = f.Value
			}
			item[f.Name] = itemValue
		}
		list = append(list, item)
		return nil
	})
	return list, err
}

//ReadList read for []struct
func ReadList(file string, isUtf8 bool, out interface{}) error {
	defer catch(file)

	if out == nil {
		return errors.New("Cannot remake from <nil>")
	}

	outv := reflect.ValueOf(out)

	outt := outv.Type()
	outk := outt.Kind()

	if outk != reflect.Ptr {
		return errors.New("Cannot reflect into non-pointer")
	}
	slicev := outv.Elem()
	slicet := slicev.Type()
	slicek := slicev.Kind()

	if slicek != reflect.Slice {
		return errors.New("Pointer must point to a slice")
	}

	elmt := slicet.Elem()

	//map field => value
	idxs := make(map[string]int)
	for i := 0; i < elmt.NumField(); i++ {
		name := elmt.Field(i).Tag.Get("csv")
		if len(name) <= 0 {
			name = elmt.Field(i).Name
		}
		idxs[format(name)] = i
	}

	err := ReadRaw(file, isUtf8, func(fields []Field) error {
		elmv := reflect.Indirect(reflect.New(elmt))
		for _, f := range fields {
			if len(f.Name) <= 0 {
				continue
			}
			idx, ok := idxs[format(f.Name)]
			if !ok {
				continue
			}
			switch f.Kind {
			case "int":
				itemValue, innerr := strconv.ParseInt(f.Value, 10, 64)
				if innerr != nil {
					itemValue = 0
				}
				elmv.Field(idx).SetInt(itemValue)
			case "float":
				itemValue, innerr := strconv.ParseFloat(f.Value, 64)
				if innerr != nil {
					itemValue = 0
				}
				elmv.Field(idx).SetFloat(itemValue)
			default:
				itemValue := f.Value
				elmv.Field(idx).SetString(itemValue)
			}
		}
		slicev.Set(reflect.Append(slicev, elmv))
		return nil
	})

	return err
}

//ReadList read for map[interface{}]struct
func ReadMap(file string, isUtf8 bool, keyField string, out interface{}) error {
	defer catch(file)

	if out == nil {
		return errors.New("Cannot remake from <nil>")
	}

	outv := reflect.ValueOf(out)

	outt := outv.Type()
	outk := outt.Kind()

	if outk != reflect.Ptr {
		return errors.New("Cannot reflect into non-pointer")
	}
	mapv := outv.Elem()
	mapt := mapv.Type()
	mapk := mapv.Kind()

	if mapk != reflect.Map {
		return errors.New("Pointer must point to a slice")
	}

	//make map
	if mapv.IsNil() {
		mapv.Set(reflect.MakeMap(mapt))
	}

	elmt := mapt.Elem()

	//map field => value
	idxs := make(map[string]int)
	for i := 0; i < elmt.NumField(); i++ {
		name := elmt.Field(i).Tag.Get("csv")
		if len(name) <= 0 {
			name = elmt.Field(i).Name
		}
		idxs[format(name)] = i
	}

	err := ReadRaw(file, isUtf8, func(fields []Field) error {
		elmv := reflect.Indirect(reflect.New(elmt))
		keyi := 0
		for _, f := range fields {
			if len(f.Name) <= 0 {
				continue
			}
			idx, ok := idxs[format(f.Name)]
			if !ok {
				continue
			}
			if f.Name == keyField {
				keyi = idx
			}
			switch f.Kind {
			case "int":
				itemValue, innerr := strconv.ParseInt(f.Value, 10, 64)
				if innerr != nil {
					itemValue = 0
				}
				elmv.Field(idx).SetInt(itemValue)
			case "float":
				itemValue, innerr := strconv.ParseFloat(f.Value, 64)
				if innerr != nil {
					itemValue = 0
				}
				elmv.Field(idx).SetFloat(itemValue)
			default:
				itemValue := f.Value
				elmv.Field(idx).SetString(itemValue)
			}
		}
		mapv.SetMapIndex(elmv.Field(keyi), elmv)
		return nil
	})

	return err
}

//Read read csv for handle
func ReadRaw(file string, isUtf8 bool, handle func([]Field) error) error {
	//open file
	fi, err := os.Open(file)
	defer fi.Close()
	//get reader
	var reader *csv.Reader
	if isUtf8 {
		reader = csv.NewReader(fi)
	} else {
		//transform gbk to utf8
		r := transform.NewReader(fi, simplifiedchinese.GBK.NewDecoder())
		reader = csv.NewReader(r)
	}

	lines, err := reader.ReadAll()
	if err != nil {
		return err
	}
	lineNum := len(lines)
	if (lineNum < 3) {
		return errors.New(fmt.Sprintf("Csv %v is invalid"))
	}
	names, kinds := lines[1], lines[2]
	fieldNum := len(names)
	//从第三行开始
	for i := 3; i < lineNum; i++ {
		line := lines[i]
		itemFields := make([]Field, fieldNum, fieldNum)
		for j := 0; j < fieldNum; j++ {
			itemField := Field{
				Name: names[j],
				Value: line[j],
				Kind: kinds[j],

			}
			itemFields[j] = itemField
		}
		perr := handle(itemFields)
		//如果返回解析错误，则跳过，直接返回
		if perr != nil {
			return perr
		}
	}
	return nil
}

//format format name
func format(name string) string {
	return fmt.Sprintf("%v%v", strings.ToLower(name[0:1]), name[1:])
}

func catch(file string)  {
	if err := recover(); err != nil{
		log.Panicf("read csv file: %v, error: %v", file, err)
	}
}<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/google/uuid"
	"github.com/musl/libgofr"
	"github.com/nfnt/resize"
	"image"
	"image/png"
	"log"
	"net/http"
	"runtime"
	"strconv"
	"time"
)

const Version = "0.0.2"

var id_chan = make(chan uuid.UUID, 100)

func finish(w http.ResponseWriter, status int, message string) {
	w.WriteHeader(status)
	fmt.Fprintf(w, message)
}

func logDuration(message string, start time.Time) {
	end := time.Now()
	log.Printf("%s %v\n", message, end.Sub(start))
}

type logResponseWriter struct {
	http.ResponseWriter
	Status int
}

func (self *logResponseWriter) WriteHeader(code int) {
	self.Status = code
	self.ResponseWriter.WriteHeader(code)
}

func timedLogWrapper(h http.HandlerFunc) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		id := <-id_chan
		start := time.Now()
		lrw := logResponseWriter{w, http.StatusOK}

		log.Printf("%s %s %s %s", id, r.RemoteAddr, r.Method, r.URL.Path)
		defer logDuration(fmt.Sprintf("%s %v", id, lrw.Status), start)

		h.ServeHTTP(w, r)
	})
}

func route_png(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		finish(w, http.StatusMethodNotAllowed, "Method not allowed.")
		return
	}

	q := r.URL.Query()

	s, err := strconv.Atoi(q.Get("s"))
	if err != nil {
		s = 1
	}

	width, err := strconv.Atoi(q.Get("w"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid width")
		return
	}

	height, err := strconv.Atoi(q.Get("h"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid height")
		return
	}

	iterations, err := strconv.Atoi(q.Get("i"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid i")
		return
	}

	er, err := strconv.ParseFloat(q.Get("e"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid e")
		return
	}

	rmin, err := strconv.ParseFloat(q.Get("rmin"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	imin, err := strconv.ParseFloat(q.Get("imin"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	rmax, err := strconv.ParseFloat(q.Get("rmax"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmax")
		return
	}

	imax, err := strconv.ParseFloat(q.Get("imax"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	c := q.Get("c")
	hex := q.Get("m")

	p := gofr.Parameters{
		ImageWidth:   width * s,
		ImageHeight:  height * s,
		MaxI:         iterations,
		EscapeRadius: er,
		Min:          complex(rmin, imin),
		Max:          complex(rmax, imax),
		ColorFunc:    c,
		MemberColor:  hex,
	}

	// TODO: Check parameters and set reasonable bounds on what we can
	// quickly calculate.

	img := image.NewNRGBA64(image.Rect(0, 0, p.ImageWidth, p.ImageHeight))
	n := runtime.NumCPU()
	contexts := gofr.MakeContexts(img, n, &p)
	gofr.Render(n, contexts, gofr.Mandelbrot)

	scaled_img := resize.Resize(uint(width), uint(height), image.Image(img), resize.Lanczos3)

	w.Header().Set("Content-Type", "image/png")
	png.Encode(w, scaled_img)
}

func main() {
	fs := http.FileServer(http.Dir("static"))
	bind_addr := "0.0.0.0:8000"

	go func() {
		for i := 0; ; i++ {
			id_chan <- uuid.New()
		}
	}()

	http.Handle("/", fs)
	http.Handle("/png", timedLogWrapper(route_png))

	log.Printf("gofrd v%s", Version)
	log.Printf("libgofrd v%s", gofr.Version)
	log.Printf("Listening on: %s\n", bind_addr)

	log.Fatal(http.ListenAndServe(bind_addr, nil))
}
<commit_msg>Improving logging, handlers, add id header<commit_after>package main

import (
	"fmt"
	"github.com/google/uuid"
	"github.com/musl/libgofr"
	"github.com/nfnt/resize"
	"image"
	"image/png"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"time"
)

const Version = "0.0.2"

var id_chan = make(chan uuid.UUID, 100)

type LogResponseWriter struct {
	http.ResponseWriter
	Status int
	Start  time.Time
	End    time.Time
}

func NewLogResponseWriter(w http.ResponseWriter) *LogResponseWriter {
	return &LogResponseWriter{w, 0, time.Now(), time.Now()}
}

func (self *LogResponseWriter) WriteHeader(code int) {
	self.Status = code
	self.ResponseWriter.WriteHeader(code)
}

func (self LogResponseWriter) Log(message string) {
	self.End = time.Now()
	log.Printf("%s %v\n", message, self.End.Sub(self.Start))
}

func wrapHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		lrw := NewLogResponseWriter(w)

		h.ServeHTTP(lrw, r)
		lrw.Log(fmt.Sprintf("%d %s %s %s", lrw.Status, r.Method, r.URL.Path, r.RemoteAddr))
	})
}

func wrapHandlerFunc(h http.HandlerFunc) http.Handler {
	return wrapHandler(http.Handler(h))
}

func finish(w http.ResponseWriter, status int, message string) {
	w.WriteHeader(status)
	io.WriteString(w, message)
}

func route_png(w http.ResponseWriter, r *http.Request) {
	id := <-id_chan

	if r.Method != "GET" {
		finish(w, http.StatusMethodNotAllowed, "Method not allowed.")
		return
	}

	q := r.URL.Query()

	s, err := strconv.Atoi(q.Get("s"))
	if err != nil {
		s = 1
	}

	width, err := strconv.Atoi(q.Get("w"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid width")
		return
	}

	height, err := strconv.Atoi(q.Get("h"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid height")
		return
	}

	iterations, err := strconv.Atoi(q.Get("i"))
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid i")
		return
	}

	er, err := strconv.ParseFloat(q.Get("e"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid e")
		return
	}

	rmin, err := strconv.ParseFloat(q.Get("rmin"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	imin, err := strconv.ParseFloat(q.Get("imin"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	rmax, err := strconv.ParseFloat(q.Get("rmax"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmax")
		return
	}

	imax, err := strconv.ParseFloat(q.Get("imax"), 64)
	if err != nil {
		finish(w, http.StatusUnprocessableEntity, "Invalid rmin")
		return
	}

	c := q.Get("c")
	hex := q.Get("m")

	p := gofr.Parameters{
		ImageWidth:   width * s,
		ImageHeight:  height * s,
		MaxI:         iterations,
		EscapeRadius: er,
		Min:          complex(rmin, imin),
		Max:          complex(rmax, imax),
		ColorFunc:    c,
		MemberColor:  hex,
	}

	// TODO: Check parameters and set reasonable bounds on what we can
	// quickly calculate.
	//
	// Create a pool of goroutines that process render jobs, with a
	// time-out for accepting render jobs. Have UI support for the "try
	// again later" response.

	img := image.NewNRGBA64(image.Rect(0, 0, p.ImageWidth, p.ImageHeight))
	n := runtime.NumCPU()
	contexts := gofr.MakeContexts(img, n, &p)
	gofr.Render(n, contexts, gofr.Mandelbrot)

	scaled_img := resize.Resize(uint(width), uint(height), image.Image(img), resize.Lanczos3)

	w.Header().Set("Content-Type", "image/png")
	w.Header().Set("X-Render-Job-ID", id.String())
	w.WriteHeader(http.StatusOK)
	png.Encode(w, scaled_img)
}

func route_status(w http.ResponseWriter, r *http.Request) {
	finish(w, http.StatusOK, "OK")
}

func main() {
	var value string

	log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.LUTC)
	log.Printf("gofrd v%s", Version)
	log.Printf("libgofrd v%s", gofr.Version)

	static_dir := "./static"
	if value = os.Getenv("GOFR_STATIC_DIR"); value != "" {
		static_dir = value
	}
	static_dir, err := filepath.Abs(static_dir)
	if err != nil {
		panic(err)
	}
	log.Printf("Serving from: %s\n", static_dir)

	bind_addr := "0.0.0.0:8000"
	if value = os.Getenv("GOFR_BIND_ADDR"); value != "" {
		bind_addr = value
	}
	log.Printf("Listening on: %s\n", bind_addr)

	go func() {
		for i := 0; ; i++ {
			id_chan <- uuid.New()
		}
	}()

	http.Handle("/", wrapHandler(http.FileServer(http.Dir(static_dir))))
	http.Handle("/png", wrapHandlerFunc(route_png))
	http.Handle("/status", wrapHandlerFunc(route_status))

	/* Run the thing. */
	log.Fatal(http.ListenAndServe(bind_addr, nil))
}
<|endoftext|>


<commit_before>package agent

import (
	"encoding/json"
	"fmt"
	"net/http"
	"unicode"
	"unicode/utf8"

	"code.google.com/p/go-uuid/uuid"
	log "github.com/Sirupsen/logrus"
	"github.com/mistifyio/kvite"
	"github.com/mistifyio/mistify-agent/client"
	"github.com/mistifyio/mistify-agent/rpc"
)

type (

	// Guest is a "helper struct"
	Guest struct {
		context *Context
		*client.Guest
	}
)

// PersistGuest writes guest data to the data store
func (ctx *Context) PersistGuest(g *client.Guest) error {
	return ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		data, err := json.Marshal(g)
		if err != nil {
			return err
		}
		return b.Put(g.Id, data)
	})
}

// DeleteGuest removes a guest from the data store
func (ctx *Context) DeleteGuest(g *client.Guest) error {
	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.Delete(g.Id)
	})

	if err != nil {
		return err
	}
	ctx.DeleteGuestRunner(g.Id)
	return nil
}

// prefixedActionName creates the appropriate action name for the guest type
func prefixedActionName(gType, actionName string) string {
	if gType != "container" || actionName == "" {
		return actionName
	}
	r, n := utf8.DecodeRuneInString(actionName)
	return "container" + string(unicode.ToUpper(r)) + actionName[n:]
}

func listGuests(r *HTTPRequest) *HTTPErrorMessage {
	var guests []*client.Guest

	err := r.Context.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.ForEach(func(k string, v []byte) error {
			var g client.Guest
			if err := json.Unmarshal(v, &g); err != nil {
				return err
			}
			// Do we want to actually verify this information or trust the pipelines??
			guests = append(guests, &g)
			return nil
		})
	})

	if err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}

	return r.JSON(http.StatusOK, guests)
}

// TODO: A lot of the duplicated code between here and the guest action wrapper
// will go away when we fix our middlewares. The initial setup here can be a
// simple middleware called first before the guest and runner retrieval
// middlewares
// NOTE: The config for create should include stages for startup
func createGuest(r *HTTPRequest) *HTTPErrorMessage {
	g := &client.Guest{}
	if err := json.NewDecoder(r.Request.Body).Decode(g); err != nil {
		return r.NewError(err, http.StatusBadRequest)
	}
	if g.Id != "" {
		if uuid.Parse(g.Id) == nil {
			return r.NewError(fmt.Errorf("id must be uuid"), http.StatusBadRequest)
		}
	} else {
		g.Id = uuid.New()
	}

	// TODO: make sure it's actually unique
	g.State = "create"

	// TODO: general validations, like memory, disks look sane, etc

	if err := r.Context.PersistGuest(g); err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}

	runner := r.Context.NewGuestRunner(g.Id, 100, 5)

	action, err := r.Context.GetAction(prefixedActionName(g.Type, "create"))
	if err != nil {
		return r.NewError(err, http.StatusNotFound)
	}
	response := &rpc.GuestResponse{}
	pipeline := action.GeneratePipeline(nil, response, r.ResponseWriter, nil)
	// Guest requests are special in that they have Args and need
	// the action name, so fold them in to the request
	for _, stage := range pipeline.Stages {
		stage.Request = &rpc.GuestRequest{
			Guest:  g,
			Args:   stage.Args,
			Action: action.Name,
		}
	}
	r.ResponseWriter.Header().Set("X-Guest-Job-ID", pipeline.ID)
	err = runner.Process(pipeline)
	if err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}
	return r.JSON(http.StatusAccepted, g)
}

func withGuest(r *HTTPRequest, fn func(r *HTTPRequest) *HTTPErrorMessage) *HTTPErrorMessage {
	id := r.Parameter("id")
	var g client.Guest
	err := r.Context.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		data, err := b.Get(id)
		if err != nil {
			return err
		}
		if data == nil {
			return ErrNotFound
		}

		return json.Unmarshal(data, &g)
	})

	if err != nil {
		code := http.StatusInternalServerError
		if err == ErrNotFound {
			code = http.StatusNotFound
		}
		return r.NewError(err, code)
	}
	r.Guest = &g
	r.GuestRunner, err = r.Context.GetGuestRunner(g.Id)
	if err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}
	return fn(r)
}

func getGuest(r *HTTPRequest) *HTTPErrorMessage {
	return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		return r.JSON(http.StatusOK, g)
	})
}

func deleteGuest(r *HTTPRequest) *HTTPErrorMessage {
	return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		// TODO: Make sure to use the DoneChan here
		err := r.Context.PersistGuest(g)
		if err != nil {
			return r.NewError(err, http.StatusInternalServerError)
		}
		return r.JSON(http.StatusAccepted, g)
	})
}

func getGuestMetadata(r *HTTPRequest) *HTTPErrorMessage {
	return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		return r.JSON(http.StatusOK, g.Metadata)
	})
}

func setGuestMetadata(r *HTTPRequest) *HTTPErrorMessage {
	return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		var metadata map[string]string
		err := json.NewDecoder(r.Request.Body).Decode(&metadata)
		if err != nil {
			return r.NewError(err, http.StatusBadRequest)
		}

		for key, value := range metadata {
			if value == "" {
				delete(g.Metadata, key)
			} else {
				g.Metadata[key] = value
			}
		}

		err = r.Context.PersistGuest(g)
		if err != nil {
			return r.NewError(err, http.StatusInternalServerError)
		}
		return r.JSON(http.StatusOK, g.Metadata)
	})
}

// TODO: These wrappers are ugly nesting. Try to find a cleaner, more modular
// way to do it

// GuestRunnerWrapper is a middleware that retrieves the runner for a guest
func (c *Chain) GuestRunnerWrapper(fn func(*HTTPRequest) *HTTPErrorMessage) http.HandlerFunc {
	return c.RequestWrapper(func(r *HTTPRequest) *HTTPErrorMessage {
		return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
			g := r.Guest
			runner, err := r.Context.GetGuestRunner(g.Id)
			if err != nil {
				return r.NewError(err, http.StatusInternalServerError)
			}

			r.GuestRunner = runner
			return fn(r)
		})
	})
}

// GuestActionWrapper wraps an HTTP request with a Guest action to avoid duplicated code
func (c *Chain) GuestActionWrapper(actionName string) http.HandlerFunc {
	return c.GuestRunnerWrapper(func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		runner := r.GuestRunner

		actionName := prefixedActionName(g.Type, actionName)
		action, err := r.Context.GetAction(actionName)
		if err != nil {
			return r.NewError(err, http.StatusNotFound)
		}

		response := &rpc.GuestResponse{}
		doneChan := make(chan error)
		pipeline := action.GeneratePipeline(nil, response, r.ResponseWriter, doneChan)
		// Guest requests are special in that they have Args and need
		// the action name, so fold them in to the request
		for _, stage := range pipeline.Stages {
			stage.Request = &rpc.GuestRequest{
				Guest:  g,
				Args:   stage.Args,
				Action: action.Name,
			}
		}
		r.ResponseWriter.Header().Set("X-Guest-Job-ID", pipeline.ID)
		err = runner.Process(pipeline)
		if err != nil {
			return r.NewError(err, http.StatusInternalServerError)
		}
		// Extra processing after the pipeline finishes
		go func() {
			err := <-doneChan
			if err != nil {
				return
			}
			if actionName == prefixedActionName(g.Type, "delete") {
				if err := r.Context.DeleteGuest(g); err != nil {
					log.WithFields(log.Fields{
						"guest": g.Id,
						"error": err,
						"func":  "agent.Context.DeleteGuest",
					}).Error("Delete Error:", err)
				}
				return
			}
			if err := r.Context.PersistGuest(g); err != nil {
				return
			}
		}()
		return r.JSON(http.StatusAccepted, g)
	})
}
<commit_msg>PreStageFunc and PostStageFunc for guest actions<commit_after>package agent

import (
	"encoding/json"
	"fmt"
	"net/http"
	"unicode"
	"unicode/utf8"

	"code.google.com/p/go-uuid/uuid"
	log "github.com/Sirupsen/logrus"
	"github.com/mistifyio/kvite"
	"github.com/mistifyio/mistify-agent/client"
	"github.com/mistifyio/mistify-agent/rpc"
)

type (

	// Guest is a "helper struct"
	Guest struct {
		context *Context
		*client.Guest
	}
)

// PersistGuest writes guest data to the data store
func (ctx *Context) PersistGuest(g *client.Guest) error {
	return ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		data, err := json.Marshal(g)
		if err != nil {
			return err
		}
		return b.Put(g.Id, data)
	})
}

// DeleteGuest removes a guest from the data store
func (ctx *Context) DeleteGuest(g *client.Guest) error {
	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.Delete(g.Id)
	})

	if err != nil {
		return err
	}
	ctx.DeleteGuestRunner(g.Id)
	return nil
}

// prefixedActionName creates the appropriate action name for the guest type
func prefixedActionName(gType, actionName string) string {
	if gType != "container" || actionName == "" {
		return actionName
	}
	r, n := utf8.DecodeRuneInString(actionName)
	return "container" + string(unicode.ToUpper(r)) + actionName[n:]
}

func listGuests(r *HTTPRequest) *HTTPErrorMessage {
	var guests []*client.Guest

	err := r.Context.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.ForEach(func(k string, v []byte) error {
			var g client.Guest
			if err := json.Unmarshal(v, &g); err != nil {
				return err
			}
			// Do we want to actually verify this information or trust the pipelines??
			guests = append(guests, &g)
			return nil
		})
	})

	if err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}

	return r.JSON(http.StatusOK, guests)
}

// TODO: A lot of the duplicated code between here and the guest action wrapper
// will go away when we fix our middlewares. The initial setup here can be a
// simple middleware called first before the guest and runner retrieval
// middlewares
// NOTE: The config for create should include stages for startup
func createGuest(r *HTTPRequest) *HTTPErrorMessage {
	g := &client.Guest{}
	if err := json.NewDecoder(r.Request.Body).Decode(g); err != nil {
		return r.NewError(err, http.StatusBadRequest)
	}
	if g.Id != "" {
		if uuid.Parse(g.Id) == nil {
			return r.NewError(fmt.Errorf("id must be uuid"), http.StatusBadRequest)
		}
	} else {
		g.Id = uuid.New()
	}

	// TODO: make sure it's actually unique
	g.State = "create"

	// TODO: general validations, like memory, disks look sane, etc

	if err := r.Context.PersistGuest(g); err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}

	runner := r.Context.NewGuestRunner(g.Id, 100, 5)

	action, err := r.Context.GetAction(prefixedActionName(g.Type, "create"))
	if err != nil {
		return r.NewError(err, http.StatusNotFound)
	}

	response := &rpc.GuestResponse{}
	request := &rpc.GuestRequest{
		Guest:  g,
		Action: action.Name,
	}
	pipeline := action.GeneratePipeline(request, response, r.ResponseWriter, nil)
	// PreStageFunc to populate copy the stage args into the request
	pipeline.PreStageFunc = func(p *Pipeline, s *Stage) error {
		request.Args = s.Args
		return nil
	}
	// PostStageFunc to update guest in request and persist guest
	pipeline.PostStageFunc = func(p *Pipeline, s *Stage) error {
		request.Guest = response.Guest
		return r.Context.PersistGuest(response.Guest)
	}

	r.ResponseWriter.Header().Set("X-Guest-Job-ID", pipeline.ID)
	err = runner.Process(pipeline)
	if err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}
	return r.JSON(http.StatusAccepted, g)
}

func withGuest(r *HTTPRequest, fn func(r *HTTPRequest) *HTTPErrorMessage) *HTTPErrorMessage {
	id := r.Parameter("id")
	var g client.Guest
	err := r.Context.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		data, err := b.Get(id)
		if err != nil {
			return err
		}
		if data == nil {
			return ErrNotFound
		}

		return json.Unmarshal(data, &g)
	})

	if err != nil {
		code := http.StatusInternalServerError
		if err == ErrNotFound {
			code = http.StatusNotFound
		}
		return r.NewError(err, code)
	}
	r.Guest = &g
	r.GuestRunner, err = r.Context.GetGuestRunner(g.Id)
	if err != nil {
		return r.NewError(err, http.StatusInternalServerError)
	}
	return fn(r)
}

func getGuest(r *HTTPRequest) *HTTPErrorMessage {
	return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		return r.JSON(http.StatusOK, g)
	})
}

func deleteGuest(r *HTTPRequest) *HTTPErrorMessage {
	return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		// TODO: Make sure to use the DoneChan here
		err := r.Context.PersistGuest(g)
		if err != nil {
			return r.NewError(err, http.StatusInternalServerError)
		}
		return r.JSON(http.StatusAccepted, g)
	})
}

func getGuestMetadata(r *HTTPRequest) *HTTPErrorMessage {
	return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		return r.JSON(http.StatusOK, g.Metadata)
	})
}

func setGuestMetadata(r *HTTPRequest) *HTTPErrorMessage {
	return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		var metadata map[string]string
		err := json.NewDecoder(r.Request.Body).Decode(&metadata)
		if err != nil {
			return r.NewError(err, http.StatusBadRequest)
		}

		for key, value := range metadata {
			if value == "" {
				delete(g.Metadata, key)
			} else {
				g.Metadata[key] = value
			}
		}

		err = r.Context.PersistGuest(g)
		if err != nil {
			return r.NewError(err, http.StatusInternalServerError)
		}
		return r.JSON(http.StatusOK, g.Metadata)
	})
}

// TODO: These wrappers are ugly nesting. Try to find a cleaner, more modular
// way to do it

// GuestRunnerWrapper is a middleware that retrieves the runner for a guest
func (c *Chain) GuestRunnerWrapper(fn func(*HTTPRequest) *HTTPErrorMessage) http.HandlerFunc {
	return c.RequestWrapper(func(r *HTTPRequest) *HTTPErrorMessage {
		return withGuest(r, func(r *HTTPRequest) *HTTPErrorMessage {
			g := r.Guest
			runner, err := r.Context.GetGuestRunner(g.Id)
			if err != nil {
				return r.NewError(err, http.StatusInternalServerError)
			}

			r.GuestRunner = runner
			return fn(r)
		})
	})
}

// GuestActionWrapper wraps an HTTP request with a Guest action to avoid duplicated code
func (c *Chain) GuestActionWrapper(actionName string) http.HandlerFunc {
	return c.GuestRunnerWrapper(func(r *HTTPRequest) *HTTPErrorMessage {
		g := r.Guest
		runner := r.GuestRunner

		actionName := prefixedActionName(g.Type, actionName)
		action, err := r.Context.GetAction(actionName)
		if err != nil {
			return r.NewError(err, http.StatusNotFound)
		}

		response := &rpc.GuestResponse{}
		request := &rpc.GuestRequest{
			Guest:  g,
			Action: action.Name,
		}
		doneChan := make(chan error)
		pipeline := action.GeneratePipeline(request, response, r.ResponseWriter, doneChan)
		// PreStageFunc to populate copy the stage args into the request
		pipeline.PreStageFunc = func(p *Pipeline, s *Stage) error {
			request.Args = s.Args
			return nil
		}
		// PostStageFunc to update guest in request and persist guest
		pipeline.PostStageFunc = func(p *Pipeline, s *Stage) error {
			request.Guest = response.Guest
			return r.Context.PersistGuest(response.Guest)
		}

		r.ResponseWriter.Header().Set("X-Guest-Job-ID", pipeline.ID)
		err = runner.Process(pipeline)
		if err != nil {
			return r.NewError(err, http.StatusInternalServerError)
		}
		// Extra processing after the pipeline finishes
		go func() {
			err := <-doneChan
			if err != nil {
				return
			}
			if actionName == prefixedActionName(g.Type, "delete") {
				if err := r.Context.DeleteGuest(g); err != nil {
					log.WithFields(log.Fields{
						"guest": g.Id,
						"error": err,
						"func":  "agent.Context.DeleteGuest",
					}).Error("Delete Error:", err)
				}
				return
			}
		}()
		return r.JSON(http.StatusAccepted, g)
	})
}
<|endoftext|>


<commit_before>/* Copyright 2014 Ooyala, Inc. All rights reserved.
 *
 * This file is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

package rpc

import (
	. "atlantis/common"
	. "atlantis/manager/rpc/types"
	"atlantis/manager/status"
)

type UsageExecutor struct {
	arg   ManagerUsageArg
	reply *ManagerUsageReply
}

func (e *UsageExecutor) Request() interface{} {
	return e.arg
}

func (e *UsageExecutor) Result() interface{} {
	return e.reply
}

func (e *UsageExecutor) Description() string {
	return "Usage"
}

func (e *UsageExecutor) Execute(t *Task) error {
	e.reply.Usage, err := status.GetUsage()
	t.Log("[RPC][Usage] -> %+v", e.reply.Usage)
	return err
}

func (e *UsageExecutor) Authorize() error {
	return SimpleAuthorize(&e.arg.ManagerAuthArg)
}

func (m *ManagerRPC) Usage(arg ManagerUsageArg, reply *ManagerUsageReply) error {
	return NewTask("Usage", &UsageExecutor{arg, reply}).Run()
}
<commit_msg>fix syntax errors<commit_after>/* Copyright 2014 Ooyala, Inc. All rights reserved.
 *
 * This file is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

package rpc

import (
	. "atlantis/common"
	. "atlantis/manager/rpc/types"
	"atlantis/manager/status"
)

type UsageExecutor struct {
	arg   ManagerUsageArg
	reply *ManagerUsageReply
}

func (e *UsageExecutor) Request() interface{} {
	return e.arg
}

func (e *UsageExecutor) Result() interface{} {
	return e.reply
}

func (e *UsageExecutor) Description() string {
	return "Usage"
}

func (e *UsageExecutor) Execute(t *Task) (err error) {
	e.reply.Usage, err = status.GetUsage()
	t.Log("[RPC][Usage] -> %+v", e.reply.Usage)
	return
}

func (e *UsageExecutor) Authorize() error {
	return SimpleAuthorize(&e.arg.ManagerAuthArg)
}

func (m *ManagerRPC) Usage(arg ManagerUsageArg, reply *ManagerUsageReply) error {
	return NewTask("Usage", &UsageExecutor{arg, reply}).Run()
}
<|endoftext|>