<commit_before>// Copyright 2015 Joel Wu
//
// 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 redis

import (
    "errors"
    "log"
    "fmt"
    "time"
    "sync"
    "strings"
    "strconv"
)

// Options is used to initialize a new redis cluster.
type Options struct {
    StartNodes	    []string	    // Startup nodes

    ConnTimeout	    time.Duration   // Connection timeout
    ReadTimeout	    time.Duration   // Read timeout
    WriteTimeout    time.Duration   // Write timeout

    KeepAlive	    int		    // Maximum keep alive connecion in each node
    AliveTime	    time.Duration   // Keep alive timeout
}

// Cluster is a redis client that manage connections to redis nodes,
// cache and update cluster info, and execute all kinds of commands.
// Multiple goroutines may invoke methods on a cluster simutaneously.
type Cluster struct {
    slots	    [kClusterSlots]*redisNode
    nodes	    map[string]*redisNode

    connTimeout	    time.Duration
    readTimeout	    time.Duration
    writeTimeout    time.Duration

    keepAlive	    int
    aliveTime	    time.Duration

    updateTime	    time.Time
    updateList	    chan updateMesg

    rwLock	    sync.RWMutex

    closed	    bool
}

type updateMesg struct {
    node*	    redisNode
    movedTime	    time.Time
}

// NewCluster create a new redis cluster client with specified options.
func NewCluster(options *Options) (*Cluster, error) {
    cluster := &Cluster{
	nodes: make(map[string]*redisNode),
	connTimeout: options.ConnTimeout,
	readTimeout: options.ReadTimeout,
	writeTimeout: options.WriteTimeout,
	keepAlive: options.KeepAlive,
	aliveTime: options.AliveTime,
	updateList: make(chan updateMesg),
    }

    for i := range options.StartNodes {
	node := &redisNode{
	    address: options.StartNodes[i],
	    connTimeout: options.ConnTimeout,
	    readTimeout: options.ReadTimeout,
	    writeTimeout: options.WriteTimeout,
	    keepAlive: options.KeepAlive,
	    aliveTime: options.AliveTime,
	}

	err := cluster.update(node)
	if err != nil {
	    continue
	} else {
	    go cluster.handleUpdate()
	    return cluster, nil
	}
    }

    return nil, fmt.Errorf("NewCluster: no valid node in %v", options.StartNodes)
}

// Do excute a redis command with random number arguments. First argument will
// be used as key to hash to a slot, so it only supports a subset of redis
// commands.
///
// SUPPORTED: most commands of keys, strings, lists, sets, sorted sets, hashes.
// NOT SUPPORTED: scripts, transactions, clusters.
//
// Particularly, MSET/MSETNX/MGET are supported using result aggregation.
// To MSET/MSETNX, there's no atomicity gurantee that given keys are set at once.
// It's possible that some keys are set, while others not.
//
// See README.md for more details.
// See full redis command list: http://www.redis.io/commands
func (cluster *Cluster) Do(cmd string, args ...interface{}) (interface{}, error) {
    if len(args) < 1 {
	return nil, fmt.Errorf("Do: no key found in args")
    }

    if cmd == "MSET" || cmd == "MSETNX" {
	return cluster.multiSet(cmd, args...)
    }

    if cmd == "MGET" {
	return cluster.multiGet(cmd, args...)
    }

    node, err := cluster.getNodeByKey(args[0])
    if err != nil {
	return nil, fmt.Errorf("Do: %v", err)
    }

    reply, err := node.do(cmd, args...)
    if err != nil {
	return nil, fmt.Errorf("Do: %v", err)
    }

    resp := checkReply(reply)

    switch(resp) {
    case kRespOK, kRespError:
	return reply, nil
    case kRespMove:
	return cluster.handleMove(node, reply.(redisError).Error(), cmd, args)
    case kRespAsk:
	return cluster.handleAsk(node, reply.(redisError).Error(), cmd, args)
    case kRespConnTimeout:
	return cluster.handleConnTimeout(node, cmd, args)
    }

    panic("unreachable")
}

// Close cluster connection, any subsequent method call will fail.
func (cluster *Cluster) Close() {
    cluster.rwLock.Lock()
    defer cluster.rwLock.Unlock()

    for addr, node := range cluster.nodes {
	node.shutdown()
	delete(cluster.nodes, addr)
    }

    cluster.closed = true
}

func (cluster *Cluster) handleMove(node *redisNode, replyMsg, cmd string, args []interface{}) (interface{}, error) {
    fields := strings.Split(replyMsg, " ")
    if len(fields) != 3 {
	return nil, fmt.Errorf("handleMove: invalid response \"%s\"", replyMsg)
    }

    // cluster has changed, inform update routine
    cluster.inform(node)

    newNode, err := cluster.getNodeByAddr(fields[2])
    if err != nil {
	return nil, fmt.Errorf("handleMove: %v", err)
    }

    return newNode.do(cmd, args...)
}

