<commit_before>package goodwill

import (
	"sync"

	"github.com/drausin/libri/libri/common/id"
	"github.com/drausin/libri/libri/librarian/api"
	prom "github.com/prometheus/client_golang/prometheus"
)

const (
	selfPeerLabel  = "self"
	otherPeerLabel = "other"
	endpointLabel  = "endpoint"
	queryTypeLabel = "query_type"
	outcomeLabel   = "outcome"

	counterNamespace = "libri"
	counterSubsystem = "goodwill"
	counterName      = "peer_query_count"
)

// Recorder manages metrics about each peer's endpoint query outcomes.
type Recorder interface {

	// Record the outcome from a given query to/from a peer on the endpoint.
	Record(peerID id.ID, endpoint api.Endpoint, qt QueryType, o Outcome)

	// Get the query types outcomes on a particular endpoint for a peer.
	Get(peerID id.ID, endpoint api.Endpoint) QueryOutcomes
}

// PromRecorder is a Recorder that exposes state via Prometheus metrics.
type PromRecorder interface {
	Recorder

	// Register registers the Prometheus metric(s) with the default Prometheus registerer.
	Register()

	// Unregister unregisters the Prometheus metrics form the default Prometheus registerer.
	Unregister()
}

type scalarRecorder struct {
	peers map[string]EndpointQueryOutcomes
	mu    sync.Mutex
}

// NewScalarRecorder creates a new Recorder that stores scalar metrics about each peer's endpoint
// query outcomes.
func NewScalarRecorder() Recorder {
	return &scalarRecorder{
		peers: make(map[string]EndpointQueryOutcomes),
	}
}

func (r *scalarRecorder) Record(peerID id.ID, endpoint api.Endpoint, qt QueryType, o Outcome) {
	if endpoint == api.All {
		panic("cannot record outcome for all endpoints")
	}
	r.mu.Lock()
	if _, in := r.peers[peerID.String()]; !in {
		r.peers[peerID.String()] = newEndpointQueryOutcomes()
	}
	r.mu.Unlock()
	r.record(peerID, endpoint, qt, o)
	r.record(peerID, api.All, qt, o)
}

func (r *scalarRecorder) record(peerID id.ID, endpoint api.Endpoint, qt QueryType, o Outcome) {
	r.mu.Lock()
	m := r.peers[peerID.String()][endpoint][qt][o]
	r.mu.Unlock()
	m.Record()
}

func (r *scalarRecorder) Get(peerID id.ID, endpoint api.Endpoint) QueryOutcomes {
	r.mu.Lock()
	po, in := r.peers[peerID.String()]
	r.mu.Unlock()
	if !in {
		return newQueryOutcomes() // zero values
	}
	return po[endpoint]
}

// NewPromScalarRecorder creates a new scalar recorder that also emits Prometheus metrics for each
// (peer, endpoint, query, outcome).
func NewPromScalarRecorder(selfID id.ID) PromRecorder {
	counter := prom.NewCounterVec(
		prom.CounterOpts{
			Namespace: counterNamespace,
			Subsystem: counterSubsystem,
			Name:      counterName,
			Help:      "Number of queries to and from other peers.",
		},
		[]string{
			selfPeerLabel,
			otherPeerLabel,
			endpointLabel,
			queryTypeLabel,
			outcomeLabel,
		},
	)
	return &promScalarRecorder{
		scalarRecorder: NewScalarRecorder().(*scalarRecorder),
		selfID:         selfID,
		counter:        counter,
	}
}

type promScalarRecorder struct {
	*scalarRecorder

	selfID  id.ID
	counter *prom.CounterVec
}

func (r *promScalarRecorder) Record(peerID id.ID, endpoint api.Endpoint, qt QueryType, o Outcome) {
	r.scalarRecorder.Record(peerID, endpoint, qt, o)
	r.counter.With(prom.Labels{
		selfPeerLabel:  r.selfID.String(),
		otherPeerLabel: peerID.String(),
		endpointLabel:  endpoint.String(),
		queryTypeLabel: qt.String(),
		outcomeLabel:   o.String(),
	}).Inc()
}

func (r *promScalarRecorder) Register() {
	prom.MustRegister(r.counter)
}

