<commit_before>package rpg2d

import (
	"errors"
	"time"

	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/rpg2d/quad"
	"github.com/ghthor/engine/sim/stime"
)

type Actor interface {
	Id() int64

	// Returns an entity that represents the
	// actor in the simulation's world.
	Entity() entity.Entity

	// Enables the simulation to send the
	// state of the world to the actor
	WriteState(WorldState)
}

// A SimulationDef used to configure a simulation
// to define the how the simulation will behave.
type SimulationDef struct {
	// The target FPS for the simulation to calculate at
	FPS int

	// Initial World State
	Now        stime.Time
	QuadTree   quad.Quad
	TerrainMap TerrainMap

	// User defined input application phase
	InputPhaseHandler quad.InputPhaseHandler

	// User defined the narrow phase
	NarrowPhaseHandler quad.NarrowPhaseHandler
}

type initialWorldState struct {
	now        stime.Time
	quadTree   quad.Quad
	terrainMap TerrainMap
}

type simSettings struct {
	fps int

	quad.InputPhaseHandler
	quad.NarrowPhaseHandler
}

type UnstartedSimulation interface {
	Begin() (RunningSimulation, error)
}

type RunningSimulation interface {
	ConnectActor(Actor)
	RemoveActor(Actor)
	Halt() (HaltedSimulation, error)
}

type HaltedSimulation interface {
	Quad() quad.Quad
}

// An implementation of RunningSimulation
type runningSimulation struct {
	//---- Communication
	// These channels are used by the public api
	// to add and remove actors. They are 1way
	// send only channels. The requests contains
	// a send only and recieve only channel so the
	// the public api call will be atomic action
	// and the caller can assume without a doubt
	// that the actor is now added or removed
	// from the simulation.
	addActor    chan<- addActorReq
	removeActor chan<- removeActorReq

	// This channel is used by the public api to request
	// that the simulation is halted. You send a 1way
	// send only channel into the game loop so the public
	// api can wait and be notified that the go routine
	// has returned and is no longer running.
	requestHalt chan<- chan<- HaltedSimulation
}

// Communication object used to atomicly add a new actor to the sim
type addActorReq struct {
	toBeAdded chan Actor
	wasAdded  chan Actor
}

// Add an actor into the running simulation
func (s runningSimulation) ConnectActor(a Actor) {
	ch := make(chan Actor)

	// Create an add request
	actor := addActorReq{ch, ch}

	// Send the add request to the game loop
	s.addActor <- actor

	// Send the actor to be added to the game loop
	actor.toBeAdded <- a

	// Wait for the add request to be successfully completed
	a = <-actor.wasAdded
}

// Communication object used to atomicly remove an actor from the sim
type removeActorReq struct {
	toBeRemoved chan Actor
	wasRemoved  chan Actor
}

// Remove an actor from the running simulation
func (s runningSimulation) RemoveActor(a Actor) {
	ch := make(chan Actor)

	// Create a remove request
	actor := removeActorReq{ch, ch}

	// Send the remove request to the game loop
	s.removeActor <- actor

	// Send the actor to be removed to the game loop
	actor.toBeRemoved <- a

	// Wait for the remove request to be successfully completed
	a = <-actor.wasRemoved
}

// Stop the simulation
func (s runningSimulation) Halt() (HaltedSimulation, error) {
	wasHalted := make(chan HaltedSimulation)

	// Send a request to the game loop to halt
	s.requestHalt <- wasHalted

	// Wait for the halt request to be successfully completed
	return <-wasHalted, nil
}

var ErrMustProvideAQuadtree = errors.New("user must provide a quad tree to a simulation defination")
var ErrMustProvideATerrainMap = errors.New("user must provide a terrain map to a simulation defination")

// Implement engine/sim.UnstartedSimulation
func (s SimulationDef) Begin() (RunningSimulation, error) {
	if s.QuadTree == nil {
		return nil, ErrMustProvideAQuadtree
	}

	if s.TerrainMap.Bounds != s.QuadTree.Bounds() {
		return nil, ErrMustProvideATerrainMap
	}

	initialState := initialWorldState{
		now:        s.Now,
		quadTree:   s.QuadTree,
		terrainMap: s.TerrainMap,
	}

	settings := simSettings{
		s.FPS,

		s.InputPhaseHandler,
		s.NarrowPhaseHandler,
	}

	rs := &runningSimulation{}

	// Starts 2 go routines and returns
	// The ticker and the engine communication kernel
	rs.startLoop(initialState, settings)

	return rs, nil
}

// Prepares a closure and executes it as a go routine
// Calling this function will create 2 infinte looping
// go routines. One for the clock ticker and one for
// calulating the next world state and adding/removing actors.
// This method has a pointer recv because it MUST set the
// addActor and removeActor communication channels used
// by the public api to request adding & removing actors
func (s *runningSimulation) startLoop(initialState initialWorldState, settings simSettings) {
	//---- Create all the communication channels

	// Make the 2way channels that will be used to make
	// add and remove actor requests to the go routine game loop
	addCh := make(chan addActorReq)
	removeCh := make(chan removeActorReq)

	// Set the 1way send channels used by the public api
	s.addActor = addCh
	s.removeActor = removeCh

	// Set the 1way recieve channels used by the game loop
	var addReq <-chan addActorReq
	var removeReq <-chan removeActorReq

	addReq = addCh
	removeReq = removeCh

	// Map of all the actors currently connected to the simulation
	actors := make(map[int64]Actor)

	// Make channel to be used to by the public api to
	// request that the simulation be halted
	haltCh := make(chan chan<- HaltedSimulation)

	// Set the 1way send channel used by the public api
	s.requestHalt = haltCh

	// Set the 1way recieve channel used by the game loop
	var haltReq <-chan chan<- HaltedSimulation
	haltReq = haltCh

	clock := stime.Clock(initialState.now)
	world := newWorld(initialState.now, initialState.quadTree, initialState.terrainMap)

	//---- User provided input application phase
	inputPhase := settings.InputPhaseHandler

	//---- User provided narrow phase
	narrowPhase := settings.NarrowPhaseHandler

	runTick := func(q quad.Quad, t stime.Time) quad.Quad {
		return quad.RunPhasesOn(q, inputPhase, narrowPhase, t)
	}

	// Start the Clock
	ticker := time.NewTicker(time.Duration(1000/settings.fps) * time.Millisecond)

	// Start the simulation server
	go func() {
		var hasHalted chan<- HaltedSimulation

		var worldState WorldState

	communicationLoop:
		// # This select prioritizes the following communication events
		// ## 2 potential events to respond to
		// 1. Trigger a simulation tick
		// 2. Halt() method has requested halting
		select {
		case <-ticker.C:
			goto tick

		case hasHalted = <-haltReq:
			goto exit
		default:
		}

		// ## 2 potential events to respond to
		// 1. Trigger a simulation tick
		// 2. ConnectActor() method has requested to connect an actor
		// 3. RemoveActor() method has requested to remove an actor
		// 4. Halt() method has requested halting
		select {
		case <-ticker.C:
			goto tick

		case actor := <-addReq:
			// a is the new sim.Actor{} to be inserted into the sim
			a := <-actor.toBeAdded

			world.Insert(a.Entity())
			actors[a.Id()] = a

			// signal that the operation was a success
			actor.wasAdded <- a

			goto communicationLoop

		case actor := <-removeReq:
			// a is the new sim.Actor{} to be removed from the sim
			a := <-actor.toBeRemoved

			world.Remove(a.Entity())
			delete(actors, a.Id())

			// signal that the operation was a success
			actor.wasRemoved <- a

			goto communicationLoop

		case hasHalted = <-haltReq:
			goto exit
		}

		panic("unclosed case in simulation communication loop select case")

	tick:
		clock = clock.Tick()
		world.stepTo(clock.Now(), runTick)

		// TODO quadTree to state

		for _, a := range actors {
			a.WriteState(worldState)
		}

		goto communicationLoop

	exit:
		// TODO pre halt cleanup
		// Signal to Halt() caller that we've finished cleanup
		hasHalted <- haltedSimulation{world.quadTree}

		// NOTE Will loop forever and won't be garbage collected
		// Not a big deal because it will only be used once the
		// server is being torn down and the process is exiting.
		// Enables Halt() method to be called an infinite number
		// of times after the simulation has actually halted.
		go func() {
			for {
				hasHalted = <-haltReq
				hasHalted <- haltedSimulation{world.quadTree}
			}
		}()
	}()
}

type haltedSimulation struct {
	quadTree quad.Quad
}

// Return the quad tree used by the simulation
func (s haltedSimulation) Quad() quad.Quad { return s.quadTree }
<commit_msg>Convert the world to a state and ship it to all the actors<commit_after>package rpg2d

import (
	"errors"
	"time"

	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/rpg2d/quad"
	"github.com/ghthor/engine/sim/stime"
)

type Actor interface {
	Id() int64

	// Returns an entity that represents the
	// actor in the simulation's world.
	Entity() entity.Entity

	// Enables the simulation to send the
	// state of the world to the actor
	WriteState(WorldState)
}

// A SimulationDef used to configure a simulation
// to define the how the simulation will behave.
type SimulationDef struct {
	// The target FPS for the simulation to calculate at
	FPS int

	// Initial World State
	Now        stime.Time
	QuadTree   quad.Quad
	TerrainMap TerrainMap

	// User defined input application phase
	InputPhaseHandler quad.InputPhaseHandler

	// User defined the narrow phase
	NarrowPhaseHandler quad.NarrowPhaseHandler
}

type initialWorldState struct {
	now        stime.Time
	quadTree   quad.Quad
	terrainMap TerrainMap
}

type simSettings struct {
	fps int

	quad.InputPhaseHandler
	quad.NarrowPhaseHandler
}

type UnstartedSimulation interface {
	Begin() (RunningSimulation, error)
}

type RunningSimulation interface {
	ConnectActor(Actor)
	RemoveActor(Actor)
	Halt() (HaltedSimulation, error)
}

type HaltedSimulation interface {
	Quad() quad.Quad
}

// An implementation of RunningSimulation
type runningSimulation struct {
	//---- Communication
	// These channels are used by the public api
	// to add and remove actors. They are 1way
	// send only channels. The requests contains
	// a send only and recieve only channel so the
	// the public api call will be atomic action
	// and the caller can assume without a doubt
	// that the actor is now added or removed
	// from the simulation.
	addActor    chan<- addActorReq
	removeActor chan<- removeActorReq

	// This channel is used by the public api to request
	// that the simulation is halted. You send a 1way
	// send only channel into the game loop so the public
	// api can wait and be notified that the go routine
	// has returned and is no longer running.
	requestHalt chan<- chan<- HaltedSimulation
}

// Communication object used to atomicly add a new actor to the sim
type addActorReq struct {
	toBeAdded chan Actor
	wasAdded  chan Actor
}

// Add an actor into the running simulation
func (s runningSimulation) ConnectActor(a Actor) {
	ch := make(chan Actor)

	// Create an add request
	actor := addActorReq{ch, ch}

	// Send the add request to the game loop
	s.addActor <- actor

	// Send the actor to be added to the game loop
	actor.toBeAdded <- a

	// Wait for the add request to be successfully completed
	a = <-actor.wasAdded
}

// Communication object used to atomicly remove an actor from the sim
type removeActorReq struct {
	toBeRemoved chan Actor
	wasRemoved  chan Actor
}

// Remove an actor from the running simulation
func (s runningSimulation) RemoveActor(a Actor) {
	ch := make(chan Actor)

	// Create a remove request
	actor := removeActorReq{ch, ch}

	// Send the remove request to the game loop
	s.removeActor <- actor

	// Send the actor to be removed to the game loop
	actor.toBeRemoved <- a

	// Wait for the remove request to be successfully completed
	a = <-actor.wasRemoved
}

// Stop the simulation
func (s runningSimulation) Halt() (HaltedSimulation, error) {
	wasHalted := make(chan HaltedSimulation)

	// Send a request to the game loop to halt
	s.requestHalt <- wasHalted

	// Wait for the halt request to be successfully completed
	return <-wasHalted, nil
}

var ErrMustProvideAQuadtree = errors.New("user must provide a quad tree to a simulation defination")
var ErrMustProvideATerrainMap = errors.New("user must provide a terrain map to a simulation defination")

// Implement engine/sim.UnstartedSimulation
func (s SimulationDef) Begin() (RunningSimulation, error) {
	if s.QuadTree == nil {
		return nil, ErrMustProvideAQuadtree
	}

	if s.TerrainMap.Bounds != s.QuadTree.Bounds() {
		return nil, ErrMustProvideATerrainMap
	}

	initialState := initialWorldState{
		now:        s.Now,
		quadTree:   s.QuadTree,
		terrainMap: s.TerrainMap,
	}

	settings := simSettings{
		s.FPS,

		s.InputPhaseHandler,
		s.NarrowPhaseHandler,
	}

	rs := &runningSimulation{}

	// Starts 2 go routines and returns
	// The ticker and the engine communication kernel
	rs.startLoop(initialState, settings)

	return rs, nil
}

