<commit_before>package neurgo

import (
	"testing"
	"github.com/couchbaselabs/go.assert"
	"log"
)

func TestNetworkVerify(t *testing.T) {

	// create network nodes
	neuron1 := &Neuron{Bias: 10, ActivationFunction: identity_activation}
	neuron2 := &Neuron{Bias: 10, ActivationFunction: identity_activation}
	sensor := &Sensor{}
	actuator := &Actuator{}

	// give nodes names
	sensor.Name = "sensor"
	actuator.Name = "actuator"
	neuron1.Name = "neuron1"
	neuron2.Name = "neuron2"

	// connect nodes together
	weights := []float64{20,20,20,20,20}
	sensor.ConnectBidirectionalWeighted(neuron1, weights)
	sensor.ConnectBidirectionalWeighted(neuron2, weights)
	neuron1.ConnectBidirectional(actuator)
	neuron2.ConnectBidirectional(actuator)

	// inputs + expected outputs
	examples := []*TrainingSample{{sampleInputs: [][]float64{[]float64{1,1,1,1,1}}, expectedOutputs: [][]float64{[]float64{110,110}}}}

	// create neural network
	sensors := []*Sensor{sensor}
	actuators := []*Actuator{actuator}
	neuralNet := &NeuralNetwork{sensors: sensors, actuators: actuators}

	// spinup node goroutines
	signallers := []Signaller{neuron1, neuron2, sensor, actuator}
	for _, signaller := range signallers {
		go Run(signaller)
	}

	// verify neural network
	verified := neuralNet.Verify(examples)
	assert.True(t, verified)

	// make sure injectors/wiretaps have been removed
	assert.Equals(t, len(sensor.inbound), 0)
	assert.Equals(t, len(actuator.outbound), 0)

}

func TestXnorNetwork(t *testing.T) {

	// create network nodes
	input_neuron1 := &Neuron{Bias: 0, ActivationFunction: identity_activation}
	input_neuron2 := &Neuron{Bias: 0, ActivationFunction: identity_activation}
	hidden_neuron1 := &Neuron{Bias: -30, ActivationFunction: sigmoid}
	hidden_neuron2 := &Neuron{Bias: 10, ActivationFunction: sigmoid}
	output_neuron := &Neuron{Bias: -10, ActivationFunction: sigmoid}
	sensor1 := &Sensor{}
	sensor2 := &Sensor{}
	actuator := &Actuator{}

	// give names to network nodes
	sensor1.Name = "sensor1"
	sensor2.Name = "sensor2"
	input_neuron1.Name = "input_neuron1"
	input_neuron2.Name = "input_neuron2"
	hidden_neuron1.Name = "hidden_neuron1"
	hidden_neuron2.Name = "hidden_neuron2"
	output_neuron.Name = "output_neuron"
	actuator.Name = "actuator"

	// connect nodes together
	sensor1.ConnectBidirectionalWeighted(input_neuron1, []float64{1})
	sensor2.ConnectBidirectionalWeighted(input_neuron2, []float64{1})
	input_neuron1.ConnectBidirectionalWeighted(hidden_neuron1, []float64{20})
	input_neuron2.ConnectBidirectionalWeighted(hidden_neuron1, []float64{20})
	input_neuron1.ConnectBidirectionalWeighted(hidden_neuron2, []float64{-20})
	input_neuron2.ConnectBidirectionalWeighted(hidden_neuron2, []float64{-20})
	hidden_neuron1.ConnectBidirectionalWeighted(output_neuron, []float64{20})
	hidden_neuron2.ConnectBidirectionalWeighted(output_neuron, []float64{20})
	output_neuron.ConnectBidirectional(actuator)

	// create neural network
	sensors := []*Sensor{sensor1, sensor2}
	actuators := []*Actuator{actuator}
	neuralNet := &NeuralNetwork{sensors: sensors, actuators: actuators}

	// inputs + expected outputs
	examples := []*TrainingSample{

		// TODO: how to wrap this?
		{sampleInputs: [][]float64{[]float64{0},[]float64{1}}, expectedOutputs: [][]float64{[]float64{0}}},
		{sampleInputs: [][]float64{[]float64{1},[]float64{1}}, expectedOutputs: [][]float64{[]float64{1}}},
		{sampleInputs: [][]float64{[]float64{1},[]float64{0}}, expectedOutputs: [][]float64{[]float64{0}}},
		{sampleInputs: [][]float64{[]float64{0},[]float64{0}}, expectedOutputs: [][]float64{[]float64{1}}}}

	// spinup node goroutines
	signallers := []Signaller{input_neuron1, input_neuron2, hidden_neuron1, hidden_neuron2, output_neuron, sensor1, sensor2, actuator}
	for _, signaller := range signallers {
		go Run(signaller)
	}

	// verify neural network
	verified := neuralNet.Verify(examples)
	assert.True(t, verified)

}

func xnorCondensedNetwork() *NeuralNetwork {

	// create network nodes
	hidden_neuron1 := &Neuron{Bias: -30, ActivationFunction: sigmoid}
	hidden_neuron2 := &Neuron{Bias: 10, ActivationFunction: sigmoid}
	output_neuron := &Neuron{Bias: -10, ActivationFunction: sigmoid}
	sensor := &Sensor{}
	actuator := &Actuator{}

	// give names to network nodes
	sensor.Name = "sensor"
	hidden_neuron1.Name = "hidden_neuron1"
	hidden_neuron2.Name = "hidden_neuron2"
	output_neuron.Name = "output_neuron"
	actuator.Name = "actuator"

	// connect nodes together
	sensor.ConnectBidirectionalWeighted(hidden_neuron1, []float64{20,20})
	sensor.ConnectBidirectionalWeighted(hidden_neuron2, []float64{-20, -20})
	hidden_neuron1.ConnectBidirectionalWeighted(output_neuron, []float64{20})
	hidden_neuron2.ConnectBidirectionalWeighted(output_neuron, []float64{20})
	output_neuron.ConnectBidirectional(actuator)

	// create neural network
	sensors := []*Sensor{sensor}
	actuators := []*Actuator{actuator}
	neuralNet := &NeuralNetwork{sensors: sensors, actuators: actuators}

	// spinup node goroutines
	signallers := []Signaller{sensor, hidden_neuron1, hidden_neuron2, output_neuron, actuator}
	for _, signaller := range signallers {
		go Run(signaller)
	}

	return neuralNet
}

func TestXnorCondensedNetwork(t *testing.T) {

	// identical to TestXnorNetwork, but uses single sensor with vector outputs, removes
	// the input layer neurons which are useless

	neuralNet := xnorCondensedNetwork()

	// inputs + expected outputs
	examples := []*TrainingSample{

		// TODO: how to wrap this?
		{sampleInputs: [][]float64{[]float64{0, 1}}, expectedOutputs: [][]float64{[]float64{0}}},
		{sampleInputs: [][]float64{[]float64{1, 1}}, expectedOutputs: [][]float64{[]float64{1}}},
		{sampleInputs: [][]float64{[]float64{1, 0}}, expectedOutputs: [][]float64{[]float64{0}}},
		{sampleInputs: [][]float64{[]float64{0, 0}}, expectedOutputs: [][]float64{[]float64{1}}}}

	// verify neural network
	verified := neuralNet.Verify(examples)
	assert.True(t, verified)

}

func TestCopy(t *testing.T) {

	neuralNet := xnorCondensedNetwork()
	neuralNetCopy := neuralNet.Copy()

	assert.NotEquals(t, neuralNet, neuralNetCopy)
	assert.Equals(t, len(neuralNet.sensors), len(neuralNetCopy.sensors))
	assert.NotEquals(t, neuralNet.sensors[0], neuralNetCopy.sensors[0])
	assert.Equals(t, neuralNet.sensors[0].Name, neuralNetCopy.sensors[0].Name)
	assert.Equals(t, len(neuralNet.actuators), len(neuralNetCopy.actuators))
	assert.NotEquals(t, neuralNet.actuators[0], neuralNetCopy.actuators[0])

	assert.Equals(t, len(neuralNet.sensors[0].outbound), len(neuralNetCopy.sensors[0].outbound))
	assert.NotEquals(t, neuralNet.sensors[0].outbound[0], neuralNetCopy.sensors[0].outbound[0])

	assert.False(t, neuralNetCopy.sensors[0].outbound[0].channel == nil)
	assert.Equals(t, len(neuralNet.actuators[0].inbound), len(neuralNetCopy.actuators[0].inbound))

	assert.Equals(t, len(neuralNetCopy.sensors[0].outbound[0].other.inboundConnections()), len(neuralNet.sensors[0].outbound[0].other.inboundConnections()))

	assert.True(t, neuralNetCopy.sensors[0].outbound[0].channel == neuralNetCopy.sensors[0].outbound[0].other.inboundConnections()[0].channel)

	assert.NotEquals(t, neuralNet.actuators[0].inbound[0], neuralNetCopy.actuators[0].inbound[0])
	assert.Equals(t, len(neuralNetCopy.sensors[0].outbound[0].other.inboundConnections()[0].weights), len(neuralNet.sensors[0].outbound[0].other.inboundConnections()[0].weights))

	log.Printf("")

}

<commit_msg>refactor training samples to its own function<commit_after>package neurgo

import (
	"testing"
	"github.com/couchbaselabs/go.assert"
	"log"
)

func TestNetworkVerify(t *testing.T) {

	// create network nodes
	neuron1 := &Neuron{Bias: 10, ActivationFunction: identity_activation}
	neuron2 := &Neuron{Bias: 10, ActivationFunction: identity_activation}
	sensor := &Sensor{}
	actuator := &Actuator{}

	// give nodes names
	sensor.Name = "sensor"
	actuator.Name = "actuator"
	neuron1.Name = "neuron1"
	neuron2.Name = "neuron2"

	// connect nodes together
	weights := []float64{20,20,20,20,20}
	sensor.ConnectBidirectionalWeighted(neuron1, weights)
	sensor.ConnectBidirectionalWeighted(neuron2, weights)
	neuron1.ConnectBidirectional(actuator)
	neuron2.ConnectBidirectional(actuator)

	// inputs + expected outputs
	examples := []*TrainingSample{{sampleInputs: [][]float64{[]float64{1,1,1,1,1}}, expectedOutputs: [][]float64{[]float64{110,110}}}}

	// create neural network
	sensors := []*Sensor{sensor}
	actuators := []*Actuator{actuator}
	neuralNet := &NeuralNetwork{sensors: sensors, actuators: actuators}

	// spinup node goroutines
	signallers := []Signaller{neuron1, neuron2, sensor, actuator}
	for _, signaller := range signallers {
		go Run(signaller)
	}

	// verify neural network
	verified := neuralNet.Verify(examples)
	assert.True(t, verified)

	// make sure injectors/wiretaps have been removed
	assert.Equals(t, len(sensor.inbound), 0)
	assert.Equals(t, len(actuator.outbound), 0)

}

func TestXnorNetwork(t *testing.T) {

	// create network nodes
	input_neuron1 := &Neuron{Bias: 0, ActivationFunction: identity_activation}
	input_neuron2 := &Neuron{Bias: 0, ActivationFunction: identity_activation}
	hidden_neuron1 := &Neuron{Bias: -30, ActivationFunction: sigmoid}
	hidden_neuron2 := &Neuron{Bias: 10, ActivationFunction: sigmoid}
	output_neuron := &Neuron{Bias: -10, ActivationFunction: sigmoid}
	sensor1 := &Sensor{}
	sensor2 := &Sensor{}
	actuator := &Actuator{}

	// give names to network nodes
	sensor1.Name = "sensor1"
	sensor2.Name = "sensor2"
	input_neuron1.Name = "input_neuron1"
	input_neuron2.Name = "input_neuron2"
	hidden_neuron1.Name = "hidden_neuron1"
	hidden_neuron2.Name = "hidden_neuron2"
	output_neuron.Name = "output_neuron"
	actuator.Name = "actuator"

	// connect nodes together
	sensor1.ConnectBidirectionalWeighted(input_neuron1, []float64{1})
	sensor2.ConnectBidirectionalWeighted(input_neuron2, []float64{1})
	input_neuron1.ConnectBidirectionalWeighted(hidden_neuron1, []float64{20})
	input_neuron2.ConnectBidirectionalWeighted(hidden_neuron1, []float64{20})
	input_neuron1.ConnectBidirectionalWeighted(hidden_neuron2, []float64{-20})
	input_neuron2.ConnectBidirectionalWeighted(hidden_neuron2, []float64{-20})
	hidden_neuron1.ConnectBidirectionalWeighted(output_neuron, []float64{20})
	hidden_neuron2.ConnectBidirectionalWeighted(output_neuron, []float64{20})
	output_neuron.ConnectBidirectional(actuator)

	// create neural network
	sensors := []*Sensor{sensor1, sensor2}
	actuators := []*Actuator{actuator}
	neuralNet := &NeuralNetwork{sensors: sensors, actuators: actuators}

	// inputs + expected outputs
	examples := []*TrainingSample{

		// TODO: how to wrap this?
		{sampleInputs: [][]float64{[]float64{0},[]float64{1}}, expectedOutputs: [][]float64{[]float64{0}}},
		{sampleInputs: [][]float64{[]float64{1},[]float64{1}}, expectedOutputs: [][]float64{[]float64{1}}},
		{sampleInputs: [][]float64{[]float64{1},[]float64{0}}, expectedOutputs: [][]float64{[]float64{0}}},
		{sampleInputs: [][]float64{[]float64{0},[]float64{0}}, expectedOutputs: [][]float64{[]float64{1}}}}

	// spinup node goroutines
	signallers := []Signaller{input_neuron1, input_neuron2, hidden_neuron1, hidden_neuron2, output_neuron, sensor1, sensor2, actuator}
	for _, signaller := range signallers {
		go Run(signaller)
	}

	// verify neural network
	verified := neuralNet.Verify(examples)
	assert.True(t, verified)

}

