<commit_before>package bot

import (
	"bytes"
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/demisto/alfred/conf"
	"github.com/demisto/alfred/domain"
	"github.com/demisto/alfred/queue"
	"github.com/demisto/alfred/repo"
	"github.com/demisto/goxforce"
	"github.com/slavikm/govt"
)

const (
	numOfPositivesToConvict = 3
	xfeScoreToConvict       = 3
)

// Worker reads messages from the queue and does the actual work
type Worker struct {
	q    queue.Queue
	c    chan *domain.WorkRequest
	r    repo.Repo
	xfe  *goxforce.Client
	vt   *govt.Client
	clam *clamEngine
}

// NewWorker that loads work messages from the queue
func NewWorker(r repo.Repo, q queue.Queue) (*Worker, error) {
	xfe, err := goxforce.New(goxforce.SetErrorLog(log.New(conf.LogWriter, "XFE:", log.Lshortfile)))
	if err != nil {
		return nil, err
	}
	vt, err := govt.New(govt.SetApikey(conf.Options.VT), govt.SetErrorLog(log.New(os.Stderr, "VT:", log.Lshortfile)))
	if err != nil {
		return nil, err
	}
	clam, err := newClamEngine()
	if err != nil {
		return nil, err
	}
	return &Worker{
		r:    r,
		q:    q,
		c:    make(chan *domain.WorkRequest, runtime.NumCPU()),
		xfe:  xfe,
		vt:   vt,
		clam: clam,
	}, nil
}

func (w *Worker) handle() {
	for msg := range w.c {
		if msg == nil {
			w.clam.close()
			return
		}
		if msg.ReplyQueue == "" {
			logrus.Warnf("Got message without a reply queue destination %+v\n", msg)
			continue
		}
		reply := &domain.WorkReply{Context: msg.Context, MessageID: msg.MessageID}
		switch msg.Type {
		case "message":
			if strings.Contains(msg.Text, "<http") {
				w.handleURL(msg.Text, reply)
			}
			if ip := ipReg.FindString(msg.Text); ip != "" {
				w.handleIP(ip, reply)
			}
			if hash := md5Reg.FindString(msg.Text); hash != "" {
				w.handleMD5(hash, reply)
			}
		case "file":
			w.handleFile(msg, reply)
		}
		if err := w.q.PushWorkReply(msg.ReplyQueue, reply); err != nil {
			logrus.Warnf("Error pushing message to reply queue %+v - %v\n", msg, err)
		}
	}
}

// Start the dedup process. To stop, just close the queue.
func (w *Worker) Start() {
	// Right now, just use the numebr of CPUs
	for i := 0; i < runtime.NumCPU(); i++ {
		go w.handle()
	}
	for {
		msg, err := w.q.PopWork(0)
		if err != nil || msg == nil {
			logrus.Infof("Stoping WorkManager process - %v, %v", err, msg)
			close(w.c)
			return
		}
		logrus.Debugf("Working on message - %+v", msg)
		w.c <- msg
	}
}

func contextFromMap(c map[string]interface{}) *domain.Context {
	return &domain.Context{
		Team:         c["team"].(string),
		User:         c["user"].(string),
		OriginalUser: c["original_user"].(string),
		Channel:      c["channel"].(string),
	}
}

// GetContext from a message based on actual type
func GetContext(context interface{}) (*domain.Context, error) {
	switch c := context.(type) {
	case *domain.Context:
		// Hack to duplicate the context so if we are using channels not to override it
		cx := *c
		return &cx, nil
	case map[string]interface{}:
		return contextFromMap(c), nil
	default:
		return nil, errors.New("Unknown context")
	}
}

func (w *Worker) handleURL(text string, reply *domain.WorkReply) {
	start := strings.Index(text, "<http")
	end := strings.Index(text[start:], ">")
	if end > 0 {
		end = end + start
		filter := strings.Index(text[start:end], "|")
		if filter > 0 {
			end = start + filter
		}
		url := text[start+1 : end]
		logrus.Debugf("URL found - %s\n", url)
		reply.URL.Details = url
		reply.Type |= domain.ReplyTypeURL
		// Do the network commands in parallel
		c := make(chan int, 2)
		go func() {
			urlResp, err := w.xfe.URL(url)
			if err != nil {
				// Small hack - see if the URL was not found
				if strings.Contains(err.Error(), "404") {
					reply.URL.XFE.NotFound = true
				} else {
					reply.URL.XFE.Error = err.Error()
				}
			} else {
				reply.URL.XFE.URLDetails = urlResp.Result
			}
			resolve, err := w.xfe.Resolve(url)
			if err == nil {
				reply.URL.XFE.Resolve = *resolve
			}
			c <- 1
		}()
		go func() {
			vtResp, err := w.vt.GetUrlReport(url)
			if err != nil {
				reply.URL.VT.Error = err.Error()
			} else {
				reply.URL.VT.URLReport = *vtResp
			}
			c <- 1
		}()
		for i := 0; i < 2; i++ {
			<-c
		}
		if reply.URL.XFE.URLDetails.Score > xfeScoreToConvict || reply.URL.VT.URLReport.Positives > numOfPositivesToConvict {
			// This is known bad scenario
			reply.URL.Result = domain.ResultDirty
		} else if !reply.MD5.XFE.NotFound || reply.MD5.VT.FileReport.ResponseCode == 1 {
			// At least one of reputation services found this to be known good
			// Keep the default
			reply.URL.Result = domain.ResultClean
		}
	}
}

func (w *Worker) handleIP(ip string, reply *domain.WorkReply) {
	reply.IP.Details = ip
	reply.Type |= domain.ReplyTypeIP
	// First, let's check if IP is globally unicast addressable and is public
	ipData := net.ParseIP(ip)
	ipv4 := ipData.To4()
	if ipv4 == nil {
		// If not IPv4 then return - by default it will be marked clean
		return
	}
	if !ipv4.IsGlobalUnicast() {
		// If not global unicast ignore
		return
	}
	// Private networks
	if ipv4[0] == 10 || ipv4[0] == 172 && ipv4[1] >= 16 && ipv4[1] <= 31 || ipv4[0] == 192 && ipv4[1] == 168 {
		return
	}
	c := make(chan int, 2)
	go func() {
		ipResp, err := w.xfe.IPR(ip)
		if err != nil {
			// Small hack - see if the URL was not found
			if strings.Contains(err.Error(), "404") {
				reply.IP.XFE.NotFound = true
			} else {
				reply.IP.XFE.Error = err.Error()
			}
		} else {
			reply.IP.XFE.IPReputation = *ipResp
		}
		c <- 1
	}()
	go func() {
		vtResp, err := w.vt.GetIpReport(ip)
		if err != nil {
			reply.IP.VT.Error = err.Error()
		} else {
			reply.IP.VT.IPReport = *vtResp
		}
		c <- 1
	}()
	for i := 0; i < 2; i++ {
		<-c
	}
	var vtPositives uint16
	now := time.Now()
	for i := range reply.IP.VT.IPReport.DetectedUrls {
		t, err := time.Parse("2006-01-02 15:04:05", reply.IP.VT.IPReport.DetectedUrls[i].ScanDate)
		if err != nil {
			logrus.Debugf("Error parsing scan date - %v", err)
			continue
		}
		if reply.IP.VT.IPReport.DetectedUrls[i].Positives > vtPositives && t.Add(365*24*time.Hour).After(now) {
			vtPositives = reply.IP.VT.IPReport.DetectedUrls[i].Positives
		}
	}
	reply.IP.Result = domain.ResultUnknown
	if reply.IP.XFE.IPReputation.Score > xfeScoreToConvict || vtPositives > numOfPositivesToConvict && reply.IP.XFE.NotFound {
		// This is known bad scenario
		reply.IP.Result = domain.ResultDirty
	} else if !reply.MD5.XFE.NotFound || reply.MD5.VT.FileReport.ResponseCode == 1 {
		// At least one of reputation services found this to be known good
		// Keep the default
		reply.IP.Result = domain.ResultClean
	}
}

func (w *Worker) handleMD5(md5 string, reply *domain.WorkReply) {
	reply.Type |= domain.ReplyTypeMD5
	reply.MD5.Details = md5
	c := make(chan int, 2)
	go func() {
		md5Resp, err := w.xfe.MalwareDetails(md5)
		if err != nil {
			// Small hack - see if the file was not found
			if strings.Contains(err.Error(), "404") {
				reply.MD5.XFE.NotFound = true
			} else {
				reply.MD5.XFE.Error = err.Error()
			}
		} else {
			reply.MD5.XFE.Malware = md5Resp.Malware
		}
		c <- 1
	}()
	go func() {
		vtResp, err := w.vt.GetFileReport(md5)
		if err != nil {
			reply.MD5.VT.Error = err.Error()
		} else {
			reply.MD5.VT.FileReport = *vtResp
		}
		c <- 1
	}()
	for i := 0; i < 2; i++ {
		<-c
	}
	reply.MD5.Result = domain.ResultUnknown
	if len(reply.MD5.XFE.Malware.Family) > 0 || reply.MD5.VT.FileReport.Positives > numOfPositivesToConvict {
		// This is known bad scenario
		reply.MD5.Result = domain.ResultDirty
	} else if !reply.MD5.XFE.NotFound || reply.MD5.VT.FileReport.ResponseCode == 1 {
		// At least one of reputation services found this to be known good
		// Keep the default
		reply.MD5.Result = domain.ResultClean
	}
}

func (w *Worker) handleFile(request *domain.WorkRequest, reply *domain.WorkReply) {
	reply.Type |= domain.ReplyTypeFile
	reply.File.Details = request.File
	if request.File.Size > 30*1024*1024 {
		logrus.Infof("File %s is bigger than 30M, skipping\n", request.File.Name)
		reply.File.FileTooLarge = true
		return
	}
	hash := md5.New()
	resp, err := http.Get(request.File.URL)
	if err != nil {
		logrus.Errorf("Unable to download file - %v\n", err)
		return
	}
	defer resp.Body.Close()
	buf := &bytes.Buffer{}
	io.Copy(buf, resp.Body)
	io.Copy(hash, bytes.NewReader(buf.Bytes()))
	h := fmt.Sprintf("%x", hash.Sum(nil))
	logrus.Debugf("MD5 for file %s is %s\n", request.File.Name, h)
	// Do the network commands in parallel
	c := make(chan int, 1)
	go func() {
		virus, err := w.clam.scan(request.File.Name, buf.Bytes())
		if (err == nil || err.Error() == "Virus(es) detected") && virus != "" {
			reply.File.Virus = virus
		} else if err != nil {
			reply.File.Error = err.Error()
		}
		c <- 1
	}()
	w.handleMD5(h, reply)
	<-c
	reply.File.Result = domain.ResultUnknown
	if reply.File.Virus != "" || len(reply.MD5.XFE.Malware.Family) > 0 || reply.MD5.VT.FileReport.Positives > numOfPositivesToConvict {
		// This is known bad scenario
		reply.File.Result = domain.ResultDirty
	} else if reply.File.Virus == "" || !reply.MD5.XFE.NotFound || reply.MD5.VT.FileReport.ResponseCode == 1 {
		// At least one of reputation services found this to be known good
		// Keep the default
		reply.File.Result = domain.ResultClean
	}
}
<commit_msg>Increase the score to convict<commit_after>package bot

import (
	"bytes"
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/demisto/alfred/conf"
	"github.com/demisto/alfred/domain"
	"github.com/demisto/alfred/queue"
	"github.com/demisto/alfred/repo"
	"github.com/demisto/goxforce"
	"github.com/slavikm/govt"
)

const (
	numOfPositivesToConvict = 7
	xfeScoreToConvict       = 6
)

// Worker reads messages from the queue and does the actual work
type Worker struct {
	q    queue.Queue
	c    chan *domain.WorkRequest
	r    repo.Repo
	xfe  *goxforce.Client
	vt   *govt.Client
	clam *clamEngine
}

// NewWorker that loads work messages from the queue
func NewWorker(r repo.Repo, q queue.Queue) (*Worker, error) {
	xfe, err := goxforce.New(goxforce.SetErrorLog(log.New(conf.LogWriter, "XFE:", log.Lshortfile)))
	if err != nil {
		return nil, err
	}
	vt, err := govt.New(govt.SetApikey(conf.Options.VT), govt.SetErrorLog(log.New(os.Stderr, "VT:", log.Lshortfile)))
	if err != nil {
		return nil, err
	}
	clam, err := newClamEngine()
	if err != nil {
		return nil, err
	}
	return &Worker{
		r:    r,
		q:    q,
		c:    make(chan *domain.WorkRequest, runtime.NumCPU()),
		xfe:  xfe,
		vt:   vt,
		clam: clam,
	}, nil
}

func (w *Worker) handle() {
	for msg := range w.c {
		if msg == nil {
			w.clam.close()
			return
		}
		if msg.ReplyQueue == "" {
			logrus.Warnf("Got message without a reply queue destination %+v\n", msg)
			continue
		}
		reply := &domain.WorkReply{Context: msg.Context, MessageID: msg.MessageID}
		switch msg.Type {
		case "message":
			if strings.Contains(msg.Text, "<http") {
				w.handleURL(msg.Text, reply)
			}
			if ip := ipReg.FindString(msg.Text); ip != "" {
				w.handleIP(ip, reply)
			}
			if hash := md5Reg.FindString(msg.Text); hash != "" {
				w.handleMD5(hash, reply)
			}
		case "file":
			w.handleFile(msg, reply)
		}
		if err := w.q.PushWorkReply(msg.ReplyQueue, reply); err != nil {
			logrus.Warnf("Error pushing message to reply queue %+v - %v\n", msg, err)
		}
	}
}

