<commit_before>package boardgame

import (
	"encoding/json"
	"errors"
	"math/rand"
	"strconv"
	"time"
)

//maxRecurseCount is the number of fixUp moves that can be considered normal--
//anything more than that and we'll panic because the delegate is likely going
//to return fixup moves forever.
const maxRecurseCount = 50

//A Game represents a specific game between a collection of Players. Create a
//new one with NewGame().
type Game struct {
	manager *GameManager

	finished bool

	winners []int

	//The current version of State.
	version int

	//Memozied answer to CurrentState. Invalidated whenever ApplyMove is
	//called.
	cachedCurrentState *State

	//Modifiable controls whether moves can be made on this game.
	modifiable bool

	//A unique ID provided to this game when it is created.
	id string

	//Proposed moves is where moves that have been proposed but have not yet been applied go.
	proposedMoves chan *proposedMoveItem

	//Initalized is set to True after SetUp is called.
	initalized bool

	//TODO: HistoricalState(index int) and HistoryLen() int

	//TODO: an array of Player objects.
}

const gameIDLength = 16

type DelayedError chan error

type proposedMoveItem struct {
	move Move
	//Ch is the channel we should either return an error on and then close, or
	//send nil and close.
	ch DelayedError
}

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

const randomStringChars = "ABCDEF0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

//NewGame returns a new game. You must call SetUp before using it.
func NewGame(manager *GameManager) *Game {

	if manager == nil {
		return nil
	}

	result := &Game{
		manager: manager,
		//TODO: set the size of chan based on something more reasonable.
		//Note: this is also set similarly in manager.ModifiableGame
		proposedMoves: make(chan *proposedMoveItem, 20),
		id:            randomString(gameIDLength),
		modifiable:    true,
	}

	manager.modifiableGameCreated(result)

	return result

}

//Winners is the player indexes who were winners. Typically, this will be
//one player, but it could be multiple in the case of tie, or 0 in the
//case of a draw.
func (g *Game) Winners() []int {
	return g.winners
}

//Finished is whether the came has been completed. If it is over, the
//Winners will be set.
func (g *Game) Finished() bool {
	return g.finished
}

//Manager is a reference to the GameManager that controls this game.
//GameManager's methods will be called at key points in the lifecycle of
//this game.
func (g *Game) Manager() *GameManager {
	return g.manager
}

//NumPlayers returns the number of players for this game, based on how many
//PlayerStates are in CurrentState.
func (g *Game) NumPlayers() int {
	return len(g.CurrentState().Players)
}

//JSONForPlayer returns an object appropriate for being json'd via
//json.Marshal. The object is the equivalent to what MarshalJSON would output,
//only as an object, and with CurrentState equal to a sanitized state for the
//given player.
func (g *Game) JSONForPlayer(playerIndex int) interface{} {
	return map[string]interface{}{
		"Name":         g.Name(),
		"Finished":     g.Finished(),
		"Winners":      g.Winners(),
		"CurrentState": g.CurrentState().SanitizedForPlayer(playerIndex),
		"Id":           g.Id(),
		"NumPlayers":   g.NumPlayers(),
		"Version":      g.Version(),
	}
}

func (g *Game) MarshalJSON() ([]byte, error) {
	//We define our own MarshalJSON because if we didn't there'd be an infinite loop because of the redirects back up.
	return json.Marshal(g.JSONForPlayer(-1))
}

func (g *Game) Name() string {
	return g.manager.Delegate().Name()
}

func (g *Game) Id() string {
	return g.id
}

//Version returns the version number of the highest State that is stored for
//this game. This number will increase by one every time a move (either Player
//or FixUp) is applied.
func (g *Game) Version() int {
	return g.version
}

//CurrentVersion returns the state object for the current state. Equivalent,
//semantically, to game.State(game.Version())
func (g *Game) CurrentState() *State {
	if g.cachedCurrentState == nil {
		g.cachedCurrentState = g.State(g.Version())
	}
	return g.cachedCurrentState
}

//Returns the game's atate at the current version.
func (g *Game) State(version int) *State {

	if version < 0 || version > g.Version() {
		return nil
	}

	if result, err := g.manager.Storage().State(g, version); err == nil {
		return result
	}

	return nil

}

//SetUp should be called a single time after all of the member variables are
//set correctly, including Chest. SetUp must be called before ProposeMove can
//be called. Even if an error is returned, the game should be in a consistent
//state. If numPlayers is 0, we will use delegate.DefaultNumPlayers().
func (g *Game) SetUp(numPlayers int) error {

	if g.initalized {
		return errors.New("Game already initalized")
	}

	//TODO: we don't need this anymore because managers can't be created without chests.
	if g.manager.Chest() == nil {
		return errors.New("No component chest set on manager")
	}

	if numPlayers == 0 {
		numPlayers = g.manager.Delegate().DefaultNumPlayers()
	}

	if numPlayers < 1 {
		return errors.New("The number of players, " + strconv.Itoa(numPlayers) + " is not legal. There must be one or more players.")
	}

	if !g.manager.Delegate().LegalNumPlayers(numPlayers) {
		return errors.New("The number of players, " + strconv.Itoa(numPlayers) + " was not legal.")
	}

	stateCopy := &State{
		delegate: g.Manager().Delegate(),
	}

	gameState, err := g.manager.emptyGameState(stateCopy)

	if err != nil {
		return err
	}

	stateCopy.Game = gameState

	playerStates := make([]PlayerState, numPlayers)

	for i := 0; i < numPlayers; i++ {
		playerState, err := g.manager.emptyPlayerState(stateCopy, i)

		if err != nil {
			return err
		}

		playerStates[i] = playerState
	}

	stateCopy.Players = playerStates

	g.manager.delegate.BeginSetUp(stateCopy)

	//Distribute all components to their starter locations

	for _, name := range g.Chest().DeckNames() {
		deck := g.Chest().Deck(name)
		for i, component := range deck.Components() {
			stack, err := g.manager.Delegate().DistributeComponentToStarterStack(stateCopy, component)
			if err != nil {
				return errors.New("Distributing components failed for deck " + name + ":" + strconv.Itoa(i) + ":" + err.Error())
			}
			if stack == nil {
				return errors.New("Distributing components failed for deck " + name + ":" + strconv.Itoa(i) + ": the delegate returned no stack.")
			}
			if stack.SlotsRemaining() < 1 {
				return errors.New("Distributing components failed for deck " + name + ":" + strconv.Itoa(i) + ": the stack the delegate returned had no more slots.")
			}
			stack.insertComponentAt(stack.effectiveIndex(NextSlotIndex), component)
		}
	}

	g.manager.delegate.FinishSetUp(stateCopy)

	if g.Modifiable() {

		//Save the initial state to DB.
		g.manager.Storage().SaveGameAndCurrentState(g, stateCopy)

		go g.mainLoop()
	}

	g.initalized = true

	//See if any fixup moves apply

	//TODO: test that fixup moves are applied at the beginning.

	move := g.manager.Delegate().ProposeFixUpMove(stateCopy)

	if move != nil {
		//We apply the move immediately. This ensures that when
		//DelayedError resolves, all of the fix up moves have been
		//applied.
		g.applyMove(move, true, 0)
	}

	return nil
}

//MainLoop should be run in a goroutine. It is what takes moves off of
//proposedMoves and applies them. It is the only method that may call
//applyMove.
func (g *Game) mainLoop() {

	for item := range g.proposedMoves {
		if item == nil {
			return
		}
		item.ch <- g.applyMove(item.move, false, 0)
		close(item.ch)
	}

}

//Modifiable returns true if this instantiation of the game can be modified.
//If false, this instantiation is read-only: attributes can be read, but not
//written. In practice this means moves cannot be made.
func (g *Game) Modifiable() bool {
	return g.modifiable
}

//PlayerMoves is a thin wrapper around GameManager.PlayerMoves, but with all
//of the moves set to the right defaults for the current state of this game.
func (g *Game) PlayerMoves() []Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.PlayerMoves()
	for _, move := range result {
		move.DefaultsForState(g.CurrentState())
	}
	return result
}

//FixUpMoves is a thin wrapper around GameManager.FixUpMoves, but with all
//of the moves set to the right defaults for the current state of this game.
func (g *Game) FixUpMoves() []Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.FixUpMoves()
	for _, move := range result {
		move.DefaultsForState(g.CurrentState())
	}
	return result

}

//PlayerMoveByName is a thin wrapper around GameManager.PlayerMoveByName, but
//with it set to the right defaults for the current state of this game.
func (g *Game) PlayerMoveByName(name string) Move {
	if !g.initalized {
		return nil
	}

	result := g.manager.PlayerMoveByName(name)

	if result == nil {
		return result
	}

	result.DefaultsForState(g.CurrentState())

	return result
}

//FixUpMoveByName is a thin wrapper around GameManager.FixUpMoveByName, but
//with it set to the right defaults for the current state of this game.
func (g *Game) FixUpMoveByName(name string) Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.FixUpMoveByName(name)

	if result == nil {
		return result
	}

	result.DefaultsForState(g.CurrentState())

	return result
}

//Chest is the ComponentChest in use for this game.
func (g *Game) Chest() *ComponentChest {
	return g.manager.Chest()
}

//Refresh goes and sets this game object to reflect the current state of the
//underlying game in Storage. Basically, when you call manager.Game() you get
//a snapshot of the game in storage at that moment. If you believe that the
//underlying game in storage has been modified, calling Refresh() will re-load
//the snapshot, effectively. Most useful after calling ProposeMove() on a non-
//modifiable game.
func (g *Game) Refresh() {

	freshGame := g.manager.Game(g.Id())

	g.cachedCurrentState = nil
	g.version = freshGame.Version()
	g.finished = freshGame.Finished()
	g.winners = freshGame.Winners()

}

//ProposedMove is the way to propose a move to the game. DelayedError will
//return an error in the future if the move was unable to be applied, or nil
//if the move was applied successfully. DelayedError will only resolve once
//any applicable FixUp moves have been applied already. Note: DelayedError
//won't return anything until after SetUp has been called. This is legal to
//call on a non-modifiable game--the change will be dispatched to a modifiable
//version of the game with this ID. However, note that if you call it on a
//non-modifiable game, even once DelayedError has resolved, the original game
//will still represent its old state. If you wantt to see its current state,
//calling game.Refresh() after DelayedError has resolved should contain the
//move changes you proposed, if they were accepted (and of course potentially
//more moves if other moves were applied in the meantime).
func (g *Game) ProposeMove(move Move) DelayedError {

	if !g.Modifiable() {
		return g.manager.proposeMoveOnGame(g.Id(), move)
	}

	errChan := make(DelayedError, 1)

	workItem := &proposedMoveItem{
		move: move,
		ch:   errChan,
	}

	g.proposedMoves <- workItem

	return errChan

}

