<commit_before>package boardgame

import (
	"encoding/json"
	"github.com/jkomoros/boardgame/enum"
	"github.com/jkomoros/boardgame/errors"
	"strconv"
)

//ComputedProperties represents a collection of computed properties for a
//given state. An object conforming to this interface will be returned from
//state.Computed(). Its values will be set based on what
//Delegate.ComputedPropertiesConfig returns.
type ComputedProperties interface {
	//The primary property reader is where top-level computed properties can
	//be accessed.
	Global() MutableSubState
	//To get the ComputedPlayerProperties, pass in the player index.
	Player(index PlayerIndex) MutableSubState
}

//ComputedPropertiesConfig is the struct that contains configuration for which
//properties to compute and how to compute them. See the package documentation
//on Computed Properties for more information.
type ComputedPropertiesConfig struct {
	//The top-level computed properties.
	Global map[string]ComputedGlobalPropertyDefinition
	//The properties that are computed for each PlayerState individually.
	Player map[string]ComputedPlayerPropertyDefinition
}

//ComputedGlobalPropertyDefinition defines how to calculate a given top-level
//computed property.
type ComputedGlobalPropertyDefinition struct {
	//Dependencies exhaustively enumerates all of the properties that need to
	//be populated on the ShadowState to calculate this value. Defining your
	//dependencies allows us to only recalculate computed properties when
	//necessary, and other kewl tricks.
	Dependencies []StatePropertyRef
	//The thing we expect to be able to cast the result of Compute to (since
	//the method necessarily has to be general).
	PropType PropertyType
	//Where the actual logic of the computed property goes. sanitizedState
	//will be a Sanitized() State populated with all of the properties
	//enumerated in Dependencies, with the other properties obscured with
	//PolicyRandom  (For PlayerState properties, we will include that property
	//on each ShadowPlayerState object). Since it's just a sanitized State,
	//you may cast the state to the concrete types for your game to more
	//easily retrieve values. The return value will be casted to PropType
	//afterward. Return an error if any state is configured in an unexpected
	//way. Note: your compute function should be resilient to values that are
	//sanitized. In many cases it makes sense to factor your compute
	//computation out into a shim that fetches the relevant properties from
	//the ShadowState and then passes them to the core computation function,
	//so that other methods can reuse the same logic.
	Compute func(sanitizedState State) (interface{}, error)
}

//ComputedPlayerPropertyDefinition is the analogue for
//ComputedPropertyDefintion, but operates on a single PlayerState at a time
//and returns properties for that particular PlayerState.
type ComputedPlayerPropertyDefinition struct {
	//Dependencies exhaustively enumerates all of the properties that need to
	//be populated on the ShadowState to calculate this value. Defining your
	//dependencies allows us to only recalculate computed properties when
	//necessary, and other kewl tricks. All Dependencies must have Group
	//StateGroupPlayer, otherwise the computation will error.
	Dependencies []StatePropertyRef
	//The thing we expect to be able to cast the result of Compute to (since
	//the method necessarily has to be general).
	PropType PropertyType
	//Where the actual logic of the computed property goes. playerState will
	//be a PlayerState from a Sanitized() state, populated with all of the
	//properties enumerated in Dependencies, with other properties obscured by
	//PolicyRandom. Since it's just a PlayerState from a sanitized State, it
	//is safe to cast to the underlying PlayerState type you know it is for
	//your package for convenience. This method will be called once per
	//PlayerState in turn. The return value will be casted to PropType
	//afterward. Return an error if any state is configured in an unexpected
	//way. Note: your compute function should be resilient to values that are
	//sanitized. In many cases it makes sense to factor your compute
	//computation out into a shim that fetches the relevant properties from
	//the ShadowState and then passes them to the core computation function,
	//so that other methods can reuse the same logic. If you need more state
	//than what is available on just the playerState, consider defining
	//GlobalCompute instead.
	Compute func(playerState PlayerState) (interface{}, error)

	//If Compute is nil but GlobalCompute is non-nil, then GlobalCompute will
	//be called instead. Instead of passing in just the single playerState for
	//the player in question, it passes the fullState, as well as the
	//PlayerIndex currently being prepared for, and expects the caller to
	//return the value for the specific playerIndex.
	GlobalCompute func(state State, player PlayerIndex) (interface{}, error)
}

//StateGroupType is the top-level grouping object used in a StatePropertyRef.
type StateGroupType int

const (
	StateGroupGame StateGroupType = iota
	StateGroupPlayer
	StateGroupDynamicComponentValues
)

//A StatePropertyRef is a reference to a particular property in a State, in a
//structured way. Currently used when defining your dependencies for computed
//properties.
type StatePropertyRef struct {
	Group StateGroupType
	//DeckName is only used when Group is StateGroupDynamicComponentValues
	DeckName string
	//PropName is the specific property on the given SubStateObject specified
	//by the rest of the StatePropertyRef.
	PropName string
}

type computedPropertiesImpl struct {
	global  MutableSubState
	players []MutableSubState
}

func transformationForDependencies(state *state, dependencies []StatePropertyRef) *sanitizationTransformation {
	result := &sanitizationTransformation{}

	result.Game = basicTransformationForDepenencies(state.GameState().Reader())

	result.Players = make([]subStateSanitizationTransformation, len(state.playerStates))

	for i, playerState := range state.playerStates {
		result.Players[i] = basicTransformationForDepenencies(playerState.Reader())
	}

	result.DynamicComponentValues = make(map[string]subStateSanitizationTransformation)

	for deckName, deckValues := range state.dynamicComponentValues {
		result.DynamicComponentValues[deckName] = basicTransformationForDepenencies(deckValues[0].Reader())
	}

	for _, dependency := range dependencies {
		if dependency.Group == StateGroupGame {
			result.Game[dependency.PropName] = PolicyVisible
		} else if dependency.Group == StateGroupPlayer {
			for i := 0; i < len(result.Players); i++ {
				result.Players[i][dependency.PropName] = PolicyVisible
			}
		} else if dependency.Group == StateGroupDynamicComponentValues {
			result.DynamicComponentValues[dependency.DeckName][dependency.PropName] = PolicyVisible
		}
	}

	return result
}

//Creates a transformation that sets each property to Random.
func basicTransformationForDepenencies(reader PropertyReader) subStateSanitizationTransformation {
	result := make(subStateSanitizationTransformation)
	for propName, _ := range reader.Props() {
		result[propName] = PolicyRandom
	}
	return result
}

func newComputedPropertiesImpl(config *ComputedPropertiesConfig, state *state) (*computedPropertiesImpl, error) {

	if !state.calculatingComputed {
		return nil, errors.New("State didn't think it was calculatingComputed when it was")
	}

	if config == nil {
		//It's fine if no config is provided--that just means no computed
		//properties.
		return nil, nil
	}

	playerBags := make([]MutableSubState, len(state.PlayerStates()))

	//TODO: calculate all properties.
	for i, _ := range state.PlayerStates() {
		collection := newGenericReader()

		playerBags[i] = collection

		if config.Player == nil {
			continue
		}

		reader := collection.ReadSetter()

		for name, propConfig := range config.Player {
			if err := propConfig.calculate(name, PlayerIndex(i), state, reader); err != nil {
				//TODO: do something better here.
				return nil, errors.Extend(err, "Player failed")
			}
		}

	}

	globalBag := newGenericReader()

	if config.Global != nil {
		for name, propConfig := range config.Global {
			if err := propConfig.calculate(name, state, globalBag.ReadSetter()); err != nil {
				//TODO: do something better here.
				return nil, errors.Extend(err, "global failed")
			}
		}
	}

	return &computedPropertiesImpl{
		global:  globalBag,
		players: playerBags,
	}, nil
}

func (c *ComputedGlobalPropertyDefinition) calculate(propName string, state *state, output PropertyReadSetter) error {

	result, err := c.compute(state)

	if err != nil {
		return errors.New("Error computing calculated prop: " + err.Error())
	}

	switch c.PropType {
	case TypeBool:
		boolVal, ok := result.(bool)
		if !ok {
			return errors.New("Property did not return bool as expected")
		}
		output.SetBoolProp(propName, boolVal)
	case TypeInt:
		intVal, ok := result.(int)
		if !ok {
			return errors.New("Property did not return int as expected")
		}
		output.SetIntProp(propName, intVal)
	case TypeString:
		stringVal, ok := result.(string)
		if !ok {
			return errors.New("Property did not return string as expected")
		}
		output.SetStringProp(propName, stringVal)
	case TypePlayerIndex:
		playerIndexVal, ok := result.(PlayerIndex)
		if !ok {
			return errors.New("Property did not return PlayerIndex as expected")
		}
		output.SetPlayerIndexProp(propName, playerIndexVal)
	case TypeGrowableStack:
		growableStackVal, ok := result.(*GrowableStack)
		if !ok {
			return errors.New("Property did not return growable stack as expected")
		}
		output.SetGrowableStackProp(propName, growableStackVal)
	case TypeSizedStack:
		sizedStackVal, ok := result.(*SizedStack)
		if !ok {
			return errors.New("Property did not return sized stack as expected")
		}
		output.SetSizedStackProp(propName, sizedStackVal)
	case TypeEnumConst:
		enumConstVal, ok := result.(enum.Const)
		if !ok {
			return errors.New("Property did not return enum const as expected")
		}
		output.SetEnumConstProp(propName, enumConstVal)
	case TypeEnumVar:
		enumVarVal, ok := result.(enum.Var)
		if !ok {
			return errors.New("Property did not return enum var as expected")
		}
		output.SetEnumVarProp(propName, enumVarVal)
	default:
		return errors.New("That property type, " + c.PropType.String() + " is not currently supported")
	}

	return nil

}

func (c *ComputedPlayerPropertyDefinition) calculate(propName string, playerIndex PlayerIndex, state *state, output PropertyReadSetter) error {

	result, err := c.compute(state, playerIndex)

	if err != nil {
		return errors.New("Error computing calculated prop: " + err.Error())
	}

	switch c.PropType {
	case TypeBool:
		boolVal, ok := result.(bool)
		if !ok {
			return errors.New("Property did not return bool as expected")
		}
		output.SetBoolProp(propName, boolVal)
	case TypeInt:
		intVal, ok := result.(int)
		if !ok {
			return errors.New("Property did not return int as expected")
		}
		output.SetIntProp(propName, intVal)
	case TypeString:
		stringVal, ok := result.(string)
		if !ok {
			return errors.New("Property did not return string as expected")
		}
		output.SetStringProp(propName, stringVal)
	case TypePlayerIndex:
		playerIndexVal, ok := result.(PlayerIndex)
		if !ok {
			return errors.New("Property did not return PlayerIndex as expected")
		}
		output.SetPlayerIndexProp(propName, playerIndexVal)
	case TypeGrowableStack:
		growableStackVal, ok := result.(*GrowableStack)
		if !ok {
			return errors.New("Property did not return growable stack as expected")
		}
		output.SetGrowableStackProp(propName, growableStackVal)
	case TypeSizedStack:
		sizedStackVal, ok := result.(*SizedStack)
		if !ok {
			return errors.New("Property did not return sized stack as expected")
		}
		output.SetSizedStackProp(propName, sizedStackVal)
	case TypeEnumConst:
		enumConstVal, ok := result.(enum.Const)
		if !ok {
			return errors.New("Property did not return enum const as expected")
		}
		output.SetEnumConstProp(propName, enumConstVal)
	case TypeEnumVar:
		enumVarVal, ok := result.(enum.Var)
		if !ok {
			return errors.New("Property did not return enum var as expected")
		}
		output.SetEnumVarProp(propName, enumVarVal)
	default:
		return errors.New("That property type, " + c.PropType.String() + " is not currently supported")
	}

	return nil

}

func (c *ComputedGlobalPropertyDefinition) compute(state *state) (interface{}, error) {

	//First, prepare a shadow state with all of the dependencies.

	transformation := transformationForDependencies(state, c.Dependencies)

	sanitized, err := state.applySanitizationTransformation(transformation)

	if err != nil {
		return nil, errors.Extend(err, "Couldn't create randomized state for globals")
	}

	return c.Compute(sanitized)

}

func (c *ComputedPlayerPropertyDefinition) compute(state *state, playerIndex PlayerIndex) (interface{}, error) {

	transformation := transformationForDependencies(state, c.Dependencies)

	sanitized, err := state.applySanitizationTransformation(transformation)

	if err != nil {
		return nil, errors.Extend(err, "Couldn't create randomized state for players")
	}

	if c.Compute != nil {
		return c.Compute(sanitized.PlayerStates()[playerIndex])
	}

	if c.GlobalCompute != nil {
		return c.GlobalCompute(sanitized, playerIndex)
	}

	return nil, errors.New("Neither Compute nor GlobalCompute were defined. One of them must be.")
}