// Start the dedup process. To stop, just close the queue.
func (w *Worker) Start() {
	// Right now, just use the numebr of CPUs
	for i := 0; i < runtime.NumCPU(); i++ {
		go w.handle()
	}
	for {
		msg, err := w.q.PopWork(0)
		if err != nil || msg == nil {
			logrus.Infof("Stoping WorkManager process - %v, %v", err, msg)
			close(w.c)
			return
		}
		logrus.Debugf("Working on message - %+v", msg)
		w.c <- msg
	}
}

func contextFromMap(c map[string]interface{}) *domain.Context {
	return &domain.Context{
		Team:         c["team"].(string),
		User:         c["user"].(string),
		OriginalUser: c["original_user"].(string),
		Channel:      c["channel"].(string),
	}
}

// GetContext from a message based on actual type
func GetContext(context interface{}) (*domain.Context, error) {
	switch c := context.(type) {
	case *domain.Context:
		// Hack to duplicate the context so if we are using channels not to override it
		cx := *c
		return &cx, nil
	case map[string]interface{}:
		return contextFromMap(c), nil
	default:
		return nil, errors.New("Unknown context")
	}
}

func (w *Worker) handleURL(text string, reply *domain.WorkReply) {
	start := strings.Index(text, "<http")
	end := strings.Index(text[start:], ">")
	if end > 0 {
		end = end + start
		filter := strings.Index(text[start:end], "|")
		if filter > 0 {
			end = start + filter
		}
		url := text[start+1 : end]
		logrus.Debugf("URL found - %s\n", url)
		reply.URL.Details = url
		reply.Type |= domain.ReplyTypeURL
		// Do the network commands in parallel
		c := make(chan int, 2)
		go func() {
			urlResp, err := w.xfe.URL(url)
			if err != nil {
				// Small hack - see if the URL was not found
				if strings.Contains(err.Error(), "404") {
					reply.URL.XFE.NotFound = true
				} else {
					reply.URL.XFE.Error = err.Error()
				}
			} else {
				reply.URL.XFE.URLDetails = urlResp.Result
			}
			resolve, err := w.xfe.Resolve(url)
			if err == nil {
				reply.URL.XFE.Resolve = *resolve
			}
			c <- 1
		}()
		go func() {
			vtResp, err := w.vt.GetUrlReport(url)
			if err != nil {
				reply.URL.VT.Error = err.Error()
			} else {
				reply.URL.VT.URLReport = *vtResp
			}
			c <- 1
		}()
		for i := 0; i < 2; i++ {
			<-c
		}
		if reply.URL.XFE.URLDetails.Score > xfeScoreToConvict || reply.URL.VT.URLReport.Positives > numOfPositivesToConvict {
			// This is known bad scenario
			reply.URL.Result = domain.ResultDirty
		} else if !reply.MD5.XFE.NotFound || reply.MD5.VT.FileReport.ResponseCode == 1 {
			// At least one of reputation services found this to be known good
			// Keep the default
			reply.URL.Result = domain.ResultClean
		}
	}
}

func (w *Worker) handleIP(ip string, reply *domain.WorkReply) {
	reply.IP.Details = ip
	reply.Type |= domain.ReplyTypeIP
	// First, let's check if IP is globally unicast addressable and is public
	ipData := net.ParseIP(ip)
	ipv4 := ipData.To4()
	if ipv4 == nil {
		// If not IPv4 then return - by default it will be marked clean
		return
	}
	if !ipv4.IsGlobalUnicast() {
		// If not global unicast ignore
		return
	}
	// Private networks
	if ipv4[0] == 10 || ipv4[0] == 172 && ipv4[1] >= 16 && ipv4[1] <= 31 || ipv4[0] == 192 && ipv4[1] == 168 {
		return
	}
	c := make(chan int, 2)
	go func() {
		ipResp, err := w.xfe.IPR(ip)
		if err != nil {
			// Small hack - see if the URL was not found
			if strings.Contains(err.Error(), "404") {
				reply.IP.XFE.NotFound = true
			} else {
				reply.IP.XFE.Error = err.Error()
			}
		} else {
			reply.IP.XFE.IPReputation = *ipResp
		}
		c <- 1
	}()
	go func() {
		vtResp, err := w.vt.GetIpReport(ip)
		if err != nil {
			reply.IP.VT.Error = err.Error()
		} else {
			reply.IP.VT.IPReport = *vtResp
		}
		c <- 1
	}()
	for i := 0; i < 2; i++ {
		<-c
	}
	var vtPositives uint16
	now := time.Now()
	for i := range reply.IP.VT.IPReport.DetectedUrls {
		t, err := time.Parse("2006-01-02 15:04:05", reply.IP.VT.IPReport.DetectedUrls[i].ScanDate)
		if err != nil {
			logrus.Debugf("Error parsing scan date - %v", err)
			continue
		}
		if reply.IP.VT.IPReport.DetectedUrls[i].Positives > vtPositives && t.Add(365*24*time.Hour).After(now) {
			vtPositives = reply.IP.VT.IPReport.DetectedUrls[i].Positives
		}
	}
	reply.IP.Result = domain.ResultUnknown
	if reply.IP.XFE.IPReputation.Score > xfeScoreToConvict || vtPositives > numOfPositivesToConvict && reply.IP.XFE.NotFound {
		// This is known bad scenario
		reply.IP.Result = domain.ResultDirty
	} else if !reply.MD5.XFE.NotFound || reply.MD5.VT.FileReport.ResponseCode == 1 {
		// At least one of reputation services found this to be known good
		// Keep the default
		reply.IP.Result = domain.ResultClean
	}
}

func (w *Worker) handleMD5(md5 string, reply *domain.WorkReply) {
	reply.Type |= domain.ReplyTypeMD5
	reply.MD5.Details = md5
	c := make(chan int, 2)
	go func() {
		md5Resp, err := w.xfe.MalwareDetails(md5)
		if err != nil {
			// Small hack - see if the file was not found
			if strings.Contains(err.Error(), "404") {
				reply.MD5.XFE.NotFound = true
			} else {
				reply.MD5.XFE.Error = err.Error()
			}
		} else {
			reply.MD5.XFE.Malware = md5Resp.Malware
		}
		c <- 1
	}()
	go func() {
		vtResp, err := w.vt.GetFileReport(md5)
		if err != nil {
			reply.MD5.VT.Error = err.Error()
		} else {
			reply.MD5.VT.FileReport = *vtResp
		}
		c <- 1
	}()
	for i := 0; i < 2; i++ {
		<-c
	}
	reply.MD5.Result = domain.ResultUnknown
	if len(reply.MD5.XFE.Malware.Family) > 0 || reply.MD5.VT.FileReport.Positives > numOfPositivesToConvict {
		// This is known bad scenario
		reply.MD5.Result = domain.ResultDirty
	} else if !reply.MD5.XFE.NotFound || reply.MD5.VT.FileReport.ResponseCode == 1 {
		// At least one of reputation services found this to be known good
		// Keep the default
		reply.MD5.Result = domain.ResultClean
	}
}

func (w *Worker) handleFile(request *domain.WorkRequest, reply *domain.WorkReply) {
	reply.Type |= domain.ReplyTypeFile
	reply.File.Details = request.File
	if request.File.Size > 30*1024*1024 {
		logrus.Infof("File %s is bigger than 30M, skipping\n", request.File.Name)
		reply.File.FileTooLarge = true
		return
	}
	hash := md5.New()
	resp, err := http.Get(request.File.URL)
	if err != nil {
		logrus.Errorf("Unable to download file - %v\n", err)
		return
	}
	defer resp.Body.Close()
	buf := &bytes.Buffer{}
	io.Copy(buf, resp.Body)
	io.Copy(hash, bytes.NewReader(buf.Bytes()))
	h := fmt.Sprintf("%x", hash.Sum(nil))
	logrus.Debugf("MD5 for file %s is %s\n", request.File.Name, h)
	// Do the network commands in parallel
	c := make(chan int, 1)
	go func() {
		virus, err := w.clam.scan(request.File.Name, buf.Bytes())
		if (err == nil || err.Error() == "Virus(es) detected") && virus != "" {
			reply.File.Virus = virus
		} else if err != nil {
			reply.File.Error = err.Error()
		}
		c <- 1
	}()
	w.handleMD5(h, reply)
	<-c
	reply.File.Result = domain.ResultUnknown
	if reply.File.Virus != "" || len(reply.MD5.XFE.Malware.Family) > 0 || reply.MD5.VT.FileReport.Positives > numOfPositivesToConvict {
		// This is known bad scenario
		reply.File.Result = domain.ResultDirty
	} else if reply.File.Virus == "" || !reply.MD5.XFE.NotFound || reply.MD5.VT.FileReport.ResponseCode == 1 {
		// At least one of reputation services found this to be known good
		// Keep the default
		reply.File.Result = domain.ResultClean
	}
}
<|endoftext|>


<commit_before>package bot

import (
	"gopkg.in/telegram-bot-api.v4"
)

func SendText(update tgbotapi.Update, ctx *BotContext, text string) {
	response := tgbotapi.NewMessage(update.Message.Chat.ID, text)
	response.ParseMode = "HTML"
	ctx.Bot.Send(response)
}

func StartMessage(update tgbotapi.Update, ctx *BotContext) {
	out := "Ciao! Sono HKNBot, il bot dell'Associazione IEEE-Eta Kappa Nu" +
		" del Politecnico di Torino. Organizziamo Eventi e Gruppi di" +
		" studio, e tramite me potrai avere tutte le informazioni" +
		" di cui hai bisogno 👍  Sono un Bot testuale, per cui scrivimi e" +
		" e cercherò di risponderti!"
	response := tgbotapi.NewMessage(update.Message.Chat.ID, out)
	button1 := tgbotapi.NewKeyboardButton("Eventi \xF0\x9F\x8E\xAB")
	button2 := tgbotapi.NewKeyboardButton("Tutoraggi \xE2\x9D\x93")
	button3 := tgbotapi.NewKeyboardButton("Nascondi")
	key := tgbotapi.NewKeyboardButtonRow(button1, button2, button3)
	response.ReplyMarkup = tgbotapi.NewReplyKeyboard(key)
	response.ParseMode = "HTML"
	ctx.Bot.Send(response)
	sendCommands(update, ctx)
}

func HelpMessage(update tgbotapi.Update, ctx *BotContext) {
	out := "Sono un bot testuale, basato sull'"
	out += "NLP, meglio conosciuta come "
	out += "elaborazione del linguaggio naturale.\n"
	out += "Per comunicare con me puoi scrivermi frasi e io "
	out += "cercherò di risponderti al meglio! 😊\n"
	SendText(update, ctx, out)
	sendCommands(update, ctx)
}

func sendCommands(update tgbotapi.Update, ctx *BotContext) {
	out := "Queste sono alcune delle cose che puoi chiedermi"
	out += "Che cosa è Eta Kappa Nu?\n"
	out += "Quali sono i prossimi eventi?\n"
	out += "Qual'è la storia di Eta Kappa Nu?\n\n"
	out += "Sono inoltre capace di ripondere ad altre domande, "
	out += "prova a scoprire quali\n\n"

	out += "Sono inoltre capace di inoltrare delle domande, "
	out += "per fare questo puoi usare il comando /domanda "
	out += "e aggiungere il testo dopo, o scrivere \n"
	out += "\"Avrei una domanda:\" seguito dal testo"
	SendText(update, ctx, out)
}

func HknHistory(update tgbotapi.Update, ctx *BotContext) {
	out := "IEEE-Eta Kappa Nu è una honor society "
	out += "fondata in Ottobre 1904 da Maurice L.Carr nell'università dell Illinois.\n "
	out += "Il suo scopo è riunire i migliori studenti delle facoltà di Ingegneria Informatica "
	out += "ed elettronica e conta oggi più di 200,000 membri in tutto il mondo!\n"
	SendText(update, ctx, out)

	// This one should be done with an uploader that stores the file id in structure, without the need to hardcode it
	//photo := tgbotapi.NewPhotoUpload(update.Message.Chat.ID, "assets/ieeehkn_logo.png")
	photo := tgbotapi.NewPhotoShare(update.Message.Chat.ID, "AgADBAADIKoxG0NL8VB8IoReowRxHedTvRkABCrHmUeeZZTrDiUBAAEC")
	ctx.Bot.Send(photo)

	out = `Questo è il nostro stemma e ogni elemento ha un significato.`
	SendText(update, ctx, out)
}
<commit_msg>fixes<commit_after>package bot

import (
	"gopkg.in/telegram-bot-api.v4"
)

func SendText(update tgbotapi.Update, ctx *BotContext, text string) {
	response := tgbotapi.NewMessage(update.Message.Chat.ID, text)
	response.ParseMode = "HTML"
	ctx.Bot.Send(response)
}

