<commit_before>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"strings"
	"time"
)

const framesPerSecond = 8
const getReadyPause = 400
const maxNameLength = 25
const maxParallelRounds = 100
const maxPlayersPerRound = 5
const minPlayersPerRound = 1
const maxRoundWaitingTimeSec = 5
const maxRoundRunningTimeSec = 600
const maxSpeed = 5

const bonusPoint = 5
const lowFactor = 50
const highFactor = 5

const mapWidth = 179
const mapHeight = 38
const nameTableWidth = 30
const horizontalCarWidth = 7
const horizontalCarHeight = 3
const verticalCarWidth = 5
const verticalCarHeight = 3

const colorPrefix = "\x1b["
const colorPostfix = "m"
const bonus = "\xE2\x99\xA5"
const bomb = "\xE2\x9C\xB3"

// States of the round
const (
	COMPILING = 1 + iota
	WAITING
	STARTING
	RUNNING
	FINISHED
)

// Directions
const (
	LEFT = 0 + iota
	RIGHT
	UP
	DOWN
)

// Car Borders
const (
	LEFTUP = 0 + iota
	RIGHTUP
	RIGHTDOWN
	LEFTDOWN
)

// Damage points
const (
	DAMAGE_BACK  = 2
	DAMAGE_FRONT = 4
	DAMAGE_SIDE  = 6
)

// Colors
const (
	RESET   = 0
	BOLD    = 1
	RED     = 31
	GREEN   = 32
	YELLOW  = 33
	BLUE    = 34
	MAGENTA = 35
)

var cars = [4][]byte{}

// http://www.isthe.com/chongo/tech/comp/ansi_escapes.html
var home = []byte{27, 91, 72}
var clear = []byte{27, 91, 50, 74}

//
var middle = []byte{27, 91, 49, 52, 65, 27, 91, 57, 52, 68}
var conf Config

type Config struct {
	Log      *log.Logger
	AcidPath string
}

type Point struct {
	X, Y int
}

type Symbol struct {
	Color int
	Char  []byte
}

type Symbols []Symbol

func getAcid(fileName string) ([]byte, error) {
	fileStat, err := os.Stat(conf.AcidPath + "/" + fileName)
	if err != nil {
		conf.Log.Printf("Acid %s does not exist: %v\n", fileName, err)
		return []byte{}, err
	}

	acid := make([]byte, fileStat.Size())
	f, err := os.OpenFile(conf.AcidPath+"/"+fileName, os.O_RDONLY, os.ModePerm)
	if err != nil {
		conf.Log.Printf("Error while opening %s: %v\n", fileName, err)
		os.Exit(1)
	}
	defer f.Close()

	f.Read(acid)

	return acid, nil
}

func getPlayerData(conn net.Conn, splash []byte) (Player, error) {
	// Get data of player and return the structure
	conn.Write(clear)
	conn.Write(home)
	conn.Write(splash)
	conn.Write(middle)

	io := bufio.NewReader(conn)

	line, _ := io.ReadString('\n')
	name := strings.Replace(strings.Replace(line, "\n", "", -1), "\r", "", -1)
	if name == "" {
		return Player{}, errors.New("Empty name")
	}
	if len(name) > maxNameLength {
		return Player{}, errors.New("Too long name")
	}

	return Player{Conn: conn, BotReady: true, Name: name, Health: 100, Car: Car{Speed: 1}}, nil
}

func checkRoundReady(compileRoundChannel, runningRoundChannel chan Round) {
	fmt.Println("compile/waiting rounds:", len(compileRoundChannel))
	for r := range compileRoundChannel {
		botReady := 0
		fmt.Println("players in round:", len(r.Players))
		for _, player := range r.Players {
			if player.BotReady {
				botReady++
			}
		}

		if len(r.Players) == maxPlayersPerRound ||
			(r.State == WAITING && r.LastStateChange.Add(maxRoundWaitingTimeSec*time.Second).Before(time.Now())) {
			// We are starting round if it is fully booked or waiting time is expired
			fmt.Println("Round has changed to the state STARTING")
			r.State = STARTING
			r.LastStateChange = time.Now()
			runningRoundChannel <- r
		} else if botReady == len(r.Players) && len(r.Players) >= minPlayersPerRound {
			if r.State == COMPILING {
				fmt.Println("Round has changed to the state WAITING")
				r.State = WAITING
				r.LastStateChange = time.Now()
			} else if r.State == WAITING {
				r.writeToAllPlayers([]byte(fmt.Sprintf(
					"Waiting %d seconds for other players to join\n",
					r.LastStateChange.Unix()+maxRoundWaitingTimeSec-time.Now().Unix())), true)
			}
			compileRoundChannel <- r
		} else {
			// Return round back
			compileRoundChannel <- r
		}
		time.Sleep(1 * time.Second)
	}
}

func checkRoundRun(runningRoundChannel chan Round) {
	for {
		for round := range runningRoundChannel {
			for len(round.Players) < maxPlayersPerRound {
				p := round.generateBot()
				p.initPlayer(len(round.Players))
				round.Players = append(round.Players, p)
			}
			go round.start()
		}
	}
}

func (symbols Symbols) symbolsToByte() []byte {
	var returnSlice []byte
	for _, symbol := range symbols {
		// Should be something like \x1b[31m^\x1b[0m for symbols with colors or ^ without
		if symbol.Color != RESET {
			returnSlice = append(returnSlice, []byte(colorPrefix+fmt.Sprintf("%d", symbol.Color)+colorPostfix)...)
		}
		returnSlice = append(returnSlice, symbol.Char...)
		if symbol.Color != RESET {
			returnSlice = append(returnSlice, []byte(colorPrefix+fmt.Sprintf("%d", RESET)+colorPostfix)...)
		}
	}
	return returnSlice
}

func prepareRound(conn net.Conn, splash []byte, compileRoundChannel, runningRoundChannel chan Round) {
	// Check for name and stuff. Ask if he agrees to play with bots
	p, err := getPlayerData(conn, splash)
	if err != nil {
		conn.Close()
		return
	}

	if len(compileRoundChannel) == 0 {
		r := Round{State: COMPILING, FrameBuffer: make([]Symbol, mapWidth*mapHeight), Bonus: Point{-1, -1}, Bombs: make(map[Point]bool)}
		compileRoundChannel <- r
	}

	p.checkBestRoundForPlayer(compileRoundChannel)
}

func main() {
	// Make random unique
	rand.Seed(time.Now().Unix())
	var logFile, acidPath string
	var port int

	flag.StringVar(&logFile, "l", "/var/log/race.log", "Log file")
	flag.IntVar(&port, "p", 4242, "Port to listen")
	flag.StringVar(&acidPath, "a", "/Users/leoleovich/go/src/github.com/leoleovich/crashci/artifacts", "Artifacts location")
	flag.Parse()

	logfile, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
	conf = Config{log.New(logfile, "", log.Ldate|log.Lmicroseconds|log.Lshortfile), acidPath}

	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		os.Exit(2)
	}
	defer l.Close()

	// Read sketches
	cars[LEFT], _ = getAcid("carLeft.txt")
	cars[RIGHT], _ = getAcid("carRight.txt")
	cars[UP], _ = getAcid("carUp.txt")
	cars[DOWN], _ = getAcid("carDown.txt")
	splash, _ := getAcid("splash.txt")

	compileRoundChannel := make(chan Round, maxParallelRounds)
	runningRoundChannel := make(chan Round, maxParallelRounds)

	go checkRoundReady(compileRoundChannel, runningRoundChannel)
	go checkRoundRun(runningRoundChannel)

	for {
		conn, err := l.Accept()
		if err != nil {
			conf.Log.Println("Failed to accept request", err)
		}

		go prepareRound(conn, splash, compileRoundChannel, runningRoundChannel)

	}
}
<commit_msg>Clean code<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"strings"
	"time"
)

const framesPerSecond = 8
const getReadyPause = 400
const maxNameLength = 25
const maxParallelRounds = 100
const maxPlayersPerRound = 5
const minPlayersPerRound = 1
const maxRoundWaitingTimeSec = 5
const maxRoundRunningTimeSec = 600
const maxSpeed = 5

const bonusPoint = 5
const lowFactor = 50
const highFactor = 5

const mapWidth = 179
const mapHeight = 38
const nameTableWidth = 30
const horizontalCarWidth = 7
const horizontalCarHeight = 3
const verticalCarWidth = 5
const verticalCarHeight = 3

const colorPrefix = "\x1b["
const colorPostfix = "m"
const bonus = "\xE2\x99\xA5"
const bomb = "\xE2\x9C\xB3"

var (
	cars = [4][]byte{}
	// http://www.isthe.com/chongo/tech/comp/ansi_escapes.html
	home  = []byte{27, 91, 72}
	clear = []byte{27, 91, 50, 74}
	//
	middle = []byte{27, 91, 49, 52, 65, 27, 91, 57, 52, 68}
	conf   Config
)

// States of the round
const (
	COMPILING = 1 + iota
	WAITING
	STARTING
	RUNNING
	FINISHED
)

// Directions
const (
	LEFT = 0 + iota
	RIGHT
	UP
	DOWN
)

// Car Borders
const (
	LEFTUP = 0 + iota
	RIGHTUP
	RIGHTDOWN
	LEFTDOWN
)

// Damage points
const (
	DAMAGE_BACK  = 2
	DAMAGE_FRONT = 4
	DAMAGE_SIDE  = 6
)

// Colors
const (
	RESET = 0
	BOLD  = 1
	RED   = 31
	GREEN = 32
	//YELLOW  = 33
	//BLUE    = 34
	//MAGENTA = 35
)

type Config struct {
	Log      *log.Logger
	AcidPath string
}

type Point struct {
	X, Y int
}

type Symbol struct {
	Color int
	Char  []byte
}

type Symbols []Symbol

func getAcid(fileName string) ([]byte, error) {
	fileStat, err := os.Stat(conf.AcidPath + "/" + fileName)
	if err != nil {
		conf.Log.Printf("Acid %s does not exist: %v\n", fileName, err)
		return []byte{}, err
	}

	acid := make([]byte, fileStat.Size())
	f, err := os.OpenFile(conf.AcidPath+"/"+fileName, os.O_RDONLY, os.ModePerm)
	if err != nil {
		conf.Log.Printf("Error while opening %s: %v\n", fileName, err)
		os.Exit(1)
	}
	defer f.Close()

	f.Read(acid)

	return acid, nil
}

func getPlayerData(conn net.Conn, splash []byte) (Player, error) {
	// Get data of player and return the structure
	conn.Write(clear)
	conn.Write(home)
	conn.Write(splash)
	conn.Write(middle)

	io := bufio.NewReader(conn)

	line, _ := io.ReadString('\n')
	name := strings.Replace(strings.Replace(line, "\n", "", -1), "\r", "", -1)
	if name == "" {
		return Player{}, errors.New("Empty name")
	}
	if len(name) > maxNameLength {
		return Player{}, errors.New("Too long name")
	}

	return Player{Conn: conn, BotReady: true, Name: name, Health: 100, Car: Car{Speed: 1}}, nil
}

func checkRoundReady(compileRoundChannel, runningRoundChannel chan Round) {
	fmt.Println("compile/waiting rounds:", len(compileRoundChannel))
	for r := range compileRoundChannel {
		botReady := 0
		fmt.Println("players in round:", len(r.Players))
		for _, player := range r.Players {
			if player.BotReady {
				botReady++
			}
		}

		if len(r.Players) == maxPlayersPerRound ||
			(r.State == WAITING && r.LastStateChange.Add(maxRoundWaitingTimeSec*time.Second).Before(time.Now())) {
			// We are starting round if it is fully booked or waiting time is expired
			fmt.Println("Round has changed to the state STARTING")
			r.State = STARTING
			r.LastStateChange = time.Now()
			runningRoundChannel <- r
		} else if botReady == len(r.Players) && len(r.Players) >= minPlayersPerRound {
			if r.State == COMPILING {
				fmt.Println("Round has changed to the state WAITING")
				r.State = WAITING
				r.LastStateChange = time.Now()
			} else if r.State == WAITING {
				r.writeToAllPlayers([]byte(fmt.Sprintf(
					"Waiting %d seconds for other players to join\n",
					r.LastStateChange.Unix()+maxRoundWaitingTimeSec-time.Now().Unix())), true)
			}
			compileRoundChannel <- r
		} else {
			// Return round back
			compileRoundChannel <- r
		}
		time.Sleep(1 * time.Second)
	}
}

func checkRoundRun(runningRoundChannel chan Round) {
	for {
		for round := range runningRoundChannel {
			for len(round.Players) < maxPlayersPerRound {
				p := round.generateBot()
				p.initPlayer(len(round.Players))
				round.Players = append(round.Players, p)
			}
			go round.start()
		}
	}
}

func (symbols Symbols) symbolsToByte() []byte {
	var returnSlice []byte
	for _, symbol := range symbols {
		// Should be something like \x1b[31m^\x1b[0m for symbols with colors or ^ without
		if symbol.Color != RESET {
			returnSlice = append(returnSlice, []byte(colorPrefix+fmt.Sprintf("%d", symbol.Color)+colorPostfix)...)
		}
		returnSlice = append(returnSlice, symbol.Char...)
		if symbol.Color != RESET {
			returnSlice = append(returnSlice, []byte(colorPrefix+fmt.Sprintf("%d", RESET)+colorPostfix)...)
		}
	}
	return returnSlice
}