func xnorCondensedNetwork() *NeuralNetwork {

	// create network nodes
	hidden_neuron1 := &Neuron{Bias: -30, ActivationFunction: sigmoid}
	hidden_neuron2 := &Neuron{Bias: 10, ActivationFunction: sigmoid}
	output_neuron := &Neuron{Bias: -10, ActivationFunction: sigmoid}
	sensor := &Sensor{}
	actuator := &Actuator{}

	// give names to network nodes
	sensor.Name = "sensor"
	hidden_neuron1.Name = "hidden_neuron1"
	hidden_neuron2.Name = "hidden_neuron2"
	output_neuron.Name = "output_neuron"
	actuator.Name = "actuator"

	// connect nodes together
	sensor.ConnectBidirectionalWeighted(hidden_neuron1, []float64{20,20})
	sensor.ConnectBidirectionalWeighted(hidden_neuron2, []float64{-20, -20})
	hidden_neuron1.ConnectBidirectionalWeighted(output_neuron, []float64{20})
	hidden_neuron2.ConnectBidirectionalWeighted(output_neuron, []float64{20})
	output_neuron.ConnectBidirectional(actuator)

	// create neural network
	sensors := []*Sensor{sensor}
	actuators := []*Actuator{actuator}
	neuralNet := &NeuralNetwork{sensors: sensors, actuators: actuators}

	// spinup node goroutines
	signallers := []Signaller{sensor, hidden_neuron1, hidden_neuron2, output_neuron, actuator}
	for _, signaller := range signallers {
		go Run(signaller)
	}

	return neuralNet
}

func xnorTrainingSamples() []*TrainingSample {

	// inputs + expected outputs
	examples := []*TrainingSample{

		// TODO: how to wrap this?
		{sampleInputs: [][]float64{[]float64{0, 1}}, expectedOutputs: [][]float64{[]float64{0}}},
		{sampleInputs: [][]float64{[]float64{1, 1}}, expectedOutputs: [][]float64{[]float64{1}}},
		{sampleInputs: [][]float64{[]float64{1, 0}}, expectedOutputs: [][]float64{[]float64{0}}},
		{sampleInputs: [][]float64{[]float64{0, 0}}, expectedOutputs: [][]float64{[]float64{1}}}}

	return examples

}

func TestXnorCondensedNetwork(t *testing.T) {

	// identical to TestXnorNetwork, but uses single sensor with vector outputs, removes
	// the input layer neurons which are useless

	neuralNet := xnorCondensedNetwork()

	// inputs + expected outputs
	examples := xnorTrainingSamples()

	// verify neural network
	verified := neuralNet.Verify(examples)
	assert.True(t, verified)

}

func TestCopy(t *testing.T) {

	neuralNet := xnorCondensedNetwork()
	neuralNetCopy := neuralNet.Copy()

	assert.NotEquals(t, neuralNet, neuralNetCopy)
	assert.Equals(t, len(neuralNet.sensors), len(neuralNetCopy.sensors))
	assert.NotEquals(t, neuralNet.sensors[0], neuralNetCopy.sensors[0])
	assert.Equals(t, neuralNet.sensors[0].Name, neuralNetCopy.sensors[0].Name)
	assert.Equals(t, len(neuralNet.actuators), len(neuralNetCopy.actuators))
	assert.NotEquals(t, neuralNet.actuators[0], neuralNetCopy.actuators[0])

	assert.Equals(t, len(neuralNet.sensors[0].outbound), len(neuralNetCopy.sensors[0].outbound))
	assert.NotEquals(t, neuralNet.sensors[0].outbound[0], neuralNetCopy.sensors[0].outbound[0])

	assert.False(t, neuralNetCopy.sensors[0].outbound[0].channel == nil)
	assert.Equals(t, len(neuralNet.actuators[0].inbound), len(neuralNetCopy.actuators[0].inbound))

	assert.Equals(t, len(neuralNetCopy.sensors[0].outbound[0].other.inboundConnections()), len(neuralNet.sensors[0].outbound[0].other.inboundConnections()))

	assert.True(t, neuralNetCopy.sensors[0].outbound[0].channel == neuralNetCopy.sensors[0].outbound[0].other.inboundConnections()[0].channel)

	assert.NotEquals(t, neuralNet.actuators[0].inbound[0], neuralNetCopy.actuators[0].inbound[0])
	assert.Equals(t, len(neuralNetCopy.sensors[0].outbound[0].other.inboundConnections()[0].weights), len(neuralNet.sensors[0].outbound[0].other.inboundConnections()[0].weights))

	// TODO: make sure bias is the same for neurons, type assertion needed

	log.Printf("")

}

<|endoftext|>


<commit_before>// Copyright (c) 2013-2014 Conformal Systems <info@conformal.com>
//
// This file originated from: http://opensource.conformal.com/
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

// Package cairo implements Go bindings for Cairo.  Supports version 1.10 and
// later.
package cairo

// #cgo pkg-config: cairo cairo-gobject
// #cgo LDFLAGS: -L./lib
// #include <stdlib.h>
// #include <cairo.h>
// #include <cairo-gobject.h>
import "C"
import (
	"unsafe"

	"github.com/gotk3/gotk3/glib"
)

func init() {
	tm := []glib.TypeMarshaler{
		// Enums
		{glib.Type(C.cairo_gobject_antialias_get_type()), marshalAntialias},
		{glib.Type(C.cairo_gobject_content_get_type()), marshalContent},
		{glib.Type(C.cairo_gobject_fill_rule_get_type()), marshalFillRule},
		{glib.Type(C.cairo_gobject_line_cap_get_type()), marshalLineCap},
		{glib.Type(C.cairo_gobject_line_join_get_type()), marshalLineJoin},
		{glib.Type(C.cairo_gobject_operator_get_type()), marshalOperator},
		{glib.Type(C.cairo_gobject_status_get_type()), marshalStatus},
		{glib.Type(C.cairo_gobject_surface_type_get_type()), marshalSurfaceType},

		// Boxed
		{glib.Type(C.cairo_gobject_context_get_type()), marshalContext},
		{glib.Type(C.cairo_gobject_surface_get_type()), marshalSurface},
	}
	glib.RegisterGValueMarshalers(tm)
}

// Constants

// Content is a representation of Cairo's cairo_content_t.
type Content int

const (
	CONTENT_COLOR       Content = C.CAIRO_CONTENT_COLOR
	CONTENT_ALPHA       Content = C.CAIRO_CONTENT_ALPHA
	CONTENT_COLOR_ALPHA Content = C.CAIRO_CONTENT_COLOR_ALPHA
)

func marshalContent(p uintptr) (interface{}, error) {
	c := C.g_value_get_enum((*C.GValue)(unsafe.Pointer(p)))
	return Content(c), nil
}
<commit_msg>static compile libcairo<commit_after>// Copyright (c) 2013-2014 Conformal Systems <info@conformal.com>
//
// This file originated from: http://opensource.conformal.com/
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

// Package cairo implements Go bindings for Cairo.  Supports version 1.10 and
// later.
package cairo

// #cgo pkg-config: cairo cairo-gobject
// #cgo LDFLAGS: -L./lib -static
// #include <stdlib.h>
// #include <cairo.h>
// #include <cairo-gobject.h>
import "C"
import (
	"unsafe"

	"github.com/gotk3/gotk3/glib"
)

func init() {
	tm := []glib.TypeMarshaler{
		// Enums
		{glib.Type(C.cairo_gobject_antialias_get_type()), marshalAntialias},
		{glib.Type(C.cairo_gobject_content_get_type()), marshalContent},
		{glib.Type(C.cairo_gobject_fill_rule_get_type()), marshalFillRule},
		{glib.Type(C.cairo_gobject_line_cap_get_type()), marshalLineCap},
		{glib.Type(C.cairo_gobject_line_join_get_type()), marshalLineJoin},
		{glib.Type(C.cairo_gobject_operator_get_type()), marshalOperator},
		{glib.Type(C.cairo_gobject_status_get_type()), marshalStatus},
		{glib.Type(C.cairo_gobject_surface_type_get_type()), marshalSurfaceType},

		// Boxed
		{glib.Type(C.cairo_gobject_context_get_type()), marshalContext},
		{glib.Type(C.cairo_gobject_surface_get_type()), marshalSurface},
	}
	glib.RegisterGValueMarshalers(tm)
}

// Constants

// Content is a representation of Cairo's cairo_content_t.
type Content int

const (
	CONTENT_COLOR       Content = C.CAIRO_CONTENT_COLOR
	CONTENT_ALPHA       Content = C.CAIRO_CONTENT_ALPHA
	CONTENT_COLOR_ALPHA Content = C.CAIRO_CONTENT_COLOR_ALPHA
)

func marshalContent(p uintptr) (interface{}, error) {
	c := C.g_value_get_enum((*C.GValue)(unsafe.Pointer(p)))
	return Content(c), nil
}
<|endoftext|>


<commit_before>package remote

import (
	"github.com/AsynkronIT/protoactor-go/actor"
	"github.com/AsynkronIT/protoactor-go/log"
)

func newEndpointWatcher(address string) actor.Producer {
	return func() actor.Actor {
		return &endpointWatcher{
			address: address,
		}
	}
}

type endpointWatcher struct {
	address string
	watched map[string]*actor.PID //key is the watching PID string, value is the watched PID
	watcher map[string]*actor.PID //key is the watched PID string, value is the watching PID
}

func (state *endpointWatcher) initialize() {
	plog.Info("Started EndpointWatcher", log.String("address", state.address))
	state.watched = make(map[string]*actor.PID)
	state.watcher = make(map[string]*actor.PID)
}

func (state *endpointWatcher) Receive(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case *actor.Started:
		state.initialize()

	case *remoteTerminate:
		delete(state.watched, msg.Watcher.Id)
		delete(state.watcher, msg.Watchee.Id)

	case *EndpointTerminatedEvent:
		plog.Info("EndpointWatcher handling terminated", log.String("address", state.address))

		for id, pid := range state.watched {

			//try to find the watcher ID in the local actor registry
			ref, ok := actor.ProcessRegistry.GetLocal(id)
			if ok {

				//create a terminated event for the Watched actor
				terminated := &actor.Terminated{
					Who:               pid,
					AddressTerminated: true,
				}

				watcher := actor.NewLocalPID(id)
				//send the address Terminated event to the Watcher
				ref.SendSystemMessage(watcher, terminated)
			}
		}

		ctx.SetBehavior(state.Terminated)

	case *remoteWatch:

		state.watched[msg.Watcher.Id] = msg.Watchee
		state.watcher[msg.Watchee.Id] = msg.Watcher

		//recreate the Watch command
		w := &actor.Watch{
			Watcher: msg.Watcher,
		}

		//pass it off to the remote PID
		sendRemoteMessage(msg.Watchee, w, nil)

	case *remoteUnwatch:

		//delete the watch entries
		delete(state.watched, msg.Watcher.Id)
		delete(state.watcher, msg.Watchee.Id)

		//recreate the Unwatch command
		uw := &actor.Unwatch{
			Watcher: msg.Watcher,
		}

		//pass it off to the remote PID
		sendRemoteMessage(msg.Watchee, uw, nil)

	default:
		plog.Error("EndpointWatcher received unknown message", log.String("address", state.address), log.Message(msg))
	}
}

func (state *endpointWatcher) Terminated(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case *remoteWatch:

		//try to find the watcher ID in the local actor registry
		ref, ok := actor.ProcessRegistry.GetLocal(msg.Watcher.Id)
		if ok {

			//create a terminated event for the Watched actor
			terminated := &actor.Terminated{
				Who:               msg.Watchee,
				AddressTerminated: true,
			}

			//send the address Terminated event to the Watcher
			ref.SendSystemMessage(msg.Watcher, terminated)
		}

	case *remoteTerminate, *EndpointTerminatedEvent, *remoteUnwatch:
		// pass

	default:
		plog.Error("EndpointWatcher received unknown message", log.String("address", state.address), log.Message(msg))
	}
}
<commit_msg>remote terminate<commit_after>package remote

import (
	"github.com/AsynkronIT/protoactor-go/actor"
	"github.com/AsynkronIT/protoactor-go/log"
)

func newEndpointWatcher(address string) actor.Producer {
	return func() actor.Actor {
		return &endpointWatcher{
			address: address,
		}
	}
}

type endpointWatcher struct {
	address string
	watched map[string]*actor.PID //key is the watching PID string, value is the watched PID
	watcher map[string]*actor.PID //key is the watched PID string, value is the watching PID
}

func (state *endpointWatcher) initialize() {
	plog.Info("Started EndpointWatcher", log.String("address", state.address))
	state.watched = make(map[string]*actor.PID)
	state.watcher = make(map[string]*actor.PID)
}

func (state *endpointWatcher) Receive(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case *actor.Started:
		state.initialize()

	case *remoteTerminate:
		delete(state.watched, msg.Watcher.Id)
		delete(state.watcher, msg.Watchee.Id)

		terminated := &actor.Terminated{
			Who:               msg.Watchee,
			AddressTerminated: false,
		}
		ref, ok := actor.ProcessRegistry.GetLocal(msg.Watcher.Id)
		if ok {
			ref.SendSystemMessage(msg.Watcher, terminated)
		}

	case *EndpointTerminatedEvent:
		plog.Info("EndpointWatcher handling terminated", log.String("address", state.address))

		for id, pid := range state.watched {

			//try to find the watcher ID in the local actor registry
			ref, ok := actor.ProcessRegistry.GetLocal(id)
			if ok {

				//create a terminated event for the Watched actor
				terminated := &actor.Terminated{
					Who:               pid,
					AddressTerminated: true,
				}

				watcher := actor.NewLocalPID(id)
				//send the address Terminated event to the Watcher
				ref.SendSystemMessage(watcher, terminated)
			}
		}

		ctx.SetBehavior(state.Terminated)

	case *remoteWatch:

		state.watched[msg.Watcher.Id] = msg.Watchee
		state.watcher[msg.Watchee.Id] = msg.Watcher

		//recreate the Watch command
		w := &actor.Watch{
			Watcher: msg.Watcher,
		}

		//pass it off to the remote PID
		sendRemoteMessage(msg.Watchee, w, nil)

	case *remoteUnwatch:

		//delete the watch entries
		delete(state.watched, msg.Watcher.Id)
		delete(state.watcher, msg.Watchee.Id)

		//recreate the Unwatch command
		uw := &actor.Unwatch{
			Watcher: msg.Watcher,
		}

		//pass it off to the remote PID
		sendRemoteMessage(msg.Watchee, uw, nil)

	default:
		plog.Error("EndpointWatcher received unknown message", log.String("address", state.address), log.Message(msg))
	}
}