func StartMessage(update tgbotapi.Update, ctx *BotContext) {
	out := "Ciao! Sono HKNBot, il bot dell'Associazione IEEE-Eta Kappa Nu" +
		" del Politecnico di Torino. Organizziamo Eventi e Gruppi di" +
		" studio, e tramite me potrai avere tutte le informazioni" +
		" di cui hai bisogno 👍  Sono un Bot testuale, per cui scrivimi e" +
		" e cercherò di risponderti!"
	response := tgbotapi.NewMessage(update.Message.Chat.ID, out)
	button1 := tgbotapi.NewKeyboardButton("Eventi \xF0\x9F\x8E\xAB")
	button2 := tgbotapi.NewKeyboardButton("Tutoraggi \xE2\x9D\x93")
	button3 := tgbotapi.NewKeyboardButton("Nascondi")
	key := tgbotapi.NewKeyboardButtonRow(button1, button2, button3)
	response.ReplyMarkup = tgbotapi.NewReplyKeyboard(key)
	response.ParseMode = "HTML"
	ctx.Bot.Send(response)
	sendCommands(update, ctx)
}

func HelpMessage(update tgbotapi.Update, ctx *BotContext) {
	out := "Sono un bot testuale, basato sull'"
	out += "NLP, meglio conosciuta come "
	out += "elaborazione del linguaggio naturale.\n"
	out += "Per comunicare con me puoi scrivermi frasi e io "
	out += "cercherò di risponderti al meglio! 😊\n"
	SendText(update, ctx, out)
	sendCommands(update, ctx)
}

func sendCommands(update tgbotapi.Update, ctx *BotContext) {
    out := "Queste sono <b>alcune delle cose che puoi chiedermi:</b>\n"
	out += "  Che cosa è Eta Kappa Nu?\n"
	out += "  Quali sono i prossimi eventi?\n"
	out += "  Qual'è la storia di Eta Kappa Nu?\n\n"
	out += "Sono inoltre capace di ripondere ad altre domande, "
	out += "prova a scoprire quali! 😀\n\n"

	out += "Sono inoltre capace di inoltrare delle domande al team di Eta Kappa Nu, "
	out += "per fare questo puoi usare il comando /domanda "
	out += "e aggiungere il testo dopo, o scrivere \n"
	out += "\"Avrei una domanda:\" seguito dal testo della domanda stessa"
	SendText(update, ctx, out)
}

func HknHistory(update tgbotapi.Update, ctx *BotContext) {
	out := "IEEE-Eta Kappa Nu è una honor society "
	out += "fondata in Ottobre 1904 da Maurice L.Carr nell'università dell Illinois.\n "
	out += "Il suo scopo è riunire i migliori studenti delle facoltà di Ingegneria Informatica "
	out += "ed elettronica e conta oggi più di 200,000 membri in tutto il mondo!\n"
	SendText(update, ctx, out)

	// This one should be done with an uploader that stores the file id in structure, without the need to hardcode it
	//photo := tgbotapi.NewPhotoUpload(update.Message.Chat.ID, "assets/ieeehkn_logo.png")
	photo := tgbotapi.NewPhotoShare(update.Message.Chat.ID, "AgADBAADIKoxG0NL8VB8IoReowRxHedTvRkABCrHmUeeZZTrDiUBAAEC")
	ctx.Bot.Send(photo)

	out = `Questo è il nostro stemma e ogni elemento ha un significato.`
	SendText(update, ctx, out)
}
<|endoftext|>


<commit_before>package exec_test

import (
	"context"
	"errors"
	"time"

	. "github.com/concourse/concourse/atc/exec"
	"github.com/concourse/concourse/atc/exec/build"
	"github.com/concourse/concourse/atc/exec/execfakes"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Timeout Step", func() {
	var (
		ctx    context.Context
		cancel func()

		fakeStep *execfakes.FakeStep

		repo  *build.Repository
		state *execfakes.FakeRunState

		step Step

		timeoutDuration string

		stepErr error
	)

	BeforeEach(func() {
		ctx, cancel = context.WithCancel(context.Background())

		fakeStep = new(execfakes.FakeStep)

		repo = build.NewRepository()
		state = new(execfakes.FakeRunState)
		state.ArtifactsReturns(repo)

		timeoutDuration = "1h"
	})

	JustBeforeEach(func() {
		step = Timeout(fakeStep, timeoutDuration)
		stepErr = step.Run(ctx, state)
	})

	Context("when the duration is valid", func() {
		It("runs the step with a deadline", func() {
			runCtx, _ := fakeStep.RunArgsForCall(0)
			deadline, ok := runCtx.Deadline()
			Expect(ok).To(BeTrue())
			Expect(deadline).To(BeTemporally("~", time.Now().Add(time.Hour), 10*time.Second))
		})

		Context("when the step returns an error", func() {
			var someError error

			BeforeEach(func() {
				someError = errors.New("some error")
				fakeStep.SucceededReturns(false)
				fakeStep.RunReturns(someError)
			})

			It("returns the error", func() {
				Expect(stepErr).NotTo(BeNil())
				Expect(stepErr).To(Equal(someError))
			})
		})

		Context("when the step exceeds the timeout", func() {
			BeforeEach(func() {
				fakeStep.SucceededReturns(true)
				fakeStep.RunReturns(context.DeadlineExceeded)
			})

			It("returns no error", func() {
				Expect(stepErr).ToNot(HaveOccurred())
			})

			It("is not successful", func() {
				Expect(step.Succeeded()).To(BeFalse())
			})
		})

		Describe("canceling", func() {
			BeforeEach(func() {
				cancel()
			})

			It("forwards the context down", func() {
				runCtx, _ := fakeStep.RunArgsForCall(0)
				Expect(runCtx.Err()).To(Equal(context.Canceled))
			})

			It("is not successful", func() {
				Expect(step.Succeeded()).To(BeFalse())
			})
		})

		Context("when the step is successful", func() {
			BeforeEach(func() {
				fakeStep.SucceededReturns(true)
			})

			It("is successful", func() {
				Expect(step.Succeeded()).To(BeTrue())
			})
		})

		Context("when the step fails", func() {
			BeforeEach(func() {
				fakeStep.SucceededReturns(false)
			})

			It("is not successful", func() {
				Expect(step.Succeeded()).To(BeFalse())
			})
		})
	})

	Context("when the duration is invalid", func() {
		BeforeEach(func() {
			timeoutDuration = "nope"
		})

		It("errors immediately without running the step", func() {
			Expect(stepErr).To(HaveOccurred())
			Expect(fakeStep.RunCallCount()).To(BeZero())
		})
	})
})
<commit_msg>fix atc/exec/timeout_step_test.go<commit_after>package exec_test

import (
	"context"
	"errors"
	"time"

	. "github.com/concourse/concourse/atc/exec"
	"github.com/concourse/concourse/atc/exec/build"
	"github.com/concourse/concourse/atc/exec/execfakes"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Timeout Step", func() {
	var (
		ctx    context.Context
		cancel func()

		fakeStep *execfakes.FakeStep

		repo  *build.Repository
		state *execfakes.FakeRunState

		step Step

		timeoutDuration string

		stepErr error
	)

	BeforeEach(func() {
		ctx, cancel = context.WithCancel(context.Background())

		fakeStep = new(execfakes.FakeStep)

		repo = build.NewRepository()
		state = new(execfakes.FakeRunState)
		state.ArtifactRepositoryReturns(repo)

		timeoutDuration = "1h"
	})

	JustBeforeEach(func() {
		step = Timeout(fakeStep, timeoutDuration)
		stepErr = step.Run(ctx, state)
	})

	Context("when the duration is valid", func() {
		It("runs the step with a deadline", func() {
			runCtx, _ := fakeStep.RunArgsForCall(0)
			deadline, ok := runCtx.Deadline()
			Expect(ok).To(BeTrue())
			Expect(deadline).To(BeTemporally("~", time.Now().Add(time.Hour), 10*time.Second))
		})

		Context("when the step returns an error", func() {
			var someError error

			BeforeEach(func() {
				someError = errors.New("some error")
				fakeStep.SucceededReturns(false)
				fakeStep.RunReturns(someError)
			})

			It("returns the error", func() {
				Expect(stepErr).NotTo(BeNil())
				Expect(stepErr).To(Equal(someError))
			})
		})

		Context("when the step exceeds the timeout", func() {
			BeforeEach(func() {
				fakeStep.SucceededReturns(true)
				fakeStep.RunReturns(context.DeadlineExceeded)
			})

			It("returns no error", func() {
				Expect(stepErr).ToNot(HaveOccurred())
			})

			It("is not successful", func() {
				Expect(step.Succeeded()).To(BeFalse())
			})
		})

		Describe("canceling", func() {
			BeforeEach(func() {
				cancel()
			})

			It("forwards the context down", func() {
				runCtx, _ := fakeStep.RunArgsForCall(0)
				Expect(runCtx.Err()).To(Equal(context.Canceled))
			})

			It("is not successful", func() {
				Expect(step.Succeeded()).To(BeFalse())
			})
		})

		Context("when the step is successful", func() {
			BeforeEach(func() {
				fakeStep.SucceededReturns(true)
			})

			It("is successful", func() {
				Expect(step.Succeeded()).To(BeTrue())
			})
		})

		Context("when the step fails", func() {
			BeforeEach(func() {
				fakeStep.SucceededReturns(false)
			})

			It("is not successful", func() {
				Expect(step.Succeeded()).To(BeFalse())
			})
		})
	})

	Context("when the duration is invalid", func() {
		BeforeEach(func() {
			timeoutDuration = "nope"
		})

		It("errors immediately without running the step", func() {
			Expect(stepErr).To(HaveOccurred())
			Expect(fakeStep.RunCallCount()).To(BeZero())
		})
	})
})
<|endoftext|>


<commit_before>package auctioneer_test