func (c *computedPropertiesImpl) Global() MutableSubState {
	return c.global
}

func (c *computedPropertiesImpl) Player(index PlayerIndex) MutableSubState {
	return c.players[int(index)]
}

func (c *computedPropertiesImpl) MarshalJSON() ([]byte, error) {

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

	playerProperties := make([]map[string]interface{}, len(c.players))

	for i, player := range c.players {
		playerProperties[i] = make(map[string]interface{})
		for propName, _ := range player.Reader().Props() {
			val, err := player.Reader().Prop(propName)

			if err != nil {
				return nil, errors.New("Player computed prop " + propName + " for player " + strconv.Itoa(i) + " returned an error: " + err.Error())
			}
			playerProperties[i][propName] = val
		}
	}

	props := c.Global()

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

	for propName, _ := range props.Reader().Props() {
		val, err := props.Reader().Prop(propName)

		if err != nil {
			return nil, errors.New("Computed Prop " + propName + " returned an error: " + err.Error())
		}

		globalProperties[propName] = val
	}

	//TODO: can't I just have this have a default marshal JSON and then move
	//these sub-impls to the global and player group?

	result["Global"] = globalProperties

	result["Players"] = playerProperties

	return json.Marshal(result)
}
<commit_msg>Tweaked the definition of Computed() to return SubStates, not MutableSubStates. Part of #502.<commit_after>package boardgame

import (
	"encoding/json"
	"github.com/jkomoros/boardgame/enum"
	"github.com/jkomoros/boardgame/errors"
	"strconv"
)

//ComputedProperties represents a collection of computed properties for a
//given state. An object conforming to this interface will be returned from
//state.Computed(). Its values will be set based on what
//Delegate.ComputedPropertiesConfig returns.
type ComputedProperties interface {
	//The primary property reader is where top-level computed properties can
	//be accessed.
	Global() SubState
	//To get the ComputedPlayerProperties, pass in the player index.
	Player(index PlayerIndex) SubState
}

//ComputedPropertiesConfig is the struct that contains configuration for which
//properties to compute and how to compute them. See the package documentation
//on Computed Properties for more information.
type ComputedPropertiesConfig struct {
	//The top-level computed properties.
	Global map[string]ComputedGlobalPropertyDefinition
	//The properties that are computed for each PlayerState individually.
	Player map[string]ComputedPlayerPropertyDefinition
}

//ComputedGlobalPropertyDefinition defines how to calculate a given top-level
//computed property.
type ComputedGlobalPropertyDefinition struct {
	//Dependencies exhaustively enumerates all of the properties that need to
	//be populated on the ShadowState to calculate this value. Defining your
	//dependencies allows us to only recalculate computed properties when
	//necessary, and other kewl tricks.
	Dependencies []StatePropertyRef
	//The thing we expect to be able to cast the result of Compute to (since
	//the method necessarily has to be general).
	PropType PropertyType
	//Where the actual logic of the computed property goes. sanitizedState
	//will be a Sanitized() State populated with all of the properties
	//enumerated in Dependencies, with the other properties obscured with
	//PolicyRandom  (For PlayerState properties, we will include that property
	//on each ShadowPlayerState object). Since it's just a sanitized State,
	//you may cast the state to the concrete types for your game to more
	//easily retrieve values. The return value will be casted to PropType
	//afterward. Return an error if any state is configured in an unexpected
	//way. Note: your compute function should be resilient to values that are
	//sanitized. In many cases it makes sense to factor your compute
	//computation out into a shim that fetches the relevant properties from
	//the ShadowState and then passes them to the core computation function,
	//so that other methods can reuse the same logic.
	Compute func(sanitizedState State) (interface{}, error)
}

//ComputedPlayerPropertyDefinition is the analogue for
//ComputedPropertyDefintion, but operates on a single PlayerState at a time
//and returns properties for that particular PlayerState.
type ComputedPlayerPropertyDefinition struct {
	//Dependencies exhaustively enumerates all of the properties that need to
	//be populated on the ShadowState to calculate this value. Defining your
	//dependencies allows us to only recalculate computed properties when
	//necessary, and other kewl tricks. All Dependencies must have Group
	//StateGroupPlayer, otherwise the computation will error.
	Dependencies []StatePropertyRef
	//The thing we expect to be able to cast the result of Compute to (since
	//the method necessarily has to be general).
	PropType PropertyType
	//Where the actual logic of the computed property goes. playerState will
	//be a PlayerState from a Sanitized() state, populated with all of the
	//properties enumerated in Dependencies, with other properties obscured by
	//PolicyRandom. Since it's just a PlayerState from a sanitized State, it
	//is safe to cast to the underlying PlayerState type you know it is for
	//your package for convenience. This method will be called once per
	//PlayerState in turn. The return value will be casted to PropType
	//afterward. Return an error if any state is configured in an unexpected
	//way. Note: your compute function should be resilient to values that are
	//sanitized. In many cases it makes sense to factor your compute
	//computation out into a shim that fetches the relevant properties from
	//the ShadowState and then passes them to the core computation function,
	//so that other methods can reuse the same logic. If you need more state
	//than what is available on just the playerState, consider defining
	//GlobalCompute instead.
	Compute func(playerState PlayerState) (interface{}, error)

	//If Compute is nil but GlobalCompute is non-nil, then GlobalCompute will
	//be called instead. Instead of passing in just the single playerState for
	//the player in question, it passes the fullState, as well as the
	//PlayerIndex currently being prepared for, and expects the caller to
	//return the value for the specific playerIndex.
	GlobalCompute func(state State, player PlayerIndex) (interface{}, error)
}

//StateGroupType is the top-level grouping object used in a StatePropertyRef.
type StateGroupType int

const (
	StateGroupGame StateGroupType = iota
	StateGroupPlayer
	StateGroupDynamicComponentValues
)

//A StatePropertyRef is a reference to a particular property in a State, in a
//structured way. Currently used when defining your dependencies for computed
//properties.
type StatePropertyRef struct {
	Group StateGroupType
	//DeckName is only used when Group is StateGroupDynamicComponentValues
	DeckName string
	//PropName is the specific property on the given SubStateObject specified
	//by the rest of the StatePropertyRef.
	PropName string
}

type computedPropertiesImpl struct {
	global  SubState
	players []SubState
}

func transformationForDependencies(state *state, dependencies []StatePropertyRef) *sanitizationTransformation {
	result := &sanitizationTransformation{}

	result.Game = basicTransformationForDepenencies(state.GameState().Reader())

	result.Players = make([]subStateSanitizationTransformation, len(state.playerStates))

	for i, playerState := range state.playerStates {
		result.Players[i] = basicTransformationForDepenencies(playerState.Reader())
	}

	result.DynamicComponentValues = make(map[string]subStateSanitizationTransformation)

	for deckName, deckValues := range state.dynamicComponentValues {
		result.DynamicComponentValues[deckName] = basicTransformationForDepenencies(deckValues[0].Reader())
	}

	for _, dependency := range dependencies {
		if dependency.Group == StateGroupGame {
			result.Game[dependency.PropName] = PolicyVisible
		} else if dependency.Group == StateGroupPlayer {
			for i := 0; i < len(result.Players); i++ {
				result.Players[i][dependency.PropName] = PolicyVisible
			}
		} else if dependency.Group == StateGroupDynamicComponentValues {
			result.DynamicComponentValues[dependency.DeckName][dependency.PropName] = PolicyVisible
		}
	}

	return result
}

//Creates a transformation that sets each property to Random.
func basicTransformationForDepenencies(reader PropertyReader) subStateSanitizationTransformation {
	result := make(subStateSanitizationTransformation)
	for propName, _ := range reader.Props() {
		result[propName] = PolicyRandom
	}
	return result
}

func newComputedPropertiesImpl(config *ComputedPropertiesConfig, state *state) (*computedPropertiesImpl, error) {

	if !state.calculatingComputed {
		return nil, errors.New("State didn't think it was calculatingComputed when it was")
	}

	if config == nil {
		//It's fine if no config is provided--that just means no computed
		//properties.
		return nil, nil
	}

	playerBags := make([]SubState, len(state.PlayerStates()))

	//TODO: calculate all properties.
	for i, _ := range state.PlayerStates() {
		collection := newGenericReader()

		playerBags[i] = collection

		if config.Player == nil {
			continue
		}

		reader := collection.ReadSetter()

		for name, propConfig := range config.Player {
			if err := propConfig.calculate(name, PlayerIndex(i), state, reader); err != nil {
				//TODO: do something better here.
				return nil, errors.Extend(err, "Player failed")
			}
		}

	}

	globalBag := newGenericReader()

	if config.Global != nil {
		for name, propConfig := range config.Global {
			if err := propConfig.calculate(name, state, globalBag.ReadSetter()); err != nil {
				//TODO: do something better here.
				return nil, errors.Extend(err, "global failed")
			}
		}
	}

	return &computedPropertiesImpl{
		global:  globalBag,
		players: playerBags,
	}, nil
}

func (c *ComputedGlobalPropertyDefinition) calculate(propName string, state *state, output PropertyReadSetter) error {

	result, err := c.compute(state)

	if err != nil {
		return errors.New("Error computing calculated prop: " + err.Error())
	}

	switch c.PropType {
	case TypeBool:
		boolVal, ok := result.(bool)
		if !ok {
			return errors.New("Property did not return bool as expected")
		}
		output.SetBoolProp(propName, boolVal)
	case TypeInt:
		intVal, ok := result.(int)
		if !ok {
			return errors.New("Property did not return int as expected")
		}
		output.SetIntProp(propName, intVal)
	case TypeString:
		stringVal, ok := result.(string)
		if !ok {
			return errors.New("Property did not return string as expected")
		}
		output.SetStringProp(propName, stringVal)
	case TypePlayerIndex:
		playerIndexVal, ok := result.(PlayerIndex)
		if !ok {
			return errors.New("Property did not return PlayerIndex as expected")
		}
		output.SetPlayerIndexProp(propName, playerIndexVal)
	case TypeGrowableStack:
		growableStackVal, ok := result.(*GrowableStack)
		if !ok {
			return errors.New("Property did not return growable stack as expected")
		}
		output.SetGrowableStackProp(propName, growableStackVal)
	case TypeSizedStack:
		sizedStackVal, ok := result.(*SizedStack)
		if !ok {
			return errors.New("Property did not return sized stack as expected")
		}
		output.SetSizedStackProp(propName, sizedStackVal)
	case TypeEnumConst:
		enumConstVal, ok := result.(enum.Const)
		if !ok {
			return errors.New("Property did not return enum const as expected")
		}
		output.SetEnumConstProp(propName, enumConstVal)
	case TypeEnumVar:
		enumVarVal, ok := result.(enum.Var)
		if !ok {
			return errors.New("Property did not return enum var as expected")
		}
		output.SetEnumVarProp(propName, enumVarVal)
	default:
		return errors.New("That property type, " + c.PropType.String() + " is not currently supported")
	}

	return nil

}

func (c *ComputedPlayerPropertyDefinition) calculate(propName string, playerIndex PlayerIndex, state *state, output PropertyReadSetter) error {

	result, err := c.compute(state, playerIndex)

	if err != nil {
		return errors.New("Error computing calculated prop: " + err.Error())
	}

	switch c.PropType {
	case TypeBool:
		boolVal, ok := result.(bool)
		if !ok {
			return errors.New("Property did not return bool as expected")
		}
		output.SetBoolProp(propName, boolVal)
	case TypeInt:
		intVal, ok := result.(int)
		if !ok {
			return errors.New("Property did not return int as expected")
		}
		output.SetIntProp(propName, intVal)
	case TypeString:
		stringVal, ok := result.(string)
		if !ok {
			return errors.New("Property did not return string as expected")
		}
		output.SetStringProp(propName, stringVal)
	case TypePlayerIndex:
		playerIndexVal, ok := result.(PlayerIndex)
		if !ok {
			return errors.New("Property did not return PlayerIndex as expected")
		}
		output.SetPlayerIndexProp(propName, playerIndexVal)
	case TypeGrowableStack:
		growableStackVal, ok := result.(*GrowableStack)
		if !ok {
			return errors.New("Property did not return growable stack as expected")
		}
		output.SetGrowableStackProp(propName, growableStackVal)
	case TypeSizedStack:
		sizedStackVal, ok := result.(*SizedStack)
		if !ok {
			return errors.New("Property did not return sized stack as expected")
		}
		output.SetSizedStackProp(propName, sizedStackVal)
	case TypeEnumConst:
		enumConstVal, ok := result.(enum.Const)
		if !ok {
			return errors.New("Property did not return enum const as expected")
		}
		output.SetEnumConstProp(propName, enumConstVal)
	case TypeEnumVar:
		enumVarVal, ok := result.(enum.Var)
		if !ok {
			return errors.New("Property did not return enum var as expected")
		}
		output.SetEnumVarProp(propName, enumVarVal)
	default:
		return errors.New("That property type, " + c.PropType.String() + " is not currently supported")
	}

	return nil

}