func prepareRound(conn net.Conn, splash []byte, compileRoundChannel chan Round) {
	// Check for name and stuff. Ask if he agrees to play with bots
	p, err := getPlayerData(conn, splash)
	if err != nil {
		conn.Close()
		return
	}

	if len(compileRoundChannel) == 0 {
		r := Round{State: COMPILING, FrameBuffer: make([]Symbol, mapWidth*mapHeight), Bonus: Point{-1, -1}, Bombs: make(map[Point]bool)}
		compileRoundChannel <- r
	}

	p.checkBestRoundForPlayer(compileRoundChannel)
}

func main() {
	// Make random unique
	rand.Seed(time.Now().Unix())
	var logFile, acidPath string
	var port int

	flag.StringVar(&logFile, "l", "/var/log/race.log", "Log file")
	flag.IntVar(&port, "p", 4242, "Port to listen")
	flag.StringVar(&acidPath, "a", "/Users/leoleovich/go/src/github.com/leoleovich/crashci/artifacts", "Artifacts location")
	flag.Parse()

	logfile, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
	conf = Config{log.New(logfile, "", log.Ldate|log.Lmicroseconds|log.Lshortfile), acidPath}

	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		os.Exit(2)
	}
	defer l.Close()

	// Read sketches
	cars[LEFT], _ = getAcid("carLeft.txt")
	cars[RIGHT], _ = getAcid("carRight.txt")
	cars[UP], _ = getAcid("carUp.txt")
	cars[DOWN], _ = getAcid("carDown.txt")
	splash, _ := getAcid("splash.txt")

	compileRoundChannel := make(chan Round, maxParallelRounds)
	runningRoundChannel := make(chan Round, maxParallelRounds)

	go checkRoundReady(compileRoundChannel, runningRoundChannel)
	go checkRoundRun(runningRoundChannel)

	for {
		conn, err := l.Accept()
		if err != nil {
			conf.Log.Println("Failed to accept request", err)
		}

		go prepareRound(conn, splash, compileRoundChannel)

	}
}
<|endoftext|>


<commit_before>package openapi3

import (
	"fmt"
	"strings"

	oas3 "github.com/getkin/kin-openapi/openapi3"
	"github.com/grokify/gotilla/os/osutil"
	"github.com/grokify/gotilla/type/maputil"
)

const defaultSep = " ~~~ "

// OperationPropertiesDescriptionStatus returns a set of
// operationIds and parameters with description status where `1`
// indicates a description and `0` indicates no descriptions.
// Descriptions for references aren't processed so they aren't
// analyzed and reported on. This returns a `MapStringMapStringInt`
// where the first key is the operationIds and the second key is the
// parameter name.
func (sm *SpecMore) OperationPropertiesDescriptionStatus() maputil.MapStringMapStringInt {
	missingDescs := maputil.MapStringMapStringInt{}
	VisitOperations(sm.Spec, func(path, method string, op *oas3.Operation) {
		if op == nil {
			return
		}
		for _, paramRef := range op.Parameters {
			if paramRef == nil {
				continue
			}
			// Is a reference
			if len(strings.TrimSpace(paramRef.Ref)) > 0 {
				continue
			}
			// Is not a reference but has no value.
			if paramRef.Value == nil {
				continue
			}
			descTry := strings.TrimSpace(paramRef.Value.Description)
			if len(descTry) == 0 {
				missingDescs.Set(op.OperationID, paramRef.Value.Name, 0)
			} else {
				missingDescs.Set(op.OperationID, paramRef.Value.Name, 1)
			}
		}
	})
	return missingDescs
}

// SchemaPropertiesDescriptionStatus returns a set of
// schema names and properties with description status where `1`
// indicates a description and `0` indicates no descriptions.
// Descriptions for references aren't processed so they aren't
// analyzed and reported on. This returns a `MapStringMapStringInt`
// where the first key is the component name and the second key is the
// property name.
func (sm *SpecMore) SchemaPropertiesDescriptionStatus() maputil.MapStringMapStringInt {
	missingDescs := maputil.MapStringMapStringInt{}
	for schName, schRef := range sm.Spec.Components.Schemas {
		if len(schRef.Ref) > 0 || schRef.Value == nil {
			continue
		}
		for propName, propRef := range schRef.Value.Properties {
			if propRef == nil ||
				len(propRef.Ref) > 0 ||
				propRef.Value == nil {
				continue
			}
			desc := strings.TrimSpace(propRef.Value.Description)
			if len(desc) == 0 {
				missingDescs.Set(schName, propName, 0)
			} else {
				missingDescs.Set(schName, propName, 1)
			}
		}
	}
	return missingDescs
}

func (sm *SpecMore) OperationParametersWithoutDescriptionsWriteFile(filename string) error {
	missing := sm.OperationPropertiesDescriptionStatus()
	arr := missing.Flatten("#/paths/...", "/",
		maputil.MapStringMapStringIntFuncExactMatch(1),
		true, true)
	withCount1, withCount2 := missing.CountsWithVal(1, defaultSep)
	woutCount1, woutCount2 := missing.CountsWithVal(0, defaultSep)
	allCount1, allCount2 := missing.Counts(defaultSep)
	lines := []string{
		fmt.Sprintf("Operations Missing/Have/All [%d/%d/%d] Params Missing/Have/All [%d/%d/%d]",
			woutCount1, withCount1, allCount1,
			woutCount2, withCount2, allCount2),
	}
	lines = append(lines, arr...)

	return osutil.CreateFileWithLines(filename, lines, "\n", true)
}

func (sm *SpecMore) SchemaPropertiesWithoutDescriptionsWriteFile(filename string) error {
	missing := sm.SchemaPropertiesDescriptionStatus()
	arr := missing.Flatten("#/components/schemas", "/",
		maputil.MapStringMapStringIntFuncExactMatch(1),
		true, true)
	withCount1, withCount2 := missing.CountsWithVal(1, defaultSep)
	woutCount1, woutCount2 := missing.CountsWithVal(0, defaultSep)
	allCount1, allCount2 := missing.Counts(defaultSep)
	lines := []string{
		fmt.Sprintf("Schemas Missing/Have/All [%d/%d/%d] Props Missing/Have/All [%d/%d/%d]",
			woutCount1, withCount1, allCount1,
			woutCount2, withCount2, allCount2),
	}
	lines = append(lines, arr...)

	return osutil.CreateFileWithLines(filename, lines, "\n", true)
}
<commit_msg>fix: openapi3: fix missing description lists<commit_after>package openapi3

import (
	"fmt"
	"strings"

	oas3 "github.com/getkin/kin-openapi/openapi3"
	"github.com/grokify/gotilla/os/osutil"
	"github.com/grokify/gotilla/type/maputil"
)

const (
	DescStatusIsEmpty    = 0
	DescStatusIsNotEmpty = 1
)

const defaultSep = " ~~~ "

// OperationPropertiesDescriptionStatus returns a set of
// operationIds and parameters with description status where `1`
// indicates a description and `0` indicates no descriptions.
// Descriptions for references aren't processed so they aren't
// analyzed and reported on. This returns a `MapStringMapStringInt`
// where the first key is the operationIds and the second key is the
// parameter name.
func (sm *SpecMore) OperationPropertiesDescriptionStatus() maputil.MapStringMapStringInt {
	descStatus := maputil.MapStringMapStringInt{}
	VisitOperations(sm.Spec, func(path, method string, op *oas3.Operation) {
		if op == nil {
			return
		}
		for _, paramRef := range op.Parameters {
			if paramRef == nil {
				continue
			}
			// Is a reference
			if len(strings.TrimSpace(paramRef.Ref)) > 0 {
				continue
			}
			// Is not a reference but has no value.
			if paramRef.Value == nil {
				continue
			}
			descTry := strings.TrimSpace(paramRef.Value.Description)
			if len(descTry) == 0 {
				descStatus.Set(op.OperationID, paramRef.Value.Name, DescStatusIsEmpty)
			} else {
				descStatus.Set(op.OperationID, paramRef.Value.Name, DescStatusIsNotEmpty)
			}
		}
	})
	return descStatus
}

// SchemaPropertiesDescriptionStatus returns a set of
// schema names and properties with description status where `1`
// indicates a description and `0` indicates no descriptions.
// Descriptions for references aren't processed so they aren't
// analyzed and reported on. This returns a `MapStringMapStringInt`
// where the first key is the component name and the second key is the
// property name.
func (sm *SpecMore) SchemaPropertiesDescriptionStatus() maputil.MapStringMapStringInt {
	descStatus := maputil.MapStringMapStringInt{}
	for schName, schRef := range sm.Spec.Components.Schemas {
		if len(schRef.Ref) > 0 || schRef.Value == nil {
			continue
		}
		for propName, propRef := range schRef.Value.Properties {
			if propRef == nil ||
				len(propRef.Ref) > 0 ||
				propRef.Value == nil {
				continue
			}
			desc := strings.TrimSpace(propRef.Value.Description)
			if len(desc) == 0 {
				descStatus.Set(schName, propName, DescStatusIsEmpty)
			} else {
				descStatus.Set(schName, propName, DescStatusIsNotEmpty)
			}
		}
	}
	return descStatus
}

func (sm *SpecMore) OperationParametersWithoutDescriptionsWriteFile(filename string) error {
	descStatus := sm.OperationPropertiesDescriptionStatus()
	missingDescPaths := descStatus.Flatten("#/paths/...", "/",
		maputil.MapStringMapStringIntFuncExactMatch(DescStatusIsEmpty),
		true, true)
	withCount1, withCount2 := descStatus.CountsWithVal(DescStatusIsNotEmpty, defaultSep)
	woutCount1, woutCount2 := descStatus.CountsWithVal(DescStatusIsEmpty, defaultSep)
	allCount1, allCount2 := descStatus.Counts(defaultSep)
	lines := []string{
		fmt.Sprintf("Operations Missing/Have/All [%d/%d/%d] Params Missing/Have/All [%d/%d/%d]",
			woutCount1, withCount1, allCount1,
			woutCount2, withCount2, allCount2),
	}
	lines = append(lines, missingDescPaths...)

	return osutil.CreateFileWithLines(filename, lines, "\n", true)
}

func (sm *SpecMore) SchemaPropertiesWithoutDescriptionsWriteFile(filename string) error {
	missing := sm.SchemaPropertiesDescriptionStatus()
	arr := missing.Flatten("#/components/schemas", "/",
		maputil.MapStringMapStringIntFuncExactMatch(DescStatusIsEmpty),
		true, true)
	withCount1, withCount2 := missing.CountsWithVal(DescStatusIsNotEmpty, defaultSep)
	woutCount1, woutCount2 := missing.CountsWithVal(DescStatusIsEmpty, defaultSep)
	allCount1, allCount2 := missing.Counts(defaultSep)
	lines := []string{
		fmt.Sprintf("Schemas Missing/Have/All [%d/%d/%d] Props Missing/Have/All [%d/%d/%d]",
			woutCount1, withCount1, allCount1,
			woutCount2, withCount2, allCount2),
	}
	lines = append(lines, arr...)

	return osutil.CreateFileWithLines(filename, lines, "\n", true)
}
<|endoftext|>


<commit_before>package sq_sharedfolder

import (
	"github.com/watermint/toolbox/domain/model/mo_path"
	"github.com/watermint/toolbox/domain/service"
	"go.uber.org/zap"
	"strings"
)

type Mount struct {
	SharedFolderId string `json:"shared_folder_id"`
	UserEmail      string `json:"user_email"`
	MountPoint     string `json:"mount_point"`
}

func (z *Mount) Do(biz service.Business) error {
	l := biz.Log().With(zap.Any("task", z))

	l.Debug("Resolve user")
	user, err := biz.Member().ResolveByEmail(z.UserEmail)
	if err != nil {
		l.Debug("Unable to resolve user", zap.Error(err))
		return err
	}
	l = l.With(zap.Any("user", user))

	l.Debug("Resolve shared folder")
	sf, err := biz.SharedFolderAsMember(user.TeamMemberId).Resolve(z.SharedFolderId)
	if err != nil {
		l.Debug("Unable to resolve shared folder for user", zap.Error(err))
		return err
	}

	if sf.PathLower == "" {
		l.Debug("Mount")
		sf, err = biz.SharedFolderMountAsMember(user.TeamMemberId).Mount(sf)
		if err != nil {
			l.Debug("Unable to mount shared folder", zap.Error(err))
			return err
		}
	}

	if sf.PathLower != strings.ToLower(z.MountPoint) {
		l.Debug("Move mount point")
		entry, err := biz.RelocationAsMember(user.TeamMemberId).Move(mo_path.NewPath(sf.PathLower), mo_path.NewPath(z.MountPoint))
		if err != nil {
			l.Debug("Unable to relocate", zap.Error(err))
			return err
		}
		l.Debug("Moved", zap.Any("entry", entry))
	}

	return nil
}
<commit_msg>fix #104 : do not move mount if a user already moved<commit_after>package sq_sharedfolder

import (
	"github.com/watermint/toolbox/domain/model/mo_path"
	"github.com/watermint/toolbox/domain/service"
	"go.uber.org/zap"
	"path/filepath"
	"strings"
)

type Mount struct {
	SharedFolderId string `json:"shared_folder_id"`
	UserEmail      string `json:"user_email"`
	MountPoint     string `json:"mount_point"`
}