import (
	"errors"
	"fmt"
	"os"
	"syscall"
	"time"

	"github.com/cloudfoundry-incubator/auction/auctionrunner"
	"github.com/cloudfoundry-incubator/auction/auctionrunner/fake_auctionrunner"
	. "github.com/cloudfoundry-incubator/auctioneer/auctioneer"
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/fake_bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	steno "github.com/cloudfoundry/gosteno"
	"github.com/tedsuo/ifrit"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Auctioneer", func() {
	var (
		bbs        *fake_bbs.FakeAuctioneerBBS
		auctioneer *Auctioneer
		runner     *fake_auctionrunner.FakeAuctionRunner
		process    ifrit.Process
		firstRep   models.RepPresence
		secondRep  models.RepPresence
		thirdRep   models.RepPresence
		logger     *steno.Logger
		auction    models.LRPStartAuction
	)

	BeforeEach(func() {
		logger = steno.NewLogger("auctioneer")
		bbs = fake_bbs.NewFakeAuctioneerBBS()

		firstRep = models.RepPresence{
			RepID: "first-rep",
			Stack: "lucid64",
		}

		secondRep = models.RepPresence{
			RepID: "second-rep",
			Stack: ".Net",
		}

		thirdRep = models.RepPresence{
			RepID: "third-rep",
			Stack: "lucid64",
		}

		bbs.SetAllReps([]models.RepPresence{
			firstRep,
			secondRep,
			thirdRep,
		})

		auction = models.LRPStartAuction{
			ProcessGuid: "my-guid",
			Stack:       "lucid64",
		}
	})

	Describe("the lock lifecycle", func() {
		var signals chan os.Signal
		var ready chan struct{}
		var errors chan error
		BeforeEach(func() {
			runner = fake_auctionrunner.NewFakeAuctionRunner(0)
			auctioneer = New(bbs, runner, 2, time.Second, logger)
			signals = make(chan os.Signal)
			ready = make(chan struct{})
			errors = make(chan error)

			go func() {
				errors <- auctioneer.Run(signals, ready)
			}()
		})

		AfterEach(func() {
			signals <- syscall.SIGTERM
			close(<-bbs.ReleaseLockChannel)
			Eventually(errors).Should(Receive())
		})

		Context("before the lock is obtained", func() {
			It("should not be ready", func() {
				Consistently(ready).ShouldNot(BeClosed())
			})
		})

		Context("once the lock is obtained", func() {
			BeforeEach(func() {
				bbs.LockChannel <- true
			})

			It("should start watching", func() {
				bbs.LRPStartAuctionChan <- auction
				Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())
			})

			It("should become ready", func() {
				Eventually(ready).Should(BeClosed())
			})

			Context("if the watch channel is closed", func() {
				BeforeEach(func() {
					close(bbs.LRPStartAuctionChan)
					time.Sleep(10 * time.Millisecond) //make sure this gets processed
				})

				It("should start watching again on the next lock tick", func() {
					bbs.LRPStartAuctionChan = make(chan models.LRPStartAuction)
					bbs.LockChannel <- true
					bbs.LRPStartAuctionChan <- auction
					Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())
				})
			})

			Context("if the watch errors", func() {
				BeforeEach(func() {
					bbs.LRPStartAuctionErrorChan <- fmt.Errorf("boom")
				})

				It("should start watching again on the next lock tick", func() {
					bbs.LRPStartAuctionChan = make(chan models.LRPStartAuction)
					bbs.LockChannel <- true
					bbs.LRPStartAuctionChan <- auction
					Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())
				})
			})

			Context("if the lock is lost", func() {
				BeforeEach(func() {
					bbs.LockChannel <- false
				})

				It("should stop watching", func() {
					Eventually(bbs.LRPStartAuctionStopChan).Should(BeClosed())
				})

				Context("when the lock is regained", func() {
					BeforeEach(func() {
						bbs.LRPStartAuctionChan = make(chan models.LRPStartAuction)
						bbs.LRPStartAuctionStopChan = make(chan bool)
						bbs.LockChannel <- true
					})

					It("should start watching again", func() {
						bbs.LRPStartAuctionChan <- auction
						Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())
					})
				})
			})
		})
	})

	Describe("the auction lifecycle", func() {
		BeforeEach(func() {
			runner = fake_auctionrunner.NewFakeAuctionRunner(0)
			auctioneer = New(bbs, runner, 2, time.Second, logger)

			go func() {
				bbs.LockChannel <- true
			}()

			process = ifrit.Envoke(auctioneer)
		})

		AfterEach(func(done Done) {
			//send a shut down signal
			process.Signal(syscall.SIGTERM)
			//which releases the lock, which we need to acknowledge by closing the channel sent to the release lock channel
			close(<-bbs.ReleaseLockChannel)
			//which (eventually) causes the process to exit
			<-process.Wait()
			//and should stop the auction
			Ω(bbs.LRPStartAuctionStopChan).Should(BeClosed())

			close(done)
		})

		Context("when a pending auction request arrives over ETCD", func() {
			JustBeforeEach(func(done Done) {
				bbs.LRPStartAuctionChan <- auction
				close(done)
			})

			It("should attempt to claim the auction", func() {
				Eventually(bbs.GetClaimedLRPStartAuctions).Should(Equal([]models.LRPStartAuction{auction}))
			})

			Context("when the claim succeeds", func() {
				It("should run the auction with reps of the proper stack", func() {
					Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())

					request := runner.GetStartAuctionRequest()
					Ω(request.LRPStartAuction).Should(Equal(auction))
					Ω(request.RepGuids).Should(HaveLen(2))
					Ω(request.RepGuids).Should(ContainElement(firstRep.RepID))
					Ω(request.RepGuids).Should(ContainElement(thirdRep.RepID))
					Ω(request.RepGuids).ShouldNot(ContainElement(secondRep.RepID))
					Ω(request.Rules).Should(Equal(auctionrunner.DefaultRules))
				})

				Context("when the auction succeeds", func() {
					It("should resolve the auction in etcd", func() {
						Eventually(bbs.GetResolvedLRPStartAuction).Should(Equal(auction))
					})
				})

				Context("when the auction fails", func() {
					BeforeEach(func() {
						runner.SetStartAuctionError(errors.New("the auction failed"))
					})

					It("should log that the auction failed and nontheless resolve the auction", func() {
						Eventually(bbs.GetResolvedLRPStartAuction).Should(Equal(auction))

						sink := steno.GetMeTheGlobalTestSink()
						records := sink.Records()
						Ω(records[len(records)-1].Message).Should(Equal("auctioneer.run-auction.auction-failed"))
					})
				})
			})

			Context("when the claim fails", func() {
				BeforeEach(func() {
					bbs.SetClaimLRPStartAuctionError(errors.New("already claimed"))
				})

				It("should not run the auction", func() {
					Consistently(runner.GetStartAuctionRequest).Should(BeZero())
				})
			})
		})

		Describe("Sad cases", func() {
			Context("when there are no reps that match the desired stack", func() {
				BeforeEach(func(done Done) {
					auction = models.LRPStartAuction{
						ProcessGuid: "my-guid",
						Stack:       "monkey-bunnies",
					}
					bbs.LRPStartAuctionChan <- auction

					Eventually(bbs.GetClaimedLRPStartAuctions).Should(Equal([]models.LRPStartAuction{auction}))
					close(done)
				})

				It("should not run the auction", func() {
					Consistently(runner.GetStartAuctionRequest).Should(BeZero())
				})

				It("should nonetheless resolve the auction in etcd", func() {
					Eventually(bbs.GetResolvedLRPStartAuction).Should(Equal(auction))
				})
			})
		})
	})

	Describe("rate limiting many auctions", func() {
		var auction1, auction2, auction3 models.LRPStartAuction

		BeforeEach(func() {
			runner = fake_auctionrunner.NewFakeAuctionRunner(time.Second)
			auctioneer = New(bbs, runner, 2, time.Second, logger)

			go func() {
				bbs.LockChannel <- true
			}()

			process = ifrit.Envoke(auctioneer)

			auction1 = models.LRPStartAuction{
				ProcessGuid: "my-guid-1",
				Stack:       "lucid64",
			}
			auction2 = models.LRPStartAuction{
				ProcessGuid: "my-guid-2",
				Stack:       "lucid64",
			}
			auction3 = models.LRPStartAuction{
				ProcessGuid: "my-guid-3",
				Stack:       "lucid64",
			}
		})

		AfterEach(func() {
			process.Signal(syscall.SIGTERM)
			close(<-bbs.ReleaseLockChannel)
			<-process.Wait()
		})

		It("should only process maxConcurrent auctions at a time", func() {
			bbs.LRPStartAuctionChan <- auction1
			bbs.LRPStartAuctionChan <- auction2
			bbs.LRPStartAuctionChan <- auction3

			Eventually(bbs.GetClaimedLRPStartAuctions).Should(HaveLen(2))
			Consistently(bbs.GetClaimedLRPStartAuctions, 0.5).Should(HaveLen(2))

			Eventually(bbs.GetClaimedLRPStartAuctions).Should(HaveLen(3))
		})
	})
})
<commit_msg>stop racing<commit_after>package auctioneer_test