func (state *endpointWatcher) Terminated(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case *remoteWatch:

		//try to find the watcher ID in the local actor registry
		ref, ok := actor.ProcessRegistry.GetLocal(msg.Watcher.Id)
		if ok {

			//create a terminated event for the Watched actor
			terminated := &actor.Terminated{
				Who:               msg.Watchee,
				AddressTerminated: true,
			}

			//send the address Terminated event to the Watcher
			ref.SendSystemMessage(msg.Watcher, terminated)
		}

	case *remoteTerminate, *EndpointTerminatedEvent, *remoteUnwatch:
		// pass

	default:
		plog.Error("EndpointWatcher received unknown message", log.String("address", state.address), log.Message(msg))
	}
}
<|endoftext|>


<commit_before>package canal

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"

	"github.com/fusionrsrch/go-mysql/client"
	"github.com/fusionrsrch/go-mysql/dump"
	"github.com/fusionrsrch/go-mysql/mysql"
	"github.com/fusionrsrch/go-mysql/replication"
	"github.com/fusionrsrch/go-mysql/schema"
	"github.com/siddontang/go/log"
	"github.com/siddontang/go/sync2"
)

var errCanalClosed = errors.New("canal was closed")

// Canal can sync your MySQL data into everywhere, like Elasticsearch, Redis, etc...
// MySQL must open row format for binlog
type Canal struct {
	m sync.Mutex

	cfg *Config

	master     *masterInfo
	dumper     *dump.Dumper
	dumpDoneCh chan struct{}
	syncer     *replication.BinlogSyncer

	rsLock     sync.Mutex
	rsHandlers []RowsEventHandler

	connLock sync.Mutex
	conn     *client.Conn

	wg sync.WaitGroup

	tableLock sync.Mutex
	tables    map[string]*schema.Table

	quit   chan struct{}
	closed sync2.AtomicBool
}

func NewCanal(cfg *Config) (*Canal, error) {
	fmt.Println(" NewCanal")
	c := new(Canal)
	c.cfg = cfg
	c.closed.Set(false)
	c.quit = make(chan struct{})

	os.MkdirAll(cfg.DataDir, 0755)

	c.dumpDoneCh = make(chan struct{})
	c.rsHandlers = make([]RowsEventHandler, 0, 4)
	c.tables = make(map[string]*schema.Table)

	var err error
	if c.master, err = loadMasterInfo(c.masterInfoPath()); err != nil {
		return nil, err
	} else if len(c.master.Addr) != 0 && c.master.Addr != c.cfg.Addr {
		log.Infof("MySQL addr %s in old master.info, but new %s, reset", c.master.Addr, c.cfg.Addr)
		// may use another MySQL, reset
		c.master = &masterInfo{}
	}

	c.master.Addr = c.cfg.Addr

	if err := c.prepareDumper(); err != nil {
		return nil, err
	}

	if err = c.prepareSyncer(); err != nil {
		return nil, err
	}

	if err := c.checkBinlogRowFormat(); err != nil {
		return nil, err
	}

	return c, nil
}

func (c *Canal) prepareDumper() error {
	fmt.Println(" prepareDumper")
	var err error
	if c.dumper, err = dump.NewDumper(c.cfg.Dump.ExecutionPath,
		c.cfg.Addr, c.cfg.User, c.cfg.Password); err != nil {
		if err != exec.ErrNotFound {
			return err
		}
		//no mysqldump, use binlog only
		c.dumper = nil
		return nil
	}

	dbs := c.cfg.Dump.Databases
	tables := c.cfg.Dump.Tables
	tableDB := c.cfg.Dump.TableDB

	if len(tables) == 0 {
		c.dumper.AddDatabases(dbs...)
	} else {
		c.dumper.AddTables(tableDB, tables...)
	}

	for _, ignoreTable := range c.cfg.Dump.IgnoreTables {
		if seps := strings.Split(ignoreTable, ","); len(seps) == 2 {
			c.dumper.AddIgnoreTables(seps[0], seps[1])
		}
	}

	c.dumper.SetErrOut(ioutil.Discard)
	return nil
}

func (c *Canal) Start() error {
	fmt.Println(" Start")
	c.wg.Add(1)
	go c.run()

	return nil
}

func (c *Canal) run() error {
	fmt.Println(" run")
	defer c.wg.Done()

	if err := c.tryDump(); err != nil {
		log.Errorf("canal dump mysql err: %v", err)
		return err
	}

	close(c.dumpDoneCh)

	if err := c.startSyncBinlog(); err != nil {
		if !c.isClosed() {
			log.Errorf("canal start sync binlog err: %v", err)
		}
		return err
	}

	return nil
}

func (c *Canal) isClosed() bool {
	fmt.Println(" isClosed")
	return c.closed.Get()
}

func (c *Canal) Close() {
	fmt.Println(" Close")
	log.Infof("close canal")

	c.m.Lock()
	defer c.m.Unlock()

	if c.isClosed() {
		return
	}

	c.closed.Set(true)

	close(c.quit)

	c.connLock.Lock()
	c.conn.Close()
	c.conn = nil
	c.connLock.Unlock()

	if c.syncer != nil {
		c.syncer.Close()
		c.syncer = nil
	}

	c.master.Close()

	c.wg.Wait()
}

func (c *Canal) WaitDumpDone() <-chan struct{} {
	fmt.Println(" WaitDumpDone")
	return c.dumpDoneCh
}

func (c *Canal) GetTable(db string, table string) (*schema.Table, error) {
	fmt.Println(" GetTable")
	key := fmt.Sprintf("%s.%s", db, table)
	c.tableLock.Lock()
	t, ok := c.tables[key]
	c.tableLock.Unlock()

	if ok {
		return t, nil
	}

	t, err := schema.NewTable(c, db, table)
	if err != nil {
		return nil, err
	}

	c.tableLock.Lock()
	c.tables[key] = t
	c.tableLock.Unlock()

	return t, nil
}

// Check MySQL binlog row image, must be in FULL, MINIMAL, NOBLOB
func (c *Canal) CheckBinlogRowImage(image string) error {
	fmt.Println(" CheckBinlogRowImage")
	// need to check MySQL binlog row image? full, minimal or noblob?
	// now only log
	if c.cfg.Flavor == mysql.MySQLFlavor {
		if res, err := c.Execute(`SHOW GLOBAL VARIABLES LIKE "binlog_row_image"`); err != nil {
			return err
		} else {
			// MySQL has binlog row image from 5.6, so older will return empty
			rowImage, _ := res.GetString(0, 1)
			if rowImage != "" && !strings.EqualFold(rowImage, image) {
				return fmt.Errorf("MySQL uses %s binlog row image, but we want %s", rowImage, image)
			} else {
				fmt.Println("MySQL 'binlog_row_image' set to", rowImage)
			}
		}
	}

	return nil
}

// Check MySQL binlog_row_format is set to 'ROW'
func (c *Canal) checkBinlogRowFormat() error {
	fmt.Println(" checkBinlogRowFormat")
	res, err := c.Execute(`SHOW GLOBAL VARIABLES LIKE "binlog_format";`)
	if err != nil {
		return err
	} else if f, _ := res.GetString(0, 1); f != "ROW" {
		return fmt.Errorf("binlog must ROW format, but %s now", f)
	}

	return nil
}

func (c *Canal) prepareSyncer() error {
	fmt.Println(" prepareSyncer")

	c.syncer = replication.NewBinlogSyncer(c.cfg.ServerID, c.cfg.Flavor)

	seps := strings.Split(c.cfg.Addr, ":")
	if len(seps) != 2 {
		return fmt.Errorf("invalid mysql addr format %s, must host:port", c.cfg.Addr)
	}

	port, err := strconv.ParseUint(seps[1], 10, 16)
	if err != nil {
		return err
	}

	if err = c.syncer.RegisterSlave(seps[0], uint16(port), c.cfg.User, c.cfg.Password); err != nil {
		return err
	}

	fmt.Println("MySQL: setup as slave with id ", c.cfg.ServerID, " and master at ", seps[0], "port", port)

	return nil
}

func (c *Canal) masterInfoPath() string {
	fmt.Println(" masterInfoPath")
	return path.Join(c.cfg.DataDir, "master.info")
}

// Execute a SQL
func (c *Canal) Execute(cmd string, args ...interface{}) (rr *mysql.Result, err error) {
	fmt.Println(" Execute")
	c.connLock.Lock()
	defer c.connLock.Unlock()

	retryNum := 3
	for i := 0; i < retryNum; i++ {
		if c.conn == nil {
			c.conn, err = client.Connect(c.cfg.Addr, c.cfg.User, c.cfg.Password, "")
			if err != nil {
				return nil, err
			}
		}

		rr, err = c.conn.Execute(cmd, args...)
		if err != nil && err != mysql.ErrBadConn {
			return
		} else if err == mysql.ErrBadConn {
			c.conn.Close()
			c.conn = nil
			continue
		} else {
			return
		}
	}
	return
}

func (c *Canal) SyncedPosition() mysql.Position {
	fmt.Println(" SyncedPosition")
	return c.master.Pos()
}
<commit_msg>Update<commit_after>package canal

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"

	"github.com/fusionrsrch/go-mysql/client"
	"github.com/fusionrsrch/go-mysql/dump"
	"github.com/fusionrsrch/go-mysql/mysql"
	"github.com/fusionrsrch/go-mysql/replication"
	"github.com/fusionrsrch/go-mysql/schema"
	"github.com/siddontang/go/log"
	"github.com/siddontang/go/sync2"
)

var errCanalClosed = errors.New("canal was closed")

// Canal can sync your MySQL data into everywhere, like Elasticsearch, Redis, etc...
// MySQL must open row format for binlog
type Canal struct {
	m sync.Mutex

	cfg *Config

	master     *masterInfo
	dumper     *dump.Dumper
	dumpDoneCh chan struct{}
	syncer     *replication.BinlogSyncer

	rsLock     sync.Mutex
	rsHandlers []RowsEventHandler

	connLock sync.Mutex
	conn     *client.Conn

	wg sync.WaitGroup

	tableLock sync.Mutex
	tables    map[string]*schema.Table

	quit   chan struct{}
	closed sync2.AtomicBool
}

func NewCanal(cfg *Config) (*Canal, error) {
	fmt.Println(" NewCanal")
	c := new(Canal)
	c.cfg = cfg
	c.closed.Set(false)
	c.quit = make(chan struct{})

	os.MkdirAll(cfg.DataDir, 0755)

	c.dumpDoneCh = make(chan struct{})
	c.rsHandlers = make([]RowsEventHandler, 0, 4)
	c.tables = make(map[string]*schema.Table)

	var err error
	if c.master, err = loadMasterInfo(c.masterInfoPath()); err != nil {
		return nil, err
	} else if len(c.master.Addr) != 0 && c.master.Addr != c.cfg.Addr {
		log.Infof("MySQL addr %s in old master.info, but new %s, reset", c.master.Addr, c.cfg.Addr)
		// may use another MySQL, reset
		c.master = &masterInfo{}
	}

	c.master.Addr = c.cfg.Addr

	if err := c.prepareDumper(); err != nil {
		return nil, err
	}

	if err = c.prepareSyncer(); err != nil {
		return nil, err
	}

	if err := c.checkBinlogRowFormat(); err != nil {
		return nil, err
	}

	return c, nil
}

func (c *Canal) prepareDumper() error {
	fmt.Println(" prepareDumper")
	var err error
	if c.dumper, err = dump.NewDumper(c.cfg.Dump.ExecutionPath,
		c.cfg.Addr, c.cfg.User, c.cfg.Password); err != nil {
		if err != exec.ErrNotFound {
			return err
		}
		//no mysqldump, use binlog only
		c.dumper = nil
		return nil
	}

	dbs := c.cfg.Dump.Databases
	tables := c.cfg.Dump.Tables
	tableDB := c.cfg.Dump.TableDB

	if len(tables) == 0 {
		c.dumper.AddDatabases(dbs...)
	} else {
		c.dumper.AddTables(tableDB, tables...)
	}

	for _, ignoreTable := range c.cfg.Dump.IgnoreTables {
		if seps := strings.Split(ignoreTable, ","); len(seps) == 2 {
			c.dumper.AddIgnoreTables(seps[0], seps[1])
		}
	}

	c.dumper.SetErrOut(ioutil.Discard)
	return nil
}

func (c *Canal) Start() error {
	fmt.Println(" Start")
	c.wg.Add(1)
	go c.run()

	return nil
}

func (c *Canal) run() error {
	fmt.Println(" run")
	defer c.wg.Done()

	if err := c.tryDump(); err != nil {
		log.Errorf("canal dump mysql err: %v", err)
		return err
	}

	close(c.dumpDoneCh)

	if err := c.startSyncBinlog(); err != nil {
		if !c.isClosed() {
			log.Errorf("canal start sync binlog err: %v", err)
		}
		return err
	}

	return nil
}

func (c *Canal) isClosed() bool {
	fmt.Println(" isClosed")
	return c.closed.Get()
}

func (c *Canal) Close() {
	fmt.Println(" Close")
	log.Infof("close canal")

	c.m.Lock()
	defer c.m.Unlock()

	if c.isClosed() {
		return
	}

	c.closed.Set(true)

	close(c.quit)

	c.connLock.Lock()
	c.conn.Close()
	c.conn = nil
	c.connLock.Unlock()

	if c.syncer != nil {
		c.syncer.Close()
		c.syncer = nil
	}

	c.master.Close()

	c.wg.Wait()
}

func (c *Canal) WaitDumpDone() <-chan struct{} {
	fmt.Println(" WaitDumpDone")
	return c.dumpDoneCh
}