func (z *Mount) Do(biz service.Business) error {
	l := biz.Log().With(zap.Any("task", z))

	l.Debug("Resolve user")
	user, err := biz.Member().ResolveByEmail(z.UserEmail)
	if err != nil {
		l.Debug("Unable to resolve user", zap.Error(err))
		return err
	}
	l = l.With(zap.Any("user", user))

	l.Debug("Resolve shared folder")
	sf, err := biz.SharedFolderAsMember(user.TeamMemberId).Resolve(z.SharedFolderId)
	if err != nil {
		l.Debug("Unable to resolve shared folder for user", zap.Error(err))
		return err
	}

	if sf.PathLower == "" {
		l.Debug("Mount")
		sf, err = biz.SharedFolderMountAsMember(user.TeamMemberId).Mount(sf)
		if err != nil {
			l.Debug("Unable to mount shared folder", zap.Error(err))
			return err
		}
	}

	if filepath.Dir(sf.PathLower) == "/" && sf.PathLower != strings.ToLower(z.MountPoint) {
		l.Debug("Move mount point")
		entry, err := biz.RelocationAsMember(user.TeamMemberId).Move(mo_path.NewPath(sf.PathLower), mo_path.NewPath(z.MountPoint))
		if err != nil {
			l.Debug("Unable to relocate", zap.Error(err))
			return err
		}
		l.Debug("Moved", zap.Any("entry", entry))
	}

	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"testing"
	"time"

	"github.com/hashicorp/vault/api"
	postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
)

func TestRenewer_Renew(t *testing.T) {
	t.Parallel()

	client, vaultDone := testVaultServer(t)
	defer vaultDone()

	t.Run("group", func(t *testing.T) {
		t.Run("kv", func(t *testing.T) {
			t.Parallel()

			if _, err := client.Logical().Write("secret/value", map[string]interface{}{
				"foo": "bar",
			}); err != nil {
				t.Fatal(err)
			}

			secret, err := client.Logical().Read("secret/value")
			if err != nil {
				t.Fatal(err)
			}

			v, err := client.NewLifetimeWatcher(&api.RenewerInput{
				Secret: secret,
			})
			if err != nil {
				t.Fatal(err)
			}
			go v.Renew()
			defer v.Stop()

			select {
			case err := <-v.DoneCh():
				if err != api.ErrLifetimeWatcherNotRenewable {
					t.Fatal(err)
				}
			case renew := <-v.RenewCh():
				t.Errorf("received renew, but should have been nil: %#v", renew)
			case <-time.After(500 * time.Millisecond):
				t.Error("should have been non-renewable")
			}
		})

		t.Run("transit", func(t *testing.T) {
			t.Parallel()

			if err := client.Sys().Mount("transit", &api.MountInput{
				Type: "transit",
			}); err != nil {
				t.Fatal(err)
			}

			secret, err := client.Logical().Write("transit/encrypt/my-app", map[string]interface{}{
				"plaintext": "Zm9vCg==",
			})
			if err != nil {
				t.Fatal(err)
			}

			v, err := client.NewLifetimeWatcher(&api.RenewerInput{
				Secret: secret,
			})
			if err != nil {
				t.Fatal(err)
			}
			go v.Renew()
			defer v.Stop()

			select {
			case err := <-v.DoneCh():
				if err != api.ErrLifetimeWatcherNotRenewable {
					t.Fatal(err)
				}
			case renew := <-v.RenewCh():
				t.Errorf("received renew, but should have been nil: %#v", renew)
			case <-time.After(500 * time.Millisecond):
				t.Error("should have been non-renewable")
			}
		})

		t.Run("database", func(t *testing.T) {
			t.Parallel()

			cleanup, pgURL := postgreshelper.PrepareTestContainer(t, "")
			defer cleanup()

			if err := client.Sys().Mount("database", &api.MountInput{
				Type: "database",
			}); err != nil {
				t.Fatal(err)
			}
			if _, err := client.Logical().Write("database/config/postgresql", map[string]interface{}{
				"plugin_name":    "postgresql-database-plugin",
				"connection_url": pgURL,
				"allowed_roles":  "readonly",
			}); err != nil {
				t.Fatal(err)
			}
			if _, err := client.Logical().Write("database/roles/readonly", map[string]interface{}{
				"db_name": "postgresql",
				"creation_statements": `` +
					`CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';` +
					`GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";`,
				"default_ttl": "5s",
				"max_ttl":     "10s",
			}); err != nil {
				t.Fatal(err)
			}

			secret, err := client.Logical().Read("database/creds/readonly")
			if err != nil {
				t.Fatal(err)
			}

			v, err := client.NewLifetimeWatcher(&api.RenewerInput{
				Secret: secret,
			})
			if err != nil {
				t.Fatal(err)
			}
			go v.Renew()
			defer v.Stop()

			done, renewed := false, false
			timeout := time.After(5 * time.Second)
			for {

				if done {
					break
				}
				select {
				case err := <-v.DoneCh():
					if renewed {
						// If we renewed but there's an error, we fail
						if err != nil {
							t.Fatalf("renewal failed with an error: %v", err)
						}
						// We can break out early here
						done = true
					} else {
						t.Errorf("should have renewed once before returning: %s", err)
					}
				case renew := <-v.RenewCh():
					if renew == nil {
						t.Fatal("renew is nil")
					}
					if !renew.Secret.Renewable {
						t.Errorf("expected lease to be renewable: %#v", renew)
					}
					if renew.Secret.LeaseDuration > 5 {
						t.Errorf("expected lease to <= 5s: %#v", renew)
					}
					renewed = true
				case <-timeout:
					if !renewed {
						t.Errorf("no renewal")
					}
					done = true
				}
			}
		})

		t.Run("auth", func(t *testing.T) {
			t.Parallel()

			secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
				Policies:       []string{"default"},
				TTL:            "5s",
				ExplicitMaxTTL: "10s",
			})
			if err != nil {
				t.Fatal(err)
			}

			v, err := client.NewLifetimeWatcher(&api.RenewerInput{
				Secret: secret,
			})
			if err != nil {
				t.Fatal(err)
			}
			go v.Renew()
			defer v.Stop()

			renewed, done := false, false
			timeout := time.After(10 * time.Second)
			for {
				if done {
					break
				}
				select {
				case err := <-v.DoneCh():
					if renewed {
						// If we renewed but there's an error, we fail
						if err != nil {
							t.Fatalf("renewal failed with an error: %v", err)
						}
						// We can break out early here
						done = true
					} else {
						t.Errorf("should have renewed once before returning: %s", err)
					}
					done = true
				case renew := <-v.RenewCh():
					if renew == nil {
						t.Fatal("renew is nil")
					}
					if renew.Secret.Auth == nil {
						t.Fatal("renew auth is nil")
					}
					if !renew.Secret.Auth.Renewable {
						t.Errorf("expected lease to be renewable: %#v", renew)
					}
					if renew.Secret.Auth.LeaseDuration > 5 {
						t.Errorf("expected lease to <= 5s: %#v", renew)
					}
					if renew.Secret.Auth.ClientToken == "" {
						t.Error("expected a client token")
					}
					if renew.Secret.Auth.Accessor == "" {
						t.Error("expected an accessor")
					}
					renewed = true
				case <-timeout:
					if !renewed {
						t.Errorf("no renewal")
					}
					done = true
				}
			}
		})
	})
}
<commit_msg>We don't need to test LifetimeWatcher's behaviour with database leases specifically. (#17208)<commit_after>package api

import (
	"testing"
	"time"

	"github.com/hashicorp/vault/api"
)

func TestRenewer_Renew(t *testing.T) {
	t.Parallel()

	client, vaultDone := testVaultServer(t)
	defer vaultDone()

	t.Run("group", func(t *testing.T) {
		t.Run("kv", func(t *testing.T) {
			t.Parallel()

			if _, err := client.Logical().Write("secret/value", map[string]interface{}{
				"foo": "bar",
			}); err != nil {
				t.Fatal(err)
			}

			secret, err := client.Logical().Read("secret/value")
			if err != nil {
				t.Fatal(err)
			}

			v, err := client.NewLifetimeWatcher(&api.RenewerInput{
				Secret: secret,
			})
			if err != nil {
				t.Fatal(err)
			}
			go v.Renew()
			defer v.Stop()

			select {
			case err := <-v.DoneCh():
				if err != api.ErrLifetimeWatcherNotRenewable {
					t.Fatal(err)
				}
			case renew := <-v.RenewCh():
				t.Errorf("received renew, but should have been nil: %#v", renew)
			case <-time.After(500 * time.Millisecond):
				t.Error("should have been non-renewable")
			}
		})

		t.Run("transit", func(t *testing.T) {
			t.Parallel()

			if err := client.Sys().Mount("transit", &api.MountInput{
				Type: "transit",
			}); err != nil {
				t.Fatal(err)
			}

			secret, err := client.Logical().Write("transit/encrypt/my-app", map[string]interface{}{
				"plaintext": "Zm9vCg==",
			})
			if err != nil {
				t.Fatal(err)
			}

			v, err := client.NewLifetimeWatcher(&api.RenewerInput{
				Secret: secret,
			})
			if err != nil {
				t.Fatal(err)
			}
			go v.Renew()
			defer v.Stop()

			select {
			case err := <-v.DoneCh():
				if err != api.ErrLifetimeWatcherNotRenewable {
					t.Fatal(err)
				}
			case renew := <-v.RenewCh():
				t.Errorf("received renew, but should have been nil: %#v", renew)
			case <-time.After(500 * time.Millisecond):
				t.Error("should have been non-renewable")
			}
		})

		t.Run("auth", func(t *testing.T) {
			t.Parallel()

			secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{
				Policies:       []string{"default"},
				TTL:            "5s",
				ExplicitMaxTTL: "10s",
			})
			if err != nil {
				t.Fatal(err)
			}

			v, err := client.NewLifetimeWatcher(&api.RenewerInput{
				Secret: secret,
			})
			if err != nil {
				t.Fatal(err)
			}
			go v.Renew()
			defer v.Stop()

			renewed, done := false, false
			timeout := time.After(10 * time.Second)
			for {
				if done {
					break
				}
				select {
				case err := <-v.DoneCh():
					if renewed {
						// If we renewed but there's an error, we fail
						if err != nil {
							t.Fatalf("renewal failed with an error: %v", err)
						}
						// We can break out early here
						done = true
					} else {
						t.Errorf("should have renewed once before returning: %s", err)
					}
					done = true
				case renew := <-v.RenewCh():
					if renew == nil {
						t.Fatal("renew is nil")
					}
					if renew.Secret.Auth == nil {
						t.Fatal("renew auth is nil")
					}
					if !renew.Secret.Auth.Renewable {
						t.Errorf("expected lease to be renewable: %#v", renew)
					}
					if renew.Secret.Auth.LeaseDuration > 5 {
						t.Errorf("expected lease to <= 5s: %#v", renew)
					}
					if renew.Secret.Auth.ClientToken == "" {
						t.Error("expected a client token")
					}
					if renew.Secret.Auth.Accessor == "" {
						t.Error("expected an accessor")
					}
					renewed = true
				case <-timeout:
					if !renewed {
						t.Errorf("no renewal")
					}
					done = true
				}
			}
		})
	})
}
<|endoftext|>


<commit_before>package writebuffer

import (
	"io"

	"github.com/ClickHouse/clickhouse-go/lib/leakypool"
)

const InitialSize = 256 * 1024

func New(initSize int) *WriteBuffer {
	wb := &WriteBuffer{}
	wb.addChunk(0, initSize)
	return wb
}

type WriteBuffer struct {
	chunks [][]byte
}

func (wb *WriteBuffer) Write(data []byte) (int, error) {
	var (
		chunkIdx = len(wb.chunks) - 1
		dataSize = len(data)
	)
	for {
		freeSize := cap(wb.chunks[chunkIdx]) - len(wb.chunks[chunkIdx])
		if freeSize >= len(data) {
			wb.chunks[chunkIdx] = append(wb.chunks[chunkIdx], data...)
			return dataSize, nil
		}
		wb.chunks[chunkIdx] = append(wb.chunks[chunkIdx], data[:freeSize]...)
		data = data[freeSize:]
		wb.addChunk(0, wb.calcCap(len(data)))
		chunkIdx++
	}
}

func (wb *WriteBuffer) WriteTo(w io.Writer) (int64, error) {
	var size int64
	for _, chunk := range wb.chunks {
		ln, err := w.Write(chunk)
		if err != nil {
			wb.Reset()
			return 0, err
		}
		size += int64(ln)
	}
	wb.Reset()
	return size, nil
}

func (wb *WriteBuffer) Bytes() []byte {
	if len(wb.chunks) == 1 {
		return wb.chunks[0]
	}
	bytes := make([]byte, 0, wb.len())
	for _, chunk := range wb.chunks {
		bytes = append(bytes, chunk...)
	}
	return bytes
}

func (wb *WriteBuffer) addChunk(size, capacity int) {
	chunk := leakypool.GetBytes(size, capacity)
	if cap(chunk) >= size {
		chunk = chunk[:size]
	}
	wb.chunks = append(wb.chunks, chunk)
}

func (wb *WriteBuffer) len() int {
	var v int
	for _, chunk := range wb.chunks {
		v += len(chunk)
	}
	return v
}

func (wb *WriteBuffer) calcCap(dataSize int) int {
	dataSize = max(dataSize, 64)
	if len(wb.chunks) == 0 {
		return dataSize
	}
	// Always double the size of the last chunk
	return max(dataSize, cap(wb.chunks[len(wb.chunks)-1])*2)
}

func (wb *WriteBuffer) Reset() {
	if len(wb.chunks) == 0 {
		return
	}
	// Recycle all chunks except the last one
	chunkSizeThreshold := cap(wb.chunks[0])
	for _, chunk := range wb.chunks[:len(wb.chunks)-1] {
		// Drain chunks smaller than the initial size
		if cap(chunk) >= chunkSizeThreshold {
			leakypool.PutBytes(chunk[:0])
		} else {
			chunkSizeThreshold = cap(chunk)
		}
	}
	// Keep the largest chunk
	wb.chunks[0] = wb.chunks[len(wb.chunks)-1][:0]
	wb.chunks = wb.chunks[:1]
}