import (
	"errors"
	"fmt"
	"os"
	"syscall"
	"time"

	"github.com/cloudfoundry-incubator/auction/auctionrunner"
	"github.com/cloudfoundry-incubator/auction/auctionrunner/fake_auctionrunner"
	. "github.com/cloudfoundry-incubator/auctioneer/auctioneer"
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/fake_bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	steno "github.com/cloudfoundry/gosteno"
	"github.com/tedsuo/ifrit"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Auctioneer", func() {
	var (
		bbs        *fake_bbs.FakeAuctioneerBBS
		auctioneer *Auctioneer
		runner     *fake_auctionrunner.FakeAuctionRunner
		process    ifrit.Process
		firstRep   models.RepPresence
		secondRep  models.RepPresence
		thirdRep   models.RepPresence
		logger     *steno.Logger
		auction    models.LRPStartAuction
	)

	BeforeEach(func() {
		logger = steno.NewLogger("auctioneer")
		bbs = fake_bbs.NewFakeAuctioneerBBS()

		firstRep = models.RepPresence{
			RepID: "first-rep",
			Stack: "lucid64",
		}

		secondRep = models.RepPresence{
			RepID: "second-rep",
			Stack: ".Net",
		}

		thirdRep = models.RepPresence{
			RepID: "third-rep",
			Stack: "lucid64",
		}

		bbs.Lock()
		bbs.Reps = []models.RepPresence{
			firstRep,
			secondRep,
			thirdRep,
		}
		bbs.Unlock()

		auction = models.LRPStartAuction{
			ProcessGuid: "my-guid",
			Stack:       "lucid64",
		}
	})

	Describe("the lock lifecycle", func() {
		var signals chan os.Signal
		var ready chan struct{}
		var errors chan error
		BeforeEach(func() {
			runner = fake_auctionrunner.NewFakeAuctionRunner(0)
			auctioneer = New(bbs, runner, 2, time.Second, logger)
			signals = make(chan os.Signal)
			ready = make(chan struct{})
			errors = make(chan error)

			go func() {
				errors <- auctioneer.Run(signals, ready)
			}()
		})

		AfterEach(func() {
			signals <- syscall.SIGTERM
			close(<-bbs.ReleaseLockChannel)
			Eventually(errors).Should(Receive())
		})

		Context("before the lock is obtained", func() {
			It("should not be ready", func() {
				Consistently(ready).ShouldNot(BeClosed())
			})
		})

		Context("once the lock is obtained", func() {
			BeforeEach(func() {
				bbs.LockChannel <- true
			})

			It("should start watching", func() {
				bbs.LRPStartAuctionChan <- auction
				Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())
			})

			It("should become ready", func() {
				Eventually(ready).Should(BeClosed())
			})

			Context("if the watch channel is closed", func() {
				BeforeEach(func() {
					close(bbs.LRPStartAuctionChan)
					time.Sleep(10 * time.Millisecond) //make sure this gets processed
				})

				It("should start watching again on the next lock tick", func() {
					bbs.Lock()
					bbs.LRPStartAuctionChan = make(chan models.LRPStartAuction)
					bbs.Unlock()
					bbs.LockChannel <- true
					bbs.LRPStartAuctionChan <- auction
					Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())
				})
			})

			Context("if the watch errors", func() {
				BeforeEach(func() {
					bbs.LRPStartAuctionErrorChan <- fmt.Errorf("boom")
				})

				It("should start watching again on the next lock tick", func() {
					bbs.LRPStartAuctionChan = make(chan models.LRPStartAuction)
					bbs.LockChannel <- true
					bbs.LRPStartAuctionChan <- auction
					Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())
				})
			})

			Context("if the lock is lost", func() {
				BeforeEach(func() {
					bbs.LockChannel <- false
				})

				It("should stop watching", func() {
					Eventually(bbs.LRPStartAuctionStopChan).Should(BeClosed())
				})

				Context("when the lock is regained", func() {
					BeforeEach(func() {
						bbs.LRPStartAuctionChan = make(chan models.LRPStartAuction)
						bbs.LRPStartAuctionStopChan = make(chan bool)
						bbs.LockChannel <- true
					})

					It("should start watching again", func() {
						bbs.LRPStartAuctionChan <- auction
						Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())
					})
				})
			})
		})
	})

	Describe("the auction lifecycle", func() {
		BeforeEach(func() {
			runner = fake_auctionrunner.NewFakeAuctionRunner(0)
			auctioneer = New(bbs, runner, 2, time.Second, logger)

			go func() {
				bbs.LockChannel <- true
			}()

			process = ifrit.Envoke(auctioneer)
		})

		AfterEach(func(done Done) {
			//send a shut down signal
			process.Signal(syscall.SIGTERM)
			//which releases the lock, which we need to acknowledge by closing the channel sent to the release lock channel
			close(<-bbs.ReleaseLockChannel)
			//which (eventually) causes the process to exit
			<-process.Wait()
			//and should stop the auction
			Ω(bbs.LRPStartAuctionStopChan).Should(BeClosed())

			close(done)
		})

		Context("when a pending auction request arrives over ETCD", func() {
			JustBeforeEach(func(done Done) {
				bbs.LRPStartAuctionChan <- auction
				close(done)
			})

			It("should attempt to claim the auction", func() {
				Eventually(bbs.GetClaimedLRPStartAuctions).Should(Equal([]models.LRPStartAuction{auction}))
			})

			Context("when the claim succeeds", func() {
				It("should run the auction with reps of the proper stack", func() {
					Eventually(runner.GetStartAuctionRequest).ShouldNot(BeZero())

					request := runner.GetStartAuctionRequest()
					Ω(request.LRPStartAuction).Should(Equal(auction))
					Ω(request.RepGuids).Should(HaveLen(2))
					Ω(request.RepGuids).Should(ContainElement(firstRep.RepID))
					Ω(request.RepGuids).Should(ContainElement(thirdRep.RepID))
					Ω(request.RepGuids).ShouldNot(ContainElement(secondRep.RepID))
					Ω(request.Rules).Should(Equal(auctionrunner.DefaultRules))
				})

				Context("when the auction succeeds", func() {
					It("should resolve the auction in etcd", func() {
						Eventually(bbs.GetResolvedLRPStartAuction).Should(Equal(auction))
					})
				})

				Context("when the auction fails", func() {
					BeforeEach(func() {
						runner.SetStartAuctionError(errors.New("the auction failed"))
					})

					It("should log that the auction failed and nontheless resolve the auction", func() {
						Eventually(bbs.GetResolvedLRPStartAuction).Should(Equal(auction))

						sink := steno.GetMeTheGlobalTestSink()
						records := sink.Records()
						Ω(records[len(records)-1].Message).Should(Equal("auctioneer.run-auction.auction-failed"))
					})
				})
			})

			Context("when the claim fails", func() {
				BeforeEach(func() {
					bbs.Lock()
					bbs.ClaimLRPStartAuctionError = errors.New("already claimed")
					bbs.Unlock()
				})

				It("should not run the auction", func() {
					Consistently(runner.GetStartAuctionRequest).Should(BeZero())
				})
			})
		})

		Describe("Sad cases", func() {
			Context("when there are no reps that match the desired stack", func() {
				BeforeEach(func(done Done) {
					auction = models.LRPStartAuction{
						ProcessGuid: "my-guid",
						Stack:       "monkey-bunnies",
					}
					bbs.LRPStartAuctionChan <- auction

					Eventually(bbs.GetClaimedLRPStartAuctions).Should(Equal([]models.LRPStartAuction{auction}))
					close(done)
				})

				It("should not run the auction", func() {
					Consistently(runner.GetStartAuctionRequest).Should(BeZero())
				})

				It("should nonetheless resolve the auction in etcd", func() {
					Eventually(bbs.GetResolvedLRPStartAuction).Should(Equal(auction))
				})
			})
		})
	})

	Describe("rate limiting many auctions", func() {
		var auction1, auction2, auction3 models.LRPStartAuction

		BeforeEach(func() {
			runner = fake_auctionrunner.NewFakeAuctionRunner(time.Second)
			auctioneer = New(bbs, runner, 2, time.Second, logger)

			go func() {
				bbs.LockChannel <- true
			}()

			process = ifrit.Envoke(auctioneer)

			auction1 = models.LRPStartAuction{
				ProcessGuid: "my-guid-1",
				Stack:       "lucid64",
			}
			auction2 = models.LRPStartAuction{
				ProcessGuid: "my-guid-2",
				Stack:       "lucid64",
			}
			auction3 = models.LRPStartAuction{
				ProcessGuid: "my-guid-3",
				Stack:       "lucid64",
			}
		})

		AfterEach(func() {
			process.Signal(syscall.SIGTERM)
			close(<-bbs.ReleaseLockChannel)
			<-process.Wait()
		})

		It("should only process maxConcurrent auctions at a time", func() {
			bbs.LRPStartAuctionChan <- auction1
			bbs.LRPStartAuctionChan <- auction2
			bbs.LRPStartAuctionChan <- auction3

			Eventually(bbs.GetClaimedLRPStartAuctions).Should(HaveLen(2))
			Consistently(bbs.GetClaimedLRPStartAuctions, 0.5).Should(HaveLen(2))

			Eventually(bbs.GetClaimedLRPStartAuctions).Should(HaveLen(3))
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	pb "github.com/eqinox76/RiseAndFallOfEmpires/proto"
	"github.com/eqinox76/RiseAndFallOfEmpires/client/simple"
	"github.com/eqinox76/RiseAndFallOfEmpires/client"
	"fmt"
	"sync"
	"time"
	_ "net/http/pprof"
	"log"
	"net/http"
	"github.com/eqinox76/RiseAndFallOfEmpires/client/special"
)

var wg sync.WaitGroup

func main() {
	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	// find out how many empires are there
	c := client.Client{}
	space, err := c.PollState()

	clientChannels := make(map[uint32]chan *pb.Space, 0)
	// TODO need to close the channel when the empire has lost
	responseChannel := make(chan *pb.Command)
	doneChannel := make(chan uint32, 100)

	if err != nil {
		panic(err)
	}

	for id := range space.Empires {
		if id == 0 {
			continue
		}
		wg.Add(1)
		clientChannels[id] = make(chan *pb.Space, 20)
		if id%3 == 0 {
			d := simple.Distributed{}
			go ControlLoop(id, d.DistributeStrategy, clientChannels[id], doneChannel, responseChannel)
		} else if id%2 == 0 {
			go ControlLoop(id, simple.RandomStrategy, clientChannels[id], doneChannel, responseChannel)
		} else {
			go ControlLoop(id, special.FergsnStrategy, clientChannels[id], doneChannel, responseChannel)
		}
	}

	finishChannel := make(chan bool)

	defer func() {
		finishChannel <- true
		close(finishChannel)
		close(responseChannel)
	}()

	go func() {
		defer c.Close()
		// wait until the finish channel has been called to clean everything up
		for len(finishChannel) == 0 {
			space, err := c.PollState()

			if err != nil {
				c.Close()
				continue
			}

			for _, c := range clientChannels {
				c <- space
			}

			// check if a client is done
			select {
			case empire := <-doneChannel:
				fmt.Println(empire, " is done.")

				close(clientChannels[empire])
				delete(clientChannels, empire)
			default:
			}
		}
		for _, c := range clientChannels {
			close(c)
		}
	}()

	// send commands
	go func() {
		for response := range responseChannel {
			err := c.SendCommand(response)
			if err != nil {
				fmt.Println(err)
			}
		}
	}()

	wg.Wait()
}

func ControlLoop(empire uint32, f func(space *pb.Space, planet *pb.Planet, empire uint32, response *pb.Command), state_channel chan *pb.Space, doneChannel chan uint32, response_channel chan *pb.Command) {
	defer func() {
		doneChannel <- empire
		wg.Done()
	}()

	fmt.Println("Starting ", empire)

	for space := range state_channel {
		if len(state_channel) > 0 {
			// we have a never game state in the pipeline
			continue
		}

		emp, ok := space.Empires[empire]
		if !ok || len(emp.Ships)+len(emp.Planets) == 0 {
			break
		}

		start := time.Now()
		response := pb.Command{
			Empire: empire,
		}

		for _, planet := range space.Planets {
			if planet.Empire != empire {
				continue
			}

			f(space, planet, empire, &response)
		}

		fmt.Println(empire, time.Now().Sub(start))

		response_channel <- &response
	}
}
<commit_msg>made choosing ki algorithms random<commit_after>package main

import (
	pb "github.com/eqinox76/RiseAndFallOfEmpires/proto"
	"github.com/eqinox76/RiseAndFallOfEmpires/client/simple"
	"github.com/eqinox76/RiseAndFallOfEmpires/client"
	"fmt"
	"sync"
	_ "net/http/pprof"
	"log"
	"net/http"
	"github.com/eqinox76/RiseAndFallOfEmpires/client/special"
	"math/rand"
)

var wg sync.WaitGroup

func main() {
	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	// find out how many empires are there
	c := client.Client{}
	space, err := c.PollState()

	clientChannels := make(map[uint32]chan *pb.Space, 0)
	// TODO need to close the channel when the empire has lost
	responseChannel := make(chan *pb.Command)
	doneChannel := make(chan uint32, 100)

	if err != nil {
		panic(err)
	}

	for id := range space.Empires {
		if id == 0 {
			continue
		}
		wg.Add(1)
		clientChannels[id] = make(chan *pb.Space, 20)
		ki := rand.Intn(3)
		switch ki {
		case 0:
			d := simple.Distributed{}
			go ControlLoop(id, d.DistributeStrategy, clientChannels[id], doneChannel, responseChannel)
		case 1:
			go ControlLoop(id, simple.RandomStrategy, clientChannels[id], doneChannel, responseChannel)
		case 2:
			go ControlLoop(id, special.FergsnStrategy, clientChannels[id], doneChannel, responseChannel)
		}
	}

	finishChannel := make(chan bool)

	defer func() {
		finishChannel <- true
		close(finishChannel)
		close(responseChannel)
	}()

	go func() {
		defer c.Close()
		// wait until the finish channel has been called to clean everything up
		for len(finishChannel) == 0 {
			space, err := c.PollState()

			if err != nil {
				c.Close()
				continue
			}

			for _, c := range clientChannels {
				c <- space
			}

			// check if a client is done
			select {
			case empire := <-doneChannel:
				fmt.Println(empire, " is done.")

				close(clientChannels[empire])
				delete(clientChannels, empire)
			default:
			}
		}
		for _, c := range clientChannels {
			close(c)
		}
	}()

	// send commands
	go func() {
		for response := range responseChannel {
			err := c.SendCommand(response)
			if err != nil {
				fmt.Println(err)
			}
		}
	}()

	wg.Wait()
}

func ControlLoop(empire uint32, f func(space *pb.Space, planet *pb.Planet, empire uint32, response *pb.Command), state_channel chan *pb.Space, doneChannel chan uint32, response_channel chan *pb.Command) {
	defer func() {
		doneChannel <- empire
		wg.Done()
	}()

	fmt.Println("Starting ", empire)

	for space := range state_channel {
		if len(state_channel) > 0 {
			// we have a never game state in the pipeline
			continue
		}

		// if we have lost terminate
		emp, ok := space.Empires[empire]
		if !ok || len(emp.Ships)+len(emp.Planets) == 0 {
			break
		}
		// or if the game has ended
		if len(space.Empires) <= 2{
			break
		}

		response := pb.Command{
			Empire: empire,
		}

		for _, planet := range space.Planets {
			if planet.Empire != empire {
				continue
			}

			f(space, planet, empire, &response)
		}

		response_channel <- &response
	}
}
<|endoftext|>


<commit_before>package etreeutils

import (
	"errors"

	"fmt"

	"sort"

	"github.com/beevik/etree"
)

const (
	defaultPrefix = ""
	xmlnsPrefix   = "xmlns"
	xmlPrefix     = "xml"

	XMLNamespace   = "http://www.w3.org/XML/1998/namespace"
	XMLNSNamespace = "http://www.w3.org/2000/xmlns/"
)

var (
	DefaultNSContext = NSContext{
		prefixes: map[string]string{
			defaultPrefix: XMLNamespace,
			xmlPrefix:     XMLNamespace,
			xmlnsPrefix:   XMLNSNamespace,
		},
	}

	EmptyNSContext = NSContext{}

	ErrReservedNamespace       = errors.New("disallowed declaration of reserved namespace")
	ErrInvalidDefaultNamespace = errors.New("invalid default namespace declaration")
	ErrTraversalHalted         = errors.New("traversal halted")
)

type ErrUndeclaredNSPrefix struct {
	Prefix string
}

func (e ErrUndeclaredNSPrefix) Error() string {
	return fmt.Sprintf("undeclared namespace prefix: '%s'", e.Prefix)
}

type NSContext struct {
	prefixes map[string]string
}

func (ctx NSContext) Copy() NSContext {
	prefixes := make(map[string]string, len(ctx.prefixes)+4)
	for k, v := range ctx.prefixes {
		prefixes[k] = v
	}

	return NSContext{prefixes: prefixes}
}

func (ctx NSContext) declare(prefix, namespace string) etree.Attr {
	ctx.prefixes[prefix] = namespace

	switch prefix {
	case defaultPrefix:
		return etree.Attr{
			Key:   xmlnsPrefix,
			Value: namespace,
		}

	default:
		return etree.Attr{
			Space: xmlnsPrefix,
			Key:   prefix,
			Value: namespace,
		}
	}
}

func (ctx NSContext) SubContext(el *etree.Element) (NSContext, error) {
	// The subcontext should inherit existing declared prefixes
	newCtx := ctx.Copy()

	// Merge new namespace declarations on top of existing ones.
	for _, attr := range el.Attr {
		if attr.Space == xmlnsPrefix {
			// This attribute is a namespace declaration of the form "xmlns:<prefix>"

			// The 'xml' namespace may only be re-declared with the name 'http://www.w3.org/XML/1998/namespace'
			if attr.Key == xmlPrefix && attr.Value != XMLNamespace {
				return ctx, ErrReservedNamespace
			}

			// The 'xmlns' namespace may not be re-declared
			if attr.Key == xmlnsPrefix {
				return ctx, ErrReservedNamespace
			}

			newCtx.declare(attr.Key, attr.Value)
		} else if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix {
			// This attribute is a default namespace declaration

			// The xmlns namespace value may not be declared as the default namespace
			if attr.Value == XMLNSNamespace {
				return ctx, ErrInvalidDefaultNamespace
			}

			newCtx.declare(defaultPrefix, attr.Value)
		}
	}

	return newCtx, nil
}

// Prefixes returns a copy of this context's prefix map.
func (ctx NSContext) Prefixes() map[string]string {
	prefixes := make(map[string]string, len(ctx.prefixes))
	for k, v := range ctx.prefixes {
		prefixes[k] = v
	}

	return prefixes
}

// LookupPrefix attempts to find a declared namespace for the specified prefix. If the prefix
// is an empty string this will be the default namespace for this context. If the prefix is
// undeclared in this context an ErrUndeclaredNSPrefix will be returned.
func (ctx NSContext) LookupPrefix(prefix string) (string, error) {
	if namespace, ok := ctx.prefixes[prefix]; ok {
		return namespace, nil
	}

	return "", ErrUndeclaredNSPrefix{
		Prefix: prefix,
	}
}

// NSIterHandler is a function which is invoked with a element and its surrounding
// NSContext during traversals.
type NSIterHandler func(NSContext, *etree.Element) error

// NSTraverse traverses an element tree, invoking the passed handler for each element
// in the tree.
func NSTraverse(ctx NSContext, el *etree.Element, handle NSIterHandler) error {
	ctx, err := ctx.SubContext(el)
	if err != nil {
		return err
	}

	err = handle(ctx, el)
	if err != nil {
		return err
	}

	// Recursively traverse child elements.
	for _, child := range el.ChildElements() {
		err := NSTraverse(ctx, child, handle)
		if err != nil {
			return err
		}
	}

	return nil
}

// NSDetatch makes a copy of the passed element, and declares any namespaces in
// the passed context onto the new element before returning it.
func NSDetatch(ctx NSContext, el *etree.Element) (*etree.Element, error) {
	ctx, err := ctx.SubContext(el)
	if err != nil {
		return nil, err
	}

	el = el.Copy()

	// Build a new attribute list
	attrs := make([]etree.Attr, 0, len(el.Attr))

	// First copy over anything that isn't a namespace declaration
	for _, attr := range el.Attr {
		if attr.Space == xmlnsPrefix {
			continue
		}

		if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix {
			continue
		}

		attrs = append(attrs, attr)
	}

	// Append all in-context namespace declarations
	for prefix, namespace := range ctx.prefixes {
		// Skip the implicit "xml" and "xmlns" prefix declarations
		if prefix == xmlnsPrefix || prefix == xmlPrefix {
			continue
		}

		// Also skip declararing the default namespace as XMLNamespace
		if prefix == defaultPrefix && namespace == XMLNamespace {
			continue
		}

		if prefix != defaultPrefix {
			attrs = append(attrs, etree.Attr{
				Space: xmlnsPrefix,
				Key:   prefix,
				Value: namespace,
			})
		} else {
			attrs = append(attrs, etree.Attr{
				Key:   xmlnsPrefix,
				Value: namespace,
			})
		}
	}

	sort.Sort(SortedAttrs(attrs))

	el.Attr = attrs

	return el, nil
}

// NSSelectOne behaves identically to NSSelectOneCtx, but uses DefaultNSContext as the
// surrounding context.
func NSSelectOne(el *etree.Element, namespace, tag string) (*etree.Element, error) {
	return NSSelectOneCtx(DefaultNSContext, el, namespace, tag)
}

// NSSelectOneCtx conducts a depth-first search for an element with the specified namespace
// and tag. If such an element is found, a new *etree.Element is returned which is a
// copy of the found element, but with all in-context namespace declarations attached
// to the element as attributes.
func NSSelectOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
	var found *etree.Element

	err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
		var err error

		found, err = NSDetatch(ctx, el)
		if err != nil {
			return err
		}

		return ErrTraversalHalted
	})

	if err != nil {
		return nil, err
	}

	return found, nil
}