func (cluster *Cluster) handleAsk(node *redisNode, replyMsg, cmd string, args []interface{}) (interface{}, error) {
    fields := strings.Split(replyMsg, " ")
    if len(fields) != 3 {
	return nil, fmt.Errorf("handleAsk: invalid response \"%s\"", replyMsg)
    }

    newNode, err := cluster.getNodeByAddr(fields[2])
    if err != nil {
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    conn, err := newNode.getConn()
    if err != nil {
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    conn.send("ASKING")
    conn.send(cmd, args...)

    err = conn.flush()
    if err != nil {
	conn.shutdown()
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    re, err := String(conn.receive())
    if err != nil || re != "OK" {
	conn.shutdown()
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    reply, err := conn.receive()
    if err != nil {
	conn.shutdown()
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    newNode.releaseConn(conn)

    return reply, nil
}

func (cluster *Cluster) handleConnTimeout(node *redisNode, cmd string, args []interface{}) (interface{}, error) {
    var randomNode *redisNode

    // choose a random node other than previous one
    cluster.rwLock.RLock()
    for _, randomNode = range cluster.nodes {
	if randomNode.address != node.address {
	    break
	}
    }
    cluster.rwLock.RUnlock()

    reply, err := randomNode.do(cmd, args...)
    if err != nil {
	return nil, fmt.Errorf("handleConnTimeout: %v", err)
    }

    if _, ok := reply.(redisError); !ok {
	// we happen to choose the right node, which means
	// that cluster has changed, so inform update routine.
	cluster.inform(randomNode)
	return reply, nil
    }

    // ignore replies other than MOVED
    errMsg := reply.(redisError).Error()
    if len(errMsg) < 5 || string(errMsg[:5]) != "MOVED" {
	return nil, errors.New(errMsg)
    }

    // When MOVED received, we check wether move adress equal to
    // previous one. If equal, then it's just an connection timeout
    // error, return error and carry on. If not, then the master may
    // down or unreachable, a new master has served the slot, request
    // new master and update cluster info.
    //
    // TODO: At worst case, it will request redis 3 times on a single
    // command, will this be a problem?
    fields := strings.Split(errMsg, " ")
    if len(fields) != 3 {
	return nil, fmt.Errorf("handleConnTimeout: invalid response \"%s\"", errMsg)
    }

    if fields[2] == node.address {
	return nil, fmt.Errorf("handleConnTimeout: %s connection timeout", node.address)
    }

    // cluster change, inform back routine to update
    cluster.inform(randomNode)

    newNode, err := cluster.getNodeByAddr(fields[2])
    if err != nil {
	return nil, fmt.Errorf("handleConnTimeout: %v", err)
    }

    return newNode.do(cmd, args...)
}

const (
    kClusterSlots	= 16384

    kRespOK		= 0
    kRespMove		= 1
    kRespAsk		= 2
    kRespConnTimeout	= 3
    kRespError		= 4
)

func checkReply(reply interface{}) int {
    if _, ok := reply.(redisError); !ok {
	return kRespOK
    }

    errMsg := reply.(redisError).Error()

    if len(errMsg) >= 3 && string(errMsg[:3]) == "ASK" {
	return kRespAsk
    }

    if len(errMsg) >= 5 && string(errMsg[:5]) == "MOVED" {
	return kRespMove
    }

    if len(errMsg) >= 12 && string(errMsg[:12]) == "ECONNTIMEOUT" {
	return kRespConnTimeout
    }

    return kRespError
}

func (cluster *Cluster) update(node *redisNode) error {
    info, err := Values(node.do("CLUSTER", "SLOTS"))
    if err != nil {
	return err
    }

    errFormat := fmt.Errorf("update: %s invalid response", node.address)

    var nslots int
    slots := make(map[string][]uint16)

    for _, i := range info {
	m, err := Values(i, err)
	if err != nil || len(m) < 3 {
	    return errFormat
	}

	start, err := Int(m[0], err)
	if err != nil {
	    return errFormat
	}

	end, err := Int(m[1], err)
	if err != nil {
	    return errFormat
	}

	t, err := Values(m[2], err)
	if err != nil || len(t) < 2 {
	    return errFormat
	}

	var ip string
	var port int

	_, err = Scan(t, &ip, &port)
	if err != nil {
	    return errFormat
	}
	addr := fmt.Sprintf("%s:%d", ip, port)

	slot, ok := slots[addr]
	if !ok {
	    slot = make([]uint16, 2)
	}

	nslots += end - start + 1

	slot = append(slot, uint16(start))
	slot = append(slot , uint16(end))

	slots[addr] = slot
    }

    // TODO: Is full coverage really needed?
    if nslots != kClusterSlots {
	return fmt.Errorf("update: %s slots not full covered", node.address)
    }

    cluster.rwLock.Lock()
    defer cluster.rwLock.Unlock()

    t := time.Now()
    cluster.updateTime = t

    for addr, slot := range slots {
	node, ok := cluster.nodes[addr]
	if !ok {
	    node = &redisNode {
		address: addr,
		connTimeout: cluster.connTimeout,
		readTimeout: cluster.readTimeout,
		writeTimeout: cluster.writeTimeout,
		keepAlive: cluster.keepAlive,
		aliveTime: cluster.aliveTime,
	    }
	}

	n := len(slot)
	for i := 0; i < n - 1; i += 2 {
	    start := slot[i]
	    end := slot[i+1]

	    for j := start; j <= end; j++ {
		cluster.slots[j] = node
	    }
	}

	node.updateTime = t
	cluster.nodes[addr] = node
    }

    // shrink
    for addr, node := range cluster.nodes {
	if node.updateTime != t {
	    node.shutdown()

	    delete(cluster.nodes, addr)
	}
    }

    return nil
}

func (cluster *Cluster) handleUpdate() {
    for {
	msg := <-cluster.updateList

	// TODO: control update frequency by updateTime and movedTime?

	err := cluster.update(msg.node)
	if err != nil {
	    log.Printf("handleUpdate: %v\n", err)
	}
    }
}

func (cluster *Cluster) inform(node *redisNode) {
    mesg := updateMesg {
	node: node,
	movedTime: time.Now(),
    }

    select {
    case cluster.updateList <- mesg:
	// Push update message, no more to do.
    default:
	// Update channel full, just carry on.
    }
}

func (cluster *Cluster) getNodeByAddr(addr string) (*redisNode, error) {
    cluster.rwLock.RLock()
    defer cluster.rwLock.RUnlock()

    if cluster.closed {
	return nil, fmt.Errorf("getNodeByAddr: cluster has been closed")
    }

    node, ok := cluster.nodes[addr]
    if !ok {
	return nil, fmt.Errorf("getNodeByAddr: %s not found", addr)
    }

    return node, nil
}

func (cluster *Cluster) getNodeByKey(arg interface{}) (*redisNode, error) {
    key, err := key(arg)
    if err != nil {
	return nil, fmt.Errorf("getNodeByKey: invalid key %v", key)
    }

    slot := hash(key)

    cluster.rwLock.RLock()
    defer cluster.rwLock.RUnlock()

    if cluster.closed {
	return nil, fmt.Errorf("getNodeByKey: cluster has been closed")
    }

    node := cluster.slots[slot]
    if node == nil {
	return nil, fmt.Errorf("getNodeByKey: %s[%d] no node found", key, slot)
    }

    return node, nil
}

func key(arg interface{}) (string, error) {
    switch arg := arg.(type) {
    case int:
	return strconv.Itoa(arg), nil
    case int64:
	return strconv.Itoa(int(arg)), nil
    case float64:
	return strconv.FormatFloat(arg, 'g', -1, 64), nil
    case string:
	return arg, nil
    case []byte:
	return string(arg), nil
    default:
	return "", fmt.Errorf("key: unknown type %T", arg)
    }
}

func hash(key string) uint16 {
    var s, e int
    for s = 0; s < len(key); s++ {
	if key[s] == '{' {
	    break
	}
    }

    if s == len(key) {
	return crc16(key) & (kClusterSlots-1)
    }

    for e = s+1; e < len(key); e++ {
	if key[e] == '}' {
	    break
	}
    }

    if e == len(key) || e == s+1 {
	return crc16(key) & (kClusterSlots-1)
    }

    return crc16(key[s+1:e]) & (kClusterSlots-1)
}

func init() {
    log.SetFlags(log.LstdFlags | log.Lshortfile)
}
<commit_msg>Incorrect mapping of slot #0<commit_after>// Copyright 2015 Joel Wu
//
// 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 redis

import (
    "errors"
    "log"
    "fmt"
    "time"
    "sync"
    "strings"
    "strconv"
)

// Options is used to initialize a new redis cluster.
type Options struct {
    StartNodes	    []string	    // Startup nodes

    ConnTimeout	    time.Duration   // Connection timeout
    ReadTimeout	    time.Duration   // Read timeout
    WriteTimeout    time.Duration   // Write timeout

    KeepAlive	    int		    // Maximum keep alive connecion in each node
    AliveTime	    time.Duration   // Keep alive timeout
}

// Cluster is a redis client that manage connections to redis nodes,
// cache and update cluster info, and execute all kinds of commands.
// Multiple goroutines may invoke methods on a cluster simutaneously.
type Cluster struct {
    slots	    [kClusterSlots]*redisNode
    nodes	    map[string]*redisNode

    connTimeout	    time.Duration
    readTimeout	    time.Duration
    writeTimeout    time.Duration

    keepAlive	    int
    aliveTime	    time.Duration

    updateTime	    time.Time
    updateList	    chan updateMesg

    rwLock	    sync.RWMutex

    closed	    bool
}

type updateMesg struct {
    node*	    redisNode
    movedTime	    time.Time
}

// NewCluster create a new redis cluster client with specified options.
func NewCluster(options *Options) (*Cluster, error) {
    cluster := &Cluster{
	nodes: make(map[string]*redisNode),
	connTimeout: options.ConnTimeout,
	readTimeout: options.ReadTimeout,
	writeTimeout: options.WriteTimeout,
	keepAlive: options.KeepAlive,
	aliveTime: options.AliveTime,
	updateList: make(chan updateMesg),
    }

    for i := range options.StartNodes {
	node := &redisNode{
	    address: options.StartNodes[i],
	    connTimeout: options.ConnTimeout,
	    readTimeout: options.ReadTimeout,
	    writeTimeout: options.WriteTimeout,
	    keepAlive: options.KeepAlive,
	    aliveTime: options.AliveTime,
	}

	err := cluster.update(node)
	if err != nil {
	    continue
	} else {
	    go cluster.handleUpdate()
	    return cluster, nil
	}
    }

    return nil, fmt.Errorf("NewCluster: no valid node in %v", options.StartNodes)
}

// Do excute a redis command with random number arguments. First argument will
// be used as key to hash to a slot, so it only supports a subset of redis
// commands.
///
// SUPPORTED: most commands of keys, strings, lists, sets, sorted sets, hashes.
// NOT SUPPORTED: scripts, transactions, clusters.
//
// Particularly, MSET/MSETNX/MGET are supported using result aggregation.
// To MSET/MSETNX, there's no atomicity gurantee that given keys are set at once.
// It's possible that some keys are set, while others not.
//
// See README.md for more details.
// See full redis command list: http://www.redis.io/commands
func (cluster *Cluster) Do(cmd string, args ...interface{}) (interface{}, error) {
    if len(args) < 1 {
	return nil, fmt.Errorf("Do: no key found in args")
    }

    if cmd == "MSET" || cmd == "MSETNX" {
	return cluster.multiSet(cmd, args...)
    }

    if cmd == "MGET" {
	return cluster.multiGet(cmd, args...)
    }

    node, err := cluster.getNodeByKey(args[0])
    if err != nil {
	return nil, fmt.Errorf("Do: %v", err)
    }

    reply, err := node.do(cmd, args...)
    if err != nil {
	return nil, fmt.Errorf("Do: %v", err)
    }

    resp := checkReply(reply)

    switch(resp) {
    case kRespOK, kRespError:
	return reply, nil
    case kRespMove:
	return cluster.handleMove(node, reply.(redisError).Error(), cmd, args)
    case kRespAsk:
	return cluster.handleAsk(node, reply.(redisError).Error(), cmd, args)
    case kRespConnTimeout:
	return cluster.handleConnTimeout(node, cmd, args)
    }

    panic("unreachable")
}

// Close cluster connection, any subsequent method call will fail.
func (cluster *Cluster) Close() {
    cluster.rwLock.Lock()
    defer cluster.rwLock.Unlock()

    for addr, node := range cluster.nodes {
	node.shutdown()
	delete(cluster.nodes, addr)
    }

    cluster.closed = true
}

func (cluster *Cluster) handleMove(node *redisNode, replyMsg, cmd string, args []interface{}) (interface{}, error) {
    fields := strings.Split(replyMsg, " ")
    if len(fields) != 3 {
	return nil, fmt.Errorf("handleMove: invalid response \"%s\"", replyMsg)
    }

    // cluster has changed, inform update routine
    cluster.inform(node)

    newNode, err := cluster.getNodeByAddr(fields[2])
    if err != nil {
	return nil, fmt.Errorf("handleMove: %v", err)
    }

    return newNode.do(cmd, args...)
}

func (cluster *Cluster) handleAsk(node *redisNode, replyMsg, cmd string, args []interface{}) (interface{}, error) {
    fields := strings.Split(replyMsg, " ")
    if len(fields) != 3 {
	return nil, fmt.Errorf("handleAsk: invalid response \"%s\"", replyMsg)
    }

    newNode, err := cluster.getNodeByAddr(fields[2])
    if err != nil {
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    conn, err := newNode.getConn()
    if err != nil {
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    conn.send("ASKING")
    conn.send(cmd, args...)

    err = conn.flush()
    if err != nil {
	conn.shutdown()
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    re, err := String(conn.receive())
    if err != nil || re != "OK" {
	conn.shutdown()
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    reply, err := conn.receive()
    if err != nil {
	conn.shutdown()
	return nil, fmt.Errorf("handleAsk: %v", err)
    }

    newNode.releaseConn(conn)

    return reply, nil
}

func (cluster *Cluster) handleConnTimeout(node *redisNode, cmd string, args []interface{}) (interface{}, error) {
    var randomNode *redisNode

    // choose a random node other than previous one
    cluster.rwLock.RLock()
    for _, randomNode = range cluster.nodes {
	if randomNode.address != node.address {
	    break
	}
    }
    cluster.rwLock.RUnlock()

    reply, err := randomNode.do(cmd, args...)
    if err != nil {
	return nil, fmt.Errorf("handleConnTimeout: %v", err)
    }

    if _, ok := reply.(redisError); !ok {
	// we happen to choose the right node, which means
	// that cluster has changed, so inform update routine.
	cluster.inform(randomNode)
	return reply, nil
    }

    // ignore replies other than MOVED
    errMsg := reply.(redisError).Error()
    if len(errMsg) < 5 || string(errMsg[:5]) != "MOVED" {
	return nil, errors.New(errMsg)
    }

    // When MOVED received, we check wether move adress equal to
    // previous one. If equal, then it's just an connection timeout
    // error, return error and carry on. If not, then the master may
    // down or unreachable, a new master has served the slot, request
    // new master and update cluster info.
    //
    // TODO: At worst case, it will request redis 3 times on a single
    // command, will this be a problem?
    fields := strings.Split(errMsg, " ")
    if len(fields) != 3 {
	return nil, fmt.Errorf("handleConnTimeout: invalid response \"%s\"", errMsg)
    }

    if fields[2] == node.address {
	return nil, fmt.Errorf("handleConnTimeout: %s connection timeout", node.address)
    }

    // cluster change, inform back routine to update
    cluster.inform(randomNode)

    newNode, err := cluster.getNodeByAddr(fields[2])
    if err != nil {
	return nil, fmt.Errorf("handleConnTimeout: %v", err)
    }

    return newNode.do(cmd, args...)
}

const (
    kClusterSlots	= 16384

    kRespOK		= 0
    kRespMove		= 1
    kRespAsk		= 2
    kRespConnTimeout	= 3
    kRespError		= 4
)

func checkReply(reply interface{}) int {
    if _, ok := reply.(redisError); !ok {
	return kRespOK
    }

    errMsg := reply.(redisError).Error()

    if len(errMsg) >= 3 && string(errMsg[:3]) == "ASK" {
	return kRespAsk
    }

    if len(errMsg) >= 5 && string(errMsg[:5]) == "MOVED" {
	return kRespMove
    }

    if len(errMsg) >= 12 && string(errMsg[:12]) == "ECONNTIMEOUT" {
	return kRespConnTimeout
    }

    return kRespError
}

func (cluster *Cluster) update(node *redisNode) error {
    info, err := Values(node.do("CLUSTER", "SLOTS"))
    if err != nil {
	return err
    }

    errFormat := fmt.Errorf("update: %s invalid response", node.address)

    var nslots int
    slots := make(map[string][]uint16)

    for _, i := range info {
	m, err := Values(i, err)
	if err != nil || len(m) < 3 {
	    return errFormat
	}

	start, err := Int(m[0], err)
	if err != nil {
	    return errFormat
	}

	end, err := Int(m[1], err)
	if err != nil {
	    return errFormat
	}

	t, err := Values(m[2], err)
	if err != nil || len(t) < 2 {
	    return errFormat
	}

	var ip string
	var port int

	_, err = Scan(t, &ip, &port)
	if err != nil {
	    return errFormat
	}
	addr := fmt.Sprintf("%s:%d", ip, port)

	slot, ok := slots[addr]
	if !ok {
	    slot = make([]uint16, 0, 2)
	}

	nslots += end - start + 1

	slot = append(slot, uint16(start))
	slot = append(slot , uint16(end))

	slots[addr] = slot
    }

    // TODO: Is full coverage really needed?
    if nslots != kClusterSlots {
	return fmt.Errorf("update: %s slots not full covered", node.address)
    }

    cluster.rwLock.Lock()
    defer cluster.rwLock.Unlock()

    t := time.Now()
    cluster.updateTime = t

    for addr, slot := range slots {
	node, ok := cluster.nodes[addr]
	if !ok {
	    node = &redisNode {
		address: addr,
		connTimeout: cluster.connTimeout,
		readTimeout: cluster.readTimeout,
		writeTimeout: cluster.writeTimeout,
		keepAlive: cluster.keepAlive,
		aliveTime: cluster.aliveTime,
	    }
	}

	n := len(slot)
	for i := 0; i < n - 1; i += 2 {
	    start := slot[i]
	    end := slot[i+1]

	    for j := start; j <= end; j++ {
		cluster.slots[j] = node
	    }
	}

	node.updateTime = t
	cluster.nodes[addr] = node
    }

    // shrink
    for addr, node := range cluster.nodes {
	if node.updateTime != t {
	    node.shutdown()

	    delete(cluster.nodes, addr)
	}
    }

    return nil
}

func (cluster *Cluster) handleUpdate() {
    for {
	msg := <-cluster.updateList

	// TODO: control update frequency by updateTime and movedTime?

	err := cluster.update(msg.node)
	if err != nil {
	    log.Printf("handleUpdate: %v\n", err)
	}
    }
}

func (cluster *Cluster) inform(node *redisNode) {
    mesg := updateMesg {
	node: node,
	movedTime: time.Now(),
    }

    select {
    case cluster.updateList <- mesg:
	// Push update message, no more to do.
    default:
	// Update channel full, just carry on.
    }
}

func (cluster *Cluster) getNodeByAddr(addr string) (*redisNode, error) {
    cluster.rwLock.RLock()
    defer cluster.rwLock.RUnlock()

    if cluster.closed {
	return nil, fmt.Errorf("getNodeByAddr: cluster has been closed")
    }

    node, ok := cluster.nodes[addr]
    if !ok {
	return nil, fmt.Errorf("getNodeByAddr: %s not found", addr)
    }

    return node, nil
}

func (cluster *Cluster) getNodeByKey(arg interface{}) (*redisNode, error) {
    key, err := key(arg)
    if err != nil {
	return nil, fmt.Errorf("getNodeByKey: invalid key %v", key)
    }

    slot := hash(key)

    cluster.rwLock.RLock()
    defer cluster.rwLock.RUnlock()

    if cluster.closed {
	return nil, fmt.Errorf("getNodeByKey: cluster has been closed")
    }

    node := cluster.slots[slot]
    if node == nil {
	return nil, fmt.Errorf("getNodeByKey: %s[%d] no node found", key, slot)
    }

    return node, nil
}

func key(arg interface{}) (string, error) {
    switch arg := arg.(type) {
    case int:
	return strconv.Itoa(arg), nil
    case int64:
	return strconv.Itoa(int(arg)), nil
    case float64:
	return strconv.FormatFloat(arg, 'g', -1, 64), nil
    case string:
	return arg, nil
    case []byte:
	return string(arg), nil
    default:
	return "", fmt.Errorf("key: unknown type %T", arg)
    }
}

func hash(key string) uint16 {
    var s, e int
    for s = 0; s < len(key); s++ {
	if key[s] == '{' {
	    break
	}
    }

    if s == len(key) {
	return crc16(key) & (kClusterSlots-1)
    }

    for e = s+1; e < len(key); e++ {
	if key[e] == '}' {
	    break
	}
    }

    if e == len(key) || e == s+1 {
	return crc16(key) & (kClusterSlots-1)
    }

    return crc16(key[s+1:e]) & (kClusterSlots-1)
}

func init() {
    log.SetFlags(log.LstdFlags | log.Lshortfile)
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"github.com/scalegray/gs-golib/fs"
	"io"
	"launchpad.net/gnuflag"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"net/http"
)

type exiter interface {
	Exit(int)
}

type osExiter struct{}

func (e osExiter) Exit(code int) {
	os.Exit(code)
}

type Manager struct {
	Commands      map[string]Command
	topics        map[string]string
	name          string
	stdout        io.Writer
	stderr        io.Writer
	stdin         io.Reader
	version       string
	versionHeader string
	e             exiter
	original      string
	wrong         bool
}

func NewManager(name, ver, verHeader string, stdout, stderr io.Writer, stdin io.Reader) *Manager {
	manager := &Manager{name: name, version: ver, versionHeader: verHeader, stdout: stdout, stderr: stderr, stdin: stdin}
	manager.Register(&help{manager})
	manager.Register(&version{manager})
	return manager
}

func BuildBaseManager(name, version, versionHeader string) *Manager {
	m := NewManager(name, version, versionHeader, os.Stdout, os.Stderr, os.Stdin)
	return m
}

func (m *Manager) Register(command Command) {
	if m.Commands == nil {
		m.Commands = make(map[string]Command)
	}
	name := command.Info().Name
	_, found := m.Commands[name]
	if found {
		panic(fmt.Sprintf("command already registered: %s", name))
	}
	m.Commands[name] = command
}

func (m *Manager) Run(args []string) {
	var status int
	if len(args) == 0 {
		args = append(args, "help")
	}
	name := args[0]
	command, ok := m.Commands[name]
	if !ok {
		fmt.Fprintf(m.stderr, "Error: command %q does not exist\n", args[0])
		m.finisher().Exit(1)
		return
	}
	args = args[1:]
	info := command.Info()
	if flagged, ok := command.(FlaggedCommand); ok {
		flagset := flagged.Flags()
		err := flagset.Parse(true, args)
		if err != nil {
			fmt.Fprint(m.stderr, err)
			m.finisher().Exit(1)
			return
		}
		args = flagset.Args()
	}
	if length := len(args); (length < info.MinArgs || (info.MaxArgs > 0 && length > info.MaxArgs)) &&
		name != "help" {
		m.wrong = true
		m.original = info.Name
		command = m.Commands["help"]
		args = []string{name}
		status = 1
	}
	context := Context{args, m.stdout, m.stderr, m.stdin}

	client := NewClient(&http.Client{}, &context, m)
	err := command.Run(&context, client)

	if err != nil {
		re := regexp.MustCompile(`^((Invalid token)|(You must provide the Authorization header))`)
		errorMsg := err.Error()
		if re.MatchString(errorMsg) {
			errorMsg = `You're not authenticated or your session has expired. Please use "login" command for authentication.`
		}
		if !strings.HasSuffix(errorMsg, "\n") {
			errorMsg += "\n"
		}
		io.WriteString(m.stderr, "Error: "+errorMsg)
		status = 1
	}
	m.finisher().Exit(status)
}

func (m *Manager) finisher() exiter {
	if m.e == nil {
		m.e = osExiter{}
	}
	return m.e
}

type Command interface {
	Info() *Info
	Run(context *Context, client *Client) error
}

type FlaggedCommand interface {
	Command
	Flags() *gnuflag.FlagSet
}

type Context struct {
	Args   []string
	Stdout io.Writer
	Stderr io.Writer
	Stdin  io.Reader
}

type Info struct {
	Name    string
	MinArgs int
	MaxArgs int
	Usage   string
	Desc    string
}

type help struct {
	manager *Manager
}

func (c *help) Info() *Info {
	return &Info{
		Name:  "help",
		Usage: "command [args]",
	}
}

func (c *help) Run(context *Context, client *Client) error {
	output := fmt.Sprintf("%s version %s.\n\n", c.manager.name, c.manager.version)
	if c.manager.wrong {
		output += fmt.Sprint("ERROR: wrong number of arguments.\n\n")
	}
	if len(context.Args) > 0 {
		if cmd, ok := c.manager.Commands[context.Args[0]]; ok {
			info := cmd.Info()
			output += fmt.Sprintf("Usage: %s %s\n", c.manager.name, info.Usage)
			output += fmt.Sprintf("\n%s\n", info.Desc)
			if info.MinArgs > 0 {
				output += fmt.Sprintf("\nMinimum # of arguments: %d", info.MinArgs)
			}
			if info.MaxArgs > 0 {
				output += fmt.Sprintf("\nMaximum # of arguments: %d", info.MaxArgs)
			}
			output += fmt.Sprint("\n")
		} else if topic, ok := c.manager.topics[context.Args[0]]; ok {
			output += topic
		} else {
			return fmt.Errorf("command %q does not exist.", context.Args[0])
		}
	} else {
		output += fmt.Sprintf("Usage: %s %s\n\nAvailable commands:\n", c.manager.name, c.Info().Usage)
		var commands []string
		for k := range c.manager.Commands {
			commands = append(commands, k)
		}
		sort.Strings(commands)
		for _, command := range commands {
			output += fmt.Sprintf("  %s\n", command)
		}
		output += fmt.Sprintf("\nUse %s help <commandname> to get more information about a command.\n", c.manager.name)
		if len(c.manager.topics) > 0 {
			output += fmt.Sprintln("\nAvailable topics:")
			for topic := range c.manager.topics {
				output += fmt.Sprintf("  %s\n", topic)
			}
			output += fmt.Sprintf("\nUse %s help <topicname> to get more information about a topic.\n", c.manager.name)
		}
	}
	io.WriteString(context.Stdout, output)
	return nil
}

type version struct {
	manager *Manager
}

func (c *version) Info() *Info {
	return &Info{
		Name:    "version",
		MinArgs: 0,
		Usage:   "version",
		Desc:    "display the current version",
	}
}

func (c *version) Run(context *Context, client *Client) error {
	fmt.Fprintf(context.Stdout, "%s version %s.\n", c.manager.name, c.manager.version)
	return nil
}

func ExtractProgramName(path string) string {
	parts := strings.Split(path, "/")
	return parts[len(parts)-1]
}

var fsystem fs.Fs

func filesystem() fs.Fs {
	if fsystem == nil {
		fsystem = fs.OsFs{}
	}
	return fsystem
}

// validateVersion checks whether current version is greater or equal to
// supported version.
func validateVersion(supported, current string) bool {
	var (
		bigger bool
		limit  int
	)
	if supported == "" {
		return true
	}
	partsSupported := strings.Split(supported, ".")
	partsCurrent := strings.Split(current, ".")
	if len(partsSupported) > len(partsCurrent) {
		limit = len(partsCurrent)
		bigger = true
	} else {
		limit = len(partsSupported)
	}
	for i := 0; i < limit; i++ {
		current, err := strconv.Atoi(partsCurrent[i])
		if err != nil {
			return false
		}
		supported, err := strconv.Atoi(partsSupported[i])
		if err != nil {
			return false
		}
		if current < supported {
			return false
		}
		if current > supported {
			return true
		}
	}
	if bigger {
		return false
	}
	return true
}
<commit_msg>smalllfix<commit_after>package cmd

import (
	"fmt"
	"github.com/scalegray/sg-golib/fs"
	"io"
	"launchpad.net/gnuflag"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"net/http"
)

type exiter interface {
	Exit(int)
}

type osExiter struct{}

func (e osExiter) Exit(code int) {
	os.Exit(code)
}

type Manager struct {
	Commands      map[string]Command
	topics        map[string]string
	name          string
	stdout        io.Writer
	stderr        io.Writer
	stdin         io.Reader
	version       string
	versionHeader string
	e             exiter
	original      string
	wrong         bool
}

func NewManager(name, ver, verHeader string, stdout, stderr io.Writer, stdin io.Reader) *Manager {
	manager := &Manager{name: name, version: ver, versionHeader: verHeader, stdout: stdout, stderr: stderr, stdin: stdin}
	manager.Register(&help{manager})
	manager.Register(&version{manager})
	return manager
}

func BuildBaseManager(name, version, versionHeader string) *Manager {
	m := NewManager(name, version, versionHeader, os.Stdout, os.Stderr, os.Stdin)
	return m
}

func (m *Manager) Register(command Command) {
	if m.Commands == nil {
		m.Commands = make(map[string]Command)
	}
	name := command.Info().Name
	_, found := m.Commands[name]
	if found {
		panic(fmt.Sprintf("command already registered: %s", name))
	}
	m.Commands[name] = command
}

func (m *Manager) Run(args []string) {
	var status int
	if len(args) == 0 {
		args = append(args, "help")
	}
	name := args[0]
	command, ok := m.Commands[name]
	if !ok {
		fmt.Fprintf(m.stderr, "Error: command %q does not exist\n", args[0])
		m.finisher().Exit(1)
		return
	}
	args = args[1:]
	info := command.Info()
	if flagged, ok := command.(FlaggedCommand); ok {
		flagset := flagged.Flags()
		err := flagset.Parse(true, args)
		if err != nil {
			fmt.Fprint(m.stderr, err)
			m.finisher().Exit(1)
			return
		}
		args = flagset.Args()
	}
	if length := len(args); (length < info.MinArgs || (info.MaxArgs > 0 && length > info.MaxArgs)) &&
		name != "help" {
		m.wrong = true
		m.original = info.Name
		command = m.Commands["help"]
		args = []string{name}
		status = 1
	}
	context := Context{args, m.stdout, m.stderr, m.stdin}

	client := NewClient(&http.Client{}, &context, m)
	err := command.Run(&context, client)

	if err != nil {
		re := regexp.MustCompile(`^((Invalid token)|(You must provide the Authorization header))`)
		errorMsg := err.Error()
		if re.MatchString(errorMsg) {
			errorMsg = `You're not authenticated or your session has expired. Please use "login" command for authentication.`
		}
		if !strings.HasSuffix(errorMsg, "\n") {
			errorMsg += "\n"
		}
		io.WriteString(m.stderr, "Error: "+errorMsg)
		status = 1
	}
	m.finisher().Exit(status)
}

func (m *Manager) finisher() exiter {
	if m.e == nil {
		m.e = osExiter{}
	}
	return m.e
}

type Command interface {
	Info() *Info
	Run(context *Context, client *Client) error
}

type FlaggedCommand interface {
	Command
	Flags() *gnuflag.FlagSet
}

type Context struct {
	Args   []string
	Stdout io.Writer
	Stderr io.Writer
	Stdin  io.Reader
}

type Info struct {
	Name    string
	MinArgs int
	MaxArgs int
	Usage   string
	Desc    string
}

type help struct {
	manager *Manager
}

func (c *help) Info() *Info {
	return &Info{
		Name:  "help",
		Usage: "command [args]",
	}
}

func (c *help) Run(context *Context, client *Client) error {
	output := fmt.Sprintf("%s version %s.\n\n", c.manager.name, c.manager.version)
	if c.manager.wrong {
		output += fmt.Sprint("ERROR: wrong number of arguments.\n\n")
	}
	if len(context.Args) > 0 {
		if cmd, ok := c.manager.Commands[context.Args[0]]; ok {
			info := cmd.Info()
			output += fmt.Sprintf("Usage: %s %s\n", c.manager.name, info.Usage)
			output += fmt.Sprintf("\n%s\n", info.Desc)
			if info.MinArgs > 0 {
				output += fmt.Sprintf("\nMinimum # of arguments: %d", info.MinArgs)
			}
			if info.MaxArgs > 0 {
				output += fmt.Sprintf("\nMaximum # of arguments: %d", info.MaxArgs)
			}
			output += fmt.Sprint("\n")
		} else if topic, ok := c.manager.topics[context.Args[0]]; ok {
			output += topic
		} else {
			return fmt.Errorf("command %q does not exist.", context.Args[0])
		}
	} else {
		output += fmt.Sprintf("Usage: %s %s\n\nAvailable commands:\n", c.manager.name, c.Info().Usage)
		var commands []string
		for k := range c.manager.Commands {
			commands = append(commands, k)
		}
		sort.Strings(commands)
		for _, command := range commands {
			output += fmt.Sprintf("  %s\n", command)
		}
		output += fmt.Sprintf("\nUse %s help <commandname> to get more information about a command.\n", c.manager.name)
		if len(c.manager.topics) > 0 {
			output += fmt.Sprintln("\nAvailable topics:")
			for topic := range c.manager.topics {
				output += fmt.Sprintf("  %s\n", topic)
			}
			output += fmt.Sprintf("\nUse %s help <topicname> to get more information about a topic.\n", c.manager.name)
		}
	}
	io.WriteString(context.Stdout, output)
	return nil
}

type version struct {
	manager *Manager
}

func (c *version) Info() *Info {
	return &Info{
		Name:    "version",
		MinArgs: 0,
		Usage:   "version",
		Desc:    "display the current version",
	}
}

func (c *version) Run(context *Context, client *Client) error {
	fmt.Fprintf(context.Stdout, "%s version %s.\n", c.manager.name, c.manager.version)
	return nil
}

func ExtractProgramName(path string) string {
	parts := strings.Split(path, "/")
	return parts[len(parts)-1]
}

var fsystem fs.Fs

func filesystem() fs.Fs {
	if fsystem == nil {
		fsystem = fs.OsFs{}
	}
	return fsystem
}

// validateVersion checks whether current version is greater or equal to
// supported version.
func validateVersion(supported, current string) bool {
	var (
		bigger bool
		limit  int
	)
	if supported == "" {
		return true
	}
	partsSupported := strings.Split(supported, ".")
	partsCurrent := strings.Split(current, ".")
	if len(partsSupported) > len(partsCurrent) {
		limit = len(partsCurrent)
		bigger = true
	} else {
		limit = len(partsSupported)
	}
	for i := 0; i < limit; i++ {
		current, err := strconv.Atoi(partsCurrent[i])
		if err != nil {
			return false
		}
		supported, err := strconv.Atoi(partsSupported[i])
		if err != nil {
			return false
		}
		if current < supported {
			return false
		}
		if current > supported {
			return true
		}
	}
	if bigger {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/driusan/dgit/git"
)

// Since libgit is somewhat out of our control and we can't implement
// a fmt.Stringer interface there, we use this helper.
func printCommit(c *git.Client, cmt git.CommitID) {
	author, err := cmt.GetAuthor(c)
	if err != nil {
		panic(err)
	}
	fmt.Printf("commit %s\n", cmt)
	if parents, err := cmt.Parents(c); len(parents) > 1 && err == nil {
		fmt.Printf("Merge: ")
		for i, p := range parents {
			fmt.Printf("%s", p)
			if i != len(parents)-1 {
				fmt.Printf(" ")
			}
		}
		fmt.Println()
	}
	date, err := cmt.GetDate(c)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Author: %v\nDate:   %v\n\n", author, date.Format("Mon Jan 2 15:04:05 2006 -0700"))
	//fmt.Printf("commit %v\nAuthor: %v\nDate: %v\n\n", c.Id, c.Author, c.Author.When.Format("Mon Jan 2 15:04:05 2006 -0700"))

	msg, err := cmt.GetCommitMessage(c)
	lines := strings.Split(strings.TrimSpace(msg), "\n")
	for _, l := range lines {
		fmt.Printf("    %v\n", l)
	}
	fmt.Printf("\n")
}

func Log(c *git.Client, args []string) error {
	if len(args) >= 2 {
		fmt.Fprintf(os.Stderr, "Usage: %s log [commitish]\n", os.Args[0])
		return errors.New("No options are currently supported for log")
	}

	var commit git.Commitish
	var err error
	if len(args) == 0 {
		commit, err = git.RevParseCommitish(c, &git.RevParseOptions{}, "HEAD")
	} else {
		commit, err = git.RevParseCommitish(c, &git.RevParseOptions{}, args[0])
	}
	if err != nil {
		return err
	}

	cmt, err := commit.CommitID(c)
	if err != nil {
		return err
	}

	ancestors, err := cmt.Ancestors(c)
	if err != nil {
		return err
	}

	for _, cmt := range ancestors {
		printCommit(c, cmt)
	}
	return nil

}
<commit_msg>Use a callback to walk the history for "git log"<commit_after>package cmd

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/driusan/dgit/git"
)

// Since libgit is somewhat out of our control and we can't implement
// a fmt.Stringer interface there, we use this helper.
func printCommit(c *git.Client, cmt git.CommitID) {
	author, err := cmt.GetAuthor(c)
	if err != nil {
		panic(err)
	}
	fmt.Printf("commit %s\n", cmt)
	if parents, err := cmt.Parents(c); len(parents) > 1 && err == nil {
		fmt.Printf("Merge: ")
		for i, p := range parents {
			fmt.Printf("%s", p)
			if i != len(parents)-1 {
				fmt.Printf(" ")
			}
		}
		fmt.Println()
	}
	date, err := cmt.GetDate(c)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Author: %v\nDate:   %v\n\n", author, date.Format("Mon Jan 2 15:04:05 2006 -0700"))
	//fmt.Printf("commit %v\nAuthor: %v\nDate: %v\n\n", c.Id, c.Author, c.Author.When.Format("Mon Jan 2 15:04:05 2006 -0700"))

	msg, err := cmt.GetCommitMessage(c)
	lines := strings.Split(strings.TrimSpace(msg), "\n")
	for _, l := range lines {
		fmt.Printf("    %v\n", l)
	}
	fmt.Printf("\n")
}

var visited map[git.CommitID]bool

func walkParents(c *git.Client, cmt git.CommitID) error {
	if visited[cmt] {
		return nil
	}
	visited[cmt] = true
	printCommit(c, cmt)
	parents, err := cmt.Parents(c)
	if err != nil {
		return err
	}
	for _, p := range parents {
		if _, visited := visited[p]; visited {
			continue
		}
		if err := walkParents(c, p); err != nil {
			return err
		}
	}
	return nil
}

func Log(c *git.Client, args []string) error {
	if len(args) >= 2 {
		fmt.Fprintf(os.Stderr, "Usage: %s log [commitish]\n", os.Args[0])
		return errors.New("No options are currently supported for log")
	}

	var commit git.Commitish
	var err error
	if len(args) == 0 {
		commit, err = git.RevParseCommitish(c, &git.RevParseOptions{}, "HEAD")
	} else {
		commit, err = git.RevParseCommitish(c, &git.RevParseOptions{}, args[0])
	}
	if err != nil {
		return err
	}

	cmt, err := commit.CommitID(c)
	if err != nil {
		return err
	}

	visited = make(map[git.CommitID]bool)

	return walkParents(c, cmt)

}
<|endoftext|>


<commit_before>/*
	This is just an example package for testing
*/
package examplepkg

import (
	"github.com/jkomoros/boardgame"
)

//go:generate autoreader

//+autoreader both
type myStruct struct {
	MyInt  int
	MyBool bool
}

//	 +autoreader
type myOtherStruct struct {
	blarg int
}

type noReaderStruct struct {
	MyInt int
}

// +autoreader reader
type onlyReader struct {
	MyString string
}

// +autoreader    readSetter
type onlyReadSetter struct {
	MyInt int
}
<commit_msg>Tweak package doc for autoreader.go. Part of #301.<commit_after>/*

	This is just an example package for testing. Note that some of the uses of
	autoreader use somewhat odd spacing or capitalization; this is primarily
	just to test how resilient the package is to unexpected input.

*/
package examplepkg

import (
	"github.com/jkomoros/boardgame"
)

//go:generate autoreader

//+autoreader both
type myStruct struct {
	MyInt  int
	MyBool bool
}

//	 +autoreader
type myOtherStruct struct {
	blarg int
}

type noReaderStruct struct {
	MyInt int
}

// +autoreader reader
type onlyReader struct {
	MyString string
}

// +autoreader    readSetter
type onlyReadSetter struct {
	MyInt int
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold Authors

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 app

import (
	"bytes"
	"io/ioutil"
	"os"
	"testing"

	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestMainHelp(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		var (
			output    bytes.Buffer
			errOutput bytes.Buffer
		)

		t.Override(&os.Args, []string{"skaffold", "help"})

		err := Run(&output, &errOutput)

		t.CheckNoError(err)
		t.CheckContains("End-to-end pipelines", output.String())
		t.CheckContains("Getting started with a new project", output.String())
		t.CheckEmpty(errOutput.String())
	})
}

func TestMainUnknownCommand(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		t.Override(&os.Args, []string{"skaffold", "unknown"})

		err := Run(ioutil.Discard, ioutil.Discard)

		t.CheckError(true, err)
	})
}
<commit_msg>Fix flaky test (#4538)<commit_after>/*
Copyright 2019 The Skaffold Authors

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 app

import (
	"bytes"
	"io/ioutil"
	"os"
	"testing"

	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestMainHelp(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		// --interactive=false removes the update check and survey prompt.
		t.Override(&os.Args, []string{"skaffold", "help", "--interactive=false"})

		var (
			output    bytes.Buffer
			errOutput bytes.Buffer
		)
		err := Run(&output, &errOutput)

		t.CheckNoError(err)
		t.CheckContains("End-to-end pipelines", output.String())
		t.CheckContains("Getting started with a new project", output.String())
		t.CheckEmpty(errOutput.String())
	})
}

func TestMainUnknownCommand(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		// --interactive=false removes the update check and survey prompt.
		t.Override(&os.Args, []string{"skaffold", "unknown", "--interactive=false"})

		err := Run(ioutil.Discard, ioutil.Discard)

		t.CheckError(true, err)
	})
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/codegangsta/cli"
	"github.com/keybase/go-libkb"
	"regexp"
	"strings"
)

type CommandLine struct {
	app  *cli.App
	ctx  *cli.Context
	cmd  Command
	name string // the name of the chosen command
}

func (p CommandLine) GetHome() string {
	return p.GetGString("home")
}
func (p CommandLine) GetServerUri() string {
	return p.GetGString("server")
}
func (p CommandLine) GetConfigFilename() string {
	return p.GetGString("config")
}
func (p CommandLine) GetSessionFilename() string {
	return p.GetGString("session")
}
func (p CommandLine) GetDbFilename() string {
	return p.GetGString("db")
}
func (p CommandLine) GetDebug() (bool, bool) {
	return p.GetBool("debug", true)
}
func (p CommandLine) GetUsername() string {
	return p.GetGString("username")
}
func (p CommandLine) GetUid() *libkb.UID {
	if s := p.GetGString("uid"); len(s) == 0 {
		return nil
	} else if i, e := libkb.UidFromHex(s); e == nil {
		return i
	} else {
		return nil
	}
}
func (p CommandLine) GetPgpFingerprint() *libkb.PgpFingerprint {
	return libkb.PgpFingerprintFromHexNoError(p.GetGString("fingerprint"))
}
func (p CommandLine) GetEmail() string {
	return p.GetGString("email")
}
func (p CommandLine) GetProxy() string {
	return p.GetGString("proxy")
}
func (p CommandLine) GetPlainLogging() (bool, bool) {
	return p.GetBool("plain-logging", true)
}
func (p CommandLine) GetPgpDir() string {
	return p.GetGString("pgpdir")
}
func (p CommandLine) GetApiDump() (bool, bool) {
	return p.GetBool("api-dump", true)
}
func (p CommandLine) GetPinentry() string {
	return p.GetGString("pinentry")
}
func (p CommandLine) GetGString(s string) string {
	return p.ctx.GlobalString(s)
}
func (p CommandLine) GetGInt(s string) int {
	return p.ctx.GlobalInt(s)
}
func (p CommandLine) GetGpg() string {
	return p.GetGString("gpg")
}
func (p CommandLine) GetSecretKeyring() string {
	return p.GetGString("secret-keyring")
}
func (p CommandLine) GetGpgOptions() []string {
	var ret []string
	s := p.GetGString("gpg-options")
	if len(s) > 0 {
		ret = regexp.MustCompile(`\s+`).Split(s, -1)
	}
	return ret
}
func (p CommandLine) GetMerkleKeyFingerprints() []string {
	s := p.GetGString("merkle-key-fingerprints")
	if len(s) != 0 {
		return strings.Split(s, ":")
	} else {
		return nil
	}
}
func (p CommandLine) GetUserCacheSize() (int, bool) {
	ret := p.GetGInt("user-cache-size")
	if ret != 0 {
		return ret, true
	} else {
		return 0, false
	}
}
func (p CommandLine) GetProofCacheSize() (int, bool) {
	ret := p.GetGInt("proof-cache-size")
	if ret != 0 {
		return ret, true
	} else {
		return 0, false
	}
}

func (p CommandLine) GetBool(s string, glbl bool) (bool, bool) {
	var v bool
	if glbl {
		v = p.ctx.GlobalBool(s)
	} else {
		v = p.ctx.Bool(s)
	}
	return v, v
}

type CmdBaseHelp struct {
	ctx *cli.Context
}

func (c *CmdBaseHelp) GetUsage() libkb.Usage {
	return libkb.Usage{}
}
func (c *CmdBaseHelp) ParseArgv(*cli.Context) error { return nil }

type CmdGeneralHelp struct {
	CmdBaseHelp
}

func (c *CmdBaseHelp) Run() error {
	cli.ShowAppHelp(c.ctx)
	return nil
}

type CmdSpecificHelp struct {
	CmdBaseHelp
	name string
}

func (c CmdSpecificHelp) Run() error {
	cli.ShowCommandHelp(c.ctx, c.name)
	return nil
}

func NewCommandLine() *CommandLine {
	app := cli.NewApp()
	ret := &CommandLine{app: app}
	ret.PopulateApp()
	return ret
}

func (cl *CommandLine) PopulateApp() {
	app := cl.app
	app.Name = "keybase"
	app.Version = libkb.CLIENT_VERSION
	app.Usage = "control keybase either with 1-off commands, " +
		"or start a daemon"

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "home, H",
			Usage: "specify an (alternate) home directory",
		},
		cli.StringFlag{
			Name: "server, s",
			Usage: "specify server API " +
				"(default: https://api.keybase.io:443/)",
		},
		cli.StringFlag{
			Name:  "config, c",
			Usage: "specify an (alternate) master config file",
		},
		cli.StringFlag{
			Name:  "session",
			Usage: "specify an alternate session data file",
		},
		cli.StringFlag{
			Name:  "db",
			Usage: "specify an alternate local DB location",
		},
		cli.StringFlag{
			Name:  "api-uri-path-prefix",
			Usage: "specify an alternate API URI path prefix",
		},
		cli.StringFlag{
			Name:  "username, u",
			Usage: "specify Keybase username of the current user",
		},
		cli.StringFlag{
			Name:  "uid, i",
			Usage: "specify Keybase UID for current user",
		},
		cli.StringFlag{
			Name:  "pinentry",
			Usage: "specify a path to find a pinentry program",
		},
		cli.StringFlag{
			Name:  "secret-keyring",
			Usage: "location of the Keybase secret-keyring (P3SKB-encoded)",
		},
		cli.StringFlag{
			Name: "proxy",
			Usage: "specify an HTTP(s) proxy to ship all Web " +
				"requests over",
		},
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "enable debugging mode",
		},
		cli.StringFlag{
			Name:  "email",
			Usage: "specify your email address for login/signup purposes",
		},
		cli.BoolFlag{
			Name:  "plain-logging, L",
			Usage: "plain logging mode (no colors)",
		},
		cli.StringFlag{
			Name:  "pgpdir, gpgdir",
			Usage: "specify a PGP directory (default is ~/.gnupg)",
		},
		cli.BoolFlag{
			Name:  "api-dump",
			Usage: "dump API call internals",
		},
		cli.StringFlag{
			Name:  "merkle-key-fingerprints",
			Usage: "Set of admissable Merkle Tree fingerprints (colon-separated)",
		},
		cli.IntFlag{
			Name:  "user-cache-size",
			Usage: "number of User entries to cache",
		},
		cli.IntFlag{
			Name:  "proof-cache-size",
			Usage: "number of proof entries to cache",
		},
		cli.StringFlag{
			Name:  "gpg",
			Usage: "Path to GPG client (optional for exporting keys)",
		},
		cli.StringFlag{
			Name:  "gpg-options",
			Usage: "Options to use when calling GPG",
		},
	}

	app.Commands = []cli.Command{
		NewCmdConfig(cl),
		NewCmdDb(cl),
		NewCmdId(cl),
		NewCmdListTracking(cl),
		NewCmdLogin(cl),
		NewCmdLogout(cl),
		NewCmdMykey(cl),
		NewCmdPing(cl),
		NewCmdProve(cl),
		NewCmdResolve(cl),
		NewCmdSigs(cl),
		NewCmdSign(cl),
		NewCmdSignup(cl),
		NewCmdTrack(cl),
		NewCmdVersion(cl),
	}

	// Finally, add a default help action...
	app.Action = func(c *cli.Context) {
		cl.cmd = &CmdGeneralHelp{CmdBaseHelp{c}}
		cl.ctx = c
		cl.name = "help"
	}
}

// Called back from inside our subcommands, when they're picked...
func (p *CommandLine) ChooseCommand(cmd Command, name string, ctx *cli.Context) {
	p.cmd = cmd
	p.name = name
	p.ctx = ctx
}

func (p *CommandLine) Parse(args []string) (cmd Command, err error) {

	// Actually pick a command
	err = p.app.Run(args)

	// Should not be populated
	cmd = p.cmd

	if err != nil || cmd == nil {
		return
	}

	// If we failed to parse arguments properly, switch to the help command
	if err = p.cmd.ParseArgv(p.ctx); err != nil {
		G.Log.Error("In '%s': %s", p.name, err.Error())
		cmd = &CmdSpecificHelp{CmdBaseHelp{p.ctx}, p.name}
	}

	return
}
<commit_msg>where to find the socketfile<commit_after>package main

import (
	"github.com/codegangsta/cli"
	"github.com/keybase/go-libkb"
	"regexp"
	"strings"
)

type CommandLine struct {
	app  *cli.App
	ctx  *cli.Context
	cmd  Command
	name string // the name of the chosen command
}

func (p CommandLine) GetHome() string {
	return p.GetGString("home")
}
func (p CommandLine) GetServerUri() string {
	return p.GetGString("server")
}
func (p CommandLine) GetConfigFilename() string {
	return p.GetGString("config")
}
func (p CommandLine) GetSessionFilename() string {
	return p.GetGString("session")
}
func (p CommandLine) GetDbFilename() string {
	return p.GetGString("db")
}
func (p CommandLine) GetDebug() (bool, bool) {
	return p.GetBool("debug", true)
}
func (p CommandLine) GetUsername() string {
	return p.GetGString("username")
}
func (p CommandLine) GetUid() *libkb.UID {
	if s := p.GetGString("uid"); len(s) == 0 {
		return nil
	} else if i, e := libkb.UidFromHex(s); e == nil {
		return i
	} else {
		return nil
	}
}
func (p CommandLine) GetPgpFingerprint() *libkb.PgpFingerprint {
	return libkb.PgpFingerprintFromHexNoError(p.GetGString("fingerprint"))
}
func (p CommandLine) GetEmail() string {
	return p.GetGString("email")
}
func (p CommandLine) GetProxy() string {
	return p.GetGString("proxy")
}
func (p CommandLine) GetPlainLogging() (bool, bool) {
	return p.GetBool("plain-logging", true)
}
func (p CommandLine) GetPgpDir() string {
	return p.GetGString("pgpdir")
}
func (p CommandLine) GetApiDump() (bool, bool) {
	return p.GetBool("api-dump", true)
}
func (p CommandLine) GetPinentry() string {
	return p.GetGString("pinentry")
}
func (p CommandLine) GetGString(s string) string {
	return p.ctx.GlobalString(s)
}
func (p CommandLine) GetGInt(s string) int {
	return p.ctx.GlobalInt(s)
}
func (p CommandLine) GetGpg() string {
	return p.GetGString("gpg")
}
func (p CommandLine) GetSecretKeyring() string {
	return p.GetGString("secret-keyring")
}
func (p CommandLine) GetSocketFile() string {
	return p.GetGString("socket-file")
}
func (p CommandLine) GetGpgOptions() []string {
	var ret []string
	s := p.GetGString("gpg-options")
	if len(s) > 0 {
		ret = regexp.MustCompile(`\s+`).Split(s, -1)
	}
	return ret
}
func (p CommandLine) GetMerkleKeyFingerprints() []string {
	s := p.GetGString("merkle-key-fingerprints")
	if len(s) != 0 {
		return strings.Split(s, ":")
	} else {
		return nil
	}
}
func (p CommandLine) GetUserCacheSize() (int, bool) {
	ret := p.GetGInt("user-cache-size")
	if ret != 0 {
		return ret, true
	} else {
		return 0, false
	}
}
func (p CommandLine) GetProofCacheSize() (int, bool) {
	ret := p.GetGInt("proof-cache-size")
	if ret != 0 {
		return ret, true
	} else {
		return 0, false
	}
}

func (p CommandLine) GetBool(s string, glbl bool) (bool, bool) {
	var v bool
	if glbl {
		v = p.ctx.GlobalBool(s)
	} else {
		v = p.ctx.Bool(s)
	}
	return v, v
}

type CmdBaseHelp struct {
	ctx *cli.Context
}

func (c *CmdBaseHelp) GetUsage() libkb.Usage {
	return libkb.Usage{}
}
func (c *CmdBaseHelp) ParseArgv(*cli.Context) error { return nil }

type CmdGeneralHelp struct {
	CmdBaseHelp
}

func (c *CmdBaseHelp) Run() error {
	cli.ShowAppHelp(c.ctx)
	return nil
}

type CmdSpecificHelp struct {
	CmdBaseHelp
	name string
}

func (c CmdSpecificHelp) Run() error {
	cli.ShowCommandHelp(c.ctx, c.name)
	return nil
}

func NewCommandLine() *CommandLine {
	app := cli.NewApp()
	ret := &CommandLine{app: app}
	ret.PopulateApp()
	return ret
}

func (cl *CommandLine) PopulateApp() {
	app := cl.app
	app.Name = "keybase"
	app.Version = libkb.CLIENT_VERSION
	app.Usage = "control keybase either with 1-off commands, " +
		"or start a daemon"

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "home, H",
			Usage: "specify an (alternate) home directory",
		},
		cli.StringFlag{
			Name: "server, s",
			Usage: "specify server API " +
				"(default: https://api.keybase.io:443/)",
		},
		cli.StringFlag{
			Name:  "config, c",
			Usage: "specify an (alternate) master config file",
		},
		cli.StringFlag{
			Name:  "session",
			Usage: "specify an alternate session data file",
		},
		cli.StringFlag{
			Name:  "db",
			Usage: "specify an alternate local DB location",
		},
		cli.StringFlag{
			Name:  "api-uri-path-prefix",
			Usage: "specify an alternate API URI path prefix",
		},
		cli.StringFlag{
			Name:  "username, u",
			Usage: "specify Keybase username of the current user",
		},
		cli.StringFlag{
			Name:  "uid, i",
			Usage: "specify Keybase UID for current user",
		},
		cli.StringFlag{
			Name:  "pinentry",
			Usage: "specify a path to find a pinentry program",
		},
		cli.StringFlag{
			Name:  "secret-keyring",
			Usage: "location of the Keybase secret-keyring (P3SKB-encoded)",
		},
		cli.StringFlag{
			Name:  "socket-file",
			Usage: "location of the keybased socket-file",
		},
		cli.StringFlag{
			Name: "proxy",
			Usage: "specify an HTTP(s) proxy to ship all Web " +
				"requests over",
		},
		cli.BoolFlag{
			Name:  "debug, d",
			Usage: "enable debugging mode",
		},
		cli.StringFlag{
			Name:  "email",
			Usage: "specify your email address for login/signup purposes",
		},
		cli.BoolFlag{
			Name:  "plain-logging, L",
			Usage: "plain logging mode (no colors)",
		},
		cli.StringFlag{
			Name:  "pgpdir, gpgdir",
			Usage: "specify a PGP directory (default is ~/.gnupg)",
		},
		cli.BoolFlag{
			Name:  "api-dump",
			Usage: "dump API call internals",
		},
		cli.StringFlag{
			Name:  "merkle-key-fingerprints",
			Usage: "Set of admissable Merkle Tree fingerprints (colon-separated)",
		},
		cli.IntFlag{
			Name:  "user-cache-size",
			Usage: "number of User entries to cache",
		},
		cli.IntFlag{
			Name:  "proof-cache-size",
			Usage: "number of proof entries to cache",
		},
		cli.StringFlag{
			Name:  "gpg",
			Usage: "Path to GPG client (optional for exporting keys)",
		},
		cli.StringFlag{
			Name:  "gpg-options",
			Usage: "Options to use when calling GPG",
		},
	}

	app.Commands = []cli.Command{
		NewCmdConfig(cl),
		NewCmdDb(cl),
		NewCmdId(cl),
		NewCmdListTracking(cl),
		NewCmdLogin(cl),
		NewCmdLogout(cl),
		NewCmdMykey(cl),
		NewCmdPing(cl),
		NewCmdProve(cl),
		NewCmdResolve(cl),
		NewCmdSigs(cl),
		NewCmdSign(cl),
		NewCmdSignup(cl),
		NewCmdTrack(cl),
		NewCmdVersion(cl),
	}

	// Finally, add a default help action...
	app.Action = func(c *cli.Context) {
		cl.cmd = &CmdGeneralHelp{CmdBaseHelp{c}}
		cl.ctx = c
		cl.name = "help"
	}
}