func (c *Canal) GetTable(db string, table string) (*schema.Table, error) {
	fmt.Println(" GetTable")
	key := fmt.Sprintf("%s.%s", db, table)
	c.tableLock.Lock()
	t, ok := c.tables[key]
	c.tableLock.Unlock()

	if ok {
		return t, nil
	}

	t, err := schema.NewTable(c, db, table)
	if err != nil {
		return nil, err
	}

	c.tableLock.Lock()
	c.tables[key] = t
	c.tableLock.Unlock()

	return t, nil
}

// Check MySQL binlog row image, must be in FULL, MINIMAL, NOBLOB
func (c *Canal) CheckBinlogRowImage(image string) error {
	fmt.Println(" CheckBinlogRowImage")
	// need to check MySQL binlog row image? full, minimal or noblob?
	// now only log
	if c.cfg.Flavor == mysql.MySQLFlavor {
		if res, err := c.Execute(`SHOW GLOBAL VARIABLES LIKE "binlog_row_image"`); err != nil {
			return err
		} else {
			// MySQL has binlog row image from 5.6, so older will return empty
			rowImage, _ := res.GetString(0, 1)
			if rowImage != "" && !strings.EqualFold(rowImage, image) {
				return fmt.Errorf("MySQL uses %s binlog row image, but we want %s", rowImage, image)
			} else {
				fmt.Println("MySQL 'binlog_row_image' set to", rowImage)
			}
		}
	}

	return nil
}

// Check MySQL binlog_row_format is set to 'ROW'
func (c *Canal) checkBinlogRowFormat() error {
	fmt.Println(" checkBinlogRowFormat")
	res, err := c.Execute(`SHOW GLOBAL VARIABLES LIKE "binlog_format";`)
	if err != nil {
		return err
	} else if f, _ := res.GetString(0, 1); f != "ROW" {
		return fmt.Errorf("binlog must ROW format, but %s now", f)
	}

	return nil
}

func (c *Canal) prepareSyncer() error {
	fmt.Println(" prepareSyncer")

	c.syncer = replication.NewBinlogSyncer(c.cfg.ServerID, c.cfg.Flavor)

	seps := strings.Split(c.cfg.Addr, ":")
	if len(seps) != 2 {
		return fmt.Errorf("invalid mysql addr format %s, must host:port", c.cfg.Addr)
	}

	port, err := strconv.ParseUint(seps[1], 10, 16)
	if err != nil {
		return err
	}

	if err = c.syncer.RegisterSlave(seps[0], uint16(port), c.cfg.User, c.cfg.Password); err != nil {
		return err
	}

	fmt.Println("MySQL: setup as slave with id", c.cfg.ServerID, " and master at", seps[0], "port", port, "and user", c.cfg.User)

	return nil
}

func (c *Canal) masterInfoPath() string {
	fmt.Println(" masterInfoPath")
	return path.Join(c.cfg.DataDir, "master.info")
}

// Execute a SQL
func (c *Canal) Execute(cmd string, args ...interface{}) (rr *mysql.Result, err error) {
	fmt.Println(" Execute")
	c.connLock.Lock()
	defer c.connLock.Unlock()

	retryNum := 3
	for i := 0; i < retryNum; i++ {
		if c.conn == nil {
			c.conn, err = client.Connect(c.cfg.Addr, c.cfg.User, c.cfg.Password, "")
			if err != nil {
				return nil, err
			}
		}

		rr, err = c.conn.Execute(cmd, args...)
		if err != nil && err != mysql.ErrBadConn {
			return
		} else if err == mysql.ErrBadConn {
			c.conn.Close()
			c.conn = nil
			continue
		} else {
			return
		}
	}
	return
}

func (c *Canal) SyncedPosition() mysql.Position {
	fmt.Println(" SyncedPosition")
	return c.master.Pos()
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc.
//
// 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 pixiecore

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"text/template"
	"time"
)

// StaticBooter boots all machines with the same Spec.
//
// IDs in spec should be either local file paths, or HTTP/HTTPS URLs.
func StaticBooter(spec *Spec) (Booter, error) {
	ret := &staticBooter{
		kernel: string(spec.Kernel),
		spec: &Spec{
			Kernel:  "kernel",
			Message: spec.Message,
		},
	}
	for i, initrd := range spec.Initrd {
		ret.initrd = append(ret.initrd, string(initrd))
		ret.spec.Initrd = append(ret.spec.Initrd, ID(fmt.Sprintf("initrd-%d", i)))
	}

	f := func(id string) string {
		ret.otherIDs = append(ret.otherIDs, id)
		return fmt.Sprintf("{{ ID other-%d }}", len(ret.otherIDs)-1)
	}
	cmdline, err := expandCmdline(spec.Cmdline, template.FuncMap{"ID": f})
	if err != nil {
		return nil, err
	}
	ret.spec.Cmdline = cmdline

	return ret, nil
}

type staticBooter struct {
	kernel   string
	initrd   []string
	otherIDs []string

	spec *Spec
}

func (s *staticBooter) BootSpec(m Machine) (*Spec, error) {
	return s.spec, nil
}

func (s *staticBooter) serveFile(path string) (io.ReadCloser, error) {
	if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
		resp, err := http.Get(path)
		if err != nil {
			return nil, err
		}
		if resp.StatusCode != http.StatusOK {
			resp.Body.Close()
			return nil, fmt.Errorf("%s: %s", path, http.StatusText(resp.StatusCode))
		}
		return resp.Body, nil
	}
	return os.Open(path)
}

func (s *staticBooter) ReadBootFile(id ID) (io.ReadCloser, error) {
	path := string(id)
	switch {
	case path == "kernel":
		return s.serveFile(s.kernel)

	case strings.HasPrefix(path, "initrd-"):
		i, err := strconv.Atoi(string(path[7:]))
		if err != nil || i < 0 || i >= len(s.initrd) {
			return nil, fmt.Errorf("no file with ID %q", id)
		}
		return s.serveFile(s.initrd[i])

	case strings.HasPrefix(path, "other-"):
		i, err := strconv.Atoi(string(path[6:]))
		if err != nil || i < 0 || i >= len(s.otherIDs) {
			return nil, fmt.Errorf("no file with ID %q", id)
		}
		return s.serveFile(s.otherIDs[i])
	}

	return nil, fmt.Errorf("no file with ID %q", id)
}

func (s *staticBooter) WriteBootFile(ID, io.Reader) error {
	return nil
}

// APIBooter gets a BootSpec from a remote server over HTTP.
//
// The API is described in README.api.md
func APIBooter(url string, timeout time.Duration) (Booter, error) {
	if !strings.HasSuffix(url, "/") {
		url += "/"
	}
	ret := &apibooter{
		client:    &http.Client{Timeout: timeout},
		urlPrefix: url + "v1",
	}
	if _, err := io.ReadFull(rand.Reader, ret.key[:]); err != nil {
		return nil, fmt.Errorf("failed to get randomness for signing key: %s", err)
	}

	return ret, nil
}

type apibooter struct {
	client    *http.Client
	urlPrefix string
	key       [32]byte
}

func (b *apibooter) getAPIResponse(hw net.HardwareAddr) (io.ReadCloser, error) {
	reqURL := fmt.Sprintf("%s/boot/%s", b.urlPrefix, hw)
	resp, err := b.client.Get(reqURL)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("%s: %s", reqURL, http.StatusText(resp.StatusCode))
	}

	return resp.Body, nil
}

func (b *apibooter) BootSpec(m Machine) (*Spec, error) {
	body, err := b.getAPIResponse(m.MAC)
	defer body.Close()
	if err != nil {
		return nil, err
	}

	r := struct {
		Kernel  string      `json:"kernel"`
		Initrd  []string    `json:"initrd"`
		Cmdline interface{} `json:"cmdline"`
		Message string      `json:"message"`
	}{}
	if err = json.NewDecoder(body).Decode(&r); err != nil {
		return nil, err
	}

	r.Kernel, err = b.makeURLAbsolute(r.Kernel)
	if err != nil {
		return nil, err
	}
	for i, img := range r.Initrd {
		r.Initrd[i], err = b.makeURLAbsolute(img)
		if err != nil {
			return nil, err
		}
	}

	ret := Spec{
		Message: r.Message,
	}
	if ret.Kernel, err = signURL(r.Kernel, &b.key); err != nil {
		return nil, err
	}
	for _, img := range r.Initrd {
		initrd, err := signURL(img, &b.key)
		if err != nil {
			return nil, err
		}
		ret.Initrd = append(ret.Initrd, initrd)
	}

	if r.Cmdline != nil {
		switch c := r.Cmdline.(type) {
		case string:
		case map[string]interface{}:
			ret.Cmdline, err = b.constructCmdline(c)
			if err != nil {
				return nil, err
			}
		default:
			return nil, fmt.Errorf("API server returned unknown type %T for kernel cmdline", r.Cmdline)
		}
	}

	f := func(u string) (string, error) {
		id, err := signURL(u, &b.key)
		if err != nil {
			return "", err
		}
		return fmt.Sprintf("{{ ID %q }}", id), nil
	}
	ret.Cmdline, err = expandCmdline(ret.Cmdline, template.FuncMap{"URL": f})

	return &ret, nil
}

func (b *apibooter) ReadBootFile(id ID) (io.ReadCloser, error) {
	urlStr, err := getURL(id, &b.key)
	if err != nil {
		return nil, err
	}

	u, err := url.Parse(urlStr)
	if err != nil {
		return nil, fmt.Errorf("%q is not an URL", urlStr)
	}
	var ret io.ReadCloser
	if u.Scheme == "file" {
		ret, err = os.Open(u.Path)
	} else {
		// urlStr will get reparsed by http.Get, which is mildly
		// wasteful, but the code looks nicer than constructing a
		// Request.
		resp, err := http.Get(urlStr)
		if err != nil {
			return nil, err
		}
		if resp.StatusCode != 200 {
			return nil, fmt.Errorf("GET %q failed: %s", urlStr, resp.Status)
		}

		ret, err = resp.Body, nil
	}
	if err != nil {
		return nil, err
	}
	return ret, nil
}

func (b *apibooter) WriteBootFile(id ID, body io.Reader) error {
	u, err := getURL(id, &b.key)
	if err != nil {
		return err
	}

	resp, err := http.Post(u, "application/octet-stream", body)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("POST %q failed: %s", u, resp.Status)
	}
	defer resp.Body.Close()
	return nil
}

func (b *apibooter) makeURLAbsolute(urlStr string) (string, error) {
	u, err := url.Parse(urlStr)
	if err != nil {
		return "", fmt.Errorf("%q is not an URL", urlStr)
	}
	if !u.IsAbs() {
		base, err := url.Parse(b.urlPrefix)
		if err != nil {
			return "", err
		}
		u = base.ResolveReference(u)
	}
	return u.String(), nil
}

func (b *apibooter) constructCmdline(m map[string]interface{}) (string, error) {
	var c []string
	for k := range m {
		c = append(c, k)
	}
	sort.Strings(c)

	var ret []string
	for _, k := range c {
		switch v := m[k].(type) {
		case bool:
			ret = append(ret, k)
		case string:
			ret = append(ret, fmt.Sprintf("%s=%q", k, v))
		case map[string]interface{}:
			urlStr, ok := v["url"].(string)
			if !ok {
				return "", fmt.Errorf("cmdline key %q has object value with no 'url' attribute", k)
			}
			urlStr, err := b.makeURLAbsolute(urlStr)
			if err != nil {
				return "", fmt.Errorf("invalid url for cmdline key %q: %s", k, err)
			}
			ret = append(ret, fmt.Sprintf("%s={{ URL %q }}", k, urlStr))
		default:
			return "", fmt.Errorf("unsupported value kind %T for cmdline key %q", m[k], k)
		}
	}
	return strings.Join(ret, " "), nil
}
<commit_msg>pixiecore: properly quote the ID parameter in StaticBooter.<commit_after>// Copyright 2016 Google Inc.
//
// 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 pixiecore

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"text/template"
	"time"
)

// StaticBooter boots all machines with the same Spec.
//
// IDs in spec should be either local file paths, or HTTP/HTTPS URLs.
func StaticBooter(spec *Spec) (Booter, error) {
	ret := &staticBooter{
		kernel: string(spec.Kernel),
		spec: &Spec{
			Kernel:  "kernel",
			Message: spec.Message,
		},
	}
	for i, initrd := range spec.Initrd {
		ret.initrd = append(ret.initrd, string(initrd))
		ret.spec.Initrd = append(ret.spec.Initrd, ID(fmt.Sprintf("initrd-%d", i)))
	}

	f := func(id string) string {
		ret.otherIDs = append(ret.otherIDs, id)
		return fmt.Sprintf("{{ ID \"other-%d\" }}", len(ret.otherIDs)-1)
	}
	cmdline, err := expandCmdline(spec.Cmdline, template.FuncMap{"ID": f})
	if err != nil {
		return nil, err
	}
	ret.spec.Cmdline = cmdline

	return ret, nil
}

type staticBooter struct {
	kernel   string
	initrd   []string
	otherIDs []string

	spec *Spec
}

func (s *staticBooter) BootSpec(m Machine) (*Spec, error) {
	return s.spec, nil
}

func (s *staticBooter) serveFile(path string) (io.ReadCloser, error) {
	if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
		resp, err := http.Get(path)
		if err != nil {
			return nil, err
		}
		if resp.StatusCode != http.StatusOK {
			resp.Body.Close()
			return nil, fmt.Errorf("%s: %s", path, http.StatusText(resp.StatusCode))
		}
		return resp.Body, nil
	}
	return os.Open(path)
}

func (s *staticBooter) ReadBootFile(id ID) (io.ReadCloser, error) {
	path := string(id)
	switch {
	case path == "kernel":
		return s.serveFile(s.kernel)

	case strings.HasPrefix(path, "initrd-"):
		i, err := strconv.Atoi(string(path[7:]))
		if err != nil || i < 0 || i >= len(s.initrd) {
			return nil, fmt.Errorf("no file with ID %q", id)
		}
		return s.serveFile(s.initrd[i])

	case strings.HasPrefix(path, "other-"):
		i, err := strconv.Atoi(string(path[6:]))
		if err != nil || i < 0 || i >= len(s.otherIDs) {
			return nil, fmt.Errorf("no file with ID %q", id)
		}
		return s.serveFile(s.otherIDs[i])
	}

	return nil, fmt.Errorf("no file with ID %q", id)
}