func (c *ComputedGlobalPropertyDefinition) compute(state *state) (interface{}, error) {

	//First, prepare a shadow state with all of the dependencies.

	transformation := transformationForDependencies(state, c.Dependencies)

	sanitized, err := state.applySanitizationTransformation(transformation)

	if err != nil {
		return nil, errors.Extend(err, "Couldn't create randomized state for globals")
	}

	return c.Compute(sanitized)

}

func (c *ComputedPlayerPropertyDefinition) compute(state *state, playerIndex PlayerIndex) (interface{}, error) {

	transformation := transformationForDependencies(state, c.Dependencies)

	sanitized, err := state.applySanitizationTransformation(transformation)

	if err != nil {
		return nil, errors.Extend(err, "Couldn't create randomized state for players")
	}

	if c.Compute != nil {
		return c.Compute(sanitized.PlayerStates()[playerIndex])
	}

	if c.GlobalCompute != nil {
		return c.GlobalCompute(sanitized, playerIndex)
	}

	return nil, errors.New("Neither Compute nor GlobalCompute were defined. One of them must be.")
}

func (c *computedPropertiesImpl) Global() SubState {
	return c.global
}

func (c *computedPropertiesImpl) Player(index PlayerIndex) SubState {
	return c.players[int(index)]
}

func (c *computedPropertiesImpl) MarshalJSON() ([]byte, error) {

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

	playerProperties := make([]map[string]interface{}, len(c.players))

	for i, player := range c.players {
		playerProperties[i] = make(map[string]interface{})
		for propName, _ := range player.Reader().Props() {
			val, err := player.Reader().Prop(propName)

			if err != nil {
				return nil, errors.New("Player computed prop " + propName + " for player " + strconv.Itoa(i) + " returned an error: " + err.Error())
			}
			playerProperties[i][propName] = val
		}
	}

	props := c.Global()

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

	for propName, _ := range props.Reader().Props() {
		val, err := props.Reader().Prop(propName)

		if err != nil {
			return nil, errors.New("Computed Prop " + propName + " returned an error: " + err.Error())
		}

		globalProperties[propName] = val
	}

	//TODO: can't I just have this have a default marshal JSON and then move
	//these sub-impls to the global and player group?

	result["Global"] = globalProperties

	result["Players"] = playerProperties

	return json.Marshal(result)
}
<|endoftext|>


<commit_before>package cfutil

import (
	"fmt"
	"net/url"
	"os"
	"strconv"
	"strings"

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

// Services() returns the list of services available from the
// Consul cluster
func Services() ([]string, error) {
	client, err := NewConsulClient()
	if err != nil {
		return nil, err
	}
	catalogServices, _, err := client.Catalog().Services(nil)
	if err != nil {
		return []string{}, err
	}
	var services []string
	for k := range catalogServices {
		services = append(services, k)
	}
	return services, nil
}

func DiscoverServiceURL(serviceName, tags string) (string, error) {
	client, err := NewConsulClient()
	if err != nil {
		return "", err
	}
	services, _, err := client.Catalog().Service(serviceName, tags, nil)
	if err != nil {
		return "", fmt.Errorf("Service `%s` not found: %s", serviceName, err)
	}
	if len(services) > 0 {
		return CreateURLFromServiceCatalog(services[0])
	}
	return "", fmt.Errorf("Service `%s` not found", serviceName)

}

func CreateURLFromServiceCatalog(catalog *consul.CatalogService) (string, error) {
	var serviceURL url.URL
	if catalog.ServicePort == 443 {
		serviceURL.Scheme = "https"
		serviceURL.Host = catalog.ServiceAddress
	} else {
		serviceURL.Scheme = "http"
		serviceURL.Host = fmt.Sprintf("%s:%d", catalog.ServiceAddress, catalog.ServicePort)
	}
	return serviceURL.String(), nil
}

// Use ServiceRegister() to register your app in the Consul cluster
// Optionally you can provide a health endpoint on your URL and
// a number of tags to make your service more discoverable
func ServiceRegister(name string, path string, tags ...string) error {
	appEnv, _ := Current()
	client, err := NewConsulClient()
	if err != nil {
		return err
	}
	schema, port := schemaAndPortForServices()

	appURL, _ := url.Parse(schema + "://" + appEnv.ApplicationURIs[0])
	splitted := strings.Split(appURL.Host, ":")
	hostWithoutPort := splitted[0]
	if hostWithoutPort == "" {
		hostWithoutPort = "localhost"
	}
	if len(splitted) > 1 {
		addedPort, err := strconv.Atoi(splitted[1])
		if err == nil && addedPort != port {
			port = addedPort
		}
	}

	err = client.Agent().ServiceRegister(&consul.AgentServiceRegistration{
		Name:    name,
		Address: hostWithoutPort,
		Port:    port,
		Tags:    tags,
		Check: &consul.AgentServiceCheck{
			HTTP:     fmt.Sprintf(schema + "://" + appURL.Host + path),
			Interval: "60s",
		},
	})
	if err != nil {
		return err
	}
	return nil
}

// NewConsulClient() returns a new consul client which you can use to
// access the Consul cluster HTTP API. It uses `CONSUL_MASTER` and
// `CONSUL_TOKEN` environment variables to set up the HTTP API connection.
func NewConsulClient() (*consul.Client, error) {
	dialScheme, dialHost, err := consulDialstring("consul")
	if err != nil {
		return nil, err
	}
	client, consulErr := consul.NewClient(&consul.Config{
		Address: dialHost,
		Scheme:  dialScheme,
		Token:   os.Getenv("CONSUL_TOKEN"),
	})
	if consulErr != nil {
		return nil, consulErr
	}
	return client, nil
}

func GetConsulKey(mooncoreKey string) (string, error) {
	ns := ConsulNamespace()
	key := "mooncore/" + ns + "/" + mooncoreKey
	client, err := NewConsulClient()
	if err != nil {
		return "", err
	}
	kvPair, _, err := client.KV().Get(key, nil)
	if err != nil {
		return "", err
	}
	if kvPair == nil || kvPair.Value == nil {
		return "", fmt.Errorf("Key not found: %s", mooncoreKey)
	}
	return string(kvPair.Value), nil
}

func ConsulDatacenter() (string, error) {
	client, err := NewConsulClient()
	if err != nil {
		return "", err
	}
	self, err := client.Agent().Self()
	if err != nil {
		return "", err
	}
	dc, ok := self["Config"]["Datacenter"].(string)
	if !ok {
		return "", fmt.Errorf("Invalid lookup for Datacenter")
	}
	return dc, nil
}

func ConsulNamespace() string {
	return os.Getenv("CONSUL_NAMESPACE")
}

func consulDialstring(serviceName string) (string, string, error) {
	consulMaster := ""
	if consulMaster = os.Getenv("CONSUL_MASTER"); consulMaster != "" {
		parsed, err := url.Parse(consulMaster)
		if err == nil {
			return parsed.Scheme, parsed.Host, nil
		}
	}
	return "", "", fmt.Errorf("CONSUL_MASTER not found or invalid url: %s", consulMaster)
}

func schemaAndPortForServices() (string, int) {
	if ForceHTTP() {
		return "http", 80
	}
	return "https", 443
}
<commit_msg>refactor Consul support<commit_after>package cfutil

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"

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

// Services() returns the list of services available from the
// Consul cluster
func (client *ConsulClient) Services() ([]string, error) {
	catalogServices, _, err := client.Catalog().Services(nil)
	if err != nil {
		return []string{}, err
	}
	var services []string
	for k := range catalogServices {
		services = append(services, k)
	}
	return services, nil
}

func (client *ConsulClient) DiscoverServiceURL(serviceName, tags string) (string, error) {
	services, _, err := client.Catalog().Service(serviceName, tags, nil)
	if err != nil {
		return "", fmt.Errorf("Service `%s` not found: %s", serviceName, err)
	}
	if len(services) > 0 {
		return CreateURLFromServiceCatalog(services[0])
	}
	return "", fmt.Errorf("Service `%s` not found", serviceName)
}

func CreateURLFromServiceCatalog(catalog *consul.CatalogService) (string, error) {
	var serviceURL url.URL
	if catalog.ServicePort == 443 {
		serviceURL.Scheme = "https"
		serviceURL.Host = catalog.ServiceAddress
	} else {
		serviceURL.Scheme = "http"
		serviceURL.Host = fmt.Sprintf("%s:%d", catalog.ServiceAddress, catalog.ServicePort)
	}
	return serviceURL.String(), nil
}

// Use ServiceRegister() to register your app in the Consul cluster
// Optionally you can provide a health endpoint on your URL and
// a number of tags to make your service more discoverable
func (client *ConsulClient) ServiceRegister(name string, path string, tags ...string) error {
	schema, port := schemaAndPortForServices()
	appEnv, _ := Current()

	appURL, _ := url.Parse(schema + "://" + appEnv.ApplicationURIs[0])
	splitted := strings.Split(appURL.Host, ":")
	hostWithoutPort := splitted[0]
	if hostWithoutPort == "" {
		hostWithoutPort = "localhost"
	}
	if len(splitted) > 1 {
		addedPort, err := strconv.Atoi(splitted[1])
		if err == nil && addedPort != port {
			port = addedPort
		}
	}

	err := client.Agent().ServiceRegister(&consul.AgentServiceRegistration{
		Name:    name,
		Address: hostWithoutPort,
		Port:    port,
		Tags:    tags,
		Check: &consul.AgentServiceCheck{
			HTTP:     fmt.Sprintf(schema + "://" + appURL.Host + path),
			Interval: "60s",
		},
	})
	if err != nil {
		return err
	}
	return nil
}

type ConsulClient struct {
	consul.Client
	Namespace string
	Token     string
}

// NewConsulClient() returns a new consul client which you can use to
// access the Consul cluster HTTP API. It uses `CONSUL_MASTER` and
// `CONSUL_TOKEN` environment variables to set up the HTTP API connection.
func NewConsulClient(server, namespace, token string) (*ConsulClient, error) {
	dialScheme, dialHost, err := consulDialstring(server)
	if err != nil {
		return nil, err
	}
	var cc ConsulClient
	cc.Token = token
	cc.Namespace = namespace
	client, consulErr := consul.NewClient(&consul.Config{
		Address: dialHost,
		Scheme:  dialScheme,
		Token:   token,
	})
	if consulErr != nil {
		return nil, consulErr
	}
	cc.Client = *client
	return &cc, nil
}

func (client *ConsulClient) GetConsulKey(mooncoreKey string) (string, error) {
	ns := client.Namespace
	key := "mooncore/" + ns + "/" + mooncoreKey
	kvPair, _, err := client.KV().Get(key, nil)
	if err != nil {
		return "", err
	}
	if kvPair == nil || kvPair.Value == nil {
		return "", fmt.Errorf("Key not found: %s", mooncoreKey)
	}
	return string(kvPair.Value), nil
}

func (client *ConsulClient) ConsulDatacenter() (string, error) {
	self, err := client.Agent().Self()
	if err != nil {
		return "", err
	}
	dc, ok := self["Config"]["Datacenter"].(string)
	if !ok {
		return "", fmt.Errorf("Invalid lookup for Datacenter")
	}
	return dc, nil
}

func consulDialstring(consulMaster string) (string, string, error) {
	parsed, err := url.Parse(consulMaster)
	if err == nil {
		return parsed.Scheme, parsed.Host, nil
	}
	return "", "", fmt.Errorf("Invalid URL: [%s]", consulMaster)
}

func schemaAndPortForServices() (string, int) {
	if ForceHTTP() {
		return "http", 80
	}
	return "https", 443
}
<|endoftext|>


<commit_before>package rs

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// A Rackspace account that is the minimum requirement to
// authenticate with the Rackspace service.
type Account struct {
	user   string
	apiKey string
}

// Creates a new account.
func NewAccount(user, apiKey string) *Account {
	return &Account{user: user, apiKey: apiKey}
}

// The service endpoints that make up the service catalog.
type Endpoint struct {
	InternalUrl string `json:"internalURL"`
	PublicUrl   string `json:"publicURL"`
	Region      string `json:"region"`
	TenantId    string `json:"tenantId"`
}

// The list od services our permissions allow us access to.
type ServiceCatalog struct {
	Endpoints []Endpoint `json:"endpoints"`
	Name      string     `json:"name"`
	Type      string     `json:"type"`
}

// The tennant data linked to an authenticated session token.
type Tenant struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

// The authenticated session token data.
type Token struct {
	AuthenticatedBy []string `json:"RAX-AUTH:authenticatedBy"`
	Expires         string   `json:"expires"`
	Id              string   `json:"id"`
	Tenant          Tenant   `json:"tenant"`
}

// The roles an authenticated user has access to.
type Role struct {
	Description string `json:"description"`
	Id          string `json:"id"`
	Name        string `json:"name"`
	TenantId    string `json:"tenanId"`
}

// The user profile data for this authenticated request.
type User struct {
	DefaultRegion string `json:"RAX-AUTH:defaultRegion"`
	Id            string `json:"id"`
	Name          string `json:"name"`
	Roles         []Role
}

// The permissions allowed for this authtnetication request.
type Access struct {
	ServiceCatalog []ServiceCatalog `json:"serviceCatalog"`
	Token          Token            `json:"token"`
	User           User             `json:"user"`
}

// The identity data returned from an authentication request.
type IdentityData struct {
	Access Access `json:"access"`
}

// An authentication identity used in conjunctioon with an
// account to create an authenticated session.
type Identity struct {
	url     string
	account Account
	Access  Access
}

// Creates a new identity.
func NewIdentity(url string, account Account) *Identity {
	return &Identity{url: url, account: account}
}

// Create an authenticated session.
func (i *Identity) Authenticate() error {
	// Fire off the API authentication request.
	data := fmt.Sprintf(`{"auth": {"RAX-KSKEY:apiKeyCredentials": {"username": "%s", "apiKey": "%s"}}}`, i.account.user, i.account.apiKey)
	resp, err := http.Post(i.url, "application/json", strings.NewReader(data))
	if err != nil {
		return err
	}

	// Decode the response json.
	var a IdentityData
	dec := json.NewDecoder(resp.Body)
	for {
		if err := dec.Decode(&a); err == io.EOF {
			break
		} else if err != nil {
			return err
		}
	}
	resp.Body.Close()
	i.Access = a.Access

	return nil
}
<commit_msg>* Added support for Rackspace containers. * Added AllContainers() to find all containers. * Added ContainerExists() to test if a container exists. * Added CreateContainer() to creae a container.<commit_after>package rs

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
)