// NSFindIterate behaves identically to NSFindIterateCtx, but uses DefaultNSContext
// as the surrounding context.
func NSFindIterate(el *etree.Element, namespace, tag string, handle NSIterHandler) error {
	return NSFindIterateCtx(DefaultNSContext, el, namespace, tag, handle)
}

// NSFindIterateCtx conducts a depth-first traversal searching for elements with the
// specified tag in the specified namespace. It uses the passed NSContext for prefix
// lookups. For each such element, the passed handler function is invoked. If the
// handler function returns an error traversal is immediately halted. If the error
// returned by the handler is  ErrTraversalHalted then nil will be returned by
// NSFindIterate. If any other error is returned by the handler, that error will be
// returned by NSFindIterate.
func NSFindIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error {
	err := NSTraverse(ctx, el, func(ctx NSContext, el *etree.Element) error {
		currentNS, err := ctx.LookupPrefix(el.Space)
		if err != nil {
			return err
		}

		// Base case, el is the sought after element.
		if currentNS == namespace && el.Tag == tag {
			return handle(ctx, el)
		}

		return nil
	})

	if err != nil && err != ErrTraversalHalted {
		return err
	}

	return nil
}

// NSFindOne behaves identically to NSFindOneCtx, but uses DefaultNSContext for
// context.
func NSFindOne(el *etree.Element, namespace, tag string) (*etree.Element, error) {
	return NSFindOneCtx(DefaultNSContext, el, namespace, tag)
}

// NSFindOneCtx conducts a depth-first search for the specified element. If such an element
// is found a reference to it is returned.
func NSFindOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
	var found *etree.Element

	err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
		found = el
		return ErrTraversalHalted
	})

	if err != nil {
		return nil, err
	}

	return found, nil
}

// NSIterateChildren iterates the children of an element, invoking the passed
// handler with each direct child of the element, and the context surrounding
// that child.
func NSIterateChildren(ctx NSContext, el *etree.Element, handle NSIterHandler) error {
	ctx, err := ctx.SubContext(el)
	if err != nil {
		return err
	}

	// Iterate the child elements.
	for _, child := range el.ChildElements() {
		err = handle(ctx, child)
		if err != nil {
			return err
		}
	}

	return nil
}

// NSFindIterateChildrenCtx takes an element and its surrounding context, and iterates
// the children of that element searching for an element matching the passed namespace
// and tag. For each such element that is found, handle is invoked with the matched
// element and its own surrounding context.
func NSFindChildrenIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error {
	err := NSIterateChildren(ctx, el, func(ctx NSContext, el *etree.Element) error {
		_ctx, err := ctx.SubContext(el)
		if err != nil {
			return err
		}

		currentNS, err := _ctx.LookupPrefix(el.Space)
		if err != nil {
			return err
		}

		// Base case, el is the sought after element.
		if currentNS == namespace && el.Tag == tag {
			return handle(ctx, el)
		}

		return nil
	})

	if err != nil && err != ErrTraversalHalted {
		return err
	}

	return nil
}

// NSFindOneChild behaves identically to NSFindOneChildCtx, but uses
// DefaultNSContext for context.
func NSFindOneChild(el *etree.Element, namespace, tag string) (*etree.Element, error) {
	return NSFindOneChildCtx(DefaultNSContext, el, namespace, tag)
}

// NSFindOneCtx conducts a depth-first search for the specified element. If such an
// element is found a reference to it is returned.
func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
	var found *etree.Element

	err := NSFindChildrenIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
		found = el
		return ErrTraversalHalted
	})

	if err != nil && err != ErrTraversalHalted {
		return nil, err
	}

	return found, nil
}

// NSBuildParentContext recurses upward from an element in order to build an NSContext
// for its immediate parent. If the element has no parent DefaultNSContext
// is returned.
func NSBuildParentContext(el *etree.Element) (NSContext, error) {
	parent := el.Parent()
	if parent == nil {
		return DefaultNSContext, nil
	}

	ctx, err := NSBuildParentContext(parent)

	if err != nil {
		return ctx, err
	}

	return ctx.SubContext(parent)
}
<commit_msg>In NSFindIterateCtx pass the surrounding context of found elements instead of their own context<commit_after>package etreeutils

import (
	"errors"

	"fmt"

	"sort"

	"github.com/beevik/etree"
)

const (
	defaultPrefix = ""
	xmlnsPrefix   = "xmlns"
	xmlPrefix     = "xml"

	XMLNamespace   = "http://www.w3.org/XML/1998/namespace"
	XMLNSNamespace = "http://www.w3.org/2000/xmlns/"
)

var (
	DefaultNSContext = NSContext{
		prefixes: map[string]string{
			defaultPrefix: XMLNamespace,
			xmlPrefix:     XMLNamespace,
			xmlnsPrefix:   XMLNSNamespace,
		},
	}

	EmptyNSContext = NSContext{}

	ErrReservedNamespace       = errors.New("disallowed declaration of reserved namespace")
	ErrInvalidDefaultNamespace = errors.New("invalid default namespace declaration")
	ErrTraversalHalted         = errors.New("traversal halted")
)

type ErrUndeclaredNSPrefix struct {
	Prefix string
}

func (e ErrUndeclaredNSPrefix) Error() string {
	return fmt.Sprintf("undeclared namespace prefix: '%s'", e.Prefix)
}

type NSContext struct {
	prefixes map[string]string
}

func (ctx NSContext) Copy() NSContext {
	prefixes := make(map[string]string, len(ctx.prefixes)+4)
	for k, v := range ctx.prefixes {
		prefixes[k] = v
	}

	return NSContext{prefixes: prefixes}
}

func (ctx NSContext) declare(prefix, namespace string) etree.Attr {
	ctx.prefixes[prefix] = namespace

	switch prefix {
	case defaultPrefix:
		return etree.Attr{
			Key:   xmlnsPrefix,
			Value: namespace,
		}

	default:
		return etree.Attr{
			Space: xmlnsPrefix,
			Key:   prefix,
			Value: namespace,
		}
	}
}

func (ctx NSContext) SubContext(el *etree.Element) (NSContext, error) {
	// The subcontext should inherit existing declared prefixes
	newCtx := ctx.Copy()

	// Merge new namespace declarations on top of existing ones.
	for _, attr := range el.Attr {
		if attr.Space == xmlnsPrefix {
			// This attribute is a namespace declaration of the form "xmlns:<prefix>"

			// The 'xml' namespace may only be re-declared with the name 'http://www.w3.org/XML/1998/namespace'
			if attr.Key == xmlPrefix && attr.Value != XMLNamespace {
				return ctx, ErrReservedNamespace
			}

			// The 'xmlns' namespace may not be re-declared
			if attr.Key == xmlnsPrefix {
				return ctx, ErrReservedNamespace
			}

			newCtx.declare(attr.Key, attr.Value)
		} else if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix {
			// This attribute is a default namespace declaration

			// The xmlns namespace value may not be declared as the default namespace
			if attr.Value == XMLNSNamespace {
				return ctx, ErrInvalidDefaultNamespace
			}

			newCtx.declare(defaultPrefix, attr.Value)
		}
	}

	return newCtx, nil
}

// Prefixes returns a copy of this context's prefix map.
func (ctx NSContext) Prefixes() map[string]string {
	prefixes := make(map[string]string, len(ctx.prefixes))
	for k, v := range ctx.prefixes {
		prefixes[k] = v
	}

	return prefixes
}

// LookupPrefix attempts to find a declared namespace for the specified prefix. If the prefix
// is an empty string this will be the default namespace for this context. If the prefix is
// undeclared in this context an ErrUndeclaredNSPrefix will be returned.
func (ctx NSContext) LookupPrefix(prefix string) (string, error) {
	if namespace, ok := ctx.prefixes[prefix]; ok {
		return namespace, nil
	}

	return "", ErrUndeclaredNSPrefix{
		Prefix: prefix,
	}
}

// NSIterHandler is a function which is invoked with a element and its surrounding
// NSContext during traversals.
type NSIterHandler func(NSContext, *etree.Element) error

// NSTraverse traverses an element tree, invoking the passed handler for each element
// in the tree.
func NSTraverse(ctx NSContext, el *etree.Element, handle NSIterHandler) error {
	ctx, err := ctx.SubContext(el)
	if err != nil {
		return err
	}

	err = handle(ctx, el)
	if err != nil {
		return err
	}

	// Recursively traverse child elements.
	for _, child := range el.ChildElements() {
		err := NSTraverse(ctx, child, handle)
		if err != nil {
			return err
		}
	}

	return nil
}

// NSDetatch makes a copy of the passed element, and declares any namespaces in
// the passed context onto the new element before returning it.
func NSDetatch(ctx NSContext, el *etree.Element) (*etree.Element, error) {
	ctx, err := ctx.SubContext(el)
	if err != nil {
		return nil, err
	}

	el = el.Copy()

	// Build a new attribute list
	attrs := make([]etree.Attr, 0, len(el.Attr))

	// First copy over anything that isn't a namespace declaration
	for _, attr := range el.Attr {
		if attr.Space == xmlnsPrefix {
			continue
		}

		if attr.Space == defaultPrefix && attr.Key == xmlnsPrefix {
			continue
		}

		attrs = append(attrs, attr)
	}

	// Append all in-context namespace declarations
	for prefix, namespace := range ctx.prefixes {
		// Skip the implicit "xml" and "xmlns" prefix declarations
		if prefix == xmlnsPrefix || prefix == xmlPrefix {
			continue
		}

		// Also skip declararing the default namespace as XMLNamespace
		if prefix == defaultPrefix && namespace == XMLNamespace {
			continue
		}

		if prefix != defaultPrefix {
			attrs = append(attrs, etree.Attr{
				Space: xmlnsPrefix,
				Key:   prefix,
				Value: namespace,
			})
		} else {
			attrs = append(attrs, etree.Attr{
				Key:   xmlnsPrefix,
				Value: namespace,
			})
		}
	}

	sort.Sort(SortedAttrs(attrs))

	el.Attr = attrs

	return el, nil
}

// NSSelectOne behaves identically to NSSelectOneCtx, but uses DefaultNSContext as the
// surrounding context.
func NSSelectOne(el *etree.Element, namespace, tag string) (*etree.Element, error) {
	return NSSelectOneCtx(DefaultNSContext, el, namespace, tag)
}

// NSSelectOneCtx conducts a depth-first search for an element with the specified namespace
// and tag. If such an element is found, a new *etree.Element is returned which is a
// copy of the found element, but with all in-context namespace declarations attached
// to the element as attributes.
func NSSelectOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
	var found *etree.Element

	err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
		var err error

		found, err = NSDetatch(ctx, el)
		if err != nil {
			return err
		}

		return ErrTraversalHalted
	})

	if err != nil {
		return nil, err
	}

	return found, nil
}

// NSFindIterate behaves identically to NSFindIterateCtx, but uses DefaultNSContext
// as the surrounding context.
func NSFindIterate(el *etree.Element, namespace, tag string, handle NSIterHandler) error {
	return NSFindIterateCtx(DefaultNSContext, el, namespace, tag, handle)
}

// NSFindIterateCtx conducts a depth-first traversal searching for elements with the
// specified tag in the specified namespace. It uses the passed NSContext for prefix
// lookups. For each such element, the passed handler function is invoked. If the
// handler function returns an error traversal is immediately halted. If the error
// returned by the handler is  ErrTraversalHalted then nil will be returned by
// NSFindIterate. If any other error is returned by the handler, that error will be
// returned by NSFindIterate.
func NSFindIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error {
	err := NSTraverse(ctx, el, func(ctx NSContext, el *etree.Element) error {
		_ctx, err := ctx.SubContext(el)
		if err != nil {
			return err
		}

		currentNS, err := _ctx.LookupPrefix(el.Space)
		if err != nil {
			return err
		}

		// Base case, el is the sought after element.
		if currentNS == namespace && el.Tag == tag {
			return handle(ctx, el)
		}

		return nil
	})

	if err != nil && err != ErrTraversalHalted {
		return err
	}

	return nil
}

// NSFindOne behaves identically to NSFindOneCtx, but uses DefaultNSContext for
// context.
func NSFindOne(el *etree.Element, namespace, tag string) (*etree.Element, error) {
	return NSFindOneCtx(DefaultNSContext, el, namespace, tag)
}

// NSFindOneCtx conducts a depth-first search for the specified element. If such an element
// is found a reference to it is returned.
func NSFindOneCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
	var found *etree.Element

	err := NSFindIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
		found = el
		return ErrTraversalHalted
	})

	if err != nil {
		return nil, err
	}

	return found, nil
}

// NSIterateChildren iterates the children of an element, invoking the passed
// handler with each direct child of the element, and the context surrounding
// that child.
func NSIterateChildren(ctx NSContext, el *etree.Element, handle NSIterHandler) error {
	ctx, err := ctx.SubContext(el)
	if err != nil {
		return err
	}

	// Iterate the child elements.
	for _, child := range el.ChildElements() {
		err = handle(ctx, child)
		if err != nil {
			return err
		}
	}

	return nil
}