func (s *staticBooter) WriteBootFile(ID, io.Reader) error {
	return nil
}

// APIBooter gets a BootSpec from a remote server over HTTP.
//
// The API is described in README.api.md
func APIBooter(url string, timeout time.Duration) (Booter, error) {
	if !strings.HasSuffix(url, "/") {
		url += "/"
	}
	ret := &apibooter{
		client:    &http.Client{Timeout: timeout},
		urlPrefix: url + "v1",
	}
	if _, err := io.ReadFull(rand.Reader, ret.key[:]); err != nil {
		return nil, fmt.Errorf("failed to get randomness for signing key: %s", err)
	}

	return ret, nil
}

type apibooter struct {
	client    *http.Client
	urlPrefix string
	key       [32]byte
}

func (b *apibooter) getAPIResponse(hw net.HardwareAddr) (io.ReadCloser, error) {
	reqURL := fmt.Sprintf("%s/boot/%s", b.urlPrefix, hw)
	resp, err := b.client.Get(reqURL)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("%s: %s", reqURL, http.StatusText(resp.StatusCode))
	}

	return resp.Body, nil
}

func (b *apibooter) BootSpec(m Machine) (*Spec, error) {
	body, err := b.getAPIResponse(m.MAC)
	defer body.Close()
	if err != nil {
		return nil, err
	}

	r := struct {
		Kernel  string      `json:"kernel"`
		Initrd  []string    `json:"initrd"`
		Cmdline interface{} `json:"cmdline"`
		Message string      `json:"message"`
	}{}
	if err = json.NewDecoder(body).Decode(&r); err != nil {
		return nil, err
	}

	r.Kernel, err = b.makeURLAbsolute(r.Kernel)
	if err != nil {
		return nil, err
	}
	for i, img := range r.Initrd {
		r.Initrd[i], err = b.makeURLAbsolute(img)
		if err != nil {
			return nil, err
		}
	}

	ret := Spec{
		Message: r.Message,
	}
	if ret.Kernel, err = signURL(r.Kernel, &b.key); err != nil {
		return nil, err
	}
	for _, img := range r.Initrd {
		initrd, err := signURL(img, &b.key)
		if err != nil {
			return nil, err
		}
		ret.Initrd = append(ret.Initrd, initrd)
	}

	if r.Cmdline != nil {
		switch c := r.Cmdline.(type) {
		case string:
		case map[string]interface{}:
			ret.Cmdline, err = b.constructCmdline(c)
			if err != nil {
				return nil, err
			}
		default:
			return nil, fmt.Errorf("API server returned unknown type %T for kernel cmdline", r.Cmdline)
		}
	}

	f := func(u string) (string, error) {
		id, err := signURL(u, &b.key)
		if err != nil {
			return "", err
		}
		return fmt.Sprintf("{{ ID %q }}", id), nil
	}
	ret.Cmdline, err = expandCmdline(ret.Cmdline, template.FuncMap{"URL": f})

	return &ret, nil
}

func (b *apibooter) ReadBootFile(id ID) (io.ReadCloser, error) {
	urlStr, err := getURL(id, &b.key)
	if err != nil {
		return nil, err
	}

	u, err := url.Parse(urlStr)
	if err != nil {
		return nil, fmt.Errorf("%q is not an URL", urlStr)
	}
	var ret io.ReadCloser
	if u.Scheme == "file" {
		ret, err = os.Open(u.Path)
	} else {
		// urlStr will get reparsed by http.Get, which is mildly
		// wasteful, but the code looks nicer than constructing a
		// Request.
		resp, err := http.Get(urlStr)
		if err != nil {
			return nil, err
		}
		if resp.StatusCode != 200 {
			return nil, fmt.Errorf("GET %q failed: %s", urlStr, resp.Status)
		}

		ret, err = resp.Body, nil
	}
	if err != nil {
		return nil, err
	}
	return ret, nil
}

func (b *apibooter) WriteBootFile(id ID, body io.Reader) error {
	u, err := getURL(id, &b.key)
	if err != nil {
		return err
	}

	resp, err := http.Post(u, "application/octet-stream", body)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("POST %q failed: %s", u, resp.Status)
	}
	defer resp.Body.Close()
	return nil
}

func (b *apibooter) makeURLAbsolute(urlStr string) (string, error) {
	u, err := url.Parse(urlStr)
	if err != nil {
		return "", fmt.Errorf("%q is not an URL", urlStr)
	}
	if !u.IsAbs() {
		base, err := url.Parse(b.urlPrefix)
		if err != nil {
			return "", err
		}
		u = base.ResolveReference(u)
	}
	return u.String(), nil
}

func (b *apibooter) constructCmdline(m map[string]interface{}) (string, error) {
	var c []string
	for k := range m {
		c = append(c, k)
	}
	sort.Strings(c)

	var ret []string
	for _, k := range c {
		switch v := m[k].(type) {
		case bool:
			ret = append(ret, k)
		case string:
			ret = append(ret, fmt.Sprintf("%s=%q", k, v))
		case map[string]interface{}:
			urlStr, ok := v["url"].(string)
			if !ok {
				return "", fmt.Errorf("cmdline key %q has object value with no 'url' attribute", k)
			}
			urlStr, err := b.makeURLAbsolute(urlStr)
			if err != nil {
				return "", fmt.Errorf("invalid url for cmdline key %q: %s", k, err)
			}
			ret = append(ret, fmt.Sprintf("%s={{ URL %q }}", k, urlStr))
		default:
			return "", fmt.Errorf("unsupported value kind %T for cmdline key %q", m[k], k)
		}
	}
	return strings.Join(ret, " "), nil
}
<|endoftext|>


<commit_before>package batcher

import (
	"runtime"
	"time"
)

const backoff = time.Microsecond

var yield func()

func yieldWithSleep() {
	time.Sleep(backoff)
}

func yieldWithGoSched() {
	runtime.Gosched()
}

func init() {
	yield = yieldWithSleep
}
<commit_msg>FIX high CPU load issue<commit_after>package batcher

import (
	"runtime"
	"time"
)

const backoff = time.Microsecond * 10 // a too small backoff will lead to high CPU load

var yield func()

func yieldWithSleep() {
	time.Sleep(backoff)
}

func yieldWithGoSched() {
	runtime.Gosched()
}

func init() {
	yield = yieldWithSleep
}
<|endoftext|>


<commit_before>package config

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/fatih/structs"
	"github.com/kelseyhightower/envconfig"
	"github.com/spf13/viper"
	"github.com/vgarvardt/rklotz/pkg/config/plugin"
)

// AppConfig represents the application configuration
type AppConfig struct {
	LogLevel     string `envconfig:"LOG_LEVEL"`
	PostsDSN     string `envconfig:"POSTS_DSN"`
	PostsPerPage int    `envconfig:"POSTS_PERPAGE"`
	StorageDSN   string `envconfig:"STORAGE_DSN"`

	Web     WebSettings
	SSL     SSLSettings
	UI      UISetting
	RootURL RootURL
	Plugins Plugins
}

// WebSettings is the configuration for web application
type WebSettings struct {
	Port          int    `envconfig:"WEB_PORT"`
	StaticPath    string `envconfig:"WEB_STATIC_PATH"`
	TemplatesPath string `envconfig:"WEB_TEMPLATES_PATH"`
}

// SSLSettings is the configuration for TLS/SSL
type SSLSettings struct {
	Enabled      bool   `envconfig:"SSL_ENABLED"`
	Port         int    `envconfig:"SSL_PORT"`
	Host         string `envconfig:"SSL_HOST"`
	CacheDir     string `envconfig:"SSL_CACHE_DIR"`
}

// UISetting is the configuration for user interface
type UISetting struct {
	Theme       string `envconfig:"UI_THEME"`
	Author      string `envconfig:"UI_AUTHOR"`
	Email       string `envconfig:"UI_EMAIL"`
	Description string `envconfig:"UI_DESCRIPTION"`
	Language    string `envconfig:"UI_LANGUAGE"`
	Title       string `envconfig:"UI_TITLE"`
	Heading     string `envconfig:"UI_HEADING"`
	Intro       string `envconfig:"UI_INTRO"`
	// DateFormat is format for posts, see http://golang.org/pkg/time/#Time.Format
	DateFormat string `envconfig:"UI_DATEFORMAT"`
	AboutPath  string `envconfig:"UI_ABOUT_PATH"`
}

// RootURL is the configuration for app root url
type RootURL struct {
	Scheme string `envconfig:"ROOT_URL_SCHEME"`
	Host   string `envconfig:"ROOT_URL_HOST"`
	Path   string `envconfig:"ROOT_URL_PATH"`
}

// URL returns the URL for currently configured root url
func (u RootURL) URL(r *http.Request) *url.URL {
	host := u.Host
	if len(host) < 1 {
		host = r.Host
	}
	return &url.URL{Scheme: u.Scheme, Host: host, Path: u.Path}
}

// Plugins is teh configuration for app plugins
type Plugins struct {
	Enabled  []string `envconfig:"PLUGINS_ENABLED"`
	Settings PluginsSettings
}

// Configure applies configuration for enabled plugins
func (p Plugins) Configure(instance plugin.Plugin) (map[string]string, error) {
	pluginName, err := plugin.GetName(instance)
	if err != nil {
		return nil, err
	}

	settingsMap := structs.Map(p.Settings)
	pluginSettings, ok := settingsMap[strings.Title(pluginName)]
	if !ok {
		return nil, errors.New("Failed to get plugin settings")
	}
	return instance.Configure(pluginSettings.(map[string]string))
}

// PluginsSettings is the configuration for available plugins
type PluginsSettings struct {
	Disqus      map[string]string `envconfig:"PLUGINS_DISQUS"`
	Ga          map[string]string `envconfig:"PLUGINS_GA"`
	Gtm         map[string]string `envconfig:"PLUGINS_GTM"`
	Yamka       map[string]string `envconfig:"PLUGINS_YAMKA"`
	Highlightjs map[string]string `envconfig:"PLUGINS_HIGHLIGHTJS"`
	Yasha       map[string]string `envconfig:"PLUGINS_YASHA"`
}

func init() {
	viper.SetDefault("logLevel", "info")
	viper.SetDefault("postsDSN", "file:///etc/rklotz/posts")
	viper.SetDefault("postsPerPage", 10)
	viper.SetDefault("storageDSN", "boltdb:///tmp/rklotz.db")

	viper.SetDefault("web.port", 8080)
	viper.SetDefault("web.staticPath", "/etc/rklotz/static")
	viper.SetDefault("web.templatesPath", "/etc/rklotz/templates")

	viper.SetDefault("ssl.enabled", false)
	viper.SetDefault("ssl.port", 8443)
	viper.SetDefault("ssl.cacheDir", "/tmp")

	viper.SetDefault("ui.theme", "foundation")
	viper.SetDefault("ui.author", "Vladimir Garvardt")
	viper.SetDefault("ui.email", "vgarvardt@gmail.com")
	viper.SetDefault("ui.description", "rKlotz - simple golang-driven blog engine")
	viper.SetDefault("ui.language", "en")
	viper.SetDefault("ui.title", "rKlotz")
	viper.SetDefault("ui.heading", "rKlotz")
	viper.SetDefault("ui.intro", "simple golang-driven blog engine")
	viper.SetDefault("ui.dateFormat", "2 Jan 2006")
	viper.SetDefault("ui.aboutPath", "/etc/rklotz/about.html")

	viper.SetDefault("rootURL.scheme", "http")
	viper.SetDefault("rootURL.host", "")
	viper.SetDefault("rootURL.path", "/")

	viper.SetDefault("plugins.enabled", []string{})
	for name, p := range plugin.GetAll() {
		viper.SetDefault(fmt.Sprintf("plugins.settings.%s", name), p.Defaults())
	}
}

// Load loads app settings from environment variables
func Load() (*AppConfig, error) {
	var appConfig AppConfig

	if err := viper.Unmarshal(&appConfig); err != nil {
		return nil, err
	}

	err := envconfig.Process("", &appConfig)
	if err != nil {
		return nil, err
	}

	return &appConfig, nil
}
<commit_msg>Fixed code formatting<commit_after>package config

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/fatih/structs"
	"github.com/kelseyhightower/envconfig"
	"github.com/spf13/viper"
	"github.com/vgarvardt/rklotz/pkg/config/plugin"
)

// AppConfig represents the application configuration
type AppConfig struct {
	LogLevel     string `envconfig:"LOG_LEVEL"`
	PostsDSN     string `envconfig:"POSTS_DSN"`
	PostsPerPage int    `envconfig:"POSTS_PERPAGE"`
	StorageDSN   string `envconfig:"STORAGE_DSN"`

	Web     WebSettings
	SSL     SSLSettings
	UI      UISetting
	RootURL RootURL
	Plugins Plugins
}

// WebSettings is the configuration for web application
type WebSettings struct {
	Port          int    `envconfig:"WEB_PORT"`
	StaticPath    string `envconfig:"WEB_STATIC_PATH"`
	TemplatesPath string `envconfig:"WEB_TEMPLATES_PATH"`
}

// SSLSettings is the configuration for TLS/SSL
type SSLSettings struct {
	Enabled  bool   `envconfig:"SSL_ENABLED"`
	Port     int    `envconfig:"SSL_PORT"`
	Host     string `envconfig:"SSL_HOST"`
	CacheDir string `envconfig:"SSL_CACHE_DIR"`
}