//Game applies the move to the state if it is currently legal. May only be
//called by mainLoop. Propose moves with game.ProposeMove instead.
func (g *Game) applyMove(move Move, isFixUp bool, recurseCount int) error {

	if !g.initalized {
		return errors.New("The game has not been initalized.")
	}

	if g.finished {
		return errors.New("Game was already finished")
	}

	if isFixUp {
		if g.manager.FixUpMoveByName(move.Name()) == nil {
			return errors.New("That move is not configured as a Fix Up move for this game.")
		}
	} else {

		//Verify that the Move is actually configured to be part of this game.
		if g.manager.PlayerMoveByName(move.Name()) == nil {
			return errors.New("That move is not configured as a Player move for this game.")
		}
	}

	currentState := g.CurrentState()

	if err := move.Legal(currentState); err != nil {
		//It's not legal, reject.
		return errors.New("The move was not legal: " + err.Error())
	}

	newState := currentState.Copy(false)

	if err := move.Apply(newState); err != nil {
		return errors.New("The move's apply function returned an error:" + err.Error())
	}

	//Check to see if that move made the game finished.

	finished, winners := g.manager.Delegate().CheckGameFinished(newState)

	if finished {
		g.finished = true
		g.winners = winners
		//TODO: persist to database here.
	}

	g.version = g.version + 1
	//Expire the currentState cache; it's no longer valid.
	g.cachedCurrentState = nil

	//TODO: test that if we fail to save state to storage everything's fine.
	if err := g.manager.Storage().SaveGameAndCurrentState(g, newState); err != nil {
		return errors.New("Storage returned an error:" + err.Error())
	}

	if recurseCount > maxRecurseCount {
		panic("We recursed deeply in fixup, which implies that ProposeFixUp has a move that is always legal. Quitting.")
	}

	move = g.manager.Delegate().ProposeFixUpMove(newState)

	if move != nil {
		//We apply the move immediately. This ensures that when
		//DelayedError resolves, all of the fix up moves have been
		//applied.
		g.applyMove(move, true, recurseCount+1)
	}

	return nil

}
<commit_msg>Game.SetUp() returns an error if storage fails. ... Don't know why this wasn't true before.<commit_after>package boardgame

import (
	"encoding/json"
	"errors"
	"math/rand"
	"strconv"
	"time"
)

//maxRecurseCount is the number of fixUp moves that can be considered normal--
//anything more than that and we'll panic because the delegate is likely going
//to return fixup moves forever.
const maxRecurseCount = 50

//A Game represents a specific game between a collection of Players. Create a
//new one with NewGame().
type Game struct {
	manager *GameManager

	finished bool

	winners []int

	//The current version of State.
	version int

	//Memozied answer to CurrentState. Invalidated whenever ApplyMove is
	//called.
	cachedCurrentState *State

	//Modifiable controls whether moves can be made on this game.
	modifiable bool

	//A unique ID provided to this game when it is created.
	id string

	//Proposed moves is where moves that have been proposed but have not yet been applied go.
	proposedMoves chan *proposedMoveItem

	//Initalized is set to True after SetUp is called.
	initalized bool

	//TODO: HistoricalState(index int) and HistoryLen() int

	//TODO: an array of Player objects.
}

const gameIDLength = 16

type DelayedError chan error

type proposedMoveItem struct {
	move Move
	//Ch is the channel we should either return an error on and then close, or
	//send nil and close.
	ch DelayedError
}

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

const randomStringChars = "ABCDEF0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

//NewGame returns a new game. You must call SetUp before using it.
func NewGame(manager *GameManager) *Game {

	if manager == nil {
		return nil
	}

	result := &Game{
		manager: manager,
		//TODO: set the size of chan based on something more reasonable.
		//Note: this is also set similarly in manager.ModifiableGame
		proposedMoves: make(chan *proposedMoveItem, 20),
		id:            randomString(gameIDLength),
		modifiable:    true,
	}

	manager.modifiableGameCreated(result)

	return result

}

//Winners is the player indexes who were winners. Typically, this will be
//one player, but it could be multiple in the case of tie, or 0 in the
//case of a draw.
func (g *Game) Winners() []int {
	return g.winners
}

//Finished is whether the came has been completed. If it is over, the
//Winners will be set.
func (g *Game) Finished() bool {
	return g.finished
}

//Manager is a reference to the GameManager that controls this game.
//GameManager's methods will be called at key points in the lifecycle of
//this game.
func (g *Game) Manager() *GameManager {
	return g.manager
}

//NumPlayers returns the number of players for this game, based on how many
//PlayerStates are in CurrentState.
func (g *Game) NumPlayers() int {
	return len(g.CurrentState().Players)
}

//JSONForPlayer returns an object appropriate for being json'd via
//json.Marshal. The object is the equivalent to what MarshalJSON would output,
//only as an object, and with CurrentState equal to a sanitized state for the
//given player.
func (g *Game) JSONForPlayer(playerIndex int) interface{} {
	return map[string]interface{}{
		"Name":         g.Name(),
		"Finished":     g.Finished(),
		"Winners":      g.Winners(),
		"CurrentState": g.CurrentState().SanitizedForPlayer(playerIndex),
		"Id":           g.Id(),
		"NumPlayers":   g.NumPlayers(),
		"Version":      g.Version(),
	}
}

func (g *Game) MarshalJSON() ([]byte, error) {
	//We define our own MarshalJSON because if we didn't there'd be an infinite loop because of the redirects back up.
	return json.Marshal(g.JSONForPlayer(-1))
}

func (g *Game) Name() string {
	return g.manager.Delegate().Name()
}

func (g *Game) Id() string {
	return g.id
}

//Version returns the version number of the highest State that is stored for
//this game. This number will increase by one every time a move (either Player
//or FixUp) is applied.
func (g *Game) Version() int {
	return g.version
}

//CurrentVersion returns the state object for the current state. Equivalent,
//semantically, to game.State(game.Version())
func (g *Game) CurrentState() *State {
	if g.cachedCurrentState == nil {
		g.cachedCurrentState = g.State(g.Version())
	}
	return g.cachedCurrentState
}

//Returns the game's atate at the current version.
func (g *Game) State(version int) *State {

	if version < 0 || version > g.Version() {
		return nil
	}

	if result, err := g.manager.Storage().State(g, version); err == nil {
		return result
	}

	return nil

}

//SetUp should be called a single time after all of the member variables are
//set correctly, including Chest. SetUp must be called before ProposeMove can
//be called. Even if an error is returned, the game should be in a consistent
//state. If numPlayers is 0, we will use delegate.DefaultNumPlayers().
func (g *Game) SetUp(numPlayers int) error {

	if g.initalized {
		return errors.New("Game already initalized")
	}

	//TODO: we don't need this anymore because managers can't be created without chests.
	if g.manager.Chest() == nil {
		return errors.New("No component chest set on manager")
	}

	if numPlayers == 0 {
		numPlayers = g.manager.Delegate().DefaultNumPlayers()
	}

	if numPlayers < 1 {
		return errors.New("The number of players, " + strconv.Itoa(numPlayers) + " is not legal. There must be one or more players.")
	}

	if !g.manager.Delegate().LegalNumPlayers(numPlayers) {
		return errors.New("The number of players, " + strconv.Itoa(numPlayers) + " was not legal.")
	}

	stateCopy := &State{
		delegate: g.Manager().Delegate(),
	}

	gameState, err := g.manager.emptyGameState(stateCopy)

	if err != nil {
		return err
	}

	stateCopy.Game = gameState

	playerStates := make([]PlayerState, numPlayers)

	for i := 0; i < numPlayers; i++ {
		playerState, err := g.manager.emptyPlayerState(stateCopy, i)

		if err != nil {
			return err
		}

		playerStates[i] = playerState
	}

	stateCopy.Players = playerStates

	g.manager.delegate.BeginSetUp(stateCopy)

	//Distribute all components to their starter locations

	for _, name := range g.Chest().DeckNames() {
		deck := g.Chest().Deck(name)
		for i, component := range deck.Components() {
			stack, err := g.manager.Delegate().DistributeComponentToStarterStack(stateCopy, component)
			if err != nil {
				return errors.New("Distributing components failed for deck " + name + ":" + strconv.Itoa(i) + ":" + err.Error())
			}
			if stack == nil {
				return errors.New("Distributing components failed for deck " + name + ":" + strconv.Itoa(i) + ": the delegate returned no stack.")
			}
			if stack.SlotsRemaining() < 1 {
				return errors.New("Distributing components failed for deck " + name + ":" + strconv.Itoa(i) + ": the stack the delegate returned had no more slots.")
			}
			stack.insertComponentAt(stack.effectiveIndex(NextSlotIndex), component)
		}
	}

	g.manager.delegate.FinishSetUp(stateCopy)

	if g.Modifiable() {

		//Save the initial state to DB.
		if err := g.manager.Storage().SaveGameAndCurrentState(g, stateCopy); err != nil {
			return errors.New("Storage failed: " + err.Error())
		}

		go g.mainLoop()
	}

	g.initalized = true

	//See if any fixup moves apply

	//TODO: test that fixup moves are applied at the beginning.

	move := g.manager.Delegate().ProposeFixUpMove(stateCopy)

	if move != nil {
		//We apply the move immediately. This ensures that when
		//DelayedError resolves, all of the fix up moves have been
		//applied.
		g.applyMove(move, true, 0)
	}

	return nil
}

//MainLoop should be run in a goroutine. It is what takes moves off of
//proposedMoves and applies them. It is the only method that may call
//applyMove.
func (g *Game) mainLoop() {

	for item := range g.proposedMoves {
		if item == nil {
			return
		}
		item.ch <- g.applyMove(item.move, false, 0)
		close(item.ch)
	}

}

//Modifiable returns true if this instantiation of the game can be modified.
//If false, this instantiation is read-only: attributes can be read, but not
//written. In practice this means moves cannot be made.
func (g *Game) Modifiable() bool {
	return g.modifiable
}

//PlayerMoves is a thin wrapper around GameManager.PlayerMoves, but with all
//of the moves set to the right defaults for the current state of this game.
func (g *Game) PlayerMoves() []Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.PlayerMoves()
	for _, move := range result {
		move.DefaultsForState(g.CurrentState())
	}
	return result
}

//FixUpMoves is a thin wrapper around GameManager.FixUpMoves, but with all
//of the moves set to the right defaults for the current state of this game.
func (g *Game) FixUpMoves() []Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.FixUpMoves()
	for _, move := range result {
		move.DefaultsForState(g.CurrentState())
	}
	return result

}

//PlayerMoveByName is a thin wrapper around GameManager.PlayerMoveByName, but
//with it set to the right defaults for the current state of this game.
func (g *Game) PlayerMoveByName(name string) Move {
	if !g.initalized {
		return nil
	}

	result := g.manager.PlayerMoveByName(name)

	if result == nil {
		return result
	}

	result.DefaultsForState(g.CurrentState())

	return result
}

//FixUpMoveByName is a thin wrapper around GameManager.FixUpMoveByName, but
//with it set to the right defaults for the current state of this game.
func (g *Game) FixUpMoveByName(name string) Move {

	if !g.initalized {
		return nil
	}

	result := g.manager.FixUpMoveByName(name)

	if result == nil {
		return result
	}

	result.DefaultsForState(g.CurrentState())

	return result
}

//Chest is the ComponentChest in use for this game.
func (g *Game) Chest() *ComponentChest {
	return g.manager.Chest()
}

//Refresh goes and sets this game object to reflect the current state of the
//underlying game in Storage. Basically, when you call manager.Game() you get
//a snapshot of the game in storage at that moment. If you believe that the
//underlying game in storage has been modified, calling Refresh() will re-load
//the snapshot, effectively. Most useful after calling ProposeMove() on a non-
//modifiable game.
func (g *Game) Refresh() {

	freshGame := g.manager.Game(g.Id())

	g.cachedCurrentState = nil
	g.version = freshGame.Version()
	g.finished = freshGame.Finished()
	g.winners = freshGame.Winners()

}

