<commit_before>package main

import (
	"errors"
	"fmt"
	"net"
	"strings"
	"sync"
	"time"
)

// the Client struct represent the information required to known about the client connection
type Client struct {
	sync.RWMutex
	MessageIds
	clientId         string
	conn             net.Conn
	rootNode         *Node
	keepAlive        uint
	connected        bool
	topicSpace       string
	outboundMessages chan ControlPacket
	sendStop         chan bool
	recvStop         chan bool
	lastSeen         time.Time
	cleanSession     bool
}

func NewClient(conn net.Conn, clientId string) *Client {
	//if !validateClientId(clientId) {
	//	return nil, errors.New("Invalid Client Id")
	//}
	c := &Client{}
	c.conn = conn
	c.clientId = clientId
	// c.subscriptions = list.New()
	c.sendStop = make(chan bool)
	c.recvStop = make(chan bool)
	c.outboundMessages = make(chan ControlPacket)
	c.rootNode = rootNode

	return c
}

func (c *Client) Remove() {
	if c.cleanSession {
		delete(clients, c.clientId)
	}
}

func (c *Client) Stop() {
	c.sendStop <- true
	c.recvStop <- true
	c.conn.Close()
}

func (c *Client) Start() {
	go c.Receive()
	go c.Send()
}

func (c *Client) resetLastSeenTime() {
	c.lastSeen = time.Now()
}

func validateClientId(clientId string) bool {
	return true
}

func (c *Client) SetRootNode(node *Node) {
	c.rootNode = node
}

func (c *Client) AddSubscription(topic string, qos uint) {
	// for s := c.subscriptions.Front(); s != nil; s = s.Next() {
	// 	if s.Value.(*Subscription).match(subscription.topicFilter) {
	// 		s.Value = subscription
	// 		return
	// 	}
	// }
	// c.subscriptions.PushBack(subscription)
	complete := make(chan bool)
	defer close(complete)
	c.rootNode.AddSub(c, strings.Split(topic, "/"), qos, complete)
	<-complete
	return
}

func (c *Client) RemoveSubscription(topic string) (bool, error) {
	// for s := c.subscriptions.Front(); s != nil; s = s.Next() {
	// 	if s.Value.(*Subscription).match(subscription.topicFilter) {
	// 		c.subscriptions.Remove(s)
	// 		return true, nil
	// 	}
	// }
	complete := make(chan bool)
	defer close(complete)
	c.rootNode.DeleteSub(c, strings.Split(topic, "/"), complete)
	<-complete
	return true, errors.New("Topic not found")
}

func (c *Client) Receive() {
	for {
		var cph FixedHeader
		//var cp ControlPacket

		header := make([]byte, 4)
		_, err := c.conn.Read(header)
		if err != nil {
			break
		}
		cph.unpack(header)

		body := make([]byte, cph.remainingLength-(4-uint32(cph.unpack(header))))
		if cph.remainingLength > 2 {
			_, err = c.conn.Read(body)
			if err != nil {
				break
			}
		}

		switch cph.MessageType {
		case DISCONNECT:
			fmt.Println("Received DISCONNECT from", c.clientId)
			dp := New(DISCONNECT).(*disconnectPacket)
			dp.Unpack(append(header, body...))
			c.Stop()
			fmt.Println("Stop has returned")
			if c.cleanSession {
				c.Remove()
			}
			break
		case PUBLISH:
			fmt.Println("Received PUBLISH from", c.clientId)
			pp := New(PUBLISH).(*publishPacket)
			pp.Unpack(append(header, body...))
			fmt.Println(pp.String())
			c.rootNode.DeliverMessage(strings.Split(pp.topicName, "/"), pp)
			switch pp.Qos {
			case 1:
				pa := New(PUBACK).(*pubackPacket)
				pa.messageId = pp.messageId
				c.outboundMessages <- pa
			case 2:
				pr := New(PUBREC).(*pubrecPacket)
				pr.messageId = pp.messageId
				c.outboundMessages <- pr
			}
		case PUBACK:
			pa := New(PUBACK).(*pubackPacket)
			pa.Unpack(append(header, body...))
		case PUBREC:
			pr := New(PUBREC).(*pubrecPacket)
			pr.Unpack(append(header, body...))
		case PUBREL:
			pr := New(PUBREL).(*pubrelPacket)
			pr.Unpack(append(header, body...))
			pc := New(PUBCOMP).(*pubcompPacket)
			pc.messageId = pr.messageId
			c.outboundMessages <- pc
		case PUBCOMP:
			pc := New(PUBCOMP).(*pubcompPacket)
			pc.Unpack(append(header, body...))
		case SUBSCRIBE:
			fmt.Println("Received SUBSCRIBE from", c.clientId)
			sp := New(SUBSCRIBE).(*subscribePacket)
			sp.Unpack(append(header, body...))
			c.AddSubscription(sp.topics[0], sp.qoss[0])
			sa := New(SUBACK).(*subackPacket)
			sa.messageId = sp.messageId
			sa.grantedQoss = append(sa.grantedQoss, byte(sp.qoss[0]))
			c.outboundMessages <- sa
		case UNSUBSCRIBE:
			fmt.Println("Received UNSUBSCRIBE from", c.clientId)
			up := New(UNSUBSCRIBE).(*unsubscribePacket)
			up.Unpack(append(header, body...))
			c.RemoveSubscription(up.topics[0])
			ua := New(UNSUBACK).(*unsubackPacket)
			ua.messageId = up.messageId
			c.outboundMessages <- ua
		case PINGREQ:
			preq := New(PINGREQ).(*pingreqPacket)
			preq.Unpack(append(header, body...))
			presp := New(PINGRESP).(*pingrespPacket)
			c.outboundMessages <- presp
		}
	}
	select {
	case <-c.recvStop:
		fmt.Println("Requested to stop")
		return
	default:
		fmt.Println("Error on socket read")
		return
	}
}

// receive a Message object on obound, and then
// actually send outgoing message to the wire
func (c *Client) Send() {
	for {
		select {
		case msg := <-c.outboundMessages:
			fmt.Println("Outbound message on channel", msg.String())
			c.conn.Write(msg.Pack())
		case <-c.sendStop:
			fmt.Println("Requested to stop")
			return
		}
	}
	/*for {
		c.trace_v(NET, "outgoing waiting for an outbound message")
		select {
		case out := <-c.obound:
			msg := out.m
			msgtype := msg.msgType()
			c.trace_v(NET, "obound got msg to write, type: %d", msgtype)
			if msg.QoS() != QOS_ZERO && msg.MsgId() == 0 {
				msg.setMsgId(c.options.mids.getId())
			}
			if out.r != nil {
				c.receipts.put(msg.MsgId(), out.r)
			}
			msg.setTime()
			persist_obound(c.persist, msg)
			_, err := c.conn.Write(msg.Bytes())
			if err != nil {
				c.trace_e(NET, "outgoing stopped with error")
				c.errors <- err
				return
			}

			if (msg.QoS() == QOS_ZERO) &&
				(msgtype == PUBLISH || msgtype == SUBSCRIBE || msgtype == UNSUBSCRIBE) {
				c.receipts.get(msg.MsgId()) <- Receipt{}
				c.receipts.end(msg.MsgId())
			}
			c.lastContact.update()
			c.trace_v(NET, "obound wrote msg, id: %v", msg.MsgId())
		case msg := <-c.oboundP:
			msgtype := msg.msgType()
			c.trace_v(NET, "obound priority msg to write, type %d", msgtype)
			_, err := c.conn.Write(msg.Bytes())
			if err != nil {
				c.trace_e(NET, "outgoing stopped with error")
				c.errors <- err
				return
			}
			c.lastContact.update()
			if msgtype == DISCONNECT {
				c.trace_v(NET, "outbound wrote disconnect, now closing connection")
				c.conn.Close()
				return
			}
		}
	}*/
}

// receive Message objects on ibound
// store messages if necessary
// send replies on obound
// delete messages from store if necessary
/*func alllogic(c *MqttClient) {

	c.trace_v(NET, "logic started")

	for {
		c.trace_v(NET, "logic waiting for msg on ibound")

		select {
		case msg := <-c.ibound:
			c.trace_v(NET, "logic got msg on ibound, type %v", msg.msgType())
			persist_ibound(c.persist, msg)
			switch msg.msgType() {
			case PINGRESP:
				c.trace_v(NET, "received pingresp")
				c.pingOutstanding = false
			case CONNACK:
				c.trace_v(NET, "received connack")
				c.begin <- msg.connRC()
				close(c.begin)
			case SUBACK:
				c.trace_v(NET, "received suback, id: %v", msg.MsgId())
				c.receipts.get(msg.MsgId()) <- Receipt{}
				c.receipts.end(msg.MsgId())
				go c.options.mids.freeId(msg.MsgId())
			case UNSUBACK:
				c.trace_v(NET, "received unsuback, id: %v", msg.MsgId())
				c.receipts.get(msg.MsgId()) <- Receipt{}
				c.receipts.end(msg.MsgId())
				go c.options.mids.freeId(msg.MsgId())
			case PUBLISH:
				c.trace_v(NET, "received publish, msgId: %v", msg.MsgId())
				c.trace_v(NET, "putting msg on onPubChan")
				switch msg.QoS() {
				case QOS_TWO:
					c.options.pubChanTwo <- msg
					c.trace_v(NET, "done putting msg on pubChanTwo")
					pubrecMsg := newPubRecMsg()
					pubrecMsg.setMsgId(msg.MsgId())
					c.trace_v(NET, "putting pubrec msg on obound")
					c.obound <- sendable{pubrecMsg, nil}
					c.trace_v(NET, "done putting pubrec msg on obound")
				case QOS_ONE:
					c.options.pubChanOne <- msg
					c.trace_v(NET, "done putting msg on pubChanOne")
					pubackMsg := newPubAckMsg()
					pubackMsg.setMsgId(msg.MsgId())
					c.trace_v(NET, "putting puback msg on obound")
					c.obound <- sendable{pubackMsg, nil}
					c.trace_v(NET, "done putting puback msg on obound")
				case QOS_ZERO:
					c.options.pubChanZero <- msg
					c.trace_v(NET, "done putting msg on pubChanZero")
				}
			case PUBACK:
				c.trace_v(NET, "received puback, id: %v", msg.MsgId())
				c.receipts.get(msg.MsgId()) <- Receipt{}
				c.receipts.end(msg.MsgId())
				go c.options.mids.freeId(msg.MsgId())
			case PUBREC:
				c.trace_v(NET, "received pubrec, id: %v", msg.MsgId())
				id := msg.MsgId()
				pubrelMsg := newPubRelMsg()
				pubrelMsg.setMsgId(id)
				select {
				case c.obound <- sendable{pubrelMsg, nil}:
				case <-time.After(time.Second):
				}
			case PUBREL:
				c.trace_v(NET, "received pubrel, id: %v", msg.MsgId())
				pubcompMsg := newPubCompMsg()
				pubcompMsg.setMsgId(msg.MsgId())
				select {
				case c.obound <- sendable{pubcompMsg, nil}:
				case <-time.After(time.Second):
				}
			case PUBCOMP:
				c.trace_v(NET, "received pubcomp, id: %v", msg.MsgId())
				c.receipts.get(msg.MsgId()) <- Receipt{}
				c.receipts.end(msg.MsgId())
				go c.options.mids.freeId(msg.MsgId())
			}
		case <-c.stopNet:
			c.trace_w(NET, "logic stopped")
			return
		case err := <-c.errors:
			c.trace_e(NET, "logic got error")
			// clean up go routines
			c.stopPing <- true
			// incoming most likely stopped if outgoing stopped,
			// but let it know to stop anyways.
			c.stopNet <- true
			c.options.stopRouter <- true

			close(c.stopPing)
			close(c.stopNet)
			c.conn.Close()

			// Call onConnectionLost or default error handler
			go c.options.onconnlost(err)
			return
		}
	}
}*/
<commit_msg>Remove large comment sections<commit_after>package main

import (
	"errors"
	"fmt"
	"net"
	"strings"
	"sync"
	"time"
)

type Client struct {
	sync.RWMutex
	MessageIds
	clientId         string
	conn             net.Conn
	rootNode         *Node
	keepAlive        uint
	connected        bool
	topicSpace       string
	outboundMessages chan ControlPacket
	sendStop         chan bool
	recvStop         chan bool
	lastSeen         time.Time
	cleanSession     bool
}

func NewClient(conn net.Conn, clientId string) *Client {
	//if !validateClientId(clientId) {
	//	return nil, errors.New("Invalid Client Id")
	//}
	c := &Client{}
	c.conn = conn
	c.clientId = clientId
	// c.subscriptions = list.New()
	c.sendStop = make(chan bool)
	c.recvStop = make(chan bool)
	c.outboundMessages = make(chan ControlPacket)
	c.rootNode = rootNode

	return c
}

func (c *Client) Remove() {
	if c.cleanSession {
		delete(clients, c.clientId)
	}
}

func (c *Client) Stop() {
	c.sendStop <- true
	c.recvStop <- true
	c.conn.Close()
}

func (c *Client) Start() {
	go c.Receive()
	go c.Send()
}