// Prepares a closure and executes it as a go routine
// Calling this function will create 2 infinte looping
// go routines. One for the clock ticker and one for
// calulating the next world state and adding/removing actors.
// This method has a pointer recv because it MUST set the
// addActor and removeActor communication channels used
// by the public api to request adding & removing actors
func (s *runningSimulation) startLoop(initialState initialWorldState, settings simSettings) {
	//---- Create all the communication channels

	// Make the 2way channels that will be used to make
	// add and remove actor requests to the go routine game loop
	addCh := make(chan addActorReq)
	removeCh := make(chan removeActorReq)

	// Set the 1way send channels used by the public api
	s.addActor = addCh
	s.removeActor = removeCh

	// Set the 1way recieve channels used by the game loop
	var addReq <-chan addActorReq
	var removeReq <-chan removeActorReq

	addReq = addCh
	removeReq = removeCh

	// Map of all the actors currently connected to the simulation
	actors := make(map[int64]Actor)

	// Make channel to be used to by the public api to
	// request that the simulation be halted
	haltCh := make(chan chan<- HaltedSimulation)

	// Set the 1way send channel used by the public api
	s.requestHalt = haltCh

	// Set the 1way recieve channel used by the game loop
	var haltReq <-chan chan<- HaltedSimulation
	haltReq = haltCh

	clock := stime.Clock(initialState.now)
	world := newWorld(initialState.now, initialState.quadTree, initialState.terrainMap)

	//---- User provided input application phase
	inputPhase := settings.InputPhaseHandler

	//---- User provided narrow phase
	narrowPhase := settings.NarrowPhaseHandler

	runTick := func(q quad.Quad, t stime.Time) quad.Quad {
		return quad.RunPhasesOn(q, inputPhase, narrowPhase, t)
	}

	// Start the Clock
	ticker := time.NewTicker(time.Duration(1000/settings.fps) * time.Millisecond)

	// Start the simulation server
	go func() {
		var hasHalted chan<- HaltedSimulation

		var worldState WorldState

	communicationLoop:
		// # This select prioritizes the following communication events
		// ## 2 potential events to respond to
		// 1. Trigger a simulation tick
		// 2. Halt() method has requested halting
		select {
		case <-ticker.C:
			goto tick

		case hasHalted = <-haltReq:
			goto exit
		default:
		}

		// ## 2 potential events to respond to
		// 1. Trigger a simulation tick
		// 2. ConnectActor() method has requested to connect an actor
		// 3. RemoveActor() method has requested to remove an actor
		// 4. Halt() method has requested halting
		select {
		case <-ticker.C:
			goto tick

		case actor := <-addReq:
			// a is the new sim.Actor{} to be inserted into the sim
			a := <-actor.toBeAdded

			world.Insert(a.Entity())
			actors[a.Id()] = a

			// signal that the operation was a success
			actor.wasAdded <- a

			goto communicationLoop

		case actor := <-removeReq:
			// a is the new sim.Actor{} to be removed from the sim
			a := <-actor.toBeRemoved

			world.Remove(a.Entity())
			delete(actors, a.Id())

			// signal that the operation was a success
			actor.wasRemoved <- a

			goto communicationLoop

		case hasHalted = <-haltReq:
			goto exit
		}

		panic("unclosed case in simulation communication loop select case")

	tick:
		clock = clock.Tick()
		world.stepTo(clock.Now(), runTick)

		worldState = world.ToState()
		worldState.Prepare()

		for _, a := range actors {
			a.WriteState(worldState)
		}

		goto communicationLoop

	exit:
		// TODO pre halt cleanup
		// Signal to Halt() caller that we've finished cleanup
		hasHalted <- haltedSimulation{world.quadTree}

		// NOTE Will loop forever and won't be garbage collected
		// Not a big deal because it will only be used once the
		// server is being torn down and the process is exiting.
		// Enables Halt() method to be called an infinite number
		// of times after the simulation has actually halted.
		go func() {
			for {
				hasHalted = <-haltReq
				hasHalted <- haltedSimulation{world.quadTree}
			}
		}()
	}()
}

type haltedSimulation struct {
	quadTree quad.Quad
}

// Return the quad tree used by the simulation
func (s haltedSimulation) Quad() quad.Quad { return s.quadTree }
<|endoftext|>


<commit_before>package render

import (
	"strconv"

	"bitbucket.org/oakmoundstudio/oak/physics"
)

const (
	Undraw = -1000
)

type Layered struct {
	layer int
}

func (ld *Layered) GetLayer() int {
	return ld.layer
}

func (ld *Layered) SetLayer(l int) {
	ld.layer = l
}

func (ld *Layered) UnDraw() {
	ld.layer = Undraw
}

type LayeredPoint struct {
	physics.Vector
	Layered
}

func NewLayeredPoint(x, y float64, l int) LayeredPoint {
	return LayeredPoint{
		Vector:  physics.NewVector(x, y),
		Layered: Layered{l},
	}
}

func (ldp *LayeredPoint) Copy() LayeredPoint {
	ldp2 := LayeredPoint{}
	ldp2.Vector = ldp.Vector.Copy()
	ldp2.Layered = ldp.Layered
	return ldp2
}

func (ldp *LayeredPoint) ShiftX(x float64) {
	ldp.Vector.ShiftX(x)
}
func (ldp *LayeredPoint) ShiftY(y float64) {
	ldp.Vector.ShiftY(y)
}

func (ldp *LayeredPoint) SetPos(x, y float64) {
	ldp.Vector.SetPos(x, y)
}

func (ldp *LayeredPoint) GetDims() (int, int) {
	// We use 6,6 here because our polygon containment library has a bug where it
	// will misreport artificially small dimensions. This function is expected to
	// only be used to determine whether something is onscreen to be drawn.
	// Todo: write own polygon containment library
	return 6, 6
}

func (ldp *LayeredPoint) String() string {
	x := strconv.FormatFloat(ldp.X(), 'f', 2, 32)
	y := strconv.FormatFloat(ldp.Y(), 'f', 2, 32)
	l := strconv.Itoa(ldp.layer)
	return "X(): " + x + ", Y(): " + y + ", L: " + l
}
<commit_msg>Add nil check for getLayer<commit_after>package render

import (
	"strconv"

	"bitbucket.org/oakmoundstudio/oak/physics"
)

const (
	Undraw = -1000
)

type Layered struct {
	layer int
}

func (ld *Layered) GetLayer() int {
	if ld == nil {
		return Undraw
	}
	return ld.layer
}

func (ld *Layered) SetLayer(l int) {
	ld.layer = l
}

func (ld *Layered) UnDraw() {
	ld.layer = Undraw
}

type LayeredPoint struct {
	physics.Vector
	Layered
}

func NewLayeredPoint(x, y float64, l int) LayeredPoint {
	return LayeredPoint{
		Vector:  physics.NewVector(x, y),
		Layered: Layered{l},
	}
}

func (ldp *LayeredPoint) Copy() LayeredPoint {
	ldp2 := LayeredPoint{}
	ldp2.Vector = ldp.Vector.Copy()
	ldp2.Layered = ldp.Layered
	return ldp2
}

func (ldp *LayeredPoint) ShiftX(x float64) {
	ldp.Vector.ShiftX(x)
}
func (ldp *LayeredPoint) ShiftY(y float64) {
	ldp.Vector.ShiftY(y)
}

func (ldp *LayeredPoint) SetPos(x, y float64) {
	ldp.Vector.SetPos(x, y)
}

func (ldp *LayeredPoint) GetDims() (int, int) {
	// We use 6,6 here because our polygon containment library has a bug where it
	// will misreport artificially small dimensions. This function is expected to
	// only be used to determine whether something is onscreen to be drawn.
	// Todo: write own polygon containment library
	return 6, 6
}

func (ldp *LayeredPoint) String() string {
	x := strconv.FormatFloat(ldp.X(), 'f', 2, 32)
	y := strconv.FormatFloat(ldp.Y(), 'f', 2, 32)
	l := strconv.Itoa(ldp.layer)
	return "X(): " + x + ", Y(): " + y + ", L: " + l
}
<|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 render

import (
	"encoding/json"
	"fmt"
	"image"
	"image/draw"
	"image/png"
	"sort"
	"strings"
	"sync"

	"github.com/thinkofdeath/steven/render/atlas"
	"github.com/thinkofdeath/steven/render/gl"
	"github.com/thinkofdeath/steven/resource"
)

var (
	textures         []*atlas.Type
	textureCount     int
	textureMap       = map[string]*textureInfo{}
	textureLock      sync.RWMutex
	animatedTextures []*animatedTexture
)

const (
	AtlasSize    = 1024
	atlasSizeStr = "1024"
)

// TextureInfo returns information about a texture in an atlas
type TextureInfo interface {
	Atlas() int
	Rect() atlas.Rect
	Sub(x, y, w, h int) TextureInfo
}

type textureInfo struct {
	atlas int
	rect  atlas.Rect
}

func (ti *textureInfo) Atlas() int       { return ti.atlas }
func (ti *textureInfo) Rect() atlas.Rect { return ti.rect }

// Sub returns a subsection of this texture.
func (ti *textureInfo) Sub(x, y, w, h int) TextureInfo {
	return &subTextureInfo{
		ox:     float64(x) / float64(ti.rect.Width),
		oy:     float64(y) / float64(ti.rect.Height),
		w:      float64(w) / float64(ti.rect.Width),
		h:      float64(h) / float64(ti.rect.Height),
		parent: ti,
	}
}

type subTextureInfo struct {
	ox, oy, w, h float64
	parent       TextureInfo
}

func (s *subTextureInfo) Atlas() int { return s.parent.Atlas() }
func (s *subTextureInfo) Rect() atlas.Rect {
	rect := s.parent.Rect()
	rect.X += int(s.ox * float64(rect.Width))
	rect.Y += int(s.oy * float64(rect.Height))
	rect.Width = int(s.w * float64(rect.Width))
	rect.Height = int(s.h * float64(rect.Height))
	return rect
}

// Sub returns a subsection of this texture.
func (s *subTextureInfo) Sub(x, y, w, h int) TextureInfo {
	rect := s.Rect()
	return &subTextureInfo{
		ox:     float64(x) / float64(rect.Width),
		oy:     float64(y) / float64(rect.Height),
		w:      float64(w) / float64(rect.Width),
		h:      float64(h) / float64(rect.Height),
		parent: s,
	}
}

func RelativeTexture(ti TextureInfo, w, h int) TextureInfo {
	return &relativeTexture{
		w: 1, h: 1,
		fakeW: float64(w), fakeH: float64(h),
		parent: ti,
	}
}

type relativeTexture struct {
	ox, oy, w, h float64
	fakeW, fakeH float64
	parent       TextureInfo
}

func (r *relativeTexture) Atlas() int { return r.parent.Atlas() }
func (r *relativeTexture) Rect() atlas.Rect {
	rect := r.parent.Rect()
	rect.X += int(r.ox * float64(rect.Width))
	rect.Y += int(r.oy * float64(rect.Height))
	rect.Width = int(r.w * float64(rect.Width))
	rect.Height = int(r.h * float64(rect.Height))
	return rect
}

func (r *relativeTexture) Sub(x, y, w, h int) TextureInfo {
	return &relativeTexture{
		ox:     float64(x) / float64(r.fakeW),
		oy:     float64(y) / float64(r.fakeH),
		w:      float64(w) / float64(r.fakeW),
		h:      float64(h) / float64(r.fakeH),
		fakeW:  float64(w),
		fakeH:  float64(h),
		parent: r,
	}
}

// GetTexture returns the related TextureInfo for the requested texture.
// If the texture isn't found a placeholder is returned instead.
func GetTexture(name string) TextureInfo {
	textureLock.RLock()
	defer textureLock.RUnlock()
	t, ok := textureMap[name]
	if !ok {
		t = textureMap["missing_texture"]
	}
	return t
}

type sortableTexture struct {
	Area  int
	File  string
	Image image.Image
}

type sortableTextures []sortableTexture

func (s sortableTextures) Swap(a, b int) {
	s[a], s[b] = s[b], s[a]
}

func (s sortableTextures) Len() int {
	return len(s)
}

func (s sortableTextures) Less(a, b int) bool {
	return s[a].Area > s[b].Area
}