// UISetting is the configuration for user interface
type UISetting struct {
	Theme       string `envconfig:"UI_THEME"`
	Author      string `envconfig:"UI_AUTHOR"`
	Email       string `envconfig:"UI_EMAIL"`
	Description string `envconfig:"UI_DESCRIPTION"`
	Language    string `envconfig:"UI_LANGUAGE"`
	Title       string `envconfig:"UI_TITLE"`
	Heading     string `envconfig:"UI_HEADING"`
	Intro       string `envconfig:"UI_INTRO"`
	// DateFormat is format for posts, see http://golang.org/pkg/time/#Time.Format
	DateFormat string `envconfig:"UI_DATEFORMAT"`
	AboutPath  string `envconfig:"UI_ABOUT_PATH"`
}

// RootURL is the configuration for app root url
type RootURL struct {
	Scheme string `envconfig:"ROOT_URL_SCHEME"`
	Host   string `envconfig:"ROOT_URL_HOST"`
	Path   string `envconfig:"ROOT_URL_PATH"`
}

// URL returns the URL for currently configured root url
func (u RootURL) URL(r *http.Request) *url.URL {
	host := u.Host
	if len(host) < 1 {
		host = r.Host
	}
	return &url.URL{Scheme: u.Scheme, Host: host, Path: u.Path}
}

// Plugins is teh configuration for app plugins
type Plugins struct {
	Enabled  []string `envconfig:"PLUGINS_ENABLED"`
	Settings PluginsSettings
}

// Configure applies configuration for enabled plugins
func (p Plugins) Configure(instance plugin.Plugin) (map[string]string, error) {
	pluginName, err := plugin.GetName(instance)
	if err != nil {
		return nil, err
	}

	settingsMap := structs.Map(p.Settings)
	pluginSettings, ok := settingsMap[strings.Title(pluginName)]
	if !ok {
		return nil, errors.New("Failed to get plugin settings")
	}
	return instance.Configure(pluginSettings.(map[string]string))
}

// PluginsSettings is the configuration for available plugins
type PluginsSettings struct {
	Disqus      map[string]string `envconfig:"PLUGINS_DISQUS"`
	Ga          map[string]string `envconfig:"PLUGINS_GA"`
	Gtm         map[string]string `envconfig:"PLUGINS_GTM"`
	Yamka       map[string]string `envconfig:"PLUGINS_YAMKA"`
	Highlightjs map[string]string `envconfig:"PLUGINS_HIGHLIGHTJS"`
	Yasha       map[string]string `envconfig:"PLUGINS_YASHA"`
}

func init() {
	viper.SetDefault("logLevel", "info")
	viper.SetDefault("postsDSN", "file:///etc/rklotz/posts")
	viper.SetDefault("postsPerPage", 10)
	viper.SetDefault("storageDSN", "boltdb:///tmp/rklotz.db")

	viper.SetDefault("web.port", 8080)
	viper.SetDefault("web.staticPath", "/etc/rklotz/static")
	viper.SetDefault("web.templatesPath", "/etc/rklotz/templates")

	viper.SetDefault("ssl.enabled", false)
	viper.SetDefault("ssl.port", 8443)
	viper.SetDefault("ssl.cacheDir", "/tmp")

	viper.SetDefault("ui.theme", "foundation")
	viper.SetDefault("ui.author", "Vladimir Garvardt")
	viper.SetDefault("ui.email", "vgarvardt@gmail.com")
	viper.SetDefault("ui.description", "rKlotz - simple golang-driven blog engine")
	viper.SetDefault("ui.language", "en")
	viper.SetDefault("ui.title", "rKlotz")
	viper.SetDefault("ui.heading", "rKlotz")
	viper.SetDefault("ui.intro", "simple golang-driven blog engine")
	viper.SetDefault("ui.dateFormat", "2 Jan 2006")
	viper.SetDefault("ui.aboutPath", "/etc/rklotz/about.html")

	viper.SetDefault("rootURL.scheme", "http")
	viper.SetDefault("rootURL.host", "")
	viper.SetDefault("rootURL.path", "/")

	viper.SetDefault("plugins.enabled", []string{})
	for name, p := range plugin.GetAll() {
		viper.SetDefault(fmt.Sprintf("plugins.settings.%s", name), p.Defaults())
	}
}

// Load loads app settings from environment variables
func Load() (*AppConfig, error) {
	var appConfig AppConfig

	if err := viper.Unmarshal(&appConfig); err != nil {
		return nil, err
	}

	err := envconfig.Process("", &appConfig)
	if err != nil {
		return nil, err
	}

	return &appConfig, nil
}
<|endoftext|>


<commit_before>package sharing

import (
	"fmt"
	"runtime"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/couchdb/mango"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/jobs"
	"github.com/cozy/cozy-stack/pkg/lock"
)

// SetupReceiver is used on the receivers' cozy to make sure the cozy can
// receive the shared documents.
func (s *Sharing) SetupReceiver(inst *instance.Instance) error {
	if err := couchdb.EnsureDBExist(inst, consts.Shared); err != nil {
		return err
	}
	if s.HasFiles() {
		return EnsureSharedWithMeDir(inst)
	}
	return nil
}

// Setup is used when a member accepts a sharing to prepare the io.cozy.shared
// database and start an initial replication. It is meant to be used in a new
// goroutine and, as such, does not return errors but log them.
func (s *Sharing) Setup(inst *instance.Instance, m *Member) {
	defer func() {
		if r := recover(); r != nil {
			var err error
			switch r := r.(type) {
			case error:
				err = r
			default:
				err = fmt.Errorf("%v", r)
			}
			stack := make([]byte, 4<<10) // 4 KB
			length := runtime.Stack(stack, false)
			log := inst.Logger().WithField("panic", true)
			log.Errorf("PANIC RECOVER %s: %s", err.Error(), stack[:length])
		}
	}()

	// Don't do the setup for most tests
	if !inst.OnboardingFinished {
		return
	}

	mu := lock.ReadWrite(inst.Domain + "/sharings/" + s.SID)
	mu.Lock()
	defer mu.Unlock()

	if err := couchdb.EnsureDBExist(inst, consts.Shared); err != nil {
		inst.Logger().Warnf("[sharing] Can't ensure io.cozy.shared exists (%s): %s", s.SID, err)
	}

	if err := s.AddTrackTriggers(inst); err != nil {
		inst.Logger().Warnf("[sharing] Errors on setup of track triggers (%s): %s", s.SID, err)
	}
	// TODO add triggers for rules that can revoke the sharing
	for i, rule := range s.Rules {
		if err := s.InitialCopy(inst, rule, i); err != nil {
			inst.Logger().Warnf("[sharing] Error on initial copy for %s (%s): %s", rule.Title, s.SID, err)
		}
	}
	if err := s.AddReplicateTrigger(inst); err != nil {
		inst.Logger().Warnf("[sharing] Error on setup replicate trigger (%s): %s", s.SID, err)
	}
	if err := s.ReplicateTo(inst, m, true); err != nil {
		inst.Logger().Warnf("[sharing] Error on initial replication (%s): %s", s.SID, err)
		s.retryReplicate(inst, 1)
	}
}

// AddTrackTriggers creates the share-track triggers for each rule of the
// sharing that will update the io.cozy.shared database.
func (s *Sharing) AddTrackTriggers(inst *instance.Instance) error {
	if s.Triggers.Track {
		return nil
	}
	sched := jobs.System()
	for i, rule := range s.Rules {
		args := rule.TriggerArgs(s.Owner)
		if args == "" {
			continue
		}
		msg, err := jobs.NewMessage(&TrackMessage{
			SharingID: s.SID,
			RuleIndex: i,
			DocType:   rule.DocType,
		})
		if err != nil {
			return err
		}
		t, err := jobs.NewTrigger(&jobs.TriggerInfos{
			Domain:     inst.Domain,
			Type:       "@event",
			WorkerType: "share-track",
			Message:    msg,
			Arguments:  args,
		})
		if err != nil {
			return err
		}
		if err = sched.AddTrigger(t); err != nil {
			return err
		}
	}
	s.Triggers.Track = true
	return couchdb.UpdateDoc(inst, s)
}

// AddReplicateTrigger creates the share-replicate trigger for this sharing:
// it will starts the replicator when some changes are made to the
// io.cozy.shared database.
func (s *Sharing) AddReplicateTrigger(inst *instance.Instance) error {
	if s.Triggers.Replicate {
		return nil
	}
	msg, err := jobs.NewMessage(&ReplicateMsg{
		SharingID: s.SID,
		Errors:    0,
	})
	if err != nil {
		return err
	}
	args := consts.Shared + ":CREATED,UPDATED:" + s.SID + ":sharing"
	t, err := jobs.NewTrigger(&jobs.TriggerInfos{
		Domain:     inst.Domain,
		Type:       "@event",
		WorkerType: "share-replicate",
		Message:    msg,
		Arguments:  args,
		Debounce:   "5s",
	})
	if err != nil {
		return err
	}
	sched := jobs.System()
	if err = sched.AddTrigger(t); err != nil {
		return err
	}
	s.Triggers.Replicate = true
	return couchdb.UpdateDoc(inst, s)
}

// InitialCopy lists the shared documents and put a reference in the
// io.cozy.shared database
func (s *Sharing) InitialCopy(inst *instance.Instance, rule Rule, r int) error {
	if rule.Local || len(rule.Values) == 0 {
		return nil
	}

	mu := lock.ReadWrite(inst.Domain + "/shared")
	mu.Lock()
	defer mu.Unlock()

	docs, err := findDocsToCopy(inst, rule)
	if err != nil {
		return err
	}
	refs, err := s.buildReferences(inst, rule, r, docs)
	if err != nil {
		return err
	}
	refs = compactSlice(refs)
	if len(refs) == 0 {
		return nil
	}
	return couchdb.BulkUpdateDocs(inst, consts.Shared, refs)
}

// findDocsToCopy finds the documents that match the given rule
func findDocsToCopy(inst *instance.Instance, rule Rule) ([]couchdb.JSONDoc, error) {
	var docs []couchdb.JSONDoc
	if rule.Selector == "" || rule.Selector == "id" {
		req := &couchdb.AllDocsRequest{
			Keys: rule.Values,
		}
		if err := couchdb.GetAllDocs(inst, rule.DocType, req, &docs); err != nil {
			return nil, err
		}
	} else {
		// Create index based on selector to retrieve documents to share
		name := "by-" + rule.Selector
		idx := mango.IndexOnFields(rule.DocType, name, []string{rule.Selector})
		if err := couchdb.DefineIndex(inst, idx); err != nil {
			return nil, err
		}
		// Request the index for all values
		for _, val := range rule.Values {
			var results []couchdb.JSONDoc
			req := &couchdb.FindRequest{
				UseIndex: name,
				Selector: mango.Equal(rule.Selector, val),
			}
			if err := couchdb.FindDocs(inst, rule.DocType, req, &results); err != nil {
				return nil, err
			}
			docs = append(docs, results...)
		}
	}
	return docs, nil
}

// buildReferences build the SharedRef to add/update the given docs in the
// io.cozy.shared database
func (s *Sharing) buildReferences(inst *instance.Instance, rule Rule, r int, docs []couchdb.JSONDoc) ([]interface{}, error) {
	ids := make([]string, len(docs))
	for i, doc := range docs {
		ids[i] = rule.DocType + "/" + doc.ID()
	}
	srefs, err := FindReferences(inst, ids)
	if err != nil {
		return nil, err
	}

	refs := make([]interface{}, len(docs))
	for i, doc := range docs {
		rev := doc.Rev()
		if srefs[i] == nil {
			refs[i] = SharedRef{
				SID:       rule.DocType + "/" + doc.ID(),
				Revisions: []string{rev},
				Infos: map[string]SharedInfo{
					s.SID: {Rule: r},
				},
			}
		} else {
			found := false
			for _, revision := range srefs[i].Revisions {
				if revision == rev {
					found = true
					break
				}
			}
			if found {
				if _, ok := srefs[i].Infos[s.SID]; ok {
					continue
				}
			} else {
				srefs[i].Revisions = append(srefs[i].Revisions, rev)
			}
			srefs[i].Infos[s.SID] = SharedInfo{Rule: r}
			refs[i] = *srefs[i]
		}
	}

	return refs, nil
}

// compactSlice returns the given slice without the nil values
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
func compactSlice(a []interface{}) []interface{} {
	b := a[:0]
	for _, x := range a {
		if x != nil {
			b = append(b, x)
		}
	}
	return b
}
<commit_msg>Add sharing triggers on the receiver too<commit_after>package sharing

import (
	"fmt"
	"runtime"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/couchdb/mango"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/jobs"
	"github.com/cozy/cozy-stack/pkg/lock"
)

// SetupReceiver is used on the receivers' cozy to make sure the cozy can
// receive the shared documents.
func (s *Sharing) SetupReceiver(inst *instance.Instance) error {
	if err := couchdb.EnsureDBExist(inst, consts.Shared); err != nil {
		return err
	}
	if s.HasFiles() {
		return EnsureSharedWithMeDir(inst)
	}
	if err := s.AddTrackTriggers(inst); err != nil {
		return err
	}
	// TODO do not add the share-replicate trigger for a one-way sharing
	if err := s.AddReplicateTrigger(inst); err != nil {
		return err
	}
	return nil
}

// Setup is used when a member accepts a sharing to prepare the io.cozy.shared
// database and start an initial replication. It is meant to be used in a new
// goroutine and, as such, does not return errors but log them.
func (s *Sharing) Setup(inst *instance.Instance, m *Member) {
	defer func() {
		if r := recover(); r != nil {
			var err error
			switch r := r.(type) {
			case error:
				err = r
			default:
				err = fmt.Errorf("%v", r)
			}
			stack := make([]byte, 4<<10) // 4 KB
			length := runtime.Stack(stack, false)
			log := inst.Logger().WithField("panic", true)
			log.Errorf("PANIC RECOVER %s: %s", err.Error(), stack[:length])
		}
	}()

	// Don't do the setup for most tests
	if !inst.OnboardingFinished {
		return
	}

	mu := lock.ReadWrite(inst.Domain + "/sharings/" + s.SID)
	mu.Lock()
	defer mu.Unlock()

	if err := couchdb.EnsureDBExist(inst, consts.Shared); err != nil {
		inst.Logger().Warnf("[sharing] Can't ensure io.cozy.shared exists (%s): %s", s.SID, err)
	}

	if err := s.AddTrackTriggers(inst); err != nil {
		inst.Logger().Warnf("[sharing] Errors on setup of track triggers (%s): %s", s.SID, err)
	}
	// TODO add triggers for rules that can revoke the sharing
	for i, rule := range s.Rules {
		if err := s.InitialCopy(inst, rule, i); err != nil {
			inst.Logger().Warnf("[sharing] Error on initial copy for %s (%s): %s", rule.Title, s.SID, err)
		}
	}
	if err := s.AddReplicateTrigger(inst); err != nil {
		inst.Logger().Warnf("[sharing] Error on setup replicate trigger (%s): %s", s.SID, err)
	}
	if err := s.ReplicateTo(inst, m, true); err != nil {
		inst.Logger().Warnf("[sharing] Error on initial replication (%s): %s", s.SID, err)
		s.retryReplicate(inst, 1)
	}
}