func (c *Client) resetLastSeenTime() {
	c.lastSeen = time.Now()
}

func validateClientId(clientId string) bool {
	return true
}

func (c *Client) SetRootNode(node *Node) {
	c.rootNode = node
}

func (c *Client) AddSubscription(topic string, qos uint) {
	// for s := c.subscriptions.Front(); s != nil; s = s.Next() {
	// 	if s.Value.(*Subscription).match(subscription.topicFilter) {
	// 		s.Value = subscription
	// 		return
	// 	}
	// }
	// c.subscriptions.PushBack(subscription)
	complete := make(chan bool)
	defer close(complete)
	c.rootNode.AddSub(c, strings.Split(topic, "/"), qos, complete)
	<-complete
	return
}

func (c *Client) RemoveSubscription(topic string) (bool, error) {
	// for s := c.subscriptions.Front(); s != nil; s = s.Next() {
	// 	if s.Value.(*Subscription).match(subscription.topicFilter) {
	// 		c.subscriptions.Remove(s)
	// 		return true, nil
	// 	}
	// }
	complete := make(chan bool)
	defer close(complete)
	c.rootNode.DeleteSub(c, strings.Split(topic, "/"), complete)
	<-complete
	return true, errors.New("Topic not found")
}

func (c *Client) Receive() {
	for {
		var cph FixedHeader
		//var cp ControlPacket

		header := make([]byte, 4)
		_, err := c.conn.Read(header)
		if err != nil {
			break
		}
		cph.unpack(header)

		body := make([]byte, cph.remainingLength-(4-uint32(cph.unpack(header))))
		if cph.remainingLength > 2 {
			_, err = c.conn.Read(body)
			if err != nil {
				break
			}
		}

		switch cph.MessageType {
		case DISCONNECT:
			fmt.Println("Received DISCONNECT from", c.clientId)
			dp := New(DISCONNECT).(*disconnectPacket)
			dp.Unpack(append(header, body...))
			c.Stop()
			fmt.Println("Stop has returned")
			if c.cleanSession {
				c.Remove()
			}
			break
		case PUBLISH:
			fmt.Println("Received PUBLISH from", c.clientId)
			pp := New(PUBLISH).(*publishPacket)
			pp.Unpack(append(header, body...))
			fmt.Println(pp.String())
			c.rootNode.DeliverMessage(strings.Split(pp.topicName, "/"), pp)
			switch pp.Qos {
			case 1:
				pa := New(PUBACK).(*pubackPacket)
				pa.messageId = pp.messageId
				c.outboundMessages <- pa
			case 2:
				pr := New(PUBREC).(*pubrecPacket)
				pr.messageId = pp.messageId
				c.outboundMessages <- pr
			}
		case PUBACK:
			pa := New(PUBACK).(*pubackPacket)
			pa.Unpack(append(header, body...))
		case PUBREC:
			pr := New(PUBREC).(*pubrecPacket)
			pr.Unpack(append(header, body...))
		case PUBREL:
			pr := New(PUBREL).(*pubrelPacket)
			pr.Unpack(append(header, body...))
			pc := New(PUBCOMP).(*pubcompPacket)
			pc.messageId = pr.messageId
			c.outboundMessages <- pc
		case PUBCOMP:
			pc := New(PUBCOMP).(*pubcompPacket)
			pc.Unpack(append(header, body...))
		case SUBSCRIBE:
			fmt.Println("Received SUBSCRIBE from", c.clientId)
			sp := New(SUBSCRIBE).(*subscribePacket)
			sp.Unpack(append(header, body...))
			c.AddSubscription(sp.topics[0], sp.qoss[0])
			sa := New(SUBACK).(*subackPacket)
			sa.messageId = sp.messageId
			sa.grantedQoss = append(sa.grantedQoss, byte(sp.qoss[0]))
			c.outboundMessages <- sa
		case UNSUBSCRIBE:
			fmt.Println("Received UNSUBSCRIBE from", c.clientId)
			up := New(UNSUBSCRIBE).(*unsubscribePacket)
			up.Unpack(append(header, body...))
			c.RemoveSubscription(up.topics[0])
			ua := New(UNSUBACK).(*unsubackPacket)
			ua.messageId = up.messageId
			c.outboundMessages <- ua
		case PINGREQ:
			preq := New(PINGREQ).(*pingreqPacket)
			preq.Unpack(append(header, body...))
			presp := New(PINGRESP).(*pingrespPacket)
			c.outboundMessages <- presp
		}
	}
	select {
	case <-c.recvStop:
		fmt.Println("Requested to stop")
		return
	default:
		fmt.Println("Error on socket read")
		return
	}
}