// LoadTextures (re)loads all the block textures from the resource pack(s)
// TODO(Think) better error handling (if possible to recover?)
func LoadTextures() {
	textureLock.Lock()

	if texturesCreated {
		glTexture.Bind(gl.Texture2DArray)
		data := make([]byte, AtlasSize*AtlasSize*textureCount*4)
		glTexture.Image3D(0, AtlasSize, AtlasSize, textureCount, gl.RGBA, gl.UnsignedByte, data)
	}
	freeTextures = nil
	animatedTextures = nil
	textures = nil
	pix := []byte{
		0, 0, 0, 255,
		255, 0, 255, 255,
		255, 0, 255, 255,
		0, 0, 0, 255,
	}
	info := addTexture(pix, 2, 2)
	if t, ok := textureMap["missing_texture"]; ok {
		t.atlas = info.atlas
		t.rect = info.rect
	} else {
		textureMap["missing_texture"] = info
	}

	for _, t := range textureMap {
		t.atlas = info.atlas
		t.rect = info.rect
	}

	names := resource.Search("minecraft", "textures/", ".png")
	tList := make(sortableTextures, 0, len(names))
	for _, file := range names {
		if strings.HasPrefix(file, "textures/font") {
			continue
		}
		r, err := resource.Open("minecraft", file)
		if err != nil {
			panic(err)
		}
		defer r.Close()
		img, err := png.Decode(r)
		if err != nil {
			panic(err)
		}
		width, height := img.Bounds().Dx(), img.Bounds().Dy()
		tList = append(tList, sortableTexture{
			Area:  width * height,
			File:  file,
			Image: img,
		})
	}
	sort.Sort(tList)
	for _, st := range tList {
		loadTexFile(st)
	}

	pix = []byte{255, 255, 255, 255}
	info = addTexture(pix, 1, 1)
	if t, ok := textureMap["solid"]; ok {
		t.atlas = info.atlas
		t.rect = info.rect
	} else {
		textureMap["solid"] = info
	}

	for _, skin := range skins {
		info := getFreeTexture(skin.info.W, skin.info.H)
		uploadTexture(info.info, skin.data)
		i := skin.info.info
		skin.info = info
		i.rect = info.info.rect
		i.atlas = info.info.atlas
		skin.info.info = i
	}

	textureLock.Unlock()

	loadFontInfo()
	loadFontPage(0)

	for i := range isFontLoaded {
		if i == 0 || !isFontLoaded[i] {
			continue
		}
		isFontLoaded[i] = false
		loadFontPage(i)
	}
}

func loadTexFile(st sortableTexture) {
	file := st.File
	ii := st.Image
	img := ii.(draw.Image)
	width, height := img.Bounds().Dx(), img.Bounds().Dy()
	var ani *animatedTexture
	if (strings.HasPrefix(file, "textures/blocks") || strings.HasPrefix(file, "textures/items")) &&
		width != height {
		height = width
		old := img
		img = image.NewNRGBA(image.Rect(0, 0, width, width))
		draw.Draw(img, img.Bounds(), old, image.ZP, draw.Over)
		ani = loadAnimation(file, old.Bounds().Dy()/old.Bounds().Dx())
		if ani != nil {
			ani.Image = old
			ani.Buffer = imgToBytes(old)
			animatedTextures = append(animatedTextures, ani)
		} else {
			img = old
			width, height = img.Bounds().Dx(), img.Bounds().Dy()
		}
	}
	pix := imgToBytes(img)
	name := file[len("textures/") : len(file)-4]
	info := addTexture(pix, width, height)
	if t, ok := textureMap[name]; ok {
		t.atlas = info.atlas
		t.rect = info.rect
	} else {
		textureMap[name] = info
	}
	if ani != nil {
		ani.Info = info
	}
}

func imgToBytes(img image.Image) []byte {
	switch img := img.(type) {
	case *image.NRGBA:
		return img.Pix
	case *image.RGBA:
		return img.Pix
	default:
		temp := image.NewNRGBA(img.Bounds())
		draw.Draw(temp, img.Bounds(), img, image.ZP, draw.Over)
		return temp.Pix
	}
}

func addTexture(pix []byte, width, height int) *textureInfo {
	for i, a := range textures {
		if a == nil {
			continue
		}
		rect, err := a.Add(pix, width, height)
		if err == nil {
			info := &textureInfo{atlas: i, rect: *rect}
			if texturesCreated {
				uploadTexture(info, pix)
			}
			return info
		}
	}
	var a *atlas.Type
	if texturesCreated {
		a = atlas.NewLight(AtlasSize, AtlasSize, 0)
	} else {
		a = atlas.New(AtlasSize, AtlasSize, 4)
	}
	textures = append(textures, a)
	reupload := false
	if len(textures) > textureCount {
		textureCount = len(textures)
		reupload = true
	}
	rect, err := a.Add(pix, width, height)
	if err != nil {
		panic(fmt.Sprintf("Failed to place %d,%d: %s", width, height, err))
	}

	info := &textureInfo{atlas: len(textures) - 1, rect: *rect}
	if texturesCreated {
		if reupload {
			glTexture.Bind(gl.Texture2DArray)
			data := make([]byte, AtlasSize*AtlasSize*textureCount*4)
			glTexture.Get(0, gl.RGBA, gl.UnsignedByte, data)
			glTexture.Image3D(0, AtlasSize, AtlasSize, textureCount, gl.RGBA, gl.UnsignedByte, data)
			textureDepth = textureCount
		}
		uploadTexture(info, pix)
	}

	return info
}

func uploadTexture(info *textureInfo, data []byte) {
	glTexture.Bind(gl.Texture2DArray)
	r := info.rect
	glTexture.SubImage3D(0, r.X, r.Y, info.atlas, r.Width, r.Height, 1, gl.RGBA, gl.UnsignedByte, data)
}

type animatedTexture struct {
	Info          *textureInfo
	Image         image.Image
	Buffer        []byte
	Interpolate   bool
	Frames        []textureFrame
	RemainingTime float64
	CurrentFrame  int
}

type textureFrame struct {
	Index int
	Time  int
}

func tickAnimatedTextures(delta float64) {
	delta /= 3 // default is 60 a second, minecraft is 20
	for _, ani := range animatedTextures {
		ani.RemainingTime -= delta
		if ani.RemainingTime < 0 {
			ani.CurrentFrame++
			ani.CurrentFrame %= len(ani.Frames)
			ani.RemainingTime += float64(ani.Frames[ani.CurrentFrame].Time)
			r := ani.Info.rect
			glTexture.Bind(gl.Texture2DArray)
			offset := r.Width * r.Width * ani.Frames[ani.CurrentFrame].Index * 4
			offset2 := offset + r.Height*r.Width*4
			glTexture.SubImage3D(0, r.X, r.Y, ani.Info.atlas, r.Width, r.Height, 1, gl.RGBA, gl.UnsignedByte, ani.Buffer[offset:offset2])
		}
	}
}

func loadAnimation(file string, max int) *animatedTexture {
	a := &animatedTexture{}
	defer func() {
		if a != nil {
			a.RemainingTime = float64(a.Frames[0].Time)
		}
	}()

	type animation struct {
		FrameTime   int
		Interpolate bool
		Frames      []json.RawMessage
	}
	type base struct {
		Animation animation
	}

	meta, err := resource.Open("minecraft", file+".mcmeta")
	if err != nil {
		fmt.Printf("%s: %s\n", file+".mcmeta", err)
		a = nil
		return nil
	}
	defer meta.Close()
	b := &base{}
	err = json.NewDecoder(meta).Decode(b)
	if err != nil {
		panic(err)
	}
	frameTime := b.Animation.FrameTime
	if frameTime == 0 {
		frameTime = 1
	}
	a.Interpolate = b.Animation.Interpolate

	if len(b.Animation.Frames) == 0 {
		a.Frames = make([]textureFrame, max)
		for i := range a.Frames {
			a.Frames[i] = textureFrame{
				Index: i,
				Time:  frameTime,
			}
		}
		return a
	}

	a.Frames = make([]textureFrame, len(b.Animation.Frames))
	for i := range a.Frames {
		a.Frames[i].Time = frameTime
		if b.Animation.Frames[i][0] == '{' {
			if err = json.Unmarshal(b.Animation.Frames[i], &a.Frames[i]); err != nil {
				panic(err)
			}
			a.Frames[i].Time *= frameTime
			continue
		}
		if err = json.Unmarshal(b.Animation.Frames[i], &a.Frames[i].Index); err != nil {
			panic(err)
		}
	}

	return a
}
<commit_msg>render: better error reporting<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 render

import (
	"encoding/json"
	"fmt"
	"image"
	"image/draw"
	"image/png"
	"sort"
	"strings"
	"sync"

	"github.com/thinkofdeath/steven/render/atlas"
	"github.com/thinkofdeath/steven/render/gl"
	"github.com/thinkofdeath/steven/resource"
)

var (
	textures         []*atlas.Type
	textureCount     int
	textureMap       = map[string]*textureInfo{}
	textureLock      sync.RWMutex
	animatedTextures []*animatedTexture
)

const (
	AtlasSize    = 1024
	atlasSizeStr = "1024"
)

// TextureInfo returns information about a texture in an atlas
type TextureInfo interface {
	Atlas() int
	Rect() atlas.Rect
	Sub(x, y, w, h int) TextureInfo
}

type textureInfo struct {
	atlas int
	rect  atlas.Rect
}

func (ti *textureInfo) Atlas() int       { return ti.atlas }
func (ti *textureInfo) Rect() atlas.Rect { return ti.rect }

// Sub returns a subsection of this texture.
func (ti *textureInfo) Sub(x, y, w, h int) TextureInfo {
	return &subTextureInfo{
		ox:     float64(x) / float64(ti.rect.Width),
		oy:     float64(y) / float64(ti.rect.Height),
		w:      float64(w) / float64(ti.rect.Width),
		h:      float64(h) / float64(ti.rect.Height),
		parent: ti,
	}
}

type subTextureInfo struct {
	ox, oy, w, h float64
	parent       TextureInfo
}

func (s *subTextureInfo) Atlas() int { return s.parent.Atlas() }
func (s *subTextureInfo) Rect() atlas.Rect {
	rect := s.parent.Rect()
	rect.X += int(s.ox * float64(rect.Width))
	rect.Y += int(s.oy * float64(rect.Height))
	rect.Width = int(s.w * float64(rect.Width))
	rect.Height = int(s.h * float64(rect.Height))
	return rect
}

// Sub returns a subsection of this texture.
func (s *subTextureInfo) Sub(x, y, w, h int) TextureInfo {
	rect := s.Rect()
	return &subTextureInfo{
		ox:     float64(x) / float64(rect.Width),
		oy:     float64(y) / float64(rect.Height),
		w:      float64(w) / float64(rect.Width),
		h:      float64(h) / float64(rect.Height),
		parent: s,
	}
}

func RelativeTexture(ti TextureInfo, w, h int) TextureInfo {
	return &relativeTexture{
		w: 1, h: 1,
		fakeW: float64(w), fakeH: float64(h),
		parent: ti,
	}
}

type relativeTexture struct {
	ox, oy, w, h float64
	fakeW, fakeH float64
	parent       TextureInfo
}

func (r *relativeTexture) Atlas() int { return r.parent.Atlas() }
func (r *relativeTexture) Rect() atlas.Rect {
	rect := r.parent.Rect()
	rect.X += int(r.ox * float64(rect.Width))
	rect.Y += int(r.oy * float64(rect.Height))
	rect.Width = int(r.w * float64(rect.Width))
	rect.Height = int(r.h * float64(rect.Height))
	return rect
}

func (r *relativeTexture) Sub(x, y, w, h int) TextureInfo {
	return &relativeTexture{
		ox:     float64(x) / float64(r.fakeW),
		oy:     float64(y) / float64(r.fakeH),
		w:      float64(w) / float64(r.fakeW),
		h:      float64(h) / float64(r.fakeH),
		fakeW:  float64(w),
		fakeH:  float64(h),
		parent: r,
	}
}

// GetTexture returns the related TextureInfo for the requested texture.
// If the texture isn't found a placeholder is returned instead.
func GetTexture(name string) TextureInfo {
	textureLock.RLock()
	defer textureLock.RUnlock()
	t, ok := textureMap[name]
	if !ok {
		t = textureMap["missing_texture"]
	}
	return t
}

type sortableTexture struct {
	Area  int
	File  string
	Image image.Image
}

type sortableTextures []sortableTexture

func (s sortableTextures) Swap(a, b int) {
	s[a], s[b] = s[b], s[a]
}

func (s sortableTextures) Len() int {
	return len(s)
}

func (s sortableTextures) Less(a, b int) bool {
	return s[a].Area > s[b].Area
}