//ProposedMove is the way to propose a move to the game. DelayedError will
//return an error in the future if the move was unable to be applied, or nil
//if the move was applied successfully. DelayedError will only resolve once
//any applicable FixUp moves have been applied already. Note: DelayedError
//won't return anything until after SetUp has been called. This is legal to
//call on a non-modifiable game--the change will be dispatched to a modifiable
//version of the game with this ID. However, note that if you call it on a
//non-modifiable game, even once DelayedError has resolved, the original game
//will still represent its old state. If you wantt to see its current state,
//calling game.Refresh() after DelayedError has resolved should contain the
//move changes you proposed, if they were accepted (and of course potentially
//more moves if other moves were applied in the meantime).
func (g *Game) ProposeMove(move Move) DelayedError {

	if !g.Modifiable() {
		return g.manager.proposeMoveOnGame(g.Id(), move)
	}

	errChan := make(DelayedError, 1)

	workItem := &proposedMoveItem{
		move: move,
		ch:   errChan,
	}

	g.proposedMoves <- workItem

	return errChan

}

//Game applies the move to the state if it is currently legal. May only be
//called by mainLoop. Propose moves with game.ProposeMove instead.
func (g *Game) applyMove(move Move, isFixUp bool, recurseCount int) error {

	if !g.initalized {
		return errors.New("The game has not been initalized.")
	}

	if g.finished {
		return errors.New("Game was already finished")
	}

	if isFixUp {
		if g.manager.FixUpMoveByName(move.Name()) == nil {
			return errors.New("That move is not configured as a Fix Up move for this game.")
		}
	} else {

		//Verify that the Move is actually configured to be part of this game.
		if g.manager.PlayerMoveByName(move.Name()) == nil {
			return errors.New("That move is not configured as a Player move for this game.")
		}
	}

	currentState := g.CurrentState()

	if err := move.Legal(currentState); err != nil {
		//It's not legal, reject.
		return errors.New("The move was not legal: " + err.Error())
	}

	newState := currentState.Copy(false)

	if err := move.Apply(newState); err != nil {
		return errors.New("The move's apply function returned an error:" + err.Error())
	}

	//Check to see if that move made the game finished.

	finished, winners := g.manager.Delegate().CheckGameFinished(newState)

	if finished {
		g.finished = true
		g.winners = winners
		//TODO: persist to database here.
	}

	g.version = g.version + 1
	//Expire the currentState cache; it's no longer valid.
	g.cachedCurrentState = nil

	//TODO: test that if we fail to save state to storage everything's fine.
	if err := g.manager.Storage().SaveGameAndCurrentState(g, newState); err != nil {
		return errors.New("Storage returned an error:" + err.Error())
	}

	if recurseCount > maxRecurseCount {
		panic("We recursed deeply in fixup, which implies that ProposeFixUp has a move that is always legal. Quitting.")
	}

	move = g.manager.Delegate().ProposeFixUpMove(newState)

	if move != nil {
		//We apply the move immediately. This ensures that when
		//DelayedError resolves, all of the fix up moves have been
		//applied.
		g.applyMove(move, true, recurseCount+1)
	}

	return nil

}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"regexp"
	"strings"
)

var thesaurus map[string][]string
var cmudict map[string]int
var skipwords []string
var err error

func loadCmudict(path string) (map[string]int, error) {

	m := make(map[string]int, 140000)

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	scan := bufio.NewScanner(f)

	for scan.Scan() {
		s := scan.Text()
		s = strings.ToLower(s)
		if s[0] == ';' {
			// skip comments
			continue
		}

		// find first word
		idx := strings.Index(s, " ")
		w := s[0:idx]

		if w[idx-1] == ')' {
			w = w[:idx-3]
		}

		c := 0
		// count syllables == digits in remaining string
		for _, r := range s[idx:] {
			if r >= '0' && r <= '9' {
				c++
			}
		}

		m[w] = c
	}

	if err := scan.Err(); err != nil {
		return nil, err
	}

	return m, nil
}

func loadThesaurus(path string) (map[string][]string, error) {

	m := make(map[string][]string)

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	scan := bufio.NewScanner(f)

	for scan.Scan() {
		s := scan.Text()
		words := strings.Split(s, "|")
		if len(words) <= 2 {
			// skip if doesn't have syllables
			continue
		}

		for i, w := range words {
			if i > 1 {
				m[words[1]] = append(m[words[1]], w)
			}
		}
	}

	if err := scan.Err(); err != nil {
		return nil, err
	}

	return m, nil

}

func getSynonyms(w string) (possibilities []string) {
	// returns a list of synonyms that have a unique number of syllables
	possibilities = append(possibilities, w)

	for _, skipword := range skipwords {
		if w == skipword {
			return
		}
	}

	syllablesAccountedFor := make(map[int]string)
	for _, synonym := range thesaurus[w] {
		sylbls := cmudict[synonym]
		if sylbls > 0 {
			if _, ok := syllablesAccountedFor[sylbls]; ok {
				// pass
			} else {
				syllablesAccountedFor[sylbls] = synonym
			}
		}
	}

	for _, word := range syllablesAccountedFor {
		possibilities = append(possibilities, word)
	}
	return
}

type haikuCluster struct {
	haikus    []string
	isHaikus  []bool
	numHaikus int
}

func getHaikus(words []string) (haikus []string, isHaikus []bool, numHaikus int) {
	numHaikus = 0
	for {
		haikuString, i, gotHaiku := isHaiku(words)
		haikus = append(haikus, haikuString)
		isHaikus = append(isHaikus, gotHaiku)
		if gotHaiku == false {
			return
		} else {
			numHaikus = numHaikus + 1
		}

		words = words[i+1:]
	}
}

func isHaiku(words []string) (string, int, bool) {
	checks := []int{5, 7, 5}
	curCheck := 0
	runningTotal := 0
	currentHaiku := ""
	for i, word := range words {
		slbles := cmudict[word]
		runningTotal = runningTotal + slbles
		currentHaiku = currentHaiku + word + " "
		if runningTotal == checks[curCheck] {
			curCheck += 1
			if curCheck == 3 {
				return currentHaiku, i, true
			} else {
				currentHaiku = currentHaiku + "\n"

			}
			runningTotal = 0
		}
	}
	return currentHaiku, -1, false
}

func randChoices(limits []int) (choices []int) {
	for _, l := range limits {
		choices = append(choices, rand.Intn(l))
	}
	return
}

func listAlternates(input []string) (output [50][]string) {
	totals := make([]int, len(input))
	for i, w := range input {
		totals[i] = len(getSynonyms(w))
	}
	for i := 0; i < len(output); i++ {
		choices := randChoices(totals)
		for j, w := range input {
			output[i] = append(output[i], getSynonyms(w)[choices[j]])
		}
	}
	return
}

func init() {
	// initialize the thesaurs and the syllable dictionary

	thesaurus, err = loadThesaurus("./resources/th_en_US_new.dat")
	if err != nil {
		panic(err)
	}
	cmudict, err = loadCmudict("./resources/cmudict.0.7a")
	if err != nil {
		panic(err)
	}
	skipwords = []string{"a", "about", "above", "above", "across", "after", "afterwards", "again", "against", "all", "almost", "alone", "along", "already", "also", "although", "always", "am", "among", "amongst", "amoungst", "amount", "an", "and", "another", "any", "anyhow", "anyone", "anything", "anyway", "anywhere", "are", "around", "as", "at", "back", "be", "became", "because", "become", "becomes", "becoming", "been", "before", "beforehand", "behind", "being", "below", "beside", "besides", "between", "beyond", "bill", "both", "bottom", "but", "by", "call", "can", "cannot", "cant", "co", "con", "could", "couldnt", "cry", "de", "describe", "detail", "do", "done", "down", "due", "during", "each", "eg", "eight", "either", "eleven", "else", "elsewhere", "empty", "enough", "etc", "even", "ever", "every", "everyone", "everything", "everywhere", "except", "few", "fifteen", "fify", "fill", "find", "fire", "first", "five", "for", "former", "formerly", "forty", "found", "four", "from", "front", "full", "further", "get", "give", "go", "had", "has", "hasnt", "have", "he", "hence", "her", "here", "hereafter", "hereby", "herein", "hereupon", "hers", "herself", "him", "himself", "his", "how", "however", "hundred", "ie", "if", "in", "inc", "indeed", "interest", "into", "is", "it", "its", "itself", "keep", "last", "latter", "latterly", "least", "less", "ltd", "made", "many", "may", "me", "meanwhile", "might", "mill", "mine", "more", "moreover", "most", "mostly", "move", "much", "must", "my", "myself", "name", "namely", "neither", "never", "nevertheless", "next", "nine", "no", "nobody", "none", "noone", "nor", "not", "nothing", "now", "nowhere", "of", "off", "often", "on", "once", "one", "only", "onto", "or", "other", "others", "otherwise", "our", "ours", "ourselves", "out", "over", "own", "part", "per", "perhaps", "please", "put", "rather", "re", "same", "see", "seem", "seemed", "seeming", "seems", "serious", "several", "she", "should", "show", "side", "since", "sincere", "six", "sixty", "so", "some", "somehow", "someone", "something", "sometime", "sometimes", "somewhere", "still", "such", "system", "take", "ten", "than", "that", "the", "their", "them", "themselves", "then", "thence", "there", "thereafter", "thereby", "therefore", "therein", "thereupon", "these", "they", "thickv", "thin", "third", "this", "those", "though", "three", "through", "throughout", "thru", "thus", "to", "together", "too", "top", "toward", "towards", "twelve", "twenty", "two", "un", "under", "until", "up", "upon", "us", "very", "via", "was", "we", "well", "were", "what", "whatever", "when", "whence", "whenever", "where", "whereafter", "whereas", "whereby", "wherein", "whereupon", "wherever", "whether", "which", "while", "whither", "who", "whoever", "whole", "whom", "whose", "why", "will", "with", "within", "without", "would", "yet", "you", "your", "yours", "yourself", "yourselves", "the"}

}

func main() {
	// words := strings.Split(`want to play a game with seventeen syllables we write some poem want to play a game with seventeen syllables we write some poem something else`, " ")
	// fmt.Println(getHaikus(words))
	//input := []string{"cat", "is", "nice"}
	sentence := `This is the sixth time we have had the pleasure of writing a birthday blog post for Go, and we would not be doing so if not for the wonderful and passionate people in our community. The Go team would like to thank everyone who has contributed code, written an open source library, authored a blog post, helped a new gopher, or just given Go a try. `
	fmt.Println("ORIGINAL:")
	fmt.Println(sentence)
	sentence = strings.ToLower(sentence)
	sentence = strings.Replace(sentence, "don't", "do not", -1)
	sentence = strings.Replace(sentence, "'", "", -1)
	words := regexp.MustCompile(`(\w+)`).FindAllString(sentence, -1)
	alternatives := listAlternates(words)
	bestNum := 0
	var bestHaikus []string
	var bestIsHaikus []bool

	for i := 0; i < len(alternatives); i++ {
		for j := 0; j < 3; j++ {
			haikuString, isHaikus, numHaikus := getHaikus(alternatives[i][j:])
			if numHaikus > bestNum {
				if j > 0 {
					haikuString = append([]string{strings.Join(alternatives[i][:j], " ")}, haikuString...)
					isHaikus = append([]bool{false}, isHaikus...)
				}
				bestHaikus = haikuString
				bestIsHaikus = isHaikus
			}
		}
	}
	fmt.Println("\n\nBEST HAIKU:")
	for i, bestHaiku := range bestHaikus {
		if bestIsHaikus[i] == true {
			fmt.Printf("\n")
		}
		fmt.Println(bestHaiku)
		if bestIsHaikus[i] == true {
			fmt.Printf("\n")
		}
	}

	// Todo: replace each word with the puncuation near the word in the original

}
<commit_msg>Increase alternatives. Make them not random<commit_after>package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"regexp"
	"strings"
)