func (c *Client) Send() {
	for {
		select {
		case msg := <-c.outboundMessages:
			fmt.Println("Outbound message on channel", msg.String())
			c.conn.Write(msg.Pack())
		case <-c.sendStop:
			fmt.Println("Requested to stop")
			return
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2015 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package wire

import (
	"fmt"
	"io"
)

// maxFlagsPerMerkleBlock is the maximum number of flag bytes that could
// possibly fit into a merkle block.  Since each transaction is represented by
// a single bit, this is the max number of transactions per block divided by
// 8 bits per byte.  Then an extra one to cover partials.
const maxFlagsPerMerkleBlock = maxTxPerBlock / 8

// MsgMerkleBlock implements the Message interface and represents a bitcoin
// merkleblock message which is used to reset a Bloom filter.
//
// This message was not added until protocol version BIP0037Version.
type MsgMerkleBlock struct {
    Header       FBlockHeader
	Hashes       []*ShaHash
	Flags        []byte
}

// AddTxHash adds a new transaction hash to the message.
func (msg *MsgMerkleBlock) AddTxHash(hash *ShaHash) error {
	if len(msg.Hashes)+1 > maxTxPerBlock {
		str := fmt.Sprintf("too many tx hashes for message [max %v]",
			maxTxPerBlock)
		return messageError("MsgMerkleBlock.AddTxHash", str)
	}

	msg.Hashes = append(msg.Hashes, hash)
	return nil
}

// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) BtcDecode(r io.Reader, pver uint32) error {
	if pver < BIP0037Version {
		str := fmt.Sprintf("merkleblock message invalid for protocol "+
			"version %d", pver)
		return messageError("MsgMerkleBlock.BtcDecode", str)
	}

	err := readBlockHeader(r, pver, &msg.Header)
	if err != nil {
		return err
	}

    // Read the transaction count.
	err = readElement(r, &msg.Header.TransCnt)
    if err != nil {
        return err
    }

    // Read num block locator hashes and limit to max.
    msg.Header.TransCnt, err = readVarInt(r, pver)
    if err != nil {
        return err
    }

	if msg.Header.TransCnt > maxTxPerBlock {
		str := fmt.Sprintf("too many transaction hashes for message "+
        "[count %v, max %v]", msg.Header.TransCnt, maxTxPerBlock)
		return messageError("MsgMerkleBlock.BtcDecode", str)
	}

	msg.Hashes = make([]*ShaHash, 0, msg.Header.TransCnt)
	for i := uint64(0); i < msg.Header.TransCnt; i++ {
		var sha ShaHash
		err := readElement(r, &sha)
		if err != nil {
			return err
		}
		msg.AddTxHash(&sha)
	}

	msg.Flags, err = readVarBytes(r, pver, maxFlagsPerMerkleBlock,
		"merkle block flags size")
	if err != nil {
		return err
	}

	return nil
}

// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) BtcEncode(w io.Writer, pver uint32) error {
	if pver < BIP0037Version {
		str := fmt.Sprintf("merkleblock message invalid for protocol "+
			"version %d", pver)
		return messageError("MsgMerkleBlock.BtcEncode", str)
	}

	// Read num transaction hashes and limit to max.
	numHashes := len(msg.Hashes)
	if numHashes > maxTxPerBlock {
		str := fmt.Sprintf("too many transaction hashes for message "+
			"[count %v, max %v]", numHashes, maxTxPerBlock)
		return messageError("MsgMerkleBlock.BtcDecode", str)
	}
	numFlagBytes := len(msg.Flags)
	if numFlagBytes > maxFlagsPerMerkleBlock {
		str := fmt.Sprintf("too many flag bytes for message [count %v, "+
			"max %v]", numFlagBytes, maxFlagsPerMerkleBlock)
		return messageError("MsgMerkleBlock.BtcDecode", str)
	}

	err := writeBlockHeader(w, pver, &msg.Header)
	if err != nil {
		return err
	}

	err = writeVarInt(w, pver, uint64(numHashes))
	if err != nil {
		return err
	}

	for _, hash := range msg.Hashes {
		err = writeElement(w, hash)
		if err != nil {
			return err
		}
	}

	err = writeVarBytes(w, pver, msg.Flags)
	if err != nil {
		return err
	}

	return nil
}

// Command returns the protocol command string for the message.  This is part
// of the Message interface implementation.
func (msg *MsgMerkleBlock) Command() string {
	return CmdMerkleBlock
}

// MaxPayloadLength returns the maximum length the payload can be for the
// receiver.  This is part of the Message interface implementation.
func (msg *MsgMerkleBlock) MaxPayloadLength(pver uint32) uint32 {
	return MaxBlockPayload
}

// NewMsgMerkleBlock returns a new bitcoin merkleblock message that conforms to
// the Message interface.  See MsgMerkleBlock for details.
func NewMsgMerkleBlock(bh *FBlockHeader) *MsgMerkleBlock {
	return &MsgMerkleBlock{
		Header:       *bh,
		Hashes:       make([]*ShaHash, 0),
		Flags:        make([]byte, 0),
	}
}
<commit_msg>Got rid of unneeded file<commit_after><|endoftext|>


<commit_before>package telegram

import (
	"gopkg.in/telegram-bot-api.v4"
	"log"
	"bytes"
	"fmt"
	"sync"
	"sort"
	"github.com/proshik/githubstatbot/github"
	"math/rand"
)

var (
	//Chains
	//-commands
	startC    = make(chan tgbotapi.Update)
	authC     = make(chan tgbotapi.Update)
	languageC = make(chan tgbotapi.Update)
	//-send message
	messages = make(chan tgbotapi.Chattable)

	//Randomize
	letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)

type Language struct {
	Title      string
	Percentage float32
}

func (b *Bot) ReadUpdates() {
	//create timeout value
	u := tgbotapi.NewUpdate(0)
	u.Timeout = 60
	//read updates from telegram server
	updates, err := b.bot.GetUpdatesChan(u)
	if err != nil {
		log.Panic(err)
	}
	//handle commands from channels
	go func() {
		for {
			select {
			case u := <-startC:
				messages <- startCommand(&u)
			case u := <-authC:
				messages <- authCommand(&u, b)
			case u := <-languageC:
				done := languageCommand(&u, b)
				messages <- done
			}
		}
	}()
	//Отправка сообщений пользователям.
	//Отдельно от предыдущего блока т.к. нельзя в select нельзя обрабатывать каналы команд из которох читается(*С)
	//и куда записыватеся(messages)
	go func() {
		for res := range messages {
			b.bot.Send(res)
		}
	}()
	//read updates and send to channels
	for update := range updates {
		if update.Message.IsCommand() {
			switch update.Message.Command() {
			case "start":
				startC <- update
			case "language":
				languageC <- update
			case "auth":
				authC <- update
			default:
				//show access commands
				startC <- update
			}
		} else {
			startC <- update
		}
	}
}

func (b *Bot) InformAuth(chatId int64, result bool) {
	if result {
		messages <- tgbotapi.NewMessage(chatId, "GitHub аккаунт был успешно подключен!")
	} else {
		messages <- tgbotapi.NewMessage(chatId, "Произошла ошибка при подключении GitHub аккаунта!")
	}
}

func startCommand(update *tgbotapi.Update) tgbotapi.MessageConfig {
	buf := bytes.NewBufferString("Телеграм бот для отображения статистики GitHub аккаунта\n")

	buf.WriteString("\n")
	buf.WriteString("Вы можете управлять мной, отправляя следующие команды:\n\n")
	buf.WriteString("*/auth* - авторизация в github.com\n")
	buf.WriteString("*/language* - статистика языков в репозиториях пользователя\n")
	buf.WriteString("*/languages <username>* - статистика языков в репозиториях заданного пользователя\n")

	msg := tgbotapi.NewMessage(update.Message.Chat.ID, buf.String())
	msg.ParseMode = "markdown"

	return msg
}

func authCommand(update *tgbotapi.Update, bot *Bot) tgbotapi.Chattable {
	//generate state for url string for auth in github
	state := randStringRunes(20)
	//save to store [state]chatId
	bot.stateStore.Add(state, update.Message.Chat.ID)
	//build url
	authUrl := bot.oAuth.BuildAuthUrl(state)
	//build text for message
	buf := bytes.NewBufferString("Для авторизации перейдите по следующей ссылке:\n")
	buf.WriteString("\n")
	buf.WriteString(authUrl + "\n")
	//build message with url for user
	text := buf.String()
	msg := tgbotapi.NewMessage(update.Message.Chat.ID, text)

	return msg
}

func languageCommand(update *tgbotapi.Update, bot *Bot) tgbotapi.MessageConfig {
	//found token by chatId in store
	token, err := bot.tokenStore.Get(update.Message.Chat.ID)
	if err != nil {
		return tgbotapi.NewMessage(update.Message.Chat.ID, "Необходимо выполнить авторизацию. Команда /auth")
	}
	//create github client
	client := github.NewClient(token)

	//receipt info for user
	user, err := client.User()
	if err != nil {
		return tgbotapi.NewMessage(update.Message.Chat.ID, "Ошибка получения данных. Выполните повторную авторизацию")
	}
	//receipt user repositories
	repos, err := client.Repos(user)
	if err != nil {
		return tgbotapi.NewMessage(update.Message.Chat.ID, "Not found repos for user="+user)
	}
	//concurrent receipt language info in repositories of an user
	wg := sync.WaitGroup{}
	languageChan := make(chan map[string]int)
	for _, repo := range repos {
		wg.Add(1)
		go func(wg *sync.WaitGroup, r *github.Repo) {
			defer wg.Done()
			//receipt language info
			lang, err := client.Language(user, *r.Name)
			if err != nil {
				log.Printf("Error on request language for user=%s, repo=%s", user, *r.Name)
			}
			languageChan <- lang
		}(&wg, repo)

	}
	//wait before not will be receipt language info
	go func() {
		wg.Wait()
		close(languageChan)
	}()
	//calculate sum of a bytes in each repository by language name
	statistics := make(map[string]int)
	for stat := range languageChan {
		for k, v := range stat {
			statistics[k] = statistics[k] + v
		}
	}
	//create messages for user
	msg := tgbotapi.NewMessage(update.Message.Chat.ID, createLangStatText(calcPercentages(statistics)))
	msg.ParseMode = "markdown"

	return msg
}

func calcPercentages(languages map[string]int) []*Language {
	result := make([]*Language, 0)
	//calculate total sum byte by all languages
	var totalSum float32
	for _, v := range languages {
		totalSum += float32(v)
	}

	var totalByteOtherLanguages int
	for key, value := range languages {
		repoPercent := float32(value) * (float32(100) / totalSum)
		roundRepoPercent := round(repoPercent, 0.1)
		if roundRepoPercent >= 0.1 {
			result = append(result, &Language{key, roundRepoPercent})
		} else {
			totalByteOtherLanguages += value
		}
	}
	//sort found languages by percentage
	sort.Slice(result, func(i, j int) bool { return result[i].Percentage > result[j].Percentage })
	//calculate percentage for language with less then 0.1% from total count
	if totalByteOtherLanguages != 0 {
		percent := round(float32(totalByteOtherLanguages)*(float32(100)/totalSum), 0.1)
		result = append(result, &Language{"Other languages", percent})
	}

	return result
}

func round(x, unit float32) float32 {
	if x > 0 {
		return float32(int32(x/unit+0.5)) * unit
	}
	return float32(int32(x/unit-0.5)) * unit
}

func createLangStatText(statistics []*Language) string {
	buf := bytes.NewBufferString("")

	for _, l := range statistics {
		buf.WriteString(fmt.Sprintf("*%s* %.1f%%\n", l.Title, l.Percentage))
	}

	return buf.String()
}

func randStringRunes(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}
<commit_msg>Change description commands<commit_after>package telegram

import (
	"gopkg.in/telegram-bot-api.v4"
	"log"
	"bytes"
	"fmt"
	"sync"
	"sort"
	"github.com/proshik/githubstatbot/github"
	"math/rand"
)

var (
	//Chains
	//-commands
	startC    = make(chan tgbotapi.Update)
	authC     = make(chan tgbotapi.Update)
	languageC = make(chan tgbotapi.Update)
	//-send message
	messages = make(chan tgbotapi.Chattable)

	//Randomize
	letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)

type Language struct {
	Title      string
	Percentage float32
}

func (b *Bot) ReadUpdates() {
	//create timeout value
	u := tgbotapi.NewUpdate(0)
	u.Timeout = 60
	//read updates from telegram server
	updates, err := b.bot.GetUpdatesChan(u)
	if err != nil {
		log.Panic(err)
	}
	//handle commands from channels
	go func() {
		for {
			select {
			case u := <-startC:
				messages <- startCommand(&u)
			case u := <-authC:
				messages <- authCommand(&u, b)
			case u := <-languageC:
				done := languageCommand(&u, b)
				messages <- done
			}
		}
	}()
	//Отправка сообщений пользователям.
	//Отдельно от предыдущего блока т.к. нельзя в select нельзя обрабатывать каналы команд из которох читается(*С)
	//и куда записыватеся(messages)
	go func() {
		for res := range messages {
			b.bot.Send(res)
		}
	}()
	//read updates and send to channels
	for update := range updates {
		if update.Message.IsCommand() {
			switch update.Message.Command() {
			case "start":
				startC <- update
			case "language":
				languageC <- update
			case "auth":
				authC <- update
			default:
				//show access commands
				startC <- update
			}
		} else {
			startC <- update
		}
	}
}

func (b *Bot) InformAuth(chatId int64, result bool) {
	if result {
		messages <- tgbotapi.NewMessage(chatId, "GitHub аккаунт был успешно подключен!")
	} else {
		messages <- tgbotapi.NewMessage(chatId, "Произошла ошибка при подключении GitHub аккаунта!")
	}
}

func startCommand(update *tgbotapi.Update) tgbotapi.MessageConfig {
	buf := bytes.NewBufferString("Телеграм бот для отображения статистики GitHub аккаунта\n")

	buf.WriteString("\n")
	buf.WriteString("Вы можете управлять мной, отправляя следующие команды:\n\n")
	buf.WriteString("*/auth* - авторизация через OAuth\n")
	buf.WriteString("*/stat* - статистика авторизованного пользователя\n")
	buf.WriteString("*/account <username>* - статистика по заданному пользователю\n")
	buf.WriteString("*/language* - статистика языков в репозиториях авторизованного пользователя\n")
	buf.WriteString("*/cancel* - отмена авторизации\n")

	msg := tgbotapi.NewMessage(update.Message.Chat.ID, buf.String())
	msg.ParseMode = "markdown"

	return msg
}

func authCommand(update *tgbotapi.Update, bot *Bot) tgbotapi.Chattable {
	//generate state for url string for auth in github
	state := randStringRunes(20)
	//save to store [state]chatId
	bot.stateStore.Add(state, update.Message.Chat.ID)
	//build url
	authUrl := bot.oAuth.BuildAuthUrl(state)
	//build text for message
	buf := bytes.NewBufferString("Для авторизации перейдите по следующей ссылке:\n")
	buf.WriteString("\n")
	buf.WriteString(authUrl + "\n")
	//build message with url for user
	text := buf.String()
	msg := tgbotapi.NewMessage(update.Message.Chat.ID, text)

	return msg
}

func languageCommand(update *tgbotapi.Update, bot *Bot) tgbotapi.MessageConfig {
	//found token by chatId in store
	token, err := bot.tokenStore.Get(update.Message.Chat.ID)
	if err != nil {
		return tgbotapi.NewMessage(update.Message.Chat.ID, "Необходимо выполнить авторизацию. Команда /auth")
	}
	//create github client
	client := github.NewClient(token)

	//receipt info for user
	user, err := client.User()
	if err != nil {
		return tgbotapi.NewMessage(update.Message.Chat.ID, "Ошибка получения данных. Выполните повторную авторизацию")
	}
	//receipt user repositories
	repos, err := client.Repos(user)
	if err != nil {
		return tgbotapi.NewMessage(update.Message.Chat.ID, "Not found repos for user="+user)
	}
	//concurrent receipt language info in repositories of an user
	wg := sync.WaitGroup{}
	languageChan := make(chan map[string]int)
	for _, repo := range repos {
		wg.Add(1)
		go func(wg *sync.WaitGroup, r *github.Repo) {
			defer wg.Done()
			//receipt language info
			lang, err := client.Language(user, *r.Name)
			if err != nil {
				log.Printf("Error on request language for user=%s, repo=%s", user, *r.Name)
			}
			languageChan <- lang
		}(&wg, repo)

	}
	//wait before not will be receipt language info
	go func() {
		wg.Wait()
		close(languageChan)
	}()
	//calculate sum of a bytes in each repository by language name
	statistics := make(map[string]int)
	for stat := range languageChan {
		for k, v := range stat {
			statistics[k] = statistics[k] + v
		}
	}
	//create messages for user
	msg := tgbotapi.NewMessage(update.Message.Chat.ID, createLangStatText(calcPercentages(statistics)))
	msg.ParseMode = "markdown"

	return msg
}

func calcPercentages(languages map[string]int) []*Language {
	result := make([]*Language, 0)
	//calculate total sum byte by all languages
	var totalSum float32
	for _, v := range languages {
		totalSum += float32(v)
	}

	var totalByteOtherLanguages int
	for key, value := range languages {
		repoPercent := float32(value) * (float32(100) / totalSum)
		roundRepoPercent := round(repoPercent, 0.1)
		if roundRepoPercent >= 0.1 {
			result = append(result, &Language{key, roundRepoPercent})
		} else {
			totalByteOtherLanguages += value
		}
	}
	//sort found languages by percentage
	sort.Slice(result, func(i, j int) bool { return result[i].Percentage > result[j].Percentage })
	//calculate percentage for language with less then 0.1% from total count
	if totalByteOtherLanguages != 0 {
		percent := round(float32(totalByteOtherLanguages)*(float32(100)/totalSum), 0.1)
		result = append(result, &Language{"Other languages", percent})
	}

	return result
}

func round(x, unit float32) float32 {
	if x > 0 {
		return float32(int32(x/unit+0.5)) * unit
	}
	return float32(int32(x/unit-0.5)) * unit
}

func createLangStatText(statistics []*Language) string {
	buf := bytes.NewBufferString("")

	for _, l := range statistics {
		buf.WriteString(fmt.Sprintf("*%s* %.1f%%\n", l.Title, l.Percentage))
	}

	return buf.String()
}

func randStringRunes(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}
<|endoftext|>


<commit_before>/*
Copyright IBM Corp. 2016 All Rights Reserved.

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 service

import (
	"sync"

	"github.com/hyperledger/fabric/core/committer"
	"github.com/hyperledger/fabric/core/deliverservice"
	"github.com/hyperledger/fabric/core/deliverservice/blocksprovider"
	"github.com/hyperledger/fabric/gossip/api"
	gossipCommon "github.com/hyperledger/fabric/gossip/common"
	"github.com/hyperledger/fabric/gossip/election"
	"github.com/hyperledger/fabric/gossip/gossip"
	"github.com/hyperledger/fabric/gossip/identity"
	"github.com/hyperledger/fabric/gossip/integration"
	"github.com/hyperledger/fabric/gossip/state"
	"github.com/hyperledger/fabric/gossip/util"
	"github.com/hyperledger/fabric/protos/common"
	proto "github.com/hyperledger/fabric/protos/gossip"
	"github.com/spf13/viper"
	"google.golang.org/grpc"
)

var (
	gossipServiceInstance *gossipServiceImpl
	once                  sync.Once
)

type gossipSvc gossip.Gossip

// GossipService encapsulates gossip and state capabilities into single interface
type GossipService interface {
	gossip.Gossip

	// NewConfigEventer creates a ConfigProcessor which the configtx.Manager can ultimately route config updates to
	NewConfigEventer() ConfigProcessor
	// InitializeChannel allocates the state provider and should be invoked once per channel per execution
	InitializeChannel(chainID string, committer committer.Committer, endpoints []string)
	// GetBlock returns block for given chain
	GetBlock(chainID string, index uint64) *common.Block
	// AddPayload appends message payload to for given chain
	AddPayload(chainID string, payload *proto.Payload) error
}

// DeliveryServiceFactory factory to create and initialize delivery service instance
type DeliveryServiceFactory interface {
	// Returns an instance of delivery client
	Service(g GossipService, endpoints []string, msc api.MessageCryptoService) (deliverclient.DeliverService, error)
}

type deliveryFactoryImpl struct {
}

// Returns an instance of delivery client
func (*deliveryFactoryImpl) Service(g GossipService, endpoints []string, mcs api.MessageCryptoService) (deliverclient.DeliverService, error) {
	return deliverclient.NewDeliverService(&deliverclient.Config{
		CryptoSvc:   mcs,
		Gossip:      g,
		Endpoints:   endpoints,
		ConnFactory: deliverclient.DefaultConnectionFactory,
		ABCFactory:  deliverclient.DefaultABCFactory,
	})
}

type gossipServiceImpl struct {
	gossipSvc
	chains          map[string]state.GossipStateProvider
	leaderElection  map[string]election.LeaderElectionService
	deliveryService deliverclient.DeliverService
	deliveryFactory DeliveryServiceFactory
	lock            sync.RWMutex
	idMapper        identity.Mapper
	mcs             api.MessageCryptoService
	peerIdentity    []byte
	secAdv          api.SecurityAdvisor
}

// This is an implementation of api.JoinChannelMessage.
type joinChannelMessage struct {
	seqNum              uint64
	members2AnchorPeers map[string][]api.AnchorPeer
}

func (jcm *joinChannelMessage) SequenceNumber() uint64 {
	return jcm.seqNum
}

// Members returns the organizations of the channel
func (jcm *joinChannelMessage) Members() []api.OrgIdentityType {
	members := make([]api.OrgIdentityType, len(jcm.members2AnchorPeers))
	i := 0
	for org := range jcm.members2AnchorPeers {
		members[i] = api.OrgIdentityType(org)
		i++
	}
	return members
}

// AnchorPeersOf returns the anchor peers of the given organization
func (jcm *joinChannelMessage) AnchorPeersOf(org api.OrgIdentityType) []api.AnchorPeer {
	return jcm.members2AnchorPeers[string(org)]
}

var logger = util.GetLogger(util.LoggingServiceModule, "")

// InitGossipService initialize gossip service
func InitGossipService(peerIdentity []byte, endpoint string, s *grpc.Server, mcs api.MessageCryptoService,
	secAdv api.SecurityAdvisor, secureDialOpts api.PeerSecureDialOpts, bootPeers ...string) {
	// TODO: Remove this.
	// TODO: This is a temporary work-around to make the gossip leader election module load its logger at startup
	// TODO: in order for the flogging package to register this logger in time so it can set the log levels as requested in the config
	util.GetLogger(util.LoggingElectionModule, "")
	InitGossipServiceCustomDeliveryFactory(peerIdentity, endpoint, s, &deliveryFactoryImpl{},
		mcs, secAdv, secureDialOpts, bootPeers...)
}

// InitGossipServiceCustomDeliveryFactory initialize gossip service with customize delivery factory
// implementation, might be useful for testing and mocking purposes
func InitGossipServiceCustomDeliveryFactory(peerIdentity []byte, endpoint string, s *grpc.Server,
	factory DeliveryServiceFactory, mcs api.MessageCryptoService, secAdv api.SecurityAdvisor,
	secureDialOpts api.PeerSecureDialOpts, bootPeers ...string) {
	once.Do(func() {
		logger.Info("Initialize gossip with endpoint", endpoint, "and bootstrap set", bootPeers)

		idMapper := identity.NewIdentityMapper(mcs)
		idMapper.Put(mcs.GetPKIidOfCert(peerIdentity), peerIdentity)

		gossip := integration.NewGossipComponent(peerIdentity, endpoint, s, secAdv,
			mcs, idMapper, secureDialOpts, bootPeers...)
		gossipServiceInstance = &gossipServiceImpl{
			mcs:             mcs,
			gossipSvc:       gossip,
			chains:          make(map[string]state.GossipStateProvider),
			leaderElection:  make(map[string]election.LeaderElectionService),
			deliveryFactory: factory,
			idMapper:        idMapper,
			peerIdentity:    peerIdentity,
			secAdv:          secAdv,
		}
	})
}

// GetGossipService returns an instance of gossip service
func GetGossipService() GossipService {
	return gossipServiceInstance
}

// NewConfigEventer creates a ConfigProcessor which the configtx.Manager can ultimately route config updates to
func (g *gossipServiceImpl) NewConfigEventer() ConfigProcessor {
	return newConfigEventer(g)
}

// InitializeChannel allocates the state provider and should be invoked once per channel per execution
func (g *gossipServiceImpl) InitializeChannel(chainID string, committer committer.Committer, endpoints []string) {
	g.lock.Lock()
	defer g.lock.Unlock()
	// Initialize new state provider for given committer
	logger.Debug("Creating state provider for chainID", chainID)
	g.chains[chainID] = state.NewGossipStateProvider(chainID, g, committer, g.mcs)
	if g.deliveryService == nil {
		var err error
		g.deliveryService, err = g.deliveryFactory.Service(gossipServiceInstance, endpoints, g.mcs)
		if err != nil {
			logger.Warning("Cannot create delivery client, due to", err)
		}
	}

	// Delivery service might be nil only if it was not able to get connected
	// to the ordering service
	if g.deliveryService != nil {
		// Parameters:
		//              - peer.gossip.useLeaderElection
		//              - peer.gossip.orgLeader
		//
		// are mutual exclusive, setting both to true is not defined, hence
		// peer will panic and terminate
		leaderElection := viper.GetBool("peer.gossip.useLeaderElection")
		isStaticOrgLeader := viper.GetBool("peer.gossip.orgLeader")

		if leaderElection && isStaticOrgLeader {
			logger.Panic("Setting both orgLeader and useLeaderElection to true isn't supported, aborting execution")
		}

		if leaderElection {
			logger.Debug("Delivery uses dynamic leader election mechanism, channel", chainID)
			g.leaderElection[chainID] = g.newLeaderElectionComponent(chainID, g.onStatusChangeFactory(chainID, committer))
		} else if isStaticOrgLeader {
			logger.Debug("This peer is configured to connect to ordering service for blocks delivery, channel", chainID)
			g.deliveryService.StartDeliverForChannel(chainID, committer)
		} else {
			logger.Debug("This peer is not configured to connect to ordering service for blocks delivery, channel", chainID)
		}
	} else {
		logger.Warning("Delivery client is down won't be able to pull blocks for chain", chainID)
	}
}

// configUpdated constructs a joinChannelMessage and sends it to the gossipSvc
func (g *gossipServiceImpl) configUpdated(config Config) {
	myOrg := string(g.secAdv.OrgByPeerIdentity(api.PeerIdentityType(g.peerIdentity)))
	if !g.amIinChannel(myOrg, config) {
		logger.Error("Tried joining channel", config.ChainID(), "but our org(", myOrg, "), isn't "+
			"among the orgs of the channel:", orgListFromConfig(config), ", aborting.")
		return
	}
	jcm := &joinChannelMessage{seqNum: config.Sequence(), members2AnchorPeers: map[string][]api.AnchorPeer{}}
	for _, appOrg := range config.Organizations() {
		logger.Debug(appOrg.MSPID(), "anchor peers:", appOrg.AnchorPeers())
		for _, ap := range appOrg.AnchorPeers() {
			anchorPeer := api.AnchorPeer{
				Host: ap.Host,
				Port: int(ap.Port),
			}
			jcm.members2AnchorPeers[appOrg.MSPID()] = append(jcm.members2AnchorPeers[appOrg.MSPID()], anchorPeer)
		}
	}

	// Initialize new state provider for given committer
	logger.Debug("Creating state provider for chainID", config.ChainID())
	g.JoinChan(jcm, gossipCommon.ChainID(config.ChainID()))
}

// GetBlock returns block for given chain
func (g *gossipServiceImpl) GetBlock(chainID string, index uint64) *common.Block {
	g.lock.RLock()
	defer g.lock.RUnlock()
	return g.chains[chainID].GetBlock(index)
}

// AddPayload appends message payload to for given chain
func (g *gossipServiceImpl) AddPayload(chainID string, payload *proto.Payload) error {
	g.lock.RLock()
	defer g.lock.RUnlock()
	return g.chains[chainID].AddPayload(payload)
}

// Stop stops the gossip component
func (g *gossipServiceImpl) Stop() {
	g.lock.Lock()
	defer g.lock.Unlock()
	for _, ch := range g.chains {
		logger.Info("Stopping chain", ch)
		ch.Stop()
	}

	for chainID, electionService := range g.leaderElection {
		logger.Info("Stopping leader election for %s", chainID)
		electionService.Stop()
	}
	g.gossipSvc.Stop()
	if g.deliveryService != nil {
		g.deliveryService.Stop()
	}
}

func (g *gossipServiceImpl) newLeaderElectionComponent(chainID string, callback func(bool)) election.LeaderElectionService {
	PKIid := g.idMapper.GetPKIidOfCert(g.peerIdentity)
	adapter := election.NewAdapter(g, PKIid, gossipCommon.ChainID(chainID))
	return election.NewLeaderElectionService(adapter, string(PKIid), callback)
}

func (g *gossipServiceImpl) amIinChannel(myOrg string, config Config) bool {
	for _, orgName := range orgListFromConfig(config) {
		if orgName == myOrg {
			return true
		}
	}
	return false
}

func (g *gossipServiceImpl) onStatusChangeFactory(chainID string, committer blocksprovider.LedgerInfo) func(bool) {
	return func(isLeader bool) {
		if isLeader {
			if err := g.deliveryService.StartDeliverForChannel(chainID, committer); err != nil {
				logger.Error("Delivery service is not able to start blocks delivery for chain, due to", err)
			}
		} else {
			if err := g.deliveryService.StopDeliverForChannel(chainID); err != nil {
				logger.Error("Delivery service is not able to stop blocks delivery for chain, due to", err)
			}

		}

	}
}

func orgListFromConfig(config Config) []string {
	var orgList []string
	for _, appOrg := range config.Organizations() {
		orgList = append(orgList, appOrg.MSPID())
	}
	return orgList
}
<commit_msg>[FAB-3643] respect peer.gossip.endpoint configuration<commit_after>/*
Copyright IBM Corp. 2016 All Rights Reserved.

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 service

import (
	"sync"

	"github.com/hyperledger/fabric/core/committer"
	"github.com/hyperledger/fabric/core/deliverservice"
	"github.com/hyperledger/fabric/core/deliverservice/blocksprovider"
	"github.com/hyperledger/fabric/gossip/api"
	gossipCommon "github.com/hyperledger/fabric/gossip/common"
	"github.com/hyperledger/fabric/gossip/election"
	"github.com/hyperledger/fabric/gossip/gossip"
	"github.com/hyperledger/fabric/gossip/identity"
	"github.com/hyperledger/fabric/gossip/integration"
	"github.com/hyperledger/fabric/gossip/state"
	"github.com/hyperledger/fabric/gossip/util"
	"github.com/hyperledger/fabric/protos/common"
	proto "github.com/hyperledger/fabric/protos/gossip"
	"github.com/spf13/viper"
	"google.golang.org/grpc"
)

var (
	gossipServiceInstance *gossipServiceImpl
	once                  sync.Once
)

type gossipSvc gossip.Gossip

// GossipService encapsulates gossip and state capabilities into single interface
type GossipService interface {
	gossip.Gossip

	// NewConfigEventer creates a ConfigProcessor which the configtx.Manager can ultimately route config updates to
	NewConfigEventer() ConfigProcessor
	// InitializeChannel allocates the state provider and should be invoked once per channel per execution
	InitializeChannel(chainID string, committer committer.Committer, endpoints []string)
	// GetBlock returns block for given chain
	GetBlock(chainID string, index uint64) *common.Block
	// AddPayload appends message payload to for given chain
	AddPayload(chainID string, payload *proto.Payload) error
}

// DeliveryServiceFactory factory to create and initialize delivery service instance
type DeliveryServiceFactory interface {
	// Returns an instance of delivery client
	Service(g GossipService, endpoints []string, msc api.MessageCryptoService) (deliverclient.DeliverService, error)
}

type deliveryFactoryImpl struct {
}

// Returns an instance of delivery client
func (*deliveryFactoryImpl) Service(g GossipService, endpoints []string, mcs api.MessageCryptoService) (deliverclient.DeliverService, error) {
	return deliverclient.NewDeliverService(&deliverclient.Config{
		CryptoSvc:   mcs,
		Gossip:      g,
		Endpoints:   endpoints,
		ConnFactory: deliverclient.DefaultConnectionFactory,
		ABCFactory:  deliverclient.DefaultABCFactory,
	})
}

type gossipServiceImpl struct {
	gossipSvc
	chains          map[string]state.GossipStateProvider
	leaderElection  map[string]election.LeaderElectionService
	deliveryService deliverclient.DeliverService
	deliveryFactory DeliveryServiceFactory
	lock            sync.RWMutex
	idMapper        identity.Mapper
	mcs             api.MessageCryptoService
	peerIdentity    []byte
	secAdv          api.SecurityAdvisor
}

// This is an implementation of api.JoinChannelMessage.
type joinChannelMessage struct {
	seqNum              uint64
	members2AnchorPeers map[string][]api.AnchorPeer
}

func (jcm *joinChannelMessage) SequenceNumber() uint64 {
	return jcm.seqNum
}

// Members returns the organizations of the channel
func (jcm *joinChannelMessage) Members() []api.OrgIdentityType {
	members := make([]api.OrgIdentityType, len(jcm.members2AnchorPeers))
	i := 0
	for org := range jcm.members2AnchorPeers {
		members[i] = api.OrgIdentityType(org)
		i++
	}
	return members
}

// AnchorPeersOf returns the anchor peers of the given organization
func (jcm *joinChannelMessage) AnchorPeersOf(org api.OrgIdentityType) []api.AnchorPeer {
	return jcm.members2AnchorPeers[string(org)]
}

var logger = util.GetLogger(util.LoggingServiceModule, "")

// InitGossipService initialize gossip service
func InitGossipService(peerIdentity []byte, endpoint string, s *grpc.Server, mcs api.MessageCryptoService,
	secAdv api.SecurityAdvisor, secureDialOpts api.PeerSecureDialOpts, bootPeers ...string) {
	// TODO: Remove this.
	// TODO: This is a temporary work-around to make the gossip leader election module load its logger at startup
	// TODO: in order for the flogging package to register this logger in time so it can set the log levels as requested in the config
	util.GetLogger(util.LoggingElectionModule, "")
	InitGossipServiceCustomDeliveryFactory(peerIdentity, endpoint, s, &deliveryFactoryImpl{},
		mcs, secAdv, secureDialOpts, bootPeers...)
}

// InitGossipServiceCustomDeliveryFactory initialize gossip service with customize delivery factory
// implementation, might be useful for testing and mocking purposes
func InitGossipServiceCustomDeliveryFactory(peerIdentity []byte, endpoint string, s *grpc.Server,
	factory DeliveryServiceFactory, mcs api.MessageCryptoService, secAdv api.SecurityAdvisor,
	secureDialOpts api.PeerSecureDialOpts, bootPeers ...string) {
	once.Do(func() {
		if overrideEndpoint := viper.GetString("peer.gossip.endpoint"); overrideEndpoint != "" {
			endpoint = overrideEndpoint
		}

		logger.Info("Initialize gossip with endpoint", endpoint, "and bootstrap set", bootPeers)

		idMapper := identity.NewIdentityMapper(mcs)
		idMapper.Put(mcs.GetPKIidOfCert(peerIdentity), peerIdentity)

		gossip := integration.NewGossipComponent(peerIdentity, endpoint, s, secAdv,
			mcs, idMapper, secureDialOpts, bootPeers...)
		gossipServiceInstance = &gossipServiceImpl{
			mcs:             mcs,
			gossipSvc:       gossip,
			chains:          make(map[string]state.GossipStateProvider),
			leaderElection:  make(map[string]election.LeaderElectionService),
			deliveryFactory: factory,
			idMapper:        idMapper,
			peerIdentity:    peerIdentity,
			secAdv:          secAdv,
		}
	})
}

// GetGossipService returns an instance of gossip service
func GetGossipService() GossipService {
	return gossipServiceInstance
}

// NewConfigEventer creates a ConfigProcessor which the configtx.Manager can ultimately route config updates to
func (g *gossipServiceImpl) NewConfigEventer() ConfigProcessor {
	return newConfigEventer(g)
}

// InitializeChannel allocates the state provider and should be invoked once per channel per execution
func (g *gossipServiceImpl) InitializeChannel(chainID string, committer committer.Committer, endpoints []string) {
	g.lock.Lock()
	defer g.lock.Unlock()
	// Initialize new state provider for given committer
	logger.Debug("Creating state provider for chainID", chainID)
	g.chains[chainID] = state.NewGossipStateProvider(chainID, g, committer, g.mcs)
	if g.deliveryService == nil {
		var err error
		g.deliveryService, err = g.deliveryFactory.Service(gossipServiceInstance, endpoints, g.mcs)
		if err != nil {
			logger.Warning("Cannot create delivery client, due to", err)
		}
	}

	// Delivery service might be nil only if it was not able to get connected
	// to the ordering service
	if g.deliveryService != nil {
		// Parameters:
		//              - peer.gossip.useLeaderElection
		//              - peer.gossip.orgLeader
		//
		// are mutual exclusive, setting both to true is not defined, hence
		// peer will panic and terminate
		leaderElection := viper.GetBool("peer.gossip.useLeaderElection")
		isStaticOrgLeader := viper.GetBool("peer.gossip.orgLeader")

		if leaderElection && isStaticOrgLeader {
			logger.Panic("Setting both orgLeader and useLeaderElection to true isn't supported, aborting execution")
		}

		if leaderElection {
			logger.Debug("Delivery uses dynamic leader election mechanism, channel", chainID)
			g.leaderElection[chainID] = g.newLeaderElectionComponent(chainID, g.onStatusChangeFactory(chainID, committer))
		} else if isStaticOrgLeader {
			logger.Debug("This peer is configured to connect to ordering service for blocks delivery, channel", chainID)
			g.deliveryService.StartDeliverForChannel(chainID, committer)
		} else {
			logger.Debug("This peer is not configured to connect to ordering service for blocks delivery, channel", chainID)
		}
	} else {
		logger.Warning("Delivery client is down won't be able to pull blocks for chain", chainID)
	}
}

// configUpdated constructs a joinChannelMessage and sends it to the gossipSvc
func (g *gossipServiceImpl) configUpdated(config Config) {
	myOrg := string(g.secAdv.OrgByPeerIdentity(api.PeerIdentityType(g.peerIdentity)))
	if !g.amIinChannel(myOrg, config) {
		logger.Error("Tried joining channel", config.ChainID(), "but our org(", myOrg, "), isn't "+
			"among the orgs of the channel:", orgListFromConfig(config), ", aborting.")
		return
	}
	jcm := &joinChannelMessage{seqNum: config.Sequence(), members2AnchorPeers: map[string][]api.AnchorPeer{}}
	for _, appOrg := range config.Organizations() {
		logger.Debug(appOrg.MSPID(), "anchor peers:", appOrg.AnchorPeers())
		for _, ap := range appOrg.AnchorPeers() {
			anchorPeer := api.AnchorPeer{
				Host: ap.Host,
				Port: int(ap.Port),
			}
			jcm.members2AnchorPeers[appOrg.MSPID()] = append(jcm.members2AnchorPeers[appOrg.MSPID()], anchorPeer)
		}
	}

	// Initialize new state provider for given committer
	logger.Debug("Creating state provider for chainID", config.ChainID())
	g.JoinChan(jcm, gossipCommon.ChainID(config.ChainID()))
}

// GetBlock returns block for given chain
func (g *gossipServiceImpl) GetBlock(chainID string, index uint64) *common.Block {
	g.lock.RLock()
	defer g.lock.RUnlock()
	return g.chains[chainID].GetBlock(index)
}

// AddPayload appends message payload to for given chain
func (g *gossipServiceImpl) AddPayload(chainID string, payload *proto.Payload) error {
	g.lock.RLock()
	defer g.lock.RUnlock()
	return g.chains[chainID].AddPayload(payload)
}

// Stop stops the gossip component
func (g *gossipServiceImpl) Stop() {
	g.lock.Lock()
	defer g.lock.Unlock()
	for _, ch := range g.chains {
		logger.Info("Stopping chain", ch)
		ch.Stop()
	}

	for chainID, electionService := range g.leaderElection {
		logger.Info("Stopping leader election for %s", chainID)
		electionService.Stop()
	}
	g.gossipSvc.Stop()
	if g.deliveryService != nil {
		g.deliveryService.Stop()
	}
}

func (g *gossipServiceImpl) newLeaderElectionComponent(chainID string, callback func(bool)) election.LeaderElectionService {
	PKIid := g.idMapper.GetPKIidOfCert(g.peerIdentity)
	adapter := election.NewAdapter(g, PKIid, gossipCommon.ChainID(chainID))
	return election.NewLeaderElectionService(adapter, string(PKIid), callback)
}

func (g *gossipServiceImpl) amIinChannel(myOrg string, config Config) bool {
	for _, orgName := range orgListFromConfig(config) {
		if orgName == myOrg {
			return true
		}
	}
	return false
}

func (g *gossipServiceImpl) onStatusChangeFactory(chainID string, committer blocksprovider.LedgerInfo) func(bool) {
	return func(isLeader bool) {
		if isLeader {
			if err := g.deliveryService.StartDeliverForChannel(chainID, committer); err != nil {
				logger.Error("Delivery service is not able to start blocks delivery for chain, due to", err)
			}
		} else {
			if err := g.deliveryService.StopDeliverForChannel(chainID); err != nil {
				logger.Error("Delivery service is not able to stop blocks delivery for chain, due to", err)
			}

		}

	}
}

func orgListFromConfig(config Config) []string {
	var orgList []string
	for _, appOrg := range config.Organizations() {
		orgList = append(orgList, appOrg.MSPID())
	}
	return orgList
}
<|endoftext|>


<commit_before>// Copyright 2011-2018 visualfc <visualfc@gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gomod

import (
	"encoding/json"
	"os/exec"
	"path/filepath"
	"strings"
)

func LooupModList(dir string) *ModuleList {
	data := ListModuleJson(dir)
	if data == nil {
		return nil
	}
	ms := parseModuleJson(data)
	return &ms
}

func LookupModFile(dir string) string {
	command := exec.Command("go", "env", "GOMOD")
	command.Dir = dir
	data, err := command.Output()
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(data))
}

func ListModuleJson(dir string) []byte {
	command := exec.Command("go", "list", "-m", "-json", "all")
	command.Dir = dir
	data, err := command.Output()
	if err != nil {
		return nil
	}
	return data
}

type ModuleList struct {
	Module  Module
	Require []*Module
}

func makePath(path, dir string, addin string) string {
	dir = filepath.ToSlash(dir)
	pos := strings.Index(dir, "mod/"+path+"@")
	if pos == -1 {
		return path
	}
	return filepath.Join(dir[pos:], addin)
}

func (m *ModuleList) LookupModule(pkgname string) (require *Module, path string, dir string) {
	for _, r := range m.Require {
		if strings.HasPrefix(pkgname, r.Path) {
			addin := pkgname[len(r.Path):]
			if r.Replace != nil {
				path = makePath(r.Replace.Path, r.Dir, addin)
			} else {
				path = makePath(r.Path, r.Dir, addin)
			}
			return r, path, filepath.Join(r.Dir, addin)
		}
	}
	return nil, "", ""
}

type Module struct {
	Path    string
	Version string
	Time    string
	Dir     string
	Main    bool
	Replace *Module
}

func parseModuleJson(data []byte) ModuleList {
	var ms ModuleList
	var index int
	var tag int
	for i, v := range data {
		switch v {
		case '{':
			if tag == 0 {
				index = i
			}
			tag++
		case '}':
			tag--
			if tag == 0 {
				var m Module
				err := json.Unmarshal(data[index:i+1], &m)
				if err == nil {
					if m.Main {
						ms.Module = m
					} else {
						ms.Require = append(ms.Require, &m)
					}
				}
			}
		}
	}
	return ms
}
<commit_msg>gomod: fix lookup module for go list<commit_after>// Copyright 2011-2018 visualfc <visualfc@gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gomod

import (
	"encoding/json"
	"os/exec"
	"path/filepath"
	"strings"
)

func LooupModList(dir string) *ModuleList {
	data := ListModuleJson(dir)
	if data == nil {
		return nil
	}
	ms := parseModuleJson(data)
	ms.Dir = dir
	return &ms
}

func LookupModFile(dir string) string {
	command := exec.Command("go", "env", "GOMOD")
	command.Dir = dir
	data, err := command.Output()
	if err != nil {
		return ""
	}
	return strings.TrimSpace(string(data))
}

func ListModuleJson(dir string) []byte {
	command := exec.Command("go", "list", "-m", "-json", "all")
	command.Dir = dir
	data, err := command.Output()
	if err != nil {
		return nil
	}
	return data
}

type ModuleList struct {
	Dir     string
	Module  Module
	Require []*Module
}

func makePath(path, dir string, addin string) string {
	dir = filepath.ToSlash(dir)
	pos := strings.Index(dir, "mod/"+path+"@")
	if pos == -1 {
		return path
	}
	return filepath.Join(dir[pos:], addin)
}

type Package struct {
	Dir        string
	ImportPath string
	Name       string
	Module     *Module
}

func (m *ModuleList) LookupModule(pkgname string) (require *Module, path string, dir string) {
	for _, r := range m.Require {
		if strings.HasPrefix(pkgname, r.Path) {
			addin := pkgname[len(r.Path):]
			if r.Replace != nil {
				path = makePath(r.Replace.Path, r.Dir, addin)
			} else {
				path = makePath(r.Path, r.Dir, addin)
			}
			return r, path, filepath.Join(r.Dir, addin)
		}
	}
	c := exec.Command("go", "list", "-json", "-e", pkgname)
	c.Dir = m.Dir
	data, err := c.Output()
	if err == nil {
		var p Package
		err = json.Unmarshal(data, &p)
		if err == nil {
			return p.Module, p.ImportPath, p.Dir
		}
	}

	return nil, "", ""
}

type Module struct {
	Path    string
	Version string
	Time    string
	Dir     string
	Main    bool
	Replace *Module
}

func parseModuleJson(data []byte) ModuleList {
	var ms ModuleList
	var index int
	var tag int
	for i, v := range data {
		switch v {
		case '{':
			if tag == 0 {
				index = i
			}
			tag++
		case '}':
			tag--
			if tag == 0 {
				var m Module
				err := json.Unmarshal(data[index:i+1], &m)
				if err == nil {
					if m.Main {
						ms.Module = m
					} else {
						ms.Require = append(ms.Require, &m)
					}
				}
			}
		}
	}
	return ms
}
<|endoftext|>


<commit_before>package gtr

import (
	"time"
)

// ReportBuilder helps build a test Report from a collection of events.
//
// The ReportBuilder keeps track of the active context whenever a test,
// benchmark or build error is created. This is necessary because the test
// parser do not contain any state themselves and simply just emit an event for
// every line that is read. By tracking the active context, any output that is
// appended to the ReportBuilder gets attributed to the correct test, benchmark
// or build error.
type ReportBuilder struct {
	packages    []Package
	tests       map[int]Test
	benchmarks  map[int]Benchmark
	buildErrors map[int]Error
	runErrors   map[int]Error

	// state
	nextId   int      // next free unused id
	lastId   int      // most recently created id
	output   []string // output that does not belong to any test
	coverage float64  // coverage percentage

	// default values
	packageName string
}

// NewReportBuilder creates a new ReportBuilder.
func NewReportBuilder(packageName string) *ReportBuilder {
	return &ReportBuilder{
		tests:       make(map[int]Test),
		benchmarks:  make(map[int]Benchmark),
		buildErrors: make(map[int]Error),
		runErrors:   make(map[int]Error),
		nextId:      1,
		packageName: packageName,
	}
}

// newId returns a new unique id and sets the active context this id.
func (b *ReportBuilder) newId() int {
	id := b.nextId
	b.lastId = id
	b.nextId += 1
	return id
}

// flush creates a new package in this report containing any tests or
// benchmarks we've collected so far. This is necessary when a test or
// benchmark did not end with a summary.
func (b *ReportBuilder) flush() {
	if len(b.tests) > 0 || len(b.benchmarks) > 0 {
		b.CreatePackage(b.packageName, "", 0, "")
	}
}

// Build returns the new Report containing all the tests, benchmarks and output
// created so far.
func (b *ReportBuilder) Build() Report {
	b.flush()
	return Report{Packages: b.packages}
}

// CreateTest adds a test with the given name to the report, and marks it as
// active.
func (b *ReportBuilder) CreateTest(name string) {
	b.tests[b.newId()] = Test{Name: name}
}

// PauseTest marks the active context as no longer active. Any results or
// output added to the report after calling PauseTest will no longer be assumed
// to belong to this test.
func (b *ReportBuilder) PauseTest(name string) {
	b.lastId = 0
}

// ContinueTest finds the test with the given name and marks it as active. If
// more than one test exist with this name, the most recently created test will
// be used.
func (b *ReportBuilder) ContinueTest(name string) {
	b.lastId = b.findTest(name)
}

// EndTest finds the test with the given name, sets the result, duration and
// level, and marks it as active. If more than one test exists with this name,
// the most recently created test will be used. If no test exists with this
// name, a new test is created.
func (b *ReportBuilder) EndTest(name, result string, duration time.Duration, level int) {
	b.lastId = b.findTest(name)
	if b.lastId < 0 {
		// test did not exist, create one
		// TODO: Likely reason is that the user ran go test without the -v
		// flag, should we report this somewhere?
		b.CreateTest(name)
	}

	t := b.tests[b.lastId]
	t.Result = parseResult(result)
	t.Duration = duration
	t.Level = level
	b.tests[b.lastId] = t
}

// End marks the active context as no longer active.
func (b *ReportBuilder) End() {
	b.lastId = 0
}

// Benchmark adds a new Benchmark to the report and marks it as active.
func (b *ReportBuilder) Benchmark(name string, iterations int64, nsPerOp, mbPerSec float64, bytesPerOp, allocsPerOp int64) {
	b.benchmarks[b.newId()] = Benchmark{
		Name:        name,
		Result:      Pass,
		Iterations:  iterations,
		NsPerOp:     nsPerOp,
		MBPerSec:    mbPerSec,
		BytesPerOp:  bytesPerOp,
		AllocsPerOp: allocsPerOp,
	}
}

// CreateBuildError creates a new build error and marks it as active.
func (b *ReportBuilder) CreateBuildError(packageName string) {
	b.buildErrors[b.newId()] = Error{Name: packageName}
}

// CreatePackage adds a new package with the given name to the Report. This
// package contains all the build errors, output, tests and benchmarks created
// so far. Afterwards all state is reset.
func (b *ReportBuilder) CreatePackage(name, result string, duration time.Duration, data string) {
	pkg := Package{
		Name:     name,
		Duration: duration,
	}

	// Build errors are treated somewhat differently. Rather than having a
	// single package with all build errors collected so far, we only care
	// about the build errors for this particular package.
	for id, buildErr := range b.buildErrors {
		if buildErr.Name == name {
			if len(b.tests) > 0 || len(b.benchmarks) > 0 {
				panic("unexpected tests and/or benchmarks found in build error package")
			}
			buildErr.Duration = duration
			buildErr.Cause = data
			pkg.BuildError = buildErr
			b.packages = append(b.packages, pkg)

			delete(b.buildErrors, id)
			// TODO: reset state
			// TODO: buildErrors shouldn't reset/use nextId/lastId, they're more like a global cache
			return
		}
	}

	// If we've collected output, but there were no tests or benchmarks then
	// either there were no tests, or there was some other non-build error.
	if len(b.output) > 0 && len(b.tests) == 0 && len(b.benchmarks) == 0 {
		if parseResult(result) == Fail {
			pkg.RunError = Error{
				Name:   name,
				Output: b.output,
			}
		}
		b.packages = append(b.packages, pkg)

		// TODO: reset state
		b.output = nil
		return
	}

	// If the summary result says we failed, but there were no failing tests
	// then something else must have failed.
	if parseResult(result) == Fail && (len(b.tests) > 0 || len(b.benchmarks) > 0) && !b.containsFailingTest() {
		pkg.RunError = Error{
			Name:   name,
			Output: b.output,
		}
		b.output = nil
	}

	// Collect tests and benchmarks for this package, maintaining insertion order.
	var tests []Test
	var benchmarks []Benchmark
	for id := 1; id < b.nextId; id++ {
		if t, ok := b.tests[id]; ok {
			tests = append(tests, t)
		}
		if bm, ok := b.benchmarks[id]; ok {
			benchmarks = append(benchmarks, bm)
		}
	}

	pkg.Coverage = b.coverage
	pkg.Output = b.output
	pkg.Tests = tests
	pkg.Benchmarks = benchmarks
	b.packages = append(b.packages, pkg)

	// reset state
	b.nextId = 1
	b.lastId = 0
	b.output = nil
	b.coverage = 0
	b.tests = make(map[int]Test)
	b.benchmarks = make(map[int]Benchmark)
}

// Coverage sets the code coverage percentage.
func (b *ReportBuilder) Coverage(pct float64, packages []string) {
	b.coverage = pct
}

// AppendOutput appends the given line to the currently active context. If no
// active context exists, the output is assumed to belong to the package.
func (b *ReportBuilder) AppendOutput(line string) {
	// TODO(jstemmer): check if output is potentially a build error
	if b.lastId <= 0 {
		b.output = append(b.output, line)
		return
	}

	if t, ok := b.tests[b.lastId]; ok {
		t.Output = append(t.Output, line)
		b.tests[b.lastId] = t
	} else if bm, ok := b.benchmarks[b.lastId]; ok {
		bm.Output = append(bm.Output, line)
		b.benchmarks[b.lastId] = bm
	} else if be, ok := b.buildErrors[b.lastId]; ok {
		be.Output = append(be.Output, line)
		b.buildErrors[b.lastId] = be
	} else {
		b.output = append(b.output, line)
	}
}

// findTest returns the id of the most recently created test with the given
// name, or -1 if no such test exists.
func (b *ReportBuilder) findTest(name string) int {
	// check if this test was lastId
	if t, ok := b.tests[b.lastId]; ok && t.Name == name {
		return b.lastId
	}
	for id := len(b.tests); id >= 0; id-- {
		if b.tests[id].Name == name {
			return id
		}
	}
	return -1
}

// findBenchmark returns the id of the most recently created benchmark with the
// given name, or -1 if no such benchmark exists.
func (b *ReportBuilder) findBenchmark(name string) int {
	// check if this benchmark was lastId
	if bm, ok := b.benchmarks[b.lastId]; ok && bm.Name == name {
		return b.lastId
	}
	for id := len(b.benchmarks); id >= 0; id-- {
		if b.benchmarks[id].Name == name {
			return id
		}
	}
	return -1
}

// containsFailingTest return true if the current list of tests contains at
// least one failing test or an unknown result.
func (b *ReportBuilder) containsFailingTest() bool {
	for _, test := range b.tests {
		if test.Result == Fail || test.Result == Unknown {
			return true
		}
	}
	return false
}

// parseResult returns a Result for the given string r.
func parseResult(r string) Result {
	switch r {
	case "PASS":
		return Pass
	case "FAIL":
		return Fail
	case "SKIP":
		return Skip
	default:
		return Unknown
	}
}
<commit_msg>gtr: Remove unused findBenchmark function<commit_after>package gtr

import (
	"time"
)

// ReportBuilder helps build a test Report from a collection of events.
//
// The ReportBuilder keeps track of the active context whenever a test,
// benchmark or build error is created. This is necessary because the test
// parser do not contain any state themselves and simply just emit an event for
// every line that is read. By tracking the active context, any output that is
// appended to the ReportBuilder gets attributed to the correct test, benchmark
// or build error.
type ReportBuilder struct {
	packages    []Package
	tests       map[int]Test
	benchmarks  map[int]Benchmark
	buildErrors map[int]Error
	runErrors   map[int]Error

	// state
	nextId   int      // next free unused id
	lastId   int      // most recently created id
	output   []string // output that does not belong to any test
	coverage float64  // coverage percentage

	// default values
	packageName string
}

// NewReportBuilder creates a new ReportBuilder.
func NewReportBuilder(packageName string) *ReportBuilder {
	return &ReportBuilder{
		tests:       make(map[int]Test),
		benchmarks:  make(map[int]Benchmark),
		buildErrors: make(map[int]Error),
		runErrors:   make(map[int]Error),
		nextId:      1,
		packageName: packageName,
	}
}

// newId returns a new unique id and sets the active context this id.
func (b *ReportBuilder) newId() int {
	id := b.nextId
	b.lastId = id
	b.nextId += 1
	return id
}

// flush creates a new package in this report containing any tests or
// benchmarks we've collected so far. This is necessary when a test or
// benchmark did not end with a summary.
func (b *ReportBuilder) flush() {
	if len(b.tests) > 0 || len(b.benchmarks) > 0 {
		b.CreatePackage(b.packageName, "", 0, "")
	}
}

// Build returns the new Report containing all the tests, benchmarks and output
// created so far.
func (b *ReportBuilder) Build() Report {
	b.flush()
	return Report{Packages: b.packages}
}

// CreateTest adds a test with the given name to the report, and marks it as
// active.
func (b *ReportBuilder) CreateTest(name string) {
	b.tests[b.newId()] = Test{Name: name}
}

// PauseTest marks the active context as no longer active. Any results or
// output added to the report after calling PauseTest will no longer be assumed
// to belong to this test.
func (b *ReportBuilder) PauseTest(name string) {
	b.lastId = 0
}

// ContinueTest finds the test with the given name and marks it as active. If
// more than one test exist with this name, the most recently created test will
// be used.
func (b *ReportBuilder) ContinueTest(name string) {
	b.lastId = b.findTest(name)
}

// EndTest finds the test with the given name, sets the result, duration and
// level, and marks it as active. If more than one test exists with this name,
// the most recently created test will be used. If no test exists with this
// name, a new test is created.
func (b *ReportBuilder) EndTest(name, result string, duration time.Duration, level int) {
	b.lastId = b.findTest(name)
	if b.lastId < 0 {
		// test did not exist, create one
		// TODO: Likely reason is that the user ran go test without the -v
		// flag, should we report this somewhere?
		b.CreateTest(name)
	}

	t := b.tests[b.lastId]
	t.Result = parseResult(result)
	t.Duration = duration
	t.Level = level
	b.tests[b.lastId] = t
}

// End marks the active context as no longer active.
func (b *ReportBuilder) End() {
	b.lastId = 0
}

// Benchmark adds a new Benchmark to the report and marks it as active.
func (b *ReportBuilder) Benchmark(name string, iterations int64, nsPerOp, mbPerSec float64, bytesPerOp, allocsPerOp int64) {
	b.benchmarks[b.newId()] = Benchmark{
		Name:        name,
		Result:      Pass,
		Iterations:  iterations,
		NsPerOp:     nsPerOp,
		MBPerSec:    mbPerSec,
		BytesPerOp:  bytesPerOp,
		AllocsPerOp: allocsPerOp,
	}
}

// CreateBuildError creates a new build error and marks it as active.
func (b *ReportBuilder) CreateBuildError(packageName string) {
	b.buildErrors[b.newId()] = Error{Name: packageName}
}

// CreatePackage adds a new package with the given name to the Report. This
// package contains all the build errors, output, tests and benchmarks created
// so far. Afterwards all state is reset.
func (b *ReportBuilder) CreatePackage(name, result string, duration time.Duration, data string) {
	pkg := Package{
		Name:     name,
		Duration: duration,
	}

	// Build errors are treated somewhat differently. Rather than having a
	// single package with all build errors collected so far, we only care
	// about the build errors for this particular package.
	for id, buildErr := range b.buildErrors {
		if buildErr.Name == name {
			if len(b.tests) > 0 || len(b.benchmarks) > 0 {
				panic("unexpected tests and/or benchmarks found in build error package")
			}
			buildErr.Duration = duration
			buildErr.Cause = data
			pkg.BuildError = buildErr
			b.packages = append(b.packages, pkg)

			delete(b.buildErrors, id)
			// TODO: reset state
			// TODO: buildErrors shouldn't reset/use nextId/lastId, they're more like a global cache
			return
		}
	}

	// If we've collected output, but there were no tests or benchmarks then
	// either there were no tests, or there was some other non-build error.
	if len(b.output) > 0 && len(b.tests) == 0 && len(b.benchmarks) == 0 {
		if parseResult(result) == Fail {
			pkg.RunError = Error{
				Name:   name,
				Output: b.output,
			}
		}
		b.packages = append(b.packages, pkg)

		// TODO: reset state
		b.output = nil
		return
	}

	// If the summary result says we failed, but there were no failing tests
	// then something else must have failed.
	if parseResult(result) == Fail && (len(b.tests) > 0 || len(b.benchmarks) > 0) && !b.containsFailingTest() {
		pkg.RunError = Error{
			Name:   name,
			Output: b.output,
		}
		b.output = nil
	}

	// Collect tests and benchmarks for this package, maintaining insertion order.
	var tests []Test
	var benchmarks []Benchmark
	for id := 1; id < b.nextId; id++ {
		if t, ok := b.tests[id]; ok {
			tests = append(tests, t)
		}
		if bm, ok := b.benchmarks[id]; ok {
			benchmarks = append(benchmarks, bm)
		}
	}

	pkg.Coverage = b.coverage
	pkg.Output = b.output
	pkg.Tests = tests
	pkg.Benchmarks = benchmarks
	b.packages = append(b.packages, pkg)

	// reset state
	b.nextId = 1
	b.lastId = 0
	b.output = nil
	b.coverage = 0
	b.tests = make(map[int]Test)
	b.benchmarks = make(map[int]Benchmark)
}

// Coverage sets the code coverage percentage.
func (b *ReportBuilder) Coverage(pct float64, packages []string) {
	b.coverage = pct
}

// AppendOutput appends the given line to the currently active context. If no
// active context exists, the output is assumed to belong to the package.
func (b *ReportBuilder) AppendOutput(line string) {
	// TODO(jstemmer): check if output is potentially a build error
	if b.lastId <= 0 {
		b.output = append(b.output, line)
		return
	}

	if t, ok := b.tests[b.lastId]; ok {
		t.Output = append(t.Output, line)
		b.tests[b.lastId] = t
	} else if bm, ok := b.benchmarks[b.lastId]; ok {
		bm.Output = append(bm.Output, line)
		b.benchmarks[b.lastId] = bm
	} else if be, ok := b.buildErrors[b.lastId]; ok {
		be.Output = append(be.Output, line)
		b.buildErrors[b.lastId] = be
	} else {
		b.output = append(b.output, line)
	}
}

// findTest returns the id of the most recently created test with the given
// name, or -1 if no such test exists.
func (b *ReportBuilder) findTest(name string) int {
	// check if this test was lastId
	if t, ok := b.tests[b.lastId]; ok && t.Name == name {
		return b.lastId
	}
	for id := len(b.tests); id >= 0; id-- {
		if b.tests[id].Name == name {
			return id
		}
	}
	return -1
}

// containsFailingTest return true if the current list of tests contains at
// least one failing test or an unknown result.
func (b *ReportBuilder) containsFailingTest() bool {
	for _, test := range b.tests {
		if test.Result == Fail || test.Result == Unknown {
			return true
		}
	}
	return false
}

// parseResult returns a Result for the given string r.
func parseResult(r string) Result {
	switch r {
	case "PASS":
		return Pass
	case "FAIL":
		return Fail
	case "SKIP":
		return Skip
	default:
		return Unknown
	}
}
<|endoftext|>


<commit_before>package handler

import (
	"crypto/rand"
	"io"
	"log"
	"net/http"
	"time"

	"github.com/dchest/authcookie"
	"github.com/dchest/passwordreset"
	"github.com/drone/drone/pkg/database"
	"github.com/drone/drone/pkg/mail"
	. "github.com/drone/drone/pkg/model"
)

var (
	// Secret key used to sign auth cookies,
	// password reset tokens, etc.
	secret = generateRandomKey(256)
)

// GenerateRandomKey creates a random key of size length bytes
func generateRandomKey(strength int) []byte {
	k := make([]byte, strength)
	if _, err := io.ReadFull(rand.Reader, k); err != nil {
		return nil
	}
	return k
}

// Returns an HTML index.html page if the user is
// not currently authenticated, otherwise redirects
// the user to their personal dashboard screen
func Index(w http.ResponseWriter, r *http.Request) error {
	// is the user already authenticated then
	// redirect to the dashboard page
	if _, err := r.Cookie("_sess"); err == nil {
		http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
		return nil
	}

	// otherwise redirect to the login page
	http.Redirect(w, r, "/login", http.StatusSeeOther)
	return nil
}

// Return an HTML form for the User to login.
func Login(w http.ResponseWriter, r *http.Request) error {
	return RenderTemplate(w, "login.html", nil)
}

// Terminate the User session.
func Logout(w http.ResponseWriter, r *http.Request) error {
	DelCookie(w, r, "_sess")

	http.Redirect(w, r, "/login", http.StatusSeeOther)
	return nil
}

// Return an HTML form for the User to request a password reset.
func Forgot(w http.ResponseWriter, r *http.Request) error {
	return RenderTemplate(w, "forgot.html", nil)
}

// Return an HTML form for the User to perform a password reset.
// This page must be visited from a Password Reset email that
// contains a hash to verify the User's identity.
func Reset(w http.ResponseWriter, r *http.Request) error {
	return RenderTemplate(w, "reset.html", &struct{ Error string }{""})
}

// Return an HTML form to register for a new account. This
// page must be visited from a Signup email that contains
// a hash to verify the Email address is correct.
func Register(w http.ResponseWriter, r *http.Request) error {
	return RenderTemplate(w, "register.html", &struct{ Error string }{""})
}

func ForgotPost(w http.ResponseWriter, r *http.Request) error {
	email := r.FormValue("email")

	// attempt to retrieve the user by email address
	user, err := database.GetUserEmail(email)
	if err != nil {
		log.Printf("could not find user %s to reset password. %s", email, err)
		// if we can't find the email, we still display
		// the template to the user. This prevents someone
		// from trying to guess passwords through trial & error
		return RenderTemplate(w, "forgot_sent.html", nil)
	}

	// hostname from settings
	hostname := database.SettingsMust().URL().String()

	// generate the password reset token
	token := passwordreset.NewToken(user.Email, 12*time.Hour, []byte(user.Password), secret)
	data := struct {
		Host  string
		User  *User
		Token string
	}{hostname, user, token}

	// send the email message async
	go func() {
		if err := mail.SendPassword(email, data); err != nil {
			log.Printf("error sending password reset email to %s. %s", email, err)
		}
	}()

	// render the template indicating a success
	return RenderTemplate(w, "forgot_sent.html", nil)
}

func ResetPost(w http.ResponseWriter, r *http.Request) error {
	// verify the token and extract the username
	token := r.FormValue("token")
	email, err := passwordreset.VerifyToken(token, database.GetPassEmail, secret)
	if err != nil {
		return RenderTemplate(w, "reset.html", &struct{ Error string }{"Your password reset request is expired."})
	}

	// get the user from the database
	user, err := database.GetUserEmail(email)
	if err != nil {
		return RenderTemplate(w, "reset.html", &struct{ Error string }{"Unable to locate user account."})
	}

	// get the new password
	password := r.FormValue("password")
	if err := user.SetPassword(password); err != nil {
		return RenderTemplate(w, "reset.html", &struct{ Error string }{err.Error()})
	}

	// save to the database
	if err := database.SaveUser(user); err != nil {
		return RenderTemplate(w, "reset.html", &struct{ Error string }{"Unable to update password. Please try again"})
	}

	// add the user to the session object
	//session, _ := store.Get(r, "_sess")
	//session.Values["username"] = user.Email
	//session.Save(r, w)
	SetCookie(w, r, "_sess", user.Email)

	http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
	return nil
}

func RegisterPost(w http.ResponseWriter, r *http.Request) error {
	// verify the token and extract the username
	token := r.FormValue("token")
	email := authcookie.Login(token, secret)
	if len(email) == 0 {
		return RenderTemplate(w, "register.html", &struct{ Error string }{"Your registration email is expired."})
	}

	// set the email and name
	user := User{}
	user.SetEmail(email)
	user.Name = r.FormValue("name")

	// set the new password
	password := r.FormValue("password")
	if err := user.SetPassword(password); err != nil {
		return RenderTemplate(w, "register.html", &struct{ Error string }{err.Error()})
	}

	// verify fields are correct
	if err := user.Validate(); err != nil {
		return RenderTemplate(w, "register.html", &struct{ Error string }{err.Error()})
	}

	// save to the database
	if err := database.SaveUser(&user); err != nil {
		return err
	}

	// add the user to the session object
	SetCookie(w, r, "_sess", user.Email)

	// redirect the user to their dashboard
	http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
	return nil
}
<commit_msg>fixed issue #15<commit_after>package handler

import (
	"crypto/rand"
	"io"
	"log"
	"net/http"
	"time"

	"github.com/dchest/authcookie"
	"github.com/dchest/passwordreset"
	"github.com/drone/drone/pkg/database"
	"github.com/drone/drone/pkg/mail"
	. "github.com/drone/drone/pkg/model"
)

var (
	// Secret key used to sign auth cookies,
	// password reset tokens, etc.
	secret = generateRandomKey(256)
)

// GenerateRandomKey creates a random key of size length bytes
func generateRandomKey(strength int) []byte {
	k := make([]byte, strength)
	if _, err := io.ReadFull(rand.Reader, k); err != nil {
		return nil
	}
	return k
}

// Returns an HTML index.html page if the user is
// not currently authenticated, otherwise redirects
// the user to their personal dashboard screen
func Index(w http.ResponseWriter, r *http.Request) error {
	// is the user already authenticated then
	// redirect to the dashboard page
	if _, err := r.Cookie("_sess"); err == nil {
		http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
		return nil
	}

	// otherwise redirect to the login page
	http.Redirect(w, r, "/login", http.StatusSeeOther)
	return nil
}

// Return an HTML form for the User to login.
func Login(w http.ResponseWriter, r *http.Request) error {
	return RenderTemplate(w, "login.html", nil)
}

// Terminate the User session.
func Logout(w http.ResponseWriter, r *http.Request) error {
	DelCookie(w, r, "_sess")

	http.Redirect(w, r, "/login", http.StatusSeeOther)
	return nil
}

// Return an HTML form for the User to request a password reset.
func Forgot(w http.ResponseWriter, r *http.Request) error {
	return RenderTemplate(w, "forgot.html", nil)
}

// Return an HTML form for the User to perform a password reset.
// This page must be visited from a Password Reset email that
// contains a hash to verify the User's identity.
func Reset(w http.ResponseWriter, r *http.Request) error {
	return RenderTemplate(w, "reset.html", &struct{ Error string }{""})
}

// Return an HTML form to register for a new account. This
// page must be visited from a Signup email that contains
// a hash to verify the Email address is correct.
func Register(w http.ResponseWriter, r *http.Request) error {
	return RenderTemplate(w, "register.html", &struct{ Error string }{""})
}

func ForgotPost(w http.ResponseWriter, r *http.Request) error {
	email := r.FormValue("email")

	// attempt to retrieve the user by email address
	user, err := database.GetUserEmail(email)
	if err != nil {
		log.Printf("could not find user %s to reset password. %s", email, err)
		// if we can't find the email, we still display
		// the template to the user. This prevents someone
		// from trying to guess passwords through trial & error
		return RenderTemplate(w, "forgot_sent.html", nil)
	}

	// hostname from settings
	hostname := database.SettingsMust().URL().String()

	// generate the password reset token
	token := passwordreset.NewToken(user.Email, 12*time.Hour, []byte(user.Password), secret)
	data := struct {
		Host  string
		User  *User
		Token string
	}{hostname, user, token}

	// send the email message async
	go func() {
		if err := mail.SendPassword(email, data); err != nil {
			log.Printf("error sending password reset email to %s. %s", email, err)
		}
	}()

	// render the template indicating a success
	return RenderTemplate(w, "forgot_sent.html", nil)
}

func ResetPost(w http.ResponseWriter, r *http.Request) error {
	// verify the token and extract the username
	token := r.FormValue("token")
	email, err := passwordreset.VerifyToken(token, database.GetPassEmail, secret)
	if err != nil {
		return RenderTemplate(w, "reset.html", &struct{ Error string }{"Your password reset request is expired."})
	}

	// get the user from the database
	user, err := database.GetUserEmail(email)
	if err != nil {
		return RenderTemplate(w, "reset.html", &struct{ Error string }{"Unable to locate user account."})
	}

	// get the new password
	password := r.FormValue("password")
	if err := user.SetPassword(password); err != nil {
		return RenderTemplate(w, "reset.html", &struct{ Error string }{err.Error()})
	}

	// save to the database
	if err := database.SaveUser(user); err != nil {
		return RenderTemplate(w, "reset.html", &struct{ Error string }{"Unable to update password. Please try again"})
	}

	// add the user to the session object
	SetCookie(w, r, "_sess", user.Email)

	http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
	return nil
}

func RegisterPost(w http.ResponseWriter, r *http.Request) error {
	// verify the token and extract the username
	token := r.FormValue("token")
	email := authcookie.Login(token, secret)
	if len(email) == 0 {
		return RenderTemplate(w, "register.html", &struct{ Error string }{"Your registration email is expired."})
	}

	// set the email and name
	user := NewUser(r.FormValue("name"), email)

	// set the new password
	password := r.FormValue("password")
	if err := user.SetPassword(password); err != nil {
		return RenderTemplate(w, "register.html", &struct{ Error string }{err.Error()})
	}

	// verify fields are correct
	if err := user.Validate(); err != nil {
		return RenderTemplate(w, "register.html", &struct{ Error string }{err.Error()})
	}

	// save to the database
	if err := database.SaveUser(user); err != nil {
		return err
	}

	// add the user to the session object
	SetCookie(w, r, "_sess", user.Email)

	// redirect the user to their dashboard
	http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
	return nil
}
<|endoftext|>


<commit_before>//
// file.go
//
// Created by Frederic DELBOS - fred@hyperboloide.com on Nov  9 2014.
//

package rw

import (
	"io"
	"os"
	"path/filepath"
)

type File struct {
	Dir  string `json:"dir"`
	Name string `json:"name"`
}

func (s *File) join(path string) string {
	return filepath.Join(s.Dir, path)
}

func (s *File) GetName() string {
	return s.Name
}

func (s *File) Init() error {
	if s.Dir == "" {
		return RwError(s, "attribute 'dir' is empty")
	}
	if err := os.MkdirAll(s.Dir, 0755); err != nil {
		return RwError(s, err.Error())
	}
	return nil
}

func (s *File) NewWriter(id string, d *Data) (io.WriteCloser, error) {
	file, err := os.OpenFile(s.join(id), os.O_RDWR|os.O_CREATE, 0655)
	if err != nil {
		return nil, RwError(s, err.Error())
	}
	return file, nil
}

func (s *File) NewReader(id string, d *Data) (io.ReadCloser, error) {
	file, err := os.OpenFile(s.join(id), os.O_RDONLY, 0444)
	if err != nil {
		return nil, RwError(s, err.Error())
	}
	return file, nil
}

func (s *File) Delete(id string, d *Data) error {
	if err := os.Remove(s.join(id)); err != nil {
		return RwError(s, err.Error())
	}
	return nil
}
<commit_msg>clean errors<commit_after>//
// file.go
//
// Created by Frederic DELBOS - fred@hyperboloide.com on Nov  9 2014.
//

package rw

import (
	"errors"
	"io"
	"os"
	"path/filepath"
)

type File struct {
	Dir  string `json:"dir"`
	Name string `json:"name"`
}

func (s *File) join(path string) string {
	return filepath.Join(s.Dir, path)
}

func (s *File) GetName() string {
	return s.Name
}

func (s *File) Init() error {
	if s.Dir == "" {
		return errors.New("attribute 'dir' is empty")
	}
	if err := os.MkdirAll(s.Dir, 0755); err != nil {
		return err
	}
	return nil
}

func (s *File) NewWriter(id string, d *Data) (io.WriteCloser, error) {
	return os.OpenFile(s.join(id), os.O_RDWR|os.O_CREATE, 0655)
}

func (s *File) NewReader(id string, d *Data) (io.ReadCloser, error) {
	return os.OpenFile(s.join(id), os.O_RDONLY, 0444)
}

func (s *File) Delete(id string, d *Data) error {
	return os.Remove(s.join(id))
}
<|endoftext|>


<commit_before>/*
 * Minimalist Object Storage, (C) 2015 Minio, Inc.
 *
 * 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 probe implements a simple mechanism to trace and return errors in large programs.
package probe

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"

	"github.com/dustin/go-humanize"
)

// GetSysInfo returns useful system statistics.
func GetSysInfo() map[string]string {
	host, err := os.Hostname()
	if err != nil {
		host = ""
	}
	memstats := &runtime.MemStats{}
	runtime.ReadMemStats(memstats)
	return map[string]string{
		"host.name":      host,
		"host.os":        runtime.GOOS,
		"host.arch":      runtime.GOARCH,
		"host.lang":      runtime.Version(),
		"host.cpus":      strconv.Itoa(runtime.NumCPU()),
		"mem.used":       humanize.Bytes(memstats.Alloc),
		"mem.total":      humanize.Bytes(memstats.Sys),
		"mem.heap.used":  humanize.Bytes(memstats.HeapAlloc),
		"mem.heap.total": humanize.Bytes(memstats.HeapSys),
	}
}

type TracePoint struct {
	Line     int                 `json:"Line,omitempty"`
	Filename string              `json:"File,omitempty"`
	Function string              `json:"Func,omitempty"`
	Env      map[string][]string `json:"Env,omitempty"`
}

// Error implements tracing error functionality.
type Error struct {
	lock      sync.RWMutex
	Cause     error             `json:"cause,omitempty"`
	CallTrace []TracePoint      `json:"trace,omitempty"`
	SysInfo   map[string]string `json:"sysinfo,omitempty"`
}

// NewError function instantiates an error probe for tracing. Original errors.error (golang's error
// interface) is injected in only once during this New call. Rest of the time, you
// trace the return path with Probe.Trace and finally handle reporting or quitting
// at the top level.
func NewError(e error) *Error {
	if e == nil {
		return nil
	}
	Err := Error{lock: sync.RWMutex{}, Cause: e, CallTrace: []TracePoint{}, SysInfo: GetSysInfo()}
	return Err.trace()
}

// Trace records the point at which it is invoked. Stack traces are important for
// debugging purposes.
func (e *Error) Trace(fields ...string) *Error {
	if e == nil {
		return nil
	}

	e.lock.Lock()
	defer e.lock.Unlock()

	return e.trace(fields...)
}

// internal trace - records the point at which it is invoked. Stack traces are important for
// debugging purposes.
func (e *Error) trace(fields ...string) *Error {
	pc, file, line, _ := runtime.Caller(2)
	function := runtime.FuncForPC(pc).Name()
	_, function = filepath.Split(function)
	file = "..." + strings.TrimPrefix(file, os.Getenv("GOPATH")) // trim gopathSource from file
	tp := TracePoint{}
	if len(fields) > 0 {
		tp = TracePoint{Line: line, Filename: file, Function: function, Env: map[string][]string{"Tags": fields}}
	} else {
		tp = TracePoint{Line: line, Filename: file, Function: function}
	}
	e.CallTrace = append(e.CallTrace, tp)
	return e
}

// Untrace erases last trace entry.
func (e *Error) Untrace() {
	if e == nil {
		return
	}
	e.lock.Lock()
	defer e.lock.Unlock()

	l := len(e.CallTrace)
	if l == 0 {
		return
	}
	e.CallTrace = e.CallTrace[:l-1]
}

// ToGoError returns original error message.
func (e *Error) ToGoError() error {
	return e.Cause
}

// String returns error message.
func (e *Error) String() string {
	if e == nil || e.Cause == nil {
		return "<nil>"
	}
	e.lock.RLock()
	defer e.lock.RUnlock()

	if e.Cause != nil {
		str := e.Cause.Error() + "\n"
		callLen := len(e.CallTrace)
		for i := callLen - 1; i >= 0; i-- {
			if len(e.CallTrace[i].Env) > 0 {
				str += fmt.Sprintf(" (%d) %s:%d %s(..) Tags: [%s]\n",
					i, e.CallTrace[i].Filename, e.CallTrace[i].Line, e.CallTrace[i].Function, strings.Join(e.CallTrace[i].Env["Tags"], ", "))
			} else {
				str += fmt.Sprintf(" (%d) %s:%d %s(..)\n",
					i, e.CallTrace[i].Filename, e.CallTrace[i].Line, e.CallTrace[i].Function)
			}
		}

		str += " Host:" + e.SysInfo["host.name"] + " | "
		str += "OS:" + e.SysInfo["host.os"] + " | "
		str += "Arch:" + e.SysInfo["host.arch"] + " | "
		str += "Lang:" + e.SysInfo["host.lang"] + " | "
		str += "Mem:" + e.SysInfo["mem.used"] + "/" + e.SysInfo["mem.total"] + " | "
		str += "Heap:" + e.SysInfo["mem.heap.used"] + "/" + e.SysInfo["mem.heap.total"]

		return str
	}
	return "<nil>"
}
<commit_msg>return *probe.Error for Untrace() as well.<commit_after>/*
 * Minimalist Object Storage, (C) 2015 Minio, Inc.
 *
 * 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 probe implements a simple mechanism to trace and return errors in large programs.
package probe

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"

	"github.com/dustin/go-humanize"
)

// GetSysInfo returns useful system statistics.
func GetSysInfo() map[string]string {
	host, err := os.Hostname()
	if err != nil {
		host = ""
	}
	memstats := &runtime.MemStats{}
	runtime.ReadMemStats(memstats)
	return map[string]string{
		"host.name":      host,
		"host.os":        runtime.GOOS,
		"host.arch":      runtime.GOARCH,
		"host.lang":      runtime.Version(),
		"host.cpus":      strconv.Itoa(runtime.NumCPU()),
		"mem.used":       humanize.Bytes(memstats.Alloc),
		"mem.total":      humanize.Bytes(memstats.Sys),
		"mem.heap.used":  humanize.Bytes(memstats.HeapAlloc),
		"mem.heap.total": humanize.Bytes(memstats.HeapSys),
	}
}

type TracePoint struct {
	Line     int                 `json:"Line,omitempty"`
	Filename string              `json:"File,omitempty"`
	Function string              `json:"Func,omitempty"`
	Env      map[string][]string `json:"Env,omitempty"`
}

// Error implements tracing error functionality.
type Error struct {
	lock      sync.RWMutex
	Cause     error             `json:"cause,omitempty"`
	CallTrace []TracePoint      `json:"trace,omitempty"`
	SysInfo   map[string]string `json:"sysinfo,omitempty"`
}

// NewError function instantiates an error probe for tracing. Original errors.error (golang's error
// interface) is injected in only once during this New call. Rest of the time, you
// trace the return path with Probe.Trace and finally handle reporting or quitting
// at the top level.
func NewError(e error) *Error {
	if e == nil {
		return nil
	}
	Err := Error{lock: sync.RWMutex{}, Cause: e, CallTrace: []TracePoint{}, SysInfo: GetSysInfo()}
	return Err.trace()
}

// Trace records the point at which it is invoked. Stack traces are important for
// debugging purposes.
func (e *Error) Trace(fields ...string) *Error {
	if e == nil {
		return nil
	}

	e.lock.Lock()
	defer e.lock.Unlock()

	return e.trace(fields...)
}

// internal trace - records the point at which it is invoked. Stack traces are important for
// debugging purposes.
func (e *Error) trace(fields ...string) *Error {
	pc, file, line, _ := runtime.Caller(2)
	function := runtime.FuncForPC(pc).Name()
	_, function = filepath.Split(function)
	file = "..." + strings.TrimPrefix(file, os.Getenv("GOPATH")) // trim gopathSource from file
	tp := TracePoint{}
	if len(fields) > 0 {
		tp = TracePoint{Line: line, Filename: file, Function: function, Env: map[string][]string{"Tags": fields}}
	} else {
		tp = TracePoint{Line: line, Filename: file, Function: function}
	}
	e.CallTrace = append(e.CallTrace, tp)
	return e
}

// Untrace erases last trace entry.
func (e *Error) Untrace() *Error {
	if e == nil {
		return nil
	}
	e.lock.Lock()
	defer e.lock.Unlock()

	l := len(e.CallTrace)
	if l == 0 {
		return nil
	}
	e.CallTrace = e.CallTrace[:l-1]
	return e
}

// ToGoError returns original error message.
func (e *Error) ToGoError() error {
	return e.Cause
}

// String returns error message.
func (e *Error) String() string {
	if e == nil || e.Cause == nil {
		return "<nil>"
	}
	e.lock.RLock()
	defer e.lock.RUnlock()

	if e.Cause != nil {
		str := e.Cause.Error() + "\n"
		callLen := len(e.CallTrace)
		for i := callLen - 1; i >= 0; i-- {
			if len(e.CallTrace[i].Env) > 0 {
				str += fmt.Sprintf(" (%d) %s:%d %s(..) Tags: [%s]\n",
					i, e.CallTrace[i].Filename, e.CallTrace[i].Line, e.CallTrace[i].Function, strings.Join(e.CallTrace[i].Env["Tags"], ", "))
			} else {
				str += fmt.Sprintf(" (%d) %s:%d %s(..)\n",
					i, e.CallTrace[i].Filename, e.CallTrace[i].Line, e.CallTrace[i].Function)
			}
		}

		str += " Host:" + e.SysInfo["host.name"] + " | "
		str += "OS:" + e.SysInfo["host.os"] + " | "
		str += "Arch:" + e.SysInfo["host.arch"] + " | "
		str += "Lang:" + e.SysInfo["host.lang"] + " | "
		str += "Mem:" + e.SysInfo["mem.used"] + "/" + e.SysInfo["mem.total"] + " | "
		str += "Heap:" + e.SysInfo["mem.heap.used"] + "/" + e.SysInfo["mem.heap.total"]

		return str
	}
	return "<nil>"
}
<|endoftext|>