// LoadTextures (re)loads all the block textures from the resource pack(s)
// TODO(Think) better error handling (if possible to recover?)
func LoadTextures() {
	textureLock.Lock()

	if texturesCreated {
		glTexture.Bind(gl.Texture2DArray)
		data := make([]byte, AtlasSize*AtlasSize*textureCount*4)
		glTexture.Image3D(0, AtlasSize, AtlasSize, textureCount, gl.RGBA, gl.UnsignedByte, data)
	}
	freeTextures = nil
	animatedTextures = nil
	textures = nil
	pix := []byte{
		0, 0, 0, 255,
		255, 0, 255, 255,
		255, 0, 255, 255,
		0, 0, 0, 255,
	}
	info := addTexture(pix, 2, 2)
	if t, ok := textureMap["missing_texture"]; ok {
		t.atlas = info.atlas
		t.rect = info.rect
	} else {
		textureMap["missing_texture"] = info
	}

	for _, t := range textureMap {
		t.atlas = info.atlas
		t.rect = info.rect
	}

	names := resource.Search("minecraft", "textures/", ".png")
	tList := make(sortableTextures, 0, len(names))
	for _, file := range names {
		if strings.HasPrefix(file, "textures/font") {
			continue
		}
		r, err := resource.Open("minecraft", file)
		if err != nil {
			panic(err)
		}
		defer r.Close()
		img, err := png.Decode(r)
		if err != nil {
			panic(fmt.Sprintf("(%s): %s", file, err))
		}
		width, height := img.Bounds().Dx(), img.Bounds().Dy()
		tList = append(tList, sortableTexture{
			Area:  width * height,
			File:  file,
			Image: img,
		})
	}
	sort.Sort(tList)
	for _, st := range tList {
		loadTexFile(st)
	}

	pix = []byte{255, 255, 255, 255}
	info = addTexture(pix, 1, 1)
	if t, ok := textureMap["solid"]; ok {
		t.atlas = info.atlas
		t.rect = info.rect
	} else {
		textureMap["solid"] = info
	}

	for _, skin := range skins {
		info := getFreeTexture(skin.info.W, skin.info.H)
		uploadTexture(info.info, skin.data)
		i := skin.info.info
		skin.info = info
		i.rect = info.info.rect
		i.atlas = info.info.atlas
		skin.info.info = i
	}

	textureLock.Unlock()

	loadFontInfo()
	loadFontPage(0)

	for i := range isFontLoaded {
		if i == 0 || !isFontLoaded[i] {
			continue
		}
		isFontLoaded[i] = false
		loadFontPage(i)
	}
}

func loadTexFile(st sortableTexture) {
	file := st.File
	ii := st.Image
	img := ii.(draw.Image)
	width, height := img.Bounds().Dx(), img.Bounds().Dy()
	var ani *animatedTexture
	if (strings.HasPrefix(file, "textures/blocks") || strings.HasPrefix(file, "textures/items")) &&
		width != height {
		height = width
		old := img
		img = image.NewNRGBA(image.Rect(0, 0, width, width))
		draw.Draw(img, img.Bounds(), old, image.ZP, draw.Over)
		ani = loadAnimation(file, old.Bounds().Dy()/old.Bounds().Dx())
		if ani != nil {
			ani.Image = old
			ani.Buffer = imgToBytes(old)
			animatedTextures = append(animatedTextures, ani)
		} else {
			img = old
			width, height = img.Bounds().Dx(), img.Bounds().Dy()
		}
	}
	pix := imgToBytes(img)
	name := file[len("textures/") : len(file)-4]
	info := addTexture(pix, width, height)
	if t, ok := textureMap[name]; ok {
		t.atlas = info.atlas
		t.rect = info.rect
	} else {
		textureMap[name] = info
	}
	if ani != nil {
		ani.Info = info
	}
}

func imgToBytes(img image.Image) []byte {
	switch img := img.(type) {
	case *image.NRGBA:
		return img.Pix
	case *image.RGBA:
		return img.Pix
	default:
		temp := image.NewNRGBA(img.Bounds())
		draw.Draw(temp, img.Bounds(), img, image.ZP, draw.Over)
		return temp.Pix
	}
}

func addTexture(pix []byte, width, height int) *textureInfo {
	for i, a := range textures {
		if a == nil {
			continue
		}
		rect, err := a.Add(pix, width, height)
		if err == nil {
			info := &textureInfo{atlas: i, rect: *rect}
			if texturesCreated {
				uploadTexture(info, pix)
			}
			return info
		}
	}
	var a *atlas.Type
	if texturesCreated {
		a = atlas.NewLight(AtlasSize, AtlasSize, 0)
	} else {
		a = atlas.New(AtlasSize, AtlasSize, 4)
	}
	textures = append(textures, a)
	reupload := false
	if len(textures) > textureCount {
		textureCount = len(textures)
		reupload = true
	}
	rect, err := a.Add(pix, width, height)
	if err != nil {
		panic(fmt.Sprintf("Failed to place %d,%d: %s", width, height, err))
	}

	info := &textureInfo{atlas: len(textures) - 1, rect: *rect}
	if texturesCreated {
		if reupload {
			glTexture.Bind(gl.Texture2DArray)
			data := make([]byte, AtlasSize*AtlasSize*textureCount*4)
			glTexture.Get(0, gl.RGBA, gl.UnsignedByte, data)
			glTexture.Image3D(0, AtlasSize, AtlasSize, textureCount, gl.RGBA, gl.UnsignedByte, data)
			textureDepth = textureCount
		}
		uploadTexture(info, pix)
	}

	return info
}

func uploadTexture(info *textureInfo, data []byte) {
	glTexture.Bind(gl.Texture2DArray)
	r := info.rect
	glTexture.SubImage3D(0, r.X, r.Y, info.atlas, r.Width, r.Height, 1, gl.RGBA, gl.UnsignedByte, data)
}

type animatedTexture struct {
	Info          *textureInfo
	Image         image.Image
	Buffer        []byte
	Interpolate   bool
	Frames        []textureFrame
	RemainingTime float64
	CurrentFrame  int
}

type textureFrame struct {
	Index int
	Time  int
}

func tickAnimatedTextures(delta float64) {
	delta /= 3 // default is 60 a second, minecraft is 20
	for _, ani := range animatedTextures {
		ani.RemainingTime -= delta
		if ani.RemainingTime < 0 {
			ani.CurrentFrame++
			ani.CurrentFrame %= len(ani.Frames)
			ani.RemainingTime += float64(ani.Frames[ani.CurrentFrame].Time)
			r := ani.Info.rect
			glTexture.Bind(gl.Texture2DArray)
			offset := r.Width * r.Width * ani.Frames[ani.CurrentFrame].Index * 4
			offset2 := offset + r.Height*r.Width*4
			glTexture.SubImage3D(0, r.X, r.Y, ani.Info.atlas, r.Width, r.Height, 1, gl.RGBA, gl.UnsignedByte, ani.Buffer[offset:offset2])
		}
	}
}

func loadAnimation(file string, max int) *animatedTexture {
	a := &animatedTexture{}
	defer func() {
		if a != nil {
			a.RemainingTime = float64(a.Frames[0].Time)
		}
	}()

	type animation struct {
		FrameTime   int
		Interpolate bool
		Frames      []json.RawMessage
	}
	type base struct {
		Animation animation
	}

	meta, err := resource.Open("minecraft", file+".mcmeta")
	if err != nil {
		fmt.Printf("%s: %s\n", file+".mcmeta", err)
		a = nil
		return nil
	}
	defer meta.Close()
	b := &base{}
	err = json.NewDecoder(meta).Decode(b)
	if err != nil {
		panic(err)
	}
	frameTime := b.Animation.FrameTime
	if frameTime == 0 {
		frameTime = 1
	}
	a.Interpolate = b.Animation.Interpolate

	if len(b.Animation.Frames) == 0 {
		a.Frames = make([]textureFrame, max)
		for i := range a.Frames {
			a.Frames[i] = textureFrame{
				Index: i,
				Time:  frameTime,
			}
		}
		return a
	}

	a.Frames = make([]textureFrame, len(b.Animation.Frames))
	for i := range a.Frames {
		a.Frames[i].Time = frameTime
		if b.Animation.Frames[i][0] == '{' {
			if err = json.Unmarshal(b.Animation.Frames[i], &a.Frames[i]); err != nil {
				panic(err)
			}
			a.Frames[i].Time *= frameTime
			continue
		}
		if err = json.Unmarshal(b.Animation.Frames[i], &a.Frames[i].Index); err != nil {
			panic(err)
		}
	}

	return a
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/binary"
	"encoding/hex"
	"flag"
	"log"
	"net"
	"os"
	"sync"
	"time"

	"github.com/calmh/syncthing/discover"
	"github.com/golang/groupcache/lru"
	"github.com/juju/ratelimit"
)

type Node struct {
	Addresses []Address
	Updated   time.Time
}

type Address struct {
	IP   []byte
	Port uint16
}

var (
	nodes    = make(map[string]Node)
	lock     sync.Mutex
	queries  = 0
	answered = 0
	limited  = 0
	debug    = false
	limiter  = lru.New(1024)
)

func main() {
	var listen string
	var timestamp bool

	flag.StringVar(&listen, "listen", ":22025", "Listen address")
	flag.BoolVar(&debug, "debug", false, "Enable debug output")
	flag.BoolVar(&timestamp, "timestamp", true, "Timestamp the log output")
	flag.Parse()

	log.SetOutput(os.Stdout)
	if !timestamp {
		log.SetFlags(0)
	}

	addr, _ := net.ResolveUDPAddr("udp", listen)
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		log.Fatal(err)
	}

	go logStats()

	var buf = make([]byte, 1024)
	for {
		buf = buf[:cap(buf)]
		n, addr, err := conn.ReadFromUDP(buf)

		if limit(addr) {
			// Rate limit in effect for source
			continue
		}

		if err != nil {
			log.Fatal(err)
		}

		if n < 4 {
			log.Printf("Received short packet (%d bytes)", n)
			continue
		}

		buf = buf[:n]
		magic := binary.BigEndian.Uint32(buf)

		switch magic {
		case discover.AnnouncementMagicV2:
			handleAnnounceV2(addr, buf)

		case discover.QueryMagicV2:
			handleQueryV2(conn, addr, buf)
		}
	}
}

func limit(addr *net.UDPAddr) bool {
	key := addr.IP.String()

	lock.Lock()
	defer lock.Unlock()

	bkt, ok := limiter.Get(key)
	if ok {
		bkt := bkt.(*ratelimit.Bucket)
		if bkt.TakeAvailable(1) != 1 {
			// Rate limit exceeded; ignore packet
			if debug {
				log.Println("Rate limit exceeded for", key)
			}
			limited++
			return true
		}
	} else {
		if debug {
			log.Println("New limiter for", key)
		}
		// One packet per ten seconds average rate, burst ten packets
		limiter.Add(key, ratelimit.NewBucket(10*time.Second, 10))
	}

	return false
}

func handleAnnounceV2(addr *net.UDPAddr, buf []byte) {
	var pkt discover.AnnounceV2
	err := pkt.UnmarshalXDR(buf)
	if err != nil {
		log.Println("AnnounceV2 Unmarshal:", err)
		log.Println(hex.Dump(buf))
		return
	}
	if debug {
		log.Printf("<- %v %#v", addr, pkt)
	}

	ip := addr.IP.To4()
	if ip == nil {
		ip = addr.IP.To16()
	}

	var addrs []Address
	for _, addr := range pkt.Addresses {
		tip := addr.IP
		if len(tip) == 0 {
			tip = ip
		}
		addrs = append(addrs, Address{
			IP:   tip,
			Port: addr.Port,
		})
	}

	node := Node{
		Addresses: addrs,
		Updated:   time.Now(),
	}

	lock.Lock()
	nodes[pkt.NodeID] = node
	lock.Unlock()
}

func handleQueryV2(conn *net.UDPConn, addr *net.UDPAddr, buf []byte) {
	var pkt discover.QueryV2
	err := pkt.UnmarshalXDR(buf)
	if err != nil {
		log.Println("QueryV2 Unmarshal:", err)
		log.Println(hex.Dump(buf))
		return
	}
	if debug {
		log.Printf("<- %v %#v", addr, pkt)
	}

	lock.Lock()
	node, ok := nodes[pkt.NodeID]
	queries++
	lock.Unlock()

	if ok && len(node.Addresses) > 0 {
		pkt := discover.AnnounceV2{
			Magic:  discover.AnnouncementMagicV2,
			NodeID: pkt.NodeID,
		}
		for _, addr := range node.Addresses {
			pkt.Addresses = append(pkt.Addresses, discover.Address{IP: addr.IP, Port: addr.Port})
		}
		if debug {
			log.Printf("-> %v %#v", addr, pkt)
		}

		tb := pkt.MarshalXDR()
		_, _, err = conn.WriteMsgUDP(tb, nil, addr)
		if err != nil {
			log.Println("QueryV2 response write:", err)
		}

		lock.Lock()
		answered++
		lock.Unlock()
	}
}

func logStats() {
	for {
		time.Sleep(600 * time.Second)

		lock.Lock()

		var deleted = 0
		for id, node := range nodes {
			if time.Since(node.Updated) > 60*time.Minute {
				delete(nodes, id)
				deleted++
			}
		}

		log.Printf("Expired %d nodes; %d nodes in registry; %d queries (%d answered)", deleted, len(nodes), queries, answered)
		log.Printf("Limited %d queries; %d entries in limiter cache", limited, limiter.Len())
		queries = 0
		answered = 0
		limited = 0

		lock.Unlock()
	}
}
<commit_msg>discosrv: Better statistics<commit_after>package main

import (
	"encoding/binary"
	"encoding/hex"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"sync"
	"time"

	"github.com/calmh/syncthing/discover"
	"github.com/golang/groupcache/lru"
	"github.com/juju/ratelimit"
)

type Node struct {
	Addresses []Address
	Updated   time.Time
}

type Address struct {
	IP   []byte
	Port uint16
}

var (
	nodes     = make(map[string]Node)
	lock      sync.Mutex
	queries   = 0
	announces = 0
	answered  = 0
	limited   = 0
	unknowns  = 0
	debug     = false
	limiter   = lru.New(1024)
)