var thesaurus map[string][]string
var cmudict map[string]int
var skipwords []string
var err error

func loadCmudict(path string) (map[string]int, error) {

	m := make(map[string]int, 140000)

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	scan := bufio.NewScanner(f)

	for scan.Scan() {
		s := scan.Text()
		s = strings.ToLower(s)
		if s[0] == ';' {
			// skip comments
			continue
		}

		// find first word
		idx := strings.Index(s, " ")
		w := s[0:idx]

		if w[idx-1] == ')' {
			w = w[:idx-3]
		}

		c := 0
		// count syllables == digits in remaining string
		for _, r := range s[idx:] {
			if r >= '0' && r <= '9' {
				c++
			}
		}

		m[w] = c
	}

	if err := scan.Err(); err != nil {
		return nil, err
	}

	return m, nil
}

func loadThesaurus(path string) (map[string][]string, error) {

	m := make(map[string][]string)

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	scan := bufio.NewScanner(f)

	for scan.Scan() {
		s := scan.Text()
		words := strings.Split(s, "|")
		if len(words) <= 2 {
			// skip if doesn't have syllables
			continue
		}

		for i, w := range words {
			if i > 1 {
				m[words[1]] = append(m[words[1]], w)
			}
		}
	}

	if err := scan.Err(); err != nil {
		return nil, err
	}

	return m, nil

}

func getSynonyms(w string) (possibilities []string) {
	// returns a list of synonyms that have a unique number of syllables
	possibilities = append(possibilities, w)

	for _, skipword := range skipwords {
		if w == skipword {
			return
		}
	}

	syllablesAccountedFor := make(map[int]string)
	for _, synonym := range thesaurus[w] {
		sylbls := cmudict[synonym]
		if sylbls > 0 {
			if _, ok := syllablesAccountedFor[sylbls]; ok {
				// pass
			} else {
				syllablesAccountedFor[sylbls] = synonym
			}
		}
	}

	for _, word := range syllablesAccountedFor {
		possibilities = append(possibilities, word)
	}
	return
}

type haikuCluster struct {
	haikus    []string
	isHaikus  []bool
	numHaikus int
}

func getHaikus(words []string) (haikus []string, isHaikus []bool, numHaikus int) {
	numHaikus = 0
	for {
		haikuString, i, gotHaiku := isHaiku(words)
		haikus = append(haikus, haikuString)
		isHaikus = append(isHaikus, gotHaiku)
		if gotHaiku == false {
			return
		} else {
			numHaikus = numHaikus + 1
		}

		words = words[i+1:]
	}
}

func isHaiku(words []string) (string, int, bool) {
	checks := []int{5, 7, 5}
	curCheck := 0
	runningTotal := 0
	currentHaiku := ""
	for i, word := range words {
		slbles := cmudict[word]
		runningTotal = runningTotal + slbles
		currentHaiku = currentHaiku + word + " "
		if runningTotal == checks[curCheck] {
			curCheck += 1
			if curCheck == 3 {
				return currentHaiku, i, true
			} else {
				currentHaiku = currentHaiku + "\n"

			}
			runningTotal = 0
		}
	}
	return currentHaiku, -1, false
}

func randChoices(limits []int) (choices []int) {
	for _, l := range limits {
		choices = append(choices, rand.Intn(l))
	}
	return
}

func listAlternates(input []string) (output [10000][]string) {
	totals := make([]int, len(input))
	for i, w := range input {
		totals[i] = len(getSynonyms(w))
	}
	for i := 0; i < len(output); i++ {
		choices := randChoices(totals)
		for j, w := range input {
			output[i] = append(output[i], getSynonyms(w)[choices[j]])
		}
	}
	return
}

func init() {
	// initialize the thesaurs and the syllable dictionary

	thesaurus, err = loadThesaurus("./resources/th_en_US_new.dat")
	if err != nil {
		panic(err)
	}
	cmudict, err = loadCmudict("./resources/cmudict.0.7a")
	if err != nil {
		panic(err)
	}
	skipwords = []string{"a", "about", "above", "above", "across", "after", "afterwards", "again", "against", "all", "almost", "alone", "along", "already", "also", "although", "always", "am", "among", "amongst", "amoungst", "amount", "an", "and", "another", "any", "anyhow", "anyone", "anything", "anyway", "anywhere", "are", "around", "as", "at", "back", "be", "became", "because", "become", "becomes", "becoming", "been", "before", "beforehand", "behind", "being", "below", "beside", "besides", "between", "beyond", "bill", "both", "bottom", "but", "by", "call", "can", "cannot", "cant", "co", "con", "could", "couldnt", "cry", "de", "describe", "detail", "do", "done", "down", "due", "during", "each", "eg", "eight", "either", "eleven", "else", "elsewhere", "empty", "enough", "etc", "even", "ever", "every", "everyone", "everything", "everywhere", "except", "few", "fifteen", "fify", "fill", "find", "fire", "first", "five", "for", "former", "formerly", "forty", "found", "four", "from", "front", "full", "further", "get", "give", "go", "had", "has", "hasnt", "have", "he", "hence", "her", "here", "hereafter", "hereby", "herein", "hereupon", "hers", "herself", "him", "himself", "his", "how", "however", "hundred", "ie", "if", "in", "inc", "indeed", "interest", "into", "is", "it", "its", "itself", "keep", "last", "latter", "latterly", "least", "less", "ltd", "made", "many", "may", "me", "meanwhile", "might", "mill", "mine", "more", "moreover", "most", "mostly", "move", "much", "must", "my", "myself", "name", "namely", "neither", "never", "nevertheless", "next", "nine", "no", "nobody", "none", "noone", "nor", "not", "nothing", "now", "nowhere", "of", "off", "often", "on", "once", "one", "only", "onto", "or", "other", "others", "otherwise", "our", "ours", "ourselves", "out", "over", "own", "part", "per", "perhaps", "please", "put", "rather", "re", "same", "see", "seem", "seemed", "seeming", "seems", "serious", "several", "she", "should", "show", "side", "since", "sincere", "six", "sixty", "so", "some", "somehow", "someone", "something", "sometime", "sometimes", "somewhere", "still", "such", "system", "take", "ten", "than", "that", "the", "their", "them", "themselves", "then", "thence", "there", "thereafter", "thereby", "therefore", "therein", "thereupon", "these", "they", "thickv", "thin", "third", "this", "those", "though", "three", "through", "throughout", "thru", "thus", "to", "together", "too", "top", "toward", "towards", "twelve", "twenty", "two", "un", "under", "until", "up", "upon", "us", "very", "via", "was", "we", "well", "were", "what", "whatever", "when", "whence", "whenever", "where", "whereafter", "whereas", "whereby", "wherein", "whereupon", "wherever", "whether", "which", "while", "whither", "who", "whoever", "whole", "whom", "whose", "why", "will", "with", "within", "without", "would", "yet", "you", "your", "yours", "yourself", "yourselves", "the"}

}

func main() {
	// words := strings.Split(`want to play a game with seventeen syllables we write some poem want to play a game with seventeen syllables we write some poem something else`, " ")
	// fmt.Println(getHaikus(words))
	//input := []string{"cat", "is", "nice"}
	sentence := `This is the sixth time we have had the pleasure of writing a birthday blog post for Go, and we would not be doing so if not for the wonderful and passionate people in our community. The Go team would like to thank everyone who has contributed code, written an open source library, authored a blog post, helped a new gopher, or just given Go a try. `
	fmt.Println("ORIGINAL:")
	fmt.Println(sentence)
	sentence = strings.ToLower(sentence)
	sentence = strings.Replace(sentence, "don't", "do not", -1)
	sentence = strings.Replace(sentence, "'", "", -1)
	words := regexp.MustCompile(`(\w+)`).FindAllString(sentence, -1)
	alternatives := listAlternates(words)
	bestNum := 0
	var bestHaikus []string
	var bestIsHaikus []bool

	for i := 0; i < len(alternatives); i++ {
		for j := 0; j < 3; j++ {
			haikuString, isHaikus, numHaikus := getHaikus(alternatives[i][j:])
			if numHaikus > bestNum {
				if j > 0 {
					haikuString = append([]string{strings.Join(alternatives[i][:j], " ")}, haikuString...)
					isHaikus = append([]bool{false}, isHaikus...)
				}
				bestHaikus = haikuString
				bestIsHaikus = isHaikus
			}
		}
	}
	fmt.Println("\n\nBEST HAIKU:")
	for i, bestHaiku := range bestHaikus {
		if bestIsHaikus[i] == true {
			fmt.Printf("\n")
		}
		fmt.Println(bestHaiku)
		if bestIsHaikus[i] == true {
			fmt.Printf("\n")
		}
	}

	// Todo: replace each word with the puncuation near the word in the original

}
<|endoftext|>


<commit_before>package hashring

/*
#cgo LDFLAGS: -lhashring

#include <stdlib.h>
#include <hash_ring.h>
*/
import "C"

import "unsafe"
import "sync"

const (
	MD5 = C.HASH_FUNCTION_MD5
)

type Ring struct {
	ptr *C.hash_ring_t
    sync.RWMutex
}

func New(numReplicas int, fn C.HASH_FUNCTION) *Ring {
	return &Ring{ptr:C.hash_ring_create(C.uint32_t(numReplicas), fn)}
}

func (r *Ring) Add(node []byte) {
	r.Lock()
	defer r.Unlock()
	C.hash_ring_add_node(r.ptr, (*C.uint8_t)(&node[0]), C.uint32_t(len(node)))
}

func (r *Ring) Remove(node []byte) {
	r.Lock()
	defer r.Unlock()
	C.hash_ring_remove_node(r.ptr, (*C.uint8_t)(&node[0]), C.uint32_t(len(node)))
}

func (r *Ring) Print() {
	r.RLock()
	defer r.RUnlock()
	C.hash_ring_print(r.ptr)
}

func (r *Ring) FindNode(key []byte) []byte {
	r.RLock()
	defer r.RUnlock()
	var s *C.hash_ring_node_t
	s = C.hash_ring_find_node(r.ptr, (*C.uint8_t)(&key[0]), C.uint32_t(len(key)))
	return C.GoBytes(unsafe.Pointer(s.name), C.int(s.nameLen))
}

func (r *Ring) Free() {
	r.Lock()
	defer r.Unlock()
	C.hash_ring_free(r.ptr)
}
<commit_msg>add some documentation<commit_after>/*

Golang client wrapper for the C consistent hash ring library http://chrismoos.com

Example:

	ring := hashring.New(128, hashring.MD5)
	defer ring.Free()

	r.Add([]byte("node1"))
	r.Add([]byte("node2"))

	node := string(r.FindNode([]byte("key")))
*/
package hashring

/*
#cgo LDFLAGS: -lhashring

#include <stdlib.h>
#include <hash_ring.h>
*/
import "C"

import "unsafe"
import "sync"

const (
	MD5  = C.HASH_FUNCTION_MD5
	SHA1 = C.HASH_FUNCTION_SHA1
)

type Ring struct {
	ptr *C.hash_ring_t
    sync.RWMutex
}

// Construct a new hash ring. fn is MD5 or SHA1
func New(numReplicas int, fn C.HASH_FUNCTION) *Ring {
	return &Ring{ptr:C.hash_ring_create(C.uint32_t(numReplicas), fn)}
}

// Add a node to the ring
func (r *Ring) Add(node []byte) {
	r.Lock()
	defer r.Unlock()
	C.hash_ring_add_node(r.ptr, (*C.uint8_t)(&node[0]), C.uint32_t(len(node)))
}