// NSFindIterateChildrenCtx takes an element and its surrounding context, and iterates
// the children of that element searching for an element matching the passed namespace
// and tag. For each such element that is found, handle is invoked with the matched
// element and its own surrounding context.
func NSFindChildrenIterateCtx(ctx NSContext, el *etree.Element, namespace, tag string, handle NSIterHandler) error {
	err := NSIterateChildren(ctx, el, func(ctx NSContext, el *etree.Element) error {
		_ctx, err := ctx.SubContext(el)
		if err != nil {
			return err
		}

		currentNS, err := _ctx.LookupPrefix(el.Space)
		if err != nil {
			return err
		}

		// Base case, el is the sought after element.
		if currentNS == namespace && el.Tag == tag {
			return handle(ctx, el)
		}

		return nil
	})

	if err != nil && err != ErrTraversalHalted {
		return err
	}

	return nil
}

// NSFindOneChild behaves identically to NSFindOneChildCtx, but uses
// DefaultNSContext for context.
func NSFindOneChild(el *etree.Element, namespace, tag string) (*etree.Element, error) {
	return NSFindOneChildCtx(DefaultNSContext, el, namespace, tag)
}

// NSFindOneCtx conducts a depth-first search for the specified element. If such an
// element is found a reference to it is returned.
func NSFindOneChildCtx(ctx NSContext, el *etree.Element, namespace, tag string) (*etree.Element, error) {
	var found *etree.Element

	err := NSFindChildrenIterateCtx(ctx, el, namespace, tag, func(ctx NSContext, el *etree.Element) error {
		found = el
		return ErrTraversalHalted
	})

	if err != nil && err != ErrTraversalHalted {
		return nil, err
	}

	return found, nil
}

// NSBuildParentContext recurses upward from an element in order to build an NSContext
// for its immediate parent. If the element has no parent DefaultNSContext
// is returned.
func NSBuildParentContext(el *etree.Element) (NSContext, error) {
	parent := el.Parent()
	if parent == nil {
		return DefaultNSContext, nil
	}

	ctx, err := NSBuildParentContext(parent)

	if err != nil {
		return ctx, err
	}

	return ctx.SubContext(parent)
}
<|endoftext|>


<commit_before>package integration_test

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/concourse/pool-resource/out"

	"github.com/onsi/gomega/gexec"

	"testing"
)

func TestIntegration(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Integration Suite")
}

var (
	outPath    string
	inPath     string
	builtCheck string
)

var _ = BeforeSuite(func() {
	var err error

	outPath, err = gexec.Build("github.com/concourse/pool-resource/cmd/out")
	Expect(err).NotTo(HaveOccurred())

	builtCheck, err = gexec.Build("github.com/concourse/pool-resource/cmd/check")
	Expect(err).NotTo(HaveOccurred())

	pwd, err := os.Getwd()
	Expect(err).NotTo(HaveOccurred())
	inPath = filepath.Join(pwd, "../assets/in")
})

type version struct {
	Ref string `json:"ref"`
}

type metadataPair struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type inResponse struct {
	Version  version        `json:"version"`
	Metadata []metadataPair `json:"metadata"`
}