func main() {
	var listen string
	var timestamp bool
	var statsIntv int
	var statsFile string

	flag.StringVar(&listen, "listen", ":22025", "Listen address")
	flag.BoolVar(&debug, "debug", false, "Enable debug output")
	flag.BoolVar(&timestamp, "timestamp", true, "Timestamp the log output")
	flag.IntVar(&statsIntv, "stats-intv", 0, "Statistics output interval (s)")
	flag.StringVar(&statsFile, "stats-file", "/var/log/discosrv.stats", "Statistics file name")
	flag.Parse()

	log.SetOutput(os.Stdout)
	if !timestamp {
		log.SetFlags(0)
	}

	addr, _ := net.ResolveUDPAddr("udp", listen)
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		log.Fatal(err)
	}

	if statsIntv > 0 {
		go logStats(statsFile, statsIntv)
	}

	var buf = make([]byte, 1024)
	for {
		buf = buf[:cap(buf)]
		n, addr, err := conn.ReadFromUDP(buf)

		if limit(addr) {
			// Rate limit in effect for source
			continue
		}

		if err != nil {
			log.Fatal(err)
		}

		if n < 4 {
			log.Printf("Received short packet (%d bytes)", n)
			continue
		}

		buf = buf[:n]
		magic := binary.BigEndian.Uint32(buf)

		switch magic {
		case discover.AnnouncementMagicV2:
			handleAnnounceV2(addr, buf)

		case discover.QueryMagicV2:
			handleQueryV2(conn, addr, buf)

		default:
			lock.Lock()
			unknowns++
			lock.Unlock()
		}
	}
}

func limit(addr *net.UDPAddr) bool {
	key := addr.IP.String()

	lock.Lock()
	defer lock.Unlock()

	bkt, ok := limiter.Get(key)
	if ok {
		bkt := bkt.(*ratelimit.Bucket)
		if bkt.TakeAvailable(1) != 1 {
			// Rate limit exceeded; ignore packet
			if debug {
				log.Println("Rate limit exceeded for", key)
			}
			limited++
			return true
		}
	} else {
		if debug {
			log.Println("New limiter for", key)
		}
		// One packet per ten seconds average rate, burst ten packets
		limiter.Add(key, ratelimit.NewBucket(10*time.Second, 10))
	}

	return false
}

func handleAnnounceV2(addr *net.UDPAddr, buf []byte) {
	var pkt discover.AnnounceV2
	err := pkt.UnmarshalXDR(buf)
	if err != nil {
		log.Println("AnnounceV2 Unmarshal:", err)
		log.Println(hex.Dump(buf))
		return
	}
	if debug {
		log.Printf("<- %v %#v", addr, pkt)
	}

	lock.Lock()
	announces++
	lock.Unlock()

	ip := addr.IP.To4()
	if ip == nil {
		ip = addr.IP.To16()
	}

	var addrs []Address
	for _, addr := range pkt.Addresses {
		tip := addr.IP
		if len(tip) == 0 {
			tip = ip
		}
		addrs = append(addrs, Address{
			IP:   tip,
			Port: addr.Port,
		})
	}

	node := Node{
		Addresses: addrs,
		Updated:   time.Now(),
	}

	lock.Lock()
	nodes[pkt.NodeID] = node
	lock.Unlock()
}

func handleQueryV2(conn *net.UDPConn, addr *net.UDPAddr, buf []byte) {
	var pkt discover.QueryV2
	err := pkt.UnmarshalXDR(buf)
	if err != nil {
		log.Println("QueryV2 Unmarshal:", err)
		log.Println(hex.Dump(buf))
		return
	}
	if debug {
		log.Printf("<- %v %#v", addr, pkt)
	}

	lock.Lock()
	node, ok := nodes[pkt.NodeID]
	queries++
	lock.Unlock()

	if ok && len(node.Addresses) > 0 {
		pkt := discover.AnnounceV2{
			Magic:  discover.AnnouncementMagicV2,
			NodeID: pkt.NodeID,
		}
		for _, addr := range node.Addresses {
			pkt.Addresses = append(pkt.Addresses, discover.Address{IP: addr.IP, Port: addr.Port})
		}
		if debug {
			log.Printf("-> %v %#v", addr, pkt)
		}

		tb := pkt.MarshalXDR()
		_, _, err = conn.WriteMsgUDP(tb, nil, addr)
		if err != nil {
			log.Println("QueryV2 response write:", err)
		}

		lock.Lock()
		answered++
		lock.Unlock()
	}
}

func next(intv int) time.Time {
	d := time.Duration(intv) * time.Second
	t0 := time.Now()
	t1 := t0.Add(d).Truncate(d)
	time.Sleep(t1.Sub(t0))
	return t1
}

func logStats(file string, intv int) {
	f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}
	for {
		t := next(intv)

		lock.Lock()

		var deleted = 0
		for id, node := range nodes {
			if time.Since(node.Updated) > 60*time.Minute {
				delete(nodes, id)
				deleted++
			}
		}

		fmt.Fprintf(f, "%d Nr:%d Ne:%d Qt:%d Qa:%d A:%d U:%d Lq:%d Lc:%d\n",
			t.Unix(), len(nodes), deleted, queries, answered, announces, unknowns, limited, limiter.Len())
		f.Sync()

		queries = 0
		announces = 0
		answered = 0
		limited = 0
		unknowns = 0

		lock.Unlock()
	}
}
<|endoftext|>


<commit_before>package mavenlink

import (
	"fmt"
	"net/url"
	"sort"
	"strconv"
	"time"

	"github.com/gistia/slackbot/db"
	"github.com/gistia/slackbot/utils"
)

type Mavenlink struct {
	Token   string
	Verbose bool
}

func NewMavenlink(token string, verbose bool) *Mavenlink {
	return &Mavenlink{Token: token, Verbose: verbose}
}

func NewFor(user string) (*Mavenlink, error) {
	token, err := db.GetSetting(user, "MAVENLINK_TOKEN")
	if err != nil {
		return nil, err
	}
	return NewMavenlink(token.Value, false), nil
}

//---------- Projects

func (mvn *Mavenlink) CreateProject(p Project) (*Project, error) {
	params := map[string]string{
		"workspace[title]":        p.Title,
		"workspace[description]":  p.Description,
		"workspace[creator_role]": p.CreatorRole,
	}
	resp, err := mvn.post("workspaces", params)
	if err != nil {
		return nil, err
	}
	projects := resp.ProjectList()
	if len(projects) > 0 {
		return &projects[0], nil
	}
	return nil, nil
}

func (mvn *Mavenlink) Projects() ([]Project, error) {
	var projects []Project
	resp, err := mvn.get("workspaces", nil)

	if err != nil {
		return nil, err
	}

	for k, _ := range resp.Projects {
		p := resp.Projects[k]
		projects = append(projects, p)
	}

	return projects, nil
}

func (mvn *Mavenlink) GetProject(id string) (*Project, error) {
	req := fmt.Sprintf("workspaces/%s", id)
	r, err := mvn.get(req, nil)

	if err != nil {
		return nil, err
	}

	return &r.ProjectList()[0], err
}

func (mvn *Mavenlink) SearchProject(term string) ([]Project, error) {
	search := fmt.Sprintf("matching=%s", term)
	r, err := mvn.get("workspaces", []string{search})

	if err != nil {
		return nil, err
	}

	return r.ProjectList(), err
}

//---------- Stories

func (mvn *Mavenlink) GetStory(id string) (*Story, error) {
	req := fmt.Sprintf("stories/%s", id)
	r, err := mvn.get(req, nil)

	if err != nil {
		return nil, err
	}

	return &r.StoryList()[0], err
}

func (mvn *Mavenlink) Stories(projectId string) ([]Story, error) {
	filters := []string{
		fmt.Sprintf("workspace_id=%s", projectId),
		"parents_only=true",
	}
	resp, err := mvn.get("stories", filters)

	if err != nil {
		return nil, err
	}

	return resp.StoryList(), nil
}

func (mvn *Mavenlink) GetChildStories(parentId string) ([]Story, error) {
	filters := []string{
		fmt.Sprintf("with_parent_id=%s", parentId),
	}
	resp, err := mvn.get("stories", filters)

	if err != nil {
		return nil, err
	}

	return resp.StoryList(), nil
}

func (mvn *Mavenlink) CreateStory(s Story) (*Story, error) {
	params, err := s.ToParams()
	if err != nil {
		return nil, err
	}

	resp, err := mvn.post("stories", params)
	if err != nil {
		return nil, err
	}

	stories := resp.StoryList()
	if len(stories) > 0 {
		return &stories[0], nil
	}

	return nil, nil
}

func (mvn *Mavenlink) UpdateStory(story Story) (*Story, error) {
	params, err := story.ToParams()
	if err != nil {
		return nil, err
	}

	resp, err := mvn.put("stories/"+story.Id, params)
	if err != nil {
		return nil, err
	}

	stories := resp.StoryList()
	if len(stories) > 0 {
		return &stories[0], nil
	}

	return nil, nil
}

func (mvn *Mavenlink) SetStoryState(id, state string) (*Story, error) {
	params := map[string]string{"story[state]": state}
	resp, err := mvn.put("stories/"+id, params)
	if err != nil {
		return nil, err
	}

	stories := resp.StoryList()
	if len(stories) > 0 {
		return &stories[0], nil
	}

	return nil, nil
}

func (mvn *Mavenlink) AddTimeEntry(s *Story, minutes int) (*TimeEntry, error) {
	date := time.Now().Format("2006-01-02")
	minStr := strconv.Itoa(minutes)
	params := map[string]string{
		"time_entry[date_performed]":  date,
		"time_entry[time_in_minutes]": minStr,
		"time_entry[story_id]":        s.Id,
		"time_entry[workspace_id]":    s.WorkspaceId,
	}
	resp, err := mvn.post("time_entries", params)
	if err != nil {
		return nil, err
	}

	entries := resp.TimeEntryList()
	if len(entries) > 0 {
		return &entries[0], nil
	}

	return nil, nil
}

//---------- Users

type UsersByName []User

func (u UsersByName) Len() int {
	return len(u)
}
func (u UsersByName) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}
func (u UsersByName) Less(i, j int) bool {
	return u[i].Name < u[j].Name
}

func (mvn *Mavenlink) GetUsers() ([]User, error) {
	var users []User
	resp, err := mvn.get("users", nil)

	if err != nil {
		return nil, err
	}

	for k, _ := range resp.Users {
		p := resp.Users[k]
		users = append(users, p)
	}
	sort.Sort(UsersByName(users))

	return users, nil
}

//---------- Internals

func (mvn *Mavenlink) makeUrl(uri string) string {
	return fmt.Sprintf("https://api.mavenlink.com/api/v1/%s.json", uri)
}

func (mvn *Mavenlink) request(method string, url string, data url.Values) ([]byte, error) {
	auth := fmt.Sprintf("Bearer %s", mvn.Token)
	headers := map[string]string{
		"Content-Type":  "application/x-www-form-urlencoded",
		"Authorization": auth,
	}

	return utils.Request(method, url, data, headers)
}

func (mvn *Mavenlink) getBody(uri string, filters []string) ([]byte, error) {
	url := mvn.makeUrl(uri)

	if filters != nil {
		url = url + "?"
		for i, f := range filters {
			if i > 0 {
				url = url + "&"
			}
			url = url + f
		}
	}

	fmt.Printf("Requesting: %s...\n", url)

	return mvn.request("GET", url, nil)
}

func (mvn *Mavenlink) get(uri string, filters []string) (*Response, error) {
	if filters == nil {
		filters = []string{}
	}

	filters = append(filters, "per_page=200")

	json, err := mvn.getBody(uri, filters)
	if err != nil {
		return nil, err
	}

	fmt.Println("Got:", string(json))

	resp, err := NewFromJson(json)
	return resp, err
}

func (mvn *Mavenlink) performRequest(method string, uri string, params map[string]string) (*Response, error) {
	postParams := url.Values{}
	for k, v := range params {
		postParams.Add(k, v)
	}

	mvnUrl := mvn.makeUrl(uri)
	// if method == "PUT" {
	// 	mvnUrl = fmt.Sprintf("http://requestb.in/tpabaatp")
	// }
	fmt.Printf("[mvn] Performing request %s to %s with %+v\n", method, mvnUrl, postParams)
	json, err := mvn.request(method, mvnUrl, postParams)
	if err != nil {
		return nil, err
	}

	resp, err := NewFromJson(json)
	return resp, err
}

func (mvn *Mavenlink) post(uri string, params map[string]string) (*Response, error) {
	return mvn.performRequest("POST", uri, params)
}

func (mvn *Mavenlink) put(uri string, params map[string]string) (*Response, error) {
	return mvn.performRequest("PUT", uri, params)
}
<commit_msg>Removed leftover<commit_after>package mavenlink

import (
	"fmt"
	"net/url"
	"sort"
	"strconv"
	"time"

	"github.com/gistia/slackbot/db"
	"github.com/gistia/slackbot/utils"
)

type Mavenlink struct {
	Token   string
	Verbose bool
}