func (r *promScalarRecorder) Unregister() {
	_ = prom.Unregister(r.counter)
}
<commit_msg>use short peer IDs in Prom labels (#181)<commit_after>package goodwill

import (
	"sync"

	"github.com/drausin/libri/libri/common/id"
	"github.com/drausin/libri/libri/librarian/api"
	prom "github.com/prometheus/client_golang/prometheus"
)

const (
	selfPeerLabel  = "self"
	otherPeerLabel = "other"
	endpointLabel  = "endpoint"
	queryTypeLabel = "query_type"
	outcomeLabel   = "outcome"

	counterNamespace = "libri"
	counterSubsystem = "goodwill"
	counterName      = "peer_query_count"
)

// Recorder manages metrics about each peer's endpoint query outcomes.
type Recorder interface {

	// Record the outcome from a given query to/from a peer on the endpoint.
	Record(peerID id.ID, endpoint api.Endpoint, qt QueryType, o Outcome)

	// Get the query types outcomes on a particular endpoint for a peer.
	Get(peerID id.ID, endpoint api.Endpoint) QueryOutcomes
}

// PromRecorder is a Recorder that exposes state via Prometheus metrics.
type PromRecorder interface {
	Recorder

	// Register registers the Prometheus metric(s) with the default Prometheus registerer.
	Register()

	// Unregister unregisters the Prometheus metrics form the default Prometheus registerer.
	Unregister()
}

type scalarRecorder struct {
	peers map[string]EndpointQueryOutcomes
	mu    sync.Mutex
}

// NewScalarRecorder creates a new Recorder that stores scalar metrics about each peer's endpoint
// query outcomes.
func NewScalarRecorder() Recorder {
	return &scalarRecorder{
		peers: make(map[string]EndpointQueryOutcomes),
	}
}

func (r *scalarRecorder) Record(peerID id.ID, endpoint api.Endpoint, qt QueryType, o Outcome) {
	if endpoint == api.All {
		panic("cannot record outcome for all endpoints")
	}
	r.mu.Lock()
	if _, in := r.peers[peerID.String()]; !in {
		r.peers[peerID.String()] = newEndpointQueryOutcomes()
	}
	r.mu.Unlock()
	r.record(peerID, endpoint, qt, o)
	r.record(peerID, api.All, qt, o)
}

func (r *scalarRecorder) record(peerID id.ID, endpoint api.Endpoint, qt QueryType, o Outcome) {
	r.mu.Lock()
	m := r.peers[peerID.String()][endpoint][qt][o]
	r.mu.Unlock()
	m.Record()
}

func (r *scalarRecorder) Get(peerID id.ID, endpoint api.Endpoint) QueryOutcomes {
	r.mu.Lock()
	po, in := r.peers[peerID.String()]
	r.mu.Unlock()
	if !in {
		return newQueryOutcomes() // zero values
	}
	return po[endpoint]
}

// NewPromScalarRecorder creates a new scalar recorder that also emits Prometheus metrics for each
// (peer, endpoint, query, outcome).
func NewPromScalarRecorder(selfID id.ID) PromRecorder {
	counter := prom.NewCounterVec(
		prom.CounterOpts{
			Namespace: counterNamespace,
			Subsystem: counterSubsystem,
			Name:      counterName,
			Help:      "Number of queries to and from other peers.",
		},
		[]string{
			selfPeerLabel,
			otherPeerLabel,
			endpointLabel,
			queryTypeLabel,
			outcomeLabel,
		},
	)
	return &promScalarRecorder{
		scalarRecorder: NewScalarRecorder().(*scalarRecorder),
		selfID:         selfID,
		counter:        counter,
	}
}

type promScalarRecorder struct {
	*scalarRecorder

	selfID  id.ID
	counter *prom.CounterVec
}

func (r *promScalarRecorder) Record(peerID id.ID, endpoint api.Endpoint, qt QueryType, o Outcome) {
	r.scalarRecorder.Record(peerID, endpoint, qt, o)
	r.counter.With(prom.Labels{
		selfPeerLabel:  id.ShortHex(r.selfID.Bytes()),
		otherPeerLabel: id.ShortHex(peerID.Bytes()),
		endpointLabel:  endpoint.String(),
		queryTypeLabel: qt.String(),
		outcomeLabel:   o.String(),
	}).Inc()
}

func (r *promScalarRecorder) Register() {
	prom.MustRegister(r.counter)
}

func (r *promScalarRecorder) Unregister() {
	_ = prom.Unregister(r.counter)
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/itsjamie/gin-cors"
	"github.com/jkomoros/boardgame"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

type Server struct {
	managers managerMap
	storage  StorageManager
	//We store the last error so that next time viewHandler is called we can
	//display it. Yes, this is a hack.
	lastErrorMessage string
	config           *ConfigMode
}

type Renderer struct {
	c        *gin.Context
	rendered bool
}

type Config struct {
	Dev  *ConfigMode
	Prod *ConfigMode
}

type ConfigMode struct {
	AllowedOrigins    string
	DefaultPort       string
	FirebaseProjectId string
	AdminUserIds      []string
	//This is a dangerous config. Only enable in Dev!
	DisableAdminChecking bool
}

type MoveForm struct {
	Name        string
	Description string
	Fields      []*MoveFormField
}

type MoveFormFieldType int

type MoveFormField struct {
	Name         string
	Type         boardgame.PropertyType
	DefaultValue interface{}
}

func (c *ConfigMode) Validate() error {
	if c.DefaultPort == "" {
		return errors.New("No default port provided")
	}
	//AllowedOrigins will just be default allow
	if c.AllowedOrigins == "" {
		log.Println("No AllowedOrigins found. Defaulting to '*'")
		c.AllowedOrigins = "*"
	}
	return nil
}

const (
	configFileName = "config.SECRET.json"
)

type managerMap map[string]*boardgame.GameManager

/*

Overview of the types of handlers and methods

server.fooHandler take a context. They grab all of the dependencies and pass them to the doers.
server.doFoo takes a renderer and all dependencies. It renders the result.
server.getRequestFoo fetches an argument from the context's request and nothing else
server.getFoo grabs a thing that was stored in Context and nothing else
server.setFoo sets a thing into context and nothing else
server.calcFoo takes dependencies and returns a result, with no touching context.
*/

/*
NewServer returns a new server. Get it to run by calling Start(). storage
should be the same underlying storage manager that is in use for manager.

Use it like so:

	func main() {
		storage := server.NewDefaultStorageManager()
		defer storage.Close()
		server.NewServer(storage, mygame.NewManager(storage)).Start()
	}

*/
func NewServer(storage StorageManager, managers ...*boardgame.GameManager) *Server {
	result := &Server{
		managers: make(managerMap),
		storage:  storage,
	}

	for _, manager := range managers {
		name := manager.Delegate().Name()
		result.managers[name] = manager
	}

	return result

}

func NewRenderer(c *gin.Context) *Renderer {
	return &Renderer{
		c,
		false,
	}
}

func (r *Renderer) Error(message string) {
	if r.rendered {
		panic("Error called on already-rendered renderer")
	}
	r.c.JSON(http.StatusOK, gin.H{
		"Status": "Failure",
		"Error":  message,
	})

	r.rendered = true
}

func (r *Renderer) Success(keys gin.H) {

	if r.rendered {
		panic("Success called on alread-rendered renderer")
	}

	if keys == nil {
		keys = gin.H{}
	}

	result := gin.H{}

	for key, val := range keys {
		result[key] = val
	}

	result["Status"] = "Success"

	r.c.JSON(http.StatusOK, result)

	r.rendered = true
}

//gameAPISetup fetches the game configured in the URL and puts it in context.
func (s *Server) gameAPISetup(c *gin.Context) {

	id := s.getRequestGameId(c)

	gameName := s.getRequestGameName(c)

	manager := s.managers[gameName]

	if manager == nil {
		log.Println("Couldnt' find manager for", gameName)
		return
	}

	game := manager.Game(id)

	//TODO: figure out a way to return a meaningful error

	if game == nil {
		log.Println("Couldn't find game with id", id)
		return
	}

	if game.Name() != c.Param("name") {
		log.Println("The name of the game was not what we were expecting. Wanted", c.Param("name"), "got", game.Name())
		return
	}

	s.setGame(c, game)

	cookie := s.getRequestCookie(c)

	if cookie == "" {
		log.Println("No cookie set")
		return
	}

	user := s.storage.GetUserByCookie(cookie)

	if user == nil {
		log.Println("No user associated with that cookie")
		return
	}

	s.setUser(c, user)

	s.setAdminAllowed(c, s.calcAdminAllowed(user))

	userIds := s.storage.UserIdsForGame(id)

	if userIds == nil {
		log.Println("No userIds associated with game")
	}

	effectiveViewingAsPlayer, inGame := s.calcViewingAsPlayer(userIds, user)

	if !inGame {
		//We aren't yet in game, so we need to join it.

		if effectiveViewingAsPlayer == boardgame.ObserverPlayerIndex {
			//I guess there weren't any slots.
			log.Println("The user is not in the game, but there are no empty slots to join in as.")
		} else {
			if err := s.storage.SetPlayerForGame(id, effectiveViewingAsPlayer, user.Id); err != nil {
				log.Println("Tried to set this user as player", effectiveViewingAsPlayer, "but failed:", err)
			} else {
				log.Println("User joined game", id, "as player", effectiveViewingAsPlayer)
			}
		}
	}

	s.setViewingAsPlayer(c, effectiveViewingAsPlayer)

}

func (s *Server) gameStatusHandler(c *gin.Context) {
	//This handler is designed to be a very simple status marker for the
	//current version of the specific game. It will be hit hard by all
	//clients, repeatedly, so it should be very fast.

	//TODO: use memcache for this handler

	game := s.getGame(c)

	r := NewRenderer(c)

	s.doGameStatus(r, game)

}

func (s *Server) doGameStatus(r *Renderer, game *boardgame.Game) {
	if game == nil {
		r.Error("Not Found")
		return
	}

	r.Success(gin.H{
		"Version": game.Version(),
	})
}

func (s *Server) newGameHandler(c *gin.Context) {

	r := NewRenderer(c)

	managerId := s.getRequestManager(c)

	manager := s.managers[managerId]

	s.doNewGame(r, manager)

}

func (s *Server) doNewGame(r *Renderer, manager *boardgame.GameManager) {

	if manager == nil {
		r.Error("No manager provided")
		return
	}

	game := boardgame.NewGame(manager)

	if game == nil {
		r.Error("No game could be created")
		return
	}

	if err := game.SetUp(0); err != nil {
		//TODO: communicate the error state back to the client in a sane way
		r.Error("Couldn't set up game: " + err.Error())
		return
	}

	r.Success(gin.H{
		"GameId":   game.Id(),
		"GameName": game.Name(),
	})
}

func (s *Server) listGamesHandler(c *gin.Context) {

	r := NewRenderer(c)
	s.doListGames(r)
}

func (s *Server) doListGames(r *Renderer) {
	r.Success(gin.H{
		"Games": s.storage.ListGames(10),
	})
}

func (s *Server) listManagerHandler(c *gin.Context) {
	var managerNames []string
	for name, _ := range s.managers {
		managerNames = append(managerNames, name)
	}

	r := NewRenderer(c)

	r.Success(gin.H{
		"Managers": managerNames,
	})
}

func (s *Server) gameViewHandler(c *gin.Context) {

	game := s.getGame(c)

	playerIndex := s.effectivePlayerIndex(c)

	r := NewRenderer(c)

	s.gameView(r, game, playerIndex)

}

func (s *Server) gameView(r *Renderer, game *boardgame.Game, playerIndex boardgame.PlayerIndex) {
	if game == nil {
		r.Error("Couldn't find game")
		return
	}

	if playerIndex == invalidPlayerIndex {
		r.Error("Got invalid playerIndex")
		return
	}

	args := gin.H{
		"Diagram":         game.CurrentState().SanitizedForPlayer(playerIndex).Diagram(),
		"Chest":           s.renderChest(game),
		"Forms":           s.generateForms(game),
		"Game":            game.JSONForPlayer(playerIndex),
		"Error":           s.lastErrorMessage,
		"ViewingAsPlayer": playerIndex,
	}

	s.lastErrorMessage = ""

	r.Success(args)

}

func (s *Server) moveHandler(c *gin.Context) {
	if c.Request.Method != http.MethodPost {
		panic("This can only be called as a post.")
	}

	r := NewRenderer(c)

	game := s.getGame(c)

	if game == nil {
		r.Error("Game not found")
		return
	}

	viewingPlayerIndex := s.getViewingAsPlayer(c)

	adminAllowed := s.getAdminAllowed(c)
	requestAdmin := s.getRequestAdmin(c)

	isAdmin := s.calcIsAdmin(adminAllowed, requestAdmin)

	proposer := viewingPlayerIndex

	if isAdmin {
		proposer = boardgame.AdminPlayerIndex
	}

	move, err := s.getMoveFromForm(c, game)

	if move == nil {

		errString := "No move returned"

		if err != nil {
			errString = err.Error()
		}

		r.Error("Couldn't get move: " + errString)
		return
	}

	if err := s.makeMove(game, proposer, move); err != nil {
		r.Error("Couldn't mave move: " + err.Error())
		return
	}

	r.Success(nil)
}

func (s *Server) makeMove(game *boardgame.Game, proposer boardgame.PlayerIndex, move boardgame.Move) error {

	if err := <-game.ProposeMove(move, proposer); err != nil {
		return errors.New(fmt.Sprint("Applying move failed: ", err))
	}
	//TODO: it would be nice if we could show which fixup moves we made, too,
	//somehow.

	return nil
}

func (s *Server) generateForms(game *boardgame.Game) []*MoveForm {

	var result []*MoveForm

	for _, move := range game.PlayerMoves() {

		move.DefaultsForState(game.CurrentState())

		moveItem := &MoveForm{
			Name:        move.Name(),
			Description: move.Description(),
			Fields:      formFields(move),
		}
		result = append(result, moveItem)
	}

	return result
}

func formFields(move boardgame.Move) []*MoveFormField {

	var result []*MoveFormField

	for fieldName, fieldType := range move.ReadSetter().Props() {

		val, _ := move.ReadSetter().Prop(fieldName)

		result = append(result, &MoveFormField{
			Name:         fieldName,
			Type:         fieldType,
			DefaultValue: val,
		})

	}

	return result
}

func (s *Server) renderChest(game *boardgame.Game) map[string][]interface{} {
	//Substantially copied from cli.renderChest().

	deck := make(map[string][]interface{})

	for _, name := range game.Chest().DeckNames() {

		components := game.Chest().Deck(name).Components()

		values := make([]interface{}, len(components))

		for i, component := range components {
			values[i] = struct {
				Index  int
				Values interface{}
			}{
				i,
				component.Values,
			}
		}

		deck[name] = values
	}

	return deck
}

//Start is where you start the server, and it never returns until it's time to shut down.
func (s *Server) Start() {

	if _, err := os.Stat(configFileName); os.IsNotExist(err) {
		log.Println("Couldn't find a " + configFileName + " in current directory. This file is required. Copy a starter one from boardgame/server/api/config.SAMPLE.json")
		return
	}

	contents, err := ioutil.ReadFile(configFileName)

	if err != nil {
		log.Println("Couldn't read config file:", err)
		return
	}

	var config Config

	if err := json.Unmarshal(contents, &config); err != nil {
		log.Println("couldn't unmarshal config file:", err)
		return
	}

	log.Println("Environment Variables")
	//Dbug print out the current environment
	for _, config := range os.Environ() {
		log.Println("Environ:", config)
	}

	if v := os.Getenv("GIN_MODE"); v == "release" {
		log.Println("Using release mode config")
		s.config = config.Prod
	} else {
		log.Println("Using dev mode config")
		s.config = config.Dev
	}

	if err := s.config.Validate(); err != nil {
		log.Println("The provided config was not valid: ", err)
		return
	}

	router := gin.Default()

	//We have everything prefixed by /api just in case at some point we do
	//want to host both static and api on the same logical server.
	mainGroup := router.Group("/api")
	mainGroup.Use(cors.Middleware(cors.Config{
		Origins:        s.config.AllowedOrigins,
		RequestHeaders: "content-type, Origin",
		ExposedHeaders: "content-type",
		Methods:        "GET, POST",
		Credentials:    true,
	}))

	{
		mainGroup.GET("list/game", s.listGamesHandler)
		mainGroup.POST("new/game", s.newGameHandler)
		mainGroup.GET("list/manager", s.listManagerHandler)

		mainGroup.POST("auth/cookie", s.authCookieHandler)
		mainGroup.OPTIONS("auth/cookie", s.authCookieHandler)

		gameAPIGroup := mainGroup.Group("game/:name/:id")
		gameAPIGroup.Use(s.gameAPISetup)
		{
			gameAPIGroup.GET("view", s.gameViewHandler)
			gameAPIGroup.POST("move", s.moveHandler)
			gameAPIGroup.GET("status", s.gameStatusHandler)
		}
	}

	if p := os.Getenv("PORT"); p != "" {
		router.Run(":" + p)
	} else {
		router.Run(":" + s.config.DefaultPort)
	}

}
<commit_msg>Update the comment describing the ideal we're striving for. Part of #92.<commit_after>package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/itsjamie/gin-cors"
	"github.com/jkomoros/boardgame"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

type Server struct {
	managers managerMap
	storage  StorageManager
	//We store the last error so that next time viewHandler is called we can
	//display it. Yes, this is a hack.
	lastErrorMessage string
	config           *ConfigMode
}

type Renderer struct {
	c        *gin.Context
	rendered bool
}

type Config struct {
	Dev  *ConfigMode
	Prod *ConfigMode
}

type ConfigMode struct {
	AllowedOrigins    string
	DefaultPort       string
	FirebaseProjectId string
	AdminUserIds      []string
	//This is a dangerous config. Only enable in Dev!
	DisableAdminChecking bool
}

type MoveForm struct {
	Name        string
	Description string
	Fields      []*MoveFormField
}

type MoveFormFieldType int

type MoveFormField struct {
	Name         string
	Type         boardgame.PropertyType
	DefaultValue interface{}
}

func (c *ConfigMode) Validate() error {
	if c.DefaultPort == "" {
		return errors.New("No default port provided")
	}
	//AllowedOrigins will just be default allow
	if c.AllowedOrigins == "" {
		log.Println("No AllowedOrigins found. Defaulting to '*'")
		c.AllowedOrigins = "*"
	}
	return nil
}

const (
	configFileName = "config.SECRET.json"
)

type managerMap map[string]*boardgame.GameManager

/*

Overview of the types of handlers and methods

server.fooHandler take a context. They grab all of the dependencies and pass them to the doers.
server.doFoo takes a renderer and all dependencies that come from context. It may fetch additional items from e.g. storage. It renders the result.
server.getRequestFoo fetches an argument from the context's request and nothing else
server.getFoo grabs a thing that was stored in Context and nothing else
server.setFoo sets a thing into context and nothing else
server.calcFoo takes dependencies and returns a result, with no touching context.
*/

/*
NewServer returns a new server. Get it to run by calling Start(). storage
should be the same underlying storage manager that is in use for manager.

Use it like so:

	func main() {
		storage := server.NewDefaultStorageManager()
		defer storage.Close()
		server.NewServer(storage, mygame.NewManager(storage)).Start()
	}

*/
func NewServer(storage StorageManager, managers ...*boardgame.GameManager) *Server {
	result := &Server{
		managers: make(managerMap),
		storage:  storage,
	}

	for _, manager := range managers {
		name := manager.Delegate().Name()
		result.managers[name] = manager
	}

	return result

}

func NewRenderer(c *gin.Context) *Renderer {
	return &Renderer{
		c,
		false,
	}
}

func (r *Renderer) Error(message string) {
	if r.rendered {
		panic("Error called on already-rendered renderer")
	}
	r.c.JSON(http.StatusOK, gin.H{
		"Status": "Failure",
		"Error":  message,
	})

	r.rendered = true
}

func (r *Renderer) Success(keys gin.H) {

	if r.rendered {
		panic("Success called on alread-rendered renderer")
	}

	if keys == nil {
		keys = gin.H{}
	}

	result := gin.H{}

	for key, val := range keys {
		result[key] = val
	}

	result["Status"] = "Success"

	r.c.JSON(http.StatusOK, result)

	r.rendered = true
}

//gameAPISetup fetches the game configured in the URL and puts it in context.
func (s *Server) gameAPISetup(c *gin.Context) {

	id := s.getRequestGameId(c)

	gameName := s.getRequestGameName(c)

	manager := s.managers[gameName]

	if manager == nil {
		log.Println("Couldnt' find manager for", gameName)
		return
	}

	game := manager.Game(id)

	//TODO: figure out a way to return a meaningful error

	if game == nil {
		log.Println("Couldn't find game with id", id)
		return
	}

	if game.Name() != c.Param("name") {
		log.Println("The name of the game was not what we were expecting. Wanted", c.Param("name"), "got", game.Name())
		return
	}

	s.setGame(c, game)

	cookie := s.getRequestCookie(c)

	if cookie == "" {
		log.Println("No cookie set")
		return
	}

	user := s.storage.GetUserByCookie(cookie)

	if user == nil {
		log.Println("No user associated with that cookie")
		return
	}

	s.setUser(c, user)

	s.setAdminAllowed(c, s.calcAdminAllowed(user))

	userIds := s.storage.UserIdsForGame(id)

	if userIds == nil {
		log.Println("No userIds associated with game")
	}

	effectiveViewingAsPlayer, inGame := s.calcViewingAsPlayer(userIds, user)

	if !inGame {
		//We aren't yet in game, so we need to join it.

		if effectiveViewingAsPlayer == boardgame.ObserverPlayerIndex {
			//I guess there weren't any slots.
			log.Println("The user is not in the game, but there are no empty slots to join in as.")
		} else {
			if err := s.storage.SetPlayerForGame(id, effectiveViewingAsPlayer, user.Id); err != nil {
				log.Println("Tried to set this user as player", effectiveViewingAsPlayer, "but failed:", err)
			} else {
				log.Println("User joined game", id, "as player", effectiveViewingAsPlayer)
			}
		}
	}

	s.setViewingAsPlayer(c, effectiveViewingAsPlayer)

}

func (s *Server) gameStatusHandler(c *gin.Context) {
	//This handler is designed to be a very simple status marker for the
	//current version of the specific game. It will be hit hard by all
	//clients, repeatedly, so it should be very fast.

	//TODO: use memcache for this handler

	game := s.getGame(c)

	r := NewRenderer(c)

	s.doGameStatus(r, game)

}

func (s *Server) doGameStatus(r *Renderer, game *boardgame.Game) {
	if game == nil {
		r.Error("Not Found")
		return
	}

	r.Success(gin.H{
		"Version": game.Version(),
	})
}

func (s *Server) newGameHandler(c *gin.Context) {

	r := NewRenderer(c)

	managerId := s.getRequestManager(c)

	manager := s.managers[managerId]

	s.doNewGame(r, manager)

}

func (s *Server) doNewGame(r *Renderer, manager *boardgame.GameManager) {

	if manager == nil {
		r.Error("No manager provided")
		return
	}

	game := boardgame.NewGame(manager)

	if game == nil {
		r.Error("No game could be created")
		return
	}

	if err := game.SetUp(0); err != nil {
		//TODO: communicate the error state back to the client in a sane way
		r.Error("Couldn't set up game: " + err.Error())
		return
	}

	r.Success(gin.H{
		"GameId":   game.Id(),
		"GameName": game.Name(),
	})
}

func (s *Server) listGamesHandler(c *gin.Context) {

	r := NewRenderer(c)
	s.doListGames(r)
}

func (s *Server) doListGames(r *Renderer) {
	r.Success(gin.H{
		"Games": s.storage.ListGames(10),
	})
}

func (s *Server) listManagerHandler(c *gin.Context) {
	var managerNames []string
	for name, _ := range s.managers {
		managerNames = append(managerNames, name)
	}

	r := NewRenderer(c)

	r.Success(gin.H{
		"Managers": managerNames,
	})
}

func (s *Server) gameViewHandler(c *gin.Context) {

	game := s.getGame(c)

	playerIndex := s.effectivePlayerIndex(c)

	r := NewRenderer(c)

	s.gameView(r, game, playerIndex)

}

func (s *Server) gameView(r *Renderer, game *boardgame.Game, playerIndex boardgame.PlayerIndex) {
	if game == nil {
		r.Error("Couldn't find game")
		return
	}

	if playerIndex == invalidPlayerIndex {
		r.Error("Got invalid playerIndex")
		return
	}

	args := gin.H{
		"Diagram":         game.CurrentState().SanitizedForPlayer(playerIndex).Diagram(),
		"Chest":           s.renderChest(game),
		"Forms":           s.generateForms(game),
		"Game":            game.JSONForPlayer(playerIndex),
		"Error":           s.lastErrorMessage,
		"ViewingAsPlayer": playerIndex,
	}

	s.lastErrorMessage = ""

	r.Success(args)

}

func (s *Server) moveHandler(c *gin.Context) {
	if c.Request.Method != http.MethodPost {
		panic("This can only be called as a post.")
	}

	r := NewRenderer(c)

	game := s.getGame(c)

	if game == nil {
		r.Error("Game not found")
		return
	}

	viewingPlayerIndex := s.getViewingAsPlayer(c)

	adminAllowed := s.getAdminAllowed(c)
	requestAdmin := s.getRequestAdmin(c)

	isAdmin := s.calcIsAdmin(adminAllowed, requestAdmin)

	proposer := viewingPlayerIndex

	if isAdmin {
		proposer = boardgame.AdminPlayerIndex
	}

	move, err := s.getMoveFromForm(c, game)

	if move == nil {

		errString := "No move returned"

		if err != nil {
			errString = err.Error()
		}

		r.Error("Couldn't get move: " + errString)
		return
	}

	if err := s.makeMove(game, proposer, move); err != nil {
		r.Error("Couldn't mave move: " + err.Error())
		return
	}

	r.Success(nil)
}

func (s *Server) makeMove(game *boardgame.Game, proposer boardgame.PlayerIndex, move boardgame.Move) error {

	if err := <-game.ProposeMove(move, proposer); err != nil {
		return errors.New(fmt.Sprint("Applying move failed: ", err))
	}
	//TODO: it would be nice if we could show which fixup moves we made, too,
	//somehow.

	return nil
}

func (s *Server) generateForms(game *boardgame.Game) []*MoveForm {

	var result []*MoveForm

	for _, move := range game.PlayerMoves() {

		move.DefaultsForState(game.CurrentState())

		moveItem := &MoveForm{
			Name:        move.Name(),
			Description: move.Description(),
			Fields:      formFields(move),
		}
		result = append(result, moveItem)
	}

	return result
}

func formFields(move boardgame.Move) []*MoveFormField {

	var result []*MoveFormField

	for fieldName, fieldType := range move.ReadSetter().Props() {

		val, _ := move.ReadSetter().Prop(fieldName)

		result = append(result, &MoveFormField{
			Name:         fieldName,
			Type:         fieldType,
			DefaultValue: val,
		})

	}

	return result
}

func (s *Server) renderChest(game *boardgame.Game) map[string][]interface{} {
	//Substantially copied from cli.renderChest().

	deck := make(map[string][]interface{})

	for _, name := range game.Chest().DeckNames() {

		components := game.Chest().Deck(name).Components()

		values := make([]interface{}, len(components))

		for i, component := range components {
			values[i] = struct {
				Index  int
				Values interface{}
			}{
				i,
				component.Values,
			}
		}

		deck[name] = values
	}

	return deck
}

//Start is where you start the server, and it never returns until it's time to shut down.
func (s *Server) Start() {

	if _, err := os.Stat(configFileName); os.IsNotExist(err) {
		log.Println("Couldn't find a " + configFileName + " in current directory. This file is required. Copy a starter one from boardgame/server/api/config.SAMPLE.json")
		return
	}

	contents, err := ioutil.ReadFile(configFileName)

	if err != nil {
		log.Println("Couldn't read config file:", err)
		return
	}

	var config Config

	if err := json.Unmarshal(contents, &config); err != nil {
		log.Println("couldn't unmarshal config file:", err)
		return
	}

	log.Println("Environment Variables")
	//Dbug print out the current environment
	for _, config := range os.Environ() {
		log.Println("Environ:", config)
	}

	if v := os.Getenv("GIN_MODE"); v == "release" {
		log.Println("Using release mode config")
		s.config = config.Prod
	} else {
		log.Println("Using dev mode config")
		s.config = config.Dev
	}

	if err := s.config.Validate(); err != nil {
		log.Println("The provided config was not valid: ", err)
		return
	}

	router := gin.Default()

	//We have everything prefixed by /api just in case at some point we do
	//want to host both static and api on the same logical server.
	mainGroup := router.Group("/api")
	mainGroup.Use(cors.Middleware(cors.Config{
		Origins:        s.config.AllowedOrigins,
		RequestHeaders: "content-type, Origin",
		ExposedHeaders: "content-type",
		Methods:        "GET, POST",
		Credentials:    true,
	}))

	{
		mainGroup.GET("list/game", s.listGamesHandler)
		mainGroup.POST("new/game", s.newGameHandler)
		mainGroup.GET("list/manager", s.listManagerHandler)

		mainGroup.POST("auth/cookie", s.authCookieHandler)
		mainGroup.OPTIONS("auth/cookie", s.authCookieHandler)

		gameAPIGroup := mainGroup.Group("game/:name/:id")
		gameAPIGroup.Use(s.gameAPISetup)
		{
			gameAPIGroup.GET("view", s.gameViewHandler)
			gameAPIGroup.POST("move", s.moveHandler)
			gameAPIGroup.GET("status", s.gameStatusHandler)
		}
	}

	if p := os.Getenv("PORT"); p != "" {
		router.Run(":" + p)
	} else {
		router.Run(":" + s.config.DefaultPort)
	}

}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"code.google.com/p/freetype-go/freetype"
)

func main() {
	args := os.Args
	if len(args) != 2 {
		fmt.Printf("usage:\t%v [path/to/sources]\n", args[0])
		return
	}

	dirPath := os.Args[1]

	err := gen(dirPath)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}

func gen(dirPath string) error {
	captionFile, _, err := getSrcFiles(dirPath)
	if err != nil {
		return err
	}
	if captionFile == nil {
		return errors.New("No caption file found")
	}
	text, err := ioutil.ReadAll(captionFile)
	if err != nil {
		return err
	}

	fontBytes, err := ioutil.ReadFile("/Users/bariteau/Downloads/love_letter_tw/Lovelt__.ttf")
	if err != nil {
		return errors.New("Unable to read font file")
	}
	font, err := freetype.ParseFont(fontBytes)

	if err != nil {
		return errors.New("Unable to parse font file")
	}

	img := image.NewNRGBA(image.Rect(0, 0, 1000, 1000))
	draw.Draw(img, image.Rect(0, 0, 1000, 1000), image.NewUniform(color.Black), image.Point{0, 0}, draw.Over)
	fontContext := freetype.NewContext()
	fontContext.SetFont(font)
	fontContext.SetFontSize(12.0)
	fontContext.SetSrc(image.NewUniform(color.White))
	fontContext.SetDst(img)
	fontContext.SetClip(img.Bounds())
	fontContext.DrawString(string(text), freetype.Pt(100, 100))
	outimg, err := os.Create("out.png")
	defer func() {
		er := outimg.Close()
		if er != nil {
			err = er
		}
	}()
	if err != nil {
		return errors.New(fmt.Sprint("Unable to open image for writing", err))
	}
	err = png.Encode(outimg, img)
	if err != nil {
		return errors.New("Unable to write image")
	}
	return nil
}

func getSrcFiles(dirPath string) (captions *os.File, pictures []*os.File, err error) {
	dir, err := os.Open(dirPath)
	if err != nil {
		return
	}

	dirInfo, err := dir.Stat()
	if err != nil {
		return
	}

	if !dirInfo.IsDir() {
		errors.New(fmt.Sprint("Not a directory: ", dirPath))
	}

	fileinfos, err := dir.Readdir(0)
	if err != nil {
		return
	}

	for _, fileinfo := range fileinfos {
		name := fileinfo.Name()
		if strings.HasPrefix(name, "caption") {
			if captions == nil {
				captions, err = os.Open(path.Join(dirPath, name))
				if err != nil {
					captions = nil
				}
			}
			continue
		}
	}
	return
}
<commit_msg>moar refactoring<commit_after>package main

import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"code.google.com/p/freetype-go/freetype"
)