// Called back from inside our subcommands, when they're picked...
func (p *CommandLine) ChooseCommand(cmd Command, name string, ctx *cli.Context) {
	p.cmd = cmd
	p.name = name
	p.ctx = ctx
}

func (p *CommandLine) Parse(args []string) (cmd Command, err error) {

	// Actually pick a command
	err = p.app.Run(args)

	// Should not be populated
	cmd = p.cmd

	if err != nil || cmd == nil {
		return
	}

	// If we failed to parse arguments properly, switch to the help command
	if err = p.cmd.ParseArgv(p.ctx); err != nil {
		G.Log.Error("In '%s': %s", p.name, err.Error())
		cmd = &CmdSpecificHelp{CmdBaseHelp{p.ctx}, p.name}
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/nsf/termbox-go"
	"log"
	"math/rand"
	"strconv"
	"time"
)

const (
	ColorWall  = termbox.ColorRed
	ColorEmpty = termbox.ColorBlack
	ColorSnake = termbox.ColorGreen
	ColorFood  = termbox.ColorYellow
)

type Cell struct {
	X     int
	Y     int
	Clear bool
	Color termbox.Attribute
}

type Segment struct {
	X   int
	Y   int
	Dir int
}

func gameOver(snake []Segment, board [][]Cell) {
	for i := range snake {
		termbox.SetCell(snake[i].X, snake[i].Y, 0x0020, termbox.ColorBlack, termbox.ColorRed)
	}
	termbox.Flush()
	return
}

func renderScore(score uint64) {
	scoreStr := strconv.FormatUint(score, 10)
	for i := range scoreStr {
		termbox.SetCell(i, 0, rune(scoreStr[i]), termbox.ColorBlack, ColorWall)
	}
}

func initGame() ([]Segment, [][]Cell) {
	boardSizeY := 40
	boardSizeX := boardSizeY * 2
	termSizeX, termSizeY := termbox.Size()
	if termSizeY < boardSizeY {
		boardSizeY = termSizeY
	}
	if termSizeX < boardSizeX {
		boardSizeX = termSizeX
	}
	board := make([][]Cell, boardSizeY)
	for y := range board {
		board[y] = make([]Cell, boardSizeX)
		for x := range board[y] {
			if x == 0 || y == 0 || x == (boardSizeX)-1 || y == boardSizeY-1 {
				board[y][x] = Cell{x, y, false, ColorWall}
			} else {
				board[y][x] = Cell{x, y, true, ColorEmpty}
			}
		}
	}
	snake := make([]Segment, 7, 1024)
	for i := range snake {
		snake[i] = Segment{40 - i, 20, 1}
		board[20][40-i].Color = ColorSnake
		board[20][40-i].Clear = false
	}
	for _, row := range board {
		for _, cell := range row {
			termbox.SetCell(cell.X, cell.Y, 0x0020, termbox.ColorBlack, cell.Color)
		}
	}
	termbox.Flush()
	return snake, board
}

func moveSnake(snake []Segment, board [][]Cell, lastDir *int) {
	score := uint64(0)
	renderScore(score)
	ticker := time.NewTicker(100 * time.Millisecond)
	food := false
	for {
		if !food {
			randX := rand.Intn(len(board[0]))
			randY := rand.Intn(len(board))
			for !board[randY][randX].Clear {
				randX = rand.Intn(len(board[0]))
				randY = rand.Intn(len(board))
			}
			board[randY][randX].Clear = false
			board[randY][randX].Color = ColorFood
			termbox.SetCell(randX, randY, 0x0020, termbox.ColorBlack, ColorFood)
			food = true
		}
		for i := 0; i < len(snake); i++ {
			if i == len(snake)-1 {
				board[snake[i].Y][snake[i].X].Color = ColorEmpty
				board[snake[i].Y][snake[i].X].Clear = true
				termbox.SetCell(snake[i].X, snake[i].Y, 0x0020, termbox.ColorBlack, ColorEmpty)
			}
			switch snake[i].Dir {
			case 0:
				snake[i].Y = snake[i].Y - 1
				break
			case 1:
				snake[i].X = snake[i].X + 1
				break
			case 2:
				snake[i].Y = snake[i].Y + 1
				break
			case 3:
				snake[i].X = snake[i].X - 1
				break
			}
			if i == 0 {
				if !board[snake[0].Y][snake[0].X].Clear { //collision
					if board[snake[0].Y][snake[0].X].Color == ColorFood { //with food
						food = false
						newX := snake[len(snake)-1].X
						newY := snake[len(snake)-1].Y
						switch snake[len(snake)-1].Dir {
						case 0:
							newY = newY + 1
							break
						case 1:
							newX = newX - 1
							break
						case 2:
							newY = newY - 1
							break
						case 3:
							newX = newX + 1
							break
						}
						snake = append(snake, Segment{newX, newY, snake[len(snake)-1].Dir})
						board[newY][newX].Color = ColorSnake
						board[newY][newX].Clear = false
						score++
						renderScore(score)
					} else { //with wall
						switch snake[0].Dir { //undo move of head segment
						case 0:
							snake[0].Y = snake[0].Y + 1
							break
						case 1:
							snake[0].X = snake[0].X - 1
							break
						case 2:
							snake[0].Y = snake[0].Y - 1
							break
						case 3:
							snake[0].X = snake[0].X + 1
							break
						}
						gameOver(snake, board)
						return
					}
				}
				board[snake[0].Y][snake[0].X].Color = ColorSnake
				board[snake[0].Y][snake[0].X].Clear = false
				termbox.SetCell(snake[0].X, snake[0].Y, 0x0020, termbox.ColorBlack, ColorSnake)
			}
		}
		for j := len(snake) - 1; j > 0; j-- {
			snake[j].Dir = snake[j-1].Dir
		}
		*lastDir = snake[0].Dir
		termbox.Flush()
		<-ticker.C
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	err := termbox.Init()
	if err != nil {
		log.Panicln(err)
	}
	defer termbox.Close()
	termbox.SetInputMode(termbox.InputEsc)

	snake, board := initGame()
	lastDir := snake[0].Dir
	go moveSnake(snake, board, &lastDir)
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			if ev.Ch == 0 { //not letter key
				switch ev.Key {
				case termbox.KeyArrowUp:
					if lastDir != 2 {
						snake[0].Dir = 0
					}
					break
				case termbox.KeyArrowRight:
					if lastDir != 3 {
						snake[0].Dir = 1
					}
					break
				case termbox.KeyArrowDown:
					if lastDir != 0 {
						snake[0].Dir = 2
					}
					break
				case termbox.KeyArrowLeft:
					if lastDir != 1 {
						snake[0].Dir = 3
					}
					break
				case termbox.KeyCtrlC:
					return
				}
			} else {
				switch ev.Ch {
				case 'w':
					if lastDir != 2 {
						snake[0].Dir = 0
					}
					break
				case 'd':
					if lastDir != 3 {
						snake[0].Dir = 1
					}
					break
				case 's':
					if lastDir != 0 {
						snake[0].Dir = 2
					}
					break
				case 'a':
					if lastDir != 1 {
						snake[0].Dir = 3
					}
					break
				}
			}
			break
		case termbox.EventError:
			log.Panic(ev.Err)
			break
		}
	}
}
<commit_msg>added new game (hit n after game ends), put game logic in own function two keys must be hit after game ends as PollEvent() is hit before gameOverC has data in it<commit_after>package main