// Remove a node from the ring
func (r *Ring) Remove(node []byte) {
	r.Lock()
	defer r.Unlock()
	C.hash_ring_remove_node(r.ptr, (*C.uint8_t)(&node[0]), C.uint32_t(len(node)))
}

// Print the members of the ring to stdout
func (r *Ring) Print() {
	r.RLock()
	defer r.RUnlock()
	C.hash_ring_print(r.ptr)
}

// Find the node for a given key
func (r *Ring) FindNode(key []byte) []byte {
	r.RLock()
	defer r.RUnlock()
	var s *C.hash_ring_node_t
	s = C.hash_ring_find_node(r.ptr, (*C.uint8_t)(&key[0]), C.uint32_t(len(key)))
	return C.GoBytes(unsafe.Pointer(s.name), C.int(s.nameLen))
}

// Cleanly dispose of the ring
func (r *Ring) Free() {
	r.Lock()
	defer r.Unlock()
	C.hash_ring_free(r.ptr)
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package server

import (
	"io/fs"
	"os"

	"github.com/issue9/cache"
	"github.com/issue9/sliceutil"
)

type Module struct {
	srv *Server
	id  string
	fs  []fs.FS
}

// NewModule 声明新的模块
//
// id 模块的 ID，需要全局唯一，且要符合 fs.ValidPath 的要求。
func (srv *Server) NewModule(id string) *Module {
	if !fs.ValidPath(id) {
		panic("无效的 id 格式。")
	}

	index := sliceutil.Index(srv.modules, func(e string) bool { return e == id })
	if index >= 0 {
		panic("存在同名模块：" + id)
	}

	f := make([]fs.FS, 0, 2)
	if fsys, err := fs.Sub(srv, id); err != nil {
		srv.Logs().Error(err) // 不退出
	} else {
		f = append(f, fsys)
	}

	srv.modules = append(srv.modules, id)
	return &Module{
		srv: srv,
		fs:  f,
		id:  id,
	}
}

// NewModule 声明 id 值为 m.ID + "/" + id 的新模块
func (m *Module) NewModule(id string) *Module {
	return m.Server().NewModule(m.ID() + "/" + id)
}

// ID 模块的唯一 ID
func (m *Module) ID() string { return m.id }

func (m *Module) Server() *Server { return m.srv }

// AddFS 添加文件系统
//
// Module 默认以 id 为名称相对于 Server 创建了一个文件系统，
// 此操作会将 fsys 作为 Module 另一个文件系统与 Module 相关联，
// 当执行 Open 等操作时，会依然以关联顺序查找相应的文件系统，直到找到。
//
// 需要注意的是，fs.Glob 不是搜索所有的 fsys 然后返回集合。
func (m *Module) AddFS(fsys ...fs.FS) { m.fs = append(m.fs, fsys...) }

func (m *Module) Open(name string) (fs.File, error) {
	for _, fsys := range m.fs {
		if existsFS(fsys, name) {
			return fsys.Open(name)
		}
	}
	return nil, fs.ErrNotExist
}

func (m *Module) Glob(pattern string) ([]string, error) {
	for _, fsys := range m.fs {
		if matches, err := fs.Glob(fsys, pattern); len(matches) > 0 {
			return matches, err
		}
	}
	return nil, nil
}

// Cache 获取缓存对象
//
// 该缓存对象的 key 会自动添加 Module.ID 作为其前缀。
func (m *Module) Cache() cache.Access {
	return cache.Prefix(m.ID(), m.Server().Cache())
}

func existsFS(fsys fs.FS, p string) bool {
	_, err := fs.Stat(fsys, p)
	return err == nil || os.IsExist(err)
}
<commit_msg>refactor(server): 采用 sliceutil.Exists 代替 sliceutil.Index<commit_after>// SPDX-License-Identifier: MIT

package server

import (
	"io/fs"
	"os"

	"github.com/issue9/cache"
	"github.com/issue9/sliceutil"
)

type Module struct {
	srv *Server
	id  string
	fs  []fs.FS
}

// NewModule 声明新的模块
//
// id 模块的 ID，需要全局唯一，且要符合 fs.ValidPath 的要求。
func (srv *Server) NewModule(id string) *Module {
	if !fs.ValidPath(id) {
		panic("无效的 id 格式。")
	}

	if sliceutil.Exists(srv.modules, func(e string) bool { return e == id }) {
		panic("存在同名模块：" + id)
	}

	f := make([]fs.FS, 0, 2)
	if fsys, err := fs.Sub(srv, id); err != nil {
		srv.Logs().Error(err) // 不退出
	} else {
		f = append(f, fsys)
	}

	srv.modules = append(srv.modules, id)
	return &Module{
		srv: srv,
		fs:  f,
		id:  id,
	}
}

// NewModule 声明 id 值为 m.ID + "/" + id 的新模块
func (m *Module) NewModule(id string) *Module {
	return m.Server().NewModule(m.ID() + "/" + id)
}

// ID 模块的唯一 ID
func (m *Module) ID() string { return m.id }

func (m *Module) Server() *Server { return m.srv }

// AddFS 添加文件系统
//
// Module 默认以 id 为名称相对于 Server 创建了一个文件系统，
// 此操作会将 fsys 作为 Module 另一个文件系统与 Module 相关联，
// 当执行 Open 等操作时，会依然以关联顺序查找相应的文件系统，直到找到。
//
// 需要注意的是，fs.Glob 不是搜索所有的 fsys 然后返回集合。
func (m *Module) AddFS(fsys ...fs.FS) { m.fs = append(m.fs, fsys...) }

func (m *Module) Open(name string) (fs.File, error) {
	for _, fsys := range m.fs {
		if existsFS(fsys, name) {
			return fsys.Open(name)
		}
	}
	return nil, fs.ErrNotExist
}

func (m *Module) Glob(pattern string) ([]string, error) {
	for _, fsys := range m.fs {
		if matches, err := fs.Glob(fsys, pattern); len(matches) > 0 {
			return matches, err
		}
	}
	return nil, nil
}

// Cache 获取缓存对象
//
// 该缓存对象的 key 会自动添加 Module.ID 作为其前缀。
func (m *Module) Cache() cache.Access {
	return cache.Prefix(m.ID(), m.Server().Cache())
}

func existsFS(fsys fs.FS, p string) bool {
	_, err := fs.Stat(fsys, p)
	return err == nil || os.IsExist(err)
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"net/http"
	"screen-server/log"
	"screen-server/models"

	l4g "github.com/alecthomas/log4go"
	"github.com/ant0ine/go-json-rest/rest"
)

var (
	//Resources make server's resource list
	Resources = models.ResourceMap{}

	//Layouts make server's layout list
	Layouts = models.Layouts{}

	ChangedLayout = models.ChangedLayout{}

	CurrentLayout = models.Layout{}

	UpdateLayout = models.UpdateLayout{}

	Mongo DBImpl

	tokens = make(map[string]models.Token)

	clients = models.Clients{
		make(map[string]models.Client),
	}
)

//Mock make mock data
// func Mock() {
// 	Resources.Mock()
// 	Layouts.Mock()
// }

func setHeartbeatHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T2-[Header]", r.Header)
	c := models.Client{}
	err := r.DecodeJsonPayload(&c)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteJson(&c)
}

//getLayoutsHandler makes T3
//last update 2015-3-26
//by tommy
func getLayoutsHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("GET /T3-[Header]", r.Header)
	w.WriteJson(Layouts.Store)
}

//getResourceHandler makes T4
//last update 2015-3-26
//by tommy
func getResourceHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("GET /T4-[Header]", r.Header)
	w.WriteJson(Resources.Store)
}

//setCurrentLayoutHandler makes T5
//last update 2015-3-27
//by tommy
func setCurrentLayoutHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T5-[Header]", r.Header)
	c := models.ChangedLayout{}
	err := r.DecodeJsonPayload(&c)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if !Layouts.IsExistLayout(c.ID) {
		rest.Error(w, "server is currently no layout", http.StatusNotFound)
		return
	}

	if !clients.IsExistDisplay() {
		rest.Error(w, "display has not been connected to the server", http.StatusMethodNotAllowed)
		return
	}

	ChangedLayout = c

	server.BroadcastTo("warroom", "LayoutChanged", &c)

	var jsonStr = []byte(`{"id":"` + c.ID + `"}`)
	client := &http.Client{}
	req, _ := http.NewRequest("POST", Settings.ServiceUrl[0], bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")
	_, err = client.Do(req)
	check(err)

	w.WriteJson(&c)
}

//setLayoutHandler makes T6
//last update 2015-3-26
//by tommy
func setLayoutHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T6-[Header]", r.Header)
	t := models.Token{}

	err := r.DecodeJsonPayload(&t)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	_, ok := tokens[t.Token]

	if ok {
		rest.Error(w, "duplicate submission", http.StatusConflict)
		return
	}

	tokens[t.Token] = t

	if Mongo.DB != nil {
		Layouts.Load(Mongo.DB)
		logger.Log.Info("[updateLayoutHandler]:layout updated")
	} else {
		logger.Log.Error("[updateLayoutHandler]:connect to mongodb failed")
	}

	server.BroadcastTo("warroom", "LayoutUpdated", &t)

	delete(tokens, t.Token)
}

//setResourceHandler makes T7
//last update 2015-3-26
//by tommy
func setResourceHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T7-[Header]", r.Header)
	t := models.Token{}

	err := r.DecodeJsonPayload(&t)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	_, ok := tokens[t.Token]

	if ok {
		rest.Error(w, "duplicate submission", http.StatusConflict)
		return
	}

	tokens[t.Token] = t

	if Mongo.DB != nil {
		Resources.Load(Mongo.DB)
		logger.Log.Info("[setResourceHandler]:resource updated")
	} else {
		logger.Log.Error("[setResourceHandler]:connect to mongodb failed")
	}

	server.BroadcastTo("warroom", "ResourceUpdated", &t)

	delete(tokens, t.Token)
	w.WriteJson(t.Token)
}

//notifyErrorHandler makes T8
//last update 2015-3-26
//by tommy
func notifyErrorHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T8-[Header]", r.Header)
	error := models.Error{}
	err := r.DecodeJsonPayload(&error)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	server.BroadcastTo("warroom", "ErrorNotify", &error)
	w.WriteJson(&error)
}

//updateLayoutGridsHandler makes T9
//last update 2015-3-27
//by tommy
func updateLayoutResourceHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("PATCH /T9-[Header]", r.Header)
	id := r.PathParam("id")

	if CurrentLayout.ID != id {
		rest.Error(w, "layout not found by id", http.StatusNotFound)
		return
	}

	err := r.DecodeJsonPayload(&UpdateLayout)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	UpdateLayout.ID = id

	server.BroadcastTo("warroom", "LayoutResourceUpdated", &UpdateLayout)
	w.WriteJson(&UpdateLayout)
}

//updateCurrentLayoutHandler makes T10
//last update 2015-3-27
//by tommy
func updateCurrentLayoutHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("PATCH /T10-[Header]", r.Header)
	d := models.Layout{}

	err := r.DecodeJsonPayload(&d)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if CurrentLayout.ID != d.ID {
		rest.Error(w, "current layout not found", http.StatusNotFound)
	}

	CurrentLayout = d
	server.BroadcastTo("warroom", "CurrentLayoutUpdated", &CurrentLayout)

	w.WriteJson(&CurrentLayout)
}
<commit_msg>update<commit_after>package server