func main() {
	args := os.Args
	if len(args) != 2 {
		fmt.Printf("usage:\t%v [path/to/sources]\n", args[0])
		return
	}

	dirPath := os.Args[1]

	err := gen(dirPath)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}

func gen(dirPath string) error {
	captionFile, _, err := getSrcFiles(dirPath)
	if err != nil {
		return err
	}
	if captionFile == nil {
		return errors.New("No caption file found")
	}
	text, err := ioutil.ReadAll(captionFile)
	if err != nil {
		return err
	}

	img := image.NewNRGBA(image.Rect(0, 0, 1000, 1000))
	draw.Draw(img, image.Rect(0, 0, 1000, 1000), image.NewUniform(color.Black), image.Point{0, 0}, draw.Over)

	fontContext, err := createFontContext(img)
	if err != nil {
		return err
	}

	fontContext.DrawString(string(text), freetype.Pt(100, 100))
	writeImgToFile("out.png", img)

	return nil
}

func writeImgToFile(filename string, img image.Image) error {
	outimg, err := os.Create(filename)
	if err != nil {
		return errors.New(fmt.Sprint("Unable to open image for writing", err))
	}
	defer outimg.Close()

	err = png.Encode(outimg, img)
	if err != nil {
		return errors.New("Unable to write image")
	}
	return nil
}

func createFontContext(dst draw.Image) (fontContext *freetype.Context, err error) {
	fontContext = freetype.NewContext()

	fontBytes, err := ioutil.ReadFile("/Users/bariteau/Downloads/love_letter_tw/Lovelt__.ttf")
	if err != nil {
		err = errors.New("Unable to read font file")
		return
	}
	font, err := freetype.ParseFont(fontBytes)

	if err != nil {
		err = errors.New("Unable to parse font file")
		return
	}

	fontContext.SetFont(font)
	fontContext.SetFontSize(12.0)
	fontContext.SetSrc(image.NewUniform(color.White))
	fontContext.SetDst(dst)
	fontContext.SetClip(dst.Bounds())
	return
}