var (
	client = &http.Client{}
)

// A Rackspace account that is the minimum requirement to
// authenticate with the Rackspace service.
type Account struct {
	user   string
	apiKey string
}

// Creates a new account.
func NewAccount(user, apiKey string) *Account {
	return &Account{user: user, apiKey: apiKey}
}

// The service endpoints that make up the service catalog.
type Endpoint struct {
	InternalUrl string `json:"internalURL"`
	PublicUrl   string `json:"publicURL"`
	Region      string `json:"region"`
	TenantId    string `json:"tenantId"`
}

// The list od services our permissions allow us access to.
type ServiceCatalog struct {
	Endpoints []Endpoint `json:"endpoints"`
	Name      string     `json:"name"`
	Type      string     `json:"type"`
}

// The tennant data linked to an authenticated session token.
type Tenant struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

// The authenticated session token data.
type Token struct {
	AuthenticatedBy []string `json:"RAX-AUTH:authenticatedBy"`
	Expires         string   `json:"expires"`
	Id              string   `json:"id"`
	Tenant          Tenant   `json:"tenant"`
}

// The roles an authenticated user has access to.
type Role struct {
	Description string `json:"description"`
	Id          string `json:"id"`
	Name        string `json:"name"`
	TenantId    string `json:"tenanId"`
}

// The user profile data for this authenticated request.
type User struct {
	DefaultRegion string `json:"RAX-AUTH:defaultRegion"`
	Id            string `json:"id"`
	Name          string `json:"name"`
	Roles         []Role
}

// The permissions allowed for this authtnetication request.
type Access struct {
	ServiceCatalog []ServiceCatalog `json:"serviceCatalog"`
	Token          Token            `json:"token"`
	User           User             `json:"user"`
}

// The identity data returned from an authentication request.
type IdentityData struct {
	Access Access `json:"access"`
}

// An authentication identity used in conjunctioon with an
// account to create an authenticated session.
type Identity struct {
	url     string
	account Account
	Access  Access
}

// Creates a new identity.
func NewIdentity(url string, account Account) *Identity {
	return &Identity{url: url, account: account}
}

// Create an authenticated session.
func (i *Identity) Authenticate() error {
	// Fire off the API authentication request.
	data := fmt.Sprintf(`{"auth": {"RAX-KSKEY:apiKeyCredentials": {"username": "%s", "apiKey": "%s"}}}`, i.account.user, i.account.apiKey)
	resp, err := http.Post(i.url, "application/json", strings.NewReader(data))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Decode the json response.
	var a IdentityData
	dec := json.NewDecoder(resp.Body)
	for {
		if err := dec.Decode(&a); err == io.EOF {
			break
		} else if err != nil {
			return err
		}
	}
	i.Access = a.Access

	return nil
}

// A Rackspace container.
type Container struct {
	Name         string `json:"name"`
	Count        int64  `json:"count"`
	Bytes        int64  `json:"bytes"`
	Uri          string `json:"cdn_uri"`
	StreamingUri string `json:"cdn_streaming_uri"`
	IosUri       string `json:"cdn_ios_uri"`
	SslUri       string `json:"cdn_ssl_uri"`
	Enabled      bool   `json:"cdn_enabled"`
	Ttl          int64  `json:"ttl"`
	LogRetention bool   `json:"log_retention"`
}

// Get a list of all containers.
func AllContainers(endpoint, authToken string) (*[]Container, error) {
	var containers *[]Container

	req, err := http.NewRequest("GET", endpoint+"?format=json", nil)
	if err != nil {
		return containers, err
	}
	req.Header.Add("X-Auth-Token", authToken)
	req.Header.Add("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		return containers, err
	}
	defer resp.Body.Close()

	// Decode the json response.
	dec := json.NewDecoder(resp.Body)
	for {
		if err := dec.Decode(&containers); err == io.EOF {
			break
		} else if err != nil {
			return containers, err
		}
	}

	return containers, nil
}

// Check for the existence of a container.
func ContainerExists(endpoint, authToken, name string) bool {
	containers, err := AllContainers(endpoint, authToken)
	if err != nil {
		log.Printf("Error: ContainerExists: %", err.Error())
		return false
	}

	for _, c := range *containers {
		if c.Name == name {
			return true
		}
	}

	return false
}

// Create a new container.
func CreateContainer(endpoint, authToken, name string) error {
	req, err := http.NewRequest("PUT", fmt.Sprintf("%s/%s", endpoint, name), nil)
	if err != nil {
		return err
	}
	req.Header.Add("X-Auth-Token", authToken)
	req.Header.Add("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 && resp.StatusCode != 202 {
		return fmt.Errorf("Error: cannot create container: %s: %d", name, resp.StatusCode)
	}

	return nil
}
<|endoftext|>


<commit_before>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

*/

package echo

import (
	"net/http"
	"net/url"
	"time"

	"github.com/webx-top/echo/param"
)

var (
	DefaultCookieOptions = &CookieOptions{
		Path: `/`,
	}
)

// CookieOptions cookie options
type CookieOptions struct {
	Prefix string

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
	// MaxAge>0 means Max-Age attribute present and given in seconds.
	MaxAge int

	// Expires
	Expires time.Time

	Path     string
	Domain   string
	Secure   bool
	HttpOnly bool
	SameSite string // strict / lax
}

func (c *CookieOptions) Clone() *CookieOptions {
	clone := *c
	return &clone
}

func (c *CookieOptions) SetMaxAge(maxAge int) *CookieOptions {
	c.MaxAge = maxAge
	c.Expires = param.EmptyTime
	return c
}

//Cookier interface
type Cookier interface {
	Get(key string) string
	Add(cookies ...*http.Cookie) Cookier
	Set(key string, val string, args ...interface{}) Cookier
	Send()
}

//NewCookier create a cookie instance
func NewCookier(ctx Context) Cookier {
	return &cookie{
		context: ctx,
		cookies: []*http.Cookie{},
		indexes: map[string]int{},
	}
}

type cookie struct {
	context Context
	cookies []*http.Cookie
	indexes map[string]int
}

func (c *cookie) Send() {
	for _, cookie := range c.cookies {
		if idx, ok := c.indexes[cookie.Name]; ok {
			c.cookies[idx] = cookie
			continue
		}
		c.indexes[cookie.Name] = len(c.cookies)
		c.cookies = append(c.cookies, cookie)
	}
}

func (c *cookie) Get(key string) string {
	var val string
	if v := c.context.Request().Cookie(c.context.CookieOptions().Prefix + key); len(v) > 0 {
		val, _ = url.QueryUnescape(v)
	}
	return val
}

func (c *cookie) Add(cookies ...*http.Cookie) Cookier {
	c.cookies = append(c.cookies, cookies...)
	return c
}

// Set Set cookie value
// @param string key
// @param string value
// @param int|int64|time.Duration args[0]:maxAge (seconds)
// @param string args[1]:path (/)
// @param string args[2]:domain
// @param bool args[3]:secure
// @param bool args[4]:httpOnly
// @param string args[5]:sameSite (lax/strict/default)
func (c *cookie) Set(key string, val string, args ...interface{}) Cookier {
	opt := c.context.CookieOptions()
	cookie := &http.Cookie{
		Name: opt.Prefix + key,
		Path: `/`,
	}
	switch len(args) {
	case 6:
		sameSite, _ := args[5].(string)
		CookieSameSite(cookie, sameSite)
		fallthrough
	case 5:
		httpOnly, _ := args[4].(bool)
		cookie.HttpOnly = httpOnly
		fallthrough
	case 4:
		secure, _ := args[3].(bool)
		cookie.Secure = secure
		fallthrough
	case 3:
		domain, _ := args[2].(string)
		cookie.Domain = domain
		fallthrough
	case 2:
		ppath, _ := args[1].(string)
		if len(ppath) == 0 {
			ppath = `/`
		}
		cookie.Path = ppath
		fallthrough
	case 1:
		switch v := args[0].(type) {
		case *http.Cookie:
			CopyCookieOptions(v, cookie)
		case *CookieOptions:
			cookie.MaxAge = v.MaxAge
			cookie.Expires = v.Expires
			if len(v.Path) == 0 {
				v.Path = `/`
			}
			cookie.Path = v.Path
			cookie.Domain = v.Domain
			cookie.Secure = v.Secure
			cookie.HttpOnly = v.HttpOnly
			CookieSameSite(cookie, v.SameSite)
		case int:
			CookieMaxAge(cookie, v)
		case int64:
			CookieMaxAge(cookie, int(v))
		case time.Duration:
			CookieMaxAge(cookie, int(v.Seconds()))
		case time.Time:
			CookieExpires(cookie, v)
		}
	}
	if idx, ok := c.indexes[cookie.Name]; ok {
		c.cookies[idx] = cookie
		return c
	}
	c.indexes[cookie.Name] = len(c.cookies)
	c.cookies = append(c.cookies, cookie)
	return c
}

// CookieMaxAge 设置有效时长（秒）
// IE6/7/8不支持
// 如果同时设置了MaxAge和Expires，则优先使用MaxAge
// 设置MaxAge则代表每次保存Cookie都会续期，因为MaxAge是基于保存时间来设置的
func CookieMaxAge(stdCookie *http.Cookie, p int) {
	stdCookie.MaxAge = p
	if p > 0 {
		stdCookie.Expires = time.Unix(time.Now().Unix()+int64(p), 0)
	} else if p < 0 {
		stdCookie.Expires = time.Unix(1, 0)
	} else {
		stdCookie.Expires = param.EmptyTime
	}
}