func runIn(inJson string, destination string, expectedExitCode int) *gexec.Session {
	inCmd := exec.Command(inPath, destination)

	stdin, err := inCmd.StdinPipe()
	Expect(err).NotTo(HaveOccurred())

	session, err := gexec.Start(inCmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	stdin.Write([]byte(inJson))
	stdin.Close()

	Expect(err).NotTo(HaveOccurred())

	<-session.Exited
	Expect(session.ExitCode()).To(Equal(expectedExitCode))

	return session
}

func runOut(request out.OutRequest, sourceDir string) *gexec.Session {
	outCmd := exec.Command(outPath, sourceDir)

	outCmd.Env = append(
		os.Environ(),
		"BUILD_ID=1234",
		"BUILD_NAME=42",
		"BUILD_JOB_NAME=job-name",
		"BUILD_PIPELINE_NAME=pipeline-name",
	)

	stdin, err := outCmd.StdinPipe()
	Expect(err).NotTo(HaveOccurred())

	session, err := gexec.Start(outCmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	json.NewEncoder(stdin).Encode(request)
	stdin.Close()

	return session
}

func setupGitRepo(dir string) {
	gitSetup := exec.Command("bash", "-e", "-c", `
	  git init

		git config user.email "ginkgo@localhost"
		git config user.name "Ginkgo Local"

		mkdir -p lock-pool/unclaimed
		mkdir -p lock-pool/claimed

		touch lock-pool/unclaimed/.gitkeep
		touch lock-pool/claimed/.gitkeep

		touch lock-pool/unclaimed/some-lock
		touch lock-pool/unclaimed/some-other-lock

		echo '{"some":"json"}' > lock-pool/unclaimed/some-lock
		echo '{"some":"wrong-json"}' > lock-pool/unclaimed/some-other-lock

		git add .
		git commit -m 'test-git-setup'
		git checkout -b another-branch
		git checkout master
	`)
	gitSetup.Dir = dir

	gitSetup.Stderr = GinkgoWriter
	gitSetup.Stdout = GinkgoWriter

	err := gitSetup.Run()
	Expect(err).NotTo(HaveOccurred())
}

func getVersion(gitURI string, ref string) out.Version {
	gitVersionRepo, err := ioutil.TempDir("", "git-version-repo")
	Expect(err).NotTo(HaveOccurred())

	defer os.RemoveAll(gitVersionRepo)

	gitSetup := exec.Command("git", "clone", gitURI, ".")
	gitSetup.Dir = gitVersionRepo
	err = gitSetup.Run()
	Expect(err).NotTo(HaveOccurred())

	gitVersion := exec.Command("git", "rev-parse", ref)
	gitVersion.Dir = gitVersionRepo
	sha, err := gitVersion.Output()
	Expect(err).NotTo(HaveOccurred())

	return out.Version{
		Ref: strings.TrimSpace(string(sha)),
	}
}
<commit_msg>Refactor test helper<commit_after>package integration_test

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/concourse/pool-resource/out"

	"github.com/onsi/gomega/gexec"

	"testing"

	"gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
)

func TestIntegration(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Integration Suite")
}

var (
	outPath    string
	inPath     string
	builtCheck string
)

var _ = BeforeSuite(func() {
	var err error

	outPath, err = gexec.Build("github.com/concourse/pool-resource/cmd/out")
	Expect(err).NotTo(HaveOccurred())

	builtCheck, err = gexec.Build("github.com/concourse/pool-resource/cmd/check")
	Expect(err).NotTo(HaveOccurred())

	pwd, err := os.Getwd()
	Expect(err).NotTo(HaveOccurred())
	inPath = filepath.Join(pwd, "../assets/in")
})

type version struct {
	Ref string `json:"ref"`
}

type metadataPair struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type inResponse struct {
	Version  version        `json:"version"`
	Metadata []metadataPair `json:"metadata"`
}

func runIn(inJson string, destination string, expectedExitCode int) *gexec.Session {
	inCmd := exec.Command(inPath, destination)

	stdin, err := inCmd.StdinPipe()
	Expect(err).NotTo(HaveOccurred())

	session, err := gexec.Start(inCmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	stdin.Write([]byte(inJson))
	stdin.Close()

	Expect(err).NotTo(HaveOccurred())

	<-session.Exited
	Expect(session.ExitCode()).To(Equal(expectedExitCode))

	return session
}

func runOut(request out.OutRequest, sourceDir string) *gexec.Session {
	outCmd := exec.Command(outPath, sourceDir)

	outCmd.Env = append(
		os.Environ(),
		"BUILD_ID=1234",
		"BUILD_NAME=42",
		"BUILD_JOB_NAME=job-name",
		"BUILD_PIPELINE_NAME=pipeline-name",
	)

	stdin, err := outCmd.StdinPipe()
	Expect(err).NotTo(HaveOccurred())

	session, err := gexec.Start(outCmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())

	json.NewEncoder(stdin).Encode(request)
	stdin.Close()

	return session
}

func setupGitRepo(dir string) {
	setupGitRepoWithPool(dir, "lock-pool")
}

func setupGitRepoWithPool(dir, pool string) {
	gitSetup := exec.Command("bash", "-e", "-c", fmt.Sprintf(`
	    git init

		git config user.email "ginkgo@localhost"
		git config user.name "Ginkgo Local"

		mkdir -p %[1]s/unclaimed
		mkdir -p %[1]s/claimed

		touch %[1]s/unclaimed/.gitkeep
		touch %[1]s/claimed/.gitkeep

		touch %[1]s/unclaimed/some-lock
		touch %[1]s/unclaimed/some-other-lock

		echo '{"some":"json"}' > %[1]s/unclaimed/some-lock
		echo '{"some":"wrong-json"}' > %[1]s/unclaimed/some-other-lock

		git add .
		git commit -m 'test-git-setup'
		git checkout -b another-branch
		git checkout master
	`, pool))
	gitSetup.Dir = dir

	gitSetup.Stderr = GinkgoWriter
	gitSetup.Stdout = GinkgoWriter

	err := gitSetup.Run()
	Expect(err).NotTo(HaveOccurred())
}

func addLockCommit(repoDir string) string {
	return addLockCommitWithPool(repoDir, "lock-pool")
}

func addLockCommitWithPool(repoDir, pool string) string {
	r, err := git.PlainOpen(repoDir)
	Expect(err).NotTo(HaveOccurred())

	w, err := r.Worktree()
	Expect(err).NotTo(HaveOccurred())

	lockFileName := fmt.Sprintf("%s-lock", randString())

	lockFilePath := filepath.Join(pool, "unclaimed", lockFileName)

	err = ioutil.WriteFile(filepath.Join(repoDir, lockFilePath), []byte("hello world"), 777)
	Expect(err).NotTo(HaveOccurred())

	_, err = w.Add(lockFilePath)
	Expect(err).NotTo(HaveOccurred())

	commit, err := w.Commit("another lock added", &git.CommitOptions{
		Author: &object.Signature{
			Name:  "Ginkgo Local",
			Email: "ginkgo@localhost",
			When:  time.Now(),
		},
	})
	Expect(err).NotTo(HaveOccurred())

	obj, err := r.CommitObject(commit)
	Expect(err).NotTo(HaveOccurred())

	return fmt.Sprint(obj.Hash)
}

func getVersion(gitURI string, ref string) out.Version {
	gitVersionRepo, err := ioutil.TempDir("", "git-version-repo")
	Expect(err).NotTo(HaveOccurred())

	defer os.RemoveAll(gitVersionRepo)

	gitSetup := exec.Command("git", "clone", gitURI, ".")
	gitSetup.Dir = gitVersionRepo
	err = gitSetup.Run()
	Expect(err).NotTo(HaveOccurred())

	gitVersion := exec.Command("git", "rev-parse", ref)
	gitVersion.Dir = gitVersionRepo
	sha, err := gitVersion.Output()
	Expect(err).NotTo(HaveOccurred())

	return out.Version{
		Ref: strings.TrimSpace(string(sha)),
	}
}

func randString() string {
	rand.Seed(time.Now().UnixNano())

	alpha := []rune("abcdefghijklmnopqrstuvwxyz")

	final := make([]rune, 10)

	for i := range final {
		final[i] = alpha[rand.Intn(len(alpha))]
	}

	return string(final)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/robfig/cron"
	"github.com/takama/daemon"
	"log"
	"os"
)

const (
	// name of the service
	name        = "goCron"
	description = "Cron service example"
)

var stdlog, errlog *log.Logger

// Service is the daemon service struct
type Service struct {
	d daemon.Daemon
}

func startCron(c *cron.Cron) {
	// Run 1x every min
	c.AddFunc("* * * * * *", func() { makeFile() })
	c.Start()
	for {

	}
}

var times int

func makeFile() {
	// create a simple file $times.txt
	times++
	f, err := os.Create(fmt.Sprintf("/SOMEPATH/%d.txt", times))
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
}

// Manage by daemon commands or run the daemon
func (service *Service) Manage() (string, error) {

	// Create a new cron manager
	c := cron.New()
	usage := "Usage: cronStock install | remove | start | stop | status"
	// If received any kind of command, do it
	if len(os.Args) > 1 {
		command := os.Args[1]
		switch command {
		case "install":
			return service.d.Install()
		case "remove":
			return service.d.Remove()
		case "start":
			return service.d.Start()
		case "stop":
			// No need to explicitly stop cron since job will be killed
			return service.d.Stop()
		case "status":
			return service.d.Status()
		default:
			return usage, nil
		}
	}
	// Begin cron job
	go startCron(c)
	for {
		//EventLoop to keep cron running
	}
	// Unreachable, but required
	return usage, nil
}
func init() {
	stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
	errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
}
func main() {
	srv, err := daemon.New(name, description)
	if err != nil {
		errlog.Println("Error: ", err)
		os.Exit(1)
	}
	service := &Service{srv}
	status, err := service.Manage()
	if err != nil {
		errlog.Println(status, "\nError: ", err)
		os.Exit(1)
	}
	fmt.Println(status)
}
<commit_msg>cleanup cron function<commit_after>package main

import (
	"fmt"
	"github.com/robfig/cron"
	"github.com/takama/daemon"
	"log"
	"os"
)

const (
	// name of the service
	name        = "goCron"
	description = "Cron service example"
)

var stdlog, errlog *log.Logger

// Service is the daemon service struct
type Service struct {
	d daemon.Daemon
}

func startCron(c *cron.Cron) {
	// Run 1x every min
	c.AddFunc("* * * * * *", func() { makeFile() })
	c.Run()
}

var times int

func makeFile() {
	// create a simple file $times.txt
	times++
	f, err := os.Create(fmt.Sprintf("/SOMEPATH/%d.txt", times))
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
}

// Manage by daemon commands or run the daemon
func (service *Service) Manage() (string, error) {

	// Create a new cron manager
	c := cron.New()
	usage := "Usage: cronStock install | remove | start | stop | status"
	// If received any kind of command, do it
	if len(os.Args) > 1 {
		command := os.Args[1]
		switch command {
		case "install":
			return service.d.Install()
		case "remove":
			return service.d.Remove()
		case "start":
			return service.d.Start()
		case "stop":
			// No need to explicitly stop cron since job will be killed
			return service.d.Stop()
		case "status":
			return service.d.Status()
		default:
			return usage, nil
		}
	}
	// Begin cron job
	go startCron(c)
	for {
		//EventLoop to keep cron running
	}
	// Unreachable, but required
	return usage, nil
}
func init() {
	stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
	errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
}
func main() {
	srv, err := daemon.New(name, description)
	if err != nil {
		errlog.Println("Error: ", err)
		os.Exit(1)
	}
	service := &Service{srv}
	status, err := service.Manage()
	if err != nil {
		errlog.Println(status, "\nError: ", err)
		os.Exit(1)
	}
	fmt.Println(status)
}
<|endoftext|>


<commit_before>package couch

import (
	"encoding/json"
	"errors"
)

const (
	conflictsDesignID = "conflicts" // Design document for conflicts view
	conflictsViewID   = "all"       // Name of the view to query documents with conflicts
)

// Describes a conflict between different document revisions.
// Opaque type, use associated methods.
type Conflict struct {
	db        *Database
	docID     string
	revisions []DynamicDoc
}

// Get conflicting revisions for a document id. Returns nil if there are no conflicts.
func (db *Database) ConflictFor(docID string) (*Conflict, error) {
	revs, err := db.openRevsFor(docID)
	if err != nil {
		return nil, err
	}
	openLeaves := filterOpenLeafDocs(revs)
	if len(openLeaves) == 1 { // One alone does not a conflict make
		return nil, nil
	}
	return &Conflict{revisions: openLeaves, docID: docID, db: db}, nil
}

// Solves a conflict with a final document. It will set the revision id of
// the document to the final revision id that CouchDB will report once the operation is complete.
//
// If the operation is successful, the conflict c will no longer hold any information about
// the formerly conflicting revisions.
//
// Be aware that while you solve a conflict, another party might have done so right before
// you. In this case of a lost update you will receive an error. You should
// then ask about the state of the conflict again using db.ConflictFor(myDocID).
func (c *Conflict) SolveWith(finalDoc Identifiable) error {
	if !c.isReal() {
		return nil
	}

	// Make finalDoc the new leaf of the first open branch.
	// To do so, assign it the revision id of the first revision.
	id, rev := c.revisions[0].IDRev()
	finalDoc.SetIDRev(id, rev)
	leaves := new(DocBulk)
	leaves.Add(finalDoc)

	// Close all other open branches by marking their leaves deleted
	for _, rev := range c.revisions[1:] {
		rev["_deleted"] = true
		leaves.Add(rev)
	}
	_, err := c.db.InsertBulk(leaves, true)
	if err == nil {
		c.revisions = nil
	}
	return err
}

// Get all conflicting document revisions in a preferred format.
// It supports the same types for v as json.Unmarshal.
//
// Revisions in a slice of structs:
//  var revs []MyStruct
//  conflict.Revisions(&revs)
//
// Other examples:
//  var revs interface{}
//  var revs []map[string]interface{}
//
// Note that map[string]interface{} will not work.
func (c *Conflict) Revisions(v interface{}) {
	// Converting []map[string]interface{} to a type provided by the user.
	// Using Marshal/Unmarshal is not exactly a great solution but still
	// faster and less memory intensive than e.g. the mapstructure package.
	// Alternative?
	tmp, _ := json.Marshal(c.revisions)
	json.Unmarshal(tmp, v)
}

// Returns number of conflicting revisions
func (c *Conflict) RevisionsCount() int {
	return len(c.revisions)
}

func (c *Conflict) isReal() bool {
	return c.revisions != nil && len(c.revisions) > 1
}

// Returns all conflicts in a database. To do so, a dedicated view is necessary at
// [db-url]/_design/conflicts/_view/all. If it doesn't exist and forceView is enabled,
// it will be automatically set up.
//
// Note, that if the database is already large at that point, this operation can take
// a very long time. It's recommended to call this method or ConflictsCount() right after
// creating a new database.
func (db *Database) Conflicts(forceView bool) (docIDs []string, err error) {
	err = db.ensureConflictView(forceView)
	if err != nil {
		return
	}
	options := map[string]interface{}{
		"reduce": false,
	}
	result, err := db.Query(conflictsDesignID, conflictsViewID, options)
	if err != nil {
		return
	}

	n := len(result.Rows)
	docIDs = make([]string, n)
	for i, row := range result.Rows {
		docIDs[i] = row.ID
	}
	return
}

// Returns the number of conflicts, sets up view if forceView is enabled.
// See db.Conflicts() for possible issues around creating a view.
func (db *Database) ConflictsCount(forceView bool) (int, error) {
	err := db.ensureConflictView(forceView)
	if err != nil {
		return 0, err
	}
	options := map[string]interface{}{
		"reduce": true,
	}
	result, err := db.Query(conflictsDesignID, conflictsViewID, options)
	if err != nil {
		return 0, err
	}
	if len(result.Rows) > 0 {
		return result.Rows[0].ValueInt(), nil
	}
	return 0, nil
}

// Make sure a conflict view exist, if not, create it if forceView is enabled
func (db *Database) ensureConflictView(forceView bool) error {
	if db.HasView(conflictsDesignID, conflictsViewID) {
		return nil
	}
	if !forceView {
		return errors.New("missing conflicts view")
	}
	err := db.createConflictView()
	return err
}

// Inserts a design document with a view containting a map function to collect
// document ids with conflicts and a reduce function to count them.
func (db *Database) createConflictView() error {
	view := view{}
	view.Map = `function(doc) { if (doc._conflicts) { emit(null, null); } }`
	view.Reduce = `_count`
	design := newDesign()
	design.Views["all"] = view
	design.SetIDRev("_design/"+conflictsDesignID, "")
	err := db.Insert(design)
	return err
}

// Used to read out CouchDBs answer to open_revs and filter by 'ok' field (=available revision)
// See http://docs.couchdb.org/en/latest/replication/conflicts.html#working-with-conflicting-documents
type openRevision struct {
	Doc DynamicDoc `json:"ok"`
}

// Gets all open and available revisions of a document (including _deleted ones)
func (db *Database) openRevsFor(docID string) ([]openRevision, error) {
	params := map[string]interface{}{"open_revs": "all"}
	var revs []openRevision
	err := db.retrieve(docID, "", &revs, params)
	return revs, err
}

// Returns docs that are not marked as deleted
func filterOpenLeafDocs(revs []openRevision) []DynamicDoc {
	var openRevs []DynamicDoc
	for _, rev := range revs {
		del, ok := rev.Doc["_deleted"]
		if !ok || del == false {
			openRevs = append(openRevs, rev.Doc)
		}
	}
	return openRevs
}
<commit_msg>Remove repetition from Conflicts() and ConflictCount()<commit_after>package couch

import (
	"encoding/json"
	"errors"
)

const (
	conflictsDesignID = "conflicts" // Design document for conflicts view
	conflictsViewID   = "all"       // Name of the view to query documents with conflicts
)

// Describes a conflict between different document revisions.
// Opaque type, use associated methods.
type Conflict struct {
	db        *Database
	docID     string
	revisions []DynamicDoc
}

// Get conflicting revisions for a document id. Returns nil if there are no conflicts.
func (db *Database) ConflictFor(docID string) (*Conflict, error) {
	revs, err := db.openRevsFor(docID)
	if err != nil {
		return nil, err
	}
	openLeaves := filterOpenLeafDocs(revs)
	if len(openLeaves) == 1 { // One alone does not a conflict make
		return nil, nil
	}
	return &Conflict{revisions: openLeaves, docID: docID, db: db}, nil
}

// Solves a conflict with a final document. It will set the revision id of
// the document to the final revision id that CouchDB will report once the operation is complete.
//
// If the operation is successful, the conflict c will no longer hold any information about
// the formerly conflicting revisions.
//
// Be aware that while you solve a conflict, another party might have done so right before
// you. In this case of a lost update you will receive an error. You should
// then ask about the state of the conflict again using db.ConflictFor(myDocID).
func (c *Conflict) SolveWith(finalDoc Identifiable) error {
	if !c.isReal() {
		return nil
	}

	// Make finalDoc the new leaf of the first open branch.
	// To do so, assign it the revision id of the first revision.
	id, rev := c.revisions[0].IDRev()
	finalDoc.SetIDRev(id, rev)
	leaves := new(DocBulk)
	leaves.Add(finalDoc)

	// Close all other open branches by marking their leaves deleted
	for _, rev := range c.revisions[1:] {
		rev["_deleted"] = true
		leaves.Add(rev)
	}
	_, err := c.db.InsertBulk(leaves, true)
	if err == nil {
		c.revisions = nil
	}
	return err
}

// Get all conflicting document revisions in a preferred format.
// It supports the same types for v as json.Unmarshal.
//
// Revisions in a slice of structs:
//  var revs []MyStruct
//  conflict.Revisions(&revs)
//
// Other examples:
//  var revs interface{}
//  var revs []map[string]interface{}
//
// Note that map[string]interface{} will not work.
func (c *Conflict) Revisions(v interface{}) {
	// Converting []map[string]interface{} to a type provided by the user.
	// Using Marshal/Unmarshal is not exactly a great solution but still
	// faster and less memory intensive than e.g. the mapstructure package.
	// Alternative?
	tmp, _ := json.Marshal(c.revisions)
	json.Unmarshal(tmp, v)
}

// Returns number of conflicting revisions
func (c *Conflict) RevisionsCount() int {
	return len(c.revisions)
}

func (c *Conflict) isReal() bool {
	return c.revisions != nil && len(c.revisions) > 1
}

// Returns all conflicts in a database. To do so, a dedicated view is necessary at
// [db-url]/_design/conflicts/_view/all. If it doesn't exist and forceView is enabled,
// it will be automatically set up.
//
// Note, that if the database is already large at that point, this operation can take
// a very long time. It's recommended to call this method or ConflictsCount() right after
// creating a new database.
func (db *Database) Conflicts(forceView bool) (docIDs []string, err error) {
	result, err := db.queryConflictView(forceView, false)
	if err != nil {
		return
	}
	n := len(result.Rows)
	docIDs = make([]string, n)
	for i, row := range result.Rows {
		docIDs[i] = row.ID
	}
	return
}

// Returns the number of conflicts, sets up view if forceView is enabled.
// See db.Conflicts() for possible issues around creating a view.
func (db *Database) ConflictsCount(forceView bool) (int, error) {
	result, err := db.queryConflictView(forceView, true)
	if err != nil {
		return 0, err
	}
	if len(result.Rows) > 0 {
		return result.Rows[0].ValueInt(), nil
	}
	return 0, nil
}

func (db *Database) queryConflictView(forceView bool, reduce bool) (*ViewResult, error) {
	options := map[string]interface{}{
		"reduce": reduce,
	}
	err := db.ensureConflictView(forceView)
	if err != nil {
		return nil, err
	}
	result, err := db.Query(conflictsDesignID, conflictsViewID, options)
	return result, err
}

// Make sure a conflict view exist, if not, create it if forceView is enabled
func (db *Database) ensureConflictView(forceView bool) error {
	if db.HasView(conflictsDesignID, conflictsViewID) {
		return nil
	}
	if !forceView {
		return errors.New("missing conflicts view")
	}
	err := db.createConflictView()
	return err
}

// Inserts a design document with a view containting a map function to collect
// document ids with conflicts and a reduce function to count them.
func (db *Database) createConflictView() error {
	view := view{}
	view.Map = `function(doc) { if (doc._conflicts) { emit(null, null); } }`
	view.Reduce = `_count`
	design := newDesign()
	design.Views["all"] = view
	design.SetIDRev("_design/"+conflictsDesignID, "")
	err := db.Insert(design)
	return err
}

// Used to read out CouchDBs answer to open_revs and filter by 'ok' field (=available revision)
// See http://docs.couchdb.org/en/latest/replication/conflicts.html#working-with-conflicting-documents
type openRevision struct {
	Doc DynamicDoc `json:"ok"`
}

// Gets all open and available revisions of a document (including _deleted ones)
func (db *Database) openRevsFor(docID string) ([]openRevision, error) {
	params := map[string]interface{}{"open_revs": "all"}
	var revs []openRevision
	err := db.retrieve(docID, "", &revs, params)
	return revs, err
}

// Returns docs that are not marked as deleted
func filterOpenLeafDocs(revs []openRevision) []DynamicDoc {
	var openRevs []DynamicDoc
	for _, rev := range revs {
		del, ok := rev.Doc["_deleted"]
		if !ok || del == false {
			openRevs = append(openRevs, rev.Doc)
		}
	}
	return openRevs
}
<|endoftext|>