func getSrcFiles(dirPath string) (captions *os.File, pictures []*os.File, err error) {
	dir, err := os.Open(dirPath)
	if err != nil {
		return
	}

	dirInfo, err := dir.Stat()
	if err != nil {
		return
	}

	if !dirInfo.IsDir() {
		errors.New(fmt.Sprint("Not a directory: ", dirPath))
	}

	fileinfos, err := dir.Readdir(0)
	if err != nil {
		return
	}

	for _, fileinfo := range fileinfos {
		name := fileinfo.Name()
		if strings.HasPrefix(name, "caption") {
			if captions == nil {
				captions, err = os.Open(path.Join(dirPath, name))
				if err != nil {
					captions = nil
				}
			}
			continue
		}
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/BlueDragonX/beacon/beacon"
	"github.com/BlueDragonX/beacon/debug"
	"github.com/BlueDragonX/beacon/docker"
	"github.com/BlueDragonX/beacon/sns"
	"github.com/pkg/errors"
	"log"
	"os"
)

// Logger is the application logger.
var Logger = log.New(os.Stdout, "", 0)

func init() {
	beacon.Logger = Logger
	docker.Logger = Logger
}

// NewBeacon creates a new Beacon from configuration.
func NewBeacon(config *Config) (beacon.Beacon, error) {
	docker, err := docker.New(
		config.Docker.Socket,
		config.Docker.HostIP,
		config.Docker.Label,
		config.Docker.StopOnExit,
	)
	if err != nil {
		return nil, err
	}

	routes := make([]beacon.Route, len(config.Backends))
	for n, backendCfg := range config.Backends {
		var backend beacon.Backend
		filter := beacon.NewFilter(backendCfg.Filter)
		if backendCfg.SNS != nil {
			backend = sns.New(
				backendCfg.SNS.Region,
				backendCfg.SNS.Topic,
			)
		} else if backendCfg.Debug != nil {
			backend = debug.New(Logger)
		} else {
			return nil, errors.New("unsupported backend")
		}
		routes[n] = beacon.NewRoute(filter, backend)
	}
	return beacon.New(docker, routes)
}

func main() {
	config := Configure(os.Args)
	bcn, err := NewBeacon(config)
	if err != nil {
		Logger.Fatalf("failed to initialize: %s", err)
	}

	signals := notifyOnStop()

	go func() {
		<-signals
		if err := bcn.Close(); err != nil {
			Logger.Fatalf("failed to shut down: %s", err)
		}
	}()

	if err := bcn.Run(); err != nil {
		Logger.Fatalf("failed to shut down: %s", err)
	}
	Logger.Print("stopped")
}
<commit_msg>cmd/beacon: Don't log "stopped".<commit_after>package main

import (
	"github.com/BlueDragonX/beacon/beacon"
	"github.com/BlueDragonX/beacon/debug"
	"github.com/BlueDragonX/beacon/docker"
	"github.com/BlueDragonX/beacon/sns"
	"github.com/pkg/errors"
	"log"
	"os"
)

// Logger is the application logger.
var Logger = log.New(os.Stdout, "", 0)

func init() {
	beacon.Logger = Logger
	docker.Logger = Logger
}

// NewBeacon creates a new Beacon from configuration.
func NewBeacon(config *Config) (beacon.Beacon, error) {
	docker, err := docker.New(
		config.Docker.Socket,
		config.Docker.HostIP,
		config.Docker.Label,
		config.Docker.StopOnExit,
	)
	if err != nil {
		return nil, err
	}

	routes := make([]beacon.Route, len(config.Backends))
	for n, backendCfg := range config.Backends {
		var backend beacon.Backend
		filter := beacon.NewFilter(backendCfg.Filter)
		if backendCfg.SNS != nil {
			backend = sns.New(
				backendCfg.SNS.Region,
				backendCfg.SNS.Topic,
			)
		} else if backendCfg.Debug != nil {
			backend = debug.New(Logger)
		} else {
			return nil, errors.New("unsupported backend")
		}
		routes[n] = beacon.NewRoute(filter, backend)
	}
	return beacon.New(docker, routes)
}

func main() {
	config := Configure(os.Args)
	bcn, err := NewBeacon(config)
	if err != nil {
		Logger.Fatalf("failed to initialize: %s", err)
	}

	signals := notifyOnStop()

	go func() {
		<-signals
		if err := bcn.Close(); err != nil {
			Logger.Fatalf("failed to shut down: %s", err)
		}
	}()

	if err := bcn.Run(); err != nil {
		Logger.Fatalf("failed to shut down: %s", err)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"net"
	"os"
	"testing"
	"time"

	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"

	"github.com/dexidp/dex/api"
	"github.com/dexidp/dex/server/internal"
	"github.com/dexidp/dex/storage"
	"github.com/dexidp/dex/storage/memory"
)

// apiClient is a test gRPC client. When constructed, it runs a server in
// the background to exercise the serialization and network configuration
// instead of just this package's server implementation.
type apiClient struct {
	// Embedded gRPC client to talk to the server.
	api.DexClient
	// Close releases resources associated with this client, includuing shutting
	// down the background server.
	Close func()
}

// newAPI constructs a gRCP client connected to a backing server.
func newAPI(s storage.Storage, logger logrus.FieldLogger, t *testing.T) *apiClient {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}

	serv := grpc.NewServer()
	api.RegisterDexServer(serv, NewAPI(s, logger))
	go serv.Serve(l)

	// Dial will retry automatically if the serv.Serve() goroutine
	// hasn't started yet.
	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
	if err != nil {
		t.Fatal(err)
	}

	return &apiClient{
		DexClient: api.NewDexClient(conn),
		Close: func() {
			conn.Close()
			serv.Stop()
			l.Close()
		},
	}
}

// Attempts to create, update and delete a test Password
func TestPassword(t *testing.T) {
	logger := &logrus.Logger{
		Out:       os.Stderr,
		Formatter: &logrus.TextFormatter{DisableColors: true},
		Level:     logrus.DebugLevel,
	}

	s := memory.New(logger)
	client := newAPI(s, logger, t)
	defer client.Close()

	ctx := context.Background()
	p := api.Password{
		Email: "test@example.com",
		// bcrypt hash of the value "test1" with cost 10
		Hash:     []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"),
		Username: "test",
		UserId:   "test123",
	}

	createReq := api.CreatePasswordReq{
		Password: &p,
	}

	if resp, err := client.CreatePassword(ctx, &createReq); err != nil || resp.AlreadyExists {
		if resp.AlreadyExists {
			t.Fatalf("Unable to create password since %s already exists", createReq.Password.Email)
		}
		t.Fatalf("Unable to create password: %v", err)
	}

	// Attempt to create a password that already exists.
	if resp, _ := client.CreatePassword(ctx, &createReq); !resp.AlreadyExists {
		t.Fatalf("Created password %s twice", createReq.Password.Email)
	}

	updateReq := api.UpdatePasswordReq{
		Email:       "test@example.com",
		NewUsername: "test1",
	}

	if _, err := client.UpdatePassword(ctx, &updateReq); err != nil {
		t.Fatalf("Unable to update password: %v", err)
	}

	pass, err := s.GetPassword(updateReq.Email)
	if err != nil {
		t.Fatalf("Unable to retrieve password: %v", err)
	}

	if pass.Username != updateReq.NewUsername {
		t.Fatalf("UpdatePassword failed. Expected username %s retrieved %s", updateReq.NewUsername, pass.Username)
	}

	deleteReq := api.DeletePasswordReq{
		Email: "test@example.com",
	}

	if _, err := client.DeletePassword(ctx, &deleteReq); err != nil {
		t.Fatalf("Unable to delete password: %v", err)
	}

}

// Ensures checkCost returns expected values
func TestCheckCost(t *testing.T) {
	logger := &logrus.Logger{
		Out:       os.Stderr,
		Formatter: &logrus.TextFormatter{DisableColors: true},
		Level:     logrus.DebugLevel,
	}

	s := memory.New(logger)
	client := newAPI(s, logger, t)
	defer client.Close()

	tests := []struct {
		name      string
		inputHash []byte

		wantErr bool
	}{
		{
			name: "valid cost",
			// bcrypt hash of the value "test1" with cost 12 (default)
			inputHash: []byte("$2a$12$M2Ot95Qty1MuQdubh1acWOiYadJDzeVg3ve4n5b.dgcgPdjCseKx2"),
		},
		{
			name:      "invalid hash",
			inputHash: []byte(""),
			wantErr:   true,
		},
		{
			name: "cost below default",
			// bcrypt hash of the value "test1" with cost 4
			inputHash: []byte("$2a$04$8bSTbuVCLpKzaqB3BmgI7edDigG5tIQKkjYUu/mEO9gQgIkw9m7eG"),
			wantErr:   true,
		},
		{
			name: "cost above recommendation",
			// bcrypt hash of the value "test1" with cost 17
			inputHash: []byte("$2a$17$tWuZkTxtSmRyWZAGWVHQE.7npdl.TgP8adjzLJD.SyjpFznKBftPe"),
			wantErr:   true,
		},
	}

	for _, tc := range tests {
		if err := checkCost(tc.inputHash); err != nil {
			if !tc.wantErr {
				t.Errorf("%s: %s", tc.name, err)
			}
			continue
		}

		if tc.wantErr {
			t.Errorf("%s: expected err", tc.name)
			continue
		}
	}
}

// Attempts to list and revoke an exisiting refresh token.
func TestRefreshToken(t *testing.T) {
	logger := &logrus.Logger{
		Out:       os.Stderr,
		Formatter: &logrus.TextFormatter{DisableColors: true},
		Level:     logrus.DebugLevel,
	}

	s := memory.New(logger)
	client := newAPI(s, logger, t)
	defer client.Close()

	ctx := context.Background()

	// Creating a storage with an existing refresh token and offline session for the user.
	id := storage.NewID()
	r := storage.RefreshToken{
		ID:          id,
		Token:       "bar",
		Nonce:       "foo",
		ClientID:    "client_id",
		ConnectorID: "client_secret",
		Scopes:      []string{"openid", "email", "profile"},
		CreatedAt:   time.Now().UTC().Round(time.Millisecond),
		LastUsed:    time.Now().UTC().Round(time.Millisecond),
		Claims: storage.Claims{
			UserID:        "1",
			Username:      "jane",
			Email:         "jane.doe@example.com",
			EmailVerified: true,
			Groups:        []string{"a", "b"},
		},
		ConnectorData: []byte(`{"some":"data"}`),
	}

	if err := s.CreateRefresh(r); err != nil {
		t.Fatalf("create refresh token: %v", err)
	}

	tokenRef := storage.RefreshTokenRef{
		ID:        r.ID,
		ClientID:  r.ClientID,
		CreatedAt: r.CreatedAt,
		LastUsed:  r.LastUsed,
	}

	session := storage.OfflineSessions{
		UserID:  r.Claims.UserID,
		ConnID:  r.ConnectorID,
		Refresh: make(map[string]*storage.RefreshTokenRef),
	}
	session.Refresh[tokenRef.ClientID] = &tokenRef

	if err := s.CreateOfflineSessions(session); err != nil {
		t.Fatalf("create offline session: %v", err)
	}

	subjectString, err := internal.Marshal(&internal.IDTokenSubject{
		UserId: r.Claims.UserID,
		ConnId: r.ConnectorID,
	})
	if err != nil {
		t.Errorf("failed to marshal offline session ID: %v", err)
	}

	//Testing the api.
	listReq := api.ListRefreshReq{
		UserId: subjectString,
	}

	listResp, err := client.ListRefresh(ctx, &listReq)
	if err != nil {
		t.Fatalf("Unable to list refresh tokens for user: %v", err)
	}

	for _, tok := range listResp.RefreshTokens {
		if tok.CreatedAt != r.CreatedAt.Unix() {
			t.Errorf("Expected CreatedAt timestamp %v, got %v", r.CreatedAt.Unix(), tok.CreatedAt)
		}

		if tok.LastUsed != r.LastUsed.Unix() {
			t.Errorf("Expected LastUsed timestamp %v, got %v", r.LastUsed.Unix(), tok.LastUsed)
		}
	}

	revokeReq := api.RevokeRefreshReq{
		UserId:   subjectString,
		ClientId: r.ClientID,
	}

	resp, err := client.RevokeRefresh(ctx, &revokeReq)
	if err != nil {
		t.Fatalf("Unable to revoke refresh tokens for user: %v", err)
	}
	if resp.NotFound {
		t.Errorf("refresh token session wasn't found")
	}

	// Try to delete again.
	//
	// See https://github.com/dexidp/dex/issues/1055
	resp, err = client.RevokeRefresh(ctx, &revokeReq)
	if err != nil {
		t.Fatalf("Unable to revoke refresh tokens for user: %v", err)
	}
	if !resp.NotFound {
		t.Errorf("refresh token session was found")
	}

	if resp, _ := client.ListRefresh(ctx, &listReq); len(resp.RefreshTokens) != 0 {
		t.Fatalf("Refresh token returned inspite of revoking it.")
	}
}
<commit_msg>Add tests for UpateClient API<commit_after>package server

import (
	"context"
	"net"
	"os"
	"testing"
	"time"

	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"

	"github.com/dexidp/dex/api"
	"github.com/dexidp/dex/server/internal"
	"github.com/dexidp/dex/storage"
	"github.com/dexidp/dex/storage/memory"
)

// apiClient is a test gRPC client. When constructed, it runs a server in
// the background to exercise the serialization and network configuration
// instead of just this package's server implementation.
type apiClient struct {
	// Embedded gRPC client to talk to the server.
	api.DexClient
	// Close releases resources associated with this client, includuing shutting
	// down the background server.
	Close func()
}

// newAPI constructs a gRCP client connected to a backing server.
func newAPI(s storage.Storage, logger logrus.FieldLogger, t *testing.T) *apiClient {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}

	serv := grpc.NewServer()
	api.RegisterDexServer(serv, NewAPI(s, logger))
	go serv.Serve(l)

	// Dial will retry automatically if the serv.Serve() goroutine
	// hasn't started yet.
	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
	if err != nil {
		t.Fatal(err)
	}

	return &apiClient{
		DexClient: api.NewDexClient(conn),
		Close: func() {
			conn.Close()
			serv.Stop()
			l.Close()
		},
	}
}