// CookieExpires 设置过期时间
// 所有浏览器都支持
// 如果仅仅设置Expires，因为过期时间是固定的，所以不会导致保存Cookie时被续期
func CookieExpires(stdCookie *http.Cookie, expires time.Time) {
	if expires.IsZero() {
		return
	}
	stdCookie.MaxAge = 0
	stdCookie.Expires = expires
}

// NewCookie 新建cookie对象
func NewCookie(key, value string, opt *CookieOptions) *http.Cookie {
	c := &http.Cookie{
		Name:     opt.Prefix + key,
		Value:    value,
		Path:     `/`,
		Domain:   opt.Domain,
		MaxAge:   opt.MaxAge,
		Expires:  opt.Expires,
		Secure:   opt.Secure,
		HttpOnly: opt.HttpOnly,
	}
	if len(opt.Path) > 0 {
		c.Path = opt.Path
	}
	if len(opt.SameSite) > 0 {
		CookieSameSite(c, opt.SameSite)
	}
	return c
}
<commit_msg>improved-cookie<commit_after>/*

   Copyright 2016 Wenhui Shen <www.webx.top>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

*/

package echo

import (
	"net/http"
	"net/url"
	"time"

	"github.com/webx-top/echo/param"
)

var (
	DefaultCookieOptions = &CookieOptions{
		Path: `/`,
	}
)

// CookieOptions cookie options
type CookieOptions struct {
	Prefix string

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
	// MaxAge>0 means Max-Age attribute present and given in seconds.
	MaxAge int

	// Expires
	Expires time.Time

	Path     string
	Domain   string
	Secure   bool
	HttpOnly bool
	SameSite string // strict / lax
}

func (c *CookieOptions) Clone() *CookieOptions {
	clone := *c
	return &clone
}

func (c *CookieOptions) SetMaxAge(maxAge int) *CookieOptions {
	c.MaxAge = maxAge
	c.Expires = param.EmptyTime
	return c
}

//Cookier interface
type Cookier interface {
	Get(key string) string
	Add(cookies ...*http.Cookie) Cookier
	Set(key string, val string, args ...interface{}) Cookier
	Send()
}

//NewCookier create a cookie instance
func NewCookier(ctx Context) Cookier {
	return &cookie{
		context: ctx,
		cookies: []*http.Cookie{},
		indexes: map[string]int{},
	}
}

type cookie struct {
	context Context
	cookies []*http.Cookie
	indexes map[string]int
}

func (c *cookie) Send() {
	for _, cookie := range c.cookies {
		c.record(cookie)
	}
}

func (c *cookie) record(stdCookie *http.Cookie) {
	if idx, ok := c.indexes[stdCookie.Name]; ok {
		c.cookies[idx] = stdCookie
		return
	}
	c.indexes[stdCookie.Name] = len(c.cookies)
	c.cookies = append(c.cookies, stdCookie)
}

func (c *cookie) Get(key string) string {
	var val string
	if v := c.context.Request().Cookie(c.context.CookieOptions().Prefix + key); len(v) > 0 {
		val, _ = url.QueryUnescape(v)
	}
	return val
}

func (c *cookie) Add(cookies ...*http.Cookie) Cookier {
	c.cookies = append(c.cookies, cookies...)
	return c
}

// Set Set cookie value
// @param string key
// @param string value
// @param int|int64|time.Duration args[0]:maxAge (seconds)
// @param string args[1]:path (/)
// @param string args[2]:domain
// @param bool args[3]:secure
// @param bool args[4]:httpOnly
// @param string args[5]:sameSite (lax/strict/default)
func (c *cookie) Set(key string, val string, args ...interface{}) Cookier {
	opt := c.context.CookieOptions()
	cookie := &http.Cookie{
		Name: opt.Prefix + key,
		Path: `/`,
	}
	switch len(args) {
	case 6:
		sameSite, _ := args[5].(string)
		CookieSameSite(cookie, sameSite)
		fallthrough
	case 5:
		httpOnly, _ := args[4].(bool)
		cookie.HttpOnly = httpOnly
		fallthrough
	case 4:
		secure, _ := args[3].(bool)
		cookie.Secure = secure
		fallthrough
	case 3:
		domain, _ := args[2].(string)
		cookie.Domain = domain
		fallthrough
	case 2:
		ppath, _ := args[1].(string)
		if len(ppath) == 0 {
			ppath = `/`
		}
		cookie.Path = ppath
		fallthrough
	case 1:
		switch v := args[0].(type) {
		case *http.Cookie:
			CopyCookieOptions(v, cookie)
		case *CookieOptions:
			cookie.MaxAge = v.MaxAge
			cookie.Expires = v.Expires
			if len(v.Path) == 0 {
				v.Path = `/`
			}
			cookie.Path = v.Path
			cookie.Domain = v.Domain
			cookie.Secure = v.Secure
			cookie.HttpOnly = v.HttpOnly
			CookieSameSite(cookie, v.SameSite)
		case int:
			CookieMaxAge(cookie, v)
		case int64:
			CookieMaxAge(cookie, int(v))
		case time.Duration:
			CookieMaxAge(cookie, int(v.Seconds()))
		case time.Time:
			CookieExpires(cookie, v)
		}
	}
	c.record(cookie)
	return c
}

// CookieMaxAge 设置有效时长（秒）
// IE6/7/8不支持
// 如果同时设置了MaxAge和Expires，则优先使用MaxAge
// 设置MaxAge则代表每次保存Cookie都会续期，因为MaxAge是基于保存时间来设置的
func CookieMaxAge(stdCookie *http.Cookie, p int) {
	stdCookie.MaxAge = p
	if p > 0 {
		stdCookie.Expires = time.Unix(time.Now().Unix()+int64(p), 0)
	} else if p < 0 {
		stdCookie.Expires = time.Unix(1, 0)
	} else {
		stdCookie.Expires = param.EmptyTime
	}
}

// CookieExpires 设置过期时间
// 所有浏览器都支持
// 如果仅仅设置Expires，因为过期时间是固定的，所以不会导致保存Cookie时被续期
func CookieExpires(stdCookie *http.Cookie, expires time.Time) {
	if expires.IsZero() {
		return
	}
	stdCookie.MaxAge = 0
	stdCookie.Expires = expires
}

// NewCookie 新建cookie对象
func NewCookie(key, value string, opt *CookieOptions) *http.Cookie {
	c := &http.Cookie{
		Name:     opt.Prefix + key,
		Value:    value,
		Path:     `/`,
		Domain:   opt.Domain,
		MaxAge:   opt.MaxAge,
		Expires:  opt.Expires,
		Secure:   opt.Secure,
		HttpOnly: opt.HttpOnly,
	}
	if len(opt.Path) > 0 {
		c.Path = opt.Path
	}
	if len(opt.SameSite) > 0 {
		CookieSameSite(c, opt.SameSite)
	}
	return c
}
<|endoftext|>


<commit_before>package wikiparse

import (
	"errors"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
)

var coordRE, nowikiRE, commentRE *regexp.Regexp

// ErrNoCoordFound is returned from ParseCoords when there's no
// coordinate date found.
var ErrNoCoordFound = errors.New("no coord data found")

var errNotSexagesimal = errors.New("not a sexagesimal value")

func init() {
	coordRE = regexp.MustCompile(`(?mi){{coord\|(.[^}]*)}}`)
	nowikiRE = regexp.MustCompile(`(?ms)<nowiki>.*</nowiki>`)
	commentRE = regexp.MustCompile(`(?ms)<!--.*-->`)
}

// Coord is Longitude/latitude pair from a coordinate match.
type Coord struct {
	Lon float64
	Lat float64
}

func dms(parts []string) (float64, error) {
	if len(parts) != 4 {
		return 0, fmt.Errorf("Wrong number of elements: %#v", parts)
	}
	rv, err := strconv.ParseFloat(parts[0], 64)
	if err != nil {
		return rv, err
	}
	f, err := strconv.ParseFloat(parts[1], 64)
	if err != nil {
		return rv, err
	}
	rv += f / 60.0
	f, err = strconv.ParseFloat(parts[2], 64)
	if err != nil {
		return rv, err
	}
	rv += f / 3600.0

	if parts[3] == "S" || parts[3] == "W" {
		rv = -rv
	}
	return rv, err
}

func parseSexagesimal(parts []string) (Coord, error) {
	if len(parts) < 8 {
		return Coord{}, errNotSexagesimal
	}
	if parts[3] != "N" && parts[3] != "S" {
		return Coord{}, errNotSexagesimal
	}
	if parts[7] != "E" && parts[7] != "W" {
		return Coord{}, errNotSexagesimal
	}

	lat, err := dms(parts[0:4])
	if err != nil {
		return Coord{}, err
	}

	lon, err := dms(parts[4:8])

	rv := Coord{
		Lat: lat,
		Lon: lon,
	}

	return rv, err
}

func parseFloat(parts []string) (Coord, error) {
	rv := Coord{}
	var err error
	if len(parts) < 2 {
		return rv, ErrNoCoordFound
	}

	offset := 0

	rv.Lat, err = strconv.ParseFloat(parts[offset], 64)
	if err != nil {
		return rv, err
	}
	offset++

	switch parts[offset] {
	case "S":
		rv.Lat = -rv.Lat
		fallthrough
	case "N":
		offset++
	}

	rv.Lon, err = strconv.ParseFloat(parts[offset], 64)
	offset++
	if len(parts) > offset && parts[offset] == "W" {
		rv.Lon = -rv.Lon
	}
	return rv, err
}

func cleanCoordParts(in []string) []string {
	out := make([]string, 0, len(in))

	firstnumber := 0
	var part string
	for firstnumber, part = range in {
		_, e := strconv.ParseFloat(part, 64)
		if e == nil {
			break
		}
	}

	for _, p := range in[firstnumber:] {
		t := strings.TrimSpace(p)
		if t != "" {
			out = append(out, t)
		}
	}

	return out
}

// ParseCoords parses geographical coordinates as specified in
// http://en.wikipedia.org/wiki/Wikipedia:WikiProject_Geographical_coordinates
func ParseCoords(text string) (Coord, error) {
	cleaned := nowikiRE.ReplaceAllString(commentRE.ReplaceAllString(text, ""), "")
	matches := coordRE.FindAllStringSubmatch(cleaned, 1)

	if len(matches) == 0 || len(matches[0]) < 2 {
		return Coord{}, ErrNoCoordFound
	}

	parts := cleanCoordParts(strings.Split(matches[0][1], "|"))

	rv, err := parseSexagesimal(parts)
	if err != nil {
		rv, err = parseFloat(parts)
	}

	if math.Abs(rv.Lat) > 90 {
		return rv, fmt.Errorf("invalid latitude: %v", rv.Lat)
	}
	if math.Abs(rv.Lon) > 180 {
		return rv, fmt.Errorf("invalid longitude: %v", rv.Lon)
	}

	return rv, err
}
<commit_msg>Minor cleanups<commit_after>package wikiparse

import (
	"errors"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
)

var coordRE, nowikiRE, commentRE *regexp.Regexp

// ErrNoCoordFound is returned from ParseCoords when there's no
// coordinate date found.
var ErrNoCoordFound = errors.New("no coord data found")

var errNotSexagesimal = errors.New("not a sexagesimal value")

func init() {
	coordRE = regexp.MustCompile(`(?mi){{coord\|(.[^}]*)}}`)
	nowikiRE = regexp.MustCompile(`(?ms)<nowiki>.*</nowiki>`)
	commentRE = regexp.MustCompile(`(?ms)<!--.*-->`)
}

// Coord is Longitude/latitude pair from a coordinate match.
type Coord struct {
	Lon, Lat float64
}

func dms(parts []string) (float64, error) {
	if len(parts) != 4 {
		return 0, fmt.Errorf("Wrong number of elements: %#v", parts)
	}
	rv, err := strconv.ParseFloat(parts[0], 64)
	if err != nil {
		return rv, err
	}
	f, err := strconv.ParseFloat(parts[1], 64)
	if err != nil {
		return rv, err
	}
	rv += f / 60.0
	f, err = strconv.ParseFloat(parts[2], 64)
	if err != nil {
		return rv, err
	}
	rv += f / 3600.0

	if parts[3] == "S" || parts[3] == "W" {
		rv = -rv
	}
	return rv, err
}