import (
	"bytes"
	"net/http"
	"screen-server/log"
	"screen-server/models"

	l4g "github.com/alecthomas/log4go"
	"github.com/ant0ine/go-json-rest/rest"
)

var (
	//Resources make server's resource list
	Resources = models.ResourceMap{}

	//Layouts make server's layout list
	Layouts = models.Layouts{}

	ChangedLayout = models.ChangedLayout{}

	CurrentLayout = models.Layout{}

	UpdateLayout = models.UpdateLayout{}

	Mongo DBImpl

	tokens = make(map[string]models.Token)

	clients = models.Clients{
		make(map[string]models.Client),
	}
)

//Mock make mock data
// func Mock() {
// 	Resources.Mock()
// 	Layouts.Mock()
// }

func setHeartbeatHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T2-[Header]", r.Header)
	c := models.Client{}
	err := r.DecodeJsonPayload(&c)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteJson(&c)
}

//getLayoutsHandler makes T3
//last update 2015-3-26
//by tommy
func getLayoutsHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("GET /T3-[Header]", r.Header)
	w.WriteJson(Layouts.Store)
}

//getResourceHandler makes T4
//last update 2015-3-26
//by tommy
func getResourceHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("GET /T4-[Header]", r.Header)
	w.WriteJson(Resources.Store)
}

//setCurrentLayoutHandler makes T5
//last update 2015-3-27
//by tommy
func setCurrentLayoutHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T5-[Header]", r.Header)
	c := models.ChangedLayout{}
	err := r.DecodeJsonPayload(&c)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if !Layouts.IsExistLayout(c.ID) {
		rest.Error(w, "server is currently no layout", http.StatusNotFound)
		return
	}

	if !clients.IsExistDisplay() {
		rest.Error(w, "display has not been connected to the server", http.StatusMethodNotAllowed)
		return
	}

	ChangedLayout = c

	server.BroadcastTo("warroom", "LayoutChanged", &c)

	w.WriteJson(&c)

	var jsonStr = []byte(`{"id":"` + c.ID + `"}`)
	client := &http.Client{}
	req, _ := http.NewRequest("POST", Settings.ServiceUrl[0], bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")
	_, err = client.Do(req)
	check(err)
}

//setLayoutHandler makes T6
//last update 2015-3-26
//by tommy
func setLayoutHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T6-[Header]", r.Header)
	t := models.Token{}

	err := r.DecodeJsonPayload(&t)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	_, ok := tokens[t.Token]

	if ok {
		rest.Error(w, "duplicate submission", http.StatusConflict)
		return
	}

	tokens[t.Token] = t

	if Mongo.DB != nil {
		Layouts.Load(Mongo.DB)
		logger.Log.Info("[updateLayoutHandler]:layout updated")
	} else {
		logger.Log.Error("[updateLayoutHandler]:connect to mongodb failed")
	}

	server.BroadcastTo("warroom", "LayoutUpdated", &t)

	delete(tokens, t.Token)
}

//setResourceHandler makes T7
//last update 2015-3-26
//by tommy
func setResourceHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T7-[Header]", r.Header)
	t := models.Token{}

	err := r.DecodeJsonPayload(&t)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	_, ok := tokens[t.Token]

	if ok {
		rest.Error(w, "duplicate submission", http.StatusConflict)
		return
	}

	tokens[t.Token] = t

	if Mongo.DB != nil {
		Resources.Load(Mongo.DB)
		logger.Log.Info("[setResourceHandler]:resource updated")
	} else {
		logger.Log.Error("[setResourceHandler]:connect to mongodb failed")
	}

	server.BroadcastTo("warroom", "ResourceUpdated", &t)

	delete(tokens, t.Token)
	w.WriteJson(t.Token)
}

//notifyErrorHandler makes T8
//last update 2015-3-26
//by tommy
func notifyErrorHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("POST /T8-[Header]", r.Header)
	error := models.Error{}
	err := r.DecodeJsonPayload(&error)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	server.BroadcastTo("warroom", "ErrorNotify", &error)
	w.WriteJson(&error)
}

//updateLayoutGridsHandler makes T9
//last update 2015-3-27
//by tommy
func updateLayoutResourceHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("PATCH /T9-[Header]", r.Header)
	id := r.PathParam("id")

	if CurrentLayout.ID != id {
		rest.Error(w, "layout not found by id", http.StatusNotFound)
		return
	}

	err := r.DecodeJsonPayload(&UpdateLayout)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	UpdateLayout.ID = id

	server.BroadcastTo("warroom", "LayoutResourceUpdated", &UpdateLayout)
	w.WriteJson(&UpdateLayout)
}

//updateCurrentLayoutHandler makes T10
//last update 2015-3-27
//by tommy
func updateCurrentLayoutHandler(w rest.ResponseWriter, r *rest.Request) {
	l4g.Debug("PATCH /T10-[Header]", r.Header)
	d := models.Layout{}

	err := r.DecodeJsonPayload(&d)

	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if CurrentLayout.ID != d.ID {
		rest.Error(w, "current layout not found", http.StatusNotFound)
	}

	CurrentLayout = d
	server.BroadcastTo("warroom", "CurrentLayoutUpdated", &CurrentLayout)

	w.WriteJson(&CurrentLayout)
}
<|endoftext|>


<commit_before>// Copyright 2012-2015 Apcera Inc. All rights reserved.

package test

import (
	"bufio"
	"fmt"
	"math/rand"
	"net"
	"testing"
	"time"

	"github.com/nats-io/gnatsd/server"
)

const PERF_PORT = 8422

// For Go routine based server.
func runBenchServer() *server.Server {
	opts := DefaultTestOptions
	opts.Port = PERF_PORT
	return RunServer(&opts)
}

const defaultRecBufSize = 32768
const defaultSendBufSize = 32768

func flushConnection(b *testing.B, c net.Conn) {
	buf := make([]byte, 32)
	c.Write([]byte("PING\r\n"))
	c.SetReadDeadline(time.Now().Add(1 * time.Second))
	n, err := c.Read(buf)
	c.SetReadDeadline(time.Time{})
	if err != nil {
		b.Fatalf("Failed read: %v\n", err)
	}
	if n != 6 && buf[0] != 'P' && buf[1] != 'O' {
		b.Fatalf("Failed read of PONG: %s\n", buf)
	}
}

func benchPub(b *testing.B, subject, payload string) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB %s %d\r\n%s\r\n", subject, len(payload), payload))
	b.SetBytes(int64(len(sendOp)))
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		bw.Write(sendOp)
	}
	bw.Flush()
	flushConnection(b, c)
	b.StopTimer()
	c.Close()
	s.Shutdown()
}

var ch = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$#%^&*()")

func sizedBytes(sz int) []byte {
	b := make([]byte, sz)
	for i := range b {
		b[i] = ch[rand.Intn(len(ch))]
	}
	return b
}

func sizedString(sz int) string {
	return string(sizedBytes(sz))
}

func Benchmark____PubNo_Payload(b *testing.B) {
	benchPub(b, "a", "")
}

func Benchmark____Pub8b_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(8)
	benchPub(b, "a", s)
}

func Benchmark___Pub32b_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(32)
	benchPub(b, "a", s)
}

func Benchmark__Pub256B_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(256)
	benchPub(b, "a", s)
}

func Benchmark____Pub1K_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(1024)
	benchPub(b, "a", s)
}

func Benchmark____Pub4K_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(4 * 1024)
	benchPub(b, "a", s)
}

func Benchmark____Pub8K_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(8 * 1024)
	benchPub(b, "a", s)
}

func drainConnection(b *testing.B, c net.Conn, ch chan bool, expected int) {
	buf := make([]byte, defaultRecBufSize)
	bytes := 0

	for {
		c.SetReadDeadline(time.Now().Add(5 * time.Second))
		n, err := c.Read(buf)
		if err != nil {
			b.Errorf("Error on read: %v\n", err)
			break
		}
		bytes += n
		if bytes >= expected {
			break
		}
	}
	if bytes != expected {
		b.Errorf("Did not receive all bytes: %d vs %d\n", bytes, expected)
	}
	ch <- true
}

func Benchmark___________PubSub(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	sendProto(b, c, "SUB foo 1\r\n")
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)
	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c, ch, expected)
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		_, err := bw.Write(sendOp)
		if err != nil {
			b.Errorf("Received error on PUB write: %v\n", err)
		}
	}
	err := bw.Flush()
	if err != nil {
		b.Errorf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	s.Shutdown()
}

func Benchmark___PubSubTwoConns(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	bw := bufio.NewWriterSize(c, defaultSendBufSize)

	c2 := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c2)
	sendProto(b, c2, "SUB foo 1\r\n")
	flushConnection(b, c2)

	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)

	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c2, ch, expected)

	b.StartTimer()
	for i := 0; i < b.N; i++ {
		bw.Write(sendOp)
	}
	err := bw.Flush()
	if err != nil {
		b.Errorf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	c2.Close()
	s.Shutdown()
}

func Benchmark___PubTwoQueueSub(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	sendProto(b, c, "SUB foo group1 1\r\n")
	sendProto(b, c, "SUB foo group1 2\r\n")
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)
	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c, ch, expected)
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		_, err := bw.Write(sendOp)
		if err != nil {
			b.Fatalf("Received error on PUB write: %v\n", err)
		}
	}
	err := bw.Flush()
	if err != nil {
		b.Fatalf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	s.Shutdown()
}

func Benchmark__PubFourQueueSub(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	sendProto(b, c, "SUB foo group1 1\r\n")
	sendProto(b, c, "SUB foo group1 2\r\n")
	sendProto(b, c, "SUB foo group1 3\r\n")
	sendProto(b, c, "SUB foo group1 4\r\n")
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)
	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c, ch, expected)
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		_, err := bw.Write(sendOp)
		if err != nil {
			b.Fatalf("Received error on PUB write: %v\n", err)
		}
	}
	err := bw.Flush()
	if err != nil {
		b.Fatalf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	s.Shutdown()
}

func Benchmark_PubEightQueueSub(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	sendProto(b, c, "SUB foo group1 1\r\n")
	sendProto(b, c, "SUB foo group1 2\r\n")
	sendProto(b, c, "SUB foo group1 3\r\n")
	sendProto(b, c, "SUB foo group1 4\r\n")
	sendProto(b, c, "SUB foo group1 5\r\n")
	sendProto(b, c, "SUB foo group1 6\r\n")
	sendProto(b, c, "SUB foo group1 7\r\n")
	sendProto(b, c, "SUB foo group1 8\r\n")
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)
	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c, ch, expected)
	b.ReportAllocs()
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		_, err := bw.Write(sendOp)
		if err != nil {
			b.Fatalf("Received error on PUB write: %v\n", err)
		}
	}
	err := bw.Flush()
	if err != nil {
		b.Fatalf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	s.Shutdown()
}
<commit_msg>remove alloc tracking<commit_after>// Copyright 2012-2015 Apcera Inc. All rights reserved.

package test

import (
	"bufio"
	"fmt"
	"math/rand"
	"net"
	"testing"
	"time"

	"github.com/nats-io/gnatsd/server"
)

const PERF_PORT = 8422

// For Go routine based server.
func runBenchServer() *server.Server {
	opts := DefaultTestOptions
	opts.Port = PERF_PORT
	return RunServer(&opts)
}

const defaultRecBufSize = 32768
const defaultSendBufSize = 32768