// Attempts to create, update and delete a test Password
func TestPassword(t *testing.T) {
	logger := &logrus.Logger{
		Out:       os.Stderr,
		Formatter: &logrus.TextFormatter{DisableColors: true},
		Level:     logrus.DebugLevel,
	}

	s := memory.New(logger)
	client := newAPI(s, logger, t)
	defer client.Close()

	ctx := context.Background()
	p := api.Password{
		Email: "test@example.com",
		// bcrypt hash of the value "test1" with cost 10
		Hash:     []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"),
		Username: "test",
		UserId:   "test123",
	}

	createReq := api.CreatePasswordReq{
		Password: &p,
	}

	if resp, err := client.CreatePassword(ctx, &createReq); err != nil || resp.AlreadyExists {
		if resp.AlreadyExists {
			t.Fatalf("Unable to create password since %s already exists", createReq.Password.Email)
		}
		t.Fatalf("Unable to create password: %v", err)
	}

	// Attempt to create a password that already exists.
	if resp, _ := client.CreatePassword(ctx, &createReq); !resp.AlreadyExists {
		t.Fatalf("Created password %s twice", createReq.Password.Email)
	}

	updateReq := api.UpdatePasswordReq{
		Email:       "test@example.com",
		NewUsername: "test1",
	}

	if _, err := client.UpdatePassword(ctx, &updateReq); err != nil {
		t.Fatalf("Unable to update password: %v", err)
	}

	pass, err := s.GetPassword(updateReq.Email)
	if err != nil {
		t.Fatalf("Unable to retrieve password: %v", err)
	}

	if pass.Username != updateReq.NewUsername {
		t.Fatalf("UpdatePassword failed. Expected username %s retrieved %s", updateReq.NewUsername, pass.Username)
	}

	deleteReq := api.DeletePasswordReq{
		Email: "test@example.com",
	}

	if _, err := client.DeletePassword(ctx, &deleteReq); err != nil {
		t.Fatalf("Unable to delete password: %v", err)
	}

}

// Ensures checkCost returns expected values
func TestCheckCost(t *testing.T) {
	logger := &logrus.Logger{
		Out:       os.Stderr,
		Formatter: &logrus.TextFormatter{DisableColors: true},
		Level:     logrus.DebugLevel,
	}

	s := memory.New(logger)
	client := newAPI(s, logger, t)
	defer client.Close()

	tests := []struct {
		name      string
		inputHash []byte

		wantErr bool
	}{
		{
			name: "valid cost",
			// bcrypt hash of the value "test1" with cost 12 (default)
			inputHash: []byte("$2a$12$M2Ot95Qty1MuQdubh1acWOiYadJDzeVg3ve4n5b.dgcgPdjCseKx2"),
		},
		{
			name:      "invalid hash",
			inputHash: []byte(""),
			wantErr:   true,
		},
		{
			name: "cost below default",
			// bcrypt hash of the value "test1" with cost 4
			inputHash: []byte("$2a$04$8bSTbuVCLpKzaqB3BmgI7edDigG5tIQKkjYUu/mEO9gQgIkw9m7eG"),
			wantErr:   true,
		},
		{
			name: "cost above recommendation",
			// bcrypt hash of the value "test1" with cost 17
			inputHash: []byte("$2a$17$tWuZkTxtSmRyWZAGWVHQE.7npdl.TgP8adjzLJD.SyjpFznKBftPe"),
			wantErr:   true,
		},
	}

	for _, tc := range tests {
		if err := checkCost(tc.inputHash); err != nil {
			if !tc.wantErr {
				t.Errorf("%s: %s", tc.name, err)
			}
			continue
		}

		if tc.wantErr {
			t.Errorf("%s: expected err", tc.name)
			continue
		}
	}
}

// Attempts to list and revoke an exisiting refresh token.
func TestRefreshToken(t *testing.T) {
	logger := &logrus.Logger{
		Out:       os.Stderr,
		Formatter: &logrus.TextFormatter{DisableColors: true},
		Level:     logrus.DebugLevel,
	}

	s := memory.New(logger)
	client := newAPI(s, logger, t)
	defer client.Close()

	ctx := context.Background()

	// Creating a storage with an existing refresh token and offline session for the user.
	id := storage.NewID()
	r := storage.RefreshToken{
		ID:          id,
		Token:       "bar",
		Nonce:       "foo",
		ClientID:    "client_id",
		ConnectorID: "client_secret",
		Scopes:      []string{"openid", "email", "profile"},
		CreatedAt:   time.Now().UTC().Round(time.Millisecond),
		LastUsed:    time.Now().UTC().Round(time.Millisecond),
		Claims: storage.Claims{
			UserID:        "1",
			Username:      "jane",
			Email:         "jane.doe@example.com",
			EmailVerified: true,
			Groups:        []string{"a", "b"},
		},
		ConnectorData: []byte(`{"some":"data"}`),
	}

	if err := s.CreateRefresh(r); err != nil {
		t.Fatalf("create refresh token: %v", err)
	}

	tokenRef := storage.RefreshTokenRef{
		ID:        r.ID,
		ClientID:  r.ClientID,
		CreatedAt: r.CreatedAt,
		LastUsed:  r.LastUsed,
	}

	session := storage.OfflineSessions{
		UserID:  r.Claims.UserID,
		ConnID:  r.ConnectorID,
		Refresh: make(map[string]*storage.RefreshTokenRef),
	}
	session.Refresh[tokenRef.ClientID] = &tokenRef

	if err := s.CreateOfflineSessions(session); err != nil {
		t.Fatalf("create offline session: %v", err)
	}

	subjectString, err := internal.Marshal(&internal.IDTokenSubject{
		UserId: r.Claims.UserID,
		ConnId: r.ConnectorID,
	})
	if err != nil {
		t.Errorf("failed to marshal offline session ID: %v", err)
	}

	//Testing the api.
	listReq := api.ListRefreshReq{
		UserId: subjectString,
	}

	listResp, err := client.ListRefresh(ctx, &listReq)
	if err != nil {
		t.Fatalf("Unable to list refresh tokens for user: %v", err)
	}

	for _, tok := range listResp.RefreshTokens {
		if tok.CreatedAt != r.CreatedAt.Unix() {
			t.Errorf("Expected CreatedAt timestamp %v, got %v", r.CreatedAt.Unix(), tok.CreatedAt)
		}

		if tok.LastUsed != r.LastUsed.Unix() {
			t.Errorf("Expected LastUsed timestamp %v, got %v", r.LastUsed.Unix(), tok.LastUsed)
		}
	}

	revokeReq := api.RevokeRefreshReq{
		UserId:   subjectString,
		ClientId: r.ClientID,
	}

	resp, err := client.RevokeRefresh(ctx, &revokeReq)
	if err != nil {
		t.Fatalf("Unable to revoke refresh tokens for user: %v", err)
	}
	if resp.NotFound {
		t.Errorf("refresh token session wasn't found")
	}

	// Try to delete again.
	//
	// See https://github.com/dexidp/dex/issues/1055
	resp, err = client.RevokeRefresh(ctx, &revokeReq)
	if err != nil {
		t.Fatalf("Unable to revoke refresh tokens for user: %v", err)
	}
	if !resp.NotFound {
		t.Errorf("refresh token session was found")
	}

	if resp, _ := client.ListRefresh(ctx, &listReq); len(resp.RefreshTokens) != 0 {
		t.Fatalf("Refresh token returned inspite of revoking it.")
	}
}

func TestUpdateClient(t *testing.T) {
	logger := &logrus.Logger{
		Out:       os.Stderr,
		Formatter: &logrus.TextFormatter{DisableColors: true},
		Level:     logrus.DebugLevel,
	}

	s := memory.New(logger)
	client := newAPI(s, logger, t)
	defer client.Close()
	ctx := context.Background()

	createClient := func(t *testing.T, clientId string) {
		resp, err := client.CreateClient(ctx, &api.CreateClientReq{
			Client: &api.Client{
				Id:           clientId,
				Secret:       "",
				RedirectUris: []string{},
				TrustedPeers: nil,
				Public:       true,
				Name:         "",
				LogoUrl:      "",
			},
		})
		if err != nil {
			t.Fatalf("unable to create the client: %v", err)
		}

		if resp == nil {
			t.Fatalf("create client returned no response")
		}
		if resp.AlreadyExists {
			t.Error("existing client was found")
		}

		if resp.Client == nil {
			t.Fatalf("no client created")
		}
	}

	deleteClient := func(t *testing.T, clientId string) {
		resp, err := client.DeleteClient(ctx, &api.DeleteClientReq{
			Id: clientId,
		})
		if err != nil {
			t.Fatalf("unable to delete the client: %v", err)
		}
		if resp == nil {
			t.Fatalf("delete client delete client returned no response")
		}
	}

	tests := map[string]struct {
		setup   func(t *testing.T, clientId string)
		cleanup func(t *testing.T, clientId string)
		req     *api.UpdateClientReq
		wantErr bool
		want    *api.UpdateClientResp
	}{
		"update client": {
			setup:   createClient,
			cleanup: deleteClient,
			req: &api.UpdateClientReq{
				Id:           "test",
				RedirectUris: []string{"https://redirect"},
				TrustedPeers: []string{"test"},
				Public:       true,
				Name:         "test",
				LogoUrl:      "https://logout",
			},
			wantErr: false,
			want: &api.UpdateClientResp{
				NotFound: false,
			},
		},
		"update client without ID": {
			setup:   createClient,
			cleanup: deleteClient,
			req: &api.UpdateClientReq{
				Id:           "",
				RedirectUris: nil,
				TrustedPeers: nil,
				Public:       false,
				Name:         "test",
				LogoUrl:      "test",
			},
			wantErr: true,
			want: &api.UpdateClientResp{
				NotFound: false,
			},
		},
		"update client which not exists ": {
			req: &api.UpdateClientReq{
				Id:           "test",
				RedirectUris: nil,
				TrustedPeers: nil,
				Public:       false,
				Name:         "test",
				LogoUrl:      "test",
			},
			wantErr: true,
			want: &api.UpdateClientResp{
				NotFound: false,
			},
		},
	}

	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			if tc.setup != nil {
				tc.setup(t, tc.req.Id)
			}
			resp, err := client.UpdateClient(ctx, tc.req)
			if err != nil && !tc.wantErr {
				t.Fatalf("failed to update the client: %v", err)
			}

			if !tc.wantErr {
				if resp == nil {
					t.Fatalf("update client response not found")
				}

				if tc.want.NotFound != resp.NotFound {
					t.Errorf("expected in response NotFound: %t", tc.want.NotFound)
				}

				client, err := s.GetClient(tc.req.Id)
				if err != nil {
					t.Errorf("no client found in the storage: %v", err)
				}

				if tc.req.Id != client.ID {
					t.Errorf("expected stored client with ID: %s, found %s", tc.req.Id, client.ID)
				}
				if tc.req.Name != client.Name {
					t.Errorf("expected stored client with Name: %s, found %s", tc.req.Name, client.Name)
				}
				if tc.req.LogoUrl != client.LogoURL {
					t.Errorf("expected stored client with LogoURL: %s, found %s", tc.req.LogoUrl, client.LogoURL)
				}
				for _, redirectURI := range tc.req.RedirectUris {
					found := find(redirectURI, client.RedirectURIs)
					if !found {
						t.Errorf("expected redirect URI: %s", redirectURI)
					}
				}
				for _, peer := range tc.req.TrustedPeers {
					found := find(peer, client.TrustedPeers)
					if !found {
						t.Errorf("expected trusted peer: %s", peer)
					}
				}
			}

			if tc.cleanup != nil {
				tc.cleanup(t, tc.req.Id)
			}

		})
	}
}