func NewMavenlink(token string, verbose bool) *Mavenlink {
	return &Mavenlink{Token: token, Verbose: verbose}
}

func NewFor(user string) (*Mavenlink, error) {
	token, err := db.GetSetting(user, "MAVENLINK_TOKEN")
	if err != nil {
		return nil, err
	}
	return NewMavenlink(token.Value, false), nil
}

//---------- Projects

func (mvn *Mavenlink) CreateProject(p Project) (*Project, error) {
	params := map[string]string{
		"workspace[title]":        p.Title,
		"workspace[description]":  p.Description,
		"workspace[creator_role]": p.CreatorRole,
	}
	resp, err := mvn.post("workspaces", params)
	if err != nil {
		return nil, err
	}
	projects := resp.ProjectList()
	if len(projects) > 0 {
		return &projects[0], nil
	}
	return nil, nil
}

func (mvn *Mavenlink) Projects() ([]Project, error) {
	var projects []Project
	resp, err := mvn.get("workspaces", nil)

	if err != nil {
		return nil, err
	}

	for k, _ := range resp.Projects {
		p := resp.Projects[k]
		projects = append(projects, p)
	}

	return projects, nil
}

func (mvn *Mavenlink) GetProject(id string) (*Project, error) {
	req := fmt.Sprintf("workspaces/%s", id)
	r, err := mvn.get(req, nil)

	if err != nil {
		return nil, err
	}

	return &r.ProjectList()[0], err
}

func (mvn *Mavenlink) SearchProject(term string) ([]Project, error) {
	search := fmt.Sprintf("matching=%s", term)
	r, err := mvn.get("workspaces", []string{search})

	if err != nil {
		return nil, err
	}

	return r.ProjectList(), err
}

//---------- Stories

func (mvn *Mavenlink) GetStory(id string) (*Story, error) {
	req := fmt.Sprintf("stories/%s", id)
	r, err := mvn.get(req, nil)

	if err != nil {
		return nil, err
	}

	return &r.StoryList()[0], err
}

func (mvn *Mavenlink) Stories(projectId string) ([]Story, error) {
	filters := []string{
		fmt.Sprintf("workspace_id=%s", projectId),
		"parents_only=true",
	}
	resp, err := mvn.get("stories", filters)

	if err != nil {
		return nil, err
	}

	return resp.StoryList(), nil
}

func (mvn *Mavenlink) GetChildStories(parentId string) ([]Story, error) {
	filters := []string{
		fmt.Sprintf("with_parent_id=%s", parentId),
	}
	resp, err := mvn.get("stories", filters)

	if err != nil {
		return nil, err
	}

	return resp.StoryList(), nil
}

func (mvn *Mavenlink) CreateStory(s Story) (*Story, error) {
	params, err := s.ToParams()
	if err != nil {
		return nil, err
	}

	resp, err := mvn.post("stories", params)
	if err != nil {
		return nil, err
	}

	stories := resp.StoryList()
	if len(stories) > 0 {
		return &stories[0], nil
	}

	return nil, nil
}

func (mvn *Mavenlink) UpdateStory(story Story) (*Story, error) {
	params, err := story.ToParams()
	if err != nil {
		return nil, err
	}

	resp, err := mvn.put("stories/"+story.Id, params)
	if err != nil {
		return nil, err
	}

	stories := resp.StoryList()
	if len(stories) > 0 {
		return &stories[0], nil
	}

	return nil, nil
}

func (mvn *Mavenlink) SetStoryState(id, state string) (*Story, error) {
	params := map[string]string{"story[state]": state}
	resp, err := mvn.put("stories/"+id, params)
	if err != nil {
		return nil, err
	}

	stories := resp.StoryList()
	if len(stories) > 0 {
		return &stories[0], nil
	}

	return nil, nil
}

func (mvn *Mavenlink) AddTimeEntry(s *Story, minutes int) (*TimeEntry, error) {
	date := time.Now().Format("2006-01-02")
	minStr := strconv.Itoa(minutes)
	params := map[string]string{
		"time_entry[date_performed]":  date,
		"time_entry[time_in_minutes]": minStr,
		"time_entry[story_id]":        s.Id,
		"time_entry[workspace_id]":    s.WorkspaceId,
	}
	resp, err := mvn.post("time_entries", params)
	if err != nil {
		return nil, err
	}

	entries := resp.TimeEntryList()
	if len(entries) > 0 {
		return &entries[0], nil
	}

	return nil, nil
}

//---------- Users

type UsersByName []User

func (u UsersByName) Len() int {
	return len(u)
}
func (u UsersByName) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}
func (u UsersByName) Less(i, j int) bool {
	return u[i].Name < u[j].Name
}

func (mvn *Mavenlink) GetUsers() ([]User, error) {
	var users []User
	resp, err := mvn.get("users", nil)

	if err != nil {
		return nil, err
	}

	for k, _ := range resp.Users {
		p := resp.Users[k]
		users = append(users, p)
	}
	sort.Sort(UsersByName(users))

	return users, nil
}

//---------- Internals

func (mvn *Mavenlink) makeUrl(uri string) string {
	return fmt.Sprintf("https://api.mavenlink.com/api/v1/%s.json", uri)
}

func (mvn *Mavenlink) request(method string, url string, data url.Values) ([]byte, error) {
	auth := fmt.Sprintf("Bearer %s", mvn.Token)
	headers := map[string]string{
		"Content-Type":  "application/x-www-form-urlencoded",
		"Authorization": auth,
	}

	return utils.Request(method, url, data, headers)
}

func (mvn *Mavenlink) getBody(uri string, filters []string) ([]byte, error) {
	url := mvn.makeUrl(uri)

	if filters != nil {
		url = url + "?"
		for i, f := range filters {
			if i > 0 {
				url = url + "&"
			}
			url = url + f
		}
	}

	fmt.Printf("Requesting: %s...\n", url)

	return mvn.request("GET", url, nil)
}

func (mvn *Mavenlink) get(uri string, filters []string) (*Response, error) {
	if filters == nil {
		filters = []string{}
	}

	filters = append(filters, "per_page=200")

	json, err := mvn.getBody(uri, filters)
	if err != nil {
		return nil, err
	}

	fmt.Println("Got:", string(json))

	resp, err := NewFromJson(json)
	return resp, err
}

func (mvn *Mavenlink) performRequest(method string, uri string, params map[string]string) (*Response, error) {
	postParams := url.Values{}
	for k, v := range params {
		postParams.Add(k, v)
	}

	mvnUrl := mvn.makeUrl(uri)
	fmt.Printf("[mvn] Performing request %s to %s with %+v\n", method, mvnUrl, postParams)
	json, err := mvn.request(method, mvnUrl, postParams)
	if err != nil {
		return nil, err
	}

	resp, err := NewFromJson(json)
	return resp, err
}

func (mvn *Mavenlink) post(uri string, params map[string]string) (*Response, error) {
	return mvn.performRequest("POST", uri, params)
}

func (mvn *Mavenlink) put(uri string, params map[string]string) (*Response, error) {
	return mvn.performRequest("PUT", uri, params)
}
<|endoftext|>


<commit_before>package util

/*
 Copyright 2019 Crunchy Data Solutions, 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.
*/