// AddTrackTriggers creates the share-track triggers for each rule of the
// sharing that will update the io.cozy.shared database.
func (s *Sharing) AddTrackTriggers(inst *instance.Instance) error {
	if s.Triggers.Track {
		return nil
	}
	sched := jobs.System()
	for i, rule := range s.Rules {
		args := rule.TriggerArgs(s.Owner)
		if args == "" {
			continue
		}
		msg, err := jobs.NewMessage(&TrackMessage{
			SharingID: s.SID,
			RuleIndex: i,
			DocType:   rule.DocType,
		})
		if err != nil {
			return err
		}
		t, err := jobs.NewTrigger(&jobs.TriggerInfos{
			Domain:     inst.Domain,
			Type:       "@event",
			WorkerType: "share-track",
			Message:    msg,
			Arguments:  args,
		})
		if err != nil {
			return err
		}
		if err = sched.AddTrigger(t); err != nil {
			return err
		}
	}
	s.Triggers.Track = true
	return couchdb.UpdateDoc(inst, s)
}

// AddReplicateTrigger creates the share-replicate trigger for this sharing:
// it will starts the replicator when some changes are made to the
// io.cozy.shared database.
func (s *Sharing) AddReplicateTrigger(inst *instance.Instance) error {
	if s.Triggers.Replicate {
		return nil
	}
	msg, err := jobs.NewMessage(&ReplicateMsg{
		SharingID: s.SID,
		Errors:    0,
	})
	if err != nil {
		return err
	}
	args := consts.Shared + ":CREATED,UPDATED:" + s.SID + ":sharing"
	t, err := jobs.NewTrigger(&jobs.TriggerInfos{
		Domain:     inst.Domain,
		Type:       "@event",
		WorkerType: "share-replicate",
		Message:    msg,
		Arguments:  args,
		Debounce:   "5s",
	})
	if err != nil {
		return err
	}
	sched := jobs.System()
	if err = sched.AddTrigger(t); err != nil {
		return err
	}
	s.Triggers.Replicate = true
	return couchdb.UpdateDoc(inst, s)
}

// InitialCopy lists the shared documents and put a reference in the
// io.cozy.shared database
func (s *Sharing) InitialCopy(inst *instance.Instance, rule Rule, r int) error {
	if rule.Local || len(rule.Values) == 0 {
		return nil
	}

	mu := lock.ReadWrite(inst.Domain + "/shared")
	mu.Lock()
	defer mu.Unlock()

	docs, err := findDocsToCopy(inst, rule)
	if err != nil {
		return err
	}
	refs, err := s.buildReferences(inst, rule, r, docs)
	if err != nil {
		return err
	}
	refs = compactSlice(refs)
	if len(refs) == 0 {
		return nil
	}
	return couchdb.BulkUpdateDocs(inst, consts.Shared, refs)
}

// findDocsToCopy finds the documents that match the given rule
func findDocsToCopy(inst *instance.Instance, rule Rule) ([]couchdb.JSONDoc, error) {
	var docs []couchdb.JSONDoc
	if rule.Selector == "" || rule.Selector == "id" {
		req := &couchdb.AllDocsRequest{
			Keys: rule.Values,
		}
		if err := couchdb.GetAllDocs(inst, rule.DocType, req, &docs); err != nil {
			return nil, err
		}
	} else {
		// Create index based on selector to retrieve documents to share
		name := "by-" + rule.Selector
		idx := mango.IndexOnFields(rule.DocType, name, []string{rule.Selector})
		if err := couchdb.DefineIndex(inst, idx); err != nil {
			return nil, err
		}
		// Request the index for all values
		for _, val := range rule.Values {
			var results []couchdb.JSONDoc
			req := &couchdb.FindRequest{
				UseIndex: name,
				Selector: mango.Equal(rule.Selector, val),
			}
			if err := couchdb.FindDocs(inst, rule.DocType, req, &results); err != nil {
				return nil, err
			}
			docs = append(docs, results...)
		}
	}
	return docs, nil
}

// buildReferences build the SharedRef to add/update the given docs in the
// io.cozy.shared database
func (s *Sharing) buildReferences(inst *instance.Instance, rule Rule, r int, docs []couchdb.JSONDoc) ([]interface{}, error) {
	ids := make([]string, len(docs))
	for i, doc := range docs {
		ids[i] = rule.DocType + "/" + doc.ID()
	}
	srefs, err := FindReferences(inst, ids)
	if err != nil {
		return nil, err
	}

	refs := make([]interface{}, len(docs))
	for i, doc := range docs {
		rev := doc.Rev()
		if srefs[i] == nil {
			refs[i] = SharedRef{
				SID:       rule.DocType + "/" + doc.ID(),
				Revisions: []string{rev},
				Infos: map[string]SharedInfo{
					s.SID: {Rule: r},
				},
			}
		} else {
			found := false
			for _, revision := range srefs[i].Revisions {
				if revision == rev {
					found = true
					break
				}
			}
			if found {
				if _, ok := srefs[i].Infos[s.SID]; ok {
					continue
				}
			} else {
				srefs[i].Revisions = append(srefs[i].Revisions, rev)
			}
			srefs[i].Infos[s.SID] = SharedInfo{Rule: r}
			refs[i] = *srefs[i]
		}
	}

	return refs, nil
}

// compactSlice returns the given slice without the nil values
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
func compactSlice(a []interface{}) []interface{} {
	b := a[:0]
	for _, x := range a {
		if x != nil {
			b = append(b, x)
		}
	}
	return b
}
<|endoftext|>


<commit_before>package s3

import (
	"io"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"

	"github.com/sjansen/hoggle/pkg/storage"
)

type Factory struct {
	Region   string
	Bucket   string
	Prefix   string
	Endpoint string
}

type Bucket struct {
	bucket     string
	prefix     string
	uploader   *s3manager.Uploader
	downloader *s3manager.Downloader
}

func (f *Factory) New() (storage.Container, error) {
	config := &aws.Config{}
	if f.Endpoint != "" {
		config.Region = aws.String(f.Region)
		config.Endpoint = aws.String(f.Endpoint)
		config.DisableSSL = aws.Bool(true)
		config.S3ForcePathStyle = aws.Bool(true)
	}
	sess := session.Must(session.NewSession(config))
	b := &Bucket{
		bucket:     f.Bucket,
		prefix:     f.Prefix,
		uploader:   s3manager.NewUploader(sess),
		downloader: s3manager.NewDownloader(sess),
	}
	return b, nil
}

func (b *Bucket) Download(oid string, dst io.WriterAt) error {
	params := &s3.GetObjectInput{
		Bucket: aws.String(b.bucket),
		Key:    aws.String(b.prefix + oid),
	}
	if _, err := b.downloader.Download(dst, params); err != nil {
		return err
	} else {
		return nil
	}
}

func (b *Bucket) Upload(oid string, src io.ReadSeeker) error {
	params := &s3manager.UploadInput{
		Bucket: aws.String(b.bucket),
		Key:    aws.String(b.prefix + oid),
		Body:   src,
	}
	if _, err := b.uploader.Upload(params); err != nil {
		return err
	} else {
		return nil
	}
}
<commit_msg>improve S3 session creation<commit_after>package s3

import (
	"io"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"

	"github.com/sjansen/hoggle/pkg/storage"
)

type Factory struct {
	Region   string
	Bucket   string
	Prefix   string
	Endpoint string
}

type Bucket struct {
	bucket     string
	prefix     string
	uploader   *s3manager.Uploader
	downloader *s3manager.Downloader
}

func (f *Factory) New() (storage.Container, error) {
	opts := session.Options{
		SharedConfigState: session.SharedConfigEnable,
	}
	config := &opts.Config
	if f.Endpoint != "" {
		config.Region = aws.String(f.Region)
		config.Endpoint = aws.String(f.Endpoint)
		if strings.HasPrefix(f.Endpoint, "http://") {
			config.DisableSSL = aws.Bool(true)
		}
		config.S3ForcePathStyle = aws.Bool(true)
	} else if f.Region != "" {
		config.Region = aws.String(f.Region)
	}
	sess := session.Must(session.NewSessionWithOptions(opts))
	b := &Bucket{
		bucket:     f.Bucket,
		prefix:     f.Prefix,
		uploader:   s3manager.NewUploader(sess),
		downloader: s3manager.NewDownloader(sess),
	}
	return b, nil
}

func (b *Bucket) Download(oid string, dst io.WriterAt) error {
	params := &s3.GetObjectInput{
		Bucket: aws.String(b.bucket),
		Key:    aws.String(b.prefix + oid),
	}
	if _, err := b.downloader.Download(dst, params); err != nil {
		return err
	} else {
		return nil
	}
}