func find(item string, items []string) bool {
	for _, i := range items {
		if item == i {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*

Gowsdl generates Go code from a WSDL file.

This project is originally intended to generate Go clients for WS-* services.

Usage: gowsdl [options] myservice.wsdl
  -o string
        File where the generated code will be saved (default "myservice.go")
  -p string
        Package under which code will be generated (default "myservice")
  -v    Shows gowsdl version

Features

Supports only Document/Literal wrapped services, which are WS-I (http://ws-i.org/) compliant.

Attempts to generate idiomatic Go code as much as possible.

Supports WSDL 1.1, XML Schema 1.0, SOAP 1.1.

Resolves external XML Schemas

Supports providing WSDL HTTP URL as well as a local WSDL file.

Not supported

UDDI.

TODO

Add support for filters to allow the user to change the generated code.

If WSDL file is local, resolve external XML schemas locally too instead of failing due to not having a URL to download them from.

Resolve XSD element references.

Support for generating namespaces.

Make code generation agnostic so generating code to other programming languages is feasible through plugins.

*/

package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/format"
	"log"
	"os"
	"path/filepath"

	gen "github.com/hooklift/gowsdl"
)

// Version is initialized in compilation time by go build.
var Version string

// Name is initialized in compilation time by go build.
var Name string

var vers = flag.Bool("v", false, "Shows gowsdl version")
var pkg = flag.String("p", "myservice", "Package under which code will be generated")
var outFile = flag.String("o", "myservice.go", "File where the generated code will be saved")
var insecure = flag.Bool("i", false, "Skips TLS Verification")
var makePublic = flag.Bool("make-public", true, "Make the generated types public/exported")

func init() {
	log.SetFlags(0)
	log.SetOutput(os.Stdout)
	log.SetPrefix("🍀  ")
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [options] myservice.wsdl\n", os.Args[0])
		flag.PrintDefaults()
	}

	flag.Parse()

	// Show app version
	if *vers {
		log.Println(Version)
		os.Exit(0)
	}

	if len(os.Args) < 2 {
		flag.Usage()
		os.Exit(0)
	}

	wsdlPath := os.Args[len(os.Args)-1]

	if *outFile == wsdlPath {
		log.Fatalln("Output file cannot be the same WSDL file")
	}

	// load wsdl
	gowsdl, err := gen.NewGoWSDL(wsdlPath, *pkg, *insecure, *makePublic)
	if err != nil {
		log.Fatalln(err)
	}

	// generate code
	gocode, err := gowsdl.Start()
	if err != nil {
		log.Fatalln(err)
	}

	pkg := filepath.Join("./", *pkg)
	err = os.Mkdir(pkg, 0744)

	file, err := os.Create(filepath.Join(pkg, *outFile))
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()

	data := new(bytes.Buffer)
	data.Write(gocode["header"])
	data.Write(gocode["types"])
	data.Write(gocode["operations"])
	data.Write(gocode["soap"])

	// go fmt the generated code
	source, err := format.Source(data.Bytes())
	if err != nil {
		file.Write(data.Bytes())
		log.Fatalln(err)
	}

	file.Write(source)

	log.Println("Done 👍")
}
<commit_msg>Add -dir option for specifing path for package directory will be created.<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*

Gowsdl generates Go code from a WSDL file.

This project is originally intended to generate Go clients for WS-* services.

Usage: gowsdl [options] myservice.wsdl
  -o string
        File where the generated code will be saved (default "myservice.go")
  -p string
        Package under which code will be generated (default "myservice")
  -v    Shows gowsdl version

Features

Supports only Document/Literal wrapped services, which are WS-I (http://ws-i.org/) compliant.

Attempts to generate idiomatic Go code as much as possible.

Supports WSDL 1.1, XML Schema 1.0, SOAP 1.1.

Resolves external XML Schemas

Supports providing WSDL HTTP URL as well as a local WSDL file.

Not supported

UDDI.

TODO

Add support for filters to allow the user to change the generated code.

If WSDL file is local, resolve external XML schemas locally too instead of failing due to not having a URL to download them from.

Resolve XSD element references.

Support for generating namespaces.

Make code generation agnostic so generating code to other programming languages is feasible through plugins.

*/

package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/format"
	"log"
	"os"
	"path/filepath"

	gen "github.com/hooklift/gowsdl"
)

// Version is initialized in compilation time by go build.
var Version string

// Name is initialized in compilation time by go build.
var Name string

var vers = flag.Bool("v", false, "Shows gowsdl version")
var pkg = flag.String("p", "myservice", "Package under which code will be generated")
var outFile = flag.String("o", "myservice.go", "File where the generated code will be saved")
var dir = flag.String("d", "./", "Directory under which package directory will be created")
var insecure = flag.Bool("i", false, "Skips TLS Verification")
var makePublic = flag.Bool("make-public", true, "Make the generated types public/exported")

func init() {
	log.SetFlags(0)
	log.SetOutput(os.Stdout)
	log.SetPrefix("🍀  ")
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [options] myservice.wsdl\n", os.Args[0])
		flag.PrintDefaults()
	}

	flag.Parse()

	// Show app version
	if *vers {
		log.Println(Version)
		os.Exit(0)
	}

	if len(os.Args) < 2 {
		flag.Usage()
		os.Exit(0)
	}

	wsdlPath := os.Args[len(os.Args)-1]

	if *outFile == wsdlPath {
		log.Fatalln("Output file cannot be the same WSDL file")
	}

	// load wsdl
	gowsdl, err := gen.NewGoWSDL(wsdlPath, *pkg, *insecure, *makePublic)
	if err != nil {
		log.Fatalln(err)
	}

	// generate code
	gocode, err := gowsdl.Start()
	if err != nil {
		log.Fatalln(err)
	}

	pkg := filepath.Join(*dir, *pkg)
	err = os.Mkdir(pkg, 0744)

	file, err := os.Create(filepath.Join(pkg, *outFile))
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()

	data := new(bytes.Buffer)
	data.Write(gocode["header"])
	data.Write(gocode["types"])
	data.Write(gocode["operations"])
	data.Write(gocode["soap"])

	// go fmt the generated code
	source, err := format.Source(data.Bytes())
	if err != nil {
		file.Write(data.Bytes())
		log.Fatalln(err)
	}

	file.Write(source)

	log.Println("Done 👍")
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"crypto/x509"
	"database/sql"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"time"

	loggingclient "code.cloudfoundry.org/diego-logging-client"
	"code.cloudfoundry.org/go-loggregator/runtimeemitter"
	"github.com/go-sql-driver/mysql"
	"github.com/hashicorp/consul/api"
	"github.com/lib/pq"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/sigmon"

	"code.cloudfoundry.org/bbs/db/sqldb/helpers"
	"code.cloudfoundry.org/bbs/db/sqldb/helpers/monitor"
	"code.cloudfoundry.org/bbs/guidprovider"
	"code.cloudfoundry.org/cfhttp"
	"code.cloudfoundry.org/clock"
	"code.cloudfoundry.org/consuladapter"
	"code.cloudfoundry.org/debugserver"
	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/lager/lagerflags"
	"code.cloudfoundry.org/locket"
	"code.cloudfoundry.org/locket/cmd/locket/config"
	"code.cloudfoundry.org/locket/db"
	"code.cloudfoundry.org/locket/expiration"
	"code.cloudfoundry.org/locket/grpcserver"
	"code.cloudfoundry.org/locket/handlers"
	"code.cloudfoundry.org/locket/metrics"
)

var configFilePath = flag.String(
	"config",
	"",
	"Path to Locket JSON Configuration file",
)

func main() {
	flag.Parse()

	cfg, err := config.NewLocketConfig(*configFilePath)
	if err != nil {
		panic("invalid-config-file: " + err.Error())
	}

	logger, reconfigurableSink := lagerflags.NewFromConfig("locket", cfg.LagerConfig)

	metronClient, err := initializeMetron(logger, cfg)
	if err != nil {
		logger.Error("failed-to-initialize-metron-client", err)
		os.Exit(1)
	}

	clock := clock.NewClock()

	connectionString := appendExtraConnectionStringParam(
		logger,
		cfg.DatabaseDriver,
		cfg.DatabaseConnectionString,
		cfg.SQLCACertFile,
		cfg.SQLEnableIdentityVerification,
	)

	sqlConn, err := sql.Open(cfg.DatabaseDriver, connectionString)
	if err != nil {
		logger.Fatal("failed-to-open-sql", err)
	}
	defer sqlConn.Close()

	sqlConn.SetMaxIdleConns(cfg.MaxOpenDatabaseConnections)
	sqlConn.SetMaxOpenConns(cfg.MaxOpenDatabaseConnections)

	err = sqlConn.Ping()
	if err != nil {
		logger.Fatal("sql-failed-to-connect", err)
	}

	dbMonitor := monitor.New()
	monitoredDB := helpers.NewMonitoredDB(sqlConn, dbMonitor)

	sqlDB := db.NewSQLDB(
		monitoredDB,
		cfg.DatabaseDriver,
		guidprovider.DefaultGuidProvider,
	)

	err = sqlDB.CreateLockTable(logger)
	if err != nil {
		logger.Fatal("failed-to-create-lock-table", err)
	}

	_, portString, err := net.SplitHostPort(cfg.ListenAddress)
	if err != nil {
		logger.Fatal("failed-invalid-listen-address", err)
	}

	portNum, err := net.LookupPort("tcp", portString)
	if err != nil {
		logger.Fatal("failed-invalid-listen-port", err)
	}

	tlsConfig, err := cfhttp.NewTLSConfig(cfg.CertFile, cfg.KeyFile, cfg.CaFile)
	if err != nil {
		logger.Fatal("invalid-tls-config", err)
	}

	lockMetricsNotifier := metrics.NewLockMetricsNotifier(logger, clock, metronClient, time.Duration(cfg.ReportInterval), sqlDB)
	dbMetricsNotifier := metrics.NewDBMetricsNotifier(logger, clock, metronClient, time.Duration(cfg.ReportInterval), sqlDB, dbMonitor)
	requestNotifier := metrics.NewRequestMetricsNotifier(logger, clock, metronClient, time.Duration(cfg.ReportInterval))
	lockPick := expiration.NewLockPick(sqlDB, clock, metronClient)
	burglar := expiration.NewBurglar(logger, sqlDB, lockPick, clock, locket.RetryInterval, metronClient)
	exitCh := make(chan struct{})
	handler := handlers.NewLocketHandler(logger, sqlDB, lockPick, requestNotifier, exitCh)
	server := grpcserver.NewGRPCServer(logger, cfg.ListenAddress, tlsConfig, handler)

	members := grouper.Members{
		{"server", server},
		{"burglar", burglar},
		{"lock-metrics-notifier", lockMetricsNotifier},
		{"db-metrics-notifier", dbMetricsNotifier},
		{"request-metrics-notifier", requestNotifier},
	}

	if cfg.EnableConsulServiceRegistration {
		consulClient, err := consuladapter.NewClientFromUrl(cfg.ConsulCluster)
		if err != nil {
			logger.Fatal("new-consul-client-failed", err)
		}
		registrationRunner := initializeRegistrationRunner(logger, consulClient, portNum, clock)
		members = append(members, grouper.Member{"registration-runner", registrationRunner})
	}

	if cfg.DebugAddress != "" {
		members = append(grouper.Members{
			{"debug-server", debugserver.Runner(cfg.DebugAddress, reconfigurableSink)},
		}, members...)
	}

	group := grouper.NewOrdered(os.Interrupt, members)
	monitor := ifrit.Invoke(sigmon.New(group))

	logger.Info("started")

	go func() {
		<-exitCh
		logger.Info("shutting-down-due-to-unrecoverable-error")
		monitor.Signal(os.Interrupt)
	}()

	err = <-monitor.Wait()
	if err != nil {
		logger.Error("exited-with-failure", err)
		os.Exit(1)
	}
}

func initializeMetron(logger lager.Logger, locketConfig config.LocketConfig) (loggingclient.IngressClient, error) {
	client, err := loggingclient.NewIngressClient(locketConfig.LoggregatorConfig)
	if err != nil {
		return nil, err
	}

	if locketConfig.LoggregatorConfig.UseV2API {
		emitter := runtimeemitter.NewV1(client)
		go emitter.Run()
	}

	return client, nil
}

func appendExtraConnectionStringParam(logger lager.Logger, driverName, databaseConnectionString, sqlCACertFile string, sqlEnableIdentityVerification bool) string {
	switch driverName {
	case "mysql":
		cfg, err := mysql.ParseDSN(databaseConnectionString)
		if err != nil {
			logger.Fatal("invalid-db-connection-string", err, lager.Data{"connection-string": databaseConnectionString})
		}

		if sqlCACertFile != "" {
			certBytes, err := ioutil.ReadFile(sqlCACertFile)
			if err != nil {
				logger.Fatal("failed-to-read-sql-ca-file", err)
			}

			caCertPool := x509.NewCertPool()
			if ok := caCertPool.AppendCertsFromPEM(certBytes); !ok {
				logger.Fatal("failed-to-parse-sql-ca", err)
			}

			tlsConfig := generateTLSConfig(logger, caCertPool, sqlEnableIdentityVerification)
			mysql.RegisterTLSConfig("bbs-tls", tlsConfig)
			cfg.TLSConfig = "bbs-tls"
		}
		cfg.Timeout = 10 * time.Minute
		cfg.ReadTimeout = 10 * time.Minute
		cfg.WriteTimeout = 10 * time.Minute
		databaseConnectionString = cfg.FormatDSN()
	case "postgres":
		var err error
		databaseConnectionString, err = pq.ParseURL(databaseConnectionString)
		if err != nil {
			logger.Fatal("invalid-db-connection-string", err, lager.Data{"connection-string": databaseConnectionString})
		}
		if sqlCACertFile == "" {
			databaseConnectionString = databaseConnectionString + " sslmode=disable"
		} else {
			databaseConnectionString = fmt.Sprintf("%s sslmode=verify-ca sslrootcert=%s", databaseConnectionString, sqlCACertFile)
		}
	}

	return databaseConnectionString
}

func generateTLSConfig(logger lager.Logger, caCertPool *x509.CertPool, sqlEnableIdentityVerification bool) *tls.Config {
	var tlsConfig *tls.Config

	if sqlEnableIdentityVerification {
		tlsConfig = &tls.Config{
			InsecureSkipVerify: false,
			RootCAs:            caCertPool,
		}
	} else {
		tlsConfig = &tls.Config{
			InsecureSkipVerify:    true,
			RootCAs:               caCertPool,
			VerifyPeerCertificate: generateCustomTLSVerificationFunction(caCertPool),
		}
	}

	return tlsConfig
}

func generateCustomTLSVerificationFunction(caCertPool *x509.CertPool) func([][]byte, [][]*x509.Certificate) error {
	return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
		opts := x509.VerifyOptions{
			Roots:         caCertPool,
			CurrentTime:   time.Now(),
			DNSName:       "",
			Intermediates: x509.NewCertPool(),
		}

		certs := make([]*x509.Certificate, len(rawCerts))
		for i, rawCert := range rawCerts {
			cert, err := x509.ParseCertificate(rawCert)
			if err != nil {
				return err
			}
			certs[i] = cert
		}

		for i, cert := range certs {
			if i == 0 {
				continue
			}

			opts.Intermediates.AddCert(cert)
		}

		_, err := certs[0].Verify(opts)
		if err != nil {
			return err
		}

		return nil
	}
}