func flushConnection(b *testing.B, c net.Conn) {
	buf := make([]byte, 32)
	c.Write([]byte("PING\r\n"))
	c.SetReadDeadline(time.Now().Add(1 * time.Second))
	n, err := c.Read(buf)
	c.SetReadDeadline(time.Time{})
	if err != nil {
		b.Fatalf("Failed read: %v\n", err)
	}
	if n != 6 && buf[0] != 'P' && buf[1] != 'O' {
		b.Fatalf("Failed read of PONG: %s\n", buf)
	}
}

func benchPub(b *testing.B, subject, payload string) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB %s %d\r\n%s\r\n", subject, len(payload), payload))
	b.SetBytes(int64(len(sendOp)))
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		bw.Write(sendOp)
	}
	bw.Flush()
	flushConnection(b, c)
	b.StopTimer()
	c.Close()
	s.Shutdown()
}

var ch = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$#%^&*()")

func sizedBytes(sz int) []byte {
	b := make([]byte, sz)
	for i := range b {
		b[i] = ch[rand.Intn(len(ch))]
	}
	return b
}

func sizedString(sz int) string {
	return string(sizedBytes(sz))
}

func Benchmark____PubNo_Payload(b *testing.B) {
	benchPub(b, "a", "")
}

func Benchmark____Pub8b_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(8)
	benchPub(b, "a", s)
}

func Benchmark___Pub32b_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(32)
	benchPub(b, "a", s)
}

func Benchmark__Pub256B_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(256)
	benchPub(b, "a", s)
}

func Benchmark____Pub1K_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(1024)
	benchPub(b, "a", s)
}

func Benchmark____Pub4K_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(4 * 1024)
	benchPub(b, "a", s)
}

func Benchmark____Pub8K_Payload(b *testing.B) {
	b.StopTimer()
	s := sizedString(8 * 1024)
	benchPub(b, "a", s)
}

func drainConnection(b *testing.B, c net.Conn, ch chan bool, expected int) {
	buf := make([]byte, defaultRecBufSize)
	bytes := 0

	for {
		c.SetReadDeadline(time.Now().Add(5 * time.Second))
		n, err := c.Read(buf)
		if err != nil {
			b.Errorf("Error on read: %v\n", err)
			break
		}
		bytes += n
		if bytes >= expected {
			break
		}
	}
	if bytes != expected {
		b.Errorf("Did not receive all bytes: %d vs %d\n", bytes, expected)
	}
	ch <- true
}

func Benchmark___________PubSub(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	sendProto(b, c, "SUB foo 1\r\n")
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)
	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c, ch, expected)
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		_, err := bw.Write(sendOp)
		if err != nil {
			b.Errorf("Received error on PUB write: %v\n", err)
		}
	}
	err := bw.Flush()
	if err != nil {
		b.Errorf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	s.Shutdown()
}

func Benchmark___PubSubTwoConns(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	bw := bufio.NewWriterSize(c, defaultSendBufSize)

	c2 := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c2)
	sendProto(b, c2, "SUB foo 1\r\n")
	flushConnection(b, c2)

	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)

	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c2, ch, expected)

	b.StartTimer()
	for i := 0; i < b.N; i++ {
		bw.Write(sendOp)
	}
	err := bw.Flush()
	if err != nil {
		b.Errorf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	c2.Close()
	s.Shutdown()
}

func Benchmark___PubTwoQueueSub(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	sendProto(b, c, "SUB foo group1 1\r\n")
	sendProto(b, c, "SUB foo group1 2\r\n")
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)
	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c, ch, expected)
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		_, err := bw.Write(sendOp)
		if err != nil {
			b.Fatalf("Received error on PUB write: %v\n", err)
		}
	}
	err := bw.Flush()
	if err != nil {
		b.Fatalf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	s.Shutdown()
}

func Benchmark__PubFourQueueSub(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	sendProto(b, c, "SUB foo group1 1\r\n")
	sendProto(b, c, "SUB foo group1 2\r\n")
	sendProto(b, c, "SUB foo group1 3\r\n")
	sendProto(b, c, "SUB foo group1 4\r\n")
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)
	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c, ch, expected)
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		_, err := bw.Write(sendOp)
		if err != nil {
			b.Fatalf("Received error on PUB write: %v\n", err)
		}
	}
	err := bw.Flush()
	if err != nil {
		b.Fatalf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	s.Shutdown()
}

func Benchmark_PubEightQueueSub(b *testing.B) {
	b.StopTimer()
	s := runBenchServer()
	c := createClientConn(b, "localhost", PERF_PORT)
	doDefaultConnect(b, c)
	sendProto(b, c, "SUB foo group1 1\r\n")
	sendProto(b, c, "SUB foo group1 2\r\n")
	sendProto(b, c, "SUB foo group1 3\r\n")
	sendProto(b, c, "SUB foo group1 4\r\n")
	sendProto(b, c, "SUB foo group1 5\r\n")
	sendProto(b, c, "SUB foo group1 6\r\n")
	sendProto(b, c, "SUB foo group1 7\r\n")
	sendProto(b, c, "SUB foo group1 8\r\n")
	bw := bufio.NewWriterSize(c, defaultSendBufSize)
	sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
	ch := make(chan bool)
	expected := len("MSG foo 1 2\r\nok\r\n") * b.N
	go drainConnection(b, c, ch, expected)
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		_, err := bw.Write(sendOp)
		if err != nil {
			b.Fatalf("Received error on PUB write: %v\n", err)
		}
	}
	err := bw.Flush()
	if err != nil {
		b.Fatalf("Received error on FLUSH write: %v\n", err)
	}

	// Wait for connection to be drained
	<-ch

	b.StopTimer()
	c.Close()
	s.Shutdown()
}
<|endoftext|>


<commit_before>// This package implements a simple HTTP server providing a REST API to a node handler.
//
// It provides four methods:
//
// GET /node/ Retrieves all the nodes.
// POST /node/ Creates a new node given a title.
// GET /node/{nodeID} Retrieves the node with the given id.
// PUT /node/{nodeID} Updates the node with the given id.
//
// Every method below gives more information about every API call, its parameters, and its results.
package server

import (
	"encoding/json"
	"fmt"
	"github.com/tracyde/hiera-gui/node"
	"github.com/gorilla/mux"
	"log"
	"net/http"
)

var nodes = node.NewNodeManager()

const PathPrefix = "/node/"

func RegisterHandlers() {
	r := mux.NewRouter()
	r.HandleFunc(PathPrefix, errorHandler(ListNodes)).Methods("GET")
	r.HandleFunc(PathPrefix, errorHandler(NewNode)).Methods("POST")
	r.HandleFunc(PathPrefix+"{name}", errorHandler(GetNode)).Methods("GET")
	r.HandleFunc(PathPrefix+"{name}", errorHandler(UpdateNode)).Methods("PUT")
	http.Handle(PathPrefix, r)
}

// badRequest is handled by setting the status code in the reply to StatusBadRequest.
type badRequest struct{ error }

// notFound is handled by setting the status code in the reply to StatusNotFound.
type notFound struct{ error }

// errorHandler wraps a function returning an error by handling the error and returning a http.Handler.
// If the error is of the one of the types defined above, it is handled as described for every type.
// If the error is of another type, it is considered as an internal error and its message is logged.
func errorHandler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := f(w, r)
		if err == nil {
			return
		}
		switch err.(type) {
		case badRequest:
			http.Error(w, err.Error(), http.StatusBadRequest)
		case notFound:
			http.Error(w, "node not found", http.StatusNotFound)
		default:
			log.Println(err)
			http.Error(w, "oops", http.StatusInternalServerError)
		}
	}
}

// ListNode handles GET requests on /node.
// There's no parameters and it returns an object with a Nodes field containing a list of nodes.
//
// Example:
//
// req: GET /node/
// res: 200 {"Nodes": [
// {"ID": 1, "Title": "Learn Go", "Done": false},
// {"ID": 2, "Title": "Buy bread", "Done": true}
// ]}
func ListNodes(w http.ResponseWriter, r *http.Request) error {
	res := struct{ Nodes []*node.Node }{nodes.All()}
	return json.NewEncoder(w).Encode(res)
}

// NewNode handles POST requests on /node.
// The request body must contain a JSON object with a Title field.
// The status code of the response is used to indicate any error.
//
// Examples:
//
// req: POST /node/ {"Title": ""}
// res: 400 empty title
//
// req: POST /node/ {"Title": "Buy bread"}
// res: 200
func NewNode(w http.ResponseWriter, r *http.Request) error {
	req := struct{ Name string; Role string }{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return badRequest{err}
	}
	t, err := node.NewNode(req.Name, req.Role)
	if err != nil {
		return badRequest{err}
	}
	return nodes.Save(t)
}

// parseName obtains the id variable from the given request url,
// parses the obtained text and returns the result.
func parseName(r *http.Request) (string, error) {
	txt, ok := mux.Vars(r)["name"]
	if !ok {
		return "", fmt.Errorf("node name not found")
	}
	return txt, nil
}

// GetNode handles GET requsts to /node/{nodeID}.
// There's no parameters and it returns a JSON encoded node.
//
// Examples:
//
// req: GET /node/1
// res: 200 {"ID": 1, "Title": "Buy bread", "Done": true}
//
// req: GET /node/42
// res: 404 node not found
func GetNode(w http.ResponseWriter, r *http.Request) error {
	name, err := parseName(r)
	log.Println("Node is ", name)
	if err != nil {
		return badRequest{err}
	}
	n, ok := nodes.Find(name)
	log.Println("Found", ok)
	if !ok {
		return notFound{}
	}
	return json.NewEncoder(w).Encode(n)
}

// UpdateNode handles PUT requests to /node/{nodeID}.
// The request body must contain a JSON encoded node.
//
// Example:
//
// req: PUT /node/1 {"ID": 1, "Title": "Learn Go", "Done": true}
// res: 200
//
// req: PUT /node/2 {"ID": 2, "Title": "Learn Go", "Done": true}
// res: 400 inconsistent node IDs
func UpdateNode(w http.ResponseWriter, r *http.Request) error {
	name, err := parseName(r)
	if err != nil {
		return badRequest{err}
	}
	var n node.Node
	if err := json.NewDecoder(r.Body).Decode(&n); err != nil {
		return badRequest{err}
	}
	if n.Name != name {
		return badRequest{fmt.Errorf("inconsistent node names")}
	}
	if _, ok := nodes.Find(name); !ok {
		return notFound{}
	}
	return nodes.Save(&n)
}
<commit_msg>formatted using goimports<commit_after>// This package implements a simple HTTP server providing a REST API to a node handler.
//
// It provides four methods:
//
// GET /node/ Retrieves all the nodes.
// POST /node/ Creates a new node given a title.
// GET /node/{nodeID} Retrieves the node with the given id.
// PUT /node/{nodeID} Updates the node with the given id.
//
// Every method below gives more information about every API call, its parameters, and its results.
package server

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
	"github.com/tracyde/hiera-gui/node"
)

var nodes = node.NewNodeManager()

const PathPrefix = "/node/"

func RegisterHandlers() {
	r := mux.NewRouter()
	r.HandleFunc(PathPrefix, errorHandler(ListNodes)).Methods("GET")
	r.HandleFunc(PathPrefix, errorHandler(NewNode)).Methods("POST")
	r.HandleFunc(PathPrefix+"{name}", errorHandler(GetNode)).Methods("GET")
	r.HandleFunc(PathPrefix+"{name}", errorHandler(UpdateNode)).Methods("PUT")
	http.Handle(PathPrefix, r)
}

// badRequest is handled by setting the status code in the reply to StatusBadRequest.
type badRequest struct{ error }

// notFound is handled by setting the status code in the reply to StatusNotFound.
type notFound struct{ error }