import (
	"github.com/nsf/termbox-go"
	"log"
	"math/rand"
	"strconv"
	"time"
)

const (
	ColorWall  = termbox.ColorRed
	ColorEmpty = termbox.ColorBlack
	ColorSnake = termbox.ColorGreen
	ColorFood  = termbox.ColorYellow
)

type Cell struct {
	X     int
	Y     int
	Clear bool
	Color termbox.Attribute
}

type Segment struct {
	X   int
	Y   int
	Dir int
}

func gameOver(snake []Segment, board [][]Cell) {
	for i := range snake {
		termbox.SetCell(snake[i].X, snake[i].Y, 0x0020, termbox.ColorBlack, termbox.ColorRed)
	}
	termbox.Flush()
	return
}

func renderScore(score uint64) {
	scoreStr := strconv.FormatUint(score, 10)
	for i := range scoreStr {
		termbox.SetCell(i, 0, rune(scoreStr[i]), termbox.ColorBlack, ColorWall)
	}
}

func initGame() ([]Segment, [][]Cell) {
	boardSizeY := 40
	boardSizeX := boardSizeY * 2
	termSizeX, termSizeY := termbox.Size()
	if termSizeY < boardSizeY {
		boardSizeY = termSizeY
	}
	if termSizeX < boardSizeX {
		boardSizeX = termSizeX
	}
	board := make([][]Cell, boardSizeY)
	for y := range board {
		board[y] = make([]Cell, boardSizeX)
		for x := range board[y] {
			if x == 0 || y == 0 || x == (boardSizeX)-1 || y == boardSizeY-1 {
				board[y][x] = Cell{x, y, false, ColorWall}
			} else {
				board[y][x] = Cell{x, y, true, ColorEmpty}
			}
		}
	}
	snake := make([]Segment, 7, 1024)
	for i := range snake {
		snake[i] = Segment{40 - i, 20, 1}
		board[20][40-i].Color = ColorSnake
		board[20][40-i].Clear = false
	}
	for _, row := range board {
		for _, cell := range row {
			termbox.SetCell(cell.X, cell.Y, 0x0020, termbox.ColorBlack, cell.Color)
		}
	}
	termbox.Flush()
	return snake, board
}