func parseSexagesimal(parts []string) (Coord, error) {
	if len(parts) < 8 {
		return Coord{}, errNotSexagesimal
	}
	if parts[3] != "N" && parts[3] != "S" {
		return Coord{}, errNotSexagesimal
	}
	if parts[7] != "E" && parts[7] != "W" {
		return Coord{}, errNotSexagesimal
	}

	lat, err := dms(parts[0:4])
	if err != nil {
		return Coord{}, err
	}

	lon, err := dms(parts[4:8])

	rv := Coord{
		Lat: lat,
		Lon: lon,
	}

	return rv, err
}

func parseFloat(parts []string) (Coord, error) {
	rv := Coord{}
	if len(parts) < 2 {
		return rv, ErrNoCoordFound
	}

	offset := 0

	var err error
	rv.Lat, err = strconv.ParseFloat(parts[offset], 64)
	if err != nil {
		return rv, err
	}
	offset++

	switch parts[offset] {
	case "S":
		rv.Lat = -rv.Lat
		fallthrough
	case "N":
		offset++
	}

	rv.Lon, err = strconv.ParseFloat(parts[offset], 64)
	offset++
	if len(parts) > offset && parts[offset] == "W" {
		rv.Lon = -rv.Lon
	}
	return rv, err
}

func cleanCoordParts(in []string) []string {
	out := make([]string, 0, len(in))

	firstnumber := 0
	var part string
	for firstnumber, part = range in {
		_, e := strconv.ParseFloat(part, 64)
		if e == nil {
			break
		}
	}

	for _, p := range in[firstnumber:] {
		t := strings.TrimSpace(p)
		if t != "" {
			out = append(out, t)
		}
	}

	return out
}

// ParseCoords parses geographical coordinates as specified in
// http://en.wikipedia.org/wiki/Wikipedia:WikiProject_Geographical_coordinates
func ParseCoords(text string) (Coord, error) {
	cleaned := nowikiRE.ReplaceAllString(commentRE.ReplaceAllString(text, ""), "")
	matches := coordRE.FindAllStringSubmatch(cleaned, 1)

	if len(matches) == 0 || len(matches[0]) < 2 {
		return Coord{}, ErrNoCoordFound
	}

	parts := cleanCoordParts(strings.Split(matches[0][1], "|"))

	rv, err := parseSexagesimal(parts)
	if err != nil {
		rv, err = parseFloat(parts)
	}

	if math.Abs(rv.Lat) > 90 {
		return rv, fmt.Errorf("invalid latitude: %v", rv.Lat)
	}
	if math.Abs(rv.Lon) > 180 {
		return rv, fmt.Errorf("invalid longitude: %v", rv.Lon)
	}

	return rv, err
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"github.com/mitchellh/goamz/aws"
	"github.com/mitchellh/goamz/s3"
	"os"
	"os/user"
	"path/filepath"
)

// S3SyncProvider implements the basic SyncProvider interface for S3
type S3SyncProvider struct {
	S3Connection *s3.S3
}

func (*S3SyncProvider) TypeID() string {
	return "s3"
}

func (*S3SyncProvider) HelpTextSummary() string {
	return `s3: transfers binaries to/from an S3 bucket`
}

func (*S3SyncProvider) HelpTextDetail() string {
	return `The "s3" provider synchronises files with a bucket on Amazon's S3 cloud storage

Required parameters in remote section of .gitconfig:
    git-lob-s3-bucket   The bucket to use as the root remote store. Will be created if
                        it doesn't already exist
    git-lob-s3-region   The AWS region to use. If not specified will use region settings
                        from your ~/.aws/config. If no region is specified, uses US East.

Example configuration:
    [remote "origin"]
        url = git@blah.com/your/usual/git/repo
        git-lob-provider = s3
        git-lob-s3-bucket = my.binary.bucket

Global AWS settings:

  Authentication is performed using the same configuration you'd use with the
  command line AWS tools. Settings are read in this order:

  1. Environment variables i.e. AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
  2. Credentials file in ~/.aws/credentials or %USERPROFILE%\.aws\credentials

  In addition, region settings are read from your config file in ~/.aws/config.
`
}

// get auth from the environment or config files
func (self *S3SyncProvider) getAuth() (aws.Auth, error) {
	auth, err := aws.EnvAuth()
	if err != nil {
		auth, err = aws.SharedAuth()
		if err != nil {
			return aws.Auth{}, errors.New("Unable to locate AWS authentication settings in environment or credentials file")
		}
	}
	return auth, nil
}

// get region from the environment or config files
func (self *S3SyncProvider) getRegion() (aws.Region, error) {
	regstr := os.Getenv("AWS_DEFAULT_REGION")
	if regstr == "" {
		// Look for config file
		profile := os.Getenv("AWS_PROFILE")
		if profile == "" {
			profile = "default"
		}

		cfgFile := os.Getenv("AWS_CONFIG_FILE")
		if cfgFile == "" {
			usr, usrerr := user.Current()
			if usrerr == nil {
				cfgFile = filepath.Join(usr.HomeDir, ".aws", "config")
			}
		}
		if cfgFile != "" {
			configmap, err := ReadConfigFile(cfgFile)
			if err == nil {
				regstr = configmap[fmt.Sprintf("%v.region", profile)]
			}
		}
	}
	if regstr != "" {
		reg, ok := aws.Regions[regstr]
		if ok {
			return reg, nil
		}
	}
	// default
	return aws.USEast, nil
}
func (self *S3SyncProvider) initS3() error {
	// Get auth - try environment first
	auth, err := self.getAuth()
	if err != nil {
		return err
	}
	region, err := self.getRegion()
	if err != nil {
		return err
	}
	self.S3Connection = s3.New(auth, region)
	return nil
}

func (*S3SyncProvider) ValidateConfig(remoteName string) error {
	bucketsetting := fmt.Sprintf("remote.%v.git-lob-s3-bucket", remoteName)
	bucket := GlobalOptions.GitConfig[bucketsetting]
	if bucket == "" {
		return fmt.Errorf("Configuration invalid for 'filesystem', missing setting %v", bucketsetting)
	}
	return nil
}
<commit_msg>Basic functionality for checking file existence<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/mitchellh/goamz/aws"
	"github.com/mitchellh/goamz/s3"
	"os"
	"os/user"
	"path/filepath"
	"strings"
)

// S3SyncProvider implements the basic SyncProvider interface for S3
type S3SyncProvider struct {
	S3Connection *s3.S3
	Buckets      []string
}

func (*S3SyncProvider) TypeID() string {
	return "s3"
}

func (*S3SyncProvider) HelpTextSummary() string {
	return `s3: transfers binaries to/from an S3 bucket`
}

func (*S3SyncProvider) HelpTextDetail() string {
	return `The "s3" provider synchronises files with a bucket on Amazon's S3 cloud storage

Required parameters in remote section of .gitconfig:
    git-lob-s3-bucket   The bucket to use as the root remote store. Must already exist.
    git-lob-s3-region   The AWS region to use. If not specified will use region settings
                        from your ~/.aws/config. If no region is specified, uses US East.

Example configuration:
    [remote "origin"]
        url = git@blah.com/your/usual/git/repo
        git-lob-provider = s3
        git-lob-s3-bucket = my.binary.bucket

Global AWS settings:

  Authentication is performed using the same configuration you'd use with the
  command line AWS tools. Settings are read in this order:

  1. Environment variables i.e. AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
  2. Credentials file in ~/.aws/credentials or %USERPROFILE%\.aws\credentials

  In addition, region settings are read from your config file in ~/.aws/config.
  See:
  http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
  for more details on the configuration process.
`
}

// get auth from the environment or config files
func (self *S3SyncProvider) getAuth() (aws.Auth, error) {
	auth, err := aws.EnvAuth()
	if err != nil {
		auth, err = aws.SharedAuth()
		if err != nil {
			return aws.Auth{}, errors.New("Unable to locate AWS authentication settings in environment or credentials file")
		}
	}
	return auth, nil
}

// get region from the environment or config files
func (self *S3SyncProvider) getRegion() (aws.Region, error) {
	regstr := os.Getenv("AWS_DEFAULT_REGION")
	if regstr == "" {
		// Look for config file
		profile := os.Getenv("AWS_PROFILE")
		if profile == "" {
			profile = "default"
		}

		cfgFile := os.Getenv("AWS_CONFIG_FILE")
		if cfgFile == "" {
			usr, usrerr := user.Current()
			if usrerr == nil {
				cfgFile = filepath.Join(usr.HomeDir, ".aws", "config")
			}
		}
		if cfgFile != "" {
			configmap, err := ReadConfigFile(cfgFile)
			if err == nil {
				regstr = configmap[fmt.Sprintf("%v.region", profile)]
			}
		}
	}
	if regstr != "" {
		reg, ok := aws.Regions[regstr]
		if ok {
			return reg, nil
		}
	}
	// default
	return aws.USEast, nil
}
func (self *S3SyncProvider) initS3() error {
	// Get auth - try environment first
	auth, err := self.getAuth()
	if err != nil {
		return err
	}
	region, err := self.getRegion()
	if err != nil {
		return err
	}
	self.S3Connection = s3.New(auth, region)

	// Read bucket list right now since we have no way to probe whether a bucket exists
	self.S3Connection.ListBuckets()

	return nil
}
func (self *S3SyncProvider) getS3Connection() (*s3.S3, error) {
	if self.S3Connection == nil {
		err := self.initS3()
		if err != nil {
			return nil, err
		}
	}
	return self.S3Connection, nil
}
func (self *S3SyncProvider) getBucketName(remoteName string) (string, error) {
	bucketsetting := fmt.Sprintf("remote.%v.git-lob-s3-bucket", remoteName)
	bucket := strings.TrimSpace(GlobalOptions.GitConfig[bucketsetting])
	if bucket == "" {
		return "", fmt.Errorf("Configuration invalid for 'filesystem', missing setting %v", bucketsetting)
	}
	return bucket, nil
}
func (self *S3SyncProvider) getBucket(remoteName string) (*s3.Bucket, error) {
	bucketname, err := self.getBucketName(remoteName)
	if err != nil {
		return nil, err
	}
	conn, err := self.getS3Connection()
	if err != nil {
		return nil, err
	}
	return conn.Bucket(bucketname), nil
}

func (self *S3SyncProvider) ValidateConfig(remoteName string) error {
	_, err := self.getBucketName(remoteName)
	if err != nil {
		return err
	}
	return nil
}

func (self *S3SyncProvider) FileExists(remoteName, filename string) bool {
	bucket, err := self.getBucket(remoteName)
	if err != nil {
		return false
	}
	key, err := bucket.GetKey(filename)
	return err == nil && key != nil
}
func (self *S3SyncProvider) FileExistsAndIsOfSize(remoteName, filename string, sz int64) bool {
	bucket, err := self.getBucket(remoteName)
	if err != nil {
		return false
	}
	key, err := bucket.GetKey(filename)
	return err == nil && key != nil && key.Size == sz
}
<|endoftext|>


<commit_before>package neurgo

import (
	"encoding/json"
	"fmt"
	"github.com/couchbaselabs/logg"
	"log"
	"time"
)

const FITNESS_THRESHOLD = 1e8

type Cortex struct {
	NodeId    *NodeId
	Sensors   []*Sensor
	Neurons   []*Neuron
	Actuators []*Actuator
	SyncChan  chan *NodeId
}

type ActuatorBarrier map[*NodeId]bool // TODO: fixme!! totally broken
type UUIDToNeuronMap map[string]*Neuron

func (cortex *Cortex) MarshalJSON() ([]byte, error) {
	return json.Marshal(
		struct {
			NodeId    *NodeId
			Sensors   []*Sensor
			Neurons   []*Neuron
			Actuators []*Actuator
		}{
			NodeId:    cortex.NodeId,
			Sensors:   cortex.Sensors,
			Neurons:   cortex.Neurons,
			Actuators: cortex.Actuators,
		})
}

func (cortex *Cortex) MarshalJSONToFile(filename string) error {
	json, err := json.Marshal(cortex)
	if err != nil {
		return err
	}
	jsonString := fmt.Sprintf("%s", json)
	logg.Log("%v", jsonString)
	WriteStringToFile(jsonString, filename)
	return nil
}

func (cortex *Cortex) String() string {
	return JsonString(cortex)
}

func (cortex *Cortex) Copy() *Cortex {

	// serialize to json
	jsonBytes, err := json.Marshal(cortex)
	if err != nil {
		log.Fatal(err)
	}

	// new cortex
	cortexCopy := &Cortex{}

	// deserialize json into new cortex
	err = json.Unmarshal(jsonBytes, cortexCopy)
	if err != nil {
		log.Fatal(err)
	}

	return cortexCopy

}