func max(a, b int) int {
	if b > a {
		return b
	}
	return a
}
<commit_msg>#233 fixed: leakypool occupy too much memory. All chunks must be returned to the pool after deleting writebuffer<commit_after>package writebuffer

import (
	"io"

	"github.com/ClickHouse/clickhouse-go/lib/leakypool"
)

const InitialSize = 256 * 1024

func New(initSize int) *WriteBuffer {
	wb := &WriteBuffer{}
	wb.addChunk(0, initSize)
	return wb
}

type WriteBuffer struct {
	chunks [][]byte
}

func (wb *WriteBuffer) Write(data []byte) (int, error) {
	var (
		chunkIdx = len(wb.chunks) - 1
		dataSize = len(data)
	)
	for {
		freeSize := cap(wb.chunks[chunkIdx]) - len(wb.chunks[chunkIdx])
		if freeSize >= len(data) {
			wb.chunks[chunkIdx] = append(wb.chunks[chunkIdx], data...)
			return dataSize, nil
		}
		wb.chunks[chunkIdx] = append(wb.chunks[chunkIdx], data[:freeSize]...)
		data = data[freeSize:]
		wb.addChunk(0, wb.calcCap(len(data)))
		chunkIdx++
	}
}

func (wb *WriteBuffer) WriteTo(w io.Writer) (int64, error) {
	var size int64
	for _, chunk := range wb.chunks {
		ln, err := w.Write(chunk)
		if err != nil {
			wb.clear()
			return 0, err
		}
		size += int64(ln)
	}
	wb.clear()
	return size, nil
}

func (wb *WriteBuffer) Bytes() []byte {
	if len(wb.chunks) == 1 {
		return wb.chunks[0]
	}
	bytes := make([]byte, 0, wb.len())
	for _, chunk := range wb.chunks {
		bytes = append(bytes, chunk...)
	}
	return bytes
}

func (wb *WriteBuffer) addChunk(size, capacity int) {
	chunk := leakypool.GetBytes(size, capacity)
	if cap(chunk) >= size {
		chunk = chunk[:size]
	}
	wb.chunks = append(wb.chunks, chunk)
}

func (wb *WriteBuffer) len() int {
	var v int
	for _, chunk := range wb.chunks {
		v += len(chunk)
	}
	return v
}

func (wb *WriteBuffer) calcCap(dataSize int) int {
	dataSize = max(dataSize, 64)
	if len(wb.chunks) == 0 {
		return dataSize
	}
	// Always double the size of the last chunk
	return max(dataSize, cap(wb.chunks[len(wb.chunks)-1])*2)
}

func (wb *WriteBuffer) clear() {
	if len(wb.chunks) == 0 {
		return
	}
	// Recycle all chunks except the last one
	chunkSizeThreshold := cap(wb.chunks[0])
	for _, chunk := range wb.chunks[:len(wb.chunks)-1] {
		// Drain chunks smaller than the initial size
		if cap(chunk) >= chunkSizeThreshold {
			leakypool.PutBytes(chunk[:0])
		} else {
			chunkSizeThreshold = cap(chunk)
		}
	}
	// Keep the largest chunk
	wb.chunks[0] = wb.chunks[len(wb.chunks)-1][:0]
	wb.chunks = wb.chunks[:1]
}

func (wb *WriteBuffer) Reset() {
	for _, chunk := range wb.chunks {
		leakypool.PutBytes(chunk[:0])
	}
	wb.chunks = nil
}

func max(a, b int) int {
	if b > a {
		return b
	}
	return a
}
<|endoftext|>


<commit_before>package libcentrifugo

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"github.com/centrifugal/centrifugo/libcentrifugo/auth"
	"github.com/centrifugal/centrifugo/libcentrifugo/logger"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/websocket"
	"gopkg.in/igm/sockjs-go.v2/sockjs"
)

func newSockJSHandler(app *application, sockjsUrl string) http.Handler {
	if sockjsUrl != "" {
		logger.INFO.Println("using SockJS url", sockjsUrl)
		sockjs.DefaultOptions.SockJSURL = sockjsUrl
	}
	return sockjs.NewHandler("/connection", sockjs.DefaultOptions, app.sockJSHandler)
}

func (app *application) sockJSHandler(s sockjs.Session) {

	c, err := newClient(app, s)
	if err != nil {
		logger.ERROR.Println(err)
		return
	}
	defer func() {
		c.clean()
	}()
	logger.INFO.Printf("new SockJS session established with uid %s\n", c.uid())

	for {
		if msg, err := s.Recv(); err == nil {
			err = c.message([]byte(msg))
			if err != nil {
				logger.ERROR.Println(err)
				s.Close(CloseStatus, "error receiving message")
				break
			}
			continue
		}
		break
	}
}

type wsConn struct {
	ws *websocket.Conn
}

func (conn wsConn) Send(message string) error {
	return conn.ws.WriteMessage(websocket.TextMessage, []byte(message))
}

func (conn wsConn) Close(status uint32, reason string) error {
	return conn.ws.Close()
}

func (app *application) rawWebsocketHandler(w http.ResponseWriter, r *http.Request) {

	ws, err := websocket.Upgrade(w, r, nil, sockjs.WebSocketReadBufSize, sockjs.WebSocketWriteBufSize)
	if _, ok := err.(websocket.HandshakeError); ok {
		http.Error(w, `Can "Upgrade" only to "WebSocket".`, http.StatusBadRequest)
		return
	} else if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	defer ws.Close()

	conn := wsConn{
		ws: ws,
	}

	c, err := newClient(app, conn)
	if err != nil {
		return
	}
	logger.INFO.Printf("new raw Websocket session established with uid %s\n", c.uid())
	defer c.clean()

	for {
		_, message, err := conn.ws.ReadMessage()
		if err != nil {
			break
		}
		err = c.message(message)
		if err != nil {
			logger.ERROR.Println(err)
			conn.ws.Close()
			break
		}
	}
}

var (
	arrayJsonPrefix  byte = '['
	objectJsonPrefix byte = '{'
)

func msgToCommands(msgBytes []byte) ([]apiCommand, error) {
	var commands []apiCommand

	firstByte := msgBytes[0]

	switch firstByte {
	case objectJsonPrefix:
		// single command request
		var command apiCommand
		err := json.Unmarshal(msgBytes, &command)
		if err != nil {
			return nil, err
		}
		commands = append(commands, command)
	case arrayJsonPrefix:
		// array of commands received
		err := json.Unmarshal(msgBytes, &commands)
		if err != nil {
			return nil, err
		}
	default:
		return nil, ErrInvalidApiMessage
	}
	return commands, nil
}

type jsonApiRequest struct {
	Sign string
	Data string
}