func initializeRegistrationRunner(
	logger lager.Logger,
	consulClient consuladapter.Client,
	port int,
	clock clock.Clock,
) ifrit.Runner {
	registration := &api.AgentServiceRegistration{
		Name: "locket",
		Port: port,
		Check: &api.AgentServiceCheck{
			TTL: "20s",
		},
	}
	return locket.NewRegistrationRunner(logger, registration, consulClient, locket.RetryInterval, clock)
}
<commit_msg>Use the tls logic from BBS' sqldb/helpers<commit_after>package main

import (
	"database/sql"
	"flag"
	"net"
	"os"
	"time"

	"code.cloudfoundry.org/bbs/db/sqldb/helpers"
	"code.cloudfoundry.org/bbs/db/sqldb/helpers/monitor"
	"code.cloudfoundry.org/bbs/guidprovider"
	"code.cloudfoundry.org/cfhttp"
	"code.cloudfoundry.org/clock"
	"code.cloudfoundry.org/consuladapter"
	"code.cloudfoundry.org/debugserver"
	loggingclient "code.cloudfoundry.org/diego-logging-client"
	"code.cloudfoundry.org/go-loggregator/runtimeemitter"
	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/lager/lagerflags"
	"code.cloudfoundry.org/locket"
	"code.cloudfoundry.org/locket/cmd/locket/config"
	"code.cloudfoundry.org/locket/db"
	"code.cloudfoundry.org/locket/expiration"
	"code.cloudfoundry.org/locket/grpcserver"
	"code.cloudfoundry.org/locket/handlers"
	"code.cloudfoundry.org/locket/metrics"
	"github.com/hashicorp/consul/api"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/sigmon"
)

var configFilePath = flag.String(
	"config",
	"",
	"Path to Locket JSON Configuration file",
)

func main() {
	flag.Parse()

	cfg, err := config.NewLocketConfig(*configFilePath)
	if err != nil {
		panic("invalid-config-file: " + err.Error())
	}

	logger, reconfigurableSink := lagerflags.NewFromConfig("locket", cfg.LagerConfig)

	metronClient, err := initializeMetron(logger, cfg)
	if err != nil {
		logger.Error("failed-to-initialize-metron-client", err)
		os.Exit(1)
	}

	clock := clock.NewClock()

	connectionString := helpers.AddTLSParams(
		logger,
		cfg.DatabaseDriver,
		cfg.DatabaseConnectionString,
		cfg.SQLCACertFile,
		cfg.SQLEnableIdentityVerification,
	)

	sqlConn, err := sql.Open(cfg.DatabaseDriver, connectionString)
	if err != nil {
		logger.Fatal("failed-to-open-sql", err)
	}
	defer sqlConn.Close()

	sqlConn.SetMaxIdleConns(cfg.MaxOpenDatabaseConnections)
	sqlConn.SetMaxOpenConns(cfg.MaxOpenDatabaseConnections)

	err = sqlConn.Ping()
	if err != nil {
		logger.Fatal("sql-failed-to-connect", err)
	}

	dbMonitor := monitor.New()
	monitoredDB := helpers.NewMonitoredDB(sqlConn, dbMonitor)

	sqlDB := db.NewSQLDB(
		monitoredDB,
		cfg.DatabaseDriver,
		guidprovider.DefaultGuidProvider,
	)

	err = sqlDB.CreateLockTable(logger)
	if err != nil {
		logger.Fatal("failed-to-create-lock-table", err)
	}

	_, portString, err := net.SplitHostPort(cfg.ListenAddress)
	if err != nil {
		logger.Fatal("failed-invalid-listen-address", err)
	}

	portNum, err := net.LookupPort("tcp", portString)
	if err != nil {
		logger.Fatal("failed-invalid-listen-port", err)
	}

	tlsConfig, err := cfhttp.NewTLSConfig(cfg.CertFile, cfg.KeyFile, cfg.CaFile)
	if err != nil {
		logger.Fatal("invalid-tls-config", err)
	}

	lockMetricsNotifier := metrics.NewLockMetricsNotifier(logger, clock, metronClient, time.Duration(cfg.ReportInterval), sqlDB)
	dbMetricsNotifier := metrics.NewDBMetricsNotifier(logger, clock, metronClient, time.Duration(cfg.ReportInterval), sqlDB, dbMonitor)
	requestNotifier := metrics.NewRequestMetricsNotifier(logger, clock, metronClient, time.Duration(cfg.ReportInterval))
	lockPick := expiration.NewLockPick(sqlDB, clock, metronClient)
	burglar := expiration.NewBurglar(logger, sqlDB, lockPick, clock, locket.RetryInterval, metronClient)
	exitCh := make(chan struct{})
	handler := handlers.NewLocketHandler(logger, sqlDB, lockPick, requestNotifier, exitCh)
	server := grpcserver.NewGRPCServer(logger, cfg.ListenAddress, tlsConfig, handler)

	members := grouper.Members{
		{"server", server},
		{"burglar", burglar},
		{"lock-metrics-notifier", lockMetricsNotifier},
		{"db-metrics-notifier", dbMetricsNotifier},
		{"request-metrics-notifier", requestNotifier},
	}

	if cfg.EnableConsulServiceRegistration {
		consulClient, err := consuladapter.NewClientFromUrl(cfg.ConsulCluster)
		if err != nil {
			logger.Fatal("new-consul-client-failed", err)
		}
		registrationRunner := initializeRegistrationRunner(logger, consulClient, portNum, clock)
		members = append(members, grouper.Member{"registration-runner", registrationRunner})
	}

	if cfg.DebugAddress != "" {
		members = append(grouper.Members{
			{"debug-server", debugserver.Runner(cfg.DebugAddress, reconfigurableSink)},
		}, members...)
	}

	group := grouper.NewOrdered(os.Interrupt, members)
	monitor := ifrit.Invoke(sigmon.New(group))

	logger.Info("started")

	go func() {
		<-exitCh
		logger.Info("shutting-down-due-to-unrecoverable-error")
		monitor.Signal(os.Interrupt)
	}()

	err = <-monitor.Wait()
	if err != nil {
		logger.Error("exited-with-failure", err)
		os.Exit(1)
	}
}

func initializeMetron(logger lager.Logger, locketConfig config.LocketConfig) (loggingclient.IngressClient, error) {
	client, err := loggingclient.NewIngressClient(locketConfig.LoggregatorConfig)
	if err != nil {
		return nil, err
	}

	if locketConfig.LoggregatorConfig.UseV2API {
		emitter := runtimeemitter.NewV1(client)
		go emitter.Run()
	}

	return client, nil
}

func initializeRegistrationRunner(
	logger lager.Logger,
	consulClient consuladapter.Client,
	port int,
	clock clock.Clock,
) ifrit.Runner {
	registration := &api.AgentServiceRegistration{
		Name: "locket",
		Port: port,
		Check: &api.AgentServiceCheck{
			TTL: "20s",
		},
	}
	return locket.NewRegistrationRunner(logger, registration, consulClient, locket.RetryInterval, clock)
}
<|endoftext|>


<commit_before>package main

//go:generate xb cat -o licenses.go xzLicense:github.com/uli-go/xz/LICENSE
//go:generate xb version-file -o version.go

import (
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/uli-go/xz/gflag"
	"github.com/uli-go/xz/term"
)

const (
	lzmaExt  = ".lzma"
	usageStr = `Usage: lzmago [OPTION]... [FILE]...
Compress or uncompress FILEs in the .lzma format (by default, compress FILES
in place).

  -c, --stdout      write to standard output and don't delete input files
  -d, --decompress  force decompression
  -f, --force       force overwrite of output file and compress links
  -h, --help        give this help
  -k, --keep        keep (don't delete) input files
  -L, --license     display software license
  -q, --quiet       suppress all warnings
  -v, --verbose     verbose mode
  -V, --version     display version string
  -z, --compress    force compression
  -0 ... -9         compression preset; default is 6

With no file, or when FILE is -, read standard input.

Report bugs using <https://github.com/uli-go/xz/issues>.
`
)

func usage(w io.Writer) {
	fmt.Fprint(w, usageStr)
}

func licenses(w io.Writer) {
	out := `
github.com/uli-go/xz -- xz for Go
=================================

{{.xz}}
`
	out = strings.TrimLeft(out, " \n")
	tmpl, err := template.New("licenses").Parse(out)
	if err != nil {
		log.Panicf("error %s parsing licenses template", err)
	}
	lmap := map[string]string{
		"xz": strings.TrimSpace(xzLicense),
	}
	if err = tmpl.Execute(w, lmap); err != nil {
		log.Fatalf("error %s writing licenses template", err)
	}
}

func main() {
	// setup logger
	cmdName := filepath.Base(os.Args[0])
	log.SetPrefix(fmt.Sprintf("%s: ", cmdName))
	log.SetFlags(0)

	// initialize flags
	gflag.CommandLine = gflag.NewFlagSet(cmdName, gflag.ExitOnError)
	gflag.Usage = func() { usage(os.Stderr); os.Exit(1) }
	var (
		help        = gflag.BoolP("help", "h", false, "")
		stdout      = gflag.BoolP("stdout", "c", false, "")
		decompress  = gflag.BoolP("decompress", "d", false, "")
		force       = gflag.BoolP("force", "f", false, "")
		keep        = gflag.BoolP("keep", "k", false, "")
		license     = gflag.BoolP("license", "L", false, "")
		versionFlag = gflag.BoolP("version", "V", false, "")
		preset      = gflag.Preset(0, 9, 6, "")
	)

	// process arguments
	gflag.Parse()

	if *help {
		usage(os.Stdout)
		os.Exit(0)
	}
	if *license {
		licenses(os.Stdout)
		os.Exit(0)
	}
	if *versionFlag {
		log.Printf("version %s\n", version)
		os.Exit(0)
	}
	if gflag.NArg() == 0 && !*stdout {
		log.Fatal("for help, type lzmago -h")
	}
	if *stdout && !*decompress && !*force && term.IsTerminal(os.Stdout.Fd()) {
		log.Print("Compressed data will not be written to a terminal.")
		log.SetPrefix("")
		log.Fatal("Use -f to force compression." +
			" For help type lzmago -h.")
	}

	log.Printf("decompress %t", *decompress)
	log.Printf("force %t", *force)
	log.Printf("keep %t", *keep)
	log.Printf("preset %d", *preset)
	log.Printf("stdout %t", *stdout)
}
<commit_msg>lzmago: handle arguments correctly<commit_after>package main

//go:generate xb cat -o licenses.go xzLicense:github.com/uli-go/xz/LICENSE
//go:generate xb version-file -o version.go

import (
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/uli-go/xz/gflag"
	"github.com/uli-go/xz/term"
)

const (
	lzmaExt  = ".lzma"
	usageStr = `Usage: lzmago [OPTION]... [FILE]...
Compress or uncompress FILEs in the .lzma format (by default, compress FILES
in place).

  -c, --stdout      write to standard output and don't delete input files
  -d, --decompress  force decompression
  -f, --force       force overwrite of output file and compress links
  -h, --help        give this help
  -k, --keep        keep (don't delete) input files
  -L, --license     display software license
  -q, --quiet       suppress all warnings
  -v, --verbose     verbose mode
  -V, --version     display version string
  -z, --compress    force compression
  -0 ... -9         compression preset; default is 6

With no file, or when FILE is -, read standard input.

Report bugs using <https://github.com/uli-go/xz/issues>.
`
)

func usage(w io.Writer) {
	fmt.Fprint(w, usageStr)
}

func licenses(w io.Writer) {
	out := `
github.com/uli-go/xz -- xz for Go
=================================

{{.xz}}
`
	out = strings.TrimLeft(out, " \n")
	tmpl, err := template.New("licenses").Parse(out)
	if err != nil {
		log.Panicf("error %s parsing licenses template", err)
	}
	lmap := map[string]string{
		"xz": strings.TrimSpace(xzLicense),
	}
	if err = tmpl.Execute(w, lmap); err != nil {
		log.Fatalf("error %s writing licenses template", err)
	}
}