func (b *Bucket) Upload(oid string, src io.ReadSeeker) error {
	params := &s3manager.UploadInput{
		Bucket: aws.String(b.bucket),
		Key:    aws.String(b.prefix + oid),
		Body:   src,
	}
	if _, err := b.uploader.Upload(params); err != nil {
		return err
	} else {
		return nil
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Matthew Collins
//
// 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 steven

import (
	"encoding/base64"
	"encoding/json"
	"sort"
	"strings"

	"github.com/thinkofdeath/steven/chat"
	"github.com/thinkofdeath/steven/protocol"
	"github.com/thinkofdeath/steven/render"
	"github.com/thinkofdeath/steven/render/ui"
)

var playerList = map[protocol.UUID]*playerInfo{}

type playerInfo struct {
	name        string
	uuid        protocol.UUID
	displayName chat.AnyComponent
	gameMode    gameMode
	ping        int

	skin     *render.TextureInfo
	skinHash string
}

type playerListUI struct {
	enabled bool

	background [3]*ui.Image
	entries    []*playerListUIEntry
}

type playerListUIEntry struct {
	text    *ui.Text
	icon    *ui.Image
	iconHat *ui.Image
	ping    *ui.Image
}

func (p playerListUIEntry) set(enabled bool) {
	p.text.Visible = enabled
	p.icon.Visible = enabled
	p.iconHat.Visible = enabled
	p.ping.Visible = enabled
}

func (p *playerListUI) init() {
	for i := range p.background {
		p.background[i] = ui.NewImage(render.GetTexture("solid"), 0, 16, 16, 16, 0, 0, 1, 1, 0, 0, 0)
		p.background[i].A = 120
		p.background[i].Visible = false
		ui.AddDrawable(p.background[i], ui.Top, ui.Center)
	}
}

func (p *playerListUI) set(enabled bool) {
	p.enabled = enabled
	for _, b := range p.background {
		b.Visible = enabled
	}
	for _, e := range p.entries {
		e.set(enabled)
	}
}

func (p *playerListUI) render(delta float64) {
	if !p.enabled {
		return
	}
	for _, b := range p.background {
		b.Visible = false
	}
	for _, e := range p.entries {
		e.set(false)
	}
	offset := 0
	count := 0
	width := 0.0
	bTab := 0
	lastEntry := 0
	for _, pl := range p.players() {
		if count >= 20 {
			entries := p.entries[lastEntry:offset]
			lastEntry = offset
			for _, e := range entries {
				if e.icon.Visible {
					e.icon.X = -width/2 - 12
					e.iconHat.X = -width/2 - 12
					e.ping.X = width/2 + 12
				}
			}
			p.background[bTab].W = width + 48
			p.background[bTab].H = float64(count * 18)
			width = 0
			count = 0
			bTab++
			if bTab >= len(p.background) {
				break
			}
		}
		background := p.background[bTab]
		background.Visible = true
		if offset >= len(p.entries) {
			text := ui.NewText("", 0, 0, 255, 255, 255)
			ui.AddDrawable(text, ui.Top, ui.Center)
			icon := ui.NewImage(pl.skin, 0, 0, 16, 16, 8/64.0, 8/64.0, 8/64.0, 8/64.0, 255, 255, 255)
			ui.AddDrawable(icon, ui.Top, ui.Center)
			iconHat := ui.NewImage(pl.skin, 0, 0, 16, 16, 40/64.0, 8/64.0, 8/64.0, 8/64.0, 255, 255, 255)
			ui.AddDrawable(iconHat, ui.Top, ui.Center)
			ping := ui.NewImage(render.GetTexture("gui/icons"), 0, 0, 20, 16, 0, 16/256.0, 10/256.0, 8/256.0, 255, 255, 255)
			ui.AddDrawable(ping, ui.Top, ui.Center)

			text.Parent = background
			icon.Parent = background
			iconHat.Parent = background
			ping.Parent = background

			p.entries = append(p.entries, &playerListUIEntry{
				text:    text,
				icon:    icon,
				iconHat: iconHat,
				ping:    ping,
			})
		}
		e := p.entries[offset]
		e.set(true)
		offset++
		e.text.Y = 1 + 18*float64(count)
		e.text.Update(pl.name)
		if e.text.Width > width {
			width = e.text.Width
		}
		e.icon.Y = 1 + 18*float64(count)
		e.icon.Texture = pl.skin
		e.iconHat.Y = 1 + 18*float64(count)
		e.iconHat.Texture = pl.skin

		e.ping.Y = 1 + 18*float64(count)
		y := 0.0
		switch {
		case pl.ping <= 75:
			y = 16 / 256.0
		case pl.ping <= 150:
			y = 24 / 256.0
		case pl.ping <= 225:
			y = 32 / 256.0
		case pl.ping <= 350:
			y = 40 / 256.0
		case pl.ping < 999:
			y = 48 / 256.0
		default:
			y = 56 / 256.0
		}
		e.ping.TY = y
		count++
	}

	if bTab < len(p.background) {
		for _, e := range p.entries {
			if e.icon.Visible {
				e.icon.X = -width/2 - 12
				e.iconHat.X = -width/2 - 12
				e.ping.X = width/2 + 12
			}
		}
		p.background[bTab].W = width + 48
		p.background[bTab].H = float64(count * 18)
	}

	switch bTab {
	case 0: // Single
		p.background[0].X = 0
	case 1: // Double
		p.background[0].X = -p.background[0].W / 2
		p.background[1].X = p.background[1].W / 2
	case 2, 3: // Triple
		p.background[0].X = -(p.background[1].W / 2) - p.background[0].W/2
		p.background[1].X = 0
		p.background[2].X = (p.background[1].W / 2) + p.background[2].W/2
	}
}

func (p *playerListUI) players() (out []*playerInfo) {
	for _, pl := range playerList {
		out = append(out, pl)
	}
	sort.Sort(sortedPlayerList(out))
	return
}

type sortedPlayerList []*playerInfo

func (s sortedPlayerList) Len() int { return len(s) }
func (s sortedPlayerList) Less(a, b int) bool {
	if s[a].name < s[b].name {
		return true
	}
	return false
}
func (s sortedPlayerList) Swap(a, b int) { s[a], s[b] = s[b], s[a] }

func (handler) PlayerListInfo(p *protocol.PlayerInfo) {
	for _, pl := range p.Players {
		if _, ok := playerList[pl.UUID]; (!ok && p.Action != 0) || (ok && p.Action == 0) {
			continue
		}
		switch p.Action {
		case 0: // Add
			i := &playerInfo{
				name:        pl.Name,
				uuid:        pl.UUID,
				displayName: pl.DisplayName,
				gameMode:    gameMode(pl.GameMode),
				ping:        int(pl.Ping),
			}
			for _, prop := range pl.Properties {
				if prop.Name == "textures" {
					data, err := base64.URLEncoding.DecodeString(prop.Value)
					if err != nil {
						continue
					}
					var blob skinBlob
					err = json.Unmarshal(data, &blob)
					if err != nil {
						continue
					}
					url := blob.Textures.Skin.Url
					if strings.HasPrefix(url, "http://textures.minecraft.net/texture/") {
						i.skinHash = url[len("http://textures.minecraft.net/texture/"):]
						render.RefSkin(i.skinHash)
						i.skin = render.Skin(i.skinHash)
					}
				}
			}
			if i.skin == nil {
				i.skin = render.GetTexture("entity/steve")
			}
			playerList[pl.UUID] = i
		case 1: // Update gamemode
			playerList[pl.UUID].gameMode = gameMode(pl.GameMode)
		case 2: // Update ping
			playerList[pl.UUID].ping = int(pl.Ping)
		case 3: // Update display name
			playerList[pl.UUID].displayName = pl.DisplayName
		case 4: // Remove
			i := playerList[pl.UUID]
			if i.skinHash != "" {
				render.FreeSkin(i.skinHash)
			}
			delete(playerList, pl.UUID)
		}
	}
}

type skinBlob struct {
	Timestamp     int64
	ProfileID     string
	ProfileString string
	IsPublic      bool
	Textures      struct {
		Skin skinPath
		Cape skinPath
	}
}

type skinPath struct {
	Url string
}
<commit_msg>steven: make the entries fixed size and add another column<commit_after>// Copyright 2015 Matthew Collins
//
// 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 steven

import (
	"encoding/base64"
	"encoding/json"
	"sort"
	"strings"

	"github.com/thinkofdeath/steven/chat"
	"github.com/thinkofdeath/steven/protocol"
	"github.com/thinkofdeath/steven/render"
	"github.com/thinkofdeath/steven/render/ui"
)

const playerListWidth = 150

var playerList = map[protocol.UUID]*playerInfo{}

type playerInfo struct {
	name        string
	uuid        protocol.UUID
	displayName chat.AnyComponent
	gameMode    gameMode
	ping        int

	skin     *render.TextureInfo
	skinHash string
}

type playerListUI struct {
	enabled bool

	background [4]*ui.Image
	entries    []*playerListUIEntry
}

type playerListUIEntry struct {
	text    *ui.Text
	icon    *ui.Image
	iconHat *ui.Image
	ping    *ui.Image
}

func (p playerListUIEntry) set(enabled bool) {
	p.text.Visible = enabled
	p.icon.Visible = enabled
	p.iconHat.Visible = enabled
	p.ping.Visible = enabled
}

func (p *playerListUI) init() {
	for i := range p.background {
		p.background[i] = ui.NewImage(render.GetTexture("solid"), 0, 16, playerListWidth+48, 16, 0, 0, 1, 1, 0, 0, 0)
		p.background[i].A = 120
		p.background[i].Visible = false
		ui.AddDrawable(p.background[i], ui.Top, ui.Center)
	}
}

func (p *playerListUI) set(enabled bool) {
	p.enabled = enabled
	for _, b := range p.background {
		b.Visible = enabled
	}
	for _, e := range p.entries {
		e.set(enabled)
	}
}

func (p *playerListUI) render(delta float64) {
	if !p.enabled {
		return
	}
	for _, b := range p.background {
		b.Visible = false
	}
	for _, e := range p.entries {
		e.set(false)
	}
	offset := 0
	count := 0
	bTab := 0
	lastEntry := 0
	for _, pl := range p.players() {
		if count >= 20 {
			entries := p.entries[lastEntry:offset]
			lastEntry = offset
			for _, e := range entries {
				if e.icon.Visible {
					e.icon.X = -playerListWidth/2 - 12
					e.iconHat.X = -playerListWidth/2 - 12
					e.ping.X = playerListWidth/2 + 12
				}
			}
			p.background[bTab].H = float64(count * 18)
			count = 0
			bTab++
			if bTab >= len(p.background) {
				break
			}
		}
		background := p.background[bTab]
		background.Visible = true
		if offset >= len(p.entries) {
			text := ui.NewText("", 0, 0, 255, 255, 255)
			ui.AddDrawable(text, ui.Top, ui.Center)
			icon := ui.NewImage(pl.skin, 0, 0, 16, 16, 8/64.0, 8/64.0, 8/64.0, 8/64.0, 255, 255, 255)
			ui.AddDrawable(icon, ui.Top, ui.Center)
			iconHat := ui.NewImage(pl.skin, 0, 0, 16, 16, 40/64.0, 8/64.0, 8/64.0, 8/64.0, 255, 255, 255)
			ui.AddDrawable(iconHat, ui.Top, ui.Center)
			ping := ui.NewImage(render.GetTexture("gui/icons"), 0, 0, 20, 16, 0, 16/256.0, 10/256.0, 8/256.0, 255, 255, 255)
			ui.AddDrawable(ping, ui.Top, ui.Center)

			text.Parent = background
			icon.Parent = background
			iconHat.Parent = background
			ping.Parent = background

			p.entries = append(p.entries, &playerListUIEntry{
				text:    text,
				icon:    icon,
				iconHat: iconHat,
				ping:    ping,
			})
		}
		e := p.entries[offset]
		e.set(true)
		offset++
		e.text.Y = 1 + 18*float64(count)
		e.text.Update(pl.name)
		e.icon.Y = 1 + 18*float64(count)
		e.icon.Texture = pl.skin
		e.iconHat.Y = 1 + 18*float64(count)
		e.iconHat.Texture = pl.skin

		e.ping.Y = 1 + 18*float64(count)
		y := 0.0
		switch {
		case pl.ping <= 75:
			y = 16 / 256.0
		case pl.ping <= 150:
			y = 24 / 256.0
		case pl.ping <= 225:
			y = 32 / 256.0
		case pl.ping <= 350:
			y = 40 / 256.0
		case pl.ping < 999:
			y = 48 / 256.0
		default:
			y = 56 / 256.0
		}
		e.ping.TY = y
		count++
	}

	if bTab < len(p.background) {
		for _, e := range p.entries {
			if e.icon.Visible {
				e.icon.X = -playerListWidth/2 - 12
				e.iconHat.X = -playerListWidth/2 - 12
				e.ping.X = playerListWidth/2 + 12
			}
		}
		p.background[bTab].H = float64(count * 18)
	}

	switch bTab {
	case 0: // Single
		p.background[0].X = 0
	case 1: // Double
		p.background[0].X = -p.background[0].W / 2
		p.background[1].X = p.background[1].W / 2
	case 2: // Triple
		p.background[0].X = -(p.background[1].W / 2) - p.background[0].W/2
		p.background[1].X = 0
		p.background[2].X = (p.background[1].W / 2) + p.background[2].W/2
	default: // Quad
		p.background[0].X = -p.background[0].W/2 - p.background[1].W
		p.background[3].X = p.background[3].W/2 + p.background[2].W

		p.background[1].X = -p.background[1].W / 2
		p.background[2].X = p.background[2].W / 2
	}
}

func (p *playerListUI) players() (out []*playerInfo) {
	for _, pl := range playerList {
		out = append(out, pl)
	}
	sort.Sort(sortedPlayerList(out))
	return
}

type sortedPlayerList []*playerInfo

func (s sortedPlayerList) Len() int { return len(s) }
func (s sortedPlayerList) Less(a, b int) bool {
	if s[a].name < s[b].name {
		return true
	}
	return false
}
func (s sortedPlayerList) Swap(a, b int) { s[a], s[b] = s[b], s[a] }

func (handler) PlayerListInfo(p *protocol.PlayerInfo) {
	for _, pl := range p.Players {
		if _, ok := playerList[pl.UUID]; (!ok && p.Action != 0) || (ok && p.Action == 0) {
			continue
		}
		switch p.Action {
		case 0: // Add
			i := &playerInfo{
				name:        pl.Name,
				uuid:        pl.UUID,
				displayName: pl.DisplayName,
				gameMode:    gameMode(pl.GameMode),
				ping:        int(pl.Ping),
			}
			for _, prop := range pl.Properties {
				if prop.Name == "textures" {
					data, err := base64.URLEncoding.DecodeString(prop.Value)
					if err != nil {
						continue
					}
					var blob skinBlob
					err = json.Unmarshal(data, &blob)
					if err != nil {
						continue
					}
					url := blob.Textures.Skin.Url
					if strings.HasPrefix(url, "http://textures.minecraft.net/texture/") {
						i.skinHash = url[len("http://textures.minecraft.net/texture/"):]
						render.RefSkin(i.skinHash)
						i.skin = render.Skin(i.skinHash)
					}
				}
			}
			if i.skin == nil {
				i.skin = render.GetTexture("entity/steve")
			}
			playerList[pl.UUID] = i
		case 1: // Update gamemode
			playerList[pl.UUID].gameMode = gameMode(pl.GameMode)
		case 2: // Update ping
			playerList[pl.UUID].ping = int(pl.Ping)
		case 3: // Update display name
			playerList[pl.UUID].displayName = pl.DisplayName
		case 4: // Remove
			i := playerList[pl.UUID]
			if i.skinHash != "" {
				render.FreeSkin(i.skinHash)
			}
			delete(playerList, pl.UUID)
		}
	}
}

type skinBlob struct {
	Timestamp     int64
	ProfileID     string
	ProfileString string
	IsPublic      bool
	Textures      struct {
		Skin skinPath
		Cape skinPath
	}
}

type skinPath struct {
	Url string
}
<|endoftext|>


<commit_before>package main

// Test of initialization order of package-level vars.

type T int

var counter int

func next() int {
	c := counter
	counter++
	return c
}

func (T) next() int {
	return next()
}

var t T

func makeOrder1() [6]int {
	return [6]int{f1, b1, d1, e1, c1, a1}
}

func makeOrder2() [6]int {
	return [6]int{f2, b2, d2, e2, c2, a2}
}

var order1 = makeOrder1()

func main() {
	// order1 is a package-level variable:
	// [a-f]1 are initialized is reference order.
	if order1 != [6]int{0, 1, 2, 3, 4, 5} {
		panic(order1)
	}

	// order2 is a local variable:
	// [a-f]2 are initialized in lexical order.
	var order2 = makeOrder2()
	if order2 != [6]int{11, 7, 9, 10, 8, 6} {
		panic(order2)
	}
}

// The references traversal visits through calls to package-level
// functions (next), method expressions (T.next) and methods (t.next).

var a1, b1 = next(), next()
var c1, d1 = T.next(0), T.next(0)
var e1, f1 = t.next(), t.next()

var a2, b2 = next(), next()
var c2, d2 = T.next(0), T.next(0)
var e2, f2 = t.next(), t.next()
<commit_msg>go.tools/ssa/interp: remove irrelevent parts of test, improve comments.<commit_after>package main

// Test of initialization order of package-level vars.

var counter int

func next() int {
	c := counter
	counter++
	return c
}

func makeOrder1() [6]int {
	// The values of these vars are determined by the (arbitrary)
	// order in which we refer to them here. f=0, b=1, d=2, etc.
	return [6]int{f1, b1, d1, e1, c1, a1}
}

func makeOrder2() [6]int {
	// The values of these vars are independent of the order in
	// which we refer to them here.  a=6, b=7, c=8, etc.
	return [6]int{f2, b2, d2, e2, c2, a2}
}

var order1 = makeOrder1()

func main() {
	// order1 is a package-level variable:
	// [a-f]1 are initialized in reference order.
	if order1 != [6]int{0, 1, 2, 3, 4, 5} {
		panic(order1)
	}

	// order2 is a local variable:
	// [a-f]2 are initialized in lexical order.
	var order2 = makeOrder2()
	if order2 != [6]int{11, 7, 9, 10, 8, 6} {
		panic(order2)
	}
}

var a1, b1 = next(), next()
var c1, d1 = next(), next()
var e1, f1 = next(), next()

var a2, b2 = next(), next()
var c2, d2 = next(), next()
var e2, f2 = next(), next()
<|endoftext|>