import (
	"encoding/json"
	"errors"
	"fmt"
	"regexp"

	"github.com/crunchydata/postgres-operator/config"
	"github.com/crunchydata/postgres-operator/kubeapi"

	_ "github.com/lib/pq"
	log "github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

// InstanceReplicationInfo is the user friendly information for the current
// status of key replication metrics for a PostgreSQL instance
type InstanceReplicationInfo struct {
	Name           string
	Node           string
	ReplicationLag int
	Status         string
	Timeline       int
}

type ReplicationStatusRequest struct {
	RESTConfig  *rest.Config
	Clientset   *kubernetes.Clientset
	Namespace   string
	ClusterName string
}

type ReplicationStatusResponse struct {
	Instances []InstanceReplicationInfo
}

// instanceReplicationInfoJSON is the information returned from the request to
// the Patroni REST endpoint for info on the replication status of all the
// replicas
type instanceReplicationInfoJSON struct {
	PodName        string `json:"Member"`
	Type           string `json:"Role"`
	ReplicationLag int    `json:"Lag in MB"`
	State          string
	Timeline       int `json:"TL"`
}

const (
	// instanceReplicationInfoTypePrimary is the label used by Patroni to indicate that an instance
	// is indeed a primary PostgreSQL instance
	instanceReplicationInfoTypePrimary = "Leader"
	// pgPodNamePattern pattern is a pattern used by regexp to look up the
	// name of the pod
	pgPodNamePattern = "%s-[0-9a-z]{10}-[0-9a-z]{5}"
)

var (
	// instanceInfoCommand is the command used to get information about the status
	// and other statistics about the instances in a PostgreSQL cluster, e.g.
	// replication lag
	instanceInfoCommand = []string{"patronictl", "list", "-f", "json"}
)

// GetPod determines the best target to fail to
func GetPod(clientset *kubernetes.Clientset, deploymentName, namespace string) (*v1.Pod, error) {

	var err error

	var pod *v1.Pod
	var pods *v1.PodList

	selector := config.LABEL_DEPLOYMENT_NAME + "=" + deploymentName + "," + config.LABEL_PGHA_ROLE + "=replica"
	pods, err = kubeapi.GetPods(clientset, selector, namespace)
	if err != nil {
		return pod, err
	}
	if len(pods.Items) != 1 {
		return pod, errors.New("could not determine which pod to failover to")
	}

	for _, v := range pods.Items {
		pod = &v
	}

	found := false

	//make sure the pod has a database container it it
	for _, c := range pod.Spec.Containers {
		if c.Name == "database" {
			found = true
		}
	}

	if !found {
		return pod, errors.New("could not find a database container in the pod")
	}

	return pod, err
}

// ReplicationStatus is responsible for retrieving and returning the replication
// information about the status of the replicas in a PostgreSQL cluster. It
// executes into a single replica pod and leverages the functionality of Patroni
// for getting the key metrics that are appropriate to help the user understand
// the current state of their replicas.
//
// Statistics include: the current node the replica is on, if it is up, the
// replication lag, etc.
func ReplicationStatus(request ReplicationStatusRequest) (ReplicationStatusResponse, error) {
	response := ReplicationStatusResponse{
		Instances: make([]InstanceReplicationInfo, 0),
	}

	// First, get replica pods using selector pg-cluster=clusterName-replica,role=replica
	selector := fmt.Sprintf("%s=%s,%s=replica",
		config.LABEL_PG_CLUSTER, request.ClusterName, config.LABEL_PGHA_ROLE)

	log.Debugf(`searching for pods with "%s"`, selector)
	pods, err := kubeapi.GetPods(request.Clientset, selector, request.Namespace)

	// If there is an error trying to get the pods, return here. Allow the caller
	// to handle the error
	if err != nil {
		return response, err
	}

	// See how many replica instances were found. If none were found then return
	log.Debugf(`replica pods found "%d"`, len(pods.Items))

	if len(pods.Items) == 0 {
		return response, err
	}

	// We need to create a quick map of "instance name" => node name
	// We will iterate through the pod list once to extract the name we refer to
	// the specific instance as, as well as which node it is deployed on
	instanceNodeMap := createInstanceNodeMap(pods)

	// Now get the statistics about the current state of the replicas, which we
	// can delegate to Patroni vis-a-vis the information that it collects
	// We can get the statistics about the current state of the managed instance
	// From executing and running a command in the first pod
	pod := pods.Items[0]

	// Execute the command that will retrieve the replica information from Patroni
	commandStdOut, _, err := kubeapi.ExecToPodThroughAPI(
		request.RESTConfig, request.Clientset, instanceInfoCommand,
		pod.Spec.Containers[0].Name, pod.Name, request.Namespace, nil)

	// if there is an error, return. We will log the error at a higher level
	if err != nil {
		return response, err
	}

	// parse the JSON and plast it into instanceInfoList
	var rawInstances []instanceReplicationInfoJSON
	json.Unmarshal([]byte(commandStdOut), &rawInstances)

	log.Debugf("patroni instance info: %v", rawInstances)

	// We need to iterate through this list to format the information for the
	// response
	for _, rawInstance := range rawInstances {
		// if this is a primary, skip it
		if rawInstance.Type == instanceReplicationInfoTypePrimary {
			continue
		}

		// set up the instance that will be returned
		instance := InstanceReplicationInfo{
			ReplicationLag: rawInstance.ReplicationLag,
			Status:         rawInstance.State,
			Timeline:       rawInstance.Timeline,
		}

		// get the instance name that is recognized by the Operator, which is the
		// first part of the name and is kept on a deployment label. We have these
		// available in our instanceNodeMap, and because we skip over the primary,
		// this will not lead to false positive
		//
		// This is not the cleanest way of doing it, but it works
		for name, node := range instanceNodeMap {
			r, err := regexp.Compile(fmt.Sprintf(pgPodNamePattern, name))

			// if there is an error compiling the regular expression, add an error to
			// log log and keep iterating
			if err != nil {
				log.Error(err)
				continue
			}

			// see if there is a match in the names. If it , add the name and node for
			// this particular instance
			if r.Match([]byte(rawInstance.PodName)) {
				instance.Name = name
				instance.Node = node
				break
			}
		}

		// append this newly created instance to the list that will be returned
		response.Instances = append(response.Instances, instance)
	}

	// pass along the response for the requestor to process
	return response, nil
}

func GetPreferredNodes(clientset *kubernetes.Clientset, selector, namespace string) ([]string, error) {
	nodes := make([]string, 0)

	nodeList, err := kubeapi.GetNodes(clientset, selector, namespace)
	if err != nil {
		return nodes, err
	}

	log.Debugf("getPreferredNodes shows %d nodes", len(nodeList.Items))

	for _, node := range nodeList.Items {
		nodes = append(nodes, node.Name)
	}

	return nodes, err
}

// ToggleAutoFailover enables or disables autofailover for a cluster.  Disabling autofailover means "pausing"
// Patroni, which will result in Patroni stepping aside from managing the cluster.  This will effectively cause
// Patroni to stop responding to failures or other database activities, e.g. it will not attempt to start the
// database when stopped to perform maintenance
func ToggleAutoFailover(clientset *kubernetes.Clientset, enable bool, pghaScope, namespace string) error {

	// find the "config" configMap created by Patroni
	configMapName := pghaScope + "-config"
	log.Debugf("setting autofailover to %t for cluster with pgha scope %s", enable, pghaScope)

	configMap, found := kubeapi.GetConfigMap(clientset, configMapName, namespace)
	if !found {
		err := fmt.Errorf("Unable to find configMap %s when attempting disable autofailover", configMapName)
		log.Error(err)
		return err
	}

	configJSONStr := configMap.ObjectMeta.Annotations["config"]

	var configJSON map[string]interface{}
	json.Unmarshal([]byte(configJSONStr), &configJSON)

	if !enable {
		// disable autofail condition
		disableFailover(clientset, configMap, configJSON, namespace)
	} else {
		// enable autofail
		enableFailover(clientset, configMap, configJSON, namespace)
	}

	return nil
}

// createInstanceNodeMap creates a mapping between the names of the PostgreSQL
// instances to the Nodes that they run on, based upon the output from a
// Kubernetes API query
func createInstanceNodeMap(pods *v1.PodList) map[string]string {
	instanceNodeMap := map[string]string{}

	// Iterate through each pod that is returned and get the mapping between the
	// PostgreSQL instance name and the node it is scheduled on
	for _, pod := range pods.Items {
		// get the replica name from the metadata on the pod
		// for legacy purposes, we are using the "deployment name" label
		replicaName := pod.ObjectMeta.Labels[config.LABEL_DEPLOYMENT_NAME]
		// get the node name from the spec on the pod
		nodeName := pod.Spec.NodeName
		// add them to the map
		instanceNodeMap[replicaName] = nodeName
	}

	log.Debugf("instance/node map: %v", instanceNodeMap)

	return instanceNodeMap
}

// If "pause" is present in the config and set to "true", then it needs to be removed to enable
// failover.  Otherwise, if "pause" isn't present in the config or if it has a value other than
// true, then assume autofail is enabled and do nothing (when Patroni see's an invalid value for
// "pause" it sets it to "true")
func enableFailover(clientset *kubernetes.Clientset, configMap *v1.ConfigMap, configJSON map[string]interface{},
	namespace string) error {
	if _, ok := configJSON["pause"]; ok && configJSON["pause"] == true {
		log.Debugf("updating pause key in configMap %s to enable autofailover", configMap.Name)
		//  disabled autofail by removing "pause" from the config
		delete(configJSON, "pause")
		configJSONFinalStr, err := json.Marshal(configJSON)
		if err != nil {
			return err
		}
		configMap.ObjectMeta.Annotations["config"] = string(configJSONFinalStr)
		err = kubeapi.UpdateConfigMap(clientset, configMap, namespace)
		if err != nil {
			return err
		}
	} else {
		log.Debugf("autofailover already enabled according to the pause key (or lack thereof) in configMap %s",
			configMap.Name)
	}
	return nil
}

// If "pause" isn't present in the config then assume autofail is enabled and needs to be disabled
// by setting "pause" to true.  Or if it is present and set to something other than "true" (e.g.
// "false" or "null"), then it also needs to be disabled by setting "pause" to true.
func disableFailover(clientset *kubernetes.Clientset, configMap *v1.ConfigMap, configJSON map[string]interface{},
	namespace string) error {
	if _, ok := configJSON["pause"]; !ok || configJSON["pause"] != true {
		log.Debugf("updating pause key in configMap %s to disable autofailover", configMap.Name)
		// disable autofail by setting "pause" to true
		configJSON["pause"] = true
		configJSONFinalStr, err := json.Marshal(configJSON)
		if err != nil {
			return err
		}
		configMap.ObjectMeta.Annotations["config"] = string(configJSONFinalStr)
		err = kubeapi.UpdateConfigMap(clientset, configMap, namespace)
		if err != nil {
			return err
		}
	} else {
		log.Debugf("autofailover already disabled according to the pause key in configMap %s",
			configMap.Name)
	}
	return nil
}
<commit_msg>Ensure all failover/scaledown targets are listed<commit_after>package util

/*
 Copyright 2019 Crunchy Data Solutions, 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.
*/

import (
	"encoding/json"
	"errors"
	"fmt"
	"regexp"

	"github.com/crunchydata/postgres-operator/config"
	"github.com/crunchydata/postgres-operator/kubeapi"

	_ "github.com/lib/pq"
	log "github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

// InstanceReplicationInfo is the user friendly information for the current
// status of key replication metrics for a PostgreSQL instance
type InstanceReplicationInfo struct {
	Name           string
	Node           string
	ReplicationLag int
	Status         string
	Timeline       int
}

type ReplicationStatusRequest struct {
	RESTConfig  *rest.Config
	Clientset   *kubernetes.Clientset
	Namespace   string
	ClusterName string
}

type ReplicationStatusResponse struct {
	Instances []InstanceReplicationInfo
}

// instanceReplicationInfoJSON is the information returned from the request to
// the Patroni REST endpoint for info on the replication status of all the
// replicas
type instanceReplicationInfoJSON struct {
	PodName        string `json:"Member"`
	Type           string `json:"Role"`
	ReplicationLag int    `json:"Lag in MB"`
	State          string
	Timeline       int `json:"TL"`
}

const (
	// instanceReplicationInfoTypePrimary is the label used by Patroni to indicate that an instance
	// is indeed a primary PostgreSQL instance
	instanceReplicationInfoTypePrimary = "Leader"
	// pgPodNamePattern pattern is a pattern used by regexp to look up the
	// name of the pod
	// The character classes are derived from the Kubernetes random generator
	// "SafeEncodeString"
	//
	// https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/rand/rand.go#L121-L127
	pgPodNamePattern = "%s-[bcdfghjklmnpqrstvwxz2456789]+-[0-9a-z]{5}$"
)

var (
	// instanceInfoCommand is the command used to get information about the status
	// and other statistics about the instances in a PostgreSQL cluster, e.g.
	// replication lag
	instanceInfoCommand = []string{"patronictl", "list", "-f", "json"}
)

// GetPod determines the best target to fail to
func GetPod(clientset *kubernetes.Clientset, deploymentName, namespace string) (*v1.Pod, error) {

	var err error

	var pod *v1.Pod
	var pods *v1.PodList

	selector := config.LABEL_DEPLOYMENT_NAME + "=" + deploymentName + "," + config.LABEL_PGHA_ROLE + "=replica"
	pods, err = kubeapi.GetPods(clientset, selector, namespace)
	if err != nil {
		return pod, err
	}
	if len(pods.Items) != 1 {
		return pod, errors.New("could not determine which pod to failover to")
	}

	for _, v := range pods.Items {
		pod = &v
	}

	found := false

	//make sure the pod has a database container it it
	for _, c := range pod.Spec.Containers {
		if c.Name == "database" {
			found = true
		}
	}

	if !found {
		return pod, errors.New("could not find a database container in the pod")
	}

	return pod, err
}

// ReplicationStatus is responsible for retrieving and returning the replication
// information about the status of the replicas in a PostgreSQL cluster. It
// executes into a single replica pod and leverages the functionality of Patroni
// for getting the key metrics that are appropriate to help the user understand
// the current state of their replicas.
//
// Statistics include: the current node the replica is on, if it is up, the
// replication lag, etc.
func ReplicationStatus(request ReplicationStatusRequest) (ReplicationStatusResponse, error) {
	response := ReplicationStatusResponse{
		Instances: make([]InstanceReplicationInfo, 0),
	}

	// First, get replica pods using selector pg-cluster=clusterName-replica,role=replica
	selector := fmt.Sprintf("%s=%s,%s=replica",
		config.LABEL_PG_CLUSTER, request.ClusterName, config.LABEL_PGHA_ROLE)

	log.Debugf(`searching for pods with "%s"`, selector)
	pods, err := kubeapi.GetPods(request.Clientset, selector, request.Namespace)

	// If there is an error trying to get the pods, return here. Allow the caller
	// to handle the error
	if err != nil {
		return response, err
	}

	// See how many replica instances were found. If none were found then return
	log.Debugf(`replica pods found "%d"`, len(pods.Items))

	if len(pods.Items) == 0 {
		return response, err
	}

	// We need to create a quick map of "instance name" => node name
	// We will iterate through the pod list once to extract the name we refer to
	// the specific instance as, as well as which node it is deployed on
	instanceNodeMap := createInstanceNodeMap(pods)

	// Now get the statistics about the current state of the replicas, which we
	// can delegate to Patroni vis-a-vis the information that it collects
	// We can get the statistics about the current state of the managed instance
	// From executing and running a command in the first pod
	pod := pods.Items[0]

	// Execute the command that will retrieve the replica information from Patroni
	commandStdOut, _, err := kubeapi.ExecToPodThroughAPI(
		request.RESTConfig, request.Clientset, instanceInfoCommand,
		pod.Spec.Containers[0].Name, pod.Name, request.Namespace, nil)

	// if there is an error, return. We will log the error at a higher level
	if err != nil {
		return response, err
	}

	// parse the JSON and plast it into instanceInfoList
	var rawInstances []instanceReplicationInfoJSON
	json.Unmarshal([]byte(commandStdOut), &rawInstances)

	log.Debugf("patroni instance info: %v", rawInstances)

	// We need to iterate through this list to format the information for the
	// response
	for _, rawInstance := range rawInstances {
		// if this is a primary, skip it
		if rawInstance.Type == instanceReplicationInfoTypePrimary {
			continue
		}

		// set up the instance that will be returned
		instance := InstanceReplicationInfo{
			ReplicationLag: rawInstance.ReplicationLag,
			Status:         rawInstance.State,
			Timeline:       rawInstance.Timeline,
		}

		// get the instance name that is recognized by the Operator, which is the
		// first part of the name and is kept on a deployment label. We have these
		// available in our instanceNodeMap, and because we skip over the primary,
		// this will not lead to false positive
		//
		// This is not the cleanest way of doing it, but it works
		for name, node := range instanceNodeMap {
			r, err := regexp.Compile(fmt.Sprintf(pgPodNamePattern, name))

			// if there is an error compiling the regular expression, add an error to
			// log log and keep iterating
			if err != nil {
				log.Error(err)
				continue
			}

			// see if there is a match in the names. If it , add the name and node for
			// this particular instance
			if r.Match([]byte(rawInstance.PodName)) {
				instance.Name = name
				instance.Node = node
				break
			}
		}

		// append this newly created instance to the list that will be returned
		response.Instances = append(response.Instances, instance)
	}

	// pass along the response for the requestor to process
	return response, nil
}

func GetPreferredNodes(clientset *kubernetes.Clientset, selector, namespace string) ([]string, error) {
	nodes := make([]string, 0)

	nodeList, err := kubeapi.GetNodes(clientset, selector, namespace)
	if err != nil {
		return nodes, err
	}

	log.Debugf("getPreferredNodes shows %d nodes", len(nodeList.Items))

	for _, node := range nodeList.Items {
		nodes = append(nodes, node.Name)
	}

	return nodes, err
}

// ToggleAutoFailover enables or disables autofailover for a cluster.  Disabling autofailover means "pausing"
// Patroni, which will result in Patroni stepping aside from managing the cluster.  This will effectively cause
// Patroni to stop responding to failures or other database activities, e.g. it will not attempt to start the
// database when stopped to perform maintenance
func ToggleAutoFailover(clientset *kubernetes.Clientset, enable bool, pghaScope, namespace string) error {

	// find the "config" configMap created by Patroni
	configMapName := pghaScope + "-config"
	log.Debugf("setting autofailover to %t for cluster with pgha scope %s", enable, pghaScope)

	configMap, found := kubeapi.GetConfigMap(clientset, configMapName, namespace)
	if !found {
		err := fmt.Errorf("Unable to find configMap %s when attempting disable autofailover", configMapName)
		log.Error(err)
		return err
	}

	configJSONStr := configMap.ObjectMeta.Annotations["config"]

	var configJSON map[string]interface{}
	json.Unmarshal([]byte(configJSONStr), &configJSON)

	if !enable {
		// disable autofail condition
		disableFailover(clientset, configMap, configJSON, namespace)
	} else {
		// enable autofail
		enableFailover(clientset, configMap, configJSON, namespace)
	}

	return nil
}

// createInstanceNodeMap creates a mapping between the names of the PostgreSQL
// instances to the Nodes that they run on, based upon the output from a
// Kubernetes API query
func createInstanceNodeMap(pods *v1.PodList) map[string]string {
	instanceNodeMap := map[string]string{}

	// Iterate through each pod that is returned and get the mapping between the
	// PostgreSQL instance name and the node it is scheduled on
	for _, pod := range pods.Items {
		// get the replica name from the metadata on the pod
		// for legacy purposes, we are using the "deployment name" label
		replicaName := pod.ObjectMeta.Labels[config.LABEL_DEPLOYMENT_NAME]
		// get the node name from the spec on the pod
		nodeName := pod.Spec.NodeName
		// add them to the map
		instanceNodeMap[replicaName] = nodeName
	}

	log.Debugf("instance/node map: %v", instanceNodeMap)

	return instanceNodeMap
}

// If "pause" is present in the config and set to "true", then it needs to be removed to enable
// failover.  Otherwise, if "pause" isn't present in the config or if it has a value other than
// true, then assume autofail is enabled and do nothing (when Patroni see's an invalid value for
// "pause" it sets it to "true")
func enableFailover(clientset *kubernetes.Clientset, configMap *v1.ConfigMap, configJSON map[string]interface{},
	namespace string) error {
	if _, ok := configJSON["pause"]; ok && configJSON["pause"] == true {
		log.Debugf("updating pause key in configMap %s to enable autofailover", configMap.Name)
		//  disabled autofail by removing "pause" from the config
		delete(configJSON, "pause")
		configJSONFinalStr, err := json.Marshal(configJSON)
		if err != nil {
			return err
		}
		configMap.ObjectMeta.Annotations["config"] = string(configJSONFinalStr)
		err = kubeapi.UpdateConfigMap(clientset, configMap, namespace)
		if err != nil {
			return err
		}
	} else {
		log.Debugf("autofailover already enabled according to the pause key (or lack thereof) in configMap %s",
			configMap.Name)
	}
	return nil
}

// If "pause" isn't present in the config then assume autofail is enabled and needs to be disabled
// by setting "pause" to true.  Or if it is present and set to something other than "true" (e.g.
// "false" or "null"), then it also needs to be disabled by setting "pause" to true.
func disableFailover(clientset *kubernetes.Clientset, configMap *v1.ConfigMap, configJSON map[string]interface{},
	namespace string) error {
	if _, ok := configJSON["pause"]; !ok || configJSON["pause"] != true {
		log.Debugf("updating pause key in configMap %s to disable autofailover", configMap.Name)
		// disable autofail by setting "pause" to true
		configJSON["pause"] = true
		configJSONFinalStr, err := json.Marshal(configJSON)
		if err != nil {
			return err
		}
		configMap.ObjectMeta.Annotations["config"] = string(configJSONFinalStr)
		err = kubeapi.UpdateConfigMap(clientset, configMap, namespace)
		if err != nil {
			return err
		}
	} else {
		log.Debugf("autofailover already disabled according to the pause key in configMap %s",
			configMap.Name)
	}
	return nil
}
<|endoftext|>


<commit_before>package cgroups

func getClockTicks() uint64 {
	return 100
}
<commit_msg>Add comments for getClockTicks<commit_after>package cgroups

func getClockTicks() uint64 {
	// The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
	// on Linux it's a constant which is safe to be hard coded,
	// so we can avoid using cgo here.
	// See https://github.com/containerd/cgroups/pull/12 for
	// more details.
	return 100
}
<|endoftext|>


<commit_before>package times

import (
	"errors"
	"time"
)

type Times struct {
	time     time.Time
	timezone string
	location *time.Location
}

func (t *Times) String() string {
	return t.time.String()
}

// This will return an instance of Times the current time localized to UTC
func Now() *Times {
	timezone := "UTC"
	location, _ := time.LoadLocation(timezone)
	time := time.Now().UTC()
	return &Times{time, timezone, location}
}

// This will localixe
func (t *Times) Normalize(timezone string) (*Times, error) {
	location, err := time.LoadLocation(timezone)

	if err != nil {
		return nil, errors.New("The provided timezone isn't a valid time.Location " + timezone)
	}
	time := t.time.In(location)
	return &Times{time, timezone, location}, nil
}

func (t *Times) Equal(u *Times) bool {
	return t.time.Equal(u.time)
}
<commit_msg>fixing the code for name change and adding a epoch func<commit_after>package times

import (
	"errors"
	"time"
)

type Times struct {
	Time     time.Time
	Timezone string
	Location *time.Location
}

func (t *Times) String() string {
	return t.Time.String()
}

// This will return an instance of Times the current time localized to UTC
func Now() *Times {
	timezone := "UTC"
	location, _ := time.LoadLocation(timezone)
	time := time.Now().UTC()
	return &Times{time, timezone, location}
}

// This will localixe
func (t *Times) Normalize(timezone string) (*Times, error) {
	location, err := time.LoadLocation(timezone)

	if err != nil {
		return nil, errors.New("The provided timezone isn't a valid time.Location " + timezone)
	}
	time := t.Time.In(location)
	return &Times{time, timezone, location}, nil
}

func (t *Times) Equal(u *Times) bool {
	return t.Time.Equal(u.Time)
}

func (t *Times) Epoch() int64 {
	return t.Time.Unix()
}
<|endoftext|>


<commit_before>package security

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"io/ioutil"
	"math/big"
	"net"
	"path/filepath"
	"time"
)

var (
	validFor = 365 * 24 * time.Hour
)

// GenerateKeypair generates a new keypair in the given location
func GenerateKeypair(location string) error {
	// Generate private key
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return err
	}

	privPEM, err := PrivatePEM(key)
	if err != nil {
		return err
	}
	pubPEM, err := PublicPEM(key)
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(filepath.Clean(location+"/server.pub"), pubPEM, 0644)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filepath.Clean(location+"/server.key"), privPEM, 0600)
	if err != nil {
		return err
	}

	return nil
}