func moveSnake(snake []Segment, board [][]Cell, lastDir *int, gameOverC chan bool) {
	score := uint64(0)
	renderScore(score)
	ticker := time.NewTicker(100 * time.Millisecond)
	food := false
	for {
		if !food {
			randX := rand.Intn(len(board[0]))
			randY := rand.Intn(len(board))
			for !board[randY][randX].Clear {
				randX = rand.Intn(len(board[0]))
				randY = rand.Intn(len(board))
			}
			board[randY][randX].Clear = false
			board[randY][randX].Color = ColorFood
			termbox.SetCell(randX, randY, 0x0020, termbox.ColorBlack, ColorFood)
			food = true
		}
		for i := 0; i < len(snake); i++ {
			if i == len(snake)-1 {
				board[snake[i].Y][snake[i].X].Color = ColorEmpty
				board[snake[i].Y][snake[i].X].Clear = true
				termbox.SetCell(snake[i].X, snake[i].Y, 0x0020, termbox.ColorBlack, ColorEmpty)
			}
			switch snake[i].Dir {
			case 0:
				snake[i].Y = snake[i].Y - 1
				break
			case 1:
				snake[i].X = snake[i].X + 1
				break
			case 2:
				snake[i].Y = snake[i].Y + 1
				break
			case 3:
				snake[i].X = snake[i].X - 1
				break
			}
			if i == 0 {
				if !board[snake[0].Y][snake[0].X].Clear { //collision
					if board[snake[0].Y][snake[0].X].Color == ColorFood { //with food
						food = false
						newX := snake[len(snake)-1].X
						newY := snake[len(snake)-1].Y
						switch snake[len(snake)-1].Dir {
						case 0:
							newY = newY + 1
							break
						case 1:
							newX = newX - 1
							break
						case 2:
							newY = newY - 1
							break
						case 3:
							newX = newX + 1
							break
						}
						snake = append(snake, Segment{newX, newY, snake[len(snake)-1].Dir})
						board[newY][newX].Color = ColorSnake
						board[newY][newX].Clear = false
						score++
						renderScore(score)
					} else { //with wall
						switch snake[0].Dir { //undo move of head segment
						case 0:
							snake[0].Y = snake[0].Y + 1
							break
						case 1:
							snake[0].X = snake[0].X - 1
							break
						case 2:
							snake[0].Y = snake[0].Y - 1
							break
						case 3:
							snake[0].X = snake[0].X + 1
							break
						}
						gameOver(snake, board)
						gameOverC <- true
						return
					}
				}
				board[snake[0].Y][snake[0].X].Color = ColorSnake
				board[snake[0].Y][snake[0].X].Clear = false
				termbox.SetCell(snake[0].X, snake[0].Y, 0x0020, termbox.ColorBlack, ColorSnake)
			}
		}
		for j := len(snake) - 1; j > 0; j-- {
			snake[j].Dir = snake[j-1].Dir
		}
		*lastDir = snake[0].Dir
		termbox.Flush()
		<-ticker.C
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	err := termbox.Init()
	if err != nil {
		log.Panicln(err)
	}
	defer termbox.Close()
	termbox.SetInputMode(termbox.InputEsc)

	OutsideGameLoop: for {
		gameLoop()
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			if ev.Ch != 0 { //letter key
				switch ev.Ch {
				case 'q':
					return
				case 'n':
					continue OutsideGameLoop
				}
			} else {
				switch ev.Key {
				case termbox.KeyCtrlC:
					return
				}
			}
		case termbox.EventError:
			log.Panic(ev.Err)
			break
		}
	}
}