func main() {
	// setup logger
	cmdName := filepath.Base(os.Args[0])
	log.SetPrefix(fmt.Sprintf("%s: ", cmdName))
	log.SetFlags(0)

	// initialize flags
	gflag.CommandLine = gflag.NewFlagSet(cmdName, gflag.ExitOnError)
	gflag.Usage = func() { usage(os.Stderr); os.Exit(1) }
	var (
		help        = gflag.BoolP("help", "h", false, "")
		stdout      = gflag.BoolP("stdout", "c", false, "")
		decompress  = gflag.BoolP("decompress", "d", false, "")
		force       = gflag.BoolP("force", "f", false, "")
		keep        = gflag.BoolP("keep", "k", false, "")
		license     = gflag.BoolP("license", "L", false, "")
		versionFlag = gflag.BoolP("version", "V", false, "")
		preset      = gflag.Preset(0, 9, 6, "")
	)

	// process arguments
	gflag.Parse()

	if *help {
		usage(os.Stdout)
		os.Exit(0)
	}
	if *license {
		licenses(os.Stdout)
		os.Exit(0)
	}
	if *versionFlag {
		log.Printf("version %s\n", version)
		os.Exit(0)
	}
	var args []string
	if gflag.NArg() == 0 {
		if !*stdout {
			log.Fatal("For help, type lzmago -h.")
		}
		args = []string{"-"}
	} else {
		args = gflag.Args()
	}

	if *stdout && !*decompress && !*force && term.IsTerminal(os.Stdout.Fd()) {
		log.Print("Compressed data will not be written to a terminal.")
		log.SetPrefix("")
		log.Fatal("Use -f to force compression." +
			" For help type lzmago -h.")
	}

	log.Printf("decompress %t", *decompress)
	log.Printf("force %t", *force)
	log.Printf("keep %t", *keep)
	log.Printf("preset %d", *preset)
	log.Printf("stdout %t", *stdout)
	log.Printf("args %v", args)
}
<|endoftext|>


<commit_before>package main

import "strconv"

func (v *View) DisplayView() {
	tabsize := int(v.Buf.Settings["tabsize"].(float64))
	if v.Type == vtLog {
		// Log views should always follow the cursor...
		v.Relocate()
	}

	// We need to know the string length of the largest line number
	// so we can pad appropriately when displaying line numbers
	maxLineNumLength := len(strconv.Itoa(v.Buf.NumLines))

	if v.Buf.Settings["ruler"] == true {
		// + 1 for the little space after the line number
		v.lineNumOffset = maxLineNumLength + 1
	} else {
		v.lineNumOffset = 0
	}

	// We need to add to the line offset if there are gutter messages
	var hasGutterMessages bool
	for _, v := range v.messages {
		if len(v) > 0 {
			hasGutterMessages = true
		}
	}
	if hasGutterMessages {
		v.lineNumOffset += 2
	}

	if v.x != 0 {
		// One space for the extra split divider
		v.lineNumOffset++
	}

	xOffset := v.x + v.lineNumOffset

	height := v.Height
	width := v.Width
	left := v.leftCol
	top := v.Topline

	v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset)

	screenX := v.x
	realLineN := top - 1
	for visualLineN, line := range v.cellview.lines {
		var firstChar *Char
		if len(line) > 0 {
			firstChar = line[0]
		}

		var softwrapped bool
		if firstChar != nil {
			if firstChar.realLoc.Y == realLineN {
				softwrapped = true
			}
			realLineN = firstChar.realLoc.Y
		} else {
			realLineN++
		}

		screenX = v.x

		if v.x != 0 {
			// Draw the split divider
			screen.SetContent(screenX, visualLineN, '|', nil, defStyle.Reverse(true))
			screenX++
		}

		lineStr := v.Buf.Line(realLineN)

		// If there are gutter messages we need to display the '>>' symbol here
		if hasGutterMessages {
			// msgOnLine stores whether or not there is a gutter message on this line in particular
			msgOnLine := false
			for k := range v.messages {
				for _, msg := range v.messages[k] {
					if msg.lineNum == realLineN {
						msgOnLine = true
						gutterStyle := defStyle
						switch msg.kind {
						case GutterInfo:
							if style, ok := colorscheme["gutter-info"]; ok {
								gutterStyle = style
							}
						case GutterWarning:
							if style, ok := colorscheme["gutter-warning"]; ok {
								gutterStyle = style
							}
						case GutterError:
							if style, ok := colorscheme["gutter-error"]; ok {
								gutterStyle = style
							}
						}
						v.drawCell(screenX, visualLineN, '>', nil, gutterStyle)
						screenX++
						v.drawCell(screenX, visualLineN, '>', nil, gutterStyle)
						screenX++
						if v.Cursor.Y == realLineN && !messenger.hasPrompt {
							messenger.Message(msg.msg)
							messenger.gutterMessage = true
						}
					}
				}
			}
			// If there is no message on this line we just display an empty offset
			if !msgOnLine {
				v.drawCell(screenX, visualLineN, ' ', nil, defStyle)
				screenX++
				v.drawCell(screenX, visualLineN, ' ', nil, defStyle)
				screenX++
				if v.Cursor.Y == realLineN && messenger.gutterMessage {
					messenger.Reset()
					messenger.gutterMessage = false
				}
			}
		}

		lineNumStyle := defStyle
		if v.Buf.Settings["ruler"] == true {
			// Write the line number
			if style, ok := colorscheme["line-number"]; ok {
				lineNumStyle = style
			}
			if style, ok := colorscheme["current-line-number"]; ok {
				if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
					lineNumStyle = style
				}
			}

			lineNum := strconv.Itoa(realLineN + 1)

			// Write the spaces before the line number if necessary
			for i := 0; i < maxLineNumLength-len(lineNum); i++ {
				screen.SetContent(screenX, visualLineN, ' ', nil, lineNumStyle)
				screenX++
			}
			if softwrapped && visualLineN != 0 {
				// Pad without the line number because it was written on the visual line before
				for range lineNum {
					screen.SetContent(screenX, visualLineN, ' ', nil, lineNumStyle)
					screenX++
				}
			} else {
				// Write the actual line number
				for _, ch := range lineNum {
					screen.SetContent(screenX, visualLineN, ch, nil, lineNumStyle)
					screenX++
				}
			}

			// Write the extra space
			screen.SetContent(screenX, visualLineN, ' ', nil, lineNumStyle)
			screenX++
		}

		var lastChar *Char
		for _, char := range line {
			if char != nil {
				lineStyle := char.style

				if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
					v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X {
					screen.ShowCursor(xOffset+char.visualLoc.X, char.visualLoc.Y)
				}

				if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
					style := GetColor("cursor-line")
					fg, _, _ := style.Decompose()
					lineStyle = lineStyle.Background(fg)

					width := StringWidth(string(char.char), tabsize)
					for i := 1; i < width; i++ {
						screen.SetContent(xOffset+char.visualLoc.X+i, char.visualLoc.Y, ' ', nil, lineStyle)
					}
				}

				screen.SetContent(xOffset+char.visualLoc.X, char.visualLoc.Y, char.drawChar, nil, lineStyle)

				lastChar = char
			}
		}

		lastX := 0
		if lastChar != nil {
			if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
				v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
				screen.ShowCursor(xOffset+StringWidth(string(lineStr), tabsize), lastChar.visualLoc.Y)
			}
			lastX = xOffset + StringWidth(string(lineStr), tabsize)
		} else if len(line) == 0 {
			if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
				v.Cursor.Y == realLineN {
				screen.ShowCursor(xOffset, visualLineN)
			}
			lastX = xOffset
		}

		if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
			for i := lastX; i < v.Width; i++ {
				style := GetColor("cursor-line")
				fg, _, _ := style.Decompose()
				style = style.Background(fg)
				screen.SetContent(i, visualLineN, ' ', nil, style)
			}
		}
	}
}
<commit_msg>Support multiple splits<commit_after>package main

import "strconv"

func (v *View) DisplayView() {
	tabsize := int(v.Buf.Settings["tabsize"].(float64))
	if v.Type == vtLog {
		// Log views should always follow the cursor...
		v.Relocate()
	}

	// We need to know the string length of the largest line number
	// so we can pad appropriately when displaying line numbers
	maxLineNumLength := len(strconv.Itoa(v.Buf.NumLines))

	if v.Buf.Settings["ruler"] == true {
		// + 1 for the little space after the line number
		v.lineNumOffset = maxLineNumLength + 1
	} else {
		v.lineNumOffset = 0
	}

	// We need to add to the line offset if there are gutter messages
	var hasGutterMessages bool
	for _, v := range v.messages {
		if len(v) > 0 {
			hasGutterMessages = true
		}
	}
	if hasGutterMessages {
		v.lineNumOffset += 2
	}

	if v.x != 0 {
		// One space for the extra split divider
		v.lineNumOffset++
	}

	xOffset := v.x + v.lineNumOffset
	yOffset := v.y

	height := v.Height
	width := v.Width
	left := v.leftCol
	top := v.Topline

	v.cellview.Draw(v.Buf, top, height, left, width-v.lineNumOffset)

	screenX := v.x
	realLineN := top - 1
	visualLineN := 0
	var line []*Char
	for visualLineN, line = range v.cellview.lines {
		var firstChar *Char
		if len(line) > 0 {
			firstChar = line[0]
		}

		var softwrapped bool
		if firstChar != nil {
			if firstChar.realLoc.Y == realLineN {
				softwrapped = true
			}
			realLineN = firstChar.realLoc.Y
		} else {
			realLineN++
		}

		screenX = v.x

		if v.x != 0 {
			// Draw the split divider
			screen.SetContent(screenX, yOffset+visualLineN, '|', nil, defStyle.Reverse(true))
			screenX++
		}

		lineStr := v.Buf.Line(realLineN)

		// If there are gutter messages we need to display the '>>' symbol here
		if hasGutterMessages {
			// msgOnLine stores whether or not there is a gutter message on this line in particular
			msgOnLine := false
			for k := range v.messages {
				for _, msg := range v.messages[k] {
					if msg.lineNum == realLineN {
						msgOnLine = true
						gutterStyle := defStyle
						switch msg.kind {
						case GutterInfo:
							if style, ok := colorscheme["gutter-info"]; ok {
								gutterStyle = style
							}
						case GutterWarning:
							if style, ok := colorscheme["gutter-warning"]; ok {
								gutterStyle = style
							}
						case GutterError:
							if style, ok := colorscheme["gutter-error"]; ok {
								gutterStyle = style
							}
						}
						v.drawCell(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
						screenX++
						v.drawCell(screenX, yOffset+visualLineN, '>', nil, gutterStyle)
						screenX++
						if v.Cursor.Y == realLineN && !messenger.hasPrompt {
							messenger.Message(msg.msg)
							messenger.gutterMessage = true
						}
					}
				}
			}
			// If there is no message on this line we just display an empty offset
			if !msgOnLine {
				v.drawCell(screenX, yOffset+visualLineN, ' ', nil, defStyle)
				screenX++
				v.drawCell(screenX, yOffset+visualLineN, ' ', nil, defStyle)
				screenX++
				if v.Cursor.Y == realLineN && messenger.gutterMessage {
					messenger.Reset()
					messenger.gutterMessage = false
				}
			}
		}

		lineNumStyle := defStyle
		if v.Buf.Settings["ruler"] == true {
			// Write the line number
			if style, ok := colorscheme["line-number"]; ok {
				lineNumStyle = style
			}
			if style, ok := colorscheme["current-line-number"]; ok {
				if realLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
					lineNumStyle = style
				}
			}

			lineNum := strconv.Itoa(realLineN + 1)

			// Write the spaces before the line number if necessary
			for i := 0; i < maxLineNumLength-len(lineNum); i++ {
				screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
				screenX++
			}
			if softwrapped && visualLineN != 0 {
				// Pad without the line number because it was written on the visual line before
				for range lineNum {
					screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
					screenX++
				}
			} else {
				// Write the actual line number
				for _, ch := range lineNum {
					screen.SetContent(screenX, yOffset+visualLineN, ch, nil, lineNumStyle)
					screenX++
				}
			}

			// Write the extra space
			screen.SetContent(screenX, yOffset+visualLineN, ' ', nil, lineNumStyle)
			screenX++
		}

		var lastChar *Char
		for _, char := range line {
			if char != nil {
				lineStyle := char.style

				if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
					v.Cursor.Y == char.realLoc.Y && v.Cursor.X == char.realLoc.X {
					screen.ShowCursor(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y)
				}

				if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
					style := GetColor("cursor-line")
					fg, _, _ := style.Decompose()
					lineStyle = lineStyle.Background(fg)

					width := StringWidth(string(char.char), tabsize)
					for i := 1; i < width; i++ {
						screen.SetContent(xOffset+char.visualLoc.X+i, yOffset+char.visualLoc.Y, ' ', nil, lineStyle)
					}
				}

				screen.SetContent(xOffset+char.visualLoc.X, yOffset+char.visualLoc.Y, char.drawChar, nil, lineStyle)

				lastChar = char
			}
		}

		lastX := 0
		if lastChar != nil {
			if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
				v.Cursor.Y == lastChar.realLoc.Y && v.Cursor.X == lastChar.realLoc.X+1 {
				screen.ShowCursor(xOffset+StringWidth(string(lineStr), tabsize), yOffset+lastChar.visualLoc.Y)
			}
			lastX = xOffset + StringWidth(string(lineStr), tabsize)
		} else if len(line) == 0 {
			if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() &&
				v.Cursor.Y == realLineN {
				screen.ShowCursor(xOffset, yOffset+visualLineN)
			}
			lastX = xOffset
		}

		if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == realLineN {
			for i := lastX; i < xOffset+v.Width; i++ {
				style := GetColor("cursor-line")
				fg, _, _ := style.Decompose()
				style = style.Background(fg)
				screen.SetContent(i, yOffset+visualLineN, ' ', nil, style)
			}
		}
	}

	if v.x != 0 && visualLineN < v.Height {
		for i := visualLineN + 1; i < v.Height; i++ {
			screen.SetContent(v.x, yOffset+i, '|', nil, defStyle.Reverse(true))
		}
	}
}
<|endoftext|>