func (cortex *Cortex) Run() {

	cortex.Init()

	cortex.checkRunnable()

	// TODO: merge slices, create Runnable() interface
	// and make into single loop

	for _, sensor := range cortex.Sensors {
		go sensor.Run()
	}
	for _, neuron := range cortex.Neurons {
		go neuron.Run()
	}
	for _, actuator := range cortex.Actuators {
		go actuator.Run()
	}
}

func (cortex *Cortex) Shutdown() {
	for _, sensor := range cortex.Sensors {
		sensor.Shutdown()
	}
	for _, neuron := range cortex.Neurons {
		neuron.Shutdown()
	}
	for _, actuator := range cortex.Actuators {
		actuator.Shutdown()
	}
	cortex.SyncChan = nil
}

// Initialize/re-initialize the cortex.
func (cortex *Cortex) Init() {

	if cortex.SyncChan == nil {
		cortex.SyncChan = make(chan *NodeId, 1)
	}

	for _, sensor := range cortex.Sensors {
		sensor.Init()
	}
	for _, neuron := range cortex.Neurons {
		neuron.Init()
	}
	for _, actuator := range cortex.Actuators {
		actuator.Init()
	}

	cortex.InitOutboundConnections()

}

func (cortex *Cortex) SetSensors(sensors []*Sensor) {
	cortex.Sensors = sensors
	for _, sensor := range cortex.Sensors {
		sensor.Cortex = cortex
	}
}

func (cortex *Cortex) SetNeurons(neurons []*Neuron) {
	cortex.Neurons = neurons
	for _, neuron := range cortex.Neurons {
		neuron.Cortex = cortex
	}
}

func (cortex *Cortex) SetActuators(actuators []*Actuator) {
	cortex.Actuators = actuators
	for _, actuator := range cortex.Actuators {
		actuator.Cortex = cortex
	}
}

func (cortex *Cortex) NeuronUUIDMap() UUIDToNeuronMap {
	neuronUUIDMap := make(UUIDToNeuronMap)
	for _, neuron := range cortex.Neurons {
		neuronUUIDMap[neuron.NodeId.UUID] = neuron
	}
	return neuronUUIDMap
}

func (cortex *Cortex) CreateNeuronInLayer(layerIndex float64) *Neuron {
	uuid := NewUuid()
	neuron := &Neuron{
		ActivationFunction: RandomEncodableActivation(),
		NodeId:             NewNeuronId(uuid, layerIndex),
		Bias:               RandomBias(),
	}
	neuron.Cortex = cortex

	neuron.Init()

	cortex.Neurons = append(cortex.Neurons, neuron)

	return neuron
}

func (cortex *Cortex) SensorNodeIds() []*NodeId {
	nodeIds := make([]*NodeId, 0)
	for _, sensor := range cortex.Sensors {
		nodeIds = append(nodeIds, sensor.NodeId)
	}
	return nodeIds
}

func (cortex *Cortex) NeuronNodeIds() []*NodeId {
	nodeIds := make([]*NodeId, 0)
	for _, neuron := range cortex.Neurons {
		nodeIds = append(nodeIds, neuron.NodeId)
	}
	return nodeIds
}

func (cortex *Cortex) ActuatorNodeIds() []*NodeId {
	nodeIds := make([]*NodeId, 0)
	for _, actuator := range cortex.Actuators {
		nodeIds = append(nodeIds, actuator.NodeId)
	}
	return nodeIds

}

func (cortex *Cortex) AllNodeIds() []*NodeId {
	neuronNodeIds := cortex.NeuronNodeIds()
	sensorNodeIds := cortex.SensorNodeIds()
	actuatorNodeIds := cortex.ActuatorNodeIds()
	availableNodeIds := append(neuronNodeIds, sensorNodeIds...)
	availableNodeIds = append(availableNodeIds, actuatorNodeIds...)
	return availableNodeIds
}

func (cortex *Cortex) NeuronLayerMap() LayerToNeuronMap {
	layerToNeuronMap := make(LayerToNeuronMap)
	for _, neuron := range cortex.Neurons {
		if _, ok := layerToNeuronMap[neuron.NodeId.LayerIndex]; !ok {
			neurons := make([]*Neuron, 0)
			neurons = append(neurons, neuron)
			layerToNeuronMap[neuron.NodeId.LayerIndex] = neurons
		} else {
			neurons := layerToNeuronMap[neuron.NodeId.LayerIndex]
			neurons = append(neurons, neuron)
			layerToNeuronMap[neuron.NodeId.LayerIndex] = neurons
		}

	}
	return layerToNeuronMap
}

func (cortex *Cortex) NodeIdLayerMap() LayerToNodeIdMap {
	layerToNodeIdMap := make(LayerToNodeIdMap)
	for _, nodeId := range cortex.AllNodeIds() {
		if _, ok := layerToNodeIdMap[nodeId.LayerIndex]; !ok {
			nodeIds := make([]*NodeId, 0)
			nodeIds = append(nodeIds, nodeId)
			layerToNodeIdMap[nodeId.LayerIndex] = nodeIds
		} else {
			nodeIds := layerToNodeIdMap[nodeId.LayerIndex]
			nodeIds = append(nodeIds, nodeId)
			layerToNodeIdMap[nodeId.LayerIndex] = nodeIds
		}

	}
	return layerToNodeIdMap
}

// We may be in a state where the outbound connections
// do not have data channels associated with them, even
// though the data channels exist.  (eg, when deserializing
// from json).  Fix this by seeking out those outbound
// connections and setting the data channels.
func (cortex *Cortex) InitOutboundConnections() {

	// build a nodeId -> dataChan map
	nodeIdToDataMsg := cortex.nodeIdToDataMsg()

	// walk all sensors and neurons and fix up their outbound connections
	for _, sensor := range cortex.Sensors {
		sensor.initOutboundConnections(nodeIdToDataMsg)
	}
	for _, neuron := range cortex.Neurons {
		neuron.initOutboundConnections(nodeIdToDataMsg)
	}

}

func (cortex *Cortex) shutdownOutboundConnections() {

	// walk all sensors and neurons and shutdown their outbound connections
	for _, sensor := range cortex.Sensors {
		sensor.shutdownOutboundConnections()
	}
	for _, neuron := range cortex.Neurons {
		neuron.shutdownOutboundConnections()
	}

}

func (cortex *Cortex) nodeIdToDataMsg() nodeIdToDataMsgMap {
	nodeIdToDataMsg := make(nodeIdToDataMsgMap)
	for _, neuron := range cortex.Neurons {
		nodeIdToDataMsg[neuron.NodeId.UUID] = neuron.DataChan
	}
	for _, actuator := range cortex.Actuators {
		nodeIdToDataMsg[actuator.NodeId.UUID] = actuator.DataChan
	}
	return nodeIdToDataMsg

}

func (cortex *Cortex) checkRunnable() {
	if cortex.SyncChan == nil {
		log.Panicf("cortex.SyncChan is nil")
	}
}

func (cortex *Cortex) Verify(samples []*TrainingSample) bool {
	fitness := cortex.Fitness(samples)
	return fitness >= FITNESS_THRESHOLD
}

func (cortex *Cortex) Fitness(samples []*TrainingSample) float64 {

	cortex.Init() // TODO: I think this is redundant

	errorAccumulated := float64(0)

	// assumes there is only one sensor and one actuator
	// (to support more, this method will require more coding)
	if len(cortex.Sensors) != 1 {
		log.Panicf("Must have exactly one sensor")
	}
	if len(cortex.Actuators) != 1 {
		log.Panicf("Must have exactly one actuator")
	}

	// install function to sensor which will stream training samples
	sensor := cortex.Sensors[0]
	sensorFunc := func(syncCounter int) []float64 {
		sampleX := samples[syncCounter]
		return sampleX.SampleInputs[0]
	}
	sensor.SensorFunction = sensorFunc

	// install function to actuator which will collect outputs
	actuator := cortex.Actuators[0]
	numTimesFuncCalled := 0
	actuatorFunc := func(outputs []float64) {
		expected := samples[numTimesFuncCalled].ExpectedOutputs[0]
		error := SumOfSquaresError(expected, outputs)
		errorAccumulated += error
		numTimesFuncCalled += 1
		cortex.SyncChan <- actuator.NodeId
	}
	actuator.ActuatorFunction = actuatorFunc

	go cortex.Run()

	for _ = range samples {
		cortex.SyncSensors()
		cortex.SyncActuators()
	}

	cortex.Shutdown()

	// calculate fitness
	fitness := float64(1) / errorAccumulated

	return fitness

}

func (cortex *Cortex) FindSensor(nodeId *NodeId) *Sensor {
	for _, sensor := range cortex.Sensors {
		if sensor.NodeId.UUID == nodeId.UUID {
			return sensor
		}
	}
	return nil
}

func (cortex *Cortex) FindNeuron(nodeId *NodeId) *Neuron {
	for _, neuron := range cortex.Neurons {
		if neuron.NodeId.UUID == nodeId.UUID {
			return neuron
		}
	}
	return nil
}

func (cortex *Cortex) FindActuator(nodeId *NodeId) *Actuator {
	for _, actuator := range cortex.Actuators {
		if actuator.NodeId.UUID == nodeId.UUID {
			return actuator
		}
	}
	return nil
}

// TODO: rename to FindOutboundConnector
func (cortex *Cortex) FindConnector(nodeId *NodeId) OutboundConnector {
	for _, sensor := range cortex.Sensors {
		if sensor.NodeId.UUID == nodeId.UUID {
			return sensor
		}
	}
	for _, neuron := range cortex.Neurons {
		if neuron.NodeId.UUID == nodeId.UUID {
			return neuron
		}
	}
	return nil
}

func (cortex *Cortex) FindInboundConnector(nodeId *NodeId) InboundConnector {
	for _, neuron := range cortex.Neurons {
		if neuron.NodeId.UUID == nodeId.UUID {
			return neuron
		}
	}
	for _, actuator := range cortex.Actuators {
		if actuator.NodeId.UUID == nodeId.UUID {
			return actuator
		}
	}

	return nil
}

func (cortex *Cortex) SyncSensors() {
	for _, sensor := range cortex.Sensors {
		select {
		case sensor.SyncChan <- true:
		case <-time.After(time.Second):
			log.Panicf("Cortex unable to send Sync message to sensor %v", sensor)
		}
	}

}

func (cortex *Cortex) SyncActuators() {
	actuatorBarrier := cortex.createActuatorBarrier()
	for {

		select {
		case senderNodeId := <-cortex.SyncChan:
			actuatorBarrier[senderNodeId] = true
		case <-time.After(time.Second):
			log.Panicf("Timeout waiting for actuator sync message")
		}

		if cortex.isBarrierSatisfied(actuatorBarrier) {
			break
		}

	}
}

func (cortex *Cortex) Validate() bool {

	for _, neuron := range cortex.Neurons {
		if neuron.Cortex == nil {
			logg.LogWarn("Neuron: %v has no cortex", neuron)
			return false
		}
	}
	return true
}

func (cortex *Cortex) Repair() {

	for _, neuron := range cortex.Neurons {
		if neuron.Cortex == nil {
			neuron.Cortex = cortex
		}
	}

}

func (cortex *Cortex) createActuatorBarrier() ActuatorBarrier {
	actuatorBarrier := make(ActuatorBarrier)
	for _, actuator := range cortex.Actuators {
		actuatorBarrier[actuator.NodeId] = false
	}
	return actuatorBarrier
}

func (cortex *Cortex) isBarrierSatisfied(barrier ActuatorBarrier) bool {
	for _, value := range barrier {
		if value == false {
			return false
		}
	}
	return true
}
<commit_msg>remove comment<commit_after>package neurgo

import (
	"encoding/json"
	"fmt"
	"github.com/couchbaselabs/logg"
	"log"
	"time"
)

const FITNESS_THRESHOLD = 1e8

type Cortex struct {
	NodeId    *NodeId
	Sensors   []*Sensor
	Neurons   []*Neuron
	Actuators []*Actuator
	SyncChan  chan *NodeId
}

type ActuatorBarrier map[*NodeId]bool // TODO: fixme!! totally broken
type UUIDToNeuronMap map[string]*Neuron

func (cortex *Cortex) MarshalJSON() ([]byte, error) {
	return json.Marshal(
		struct {
			NodeId    *NodeId
			Sensors   []*Sensor
			Neurons   []*Neuron
			Actuators []*Actuator
		}{
			NodeId:    cortex.NodeId,
			Sensors:   cortex.Sensors,
			Neurons:   cortex.Neurons,
			Actuators: cortex.Actuators,
		})
}