func gameLoop() {
	snake, board := initGame()
	lastDir := snake[0].Dir
	gameOverC := make(chan bool, 1)
	go moveSnake(snake, board, &lastDir, gameOverC)
	for {
		select {
		case <-gameOverC:
			return
		default:
			switch ev := termbox.PollEvent(); ev.Type {
			case termbox.EventKey:
				if ev.Ch == 0 { //not letter key
					switch ev.Key {
					case termbox.KeyArrowUp:
						if lastDir != 2 {
							snake[0].Dir = 0
						}
						break
					case termbox.KeyArrowRight:
						if lastDir != 3 {
							snake[0].Dir = 1
						}
						break
					case termbox.KeyArrowDown:
						if lastDir != 0 {
							snake[0].Dir = 2
						}
						break
					case termbox.KeyArrowLeft:
						if lastDir != 1 {
							snake[0].Dir = 3
						}
						break
					case termbox.KeyCtrlC:
						return
					}
				} else {
					switch ev.Ch {
					case 'w':
						if lastDir != 2 {
							snake[0].Dir = 0
						}
						break
					case 'd':
						if lastDir != 3 {
							snake[0].Dir = 1
						}
						break
					case 's':
						if lastDir != 0 {
							snake[0].Dir = 2
						}
						break
					case 'a':
						if lastDir != 1 {
							snake[0].Dir = 3
						}
						break
					}
				}
				break
			case termbox.EventError:
				log.Panic(ev.Err)
				break
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"crypto/md5"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/brotherlogic/goserver"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	pbd "github.com/brotherlogic/discovery/proto"
	pbgh "github.com/brotherlogic/githubcard/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
	pbs "github.com/brotherlogic/goserver/proto"
	"github.com/brotherlogic/goserver/utils"
)

// Server the main server type
type Server struct {
	*goserver.GoServer
	runner     *Runner
	disk       diskChecker
	jobs       map[string]*pb.JobDetails
	njobs      map[string]*pb.JobAssignment
	translator translator
	scheduler  *Scheduler
}

func deliverCrashReport(job *runnerCommand, getter func(name string) (string, int), logger func(text string)) {
	ip, port := getter("githubcard")
	if port > 0 {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		conn, err := grpc.Dial(ip+":"+strconv.Itoa(port), grpc.WithInsecure())
		if err == nil {
			defer conn.Close()
			client := pbgh.NewGithubClient(conn)
			elems := strings.Split(job.details.Spec.GetName(), "/")
			if len(job.output) > 0 {
				client.AddIssue(ctx, &pbgh.Issue{Service: elems[len(elems)-1], Title: "CRASH REPORT", Body: job.output}, grpc.FailFast(false))
			}
		}
	}
}

func (s *Server) addMessage(details *pb.JobDetails, message string) {
	for _, t := range s.runner.backgroundTasks {
		if t.details.GetSpec().Name == details.Spec.Name {
			t.output += message
		}
	}
}

func (s *Server) nmonitor(job *pb.JobAssignment) {
	for job.State != pb.State_DEAD {
		time.Sleep(time.Second)
		s.runTransition(job)
	}
}

func (s *Server) monitor(job *pb.JobDetails) {
	for true {
		switch job.State {
		case pb.State_ACKNOWLEDGED:
			job.StartTime = 0
			job.GetSpec().Port = 0
			job.State = pb.State_BUILDING
			s.runner.Checkout(job.GetSpec().Name)
			job.State = pb.State_BUILT
		case pb.State_BUILT:
			s.runner.Run(job)
			for job.StartTime == 0 {
				time.Sleep(waitTime)
			}
			job.State = pb.State_PENDING
		case pb.State_KILLING:
			s.runner.kill(job)
			if !isAlive(job.GetSpec()) {
				job.State = pb.State_DEAD
			}
		case pb.State_UPDATE_STARTING:
			s.runner.Update(job)
			job.State = pb.State_UPDATE_STARTING
		case pb.State_PENDING:
			time.Sleep(time.Minute)
			if isAlive(job.GetSpec()) {
				job.State = pb.State_RUNNING
			} else {
				job.State = pb.State_DEAD
			}
		case pb.State_RUNNING:
			time.Sleep(waitTime)
			if !isAlive(job.GetSpec()) {
				job.TestCount++
			} else {
				job.TestCount = 0
			}
			if job.TestCount > 60 {
				s.Log(fmt.Sprintf("Killing beacuse we couldn't reach 60 times: %v", job))
				job.State = pb.State_DEAD
			}
		case pb.State_DEAD:
			job.State = pb.State_ACKNOWLEDGED
		}
	}
}

func getHash(file string) (string, error) {
	env := os.Environ()
	home := ""
	for _, s := range env {
		if strings.HasPrefix(s, "HOME=") {
			home = s[5:]
		}
	}

	gpath := home + "/gobuild"

	f, err := os.Open(strings.Replace(file, "$GOPATH", gpath, 1))
	if err != nil {
		return "", err
	}
	defer f.Close()

	h := md5.New()
	if _, err := io.Copy(h, f); err != nil {
		return "", err
	}

	return string(h.Sum(nil)), nil
}

func getIP(name string, server string) (string, int32, error) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()
	conn, err := grpc.Dial(utils.RegistryIP+":"+strconv.Itoa(utils.RegistryPort), grpc.WithInsecure())
	defer conn.Close()

	if err != nil {
		return "", -1, err
	}

	registry := pbd.NewDiscoveryServiceClient(conn)
	entry := pbd.RegistryEntry{Name: name, Identifier: server}
	r, err := registry.Discover(ctx, &pbd.DiscoverRequest{Request: &entry}, grpc.FailFast(false))

	if err != nil {
		return "", -1, err
	}

	return r.GetService().Ip, r.GetService().Port, nil
}

// updateState of the runner command
func isAlive(spec *pb.JobSpec) bool {
	elems := strings.Split(spec.Name, "/")
	if spec.GetPort() == 0 {
		dServer, dPort, err := getIP(elems[len(elems)-1], spec.Server)

		e, ok := status.FromError(err)
		if ok && e.Code() == codes.DeadlineExceeded {
			//Ignore deadline exceeds on discover
			return true
		}

		if err != nil {
			return false
		}

		spec.Host = dServer
		spec.Port = dPort
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	dConn, err := grpc.Dial(spec.Host+":"+strconv.Itoa(int(spec.Port)), grpc.WithInsecure())
	if err != nil {
		return false
	}
	defer dConn.Close()

	c := pbs.NewGoserverServiceClient(dConn)
	resp, err := c.IsAlive(ctx, &pbs.Alive{}, grpc.FailFast(false))

	if err != nil || resp.Name != elems[len(elems)-1] {
		e, ok := status.FromError(err)
		if ok && e.Code() != codes.Unavailable {
			return false
		}
	}

	return true
}

// DoRegister Registers this server
func (s Server) DoRegister(server *grpc.Server) {
	pb.RegisterGoBuildSlaveServer(server, &s)
	pb.RegisterBuildSlaveServer(server, &s)
}

// ReportHealth determines if the server is healthy
func (s Server) ReportHealth() bool {
	return true
}

// Mote promotes/demotes this server
func (s Server) Mote(master bool) error {
	return nil
}

// GetState gets the state of the server
func (s Server) GetState() []*pbs.State {
	return []*pbs.State{}
}

//Init builds the default runner framework
func Init() *Runner {
	r := &Runner{gopath: "goautobuild", m: &sync.Mutex{}, bm: &sync.Mutex{}}
	r.runner = runCommand
	go r.run()
	return r
}

func runCommand(c *runnerCommand) {
	if c == nil || c.command == nil {
		return
	}

	env := os.Environ()
	home := ""
	for _, s := range env {
		if strings.HasPrefix(s, "HOME=") {
			home = s[5:]
		}
	}

	gpath := home + "/gobuild"
	c.command.Path = strings.Replace(c.command.Path, "$GOPATH", gpath, -1)
	for i := range c.command.Args {
		c.command.Args[i] = strings.Replace(c.command.Args[i], "$GOPATH", gpath, -1)
	}

	path := fmt.Sprintf("GOPATH=" + home + "/gobuild/")
	pathbin := fmt.Sprintf("GOBIN=" + home + "/gobuild/bin/")
	found := false
	envl := os.Environ()
	for i, blah := range envl {
		if strings.HasPrefix(blah, "GOPATH") {
			envl[i] = path
			found = true
		}
		if strings.HasPrefix(blah, "GOBIN") {
			envl[i] = pathbin
			found = true
		}
	}
	if !found {
		envl = append(envl, path)
	}
	c.command.Env = envl

	out, err := c.command.StderrPipe()
	if err != nil {
		log.Fatalf("Problem getting stderr: %v", err)
	}

	if out != nil {
		scanner := bufio.NewScanner(out)
		go func() {
			for scanner != nil && scanner.Scan() {
				c.output += scanner.Text()
			}
			out.Close()
		}()
	}

	c.command.Start()

	if !c.background {
		c.command.Wait()
		c.complete = true
	} else {
		c.details.StartTime = time.Now().Unix()
	}
}

func (diskChecker prodDiskChecker) diskUsage(path string) int64 {
	return diskUsage(path)
}

func (s *Server) rebuildLoop() {
	for true {
		time.Sleep(time.Minute * 60)

		var rebuildList []*pb.JobDetails
		var hashList []string
		for _, job := range s.runner.backgroundTasks {
			if time.Since(job.started) > time.Hour {
				rebuildList = append(rebuildList, job.details)
				hashList = append(hashList, job.hash)
			}
		}

		for i := range rebuildList {
			s.runner.Rebuild(rebuildList[i], hashList[i])
		}
	}
}

type pTranslator struct{}

func (p *pTranslator) build(job *pb.Job) *exec.Cmd {
	return exec.Command("go", "get", job.Name)
}

func main() {
	var quiet = flag.Bool("quiet", false, "Show all output")
	flag.Parse()

	if *quiet {
		log.SetFlags(0)
		log.SetOutput(ioutil.Discard)
	}

	s := Server{&goserver.GoServer{}, Init(), prodDiskChecker{}, make(map[string]*pb.JobDetails), make(map[string]*pb.JobAssignment), &pTranslator{}, &Scheduler{cMutex: &sync.Mutex{}}}
	s.runner.getip = s.GetIP
	s.runner.logger = s.Log
	s.Register = s
	s.PrepServer()
	s.GoServer.Killme = false
	s.RegisterServingTask(s.rebuildLoop)
	s.RegisterServer("gobuildslave", false)
	err := s.Serve()
	log.Fatalf("Unable to serve: %v", err)
}
<commit_msg>Gets dependencies on build<commit_after>package main

import (
	"bufio"
	"crypto/md5"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/brotherlogic/goserver"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	pbd "github.com/brotherlogic/discovery/proto"
	pbgh "github.com/brotherlogic/githubcard/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
	pbs "github.com/brotherlogic/goserver/proto"
	"github.com/brotherlogic/goserver/utils"
)

// Server the main server type
type Server struct {
	*goserver.GoServer
	runner     *Runner
	disk       diskChecker
	jobs       map[string]*pb.JobDetails
	njobs      map[string]*pb.JobAssignment
	translator translator
	scheduler  *Scheduler
}

func deliverCrashReport(job *runnerCommand, getter func(name string) (string, int), logger func(text string)) {
	ip, port := getter("githubcard")
	if port > 0 {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		conn, err := grpc.Dial(ip+":"+strconv.Itoa(port), grpc.WithInsecure())
		if err == nil {
			defer conn.Close()
			client := pbgh.NewGithubClient(conn)
			elems := strings.Split(job.details.Spec.GetName(), "/")
			if len(job.output) > 0 {
				client.AddIssue(ctx, &pbgh.Issue{Service: elems[len(elems)-1], Title: "CRASH REPORT", Body: job.output}, grpc.FailFast(false))
			}
		}
	}
}

func (s *Server) addMessage(details *pb.JobDetails, message string) {
	for _, t := range s.runner.backgroundTasks {
		if t.details.GetSpec().Name == details.Spec.Name {
			t.output += message
		}
	}
}

func (s *Server) nmonitor(job *pb.JobAssignment) {
	for job.State != pb.State_DEAD {
		time.Sleep(time.Second)
		s.runTransition(job)
	}
}

func (s *Server) monitor(job *pb.JobDetails) {
	for true {
		switch job.State {
		case pb.State_ACKNOWLEDGED:
			job.StartTime = 0
			job.GetSpec().Port = 0
			job.State = pb.State_BUILDING
			s.runner.Checkout(job.GetSpec().Name)
			job.State = pb.State_BUILT
		case pb.State_BUILT:
			s.runner.Run(job)
			for job.StartTime == 0 {
				time.Sleep(waitTime)
			}
			job.State = pb.State_PENDING
		case pb.State_KILLING:
			s.runner.kill(job)
			if !isAlive(job.GetSpec()) {
				job.State = pb.State_DEAD
			}
		case pb.State_UPDATE_STARTING:
			s.runner.Update(job)
			job.State = pb.State_UPDATE_STARTING
		case pb.State_PENDING:
			time.Sleep(time.Minute)
			if isAlive(job.GetSpec()) {
				job.State = pb.State_RUNNING
			} else {
				job.State = pb.State_DEAD
			}
		case pb.State_RUNNING:
			time.Sleep(waitTime)
			if !isAlive(job.GetSpec()) {
				job.TestCount++
			} else {
				job.TestCount = 0
			}
			if job.TestCount > 60 {
				s.Log(fmt.Sprintf("Killing beacuse we couldn't reach 60 times: %v", job))
				job.State = pb.State_DEAD
			}
		case pb.State_DEAD:
			job.State = pb.State_ACKNOWLEDGED
		}
	}
}

func getHash(file string) (string, error) {
	env := os.Environ()
	home := ""
	for _, s := range env {
		if strings.HasPrefix(s, "HOME=") {
			home = s[5:]
		}
	}

	gpath := home + "/gobuild"

	f, err := os.Open(strings.Replace(file, "$GOPATH", gpath, 1))
	if err != nil {
		return "", err
	}
	defer f.Close()

	h := md5.New()
	if _, err := io.Copy(h, f); err != nil {
		return "", err
	}

	return string(h.Sum(nil)), nil
}

func getIP(name string, server string) (string, int32, error) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()
	conn, err := grpc.Dial(utils.RegistryIP+":"+strconv.Itoa(utils.RegistryPort), grpc.WithInsecure())
	defer conn.Close()

	if err != nil {
		return "", -1, err
	}

	registry := pbd.NewDiscoveryServiceClient(conn)
	entry := pbd.RegistryEntry{Name: name, Identifier: server}
	r, err := registry.Discover(ctx, &pbd.DiscoverRequest{Request: &entry}, grpc.FailFast(false))

	if err != nil {
		return "", -1, err
	}

	return r.GetService().Ip, r.GetService().Port, nil
}

// updateState of the runner command
func isAlive(spec *pb.JobSpec) bool {
	elems := strings.Split(spec.Name, "/")
	if spec.GetPort() == 0 {
		dServer, dPort, err := getIP(elems[len(elems)-1], spec.Server)

		e, ok := status.FromError(err)
		if ok && e.Code() == codes.DeadlineExceeded {
			//Ignore deadline exceeds on discover
			return true
		}

		if err != nil {
			return false
		}

		spec.Host = dServer
		spec.Port = dPort
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	dConn, err := grpc.Dial(spec.Host+":"+strconv.Itoa(int(spec.Port)), grpc.WithInsecure())
	if err != nil {
		return false
	}
	defer dConn.Close()

	c := pbs.NewGoserverServiceClient(dConn)
	resp, err := c.IsAlive(ctx, &pbs.Alive{}, grpc.FailFast(false))

	if err != nil || resp.Name != elems[len(elems)-1] {
		e, ok := status.FromError(err)
		if ok && e.Code() != codes.Unavailable {
			return false
		}
	}

	return true
}

// DoRegister Registers this server
func (s Server) DoRegister(server *grpc.Server) {
	pb.RegisterGoBuildSlaveServer(server, &s)
	pb.RegisterBuildSlaveServer(server, &s)
}

// ReportHealth determines if the server is healthy
func (s Server) ReportHealth() bool {
	return true
}

// Mote promotes/demotes this server
func (s Server) Mote(master bool) error {
	return nil
}

// GetState gets the state of the server
func (s Server) GetState() []*pbs.State {
	return []*pbs.State{}
}

//Init builds the default runner framework
func Init() *Runner {
	r := &Runner{gopath: "goautobuild", m: &sync.Mutex{}, bm: &sync.Mutex{}}
	r.runner = runCommand
	go r.run()
	return r
}

func runCommand(c *runnerCommand) {
	if c == nil || c.command == nil {
		return
	}

	env := os.Environ()
	home := ""
	for _, s := range env {
		if strings.HasPrefix(s, "HOME=") {
			home = s[5:]
		}
	}

	gpath := home + "/gobuild"
	c.command.Path = strings.Replace(c.command.Path, "$GOPATH", gpath, -1)
	for i := range c.command.Args {
		c.command.Args[i] = strings.Replace(c.command.Args[i], "$GOPATH", gpath, -1)
	}

	path := fmt.Sprintf("GOPATH=" + home + "/gobuild/")
	pathbin := fmt.Sprintf("GOBIN=" + home + "/gobuild/bin/")
	found := false
	envl := os.Environ()
	for i, blah := range envl {
		if strings.HasPrefix(blah, "GOPATH") {
			envl[i] = path
			found = true
		}
		if strings.HasPrefix(blah, "GOBIN") {
			envl[i] = pathbin
			found = true
		}
	}
	if !found {
		envl = append(envl, path)
	}
	c.command.Env = envl

	out, err := c.command.StderrPipe()
	if err != nil {
		log.Fatalf("Problem getting stderr: %v", err)
	}

	if out != nil {
		scanner := bufio.NewScanner(out)
		go func() {
			for scanner != nil && scanner.Scan() {
				c.output += scanner.Text()
			}
			out.Close()
		}()
	}

	c.command.Start()

	if !c.background {
		c.command.Wait()
		c.complete = true
	} else {
		c.details.StartTime = time.Now().Unix()
	}
}

func (diskChecker prodDiskChecker) diskUsage(path string) int64 {
	return diskUsage(path)
}

func (s *Server) rebuildLoop() {
	for true {
		time.Sleep(time.Minute * 60)

		var rebuildList []*pb.JobDetails
		var hashList []string
		for _, job := range s.runner.backgroundTasks {
			if time.Since(job.started) > time.Hour {
				rebuildList = append(rebuildList, job.details)
				hashList = append(hashList, job.hash)
			}
		}

		for i := range rebuildList {
			s.runner.Rebuild(rebuildList[i], hashList[i])
		}
	}
}

type pTranslator struct{}

func (p *pTranslator) build(job *pb.Job) *exec.Cmd {
	return exec.Command("go", "get", "-u", job.Name)
}

func main() {
	var quiet = flag.Bool("quiet", false, "Show all output")
	flag.Parse()

	if *quiet {
		log.SetFlags(0)
		log.SetOutput(ioutil.Discard)
	}

	s := Server{&goserver.GoServer{}, Init(), prodDiskChecker{}, make(map[string]*pb.JobDetails), make(map[string]*pb.JobAssignment), &pTranslator{}, &Scheduler{cMutex: &sync.Mutex{}}}
	s.runner.getip = s.GetIP
	s.runner.logger = s.Log
	s.Register = s
	s.PrepServer()
	s.GoServer.Killme = false
	s.RegisterServingTask(s.rebuildLoop)
	s.RegisterServer("gobuildslave", false)
	err := s.Serve()
	log.Fatalf("Unable to serve: %v", err)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/mikee385/GolangRayTracer/GolangRayTracer/color"
	"github.com/mikee385/GolangRayTracer/GolangRayTracer/table"
)

func main() {
	var red = color.New(1.0, 0.0, 0.0)
	var green = color.New(0.0, 1.0, 0.0)
	var result = red.Add(green)
	fmt.Println(result.Red, result.Green, result.Blue)

	fmt.Println(color.New(0.5, 0.5, 0.5))

	fmt.Println(color.White())

	var table = table.New(8, 6)
	for row := 0; row < table.GetHeight(); row++ {
		for column := 0; column < table.GetWidth(); column++ {
			table.Set(row, column, color.Green())
		}
	}
	for row := 0; row < table.GetHeight(); row++ {
		for column := 0; column < table.GetWidth(); column++ {
			fmt.Printf("%v, %v = %v\n", row, column, table.Get(row, column))
		}
	}
}
<commit_msg>Updates `main` to create the first scene spheres.<commit_after>package main

import (
	"github.com/mikee385/GolangRayTracer/GolangRayTracer/color"
	"github.com/mikee385/GolangRayTracer/GolangRayTracer/geometry"
	"github.com/mikee385/GolangRayTracer/GolangRayTracer/material"
	"github.com/mikee385/GolangRayTracer/GolangRayTracer/scene"
)

func main() {
	var width = 640
	var height = 480

	var groundSphere = scene.NewSphere(geometry.NewPoint(0.0, -10004.0, 20.0), 10000.0, material.NewBuilder().
		Color(color.New(0.2, 0.2, 0.2)).
		Diffuse(1.0).
		Specular(0.0).
		Shininess(0).
		Reflection(0.0).
		Refraction(0.0).
		RefractiveIndex(0.0).
		ToMaterial())

	var sphere1 = scene.NewSphere(geometry.NewPoint(0.0, 0.0, 20.0), 4.0, material.NewBuilder().
		Color(color.New(1.0, 0.32, 0.36)).
		Diffuse(1.0).
		Specular(0.0).
		Shininess(0).
		Reflection(1.0).
		Refraction(0.5).
		RefractiveIndex(1.1).
		ToMaterial())

	var sphere2 = scene.NewSphere(geometry.NewPoint(5.0, -1.0, 15.0), 2.0, material.NewBuilder().
		Color(color.New(0.9, 0.76, 0.46)).
		Diffuse(1.0).
		Specular(0.0).
		Shininess(0).
		Reflection(1.0).
		Refraction(0.0).
		RefractiveIndex(0.0).
		ToMaterial())

	var sphere3 = scene.NewSphere(geometry.NewPoint(5.0, 0.0, 25.0), 3.0, material.NewBuilder().
		Color(color.New(0.65, 0.77, 0.97)).
		Diffuse(1.0).
		Specular(0.0).
		Shininess(0).
		Reflection(1.0).
		Refraction(0.0).
		RefractiveIndex(0.0).
		ToMaterial())

	var sphere4 = scene.NewSphere(geometry.NewPoint(-5.5, 0.0, 15.0), 3.0, material.NewBuilder().
		Color(color.New(0.90, 0.90, 0.90)).
		Diffuse(1.0).
		Specular(0.0).
		Shininess(0).
		Reflection(1.0).
		Refraction(0.0).
		RefractiveIndex(0.0).
		ToMaterial())
}
<|endoftext|>