// GenerateCert generates a certificate for the given hostnames in the given location
func GenerateCert(location string, hostnames ...string) error {
	privKey, err := LoadKeypair(location)
	if err != nil {
		return err
	}

	// Build Certificate
	notBefore := time.Now()
	notAfter := notBefore.Add(validFor)
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return err
	}
	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"The Things Network"},
		},
		IsCA:                  true,
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
	}
	for _, h := range hostnames {
		if ip := net.ParseIP(h); ip != nil {
			template.IPAddresses = append(template.IPAddresses, ip)
		} else {
			template.DNSNames = append(template.DNSNames, h)
		}
	}

	// Generate certificate
	certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, privKey.Public(), privKey)
	if err != nil {
		return err
	}
	certPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: certBytes,
	})

	err = ioutil.WriteFile(filepath.Clean(location+"/server.cert"), certPEM, 0644)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Add x509.KeyUsageCertSign usage<commit_after>package security

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"io/ioutil"
	"math/big"
	"net"
	"path/filepath"
	"time"
)

var (
	validFor = 365 * 24 * time.Hour
)

// GenerateKeypair generates a new keypair in the given location
func GenerateKeypair(location string) error {
	// Generate private key
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return err
	}

	privPEM, err := PrivatePEM(key)
	if err != nil {
		return err
	}
	pubPEM, err := PublicPEM(key)
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(filepath.Clean(location+"/server.pub"), pubPEM, 0644)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filepath.Clean(location+"/server.key"), privPEM, 0600)
	if err != nil {
		return err
	}

	return nil
}

// GenerateCert generates a certificate for the given hostnames in the given location
func GenerateCert(location string, hostnames ...string) error {
	privKey, err := LoadKeypair(location)
	if err != nil {
		return err
	}

	// Build Certificate
	notBefore := time.Now()
	notAfter := notBefore.Add(validFor)
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return err
	}
	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"The Things Network"},
		},
		IsCA:                  true,
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
	}
	for _, h := range hostnames {
		if ip := net.ParseIP(h); ip != nil {
			template.IPAddresses = append(template.IPAddresses, ip)
		} else {
			template.DNSNames = append(template.DNSNames, h)
		}
	}

	// Generate certificate
	certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, privKey.Public(), privKey)
	if err != nil {
		return err
	}
	certPEM := pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: certBytes,
	})

	err = ioutil.WriteFile(filepath.Clean(location+"/server.cert"), certPEM, 0644)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package log

import (
	"fmt"
	"github.com/yang-f/beauty/settings"
	"log"
	"os"
	"runtime"
	"strings"
)

var Fatalln = log.Fatalln
var Fatal = log.Fatal

func Printf(format string, params ...interface{}) {
	_, f, line, _ := runtime.Caller(1)
	log.Printf(format, params...)
	file, err := os.OpenFile(settings.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	if err != nil {
		log.Printf("%v", err)
		return
	}
	defer file.Close()
	_, err = file.Seek(0, os.SEEK_END)
	if err != nil {
		return
	}
	args := strings.Split(f, "/")
	f = args[len(args)-1]
	msg := fmt.Sprintf("%v:%v(%v)", line, format, f)
	logger := log.New(file, "", log.LstdFlags)
	logger.Printf(msg, params...)
}

func Println(v ...interface{}) {
	_, f, line, _ := runtime.Caller(1)
	log.Println(v...)
	file, err := os.OpenFile(settings.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	if err != nil {
		log.Printf("%v", err)
		return
	}
	defer file.Close()
	_, err = file.Seek(0, os.SEEK_END)
	if err != nil {
		return
	}
	args := strings.Split(f, "/")
	f = args[len(args)-1]
	msg := fmt.Sprintf("%v:%v(%v)", line, fmt.Sprintln(v...), f)
	logger := log.New(file, "", log.LstdFlags)
	logger.Println(msg)
}
<commit_msg>log archive<commit_after>package log

import (
	"bytes"
	"fmt"
	"github.com/yang-f/beauty/settings"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

var Fatalln = log.Fatalln
var Fatal = log.Fatal

var ticker = time.NewTicker(time.Minute * 60 * 12)

func init() {
	go func() {
		for _ = range ticker.C {
			archive()
		}
	}()
}

func Printf(format string, params ...interface{}) {
	_, f, line, _ := runtime.Caller(1)
	log.Printf(format, params...)
	file, err := os.OpenFile(settings.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	if err != nil {
		log.Printf("%v", err)
		return
	}
	defer file.Close()
	_, err = file.Seek(0, os.SEEK_END)
	if err != nil {
		return
	}
	args := strings.Split(f, "/")
	f = args[len(args)-1]
	msg := fmt.Sprintf("%v:%v(%v)", line, format, f)
	logger := log.New(file, "", log.LstdFlags)
	logger.Printf(msg, params...)
}

func Println(v ...interface{}) {
	_, f, line, _ := runtime.Caller(1)
	log.Println(v...)
	file, err := os.OpenFile(settings.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	if err != nil {
		log.Printf("%v", err)
		return
	}
	defer file.Close()
	_, err = file.Seek(0, os.SEEK_END)
	if err != nil {
		return
	}
	args := strings.Split(f, "/")
	f = args[len(args)-1]
	msg := fmt.Sprintf("%v:%v(%v)", line, fmt.Sprintln(v...), f)
	logger := log.New(file, "", log.LstdFlags)
	logger.Println(msg)
}

func shortFileName(file string) string {
	return filepath.Base(file)
}

func shortFileDir(file string) string {
	return filepath.Dir(file)
}

func archive() error {
	info, _ := os.Stat(settings.LogFile)
	if info.Size() > 1024*1024*50 {
		target := fmt.Sprintf("%v.%v.tar.gz",
			shortFileName(settings.LogFile),
			time.Now().Format("2006-01-02-15-04"),
		)

		_, err := os.Stat(target)
		if os.IsNotExist(err) {
			tmp := fmt.Sprintf("%v.%v.tmp",
				shortFileName(settings.LogFile),
				time.Now().Format("2006-01-02-15-04"),
			)
			in := bytes.NewBuffer(nil)
			cmd := exec.Command("sh")
			cmd.Stdin = in
			go func() {
				in.WriteString(fmt.Sprintf("cd %v\n", shortFileDir(settings.LogFile)))
				in.WriteString(fmt.Sprintf("cp %v %v\n", shortFileName(settings.LogFile), tmp))
				in.WriteString(fmt.Sprintf("echo '' > %v\n", shortFileName(settings.LogFile)))
				in.WriteString(fmt.Sprintf("tar -czvf %v %v\n", target, tmp))
				in.WriteString(fmt.Sprintf("rm %v\n", tmp))
				in.WriteString("exit\n")
			}()
			if err := cmd.Run(); err != nil {
				fmt.Println(err)
				return err
			}
		}
	}
	return nil
}
<|endoftext|>