func (cortex *Cortex) MarshalJSONToFile(filename string) error {
	json, err := json.Marshal(cortex)
	if err != nil {
		return err
	}
	jsonString := fmt.Sprintf("%s", json)
	logg.Log("%v", jsonString)
	WriteStringToFile(jsonString, filename)
	return nil
}

func (cortex *Cortex) String() string {
	return JsonString(cortex)
}

func (cortex *Cortex) Copy() *Cortex {

	// serialize to json
	jsonBytes, err := json.Marshal(cortex)
	if err != nil {
		log.Fatal(err)
	}

	// new cortex
	cortexCopy := &Cortex{}

	// deserialize json into new cortex
	err = json.Unmarshal(jsonBytes, cortexCopy)
	if err != nil {
		log.Fatal(err)
	}

	return cortexCopy

}

func (cortex *Cortex) Run() {

	cortex.Init()

	cortex.checkRunnable()

	// TODO: merge slices, create Runnable() interface
	// and make into single loop

	for _, sensor := range cortex.Sensors {
		go sensor.Run()
	}
	for _, neuron := range cortex.Neurons {
		go neuron.Run()
	}
	for _, actuator := range cortex.Actuators {
		go actuator.Run()
	}
}

func (cortex *Cortex) Shutdown() {
	for _, sensor := range cortex.Sensors {
		sensor.Shutdown()
	}
	for _, neuron := range cortex.Neurons {
		neuron.Shutdown()
	}
	for _, actuator := range cortex.Actuators {
		actuator.Shutdown()
	}
	cortex.SyncChan = nil
}

// Initialize/re-initialize the cortex.
func (cortex *Cortex) Init() {

	if cortex.SyncChan == nil {
		cortex.SyncChan = make(chan *NodeId, 1)
	}

	for _, sensor := range cortex.Sensors {
		sensor.Init()
	}
	for _, neuron := range cortex.Neurons {
		neuron.Init()
	}
	for _, actuator := range cortex.Actuators {
		actuator.Init()
	}

	cortex.InitOutboundConnections()

}

func (cortex *Cortex) SetSensors(sensors []*Sensor) {
	cortex.Sensors = sensors
	for _, sensor := range cortex.Sensors {
		sensor.Cortex = cortex
	}
}

func (cortex *Cortex) SetNeurons(neurons []*Neuron) {
	cortex.Neurons = neurons
	for _, neuron := range cortex.Neurons {
		neuron.Cortex = cortex
	}
}

func (cortex *Cortex) SetActuators(actuators []*Actuator) {
	cortex.Actuators = actuators
	for _, actuator := range cortex.Actuators {
		actuator.Cortex = cortex
	}
}

func (cortex *Cortex) NeuronUUIDMap() UUIDToNeuronMap {
	neuronUUIDMap := make(UUIDToNeuronMap)
	for _, neuron := range cortex.Neurons {
		neuronUUIDMap[neuron.NodeId.UUID] = neuron
	}
	return neuronUUIDMap
}

func (cortex *Cortex) CreateNeuronInLayer(layerIndex float64) *Neuron {
	uuid := NewUuid()
	neuron := &Neuron{
		ActivationFunction: RandomEncodableActivation(),
		NodeId:             NewNeuronId(uuid, layerIndex),
		Bias:               RandomBias(),
	}
	neuron.Cortex = cortex

	neuron.Init()

	cortex.Neurons = append(cortex.Neurons, neuron)

	return neuron
}

func (cortex *Cortex) SensorNodeIds() []*NodeId {
	nodeIds := make([]*NodeId, 0)
	for _, sensor := range cortex.Sensors {
		nodeIds = append(nodeIds, sensor.NodeId)
	}
	return nodeIds
}

func (cortex *Cortex) NeuronNodeIds() []*NodeId {
	nodeIds := make([]*NodeId, 0)
	for _, neuron := range cortex.Neurons {
		nodeIds = append(nodeIds, neuron.NodeId)
	}
	return nodeIds
}

func (cortex *Cortex) ActuatorNodeIds() []*NodeId {
	nodeIds := make([]*NodeId, 0)
	for _, actuator := range cortex.Actuators {
		nodeIds = append(nodeIds, actuator.NodeId)
	}
	return nodeIds

}

func (cortex *Cortex) AllNodeIds() []*NodeId {
	neuronNodeIds := cortex.NeuronNodeIds()
	sensorNodeIds := cortex.SensorNodeIds()
	actuatorNodeIds := cortex.ActuatorNodeIds()
	availableNodeIds := append(neuronNodeIds, sensorNodeIds...)
	availableNodeIds = append(availableNodeIds, actuatorNodeIds...)
	return availableNodeIds
}

func (cortex *Cortex) NeuronLayerMap() LayerToNeuronMap {
	layerToNeuronMap := make(LayerToNeuronMap)
	for _, neuron := range cortex.Neurons {
		if _, ok := layerToNeuronMap[neuron.NodeId.LayerIndex]; !ok {
			neurons := make([]*Neuron, 0)
			neurons = append(neurons, neuron)
			layerToNeuronMap[neuron.NodeId.LayerIndex] = neurons
		} else {
			neurons := layerToNeuronMap[neuron.NodeId.LayerIndex]
			neurons = append(neurons, neuron)
			layerToNeuronMap[neuron.NodeId.LayerIndex] = neurons
		}

	}
	return layerToNeuronMap
}

func (cortex *Cortex) NodeIdLayerMap() LayerToNodeIdMap {
	layerToNodeIdMap := make(LayerToNodeIdMap)
	for _, nodeId := range cortex.AllNodeIds() {
		if _, ok := layerToNodeIdMap[nodeId.LayerIndex]; !ok {
			nodeIds := make([]*NodeId, 0)
			nodeIds = append(nodeIds, nodeId)
			layerToNodeIdMap[nodeId.LayerIndex] = nodeIds
		} else {
			nodeIds := layerToNodeIdMap[nodeId.LayerIndex]
			nodeIds = append(nodeIds, nodeId)
			layerToNodeIdMap[nodeId.LayerIndex] = nodeIds
		}

	}
	return layerToNodeIdMap
}

// We may be in a state where the outbound connections
// do not have data channels associated with them, even
// though the data channels exist.  (eg, when deserializing
// from json).  Fix this by seeking out those outbound
// connections and setting the data channels.
func (cortex *Cortex) InitOutboundConnections() {

	// build a nodeId -> dataChan map
	nodeIdToDataMsg := cortex.nodeIdToDataMsg()

	// walk all sensors and neurons and fix up their outbound connections
	for _, sensor := range cortex.Sensors {
		sensor.initOutboundConnections(nodeIdToDataMsg)
	}
	for _, neuron := range cortex.Neurons {
		neuron.initOutboundConnections(nodeIdToDataMsg)
	}

}

func (cortex *Cortex) shutdownOutboundConnections() {

	// walk all sensors and neurons and shutdown their outbound connections
	for _, sensor := range cortex.Sensors {
		sensor.shutdownOutboundConnections()
	}
	for _, neuron := range cortex.Neurons {
		neuron.shutdownOutboundConnections()
	}

}

func (cortex *Cortex) nodeIdToDataMsg() nodeIdToDataMsgMap {
	nodeIdToDataMsg := make(nodeIdToDataMsgMap)
	for _, neuron := range cortex.Neurons {
		nodeIdToDataMsg[neuron.NodeId.UUID] = neuron.DataChan
	}
	for _, actuator := range cortex.Actuators {
		nodeIdToDataMsg[actuator.NodeId.UUID] = actuator.DataChan
	}
	return nodeIdToDataMsg

}

func (cortex *Cortex) checkRunnable() {
	if cortex.SyncChan == nil {
		log.Panicf("cortex.SyncChan is nil")
	}
}

func (cortex *Cortex) Verify(samples []*TrainingSample) bool {
	fitness := cortex.Fitness(samples)
	return fitness >= FITNESS_THRESHOLD
}

func (cortex *Cortex) Fitness(samples []*TrainingSample) float64 {

	cortex.Init()

	errorAccumulated := float64(0)

	// assumes there is only one sensor and one actuator
	// (to support more, this method will require more coding)
	if len(cortex.Sensors) != 1 {
		log.Panicf("Must have exactly one sensor")
	}
	if len(cortex.Actuators) != 1 {
		log.Panicf("Must have exactly one actuator")
	}

	// install function to sensor which will stream training samples
	sensor := cortex.Sensors[0]
	sensorFunc := func(syncCounter int) []float64 {
		sampleX := samples[syncCounter]
		return sampleX.SampleInputs[0]
	}
	sensor.SensorFunction = sensorFunc

	// install function to actuator which will collect outputs
	actuator := cortex.Actuators[0]
	numTimesFuncCalled := 0
	actuatorFunc := func(outputs []float64) {
		expected := samples[numTimesFuncCalled].ExpectedOutputs[0]
		error := SumOfSquaresError(expected, outputs)
		errorAccumulated += error
		numTimesFuncCalled += 1
		cortex.SyncChan <- actuator.NodeId
	}
	actuator.ActuatorFunction = actuatorFunc

	go cortex.Run()

	for _ = range samples {
		cortex.SyncSensors()
		cortex.SyncActuators()
	}

	cortex.Shutdown()

	// calculate fitness
	fitness := float64(1) / errorAccumulated

	return fitness

}

func (cortex *Cortex) FindSensor(nodeId *NodeId) *Sensor {
	for _, sensor := range cortex.Sensors {
		if sensor.NodeId.UUID == nodeId.UUID {
			return sensor
		}
	}
	return nil
}

func (cortex *Cortex) FindNeuron(nodeId *NodeId) *Neuron {
	for _, neuron := range cortex.Neurons {
		if neuron.NodeId.UUID == nodeId.UUID {
			return neuron
		}
	}
	return nil
}

func (cortex *Cortex) FindActuator(nodeId *NodeId) *Actuator {
	for _, actuator := range cortex.Actuators {
		if actuator.NodeId.UUID == nodeId.UUID {
			return actuator
		}
	}
	return nil
}

// TODO: rename to FindOutboundConnector
func (cortex *Cortex) FindConnector(nodeId *NodeId) OutboundConnector {
	for _, sensor := range cortex.Sensors {
		if sensor.NodeId.UUID == nodeId.UUID {
			return sensor
		}
	}
	for _, neuron := range cortex.Neurons {
		if neuron.NodeId.UUID == nodeId.UUID {
			return neuron
		}
	}
	return nil
}

func (cortex *Cortex) FindInboundConnector(nodeId *NodeId) InboundConnector {
	for _, neuron := range cortex.Neurons {
		if neuron.NodeId.UUID == nodeId.UUID {
			return neuron
		}
	}
	for _, actuator := range cortex.Actuators {
		if actuator.NodeId.UUID == nodeId.UUID {
			return actuator
		}
	}

	return nil
}

func (cortex *Cortex) SyncSensors() {
	for _, sensor := range cortex.Sensors {
		select {
		case sensor.SyncChan <- true:
		case <-time.After(time.Second):
			log.Panicf("Cortex unable to send Sync message to sensor %v", sensor)
		}
	}

}

func (cortex *Cortex) SyncActuators() {
	actuatorBarrier := cortex.createActuatorBarrier()
	for {

		select {
		case senderNodeId := <-cortex.SyncChan:
			actuatorBarrier[senderNodeId] = true
		case <-time.After(time.Second):
			log.Panicf("Timeout waiting for actuator sync message")
		}

		if cortex.isBarrierSatisfied(actuatorBarrier) {
			break
		}

	}
}

func (cortex *Cortex) Validate() bool {

	for _, neuron := range cortex.Neurons {
		if neuron.Cortex == nil {
			logg.LogWarn("Neuron: %v has no cortex", neuron)
			return false
		}
	}
	return true
}

func (cortex *Cortex) Repair() {

	for _, neuron := range cortex.Neurons {
		if neuron.Cortex == nil {
			neuron.Cortex = cortex
		}
	}

}

func (cortex *Cortex) createActuatorBarrier() ActuatorBarrier {
	actuatorBarrier := make(ActuatorBarrier)
	for _, actuator := range cortex.Actuators {
		actuatorBarrier[actuator.NodeId] = false
	}
	return actuatorBarrier
}

func (cortex *Cortex) isBarrierSatisfied(barrier ActuatorBarrier) bool {
	for _, value := range barrier {
		if value == false {
			return false
		}
	}
	return true
}
<|endoftext|>