// errorHandler wraps a function returning an error by handling the error and returning a http.Handler.
// If the error is of the one of the types defined above, it is handled as described for every type.
// If the error is of another type, it is considered as an internal error and its message is logged.
func errorHandler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := f(w, r)
		if err == nil {
			return
		}
		switch err.(type) {
		case badRequest:
			http.Error(w, err.Error(), http.StatusBadRequest)
		case notFound:
			http.Error(w, "node not found", http.StatusNotFound)
		default:
			log.Println(err)
			http.Error(w, "oops", http.StatusInternalServerError)
		}
	}
}

// ListNode handles GET requests on /node.
// There's no parameters and it returns an object with a Nodes field containing a list of nodes.
//
// Example:
//
// req: GET /node/
// res: 200 {"Nodes": [
// {"ID": 1, "Title": "Learn Go", "Done": false},
// {"ID": 2, "Title": "Buy bread", "Done": true}
// ]}
func ListNodes(w http.ResponseWriter, r *http.Request) error {
	res := struct{ Nodes []*node.Node }{nodes.All()}
	return json.NewEncoder(w).Encode(res)
}

// NewNode handles POST requests on /node.
// The request body must contain a JSON object with a Title field.
// The status code of the response is used to indicate any error.
//
// Examples:
//
// req: POST /node/ {"Title": ""}
// res: 400 empty title
//
// req: POST /node/ {"Title": "Buy bread"}
// res: 200
func NewNode(w http.ResponseWriter, r *http.Request) error {
	req := struct {
		Name string
		Role string
	}{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return badRequest{err}
	}
	t, err := node.NewNode(req.Name, req.Role)
	if err != nil {
		return badRequest{err}
	}
	return nodes.Save(t)
}

// parseName obtains the id variable from the given request url,
// parses the obtained text and returns the result.
func parseName(r *http.Request) (string, error) {
	txt, ok := mux.Vars(r)["name"]
	if !ok {
		return "", fmt.Errorf("node name not found")
	}
	return txt, nil
}

// GetNode handles GET requsts to /node/{nodeID}.
// There's no parameters and it returns a JSON encoded node.
//
// Examples:
//
// req: GET /node/1
// res: 200 {"ID": 1, "Title": "Buy bread", "Done": true}
//
// req: GET /node/42
// res: 404 node not found
func GetNode(w http.ResponseWriter, r *http.Request) error {
	name, err := parseName(r)
	log.Println("Node is ", name)
	if err != nil {
		return badRequest{err}
	}
	n, ok := nodes.Find(name)
	log.Println("Found", ok)
	if !ok {
		return notFound{}
	}
	return json.NewEncoder(w).Encode(n)
}

// UpdateNode handles PUT requests to /node/{nodeID}.
// The request body must contain a JSON encoded node.
//
// Example:
//
// req: PUT /node/1 {"ID": 1, "Title": "Learn Go", "Done": true}
// res: 200
//
// req: PUT /node/2 {"ID": 2, "Title": "Learn Go", "Done": true}
// res: 400 inconsistent node IDs
func UpdateNode(w http.ResponseWriter, r *http.Request) error {
	name, err := parseName(r)
	if err != nil {
		return badRequest{err}
	}
	var n node.Node
	if err := json.NewDecoder(r.Body).Decode(&n); err != nil {
		return badRequest{err}
	}
	if n.Name != name {
		return badRequest{fmt.Errorf("inconsistent node names")}
	}
	if _, ok := nodes.Find(name); !ok {
		return notFound{}
	}
	return nodes.Save(&n)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"

	"github.com/bamnet/apartment/wemo"
	"github.com/cenk/backoff"
	"golang.org/x/net/context"

	apb "github.com/bamnet/apartment/proto/apartment"
)

// Server holds the internal device connections.
type Server struct {
	devices map[string]*wemo.Device
}

// NewServer builds a new Apartment server.
// It connects and maps the initial set of devices.
func NewServer() (*Server, error) {
	aSrv := &Server{devices: map[string]*wemo.Device{}}
	devices, err := wemo.DiscoverDevices()
	if err != nil {
		return nil, err
	}
	for _, d := range devices {
		aSrv.devices[rename(d.FriendlyName)] = d
	}
	return aSrv, nil
}

// ListDevices lists all the devices the server is aware of.
// It does not attempt to identify the state of the devices.
func (s *Server) ListDevices(ctx context.Context, _ *apb.ListDevicesRequest) (*apb.ListDevicesResponse, error) {
	resp := apb.ListDevicesResponse{}
	for n, d := range s.devices {
		device := &apb.Device{
			Name:         n,
			FriendlyName: d.FriendlyName,
		}
		resp.Device = append(resp.Device, device)
	}
	return &resp, nil
}

// GetDevice gets the latest information about a Device.
func (s *Server) GetDevice(ctx context.Context, in *apb.GetDeviceRequest) (*apb.Device, error) {
	d, err := s.lookupDevice(in.Name)
	if err != nil {
		return nil, err
	}

	return apiDevice(d)
}

// UpdateDevice sets the state of a Device.
func (s *Server) UpdateDevice(ctx context.Context, in *apb.UpdateDeviceRequest) (*apb.Device, error) {
	d, err := s.lookupDevice(in.Device.Name)
	if err != nil {
		return nil, err
	}

	if err := backoff.Retry(func() error {
		return d.SetState(in.Device.State)
	}, backoff.NewExponentialBackOff()); err != nil {
		return nil, err
	}

	return apiDevice(d)
}

// lookupDevice is a shortcut function to try and find a device in
// the internal device map.
func (s *Server) lookupDevice(name string) (*wemo.Device, error) {
	d, ok := s.devices[name]
	if !ok {
		return nil, fmt.Errorf("no device found")
	}
	return d, nil
}

func rename(in string) string {
	return strings.ToLower(in)
}

// apiDevice converts a wemo.Device to an apartment protobuf Device.
func apiDevice(d *wemo.Device) (*apb.Device, error) {
	device := &apb.Device{
		Name:         rename(d.FriendlyName),
		FriendlyName: d.FriendlyName,
	}

	if err := backoff.Retry(func() error {
		state, err := d.State()
		if err != nil {
			return err
		}
		device.State = state
		return nil
	}, backoff.NewExponentialBackOff()); err != nil {
		return nil, err
	}
	return device, nil
}
<commit_msg>Rescan for devices every $interval.<commit_after>package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/bamnet/apartment/wemo"
	"github.com/cenk/backoff"
	"golang.org/x/net/context"

	apb "github.com/bamnet/apartment/proto/apartment"
)

// Server holds the internal device connections.
type Server struct {
	devices map[string]*wemo.Device
}

// NewServer builds a new Apartment server.
// It connects and maps the initial set of devices.
func NewServer() (*Server, error) {
	aSrv := &Server{devices: map[string]*wemo.Device{}}
	if err := aSrv.mapDevices(); err != nil {
		return nil, err
	}
	aSrv.remapper(60 * time.Second)

	return aSrv, nil
}

func (s *Server) mapDevices() error {
	devices, err := wemo.DiscoverDevices()
	if err != nil {
		return err
	}
	// TODO(bamnet): Wrap this in a mutex.
	for _, d := range devices {
		s.devices[rename(d.FriendlyName)] = d
	}
	return nil
}

func (s *Server) remapper(interval time.Duration) {
	ticker := time.NewTicker(interval)
	go func() {
		for {
			select {
			case <-ticker.C:
				s.mapDevices()
			}
		}
	}()
}

// ListDevices lists all the devices the server is aware of.
// It does not attempt to identify the state of the devices.
func (s *Server) ListDevices(ctx context.Context, _ *apb.ListDevicesRequest) (*apb.ListDevicesResponse, error) {
	resp := apb.ListDevicesResponse{}
	for n, d := range s.devices {
		device := &apb.Device{
			Name:         n,
			FriendlyName: d.FriendlyName,
		}
		resp.Device = append(resp.Device, device)
	}
	return &resp, nil
}

// GetDevice gets the latest information about a Device.
func (s *Server) GetDevice(ctx context.Context, in *apb.GetDeviceRequest) (*apb.Device, error) {
	d, err := s.lookupDevice(in.Name)
	if err != nil {
		return nil, err
	}

	return apiDevice(d)
}

// UpdateDevice sets the state of a Device.
func (s *Server) UpdateDevice(ctx context.Context, in *apb.UpdateDeviceRequest) (*apb.Device, error) {
	d, err := s.lookupDevice(in.Device.Name)
	if err != nil {
		return nil, err
	}

	if err := backoff.Retry(func() error {
		return d.SetState(in.Device.State)
	}, backoff.NewExponentialBackOff()); err != nil {
		return nil, err
	}

	return apiDevice(d)
}

// lookupDevice is a shortcut function to try and find a device in
// the internal device map.
func (s *Server) lookupDevice(name string) (*wemo.Device, error) {
	d, ok := s.devices[name]
	if !ok {
		return nil, fmt.Errorf("no device found")
	}
	return d, nil
}

func rename(in string) string {
	return strings.ToLower(in)
}

// apiDevice converts a wemo.Device to an apartment protobuf Device.
func apiDevice(d *wemo.Device) (*apb.Device, error) {
	device := &apb.Device{
		Name:         rename(d.FriendlyName),
		FriendlyName: d.FriendlyName,
	}

	if err := backoff.Retry(func() error {
		state, err := d.State()
		if err != nil {
			return err
		}
		device.State = state
		return nil
	}, backoff.NewExponentialBackOff()); err != nil {
		return nil, err
	}
	return device, nil
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"net"
)

var debugMode bool

// Start brings up the TCP server.
func Start(port int, cert string, key string, poolSize int, debug bool) (err error) {
	var listener net.Listener

	debugMode = debug

	if tlsEnabled(cert, key) {
		listener, err = createTLSListener(port, cert, key)
		if err != nil {
			return err
		}
	} else {
		listener, err = net.Listen("tcp", fmt.Sprintf(":%d", port))
		if err != nil {
			return err
		}
	}

	defer listener.Close()

	t := &tracker{
		poolSize: poolSize,
	}
	t.init()

	packetInfoChan := make(chan *packetInfo)
	go startPacketInfoChan(packetInfoChan)

	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}

		go handleConnection(conn, packetInfoChan, t)
	}
}

func handleConnection(conn net.Conn, c chan *packetInfo, tracker *tracker) {
	defer conn.Close()
	defer tracker.remove(conn)

	processMessages(conn, c, tracker)

	return
}
<commit_msg>log port + pool size when server starts<commit_after>package server

import (
	"fmt"
	"net"
)

var debugMode bool

// Start brings up the TCP server.
func Start(port int, cert string, key string, poolSize int, debug bool) (err error) {
	var listener net.Listener

	debugMode = debug

	if tlsEnabled(cert, key) {
		listener, err = createTLSListener(port, cert, key)
		if err != nil {
			return err
		}
	} else {
		listener, err = net.Listen("tcp", fmt.Sprintf(":%d", port))
		if err != nil {
			return err
		}
	}

	defer listener.Close()

	t := &tracker{
		poolSize: poolSize,
	}
	t.init()

	packetInfoChan := make(chan *packetInfo)
	go startPacketInfoChan(packetInfoChan)

	fmt.Printf("listening on TCP port %d (pool size: %d)\n", port, poolSize)
	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}

		go handleConnection(conn, packetInfoChan, t)
	}
}

func handleConnection(conn net.Conn, c chan *packetInfo, tracker *tracker) {
	defer conn.Close()
	defer tracker.remove(conn)

	processMessages(conn, c, tracker)

	return
}
<|endoftext|>