func (app *application) apiHandler(w http.ResponseWriter, r *http.Request) {

	pk := ProjectKey(r.URL.Path[len("/api/"):])
	contentType := r.Header.Get("Content-Type")

	var sign string
	var encodedData string

	if strings.HasPrefix(strings.ToLower(contentType), "application/json") {
		// json request
		var req jsonApiRequest
		var decoder = json.NewDecoder(r.Body)
		err := decoder.Decode(&req)
		if err != nil {
			logger.ERROR.Println(err)
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}
		sign = req.Sign
		encodedData = req.Data
	} else {
		// application/x-www-form-urlencoded request
		sign = r.FormValue("sign")
		encodedData = r.FormValue("data")
	}

	if sign == "" {
		logger.ERROR.Println("no sign found in API request")
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	if encodedData == "" {
		logger.ERROR.Println("no data found in API request")
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	project, exists := app.projectByKey(pk)
	if !exists {
		logger.ERROR.Println("no project found with key", pk)
		http.Error(w, "Project not found", http.StatusNotFound)
		return
	}

	secret := project.Secret

	isValid := auth.CheckApiSign(secret, string(pk), encodedData, sign)
	if !isValid {
		logger.ERROR.Println("invalid sign")
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	msgBytes := []byte(encodedData)

	commands, err := msgToCommands(msgBytes)
	if err != nil {
		logger.ERROR.Println(err)
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	var mr multiResponse

	for _, command := range commands {
		resp, err := app.apiCmd(project, command)
		if err != nil {
			logger.ERROR.Println(err)
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}
		mr = append(mr, resp)
	}
	jsonResp, err := json.Marshal(mr)
	if err != nil {
		logger.ERROR.Println(err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(jsonResp)
}

func (app *application) authHandler(w http.ResponseWriter, r *http.Request) {
	password := r.FormValue("password")
	if app.config.webPassword == "" || app.config.webSecret == "" {
		logger.ERROR.Println("web_password and web_secret must be set in configuration")
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}
	if password == app.config.webPassword {
		w.Header().Set("Content-Type", "application/json")
		app.RLock()
		s := securecookie.New([]byte(app.config.webSecret), nil)
		app.RUnlock()
		token, err := s.Encode(AuthTokenKey, AuthTokenValue)
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}
		resp := map[string]string{
			"token": token,
		}
		json.NewEncoder(w).Encode(resp)
		return
	}
	http.Error(w, "Bad Request", http.StatusBadRequest)
}

func (app *application) Authenticated(h http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		authHeader := r.Header.Get("Authorization")
		if authHeader != "" {
			token := strings.TrimPrefix(authHeader, "Token ")
			err := app.checkAuthToken(token)
			if err == nil {
				h.ServeHTTP(w, r)
				return
			}
		}
		w.WriteHeader(401)
	}
	return http.HandlerFunc(fn)
}

func (app *application) Logged(h http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		addr := r.Header.Get("X-Real-IP")
		if addr == "" {
			addr = r.Header.Get("X-Forwarded-For")
			if addr == "" {
				addr = r.RemoteAddr
			}
		}
		h.ServeHTTP(w, r)
		logger.INFO.Printf("%s %s from %s completed in %s\n", r.Method, r.URL.Path, addr, time.Since(start))
		return
	}
	return http.HandlerFunc(fn)
}

func (app *application) infoHandler(w http.ResponseWriter, r *http.Request) {
	app.nodesMu.Lock()
	defer app.nodesMu.Unlock()
	app.RLock()
	defer app.RUnlock()
	info := map[string]interface{}{
		"version":   VERSION,
		"structure": app.structure.ProjectList,
		"engine":    app.engine.name(),
		"node_name": app.config.name,
		"nodes":     app.nodes,
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(info)
}

func (app *application) actionHandler(w http.ResponseWriter, r *http.Request) {
	pk := ProjectKey(r.FormValue("project"))
	method := r.FormValue("method")

	project, exists := app.projectByKey(pk)
	if !exists {
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	var resp *response
	var err error

	switch method {
	case "publish":
		channel := Channel(r.FormValue("channel"))
		data := r.FormValue("data")
		if data == "" {
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}
		cmd := &publishApiCommand{
			Channel: channel,
			Data:    []byte(data),
		}
		resp, err = app.publishCmd(project, cmd)
	case "unsubscribe":
		channel := Channel(r.FormValue("channel"))
		user := UserID(r.FormValue("user"))
		cmd := &unsubscribeApiCommand{
			Channel: channel,
			User:    user,
		}
		resp, err = app.unsubcribeCmd(project, cmd)
	case "disconnect":
		user := UserID(r.FormValue("user"))
		cmd := &disconnectApiCommand{
			User: user,
		}
		resp, err = app.disconnectCmd(project, cmd)
	case "presence":
		channel := Channel(r.FormValue("channel"))
		cmd := &presenceApiCommand{
			Channel: channel,
		}
		resp, err = app.presenceCmd(project, cmd)
	case "history":
		channel := Channel(r.FormValue("channel"))
		cmd := &historyApiCommand{
			Channel: channel,
		}
		resp, err = app.historyCmd(project, cmd)
	}

	if err != nil {
		logger.ERROR.Println(err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

var upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}

func (app *application) adminWebsocketHandler(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}
	defer ws.Close()
	c, err := newAdminClient(app, ws)
	if err != nil {
		return
	}
	logger.INFO.Printf("new admin session established with uid %s\n", c.uid())
	defer func() {
		close(c.closeChan)
		err := app.removeAdminConn(c)
		if err != nil {
			logger.ERROR.Println(err)
		}
	}()

	go c.writer()

	for {
		_, message, err := ws.ReadMessage()
		if err != nil {
			break
		}
		resp, err := c.handleMessage(message)
		if err != nil {
			break
		}
		msgBytes, err := json.Marshal(resp)
		if err != nil {
			break
		} else {
			err := c.send(string(msgBytes))
			if err != nil {
				break
			}
		}
	}
}
<commit_msg>comments for handlers<commit_after>package libcentrifugo

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"github.com/centrifugal/centrifugo/libcentrifugo/auth"
	"github.com/centrifugal/centrifugo/libcentrifugo/logger"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/websocket"
	"gopkg.in/igm/sockjs-go.v2/sockjs"
)

// newSockJSHandler returns SockJS handler bind to `/connection` prefix
// SockJS handler has several handlers inside responsible for various tasks -
// SockJS server info, websocket, HTTP streaming and polling transports.
func newSockJSHandler(app *application, sockjsUrl string) http.Handler {
	if sockjsUrl != "" {
		logger.INFO.Println("using SockJS url", sockjsUrl)
		sockjs.DefaultOptions.SockJSURL = sockjsUrl
	}
	return sockjs.NewHandler("/connection", sockjs.DefaultOptions, app.sockJSHandler)
}

// sockJSHandler called when new client connection comes to SockJS endpoint.
func (app *application) sockJSHandler(s sockjs.Session) {

	c, err := newClient(app, s)
	if err != nil {
		logger.ERROR.Println(err)
		return
	}
	defer func() {
		c.clean()
	}()
	logger.INFO.Printf("new SockJS session established with uid %s\n", c.uid())

	for {
		if msg, err := s.Recv(); err == nil {
			err = c.message([]byte(msg))
			if err != nil {
				logger.ERROR.Println(err)
				s.Close(CloseStatus, "error receiving message")
				break
			}
			continue
		}
		break
	}
}

// wsConn is a struct to fit SockJS session interface so client will accept
// it as its sess
type wsConn struct {
	ws *websocket.Conn
}

func (conn wsConn) Send(message string) error {
	return conn.ws.WriteMessage(websocket.TextMessage, []byte(message))
}

func (conn wsConn) Close(status uint32, reason string) error {
	return conn.ws.Close()
}

// rawWebsocketHandler called when new client connection comes to raw Websocket endpoint.
func (app *application) rawWebsocketHandler(w http.ResponseWriter, r *http.Request) {

	ws, err := websocket.Upgrade(w, r, nil, sockjs.WebSocketReadBufSize, sockjs.WebSocketWriteBufSize)
	if _, ok := err.(websocket.HandshakeError); ok {
		http.Error(w, `Can "Upgrade" only to "WebSocket".`, http.StatusBadRequest)
		return
	} else if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	defer ws.Close()

	conn := wsConn{
		ws: ws,
	}

	c, err := newClient(app, conn)
	if err != nil {
		return
	}
	logger.INFO.Printf("new raw Websocket session established with uid %s\n", c.uid())
	defer c.clean()

	for {
		_, message, err := conn.ws.ReadMessage()
		if err != nil {
			break
		}
		err = c.message(message)
		if err != nil {
			logger.ERROR.Println(err)
			conn.ws.Close()
			break
		}
	}
}

var (
	arrayJsonPrefix  byte = '['
	objectJsonPrefix byte = '{'
)

func msgToCommands(msgBytes []byte) ([]apiCommand, error) {
	var commands []apiCommand

	firstByte := msgBytes[0]

	switch firstByte {
	case objectJsonPrefix:
		// single command request
		var command apiCommand
		err := json.Unmarshal(msgBytes, &command)
		if err != nil {
			return nil, err
		}
		commands = append(commands, command)
	case arrayJsonPrefix:
		// array of commands received
		err := json.Unmarshal(msgBytes, &commands)
		if err != nil {
			return nil, err
		}
	default:
		return nil, ErrInvalidApiMessage
	}
	return commands, nil
}

type jsonApiRequest struct {
	Sign string
	Data string
}

// apiHandler is responsible for receiving API commands over HTTP.
func (app *application) apiHandler(w http.ResponseWriter, r *http.Request) {

	pk := ProjectKey(r.URL.Path[len("/api/"):])
	contentType := r.Header.Get("Content-Type")

	var sign string
	var encodedData string

	if strings.HasPrefix(strings.ToLower(contentType), "application/json") {
		// json request
		var req jsonApiRequest
		var decoder = json.NewDecoder(r.Body)
		err := decoder.Decode(&req)
		if err != nil {
			logger.ERROR.Println(err)
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}
		sign = req.Sign
		encodedData = req.Data
	} else {
		// application/x-www-form-urlencoded request
		sign = r.FormValue("sign")
		encodedData = r.FormValue("data")
	}

	if sign == "" {
		logger.ERROR.Println("no sign found in API request")
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	if encodedData == "" {
		logger.ERROR.Println("no data found in API request")
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	project, exists := app.projectByKey(pk)
	if !exists {
		logger.ERROR.Println("no project found with key", pk)
		http.Error(w, "Project not found", http.StatusNotFound)
		return
	}

	secret := project.Secret

	isValid := auth.CheckApiSign(secret, string(pk), encodedData, sign)
	if !isValid {
		logger.ERROR.Println("invalid sign")
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	msgBytes := []byte(encodedData)

	commands, err := msgToCommands(msgBytes)
	if err != nil {
		logger.ERROR.Println(err)
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	var mr multiResponse

	for _, command := range commands {
		resp, err := app.apiCmd(project, command)
		if err != nil {
			logger.ERROR.Println(err)
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}
		mr = append(mr, resp)
	}
	jsonResp, err := json.Marshal(mr)
	if err != nil {
		logger.ERROR.Println(err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(jsonResp)
}

// authHandler allows to get admin web interface token.
func (app *application) authHandler(w http.ResponseWriter, r *http.Request) {
	password := r.FormValue("password")
	if app.config.webPassword == "" || app.config.webSecret == "" {
		logger.ERROR.Println("web_password and web_secret must be set in configuration")
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}
	if password == app.config.webPassword {
		w.Header().Set("Content-Type", "application/json")
		app.RLock()
		s := securecookie.New([]byte(app.config.webSecret), nil)
		app.RUnlock()
		token, err := s.Encode(AuthTokenKey, AuthTokenValue)
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}
		resp := map[string]string{
			"token": token,
		}
		json.NewEncoder(w).Encode(resp)
		return
	}
	http.Error(w, "Bad Request", http.StatusBadRequest)
}

// Authenticated middleware checks that request contains valid auth token in headers.
func (app *application) Authenticated(h http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		authHeader := r.Header.Get("Authorization")
		if authHeader != "" {
			token := strings.TrimPrefix(authHeader, "Token ")
			err := app.checkAuthToken(token)
			if err == nil {
				h.ServeHTTP(w, r)
				return
			}
		}
		w.WriteHeader(401)
	}
	return http.HandlerFunc(fn)
}

// Logged middleware logs request.
func (app *application) Logged(h http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		addr := r.Header.Get("X-Real-IP")
		if addr == "" {
			addr = r.Header.Get("X-Forwarded-For")
			if addr == "" {
				addr = r.RemoteAddr
			}
		}
		h.ServeHTTP(w, r)
		logger.INFO.Printf("%s %s from %s completed in %s\n", r.Method, r.URL.Path, addr, time.Since(start))
		return
	}
	return http.HandlerFunc(fn)
}

// infoHahdler allows to get actual information about Centrifugo nodes running.
func (app *application) infoHandler(w http.ResponseWriter, r *http.Request) {
	app.nodesMu.Lock()
	defer app.nodesMu.Unlock()
	app.RLock()
	defer app.RUnlock()
	info := map[string]interface{}{
		"version":   VERSION,
		"structure": app.structure.ProjectList,
		"engine":    app.engine.name(),
		"node_name": app.config.name,
		"nodes":     app.nodes,
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(info)
}

// actionHandler allows to call API commands via submitting a form.
func (app *application) actionHandler(w http.ResponseWriter, r *http.Request) {
	pk := ProjectKey(r.FormValue("project"))
	method := r.FormValue("method")

	project, exists := app.projectByKey(pk)
	if !exists {
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	var resp *response
	var err error

	switch method {
	case "publish":
		channel := Channel(r.FormValue("channel"))
		data := r.FormValue("data")
		if data == "" {
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}
		cmd := &publishApiCommand{
			Channel: channel,
			Data:    []byte(data),
		}
		resp, err = app.publishCmd(project, cmd)
	case "unsubscribe":
		channel := Channel(r.FormValue("channel"))
		user := UserID(r.FormValue("user"))
		cmd := &unsubscribeApiCommand{
			Channel: channel,
			User:    user,
		}
		resp, err = app.unsubcribeCmd(project, cmd)
	case "disconnect":
		user := UserID(r.FormValue("user"))
		cmd := &disconnectApiCommand{
			User: user,
		}
		resp, err = app.disconnectCmd(project, cmd)
	case "presence":
		channel := Channel(r.FormValue("channel"))
		cmd := &presenceApiCommand{
			Channel: channel,
		}
		resp, err = app.presenceCmd(project, cmd)
	case "history":
		channel := Channel(r.FormValue("channel"))
		cmd := &historyApiCommand{
			Channel: channel,
		}
		resp, err = app.historyCmd(project, cmd)
	}

	if err != nil {
		logger.ERROR.Println(err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

var upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}

// adminWebsocketHandler handles admin websocket connections.
func (app *application) adminWebsocketHandler(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}
	defer ws.Close()
	c, err := newAdminClient(app, ws)
	if err != nil {
		return
	}
	logger.INFO.Printf("new admin session established with uid %s\n", c.uid())
	defer func() {
		close(c.closeChan)
		err := app.removeAdminConn(c)
		if err != nil {
			logger.ERROR.Println(err)
		}
	}()

	go c.writer()

	for {
		_, message, err := ws.ReadMessage()
		if err != nil {
			break
		}
		resp, err := c.handleMessage(message)
		if err != nil {
			break
		}
		msgBytes, err := json.Marshal(resp)
		if err != nil {
			break
		} else {
			err := c.send(string(msgBytes))
			if err != nil {
				break
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"strings"
	"time"
)

const framesPerSecond = 8
const getReadyPause = 400
const maxNameLength = 25
const maxParallelRounds = 100
const maxPlayersPerRound = 5
const minPlayersPerRound = 1
const maxRoundWaitingTimeSec = 5
const maxRoundRunningTimeSec = 600
const maxSpeed = 5

const bonusPoint = 5
const lowFactor = 50
const highFactor = 5

const mapWidth = 179
const mapHeight = 38
const nameTableWidth = 30
const horizontalCarWidth = 7
const horizontalCarHeight = 3
const verticalCarWidth = 5
const verticalCarHeight = 3

const colorPrefix = "\x1b["
const colorPostfix = "m"
const bonus = "\xE2\x99\xA5"
const bomb = "\xE2\x9C\xB3"

var (
	cars = [4][]byte{}
	// http://www.isthe.com/chongo/tech/comp/ansi_escapes.html
	home  = []byte{27, 91, 72}
	clear = []byte{27, 91, 50, 74}
	//
	middle = []byte{27, 91, 49, 52, 65, 27, 91, 57, 52, 68}
	conf   Config
)

// States of the round
const (
	COMPILING = 1 + iota
	WAITING
	STARTING
	RUNNING
	FINISHED
)

// Directions
const (
	LEFT = 0 + iota
	RIGHT
	UP
	DOWN
)

// Car Borders
const (
	LEFTUP = 0 + iota
	RIGHTUP
	RIGHTDOWN
	LEFTDOWN
)

// Damage points
const (
	DAMAGE_BACK  = 2
	DAMAGE_FRONT = 4
	DAMAGE_SIDE  = 6
)

// Colors
const (
	RESET = 0
	BOLD  = 1
	RED   = 31
	GREEN = 32
	//YELLOW  = 33
	//BLUE    = 34
	//MAGENTA = 35
)

type Config struct {
	Log      *log.Logger
	AcidPath string
}

type Point struct {
	X, Y int
}

type Symbol struct {
	Color int
	Char  []byte
}

type Symbols []Symbol

func getAcid(fileName string) ([]byte, error) {
	fileStat, err := os.Stat(conf.AcidPath + "/" + fileName)
	if err != nil {
		conf.Log.Printf("Acid %s does not exist: %v\n", fileName, err)
		return []byte{}, err
	}

	acid := make([]byte, fileStat.Size())
	f, err := os.OpenFile(conf.AcidPath+"/"+fileName, os.O_RDONLY, os.ModePerm)
	if err != nil {
		conf.Log.Printf("Error while opening %s: %v\n", fileName, err)
		os.Exit(1)
	}
	defer f.Close()

	f.Read(acid)

	return acid, nil
}

func getPlayerData(conn net.Conn, splash []byte) (Player, error) {
	// Get data of player and return the structure
	conn.Write(clear)
	conn.Write(home)
	conn.Write(splash)
	conn.Write(middle)

	io := bufio.NewReader(conn)

	line, _ := io.ReadString('\n')
	name := strings.Replace(strings.Replace(line, "\n", "", -1), "\r", "", -1)
	if name == "" {
		return Player{}, errors.New("Empty name")
	}
	if len(name) > maxNameLength {
		return Player{}, errors.New("Too long name")
	}

	return Player{Conn: conn, Name: name, Health: 100, Car: Car{Speed: 1}}, nil
}

func checkRoundReady(compileRoundChannel, runningRoundChannel chan Round) {
	for {
		fmt.Println("compile/waiting rounds:", len(compileRoundChannel))
		r := <-compileRoundChannel
		fmt.Println(r.Id, "players in round:", len(r.Players))

		if len(r.Players) == maxPlayersPerRound ||
			(r.State == WAITING && r.LastStateChange.Add(maxRoundWaitingTimeSec*time.Second).Before(time.Now())) {
			// We are starting round if it is fully booked or waiting time is expired
			fmt.Println(r.Id, "Round has changed to the state STARTING")
			r.State = STARTING
			r.LastStateChange = time.Now()
			runningRoundChannel <- r
		} else if len(r.Players) >= minPlayersPerRound {
			if r.State == COMPILING {
				fmt.Println(r.Id, "Round has changed to the state WAITING")
				r.State = WAITING
				r.LastStateChange = time.Now()
			} else if r.State == WAITING {
				r.writeToAllPlayers([]byte(fmt.Sprintf(
					"Waiting %d seconds for other players to join\n",
					r.LastStateChange.Unix()+maxRoundWaitingTimeSec-time.Now().Unix())), true)
			}
			compileRoundChannel <- r
		} else {
			// Return round back
			compileRoundChannel <- r
		}
		time.Sleep(1 * time.Second)
	}
}

func checkRoundRun(runningRoundChannel chan Round) {
	for {
		round := <-runningRoundChannel
		if len(round.Players) > 0 {
			for len(round.Players) < maxPlayersPerRound {
				p := round.generateBot()
				p.initPlayer(len(round.Players))
				round.Players = append(round.Players, p)
			}
			go round.start()
		}
	}
}

func (symbols Symbols) symbolsToByte() []byte {
	var returnSlice []byte
	for _, symbol := range symbols {
		// Should be something like \x1b[31m^\x1b[0m for symbols with colors or ^ without
		if symbol.Color != RESET {
			returnSlice = append(returnSlice, []byte(colorPrefix+fmt.Sprintf("%d", symbol.Color)+colorPostfix)...)
		}
		returnSlice = append(returnSlice, symbol.Char...)
		if symbol.Color != RESET {
			returnSlice = append(returnSlice, []byte(colorPrefix+fmt.Sprintf("%d", RESET)+colorPostfix)...)
		}
	}
	return returnSlice
}

func prepare(conn net.Conn, splash []byte, compileRoundChannel chan Round) {
	p, err := getPlayerData(conn, splash)
	if err != nil {
		conn.Close()
		return
	}

	p.checkBestRoundForPlayer(compileRoundChannel)
}

func main() {
	// Make random unique
	rand.Seed(time.Now().Unix())
	var logFile, acidPath string
	var port, users int

	flag.StringVar(&logFile, "l", "/var/log/race.log", "Log file")
	flag.IntVar(&port, "p", 4242, "Port to listen")
	flag.StringVar(&acidPath, "a", "/Users/leoleovich/go/src/github.com/leoleovich/crashci/artifacts", "Artifacts location")
	flag.Parse()

	logfile, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
	conf = Config{log.New(logfile, "", log.Ldate|log.Lmicroseconds|log.Lshortfile), acidPath}

	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		os.Exit(2)
	}
	defer l.Close()

	// Read sketches
	cars[LEFT], _ = getAcid("carLeft.txt")
	cars[RIGHT], _ = getAcid("carRight.txt")
	cars[UP], _ = getAcid("carUp.txt")
	cars[DOWN], _ = getAcid("carDown.txt")
	splash, _ := getAcid("splash.txt")

	compileRoundChannel := make(chan Round, maxParallelRounds)
	runningRoundChannel := make(chan Round, maxParallelRounds)

	go checkRoundReady(compileRoundChannel, runningRoundChannel)
	go checkRoundRun(runningRoundChannel)

	for {
		conn, err := l.Accept()
		users++
		fmt.Println("In total", users, "users connected")
		if err != nil {
			conf.Log.Println("Failed to accept request", err)
		}

		go prepare(conn, splash, compileRoundChannel)
	}
}
<commit_msg>Kick out player with broken connection at the beginning<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"strings"
	"time"
)

const framesPerSecond = 8
const getReadyPause = 400
const maxNameLength = 25
const maxParallelRounds = 100
const maxPlayersPerRound = 5
const minPlayersPerRound = 1
const maxRoundWaitingTimeSec = 5
const maxRoundRunningTimeSec = 600
const maxSpeed = 5

const bonusPoint = 5
const lowFactor = 50
const highFactor = 5

const mapWidth = 179
const mapHeight = 38
const nameTableWidth = 30
const horizontalCarWidth = 7
const horizontalCarHeight = 3
const verticalCarWidth = 5
const verticalCarHeight = 3

const colorPrefix = "\x1b["
const colorPostfix = "m"
const bonus = "\xE2\x99\xA5"
const bomb = "\xE2\x9C\xB3"

var (
	cars = [4][]byte{}
	// http://www.isthe.com/chongo/tech/comp/ansi_escapes.html
	home  = []byte{27, 91, 72}
	clear = []byte{27, 91, 50, 74}
	//
	middle = []byte{27, 91, 49, 52, 65, 27, 91, 57, 52, 68}
	conf   Config
)

// States of the round
const (
	COMPILING = 1 + iota
	WAITING
	STARTING
	RUNNING
	FINISHED
)

// Directions
const (
	LEFT = 0 + iota
	RIGHT
	UP
	DOWN
)

// Car Borders
const (
	LEFTUP = 0 + iota
	RIGHTUP
	RIGHTDOWN
	LEFTDOWN
)

// Damage points
const (
	DAMAGE_BACK  = 2
	DAMAGE_FRONT = 4
	DAMAGE_SIDE  = 6
)

// Colors
const (
	RESET = 0
	BOLD  = 1
	RED   = 31
	GREEN = 32
	//YELLOW  = 33
	//BLUE    = 34
	//MAGENTA = 35
)

type Config struct {
	Log      *log.Logger
	AcidPath string
}

type Point struct {
	X, Y int
}

type Symbol struct {
	Color int
	Char  []byte
}

type Symbols []Symbol

func getAcid(fileName string) ([]byte, error) {
	fileStat, err := os.Stat(conf.AcidPath + "/" + fileName)
	if err != nil {
		conf.Log.Printf("Acid %s does not exist: %v\n", fileName, err)
		return []byte{}, err
	}

	acid := make([]byte, fileStat.Size())
	f, err := os.OpenFile(conf.AcidPath+"/"+fileName, os.O_RDONLY, os.ModePerm)
	if err != nil {
		conf.Log.Printf("Error while opening %s: %v\n", fileName, err)
		os.Exit(1)
	}
	defer f.Close()

	f.Read(acid)

	return acid, nil
}

func getPlayerData(conn net.Conn, splash []byte) (Player, error) {
	// Get data of player and return the structure
	_, err := conn.Write(clear)
	if err != nil {
		return Player{}, errors.New("Communication error")
	}
	_, err = conn.Write(home)
	if err != nil {
		return Player{}, errors.New("Communication error")
	}
	_, err = conn.Write(splash)
	if err != nil {
		return Player{}, errors.New("Communication error")
	}
	_, err = conn.Write(middle)
	if err != nil {
		return Player{}, errors.New("Communication error")
	}

	io := bufio.NewReader(conn)

	line, err := io.ReadString('\n')
	if err != nil {
		return Player{}, errors.New("Communication error")
	}

	name := strings.Replace(strings.Replace(line, "\n", "", -1), "\r", "", -1)
	if name == "" {
		return Player{}, errors.New("Empty name")
	}
	if len(name) > maxNameLength {
		return Player{}, errors.New("Too long name")
	}

	return Player{Conn: conn, Name: name, Health: 100, Car: Car{Speed: 1}}, nil
}

func checkRoundReady(compileRoundChannel, runningRoundChannel chan Round) {
	for {
		fmt.Println("compile/waiting rounds:", len(compileRoundChannel))
		r := <-compileRoundChannel
		fmt.Println(r.Id, "players in round:", len(r.Players))

		if len(r.Players) == maxPlayersPerRound ||
			(r.State == WAITING && r.LastStateChange.Add(maxRoundWaitingTimeSec*time.Second).Before(time.Now())) {
			// We are starting round if it is fully booked or waiting time is expired
			fmt.Println(r.Id, "Round has changed to the state STARTING")
			r.State = STARTING
			r.LastStateChange = time.Now()
			runningRoundChannel <- r
		} else if len(r.Players) >= minPlayersPerRound {
			if r.State == COMPILING {
				fmt.Println(r.Id, "Round has changed to the state WAITING")
				r.State = WAITING
				r.LastStateChange = time.Now()
			} else if r.State == WAITING {
				r.writeToAllPlayers([]byte(fmt.Sprintf(
					"Waiting %d seconds for other players to join\n",
					r.LastStateChange.Unix()+maxRoundWaitingTimeSec-time.Now().Unix())), true)
			}
			compileRoundChannel <- r
		} else {
			// Return round back
			compileRoundChannel <- r
		}
		time.Sleep(1 * time.Second)
	}
}

func checkRoundRun(runningRoundChannel chan Round) {
	for {
		round := <-runningRoundChannel
		if len(round.Players) > 0 {
			for len(round.Players) < maxPlayersPerRound {
				p := round.generateBot()
				p.initPlayer(len(round.Players))
				round.Players = append(round.Players, p)
			}
			go round.start()
		}
	}
}

func (symbols Symbols) symbolsToByte() []byte {
	var returnSlice []byte
	for _, symbol := range symbols {
		// Should be something like \x1b[31m^\x1b[0m for symbols with colors or ^ without
		if symbol.Color != RESET {
			returnSlice = append(returnSlice, []byte(colorPrefix+fmt.Sprintf("%d", symbol.Color)+colorPostfix)...)
		}
		returnSlice = append(returnSlice, symbol.Char...)
		if symbol.Color != RESET {
			returnSlice = append(returnSlice, []byte(colorPrefix+fmt.Sprintf("%d", RESET)+colorPostfix)...)
		}
	}
	return returnSlice
}

func prepare(conn net.Conn, splash []byte, compileRoundChannel chan Round) {
	p, err := getPlayerData(conn, splash)
	if err != nil {
		conn.Close()
		return
	}

	p.checkBestRoundForPlayer(compileRoundChannel)
}

func main() {
	// Make random unique
	rand.Seed(time.Now().Unix())
	var logFile, acidPath string
	var port, users int

	flag.StringVar(&logFile, "l", "/var/log/race.log", "Log file")
	flag.IntVar(&port, "p", 4242, "Port to listen")
	flag.StringVar(&acidPath, "a", "/Users/leoleovich/go/src/github.com/leoleovich/crashci/artifacts", "Artifacts location")
	flag.Parse()

	logfile, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
	conf = Config{log.New(logfile, "", log.Ldate|log.Lmicroseconds|log.Lshortfile), acidPath}

	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		os.Exit(2)
	}
	defer l.Close()

	// Read sketches
	cars[LEFT], _ = getAcid("carLeft.txt")
	cars[RIGHT], _ = getAcid("carRight.txt")
	cars[UP], _ = getAcid("carUp.txt")
	cars[DOWN], _ = getAcid("carDown.txt")
	splash, _ := getAcid("splash.txt")

	compileRoundChannel := make(chan Round, maxParallelRounds)
	runningRoundChannel := make(chan Round, maxParallelRounds)

	go checkRoundReady(compileRoundChannel, runningRoundChannel)
	go checkRoundRun(runningRoundChannel)

	for {
		conn, err := l.Accept()
		users++
		fmt.Println("In total", users, "users connected")
		if err != nil {
			conf.Log.Println("Failed to accept request", err)
		}

		go prepare(conn, splash, compileRoundChannel)
	}
}
<|endoftext|>


<commit_before>package libgobuster

// GobusterPlugin is an interface which plugins must implement
type GobusterPlugin interface {
	Name() string
	RequestsPerRun() int
	PreRun() error
	Run(string, chan<- Result) error
	GetConfigString() (string, error)
}

type Result interface {
	ResultToString() (string, error)
}
<commit_msg>missing comment<commit_after>package libgobuster

// GobusterPlugin is an interface which plugins must implement
type GobusterPlugin interface {
	Name() string
	RequestsPerRun() int
	PreRun() error
	Run(string, chan<- Result) error
	GetConfigString() (string, error)
}

// Result is an interface for the Result object
type Result interface {
	ResultToString() (string, error)
}
<|endoftext|>


<commit_before>package main

import (
	"math/rand"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/mux"
	"gopkg.in/unrolled/render.v1"
)

// TransitEdge is a direct transportation between two locations.
type TransitEdge struct {
	VoyageNumber string    `json:"voyage"`
	Origin       string    `json:"origin"`
	Destination  string    `json:"destination"`
	Departure    time.Time `json:"departure"`
	Arrival      time.Time `json:"arrival"`
}

// TransitPath is a series of transit edges.
type TransitPath struct {
	Edges []TransitEdge `json:"edges"`
}

// FindShortestPath computes the shortest paths between two locations.
func FindShortestPath(origin, destination string) []TransitPath {
	start := time.Now()
	date := nextDate(start)

	v := allVertices(origin, destination)

	candidates := make([]TransitPath, 3+rand.Intn(3))
	for i := range candidates {
		v = randChunk(v)

		var edges []TransitEdge

		edges, date = appendEdge(edges, origin, v[0], date)

		for j := 0; j < len(v)-1; j++ {
			edges, date = appendEdge(edges, v[j], v[j+1], date)
		}

		edges, _ = appendEdge(edges, v[len(v)-1], destination, date)

		candidates[i] = TransitPath{Edges: edges}
	}

	return candidates
}

func appendEdge(edges []TransitEdge, curr, next string, date time.Time) ([]TransitEdge, time.Time) {
	var (
		from = nextDate(date)
		to   = nextDate(from)
	)

	edges = append(edges, TransitEdge{
		VoyageNumber: randVoyageNumber(),
		Origin:       curr,
		Destination:  next,
		Departure:    from,
		Arrival:      to,
	})

	return edges, nextDate(to)
}

func allVertices(origin, destination string) []string {
	locations := []string{"CNHKG", "AUMEL", "SESTO", "FIHEL", "USCHI", "JNTKO", "DEHAM", "CNSHA", "NLRTM", "SEGOT", "CNHGH", "USNYC", "USDAL"}
	for i, l := range locations {
		if l == origin {
			locations = append(locations[:i], locations[i+1:]...)
		}
	}
	for i, l := range locations {
		if l == destination {
			locations = append(locations[:i], locations[i+1:]...)
		}
	}
	return locations
}

func randVoyageNumber() string {
	switch rand.Intn(5) {
	case 0:
		return "0100S"
	case 1:
		return "0200T"
	case 2:
		return "0300A"
	case 3:
		return "0301S"
	}
	return "0400S"
}

func randChunk(locations []string) []string {
	// TODO: Shuffle the locations first for even more randomness.

	t := len(locations)

	var c int
	if t > 4 {
		c = 1 + rand.Intn(5)
	} else {
		c = t
	}

	return locations[:c]
}

func nextDate(t time.Time) time.Time {
	n := time.Duration(rand.Intn(1000) - 500)
	return t.Add(24 * time.Hour).Add(n * time.Minute)
}

func main() {

	r := render.New(render.Options{IndentJSON: true})
	router := mux.NewRouter()

	router.HandleFunc("/paths", func(w http.ResponseWriter, req *http.Request) {
		from := req.URL.Query().Get("from")
		to := req.URL.Query().Get("to")

		if len(from) == 0 || len(to) == 0 {
			r.JSON(w, http.StatusBadRequest, map[string]interface{}{"error": "missing parameters"})
			return
		}

		r.JSON(w, http.StatusOK, FindShortestPath(from, to))
	}).Methods("GET")

	http.Handle("/", router)

	http.ListenAndServe(":"+os.Getenv("PORT"), nil)
}
<commit_msg>Implement a candidate generator using goroutines.<commit_after>package main

import (
	"math/rand"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/gorilla/mux"
	"gopkg.in/unrolled/render.v1"
)

// TransitEdge is a direct transportation between two locations.
type TransitEdge struct {
	VoyageNumber string    `json:"voyage"`
	Origin       string    `json:"origin"`
	Destination  string    `json:"destination"`
	Departure    time.Time `json:"departure"`
	Arrival      time.Time `json:"arrival"`
}

// TransitPath is a series of transit edges.
type TransitPath struct {
	Edges []TransitEdge `json:"edges"`
}

// FindShortestPath computes the shortest paths between two locations.
func FindShortestPath(origin, destination string) []TransitPath {

	var candidates []TransitPath

	for p := range generateCandidates(origin, destination, 3+rand.Intn(3)) {
		candidates = append(candidates, p)
	}

	return candidates
}

// generateCandidates generates new candidates and pushes them into a channel.
// Finding each candidate is potentially time-consuming, so we compute them in
// parallel.
func generateCandidates(origin, destination string, n int) chan TransitPath {
	ch := make(chan TransitPath)

	var wg sync.WaitGroup
	wg.Add(n)

	for i := 0; i < n; i++ {
		go func() {
			ch <- findCandidate(origin, destination, nextDate(time.Now()))
			wg.Done()
		}()
	}

	go func() {
		wg.Wait()
		close(ch)
	}()

	return ch
}

// findCandidate finds a random path between two locations starting from a
// given time.
func findCandidate(origin, destination string, start time.Time) TransitPath {
	v := allVertices(origin, destination)
	v = randChunk(v)

	var edges []TransitEdge

	edges, start = appendEdge(edges, origin, v[0], start)

	for j := 0; j < len(v)-1; j++ {
		edges, start = appendEdge(edges, v[j], v[j+1], start)
	}

	edges, _ = appendEdge(edges, v[len(v)-1], destination, start)

	return TransitPath{Edges: edges}
}

func appendEdge(edges []TransitEdge, curr, next string, date time.Time) ([]TransitEdge, time.Time) {
	var (
		from = nextDate(date)
		to   = nextDate(from)
	)

	edges = append(edges, TransitEdge{
		VoyageNumber: randVoyageNumber(),
		Origin:       curr,
		Destination:  next,
		Departure:    from,
		Arrival:      to,
	})

	return edges, nextDate(to)
}

func allVertices(origin, destination string) []string {
	locations := []string{"CNHKG", "AUMEL", "SESTO", "FIHEL", "USCHI", "JNTKO", "DEHAM", "CNSHA", "NLRTM", "SEGOT", "CNHGH", "USNYC", "USDAL"}
	for i, l := range locations {
		if l == origin {
			locations = append(locations[:i], locations[i+1:]...)
		}
	}
	for i, l := range locations {
		if l == destination {
			locations = append(locations[:i], locations[i+1:]...)
		}
	}
	return locations
}

func randVoyageNumber() string {
	switch rand.Intn(5) {
	case 0:
		return "0100S"
	case 1:
		return "0200T"
	case 2:
		return "0300A"
	case 3:
		return "0301S"
	}
	return "0400S"
}

func randChunk(locations []string) []string {
	// TODO: Shuffle the locations first for even more randomness.

	t := len(locations)

	var c int
	if t > 4 {
		c = 1 + rand.Intn(5)
	} else {
		c = t
	}

	return locations[:c]
}

func nextDate(t time.Time) time.Time {
	n := time.Duration(rand.Intn(1000) - 500)
	return t.Add(24 * time.Hour).Add(n * time.Minute)
}

func main() {
	r := render.New(render.Options{IndentJSON: true})
	router := mux.NewRouter()

	router.HandleFunc("/paths", func(w http.ResponseWriter, req *http.Request) {
		from := req.URL.Query().Get("from")
		to := req.URL.Query().Get("to")

		if len(from) == 0 || len(to) == 0 {
			r.JSON(w, http.StatusBadRequest, map[string]interface{}{"error": "missing parameters"})
			return
		}

		r.JSON(w, http.StatusOK, FindShortestPath(from, to))
	}).Methods("GET")

	http.Handle("/", router)

	http.ListenAndServe(":"+os.Getenv("PORT"), nil)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"strings"

	sh "github.com/codeskyblue/go-sh"
	"github.com/spf13/cobra"
)

var environment []string
var volumes []string
var skip string
var timeout string
var checksh = `#!/bin/bash
set -e
type docker >/dev/null 2>&1 || { echo >&2 "Can't find docker command."; exit 1; }

docker_path=""

if [ $OSTYPE == "linux-gnu" ] ; then
  docker_path=$(which docker)
  libltdl_path=$(ldd $docker_path | grep libltdl | awk '{print $3}')
	if [ -n "$libltdl_path" ] ; then
    libltdl_path="--volume $libltdl_path:/usr/lib/$(basename $libltdl_path)"
	fi
elif  [[ $OSTYPE == *"darwin"* ]]; then
  docker_path="/usr/bin/docker"
  libltdl_path=""
else
  echo "Unrecognized OS: $OSTYPE"
  exit 1
fi

echo "Popper check started"
docker run --rm -i %s \
  $libltdl_path \
  --volume $PWD:$PWD \
  --volume $docker_path:/usr/bin/docker \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --workdir $PWD \
  ivotron/popperci-experimenter %s %s
echo "Popper check finished"
echo "status: $(cat popper_status)"
`

func writePopperCheckScript() {

	env := ""
	if len(environment) > 0 {
		env += " -e " + strings.Join(environment, " -e ")
	}
	if len(volumes) > 0 {
		env += " -v " + strings.Join(volumes, " -v ")
	}
	var content string
	if len(skip) > 0 {
		content = fmt.Sprintf(checksh, env, "--timeout "+timeout, "--skip "+skip)
	} else {
		content = fmt.Sprintf(checksh, env, "--timeout "+timeout, "")
	}
	err := ioutil.WriteFile("/tmp/poppercheck", []byte(content), 0755)
	if err != nil {
		log.Fatalln("Error writing bash script to /tmp")
	}
}

var checkCmd = &cobra.Command{
	Use:   "check",
	Short: "Run experiment and check integrity (status) of experiment",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) > 0 {
			log.Fatalln("This command doesn't take arguments")
		}
		writePopperCheckScript()
		if err := sh.Command("/tmp/poppercheck").Run(); err != nil {
			log.Fatalln(err)
		}
	},
}

func checkWeInExperimentFolder() {
	if !sh.Test("dir", "../../experiments") {
		log.Fatalln("Not inside an experiment folder, 'cd' into one first.")
	}
}

func init() {
	RootCmd.AddCommand(checkCmd)

	checkCmd.Flags().StringSliceVarP(&environment, "environment", "e", []string{}, "Environment variables to be defined inside the test container.")
	checkCmd.Flags().StringSliceVarP(&volumes, "volume", "v", []string{}, "Volumes to be passed to the test container.")
	checkCmd.Flags().StringVarP(&skip, "skip", "s", "", "Comma-separated list of stages to skip.")
	checkCmd.Flags().StringVarP(&timeout, "timeout", "t", "36000", "Timeout limit for experiment (default: 10 hrs).")
}
<commit_msg>Removes print of popper status<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"strings"

	sh "github.com/codeskyblue/go-sh"
	"github.com/spf13/cobra"
)

var environment []string
var volumes []string
var skip string
var timeout string
var checksh = `#!/bin/bash
set -e
type docker >/dev/null 2>&1 || { echo >&2 "Can't find docker command."; exit 1; }

docker_path=""

if [ $OSTYPE == "linux-gnu" ] ; then
  docker_path=$(which docker)
  libltdl_path=$(ldd $docker_path | grep libltdl | awk '{print $3}')
	if [ -n "$libltdl_path" ] ; then
    libltdl_path="--volume $libltdl_path:/usr/lib/$(basename $libltdl_path)"
	fi
elif  [[ $OSTYPE == *"darwin"* ]]; then
  docker_path="/usr/bin/docker"
  libltdl_path=""
else
  echo "Unrecognized OS: $OSTYPE"
  exit 1
fi

echo "Popper check started"
docker run --rm -i %s \
  $libltdl_path \
  --volume $PWD:$PWD \
  --volume $docker_path:/usr/bin/docker \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --workdir $PWD \
  ivotron/popperci-experimenter %s %s
echo "Popper check finished"
`

func writePopperCheckScript() {

	env := ""
	if len(environment) > 0 {
		env += " -e " + strings.Join(environment, " -e ")
	}
	if len(volumes) > 0 {
		env += " -v " + strings.Join(volumes, " -v ")
	}
	var content string
	if len(skip) > 0 {
		content = fmt.Sprintf(checksh, env, "--timeout "+timeout, "--skip "+skip)
	} else {
		content = fmt.Sprintf(checksh, env, "--timeout "+timeout, "")
	}
	err := ioutil.WriteFile("/tmp/poppercheck", []byte(content), 0755)
	if err != nil {
		log.Fatalln("Error writing bash script to /tmp")
	}
}

var checkCmd = &cobra.Command{
	Use:   "check",
	Short: "Run experiment and check integrity (status) of experiment",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) > 0 {
			log.Fatalln("This command doesn't take arguments")
		}
		writePopperCheckScript()
		if err := sh.Command("/tmp/poppercheck").Run(); err != nil {
			log.Fatalln(err)
		}
	},
}

func checkWeInExperimentFolder() {
	if !sh.Test("dir", "../../experiments") {
		log.Fatalln("Not inside an experiment folder, 'cd' into one first.")
	}
}

func init() {
	RootCmd.AddCommand(checkCmd)

	checkCmd.Flags().StringSliceVarP(&environment, "environment", "e", []string{}, "Environment variables to be defined inside the test container.")
	checkCmd.Flags().StringSliceVarP(&volumes, "volume", "v", []string{}, "Volumes to be passed to the test container.")
	checkCmd.Flags().StringVarP(&skip, "skip", "s", "", "Comma-separated list of stages to skip.")
	checkCmd.Flags().StringVarP(&timeout, "timeout", "t", "36000", "Timeout limit for experiment (default: 10 hrs).")
}
<|endoftext|>


<commit_before>package butlerd

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/itchio/butler/database"
	"github.com/itchio/butler/progress"

	"github.com/itchio/butler/comm"
	"github.com/itchio/butler/database/models"
	"github.com/itchio/butler/mansion"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/wharf/state"
	"github.com/jinzhu/gorm"
	"github.com/pkg/errors"
	"github.com/sourcegraph/jsonrpc2"
)

type RequestHandler func(rc *RequestContext) (interface{}, error)

type Router struct {
	Handlers       map[string]RequestHandler
	MansionContext *mansion.Context
	CancelFuncs    *CancelFuncs
	openDB         OpenDBFunc
}

type OpenDBFunc func() (*gorm.DB, error)

func NewRouter(mansionContext *mansion.Context, openDB OpenDBFunc) *Router {
	return &Router{
		Handlers:       make(map[string]RequestHandler),
		MansionContext: mansionContext,
		CancelFuncs: &CancelFuncs{
			Funcs: make(map[string]context.CancelFunc),
		},

		openDB: openDB,
	}
}

func (r *Router) Register(method string, rh RequestHandler) {
	if _, ok := r.Handlers[method]; ok {
		panic(fmt.Sprintf("Can't register handler twice for %s", method))
	}
	r.Handlers[method] = rh
}

func (r *Router) Dispatch(ctx context.Context, origConn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	method := req.Method
	var res interface{}

	conn := &jsonrpc2Conn{origConn}
	consumer, cErr := NewStateConsumer(&NewStateConsumerParams{
		Ctx:  ctx,
		Conn: conn,
	})
	if cErr != nil {
		return
	}

	err := func() (err error) {
		defer func() {
			if r := recover(); r != nil {
				if rErr, ok := r.(error); ok {
					err = errors.WithStack(rErr)
				} else {
					err = errors.Errorf("panic: %v", r)
				}
			}
		}()

		if h, ok := r.Handlers[method]; ok {
			var _db *gorm.DB
			getDB := func() *gorm.DB {
				if _db == nil {
					db, err := r.openDB()
					if err != nil {
						panic(err)
					}

					database.SetLogger(db, consumer)

					_db = db
				}
				return _db
			}
			defer func() {
				if _db != nil {
					err := _db.Close()
					if err != nil {
						comm.Warnf("Could not close db connection: %s", err.Error())
					}
				}
			}()

			rc := &RequestContext{
				Ctx:            ctx,
				Router:         r,
				Harness:        NewProductionHarness(),
				Consumer:       consumer,
				Params:         req.Params,
				Conn:           conn,
				MansionContext: r.MansionContext,
				CancelFuncs:    r.CancelFuncs,
				DB:             getDB,
			}

			rc.Consumer.OnProgress = func(alpha float64) {
				if rc.counter == nil {
					// skip
					return
				}

				rc.counter.SetProgress(alpha)
				notif := &ProgressNotification{
					Progress: alpha,
					ETA:      rc.counter.ETA().Seconds(),
					BPS:      rc.counter.BPS(),
				}
				// cannot use autogenerated wrappers to avoid import cycles
				rc.Notify("Progress", notif)
			}
			rc.Consumer.OnProgressLabel = func(label string) {
				// muffin
			}
			rc.Consumer.OnPauseProgress = func() {
				if rc.counter != nil {
					rc.counter.Pause()
				}
			}
			rc.Consumer.OnResumeProgress = func() {
				if rc.counter != nil {
					rc.counter.Resume()
				}
			}

			res, err = h(rc)
		} else {
			err = StandardRpcError(jsonrpc2.CodeMethodNotFound)
		}
		return
	}()

	if err == nil {
		err = origConn.Reply(ctx, req.ID, res)
		if err != nil {
			consumer.Errorf("Error while replying: %s", err.Error())
		}
		return
	}

	if ee, ok := AsButlerdError(err); ok {
		origConn.ReplyWithError(ctx, req.ID, ee.AsJsonRpc2())
		return
	}

	var rawData *json.RawMessage
	input := map[string]interface{}{
		"stack":         fmt.Sprintf("%+v", err),
		"butlerVersion": r.MansionContext.VersionString,
	}
	es, err := json.Marshal(input)
	if err == nil {
		rm := json.RawMessage(es)
		rawData = &rm
	}
	origConn.ReplyWithError(ctx, req.ID, &jsonrpc2.Error{
		Code:    jsonrpc2.CodeInternalError,
		Message: err.Error(),
		Data:    rawData,
	})
}

type RequestContext struct {
	Ctx            context.Context
	Harness        Harness
	Consumer       *state.Consumer
	Params         *json.RawMessage
	Conn           Conn
	MansionContext *mansion.Context
	CancelFuncs    *CancelFuncs
	DB             DBGetter
	Router         *Router

	notificationInterceptors map[string]NotificationInterceptor
	counter                  *progress.Counter
}

type DBGetter func() *gorm.DB

type WithParamsFunc func() (interface{}, error)

type NotificationInterceptor func(method string, params interface{}) error

func (rc *RequestContext) Call(method string, params interface{}, res interface{}) error {
	return rc.Conn.Call(rc.Ctx, method, params, res)
}

func (rc *RequestContext) InterceptNotification(method string, interceptor NotificationInterceptor) {
	if rc.notificationInterceptors == nil {
		rc.notificationInterceptors = make(map[string]NotificationInterceptor)
	}
	rc.notificationInterceptors[method] = interceptor
}

func (rc *RequestContext) StopInterceptingNotification(method string) {
	if rc.notificationInterceptors == nil {
		return
	}
	delete(rc.notificationInterceptors, method)
}

func (rc *RequestContext) Notify(method string, params interface{}) error {
	if rc.notificationInterceptors != nil {
		if ni, ok := rc.notificationInterceptors[method]; ok {
			return ni(method, params)
		}
	}
	return rc.Conn.Notify(rc.Ctx, method, params)
}

func (rc *RequestContext) RootClient() (*itchio.Client, error) {
	return rc.KeyClient("<keyless>")
}

func (rc *RequestContext) KeyClient(key string) (*itchio.Client, error) {
	return rc.MansionContext.NewClient(key)
}

func (rc *RequestContext) ProfileClient(profileID int64) (*models.Profile, *itchio.Client) {
	if profileID == 0 {
		panic(errors.New("profileId must be non-zero"))
	}

	profile := models.ProfileByID(rc.DB(), profileID)
	if profile == nil {
		panic(errors.Errorf("Could not find profile %d", profileID))
	}

	if profile.APIKey == "" {
		panic(errors.Errorf("Profile %d lacks API key", profileID))
	}

	client, err := rc.MansionContext.NewClient(profile.APIKey)
	if err != nil {
		panic(errors.WithStack(err))
	}

	return profile, client
}

func (rc *RequestContext) StartProgress() {
	rc.StartProgressWithTotalBytes(0)
}

func (rc *RequestContext) StartProgressWithTotalBytes(totalBytes int64) {
	rc.StartProgressWithInitialAndTotal(0.0, totalBytes)
}

func (rc *RequestContext) StartProgressWithInitialAndTotal(initialProgress float64, totalBytes int64) {
	rc.Consumer.Infof("Starting progress (initial %.2f, totalBytes %d)", initialProgress, totalBytes)

	if rc.counter != nil {
		rc.Consumer.Warnf("Asked to start progress but already tracking progress!")
		return
	}

	rc.counter = progress.NewCounter()
	rc.counter.SetSilent(true)
	rc.counter.SetProgress(initialProgress)
	rc.counter.SetTotalBytes(totalBytes)
	rc.counter.Start()
}

func (rc *RequestContext) EndProgress() {
	if rc.counter != nil {
		rc.counter.Finish()
		rc.counter = nil
	} else {
		rc.Consumer.Warnf("Asked to stop progress but wasn't tracking progress!")
	}
}

type CancelFuncs struct {
	Funcs map[string]context.CancelFunc
}

func (cf *CancelFuncs) Add(id string, f context.CancelFunc) {
	cf.Funcs[id] = f
}

func (cf *CancelFuncs) Remove(id string) {
	delete(cf.Funcs, id)
}

func (cf *CancelFuncs) Call(id string) bool {
	if f, ok := cf.Funcs[id]; ok {
		f()
		delete(cf.Funcs, id)
		return true
	}

	return false
}
<commit_msg>Don't overwrite error in router<commit_after>package butlerd

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/itchio/butler/database"
	"github.com/itchio/butler/progress"

	"github.com/itchio/butler/comm"
	"github.com/itchio/butler/database/models"
	"github.com/itchio/butler/mansion"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/wharf/state"
	"github.com/jinzhu/gorm"
	"github.com/pkg/errors"
	"github.com/sourcegraph/jsonrpc2"
)

type RequestHandler func(rc *RequestContext) (interface{}, error)

type Router struct {
	Handlers       map[string]RequestHandler
	MansionContext *mansion.Context
	CancelFuncs    *CancelFuncs
	openDB         OpenDBFunc
}

type OpenDBFunc func() (*gorm.DB, error)

func NewRouter(mansionContext *mansion.Context, openDB OpenDBFunc) *Router {
	return &Router{
		Handlers:       make(map[string]RequestHandler),
		MansionContext: mansionContext,
		CancelFuncs: &CancelFuncs{
			Funcs: make(map[string]context.CancelFunc),
		},

		openDB: openDB,
	}
}

func (r *Router) Register(method string, rh RequestHandler) {
	if _, ok := r.Handlers[method]; ok {
		panic(fmt.Sprintf("Can't register handler twice for %s", method))
	}
	r.Handlers[method] = rh
}

func (r *Router) Dispatch(ctx context.Context, origConn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	method := req.Method
	var res interface{}

	conn := &jsonrpc2Conn{origConn}
	consumer, cErr := NewStateConsumer(&NewStateConsumerParams{
		Ctx:  ctx,
		Conn: conn,
	})
	if cErr != nil {
		return
	}

	err := func() (err error) {
		defer func() {
			if r := recover(); r != nil {
				if rErr, ok := r.(error); ok {
					err = errors.WithStack(rErr)
				} else {
					err = errors.Errorf("panic: %v", r)
				}
			}
		}()

		if h, ok := r.Handlers[method]; ok {
			var _db *gorm.DB
			getDB := func() *gorm.DB {
				if _db == nil {
					db, err := r.openDB()
					if err != nil {
						panic(err)
					}

					database.SetLogger(db, consumer)

					_db = db
				}
				return _db
			}
			defer func() {
				if _db != nil {
					err := _db.Close()
					if err != nil {
						comm.Warnf("Could not close db connection: %s", err.Error())
					}
				}
			}()

			rc := &RequestContext{
				Ctx:            ctx,
				Router:         r,
				Harness:        NewProductionHarness(),
				Consumer:       consumer,
				Params:         req.Params,
				Conn:           conn,
				MansionContext: r.MansionContext,
				CancelFuncs:    r.CancelFuncs,
				DB:             getDB,
			}

			rc.Consumer.OnProgress = func(alpha float64) {
				if rc.counter == nil {
					// skip
					return
				}

				rc.counter.SetProgress(alpha)
				notif := &ProgressNotification{
					Progress: alpha,
					ETA:      rc.counter.ETA().Seconds(),
					BPS:      rc.counter.BPS(),
				}
				// cannot use autogenerated wrappers to avoid import cycles
				rc.Notify("Progress", notif)
			}
			rc.Consumer.OnProgressLabel = func(label string) {
				// muffin
			}
			rc.Consumer.OnPauseProgress = func() {
				if rc.counter != nil {
					rc.counter.Pause()
				}
			}
			rc.Consumer.OnResumeProgress = func() {
				if rc.counter != nil {
					rc.counter.Resume()
				}
			}

			res, err = h(rc)
		} else {
			err = StandardRpcError(jsonrpc2.CodeMethodNotFound)
		}
		return
	}()

	if err == nil {
		err = origConn.Reply(ctx, req.ID, res)
		if err != nil {
			consumer.Errorf("Error while replying: %s", err.Error())
		}
		return
	}

	if ee, ok := AsButlerdError(err); ok {
		origConn.ReplyWithError(ctx, req.ID, ee.AsJsonRpc2())
		return
	}

	var rawData *json.RawMessage
	input := map[string]interface{}{
		"stack":         fmt.Sprintf("%+v", err),
		"butlerVersion": r.MansionContext.VersionString,
	}
	es, marshalErr := json.Marshal(input)
	if marshalErr == nil {
		rm := json.RawMessage(es)
		rawData = &rm
	}
	origConn.ReplyWithError(ctx, req.ID, &jsonrpc2.Error{
		Code:    jsonrpc2.CodeInternalError,
		Message: err.Error(),
		Data:    rawData,
	})
}

type RequestContext struct {
	Ctx            context.Context
	Harness        Harness
	Consumer       *state.Consumer
	Params         *json.RawMessage
	Conn           Conn
	MansionContext *mansion.Context
	CancelFuncs    *CancelFuncs
	DB             DBGetter
	Router         *Router

	notificationInterceptors map[string]NotificationInterceptor
	counter                  *progress.Counter
}

type DBGetter func() *gorm.DB

type WithParamsFunc func() (interface{}, error)

type NotificationInterceptor func(method string, params interface{}) error

func (rc *RequestContext) Call(method string, params interface{}, res interface{}) error {
	return rc.Conn.Call(rc.Ctx, method, params, res)
}

func (rc *RequestContext) InterceptNotification(method string, interceptor NotificationInterceptor) {
	if rc.notificationInterceptors == nil {
		rc.notificationInterceptors = make(map[string]NotificationInterceptor)
	}
	rc.notificationInterceptors[method] = interceptor
}

func (rc *RequestContext) StopInterceptingNotification(method string) {
	if rc.notificationInterceptors == nil {
		return
	}
	delete(rc.notificationInterceptors, method)
}

func (rc *RequestContext) Notify(method string, params interface{}) error {
	if rc.notificationInterceptors != nil {
		if ni, ok := rc.notificationInterceptors[method]; ok {
			return ni(method, params)
		}
	}
	return rc.Conn.Notify(rc.Ctx, method, params)
}

func (rc *RequestContext) RootClient() (*itchio.Client, error) {
	return rc.KeyClient("<keyless>")
}

func (rc *RequestContext) KeyClient(key string) (*itchio.Client, error) {
	return rc.MansionContext.NewClient(key)
}

func (rc *RequestContext) ProfileClient(profileID int64) (*models.Profile, *itchio.Client) {
	if profileID == 0 {
		panic(errors.New("profileId must be non-zero"))
	}

	profile := models.ProfileByID(rc.DB(), profileID)
	if profile == nil {
		panic(errors.Errorf("Could not find profile %d", profileID))
	}

	if profile.APIKey == "" {
		panic(errors.Errorf("Profile %d lacks API key", profileID))
	}

	client, err := rc.MansionContext.NewClient(profile.APIKey)
	if err != nil {
		panic(errors.WithStack(err))
	}

	return profile, client
}

func (rc *RequestContext) StartProgress() {
	rc.StartProgressWithTotalBytes(0)
}

func (rc *RequestContext) StartProgressWithTotalBytes(totalBytes int64) {
	rc.StartProgressWithInitialAndTotal(0.0, totalBytes)
}

func (rc *RequestContext) StartProgressWithInitialAndTotal(initialProgress float64, totalBytes int64) {
	rc.Consumer.Infof("Starting progress (initial %.2f, totalBytes %d)", initialProgress, totalBytes)

	if rc.counter != nil {
		rc.Consumer.Warnf("Asked to start progress but already tracking progress!")
		return
	}

	rc.counter = progress.NewCounter()
	rc.counter.SetSilent(true)
	rc.counter.SetProgress(initialProgress)
	rc.counter.SetTotalBytes(totalBytes)
	rc.counter.Start()
}

func (rc *RequestContext) EndProgress() {
	if rc.counter != nil {
		rc.counter.Finish()
		rc.counter = nil
	} else {
		rc.Consumer.Warnf("Asked to stop progress but wasn't tracking progress!")
	}
}

type CancelFuncs struct {
	Funcs map[string]context.CancelFunc
}

func (cf *CancelFuncs) Add(id string, f context.CancelFunc) {
	cf.Funcs[id] = f
}

func (cf *CancelFuncs) Remove(id string) {
	delete(cf.Funcs, id)
}

func (cf *CancelFuncs) Call(id string) bool {
	if f, ok := cf.Funcs[id]; ok {
		f()
		delete(cf.Funcs, id)
		return true
	}

	return false
}
<|endoftext|>