<commit_before>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package glutil

import (
	"bytes"
	"encoding/binary"
	"image"
	"log"
	"math"
	"sync"

	"code.google.com/p/go.mobile/f32"
	"code.google.com/p/go.mobile/geom"
	"code.google.com/p/go.mobile/gl"
)

var glimage struct {
	sync.Once
	square        gl.Buffer
	squareUV      gl.Buffer
	program       gl.Program
	pos           gl.Attrib
	mvp           gl.Uniform
	uvp           gl.Uniform
	inUV          gl.Attrib
	textureSample gl.Uniform
}

func glInit() {
	var err error
	glimage.program, err = CreateProgram(vertexShader, fragmentShader)
	if err != nil {
		panic(err)
	}

	glimage.square = gl.GenBuffer()
	glimage.squareUV = gl.GenBuffer()

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.square)
	gl.BufferData(gl.ARRAY_BUFFER, gl.STATIC_DRAW, squareCoords)
	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.squareUV)
	gl.BufferData(gl.ARRAY_BUFFER, gl.STATIC_DRAW, squareUVCoords)

	glimage.pos = gl.GetAttribLocation(glimage.program, "pos")
	glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp")
	glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp")
	glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV")
	glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample")
}

// Image bridges between an *image.RGBA and an OpenGL texture.
//
// The contents of the embedded *image.RGBA can be uploaded as a
// texture and drawn as a 2D quad.
//
// The number of active Images must fit in the system's OpenGL texture
// limit. The typical use of an Image is as a texture atlas.
type Image struct {
	*image.RGBA

	Texture   gl.Texture
	texWidth  int
	texHeight int
}

// NewImage creates an Image of the given size.
//
// Both a host-memory *image.RGBA and a GL texture are created.
func NewImage(size geom.Point) *Image {
	realx := int(math.Ceil(float64(size.X.Px())))
	realy := int(math.Ceil(float64(size.Y.Px())))
	dx := roundToPower2(realx)
	dy := roundToPower2(realy)

	// TODO(crawshaw): Using VertexAttribPointer we can pass texture
	// data with a stride, which would let us use the exact number of
	// pixels on the host instead of the rounded up power 2 size.
	m := image.NewRGBA(image.Rect(0, 0, dx, dy))

	glimage.Do(glInit)

	img := &Image{
		RGBA:      m.SubImage(image.Rect(0, 0, realx, realy)).(*image.RGBA),
		Texture:   gl.GenTexture(),
		texWidth:  dx,
		texHeight: dy,
	}
	// TODO(crawshaw): We don't have the context on a finalizer. Find a way.
	// runtime.SetFinalizer(img, func(img *Image) { gl.DeleteTexture(img.Texture) })
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.TexImage2D(gl.TEXTURE_2D, 0, dx, dy, gl.RGBA, gl.UNSIGNED_BYTE, nil)

	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

	return img
}

func roundToPower2(x int) int {
	x2 := 1
	for x2 < x {
		x2 *= 2
	}
	return x2
}

// Upload copies the host image data to the GL device.
func (img *Image) Upload() {
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.texWidth, img.texHeight, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
}

// Draw draws the image onto the current GL framebuffer.
func (img *Image) Draw(dstBounds geom.Rectangle, srcBounds image.Rectangle) {
	// TODO(crawshaw): Adjust viewport for the top bar on android?
	gl.UseProgram(glimage.program)

	// We are drawing a sub-image of dst, defined by dstBounds. Let ABCD
	// be the image, and PQRS be the sub-image. The two images may actually
	// be equal, but in the general case, PQRS can be smaller:
	//
	//	       M
	//	A +----+----------+ B
	//	  |               |
	//	N +  P +-----+ Q  |
	//	  |    |     |    |
	//	  |  S +-----+ R  |
	//	  |               |
	//	D +---------------+ C
	//
	// There are two co-ordinate spaces: geom space and framebuffer space.
	// In geom space, the ABCD rectangle is:
	//
	//	(0, 0)           (geom.Width, 0)
	//	(0, geom.Height) (geom.Width, geom.Height)
	//
	// and the PQRS rectangle is:
	//
	//	(dstBounds.Min.X, dstBounds.Min.Y) (dstBounds.Max.X, dstBounds.Min.Y)
	//	(dstBounds.Min.X, dstBounds.Max.Y) (dstBounds.Max.X, dstBounds.Max.Y)
	//
	// In framebuffer space, the ABCD rectangle is:
	//
	//	(-1, +1) (+1, +1)
	//	(-1, -1) (+1, -1)
	//
	// We need to solve for PQRS' co-ordinates in framebuffer space, and
	// calculate the MVP matrix that transforms the -1/+1 ABCD co-ordinates
	// to PQRS co-ordinates.
	//
	// To solve for PQRS, note that PQ / AB must match in both spaces. Call
	// this ratio fracX, and likewise for fracY.
	//
	//	[EQ1]   fracX = (dstBounds.Max.X - dstBounds.Min.X) / geom.Width
	//
	// Similarly, the AM / AB ratio must match:
	//
	//	(P.x - -1) / (1 - -1) = dstBounds.Min.X / geom.Width
	//
	// where the LHS is in framebuffer space and the RHS in geom space. This
	// equation is equivalent to:
	//
	//	[EQ2]   P.x = -1 + 2 * dstBounds.Min.X / geom.Width
	//
	// This MVP matrix is a scale followed by a translate. The scale is by
	// (fracX, fracY). After this, our corners have been transformed to:
	//
	//	(-fracX, +fracY) (+fracX, +fracY)
	//	(-fracX, -fracY) (+fracX, -fracY)
	//
	// so the translate is by (P.x + fracX) in the X direction, and
	// likewise for Y. Combining equations EQ1 and EQ2 simplifies the
	// translate to be:
	//
	//	-1 + (dstBounds.Max.X + dstBounds.Min.X) / geom.Width
	//	+1 - (dstBounds.Max.Y + dstBounds.Min.Y) / geom.Height
	var a f32.Affine
	a.Identity()
	a.Translate(
		&a,
		-1+float32((dstBounds.Max.X+dstBounds.Min.X)/geom.Width),
		+1-float32((dstBounds.Max.Y+dstBounds.Min.Y)/geom.Height),
	)
	a.Scale(
		&a,
		float32((dstBounds.Max.X-dstBounds.Min.X)/geom.Width),
		float32((dstBounds.Max.Y-dstBounds.Min.Y)/geom.Height),
	)
	glimage.mvp.WriteAffine(&a)

	// Texture UV co-ordinates start out as:
	//
	//	(0,0) (1,0)
	//	(0,1) (1,1)
	//
	// These co-ordinates need to be scaled to texWidth/Height,
	// which may be less than 1 as the source image may not have
	// power-of-2 dimensions. Then it is scaled and translated
	// to represent the srcBounds rectangle of the source texture.
	//
	// The math is simpler here because in both co-ordinate spaces,
	// the top-left corner is (0, 0).
	a.Identity()
	a.Translate(
		&a,
		float32(srcBounds.Min.X)/float32(img.Rect.Dx()),
		float32(srcBounds.Min.Y)/float32(img.Rect.Dy()),
	)
	a.Scale(
		&a,
		float32(srcBounds.Dx())/float32(img.texWidth),
		float32(srcBounds.Dy())/float32(img.texHeight),
	)
	glimage.uvp.WriteAffine(&a)

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
	gl.Uniform1i(glimage.textureSample, 0)

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.square)
	gl.EnableVertexAttribArray(glimage.pos)
	gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.squareUV)
	gl.EnableVertexAttribArray(glimage.inUV)
	gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)

	gl.DrawArrays(gl.TRIANGLES, 0, 6)

	gl.DisableVertexAttribArray(glimage.pos)
	gl.DisableVertexAttribArray(glimage.inUV)
}

// Vertices of two triangles.
var squareCoords = toBytes([]float32{
	-1, -1,
	+1, -1,
	+1, +1,

	-1, -1,
	+1, +1,
	-1, +1,
})

var squareUVCoords = toBytes([]float32{
	0, 1,
	1, 1,
	1, 0,

	0, 1,
	1, 0,
	0, 0,
})

func toBytes(v []float32) []byte {
	buf := new(bytes.Buffer)
	if err := binary.Write(buf, binary.LittleEndian, v); err != nil {
		log.Fatal(err)
	}
	return buf.Bytes()
}
<commit_msg>go.mobile/gl/glutil: use a triangle strip instead of two triangles.<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package glutil

import (
	"bytes"
	"encoding/binary"
	"image"
	"log"
	"math"
	"sync"

	"code.google.com/p/go.mobile/f32"
	"code.google.com/p/go.mobile/geom"
	"code.google.com/p/go.mobile/gl"
)

var glimage struct {
	sync.Once
	quadXY        gl.Buffer
	quadUV        gl.Buffer
	program       gl.Program
	pos           gl.Attrib
	mvp           gl.Uniform
	uvp           gl.Uniform
	inUV          gl.Attrib
	textureSample gl.Uniform
}

func glInit() {
	var err error
	glimage.program, err = CreateProgram(vertexShader, fragmentShader)
	if err != nil {
		panic(err)
	}

	glimage.quadXY = gl.GenBuffer()
	glimage.quadUV = gl.GenBuffer()

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
	gl.BufferData(gl.ARRAY_BUFFER, gl.STATIC_DRAW, quadXYCoords)
	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
	gl.BufferData(gl.ARRAY_BUFFER, gl.STATIC_DRAW, quadUVCoords)

	glimage.pos = gl.GetAttribLocation(glimage.program, "pos")
	glimage.mvp = gl.GetUniformLocation(glimage.program, "mvp")
	glimage.uvp = gl.GetUniformLocation(glimage.program, "uvp")
	glimage.inUV = gl.GetAttribLocation(glimage.program, "inUV")
	glimage.textureSample = gl.GetUniformLocation(glimage.program, "textureSample")
}

// Image bridges between an *image.RGBA and an OpenGL texture.
//
// The contents of the embedded *image.RGBA can be uploaded as a
// texture and drawn as a 2D quad.
//
// The number of active Images must fit in the system's OpenGL texture
// limit. The typical use of an Image is as a texture atlas.
type Image struct {
	*image.RGBA

	Texture   gl.Texture
	texWidth  int
	texHeight int
}

// NewImage creates an Image of the given size.
//
// Both a host-memory *image.RGBA and a GL texture are created.
func NewImage(size geom.Point) *Image {
	realx := int(math.Ceil(float64(size.X.Px())))
	realy := int(math.Ceil(float64(size.Y.Px())))
	dx := roundToPower2(realx)
	dy := roundToPower2(realy)

	// TODO(crawshaw): Using VertexAttribPointer we can pass texture
	// data with a stride, which would let us use the exact number of
	// pixels on the host instead of the rounded up power 2 size.
	m := image.NewRGBA(image.Rect(0, 0, dx, dy))

	glimage.Do(glInit)

	img := &Image{
		RGBA:      m.SubImage(image.Rect(0, 0, realx, realy)).(*image.RGBA),
		Texture:   gl.GenTexture(),
		texWidth:  dx,
		texHeight: dy,
	}
	// TODO(crawshaw): We don't have the context on a finalizer. Find a way.
	// runtime.SetFinalizer(img, func(img *Image) { gl.DeleteTexture(img.Texture) })
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.TexImage2D(gl.TEXTURE_2D, 0, dx, dy, gl.RGBA, gl.UNSIGNED_BYTE, nil)

	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)

	return img
}

func roundToPower2(x int) int {
	x2 := 1
	for x2 < x {
		x2 *= 2
	}
	return x2
}

// Upload copies the host image data to the GL device.
func (img *Image) Upload() {
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.texWidth, img.texHeight, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix)
}

// Draw draws the image onto the current GL framebuffer.
func (img *Image) Draw(dstBounds geom.Rectangle, srcBounds image.Rectangle) {
	// TODO(crawshaw): Adjust viewport for the top bar on android?
	gl.UseProgram(glimage.program)

	// We are drawing a sub-image of dst, defined by dstBounds. Let ABCD
	// be the image, and PQRS be the sub-image. The two images may actually
	// be equal, but in the general case, PQRS can be smaller:
	//
	//	       M
	//	A +----+----------+ B
	//	  |               |
	//	N +  P +-----+ Q  |
	//	  |    |     |    |
	//	  |  S +-----+ R  |
	//	  |               |
	//	D +---------------+ C
	//
	// There are two co-ordinate spaces: geom space and framebuffer space.
	// In geom space, the ABCD rectangle is:
	//
	//	(0, 0)           (geom.Width, 0)
	//	(0, geom.Height) (geom.Width, geom.Height)
	//
	// and the PQRS rectangle is:
	//
	//	(dstBounds.Min.X, dstBounds.Min.Y) (dstBounds.Max.X, dstBounds.Min.Y)
	//	(dstBounds.Min.X, dstBounds.Max.Y) (dstBounds.Max.X, dstBounds.Max.Y)
	//
	// In framebuffer space, the ABCD rectangle is:
	//
	//	(-1, +1) (+1, +1)
	//	(-1, -1) (+1, -1)
	//
	// We need to solve for PQRS' co-ordinates in framebuffer space, and
	// calculate the MVP matrix that transforms the -1/+1 ABCD co-ordinates
	// to PQRS co-ordinates.
	//
	// To solve for PQRS, note that PQ / AB must match in both spaces. Call
	// this ratio fracX, and likewise for fracY.
	//
	//	[EQ1]   fracX = (dstBounds.Max.X - dstBounds.Min.X) / geom.Width
	//
	// Similarly, the AM / AB ratio must match:
	//
	//	(P.x - -1) / (1 - -1) = dstBounds.Min.X / geom.Width
	//
	// where the LHS is in framebuffer space and the RHS in geom space. This
	// equation is equivalent to:
	//
	//	[EQ2]   P.x = -1 + 2 * dstBounds.Min.X / geom.Width
	//
	// This MVP matrix is a scale followed by a translate. The scale is by
	// (fracX, fracY). After this, our corners have been transformed to:
	//
	//	(-fracX, +fracY) (+fracX, +fracY)
	//	(-fracX, -fracY) (+fracX, -fracY)
	//
	// so the translate is by (P.x + fracX) in the X direction, and
	// likewise for Y. Combining equations EQ1 and EQ2 simplifies the
	// translate to be:
	//
	//	-1 + (dstBounds.Max.X + dstBounds.Min.X) / geom.Width
	//	+1 - (dstBounds.Max.Y + dstBounds.Min.Y) / geom.Height
	var a f32.Affine
	a.Identity()
	a.Translate(
		&a,
		-1+float32((dstBounds.Max.X+dstBounds.Min.X)/geom.Width),
		+1-float32((dstBounds.Max.Y+dstBounds.Min.Y)/geom.Height),
	)
	a.Scale(
		&a,
		float32((dstBounds.Max.X-dstBounds.Min.X)/geom.Width),
		float32((dstBounds.Max.Y-dstBounds.Min.Y)/geom.Height),
	)
	glimage.mvp.WriteAffine(&a)

	// Texture UV co-ordinates start out as:
	//
	//	(0,0) (1,0)
	//	(0,1) (1,1)
	//
	// These co-ordinates need to be scaled to texWidth/Height,
	// which may be less than 1 as the source image may not have
	// power-of-2 dimensions. Then it is scaled and translated
	// to represent the srcBounds rectangle of the source texture.
	//
	// The math is simpler here because in both co-ordinate spaces,
	// the top-left corner is (0, 0).
	a.Identity()
	a.Translate(
		&a,
		float32(srcBounds.Min.X)/float32(img.Rect.Dx()),
		float32(srcBounds.Min.Y)/float32(img.Rect.Dy()),
	)
	a.Scale(
		&a,
		float32(srcBounds.Dx())/float32(img.texWidth),
		float32(srcBounds.Dy())/float32(img.texHeight),
	)
	glimage.uvp.WriteAffine(&a)

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, img.Texture)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
	gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
	gl.Uniform1i(glimage.textureSample, 0)

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
	gl.EnableVertexAttribArray(glimage.pos)
	gl.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)

	gl.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
	gl.EnableVertexAttribArray(glimage.inUV)
	gl.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)

	gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)

	gl.DisableVertexAttribArray(glimage.pos)
	gl.DisableVertexAttribArray(glimage.inUV)
}

var quadXYCoords = toBytes([]float32{
	-1, -1, // bottom left
	+1, -1, // bottom right
	-1, +1, // top left
	+1, +1, // top right
})

var quadUVCoords = toBytes([]float32{
	0, 1, // bottom left
	1, 1, // bottom right
	0, 0, // top left
	1, 0, // top right
})

func toBytes(v []float32) []byte {
	buf := new(bytes.Buffer)
	if err := binary.Write(buf, binary.LittleEndian, v); err != nil {
		log.Fatal(err)
	}
	return buf.Bytes()
}
<|endoftext|>


<commit_before>package engi

import (
	"code.google.com/p/freetype-go/freetype"
	"code.google.com/p/freetype-go/freetype/truetype"
	"image"
	"image/draw"
	"io/ioutil"
	"log"
)

var (
	dpi = float64(72)
	fg  = image.Black
	bg  = image.Transparent
)

// TODO FG and BG color config
type Font struct {
	Url  string
	Size float64
	ttf  *truetype.Font
}

func (f *Font) Create() {
	url := f.Url

	// Read and parse the font
	ttfBytes, err := ioutil.ReadFile(url)
	if err != nil {
		log.Println(err)
		return
	}

	ttf, err := freetype.ParseFont(ttfBytes)
	if err != nil {
		log.Println(err)
		return
	}
	f.ttf = ttf

	return
}

func (f *Font) TextDimensions(text string) (int, int, int) {
	font := f.ttf
	size := f.Size
	var (
		totalWidth  = int32(0)
		totalHeight = int32(size)
		maxYBearing = int32(0)
	)
	fupe := font.FUnitsPerEm()
	for _, char := range text {
		idx := font.Index(char)
		hm := font.HMetric(fupe, idx)
		vm := font.VMetric(fupe, idx)
		g := truetype.NewGlyphBuf()
		err := g.Load(font, fupe, idx, truetype.NoHinting)
		if err != nil {
			log.Println(err)
			return 0, 0, 0
		}
		totalWidth += hm.AdvanceWidth
		yB := (vm.TopSideBearing * int32(size)) / fupe
		if yB > maxYBearing {
			maxYBearing = yB
		}
	}

	// Scale to actual pixel size
	totalWidth *= int32(size)
	totalWidth /= fupe

	return int(totalWidth), int(totalHeight), int(maxYBearing)
}

func (f *Font) Render(text string) *Texture {
	width, height, yBearing := f.TextDimensions(text)
	font := f.ttf
	size := f.Size
	// Create the font context
	c := freetype.NewContext()

	nrgba := image.NewNRGBA(image.Rect(0, 0, width, height))
	draw.Draw(nrgba, nrgba.Bounds(), bg, image.ZP, draw.Src)

	c.SetDPI(dpi)
	c.SetFont(font)
	c.SetFontSize(size)
	c.SetClip(nrgba.Bounds())
	c.SetDst(nrgba)
	c.SetSrc(fg)

	// Draw the text.
	pt := freetype.Pt(0, int(yBearing))
	_, err := c.DrawString(text, pt)
	if err != nil {
		log.Println(err)
		return nil
	}

	// Create texture
	imObj := &ImageObject{nrgba}
	return NewTexture(imObj)

}
<commit_msg>font: fix git issues/ added font color control<commit_after>package engi

import (
	"code.google.com/p/freetype-go/freetype"
	"code.google.com/p/freetype-go/freetype/truetype"
	"image"
	"image/color"
	"image/draw"
	"io/ioutil"
	"log"
)

var (
	dpi = float64(72)
)

type Color struct {
	R, G, B, A uint8
}

// TODO FG and BG color config
type Font struct {
	URL  string
	Size float64
	BG   Color
	FG   Color
	ttf  *truetype.Font
}

func (f *Font) Create() {
	url := f.URL

	// Read and parse the font
	ttfBytes, err := ioutil.ReadFile(url)
	if err != nil {
		log.Println(err)
		return
	}

	ttf, err := freetype.ParseFont(ttfBytes)
	if err != nil {
		log.Println(err)
		return
	}
	f.ttf = ttf
}

func (f *Font) TextDimensions(text string) (int, int, int) {
	font := f.ttf
	size := f.Size
	var (
		totalWidth  = int32(0)
		totalHeight = int32(size)
		maxYBearing = int32(0)
	)
	fupe := font.FUnitsPerEm()
	for _, char := range text {
		idx := font.Index(char)
		hm := font.HMetric(fupe, idx)
		vm := font.VMetric(fupe, idx)
		g := truetype.NewGlyphBuf()
		err := g.Load(font, fupe, idx, truetype.NoHinting)
		if err != nil {
			log.Println(err)
			return 0, 0, 0
		}
		totalWidth += hm.AdvanceWidth
		yB := (vm.TopSideBearing * int32(size)) / fupe
		if yB > maxYBearing {
			maxYBearing = yB
		}
	}

	// Scale to actual pixel size
	totalWidth *= int32(size)
	totalWidth /= fupe

	return int(totalWidth), int(totalHeight), int(maxYBearing)
}

func (f *Font) Render(text string) *Texture {
	width, height, yBearing := f.TextDimensions(text)
	font := f.ttf
	size := f.Size

	// Colors
	fg := image.NewUniform(color.NRGBA{f.FG.R, f.FG.G, f.FG.B, f.FG.A})
	bg := image.NewUniform(color.NRGBA{f.BG.R, f.BG.G, f.BG.B, f.BG.A})

	// Create the font context
	c := freetype.NewContext()

	nrgba := image.NewNRGBA(image.Rect(0, 0, width, height))
	draw.Draw(nrgba, nrgba.Bounds(), bg, image.ZP, draw.Src)

	c.SetDPI(dpi)
	c.SetFont(font)
	c.SetFontSize(size)
	c.SetClip(nrgba.Bounds())
	c.SetDst(nrgba)
	c.SetSrc(fg)

	// Draw the text.
	pt := freetype.Pt(0, int(yBearing))
	_, err := c.DrawString(text, pt)
	if err != nil {
		log.Println(err)
		return nil
	}

	// Create texture
	imObj := &ImageObject{nrgba}
	return NewTexture(imObj)

}
<|endoftext|>


<commit_before>package main

//import (
//	"github.com/blablacar/dgr/utils"
//	"github.com/n0rad/go-erlog/data"
//	"github.com/n0rad/go-erlog/logs"
//	"io/ioutil"
//	"os"
//	"path/filepath"
//)
//
//func discoverAndRunInitType(path string) {
//	fields := data.WithField("path", path)
//	if _, err := os.Stat(path); err != nil {
//		if err := os.MkdirAll(path, 0755); err != nil {
//			logs.WithEF(err, fields).Fatal("Cannot create path directory")
//		}
//	}
//
//	empty, err := utils.IsDirEmpty(path)
//	if err != nil {
//		logs.WithEF(err, fields).Fatal("Cannot read path directory")
//	}
//	if !initForce {
//		if !empty {
//			logs.WithEF(err, fields).Fatal("Path is not empty cannot init")
//		}
//	}
//
//	logs.WithEF(err, fields).Info("Init project")
//
//	files := make(map[string]string)
//
//	files[PATH_RUNLEVELS+PATH_PRESTART_EARLY+"/10.prestart-early.sh"] = `#!/dgr/bin/busybox sh
//source /dgr/bin/functions.sh
//isLevelEnabled "debug" && set -x
//
//echo "I'm a prestart early script that is run before templating"
//`
//	files[PATH_RUNLEVELS+PATH_PRESTART_LATE+"/10.prestart-late.sh"] = `#!/dgr/bin/busybox sh
//source /dgr/bin/functions.sh
//isLevelEnabled "debug" && set -x
//
//echo "I'm a prestart late script that is run after templating"
//`
//	files[PATH_RUNLEVELS+PATH_BUILD+"/10.install.sh"] = `#!/dgr/bin/busybox sh
//source /dgr/bin/functions.sh
//isLevelEnabled "debug" && set -x
//
//echo "I'm a build script that is run to install applications"
//`
//	files[PATH_RUNLEVELS+PATH_BUILD_SETUP+"/10.setup.sh"] = `#!/bin/sh
//source ${TARGET}/rootfs/dgr/bin/functions.sh
//isLevelEnabled "debug" && set -x
//
//echo "I'm build setup script file that is run from $BASEDIR to prepare $TARGET/rootfs before running build scripts"
//`
//	files[PATH_RUNLEVELS+PATH_BUILD_LATE+"/10.setup.sh"] = `#!/dgr/bin/busybox sh
//source /dgr/bin/functions.sh
//isLevelEnabled "debug" && set -x
//
//echo "I'm a build late script that is run to install applications after the copy of files,template,etc..."
//`
//	files[PATH_RUNLEVELS+PATH_INHERIT_BUILD_EARLY+"/10.inherit-build-early.sh"] = `#!/dgr/bin/busybox sh
//source /dgr/bin/functions.sh
//isLevelEnabled "debug" && set -x
//
//echo "I'm a inherit build early script that is run on this image and all images that have me as From during build"
//`
//	files[PATH_RUNLEVELS+PATH_INHERIT_BUILD_LATE+"/10.inherit-build-early.sh"] = `#!/dgr/bin/busybox sh
//source /dgr/bin/functions.sh
//isLevelEnabled "debug" && set -x
//
//echo "I'm a inherit build late script that is run on this image and all images that have me as From during build"
//`
//	files[PATH_FILES+"/etc/dummy"] = `Dummy file
//`
//	files[PATH_ATTRIBUTES+"/attributes.yml"] = `default:
//  dummy: world
//`
//	files[PATH_TEMPLATES+"/etc/templated.tmpl.cfg"] = `---
//uid: 0
//gid: 0
//mode: 0644
//checkCmd: true
//`
//	files[PATH_TEMPLATES+"/etc/templated.tmpl"] = `Hello {{ .dummy }}
//`
//	files[PATH_TEMPLATES+"/header.partial"] = `{{define "header"}}
//world
//{{end}}
//`
//	files[".gitignore"] = `target/
//`
//	files["aci-manifest.yml"] = `from: ""
//name: aci.example.com/aci-dummy:1
//aci:
//  app:
//    exec: [ "/dgr/bin/busybox", "sh" ]
//`
//	files[PATH_TESTS+"/dummy.bats"] = `#!/dgr/bin/bats
//
//@test "Prestart should template" {
//  result="$(cat /etc/templated)"
//  [ "$result" == "Hello world" ]
//}
//
//@test "dgr should copy files" {
//  result="$(cat /etc/dummy)"
//  [ "$result" == "Dummy file" ]
//}
//`
//	files[PATH_TESTS+"/wait.sh"] = `exit 0`
//
//	for filePath, data := range files {
//		fpath := path + "/" + filePath
//		os.MkdirAll(filepath.Dir(fpath), 0777)
//		ioutil.WriteFile(fpath, []byte(data), 0777)
//	}
//}
<commit_msg>remove commented code for init<commit_after><|endoftext|>


<commit_before>package stellar

import (
	"fmt"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/stellar1"
)

type chatMsg struct {
	convID chat1.ConversationID
	msgID  chat1.MessageID
	sender libkb.NormalizedUsername
}

type Loader struct {
	libkb.Contextified

	payments  map[stellar1.PaymentID]*stellar1.PaymentLocal
	pmessages map[stellar1.PaymentID]chatMsg
	pqueue    chan stellar1.PaymentID

	requests  map[stellar1.KeybaseRequestID]*stellar1.RequestDetailsLocal
	rmessages map[stellar1.KeybaseRequestID]chatMsg
	rqueue    chan stellar1.KeybaseRequestID

	shutdownOnce sync.Once
	done         bool

	sync.Mutex
}

var defaultLoader *Loader
var defaultLock sync.Mutex

func NewLoader(g *libkb.GlobalContext) *Loader {
	p := &Loader{
		Contextified: libkb.NewContextified(g),
		payments:     make(map[stellar1.PaymentID]*stellar1.PaymentLocal),
		pmessages:    make(map[stellar1.PaymentID]chatMsg),
		pqueue:       make(chan stellar1.PaymentID, 100),
		requests:     make(map[stellar1.KeybaseRequestID]*stellar1.RequestDetailsLocal),
		rmessages:    make(map[stellar1.KeybaseRequestID]chatMsg),
		rqueue:       make(chan stellar1.KeybaseRequestID, 100),
	}

	go p.runPayments()
	go p.runRequests()

	return p
}

func DefaultLoader(g *libkb.GlobalContext) *Loader {
	defaultLock.Lock()
	defer defaultLock.Unlock()

	if defaultLoader == nil {
		defaultLoader = NewLoader(g)
		g.PushShutdownHook(func() error {
			defaultLock.Lock()
			err := defaultLoader.Shutdown()
			defaultLoader = nil
			defaultLock.Unlock()
			return err
		})
	}

	return defaultLoader
}

func (p *Loader) LoadPayment(ctx context.Context, convID chat1.ConversationID, msgID chat1.MessageID, senderUsername string, paymentID stellar1.PaymentID) *chat1.UIPaymentInfo {
	defer libkb.CTrace(ctx, p.G().GetLog(), fmt.Sprintf("Loader.LoadPayment(cid=%s,mid=%s,pid=%s)", convID, msgID, paymentID), func() error { return nil })()

	p.Lock()
	defer p.Unlock()

	m := libkb.NewMetaContext(ctx, p.G())

	if p.done {
		m.CDebugf("loader shutdown, not loading payment %s", paymentID)
		return nil
	}

	if len(paymentID) == 0 {
		m.CDebugf("LoadPayment called with empty paymentID for %s/%s", convID, msgID)
		return nil
	}

	msg, ok := p.pmessages[paymentID]
	// store the msg info if necessary
	if !ok {
		msg = chatMsg{
			convID: convID,
			msgID:  msgID,
			sender: libkb.NewNormalizedUsername(senderUsername),
		}
		p.pmessages[paymentID] = msg
	} else if !msg.convID.Eq(convID) || msg.msgID != msgID {
		m.CWarningf("existing payment message info does not match load info: (%v, %v) != (%v, %v)", msg.convID, msg.msgID, convID, msgID)
	}

	payment, ok := p.payments[paymentID]
	if ok {
		info := p.uiPaymentInfo(m, payment, msg)
		p.G().NotifyRouter.HandleChatPaymentInfo(m.Ctx(), p.G().ActiveDevice.UID(), convID, msgID, *info)
		if info.Status != stellar1.PaymentStatus_COMPLETED {
			// to be safe, schedule a reload of the payment in case it has
			// changed since stored
			p.enqueuePayment(paymentID)
		}
		return info
	}

	// not found, need to load payment in background
	p.enqueuePayment(paymentID)

	return nil
}

func (p *Loader) LoadRequest(ctx context.Context, convID chat1.ConversationID, msgID chat1.MessageID, senderUsername string, requestID stellar1.KeybaseRequestID) *chat1.UIRequestInfo {
	defer libkb.CTrace(ctx, p.G().GetLog(), fmt.Sprintf("Loader.LoadRequest(cid=%s,mid=%s,rid=%s)", convID, msgID, requestID), func() error { return nil })()

	p.Lock()
	defer p.Unlock()

	m := libkb.NewMetaContext(ctx, p.G())

	if p.done {
		m.CDebugf("loader shutdown, not loading request %s", requestID)
		return nil
	}

	msg, ok := p.rmessages[requestID]
	// store the msg info if necessary
	if !ok {
		msg = chatMsg{
			convID: convID,
			msgID:  msgID,
			sender: libkb.NewNormalizedUsername(senderUsername),
		}
		p.rmessages[requestID] = msg
	} else if !msg.convID.Eq(convID) || msg.msgID != msgID {
		m.CWarningf("existing request message info does not match load info: (%v, %v) != (%v, %v)", msg.convID, msg.msgID, convID, msgID)
	}

	request, ok := p.requests[requestID]
	var info *chat1.UIRequestInfo
	if ok {
		info = p.uiRequestInfo(m, request, msg)
	}

	// always load request in background (even if found) to make sure stored value is up-to-date.
	p.enqueueRequest(requestID)

	return info
}

// UpdatePayment schedules a load of paymentID. Gregor status notification handlers
// should call this to update the payment data.
func (p *Loader) UpdatePayment(ctx context.Context, paymentID stellar1.PaymentID) {
	if p.done {
		return
	}

	p.enqueuePayment(paymentID)
}

// UpdateRequest schedules a load for requestID. Gregor status notification handlers
// should call this to update the request data.
func (p *Loader) UpdateRequest(ctx context.Context, requestID stellar1.KeybaseRequestID) {
	if p.done {
		return
	}

	p.enqueueRequest(requestID)
}

func (p *Loader) Shutdown() error {
	p.shutdownOnce.Do(func() {
		p.Lock()
		p.G().GetLog().Debug("shutting down stellar loader")
		p.done = true
		close(p.pqueue)
		close(p.rqueue)
		p.Unlock()
	})
	return nil
}

func (p *Loader) runPayments() {
	for id := range p.pqueue {
		p.loadPayment(id)
	}
}

func (p *Loader) runRequests() {
	for id := range p.rqueue {
		p.loadRequest(id)
	}
}

func (p *Loader) loadPayment(id stellar1.PaymentID) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	mctx := libkb.NewMetaContext(ctx, p.G())
	defer mctx.CTraceTimed(fmt.Sprintf("loadPayment(%s)", id), func() error { return nil })()

	s := getGlobal(p.G())
	details, err := s.remoter.PaymentDetails(ctx, stellar1.TransactionIDFromPaymentID(id).String())
	if err != nil {
		mctx.CDebugf("error getting payment details for %s: %s", id, err)
		return
	}

	oc := NewOwnAccountLookupCache(mctx)
	summary, err := TransformPaymentSummaryGeneric(mctx, details.Summary, oc)
	if err != nil {
		mctx.CDebugf("error transforming details for %s: %s", id, err)
		return
	}

	p.Lock()
	p.payments[id] = summary
	p.Unlock()

	p.sendPaymentNotification(mctx, id, summary)
}

func (p *Loader) loadRequest(id stellar1.KeybaseRequestID) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	m := libkb.NewMetaContext(ctx, p.G())
	defer m.CTraceTimed(fmt.Sprintf("loadRequest(%s)", id), func() error { return nil })()

	s := getGlobal(p.G())
	details, err := s.remoter.RequestDetails(ctx, id)
	if err != nil {
		m.CDebugf("error getting request details for %s: %s", id, err)
		return
	}
	local, err := TransformRequestDetails(m, details)
	if err != nil {
		m.CDebugf("error transforming request details for %s: %s", id, err)
		return
	}

	isUpdate := false
	p.Lock()
	existing, ok := p.requests[id]
	if !ok || local.Status != existing.Status {
		// must be a newly loaded request or the status changed for
		// a notification to be sent below
		isUpdate = true
	}
	p.requests[id] = local
	p.Unlock()

	if isUpdate {
		p.sendRequestNotification(m, id, local)
	}
}

func (p *Loader) uiPaymentInfo(m libkb.MetaContext, summary *stellar1.PaymentLocal, msg chatMsg) *chat1.UIPaymentInfo {
	info := chat1.UIPaymentInfo{
		AccountID:         &summary.FromAccountID,
		AmountDescription: summary.AmountDescription,
		Worth:             summary.Worth,
		Delta:             summary.Delta,
		Note:              summary.Note,
		PaymentID:         summary.Id,
		Status:            summary.StatusSimplified,
		StatusDescription: summary.StatusDescription,
		ShowCancel:        summary.ShowCancel,
	}

	info.Delta = stellar1.BalanceDelta_NONE

	// Calculate the payment delta & relevant accountID
	if summary.FromType == stellar1.ParticipantType_OWNACCOUNT && summary.ToType == stellar1.ParticipantType_OWNACCOUNT {
		// This is a transfer between the user's own accounts.
		info.Delta = stellar1.BalanceDelta_NONE
	} else {
		info.Delta = stellar1.BalanceDelta_INCREASE
		if msg.sender.Eq(p.G().ActiveDevice.Username(m)) {
			info.Delta = stellar1.BalanceDelta_DECREASE
		} else {
			info.AccountID = summary.ToAccountID
		}
	}

	return &info
}

func (p *Loader) sendPaymentNotification(m libkb.MetaContext, id stellar1.PaymentID, summary *stellar1.PaymentLocal) {
	p.Lock()
	msg, ok := p.pmessages[id]
	p.Unlock()

	if !ok {
		m.CDebugf("not sending payment chat notification for %s (no associated convID, msgID)", id)
		return
	}

	m.CDebugf("sending chat notification for payment %s to %s, %s", id, msg.convID, msg.msgID)
	uid := p.G().ActiveDevice.UID()
	info := p.uiPaymentInfo(m, summary, msg)
	p.G().NotifyRouter.HandleChatPaymentInfo(m.Ctx(), uid, msg.convID, msg.msgID, *info)
}

func (p *Loader) uiRequestInfo(m libkb.MetaContext, details *stellar1.RequestDetailsLocal, msg chatMsg) *chat1.UIRequestInfo {
	info := chat1.UIRequestInfo{
		Amount:            details.Amount,
		AmountDescription: details.AmountDescription,
		Asset:             details.Asset,
		Currency:          details.Currency,
		Status:            details.Status,
	}

	return &info
}

func (p *Loader) sendRequestNotification(m libkb.MetaContext, id stellar1.KeybaseRequestID, details *stellar1.RequestDetailsLocal) {
	p.Lock()
	msg, ok := p.rmessages[id]
	p.Unlock()

	if !ok {
		m.CDebugf("not sending request chat notification for %s (no associated convID, msgID)", id)
		return
	}

	m.CDebugf("sending chat notification for request %s to %s, %s", id, msg.convID, msg.msgID)
	uid := p.G().ActiveDevice.UID()
	info := p.uiRequestInfo(m, details, msg)
	p.G().NotifyRouter.HandleChatRequestInfo(m.Ctx(), uid, msg.convID, msg.msgID, *info)
}

func (p *Loader) enqueuePayment(paymentID stellar1.PaymentID) {
	select {
	case p.pqueue <- paymentID:
	default:
		p.G().Log.Debug("stellar.Loader payment queue full")
	}
}

func (p *Loader) enqueueRequest(requestID stellar1.KeybaseRequestID) {
	select {
	case p.rqueue <- requestID:
	default:
		p.G().Log.Debug("stellar.Loader request queue full")
	}
}
<commit_msg>Don't require a message to send notification (#15102)<commit_after>package stellar

import (
	"fmt"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/stellar1"
)

type chatMsg struct {
	convID chat1.ConversationID
	msgID  chat1.MessageID
	sender libkb.NormalizedUsername
}

type Loader struct {
	libkb.Contextified

	payments  map[stellar1.PaymentID]*stellar1.PaymentLocal
	pmessages map[stellar1.PaymentID]chatMsg
	pqueue    chan stellar1.PaymentID

	requests  map[stellar1.KeybaseRequestID]*stellar1.RequestDetailsLocal
	rmessages map[stellar1.KeybaseRequestID]chatMsg
	rqueue    chan stellar1.KeybaseRequestID

	shutdownOnce sync.Once
	done         bool

	sync.Mutex
}

var defaultLoader *Loader
var defaultLock sync.Mutex

func NewLoader(g *libkb.GlobalContext) *Loader {
	p := &Loader{
		Contextified: libkb.NewContextified(g),
		payments:     make(map[stellar1.PaymentID]*stellar1.PaymentLocal),
		pmessages:    make(map[stellar1.PaymentID]chatMsg),
		pqueue:       make(chan stellar1.PaymentID, 100),
		requests:     make(map[stellar1.KeybaseRequestID]*stellar1.RequestDetailsLocal),
		rmessages:    make(map[stellar1.KeybaseRequestID]chatMsg),
		rqueue:       make(chan stellar1.KeybaseRequestID, 100),
	}

	go p.runPayments()
	go p.runRequests()

	return p
}

func DefaultLoader(g *libkb.GlobalContext) *Loader {
	defaultLock.Lock()
	defer defaultLock.Unlock()

	if defaultLoader == nil {
		defaultLoader = NewLoader(g)
		g.PushShutdownHook(func() error {
			defaultLock.Lock()
			err := defaultLoader.Shutdown()
			defaultLoader = nil
			defaultLock.Unlock()
			return err
		})
	}

	return defaultLoader
}

func (p *Loader) LoadPayment(ctx context.Context, convID chat1.ConversationID, msgID chat1.MessageID, senderUsername string, paymentID stellar1.PaymentID) *chat1.UIPaymentInfo {
	defer libkb.CTrace(ctx, p.G().GetLog(), fmt.Sprintf("Loader.LoadPayment(cid=%s,mid=%s,pid=%s)", convID, msgID, paymentID), func() error { return nil })()

	p.Lock()
	defer p.Unlock()

	m := libkb.NewMetaContext(ctx, p.G())

	if p.done {
		m.CDebugf("loader shutdown, not loading payment %s", paymentID)
		return nil
	}

	if len(paymentID) == 0 {
		m.CDebugf("LoadPayment called with empty paymentID for %s/%s", convID, msgID)
		return nil
	}

	msg, ok := p.pmessages[paymentID]
	// store the msg info if necessary
	if !ok {
		msg = chatMsg{
			convID: convID,
			msgID:  msgID,
			sender: libkb.NewNormalizedUsername(senderUsername),
		}
		p.pmessages[paymentID] = msg
	} else if !msg.convID.Eq(convID) || msg.msgID != msgID {
		m.CWarningf("existing payment message info does not match load info: (%v, %v) != (%v, %v)", msg.convID, msg.msgID, convID, msgID)
	}

	payment, ok := p.payments[paymentID]
	if ok {
		info := p.uiPaymentInfo(m, payment, msg)
		p.G().NotifyRouter.HandleChatPaymentInfo(m.Ctx(), p.G().ActiveDevice.UID(), convID, msgID, *info)
		if info.Status != stellar1.PaymentStatus_COMPLETED {
			// to be safe, schedule a reload of the payment in case it has
			// changed since stored
			p.enqueuePayment(paymentID)
		}
		return info
	}

	// not found, need to load payment in background
	p.enqueuePayment(paymentID)

	return nil
}

func (p *Loader) LoadRequest(ctx context.Context, convID chat1.ConversationID, msgID chat1.MessageID, senderUsername string, requestID stellar1.KeybaseRequestID) *chat1.UIRequestInfo {
	defer libkb.CTrace(ctx, p.G().GetLog(), fmt.Sprintf("Loader.LoadRequest(cid=%s,mid=%s,rid=%s)", convID, msgID, requestID), func() error { return nil })()

	p.Lock()
	defer p.Unlock()

	m := libkb.NewMetaContext(ctx, p.G())

	if p.done {
		m.CDebugf("loader shutdown, not loading request %s", requestID)
		return nil
	}

	msg, ok := p.rmessages[requestID]
	// store the msg info if necessary
	if !ok {
		msg = chatMsg{
			convID: convID,
			msgID:  msgID,
			sender: libkb.NewNormalizedUsername(senderUsername),
		}
		p.rmessages[requestID] = msg
	} else if !msg.convID.Eq(convID) || msg.msgID != msgID {
		m.CWarningf("existing request message info does not match load info: (%v, %v) != (%v, %v)", msg.convID, msg.msgID, convID, msgID)
	}

	request, ok := p.requests[requestID]
	var info *chat1.UIRequestInfo
	if ok {
		info = p.uiRequestInfo(m, request, msg)
	}

	// always load request in background (even if found) to make sure stored value is up-to-date.
	p.enqueueRequest(requestID)

	return info
}

// UpdatePayment schedules a load of paymentID. Gregor status notification handlers
// should call this to update the payment data.
func (p *Loader) UpdatePayment(ctx context.Context, paymentID stellar1.PaymentID) {
	if p.done {
		return
	}

	p.enqueuePayment(paymentID)
}

// UpdateRequest schedules a load for requestID. Gregor status notification handlers
// should call this to update the request data.
func (p *Loader) UpdateRequest(ctx context.Context, requestID stellar1.KeybaseRequestID) {
	if p.done {
		return
	}

	p.enqueueRequest(requestID)
}

func (p *Loader) Shutdown() error {
	p.shutdownOnce.Do(func() {
		p.Lock()
		p.G().GetLog().Debug("shutting down stellar loader")
		p.done = true
		close(p.pqueue)
		close(p.rqueue)
		p.Unlock()
	})
	return nil
}

func (p *Loader) runPayments() {
	for id := range p.pqueue {
		p.loadPayment(id)
	}
}

func (p *Loader) runRequests() {
	for id := range p.rqueue {
		p.loadRequest(id)
	}
}

func (p *Loader) loadPayment(id stellar1.PaymentID) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	mctx := libkb.NewMetaContext(ctx, p.G())
	defer mctx.CTraceTimed(fmt.Sprintf("loadPayment(%s)", id), func() error { return nil })()

	s := getGlobal(p.G())
	details, err := s.remoter.PaymentDetails(ctx, stellar1.TransactionIDFromPaymentID(id).String())
	if err != nil {
		mctx.CDebugf("error getting payment details for %s: %s", id, err)
		return
	}

	oc := NewOwnAccountLookupCache(mctx)
	summary, err := TransformPaymentSummaryGeneric(mctx, details.Summary, oc)
	if err != nil {
		mctx.CDebugf("error transforming details for %s: %s", id, err)
		return
	}

	p.Lock()
	p.payments[id] = summary
	p.Unlock()

	p.sendPaymentNotification(mctx, id, summary)
}

func (p *Loader) loadRequest(id stellar1.KeybaseRequestID) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	m := libkb.NewMetaContext(ctx, p.G())
	defer m.CTraceTimed(fmt.Sprintf("loadRequest(%s)", id), func() error { return nil })()

	s := getGlobal(p.G())
	details, err := s.remoter.RequestDetails(ctx, id)
	if err != nil {
		m.CDebugf("error getting request details for %s: %s", id, err)
		return
	}
	local, err := TransformRequestDetails(m, details)
	if err != nil {
		m.CDebugf("error transforming request details for %s: %s", id, err)
		return
	}

	isUpdate := false
	p.Lock()
	existing, ok := p.requests[id]
	if !ok || local.Status != existing.Status {
		// must be a newly loaded request or the status changed for
		// a notification to be sent below
		isUpdate = true
	}
	p.requests[id] = local
	p.Unlock()

	if isUpdate {
		p.sendRequestNotification(m, id, local)
	}
}

func (p *Loader) uiPaymentInfo(m libkb.MetaContext, summary *stellar1.PaymentLocal, msg chatMsg) *chat1.UIPaymentInfo {
	info := chat1.UIPaymentInfo{
		AccountID:         &summary.FromAccountID,
		AmountDescription: summary.AmountDescription,
		Worth:             summary.Worth,
		Delta:             summary.Delta,
		Note:              summary.Note,
		PaymentID:         summary.Id,
		Status:            summary.StatusSimplified,
		StatusDescription: summary.StatusDescription,
		ShowCancel:        summary.ShowCancel,
	}

	info.Delta = stellar1.BalanceDelta_NONE

	// Calculate the payment delta & relevant accountID
	if summary.FromType == stellar1.ParticipantType_OWNACCOUNT && summary.ToType == stellar1.ParticipantType_OWNACCOUNT {
		// This is a transfer between the user's own accounts.
		info.Delta = stellar1.BalanceDelta_NONE
	} else {
		info.Delta = stellar1.BalanceDelta_INCREASE
		if msg.sender.Eq(p.G().ActiveDevice.Username(m)) {
			info.Delta = stellar1.BalanceDelta_DECREASE
		} else {
			info.AccountID = summary.ToAccountID
		}
	}

	return &info
}

func (p *Loader) sendPaymentNotification(m libkb.MetaContext, id stellar1.PaymentID, summary *stellar1.PaymentLocal) {
	p.Lock()
	msg, ok := p.pmessages[id]
	p.Unlock()

	if !ok {
		// this is ok: frontend only needs the payment ID
		m.CDebugf("sending chat notification for payment %s using empty msg info", id)
		msg = chatMsg{}
	} else {
		m.CDebugf("sending chat notification for payment %s to %s, %s", id, msg.convID, msg.msgID)
	}
	uid := p.G().ActiveDevice.UID()
	info := p.uiPaymentInfo(m, summary, msg)
	p.G().NotifyRouter.HandleChatPaymentInfo(m.Ctx(), uid, msg.convID, msg.msgID, *info)
}

func (p *Loader) uiRequestInfo(m libkb.MetaContext, details *stellar1.RequestDetailsLocal, msg chatMsg) *chat1.UIRequestInfo {
	info := chat1.UIRequestInfo{
		Amount:            details.Amount,
		AmountDescription: details.AmountDescription,
		Asset:             details.Asset,
		Currency:          details.Currency,
		Status:            details.Status,
	}

	return &info
}

func (p *Loader) sendRequestNotification(m libkb.MetaContext, id stellar1.KeybaseRequestID, details *stellar1.RequestDetailsLocal) {
	p.Lock()
	msg, ok := p.rmessages[id]
	p.Unlock()

	if !ok {
		m.CDebugf("not sending request chat notification for %s (no associated convID, msgID)", id)
		return
	}

	m.CDebugf("sending chat notification for request %s to %s, %s", id, msg.convID, msg.msgID)
	uid := p.G().ActiveDevice.UID()
	info := p.uiRequestInfo(m, details, msg)
	p.G().NotifyRouter.HandleChatRequestInfo(m.Ctx(), uid, msg.convID, msg.msgID, *info)
}

func (p *Loader) enqueuePayment(paymentID stellar1.PaymentID) {
	select {
	case p.pqueue <- paymentID:
	default:
		p.G().Log.Debug("stellar.Loader payment queue full")
	}
}

func (p *Loader) enqueueRequest(requestID stellar1.KeybaseRequestID) {
	select {
	case p.rqueue <- requestID:
	default:
		p.G().Log.Debug("stellar.Loader request queue full")
	}
}
<|endoftext|>


<commit_before>package builtinplugins

import (
	"github.com/hashicorp/vault/sdk/database/helper/credsutil"
	"github.com/hashicorp/vault/sdk/helper/consts"
	"github.com/hashicorp/vault/sdk/logical"

	credAliCloud "github.com/hashicorp/vault-plugin-auth-alicloud"
	credAzure "github.com/hashicorp/vault-plugin-auth-azure"
	credCentrify "github.com/hashicorp/vault-plugin-auth-centrify"
	credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin"
	credJWT "github.com/hashicorp/vault-plugin-auth-jwt"
	credKube "github.com/hashicorp/vault-plugin-auth-kubernetes"
	credPCF "github.com/hashicorp/vault-plugin-auth-pcf"
	credAppId "github.com/hashicorp/vault/builtin/credential/app-id"
	credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
	credAws "github.com/hashicorp/vault/builtin/credential/aws"
	credCert "github.com/hashicorp/vault/builtin/credential/cert"
	credGitHub "github.com/hashicorp/vault/builtin/credential/github"
	credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
	credOkta "github.com/hashicorp/vault/builtin/credential/okta"
	credRadius "github.com/hashicorp/vault/builtin/credential/radius"
	credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"

	dbElastic "github.com/hashicorp/vault-plugin-database-elasticsearch"
	dbCass "github.com/hashicorp/vault/plugins/database/cassandra"
	dbHana "github.com/hashicorp/vault/plugins/database/hana"
	dbInflux "github.com/hashicorp/vault/plugins/database/influxdb"
	dbMongo "github.com/hashicorp/vault/plugins/database/mongodb"
	dbMssql "github.com/hashicorp/vault/plugins/database/mssql"
	dbMysql "github.com/hashicorp/vault/plugins/database/mysql"
	dbPostgres "github.com/hashicorp/vault/plugins/database/postgresql"

	logicalAd "github.com/hashicorp/vault-plugin-secrets-ad/plugin"
	logicalAlicloud "github.com/hashicorp/vault-plugin-secrets-alicloud"
	logicalAzure "github.com/hashicorp/vault-plugin-secrets-azure"
	logicalGcp "github.com/hashicorp/vault-plugin-secrets-gcp/plugin"
	logicalGcpKms "github.com/hashicorp/vault-plugin-secrets-gcpkms"
	logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
	logicalAws "github.com/hashicorp/vault/builtin/logical/aws"
	logicalCass "github.com/hashicorp/vault/builtin/logical/cassandra"
	logicalConsul "github.com/hashicorp/vault/builtin/logical/consul"
	logicalMongo "github.com/hashicorp/vault/builtin/logical/mongodb"
	logicalMssql "github.com/hashicorp/vault/builtin/logical/mssql"
	logicalMysql "github.com/hashicorp/vault/builtin/logical/mysql"
	logicalNomad "github.com/hashicorp/vault/builtin/logical/nomad"
	logicalPki "github.com/hashicorp/vault/builtin/logical/pki"
	logicalPostgres "github.com/hashicorp/vault/builtin/logical/postgresql"
	logicalRabbit "github.com/hashicorp/vault/builtin/logical/rabbitmq"
	logicalSsh "github.com/hashicorp/vault/builtin/logical/ssh"
	logicalTotp "github.com/hashicorp/vault/builtin/logical/totp"
	logicalTransit "github.com/hashicorp/vault/builtin/logical/transit"
)

// Registry is inherently thread-safe because it's immutable.
// Thus, rather than creating multiple instances of it, we only need one.
var Registry = newRegistry()

// BuiltinFactory is the func signature that should be returned by
// the plugin's New() func.
type BuiltinFactory func() (interface{}, error)

func newRegistry() *registry {
	return &registry{
		credentialBackends: map[string]logical.Factory{
			"alicloud":   credAliCloud.Factory,
			"app-id":     credAppId.Factory,
			"approle":    credAppRole.Factory,
			"aws":        credAws.Factory,
			"azure":      credAzure.Factory,
			"centrify":   credCentrify.Factory,
			"cert":       credCert.Factory,
			"gcp":        credGcp.Factory,
			"github":     credGitHub.Factory,
			"jwt":        credJWT.Factory,
			"kubernetes": credKube.Factory,
			"ldap":       credLdap.Factory,
			"oidc":       credJWT.Factory,
			"okta":       credOkta.Factory,
			"pcf":        credPCF.Factory,
			"radius":     credRadius.Factory,
			"userpass":   credUserpass.Factory,
		},
		databasePlugins: map[string]BuiltinFactory{
			// These four plugins all use the same mysql implementation but with
			// different username settings passed by the constructor.
			"mysql-database-plugin":        dbMysql.New(dbMysql.MetadataLen, dbMysql.MetadataLen, dbMysql.UsernameLen),
			"mysql-aurora-database-plugin": dbMysql.New(credsutil.NoneLength, dbMysql.LegacyMetadataLen, dbMysql.LegacyUsernameLen),
			"mysql-rds-database-plugin":    dbMysql.New(credsutil.NoneLength, dbMysql.LegacyMetadataLen, dbMysql.LegacyUsernameLen),
			"mysql-legacy-database-plugin": dbMysql.New(credsutil.NoneLength, dbMysql.LegacyMetadataLen, dbMysql.LegacyUsernameLen),

			"postgresql-database-plugin":    dbPostgres.New,
			"mssql-database-plugin":         dbMssql.New,
			"cassandra-database-plugin":     dbCass.New,
			"mongodb-database-plugin":       dbMongo.New,
			"hana-database-plugin":          dbHana.New,
			"influxdb-database-plugin":      dbInflux.New,
			"elasticsearch-database-plugin": dbElastic.New,
		},
		logicalBackends: map[string]logical.Factory{
			"ad":         logicalAd.Factory,
			"alicloud":   logicalAlicloud.Factory,
			"aws":        logicalAws.Factory,
			"azure":      logicalAzure.Factory,
			"cassandra":  logicalCass.Factory,
			"consul":     logicalConsul.Factory,
			"gcp":        logicalGcp.Factory,
			"gcpkms":     logicalGcpKms.Factory,
			"kv":         logicalKv.Factory,
			"mongodb":    logicalMongo.Factory,
			"mssql":      logicalMssql.Factory,
			"mysql":      logicalMysql.Factory,
			"nomad":      logicalNomad.Factory,
			"pki":        logicalPki.Factory,
			"postgresql": logicalPostgres.Factory,
			"rabbitmq":   logicalRabbit.Factory,
			"ssh":        logicalSsh.Factory,
			"totp":       logicalTotp.Factory,
			"transit":    logicalTransit.Factory,
		},
	}
}

type registry struct {
	credentialBackends map[string]logical.Factory
	databasePlugins    map[string]BuiltinFactory
	logicalBackends    map[string]logical.Factory
}

// Get returns the BuiltinFactory func for a particular backend plugin
// from the plugins map.
func (r *registry) Get(name string, pluginType consts.PluginType) (func() (interface{}, error), bool) {
	switch pluginType {
	case consts.PluginTypeCredential:
		f, ok := r.credentialBackends[name]
		return toFunc(f), ok
	case consts.PluginTypeSecrets:
		f, ok := r.logicalBackends[name]
		return toFunc(f), ok
	case consts.PluginTypeDatabase:
		f, ok := r.databasePlugins[name]
		return f, ok
	default:
		return nil, false
	}
}

// Keys returns the list of plugin names that are considered builtin plugins.
func (r *registry) Keys(pluginType consts.PluginType) []string {
	var keys []string
	switch pluginType {
	case consts.PluginTypeDatabase:
		for key := range r.databasePlugins {
			keys = append(keys, key)
		}
	case consts.PluginTypeCredential:
		for key := range r.credentialBackends {
			keys = append(keys, key)
		}
	case consts.PluginTypeSecrets:
		for key := range r.logicalBackends {
			keys = append(keys, key)
		}
	}
	return keys
}

func (r *registry) Contains(name string, pluginType consts.PluginType) bool {
	for _, key := range r.Keys(pluginType) {
		if key == name {
			return true
		}
	}
	return false
}

func toFunc(ifc interface{}) func() (interface{}, error) {
	return func() (interface{}, error) {
		return ifc, nil
	}
}
<commit_msg>Abstract out adding external plugins<commit_after>package builtinplugins

import (
	"github.com/hashicorp/vault/sdk/database/helper/credsutil"
	"github.com/hashicorp/vault/sdk/helper/consts"
	"github.com/hashicorp/vault/sdk/logical"

	credAliCloud "github.com/hashicorp/vault-plugin-auth-alicloud"
	credAzure "github.com/hashicorp/vault-plugin-auth-azure"
	credCentrify "github.com/hashicorp/vault-plugin-auth-centrify"
	credGcp "github.com/hashicorp/vault-plugin-auth-gcp/plugin"
	credJWT "github.com/hashicorp/vault-plugin-auth-jwt"
	credKube "github.com/hashicorp/vault-plugin-auth-kubernetes"
	credPCF "github.com/hashicorp/vault-plugin-auth-pcf"
	credAppId "github.com/hashicorp/vault/builtin/credential/app-id"
	credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
	credAws "github.com/hashicorp/vault/builtin/credential/aws"
	credCert "github.com/hashicorp/vault/builtin/credential/cert"
	credGitHub "github.com/hashicorp/vault/builtin/credential/github"
	credLdap "github.com/hashicorp/vault/builtin/credential/ldap"
	credOkta "github.com/hashicorp/vault/builtin/credential/okta"
	credRadius "github.com/hashicorp/vault/builtin/credential/radius"
	credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"

	dbElastic "github.com/hashicorp/vault-plugin-database-elasticsearch"
	dbCass "github.com/hashicorp/vault/plugins/database/cassandra"
	dbHana "github.com/hashicorp/vault/plugins/database/hana"
	dbInflux "github.com/hashicorp/vault/plugins/database/influxdb"
	dbMongo "github.com/hashicorp/vault/plugins/database/mongodb"
	dbMssql "github.com/hashicorp/vault/plugins/database/mssql"
	dbMysql "github.com/hashicorp/vault/plugins/database/mysql"
	dbPostgres "github.com/hashicorp/vault/plugins/database/postgresql"

	logicalAd "github.com/hashicorp/vault-plugin-secrets-ad/plugin"
	logicalAlicloud "github.com/hashicorp/vault-plugin-secrets-alicloud"
	logicalAzure "github.com/hashicorp/vault-plugin-secrets-azure"
	logicalGcp "github.com/hashicorp/vault-plugin-secrets-gcp/plugin"
	logicalGcpKms "github.com/hashicorp/vault-plugin-secrets-gcpkms"
	logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
	logicalAws "github.com/hashicorp/vault/builtin/logical/aws"
	logicalCass "github.com/hashicorp/vault/builtin/logical/cassandra"
	logicalConsul "github.com/hashicorp/vault/builtin/logical/consul"
	logicalMongo "github.com/hashicorp/vault/builtin/logical/mongodb"
	logicalMssql "github.com/hashicorp/vault/builtin/logical/mssql"
	logicalMysql "github.com/hashicorp/vault/builtin/logical/mysql"
	logicalNomad "github.com/hashicorp/vault/builtin/logical/nomad"
	logicalPki "github.com/hashicorp/vault/builtin/logical/pki"
	logicalPostgres "github.com/hashicorp/vault/builtin/logical/postgresql"
	logicalRabbit "github.com/hashicorp/vault/builtin/logical/rabbitmq"
	logicalSsh "github.com/hashicorp/vault/builtin/logical/ssh"
	logicalTotp "github.com/hashicorp/vault/builtin/logical/totp"
	logicalTransit "github.com/hashicorp/vault/builtin/logical/transit"
)

// Registry is inherently thread-safe because it's immutable.
// Thus, rather than creating multiple instances of it, we only need one.
var Registry = newRegistry()

var addExternalPlugins = addExtPluginsImpl

// BuiltinFactory is the func signature that should be returned by
// the plugin's New() func.
type BuiltinFactory func() (interface{}, error)

func newRegistry() *registry {
	reg := &registry{
		credentialBackends: map[string]logical.Factory{
			"alicloud":   credAliCloud.Factory,
			"app-id":     credAppId.Factory,
			"approle":    credAppRole.Factory,
			"aws":        credAws.Factory,
			"azure":      credAzure.Factory,
			"centrify":   credCentrify.Factory,
			"cert":       credCert.Factory,
			"gcp":        credGcp.Factory,
			"github":     credGitHub.Factory,
			"jwt":        credJWT.Factory,
			"kubernetes": credKube.Factory,
			"ldap":       credLdap.Factory,
			"oidc":       credJWT.Factory,
			"okta":       credOkta.Factory,
			"pcf":        credPCF.Factory,
			"radius":     credRadius.Factory,
			"userpass":   credUserpass.Factory,
		},
		databasePlugins: map[string]BuiltinFactory{
			// These four plugins all use the same mysql implementation but with
			// different username settings passed by the constructor.
			"mysql-database-plugin":        dbMysql.New(dbMysql.MetadataLen, dbMysql.MetadataLen, dbMysql.UsernameLen),
			"mysql-aurora-database-plugin": dbMysql.New(credsutil.NoneLength, dbMysql.LegacyMetadataLen, dbMysql.LegacyUsernameLen),
			"mysql-rds-database-plugin":    dbMysql.New(credsutil.NoneLength, dbMysql.LegacyMetadataLen, dbMysql.LegacyUsernameLen),
			"mysql-legacy-database-plugin": dbMysql.New(credsutil.NoneLength, dbMysql.LegacyMetadataLen, dbMysql.LegacyUsernameLen),

			"postgresql-database-plugin":    dbPostgres.New,
			"mssql-database-plugin":         dbMssql.New,
			"cassandra-database-plugin":     dbCass.New,
			"mongodb-database-plugin":       dbMongo.New,
			"hana-database-plugin":          dbHana.New,
			"influxdb-database-plugin":      dbInflux.New,
			"elasticsearch-database-plugin": dbElastic.New,
		},
		logicalBackends: map[string]logical.Factory{
			"ad":         logicalAd.Factory,
			"alicloud":   logicalAlicloud.Factory,
			"aws":        logicalAws.Factory,
			"azure":      logicalAzure.Factory,
			"cassandra":  logicalCass.Factory,
			"consul":     logicalConsul.Factory,
			"gcp":        logicalGcp.Factory,
			"gcpkms":     logicalGcpKms.Factory,
			"kv":         logicalKv.Factory,
			"mongodb":    logicalMongo.Factory,
			"mssql":      logicalMssql.Factory,
			"mysql":      logicalMysql.Factory,
			"nomad":      logicalNomad.Factory,
			"pki":        logicalPki.Factory,
			"postgresql": logicalPostgres.Factory,
			"rabbitmq":   logicalRabbit.Factory,
			"ssh":        logicalSsh.Factory,
			"totp":       logicalTotp.Factory,
			"transit":    logicalTransit.Factory,
		},
	}

	addExternalPlugins(reg)

	return reg
}

func addExtPluginsImpl(r *registry) {}

type registry struct {
	credentialBackends map[string]logical.Factory
	databasePlugins    map[string]BuiltinFactory
	logicalBackends    map[string]logical.Factory
}

// Get returns the BuiltinFactory func for a particular backend plugin
// from the plugins map.
func (r *registry) Get(name string, pluginType consts.PluginType) (func() (interface{}, error), bool) {
	switch pluginType {
	case consts.PluginTypeCredential:
		f, ok := r.credentialBackends[name]
		return toFunc(f), ok
	case consts.PluginTypeSecrets:
		f, ok := r.logicalBackends[name]
		return toFunc(f), ok
	case consts.PluginTypeDatabase:
		f, ok := r.databasePlugins[name]
		return f, ok
	default:
		return nil, false
	}
}

// Keys returns the list of plugin names that are considered builtin plugins.
func (r *registry) Keys(pluginType consts.PluginType) []string {
	var keys []string
	switch pluginType {
	case consts.PluginTypeDatabase:
		for key := range r.databasePlugins {
			keys = append(keys, key)
		}
	case consts.PluginTypeCredential:
		for key := range r.credentialBackends {
			keys = append(keys, key)
		}
	case consts.PluginTypeSecrets:
		for key := range r.logicalBackends {
			keys = append(keys, key)
		}
	}
	return keys
}

func (r *registry) Contains(name string, pluginType consts.PluginType) bool {
	for _, key := range r.Keys(pluginType) {
		if key == name {
			return true
		}
	}
	return false
}

func toFunc(ifc interface{}) func() (interface{}, error) {
	return func() (interface{}, error) {
		return ifc, nil
	}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"os/exec"
	"time"

	"github.com/brotherlogic/goserver/utils"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"

	pbb "github.com/brotherlogic/buildserver/proto"
	pbfc "github.com/brotherlogic/filecopier/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
	pbvt "github.com/brotherlogic/versiontracker/proto"
)

const (
	pendWait = time.Minute
)

var (
	ackQueueLen = promauto.NewGauge(prometheus.GaugeOpts{
		Name: "gobuildslave_ackqueuelen",
		Help: "The size of the ack queue",
	})
)

func (s *Server) procAcks() {
	for job := range s.ackChan {
		ackQueueLen.Set(float64(len(s.ackChan)))
		ctx, cancel := utils.ManualContext("gobuildslaveack", "gobuildslaveack", time.Minute, false)
		conn, err := s.FDialSpecificServer(ctx, "versiontracker", s.Registry.GetIdentifier())
		if err != nil {
			s.DLog(fmt.Sprintf("Dial error: (%v), %v\n", job.GetJob(), err))
			s.ackChan <- job
		} else {

			client := pbvt.NewVersionTrackerServiceClient(conn)
			_, err = client.NewJob(ctx, &pbvt.NewJobRequest{Version: &pbb.Version{Job: job.GetJob()}})
			if err != nil {
				s.ackChan <- job
			}
			conn.Close()
		}
		cancel()

		// Don't rush the system
		time.Sleep(time.Second * 10)
	}
}

func (s *Server) runTransition(ctx context.Context, job *pb.JobAssignment) {
	s.DLog(fmt.Sprintf("TRANS: %v\n", job))
	startState := job.State
	job.LastUpdateTime = time.Now().Unix()
	switch job.State {
	case pb.State_WARMUP:
		s.ackChan <- job
		job.State = pb.State_ACKNOWLEDGED
	case pb.State_ACKNOWLEDGED:
		key := s.scheduleBuild(ctx, job)
		job.SubState = fmt.Sprintf("SCHED: %v @ %v", key, time.Now())
		if !job.Job.Bootstrap {
			if key != "" {
				job.State = pb.State_BUILT
				job.RunningVersion = key
			} else {
				// Bootstrap this job since we don't have an initial version
				if job.Job.PartialBootstrap {
					job.Job.Bootstrap = true
				}
			}
		} else {
			job.CommandKey = key
			job.State = pb.State_BUILDING
		}
	case pb.State_BUILDING:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILD(%v): %v", job.CommandKey, s.scheduler.getState(job.CommandKey))
		s.stateMutex.Unlock()
		s.scheduler.wait(job.CommandKey)
		job.State = pb.State_BUILT
	case pb.State_BUILT:
		job.SubState = "Getting Output"
		output, _ := s.scheduler.getOutput(job.CommandKey)
		job.SubState = "Entering Lock"
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILT(%v): (%v): %v", job.CommandKey, len(output), output)
		s.stateMutex.Unlock()
		if job.Job.Bootstrap && len(output) > 0 {
			if job.BuildFail == 5 {
				job.SubState = "Sending Report"
				s.deliverCrashReport(ctx, job, output)
				job.BuildFail = 0
			}
			job.BuildFail++
			job.State = pb.State_DIED
		} else {
			job.BuildFail = 0
			job.SubState = "Scheduling Run"
			key := s.scheduleRun(job)
			job.CommandKey = key
			job.StartTime = time.Now().Unix()
			job.State = pb.State_PENDING
			if _, ok := s.pendingMap[time.Now().Weekday()]; !ok {
				s.pendingMap[time.Now().Weekday()] = make(map[string]int)
			}
			s.pendingMap[time.Now().Weekday()][job.Job.Name]++
		}
		job.SubState = "Out of case"
	case pb.State_PENDING:
		if job.Job.PartialBootstrap && job.Job.Bootstrap {
			job.Job.Bootstrap = false
		}
		s.stateMutex.Lock()
		out, _ := s.scheduler.getOutput(job.CommandKey)
		s.stateMap[job.Job.Name] = fmt.Sprintf("OUTPUT = %v", out)
		s.stateMutex.Unlock()
		if time.Now().Add(-time.Minute).Unix() > job.StartTime {
			job.State = pb.State_RUNNING
		}
	case pb.State_RUNNING:
		output, errout := s.scheduler.getOutput(job.CommandKey)
		output2, _ := s.scheduler.getErrOutput(job.CommandKey)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("ROUTPUT = %v, %v", output, s.scheduler.getStatus(job.CommandKey))
		job.Status = s.scheduler.getStatus(job.CommandKey)
		s.stateMutex.Unlock()
		if len(job.CommandKey) > 0 {
			s.scheduler.wait(job.CommandKey)
			s.stateMutex.Lock()
			s.stateMap[job.Job.Name] = fmt.Sprintf("COMPLETE = (%v, %v)", job, output)
			s.stateMutex.Unlock()
			s.deliverCrashReport(ctx, job, output+"\n"+output2)
			job.State = pb.State_DIED
		}

		if s.discover != nil && s.Registry != nil {
			port, err := s.discover.discover(job.Job.Name, s.Registry.Identifier)
			if err != nil {
				if job.DiscoverCount > 30 {
					output2, errout2 := s.scheduler.getErrOutput(job.CommandKey)
					s.RaiseIssue("Cannot Discover Running Server", fmt.Sprintf("%v on %v is not discoverable, despite running (%v) the output says %v (%v), %v, %v", job.Job.Name, s.Registry.Identifier, err, output, errout, output2, errout2))
				}
				job.DiscoverCount++
			} else {
				job.Port = port
				job.DiscoverCount = 0
			}
		}

		// Restart this job if we need to
		if !job.Job.Bootstrap {
			if time.Now().Sub(time.Unix(job.LastVersionPull, 0)) > time.Minute*5 {
				version, err := s.getVersion(ctx, job.Job)
				job.LastVersionPull = time.Now().Unix()

				if err == nil && version.Version != job.RunningVersion {
					s.stateMutex.Lock()
					s.stateMap[job.Job.Name] = fmt.Sprintf("VERSION_MISMATCH = %v,%v", version, job.RunningVersion)
					s.stateMutex.Unlock()
					s.scheduler.killJob(job.CommandKey)
				}
			}
		}
	case pb.State_BRINK_OF_DEATH:
		if s.version.confirm(ctx, job.Job.Name) {
			s.scheduler.killJob(job.CommandKey)
			job.State = pb.State_ACKNOWLEDGED
		}
	case pb.State_DIED:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("DIED %v", job.CommandKey)
		s.stateMutex.Unlock()
		job.State = pb.State_ACKNOWLEDGED
	}

	if job.State != startState {
		job.LastTransitionTime = time.Now().Unix()
	}
}

type translator interface {
	build(job *pb.Job) *exec.Cmd
	run(job *pb.Job) *exec.Cmd
}

type checker interface {
	isAlive(ctx context.Context, job *pb.JobAssignment) bool
}

func (s *Server) getVersion(ctx context.Context, job *pb.Job) (*pbb.Version, error) {
	version, err := s.builder.build(ctx, job)
	if err != nil {
		return &pbb.Version{}, err
	}

	return version, nil
}

func updateJob(err error, job *pb.JobAssignment, resp *pbfc.CopyResponse) {
	if err == nil {
		job.QueuePos = resp.IndexInQueue
	}

}

// scheduleBuild builds out the job, returning the current version
func (s *Server) scheduleBuild(ctx context.Context, job *pb.JobAssignment) string {
	if job.Job.Bootstrap {
		c := s.translator.build(job.Job)
		// Block builds to prevent clashes
		return s.scheduler.Schedule(&rCommand{command: c, base: job.Job.Name, block: true})
	}

	val, err := s.builder.build(ctx, job.Job)
	if err != nil {
		s.DLog(fmt.Sprintf("BUILD Error: %v\n", err))
		return ""
	}
	return val.Version
}

func (s *Server) scheduleRun(job *pb.JobAssignment) string {
	//Copy over any existing new versions
	key := s.scheduler.Schedule(&rCommand{command: exec.Command("mv", "$GOPATH/bin/"+job.GetJob().GetName()+".new", "$GOPATH/bin/"+job.GetJob().GetName()), base: job.GetJob().GetName()})
	s.scheduler.wait(key)

	c := s.translator.run(job.GetJob())
	return s.scheduler.Schedule(&rCommand{command: c, base: job.GetJob().GetName()})
}
<commit_msg>Bounce<commit_after>package main

import (
	"context"
	"fmt"
	"os/exec"
	"time"

	"github.com/brotherlogic/goserver/utils"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"

	pbb "github.com/brotherlogic/buildserver/proto"
	pbfc "github.com/brotherlogic/filecopier/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
	pbvt "github.com/brotherlogic/versiontracker/proto"
)

const (
	pendWait = time.Minute
)

var (
	ackQueueLen = promauto.NewGauge(prometheus.GaugeOpts{
		Name: "gobuildslave_ackqueuelen",
		Help: "The size of the ack queue",
	})
)

func (s *Server) procAcks() {
	for job := range s.ackChan {
		ackQueueLen.Set(float64(len(s.ackChan)))
		ctx, cancel := utils.ManualContext("gobuildslaveack", "gobuildslaveack", time.Minute, false)
		conn, err := s.FDialSpecificServer(ctx, "versiontracker", s.Registry.GetIdentifier())
		if err != nil {
			s.DLog(fmt.Sprintf("Dial error: (%v), %v\n", job.GetJob(), err))
			s.ackChan <- job
		} else {

			client := pbvt.NewVersionTrackerServiceClient(conn)
			_, err = client.NewJob(ctx, &pbvt.NewJobRequest{Version: &pbb.Version{Job: job.GetJob()}})
			if err != nil {
				s.ackChan <- job
			}
			conn.Close()
		}
		cancel()

		// Don't rush the system
		time.Sleep(time.Second * 10)
	}
}

func (s *Server) runTransition(ctx context.Context, job *pb.JobAssignment) {
	s.DLog(fmt.Sprintf("TRANS: %v\n", job))
	startState := job.State
	job.LastUpdateTime = time.Now().Unix()
	switch job.State {
	case pb.State_WARMUP:
		s.ackChan <- job
		job.State = pb.State_ACKNOWLEDGED
	case pb.State_ACKNOWLEDGED:
		key := s.scheduleBuild(ctx, job)
		job.SubState = fmt.Sprintf("SCHED: %v @ %v", key, time.Now())
		if !job.Job.Bootstrap {
			if key != "" {
				job.State = pb.State_BUILT
				job.RunningVersion = key
			} else {
				// Bootstrap this job since we don't have an initial version
				if job.Job.PartialBootstrap {
					job.Job.Bootstrap = true
				}
			}
		} else {
			job.CommandKey = key
			job.State = pb.State_BUILDING
		}
	case pb.State_BUILDING:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILD(%v): %v", job.CommandKey, s.scheduler.getState(job.CommandKey))
		s.stateMutex.Unlock()
		s.scheduler.wait(job.CommandKey)
		job.State = pb.State_BUILT
	case pb.State_BUILT:
		job.SubState = "Getting Output"
		output, _ := s.scheduler.getOutput(job.CommandKey)
		job.SubState = "Entering Lock"
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILT(%v): (%v): %v", job.CommandKey, len(output), output)
		s.stateMutex.Unlock()
		if job.Job.Bootstrap && len(output) > 0 {
			if job.BuildFail == 5 {
				job.SubState = "Sending Report"
				s.deliverCrashReport(ctx, job, output)
				job.BuildFail = 0
			}
			job.BuildFail++
			job.State = pb.State_DIED
		} else {
			job.BuildFail = 0
			job.SubState = "Scheduling Run"
			key := s.scheduleRun(job)
			job.CommandKey = key
			job.StartTime = time.Now().Unix()
			job.State = pb.State_PENDING
			if _, ok := s.pendingMap[time.Now().Weekday()]; !ok {
				s.pendingMap[time.Now().Weekday()] = make(map[string]int)
			}
			s.pendingMap[time.Now().Weekday()][job.Job.Name]++
		}
		job.SubState = "Out of case"
	case pb.State_PENDING:
		if job.Job.PartialBootstrap && job.Job.Bootstrap {
			job.Job.Bootstrap = false
		}
		s.stateMutex.Lock()
		out, _ := s.scheduler.getOutput(job.CommandKey)
		s.stateMap[job.Job.Name] = fmt.Sprintf("OUTPUT = %v", out)
		s.stateMutex.Unlock()
		if time.Now().Add(-time.Minute).Unix() > job.StartTime {
			job.State = pb.State_RUNNING
		}
	case pb.State_RUNNING:
		output, errout := s.scheduler.getOutput(job.CommandKey)
		output2, errout2 := s.scheduler.getErrOutput(job.CommandKey)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("ROUTPUT = %v, %v", output, s.scheduler.getStatus(job.CommandKey))
		job.Status = s.scheduler.getStatus(job.CommandKey)
		s.stateMutex.Unlock()
		if len(job.CommandKey) > 0 {
			s.scheduler.wait(job.CommandKey)
			s.stateMutex.Lock()
			s.stateMap[job.Job.Name] = fmt.Sprintf("COMPLETE = (%v, %v)", job, output)
			s.stateMutex.Unlock()
			s.deliverCrashReport(ctx, job, fmt.Sprintf("%v and %v and %v and %v", output, output2, errout, errout2))
			job.State = pb.State_DIED
		}

		if s.discover != nil && s.Registry != nil {
			port, err := s.discover.discover(job.Job.Name, s.Registry.Identifier)
			if err != nil {
				if job.DiscoverCount > 30 {
					output2, errout2 := s.scheduler.getErrOutput(job.CommandKey)
					s.RaiseIssue("Cannot Discover Running Server", fmt.Sprintf("%v on %v is not discoverable, despite running (%v) the output says %v (%v), %v, %v", job.Job.Name, s.Registry.Identifier, err, output, errout, output2, errout2))
				}
				job.DiscoverCount++
			} else {
				job.Port = port
				job.DiscoverCount = 0
			}
		}

		// Restart this job if we need to
		if !job.Job.Bootstrap {
			if time.Now().Sub(time.Unix(job.LastVersionPull, 0)) > time.Minute*5 {
				version, err := s.getVersion(ctx, job.Job)
				job.LastVersionPull = time.Now().Unix()

				if err == nil && version.Version != job.RunningVersion {
					s.stateMutex.Lock()
					s.stateMap[job.Job.Name] = fmt.Sprintf("VERSION_MISMATCH = %v,%v", version, job.RunningVersion)
					s.stateMutex.Unlock()
					s.scheduler.killJob(job.CommandKey)
				}
			}
		}
	case pb.State_BRINK_OF_DEATH:
		if s.version.confirm(ctx, job.Job.Name) {
			s.scheduler.killJob(job.CommandKey)
			job.State = pb.State_ACKNOWLEDGED
		}
	case pb.State_DIED:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("DIED %v", job.CommandKey)
		s.stateMutex.Unlock()
		job.State = pb.State_ACKNOWLEDGED
	}

	if job.State != startState {
		job.LastTransitionTime = time.Now().Unix()
	}
}

type translator interface {
	build(job *pb.Job) *exec.Cmd
	run(job *pb.Job) *exec.Cmd
}

type checker interface {
	isAlive(ctx context.Context, job *pb.JobAssignment) bool
}

func (s *Server) getVersion(ctx context.Context, job *pb.Job) (*pbb.Version, error) {
	version, err := s.builder.build(ctx, job)
	if err != nil {
		return &pbb.Version{}, err
	}

	return version, nil
}

func updateJob(err error, job *pb.JobAssignment, resp *pbfc.CopyResponse) {
	if err == nil {
		job.QueuePos = resp.IndexInQueue
	}

}

// scheduleBuild builds out the job, returning the current version
func (s *Server) scheduleBuild(ctx context.Context, job *pb.JobAssignment) string {
	if job.Job.Bootstrap {
		c := s.translator.build(job.Job)
		// Block builds to prevent clashes
		return s.scheduler.Schedule(&rCommand{command: c, base: job.Job.Name, block: true})
	}

	val, err := s.builder.build(ctx, job.Job)
	if err != nil {
		s.DLog(fmt.Sprintf("BUILD Error: %v\n", err))
		return ""
	}
	return val.Version
}

func (s *Server) scheduleRun(job *pb.JobAssignment) string {
	//Copy over any existing new versions
	key := s.scheduler.Schedule(&rCommand{command: exec.Command("mv", "$GOPATH/bin/"+job.GetJob().GetName()+".new", "$GOPATH/bin/"+job.GetJob().GetName()), base: job.GetJob().GetName()})
	s.scheduler.wait(key)

	c := s.translator.run(job.GetJob())
	return s.scheduler.Schedule(&rCommand{command: c, base: job.GetJob().GetName()})
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"github.com/kisielk/gostatsd/statsd"
)

func main() {
	err := statsd.ListenAndServe(":8125", ":8126", "localhost:1234")
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Added command-line options for configuring the server<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/kisielk/gostatsd/statsd"
	"log"
	"os"
)

var (
	metricsAddr string
	consoleAddr string
	graphiteAddr string
)

func init() {
	const (
		defaultMetricsAddr = ":8125"
		defaultConsoleAddr = ":8126"
		defaultGraphiteAddr = ""
	)
	flag.StringVar(&metricsAddr, "l", defaultMetricsAddr, "Address on which to listen for metrics (optional)")
	flag.StringVar(&consoleAddr, "c", defaultConsoleAddr, "Address on which to listen for console sessions (optional)")
	flag.StringVar(&graphiteAddr, "g", defaultGraphiteAddr, "Address of the graphite server (required)")
}

func main() {
	flag.Parse()
	if graphiteAddr == "" {
		fmt.Printf("Error: You must provide the address of the graphite server with the -g flag\n")
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		flag.PrintDefaults()
		return
	}
	err := statsd.ListenAndServe(metricsAddr, consoleAddr, graphiteAddr)
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package sm

import (
	"bytes"
	"fmt"
	"image"
	_ "image/jpeg" // to be able to decode jpegs
	_ "image/png"  // to be able to decode pngs
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/Wessie/appdirs"
)

// TileFetcher downloads map tile images from a TileProvider
type TileFetcher struct {
	tileProvider *TileProvider
	cacheDir     string
	useCaching   bool
}

// NewTileFetcher creates a new Tilefetcher struct
func NewTileFetcher(tileProvider *TileProvider) *TileFetcher {
	t := new(TileFetcher)
	t.tileProvider = tileProvider
	app := appdirs.New("go-staticmaps", "flopp.net", "0.1")
	t.cacheDir = fmt.Sprintf("%s/%s", app.UserCache(), tileProvider.Name)
	t.useCaching = true
	return t
}

func (t *TileFetcher) url(zoom, x, y int) string {
	shard := ""
	ss := len(t.tileProvider.Shards)
	if len(t.tileProvider.Shards) > 0 {
		shard = t.tileProvider.Shards[(x+y)%ss]
	}
	return t.tileProvider.getURL(shard, zoom, x, y)
}

func (t *TileFetcher) cacheFileName(zoom int, x, y int) string {
	return fmt.Sprintf("%s/%d/%d/%d", t.cacheDir, zoom, x, y)
}

// ToggleCaching enables/disables caching
func (t *TileFetcher) ToggleCaching(enabled bool) {
	t.useCaching = enabled
}

// Fetch download (or retrieves from the cache) a tile image for the specified zoom level and tile coordinates
func (t *TileFetcher) Fetch(zoom, x, y int) (image.Image, error) {
	if t.useCaching {
		fileName := t.cacheFileName(zoom, x, y)
		cachedImg, err := t.loadCache(fileName)
		if err == nil {
			return cachedImg, nil
		}
	}

	url := t.url(zoom, x, y)
	data, err := t.download(url)
	if err != nil {
		return nil, err
	}

	img, _, err := image.Decode(bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	if t.useCaching {
		fileName := t.cacheFileName(zoom, x, y)
		if err := t.storeCache(fileName, data); err != nil {
			log.Printf("Failed to store map tile as '%s': %s", fileName, err)
		}
	}

	return img, nil
}

func (t *TileFetcher) download(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("GET %s: %s", url, resp.Status)
	}

	defer resp.Body.Close()

	contents, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return contents, nil
}

func (t *TileFetcher) loadCache(fileName string) (image.Image, error) {
	file, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	img, _, err := image.Decode(file)
	if err != nil {
		return nil, err
	}

	return img, nil
}

func (t *TileFetcher) createCacheDir(path string) error {
	src, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return os.MkdirAll(path, 0777)
		}
		return err
	}
	if src.IsDir() {
		return nil
	}
	return fmt.Errorf("File exists but is not a directory: %s", path)
}

func (t *TileFetcher) storeCache(fileName string, data []byte) error {
	dir, _ := filepath.Split(fileName)

	if err := t.createCacheDir(dir); err != nil {
		return err
	}

	file, err := os.Create(fileName)
	if err != nil {
		return err
	}
	defer file.Close()

	if _, err = io.Copy(file, bytes.NewBuffer(data)); err != nil {
		return err
	}

	return nil
}
<commit_msg>Add user-agent<commit_after>// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package sm

import (
	"bytes"
	"fmt"
	"image"
	_ "image/jpeg" // to be able to decode jpegs
	_ "image/png"  // to be able to decode pngs
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/Wessie/appdirs"
)

var TileFetcherUserAgent = "Mozilla/5.0+(compatible; go-staticmaps/0.1; https://github.com/flopp/go-staticmaps)"

// TileFetcher downloads map tile images from a TileProvider
type TileFetcher struct {
	tileProvider *TileProvider
	cacheDir     string
	useCaching   bool
}

// NewTileFetcher creates a new Tilefetcher struct
func NewTileFetcher(tileProvider *TileProvider) *TileFetcher {
	t := new(TileFetcher)
	t.tileProvider = tileProvider
	app := appdirs.New("go-staticmaps", "flopp.net", "0.1")
	t.cacheDir = fmt.Sprintf("%s/%s", app.UserCache(), tileProvider.Name)
	t.useCaching = true
	return t
}

func (t *TileFetcher) url(zoom, x, y int) string {
	shard := ""
	ss := len(t.tileProvider.Shards)
	if len(t.tileProvider.Shards) > 0 {
		shard = t.tileProvider.Shards[(x+y)%ss]
	}
	return t.tileProvider.getURL(shard, zoom, x, y)
}

func (t *TileFetcher) cacheFileName(zoom int, x, y int) string {
	return fmt.Sprintf("%s/%d/%d/%d", t.cacheDir, zoom, x, y)
}

// ToggleCaching enables/disables caching
func (t *TileFetcher) ToggleCaching(enabled bool) {
	t.useCaching = enabled
}

// Fetch download (or retrieves from the cache) a tile image for the specified zoom level and tile coordinates
func (t *TileFetcher) Fetch(zoom, x, y int) (image.Image, error) {
	if t.useCaching {
		fileName := t.cacheFileName(zoom, x, y)
		cachedImg, err := t.loadCache(fileName)
		if err == nil {
			return cachedImg, nil
		}
	}

	url := t.url(zoom, x, y)
	data, err := t.download(url)
	if err != nil {
		return nil, err
	}

	img, _, err := image.Decode(bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	if t.useCaching {
		fileName := t.cacheFileName(zoom, x, y)
		if err := t.storeCache(fileName, data); err != nil {
			log.Printf("Failed to store map tile as '%s': %s", fileName, err)
		}
	}

	return img, nil
}

func (t *TileFetcher) download(url string) ([]byte, error) {
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("User-Agent", TileFetcherUserAgent)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("GET %s: %s", url, resp.Status)
	}

	defer resp.Body.Close()

	contents, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return contents, nil
}

func (t *TileFetcher) loadCache(fileName string) (image.Image, error) {
	file, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	img, _, err := image.Decode(file)
	if err != nil {
		return nil, err
	}

	return img, nil
}

func (t *TileFetcher) createCacheDir(path string) error {
	src, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return os.MkdirAll(path, 0777)
		}
		return err
	}
	if src.IsDir() {
		return nil
	}
	return fmt.Errorf("File exists but is not a directory: %s", path)
}

func (t *TileFetcher) storeCache(fileName string, data []byte) error {
	dir, _ := filepath.Split(fileName)

	if err := t.createCacheDir(dir); err != nil {
		return err
	}

	file, err := os.Create(fileName)
	if err != nil {
		return err
	}
	defer file.Close()

	if _, err = io.Copy(file, bytes.NewBuffer(data)); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package scene

import (
	"fmt"
	"image"

	"github.com/pankona/gomobile_gamelib_test/peer"
)

type Title struct {
	background     peer.PeerSprite
	notifySceneEnd func(nextScene Driver)
}

func (self *Title) Initialize(sceneEndCallback func(nextScene Driver)) {
	self.notifySceneEnd = sceneEndCallback

	peer.SetDesiredScreenSize(1080/2, 1920/2)

	// initialize sprites
	self.initSprite()

	// add touch event listener
	peer.GetTouchPeer().AddTouchListener(self)
}

func (self *Title) initSprite() {
	fmt.Println("[IN] Title.initTitleSprite")

	// add background sprite
	self.background.W = float32(1080 / 2)
	self.background.H = float32(1920 / 2)

	// put center of screen
	self.background.X = 1080 / 2 / 2
	self.background.Y = 1920 / 2 / 2

	tex_background := peer.GetGLPeer().LoadTexture("title.png",
		image.Rect(0, 0, int(self.background.W), int(self.background.H)))
	peer.GetGLPeer().AddSprite(&self.background, tex_background)

	fmt.Println("[OUT] Title.initTitleSprite")
}

func (self *Title) Drive() {
}

func (self *Title) OnTouchBegin(x, y float32) {
	fmt.Println("OnTouchBegin = ", x, y)
	self.notifySceneEnd(&Stage1{})
}

func (self *Title) OnTouchMove(x, y float32) {
}

func (self *Title) OnTouchEnd(x, y float32) {
}
<commit_msg>scene change at touch release<commit_after>package scene

import (
	"fmt"
	"image"

	"github.com/pankona/gomobile_gamelib_test/peer"
)

type Title struct {
	background     peer.PeerSprite
	notifySceneEnd func(nextScene Driver)
}

func (self *Title) Initialize(sceneEndCallback func(nextScene Driver)) {
	self.notifySceneEnd = sceneEndCallback

	peer.SetDesiredScreenSize(1080/2, 1920/2)

	// initialize sprites
	self.initSprite()

	// add touch event listener
	peer.GetTouchPeer().AddTouchListener(self)
}

func (self *Title) initSprite() {
	fmt.Println("[IN] Title.initTitleSprite")

	// add background sprite
	self.background.W = float32(1080 / 2)
	self.background.H = float32(1920 / 2)

	// put center of screen
	self.background.X = 1080 / 2 / 2
	self.background.Y = 1920 / 2 / 2

	tex_background := peer.GetGLPeer().LoadTexture("title.png",
		image.Rect(0, 0, int(self.background.W), int(self.background.H)))
	peer.GetGLPeer().AddSprite(&self.background, tex_background)

	fmt.Println("[OUT] Title.initTitleSprite")
}

func (self *Title) Drive() {
}

func (self *Title) OnTouchBegin(x, y float32) {
}

func (self *Title) OnTouchMove(x, y float32) {
}

func (self *Title) OnTouchEnd(x, y float32) {
	fmt.Println("OnTouchBegin = ", x, y)
	self.notifySceneEnd(&Stage1{})
}
<|endoftext|>


<commit_before>package main

import "github.com/nsf/termbox-go"

const NAME_PADDING = 2
const TAB_MARGIN = 3

type TabListViewPort struct {
	width  int
	offset int
}

type DataScreen struct {
	tabs          []*DataTab
	tab_view_port TabListViewPort
	active_tab    int
	show_tabs     bool
}

func (screen *DataScreen) initializeWithFiles(files []FileInfo) {
	var tabs []*DataTab
	for _, file := range files {
		tab := NewDataTab(file)
		tabs = append(tabs, &tab)
	}

	screen.tabs = tabs
	screen.show_tabs = true
}

func (screen *DataScreen) receiveEvents(input <-chan termbox.Event, output chan<- int, quit <-chan bool) {
	for _, t := range screen.tabs {
		go func(tab *DataTab) {
			tab.receiveEvents(output)
		}(t)
	}

	for {
		do_quit := false
		select {
		case event := <-input:
			output <- screen.handleKeyEvent(event, output)
		case <-quit:
			do_quit = true
		}
		if do_quit {
			for _, t := range screen.tabs {
				t.quit_channel <- true
			}
			break
		}
	}
}

func (screen *DataScreen) handleKeyEvent(event termbox.Event, output chan<- int) int {
	active_tab := screen.tabs[screen.active_tab]
	if active_tab.field_editor != nil {
		return active_tab.handleKeyEvent(event)
	} else if event.Key == termbox.KeyCtrlP { // color palette
		return PALETTE_SCREEN_INDEX
	} else if event.Ch == '?' { // about
		return ABOUT_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlJ {
		screen.show_tabs = true
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlK {
		screen.show_tabs = false
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlT {
		var new_tabs []*DataTab
		for index, old_tab := range screen.tabs {
			new_tabs = append(new_tabs, old_tab)
			if old_tab == active_tab {
				tab_copy := NewDataTab(FileInfo{filename: old_tab.filename, bytes: old_tab.bytes})
				tab_copy.cursor = old_tab.cursor
				tab_copy.view_port = old_tab.view_port
				tab_copy.cursor.pos = tab_copy.view_port.first_row * tab_copy.view_port.bytes_per_row
				tab_copy.cursor.mode = StringMode
				new_tabs = append(new_tabs, &tab_copy)
				screen.active_tab = index + 1
				go func() {
					(&tab_copy).receiveEvents(output)
				}()
			}
		}
		screen.tabs = new_tabs
		screen.show_tabs = true
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlW {
		active_tab.quit_channel <- true

		if len(screen.tabs) == 1 {
			return EXIT_SCREEN_INDEX
		}

		var new_tabs []*DataTab
		for _, old_tab := range screen.tabs {
			if old_tab != active_tab {
				new_tabs = append(new_tabs, old_tab)
			}
		}
		screen.tabs = new_tabs
		if screen.active_tab >= len(new_tabs) {
			screen.active_tab = len(new_tabs) - 1
		}
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlL && screen.show_tabs {
		if screen.active_tab < len(screen.tabs)-1 {
			screen.active_tab++
		}
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlH && screen.show_tabs {
		if screen.active_tab > 0 {
			screen.active_tab--
		}
		return DATA_SCREEN_INDEX
	}
	return active_tab.handleKeyEvent(event)
}

func (screen *DataScreen) performLayout() {
	width, height := termbox.Size()

	for _, tab := range screen.tabs {
		if screen.show_tabs {
			tab.performLayout(width, height-3)
		} else {
			tab.performLayout(width, height)
		}
	}

	screen.tab_view_port.width = width
	tab_pos := TAB_MARGIN
	active_tab_start_pos := 0
	active_tab_name_len := 0
	for index, tab := range screen.tabs {
		if index == screen.active_tab {
			active_tab_start_pos = tab_pos
			for _ = range tab.filename {
				active_tab_name_len++
			}
		}
		tab_pos += 2 + 2*NAME_PADDING
		for _ = range tab.filename {
			tab_pos++
		}
	}
	active_tab_end_pos := active_tab_start_pos + 2 + 2*NAME_PADDING + active_tab_name_len
	if tab_pos+TAB_MARGIN < screen.tab_view_port.offset+width {
		if tab_pos+TAB_MARGIN > width {
			screen.tab_view_port.offset = tab_pos + TAB_MARGIN - width
		} else {
			screen.tab_view_port.offset = 0
		}
	}
	if screen.tab_view_port.offset > active_tab_start_pos-TAB_MARGIN {
		screen.tab_view_port.offset = active_tab_start_pos - TAB_MARGIN
	}
	if screen.tab_view_port.offset+width < active_tab_end_pos+TAB_MARGIN {
		screen.tab_view_port.offset = active_tab_end_pos - width + TAB_MARGIN
	}
}

func (screen *DataScreen) drawScreen(style Style) {
	width, _ := termbox.Size()
	active_tab := screen.tabs[screen.active_tab]
	if screen.show_tabs {
		fg := style.default_fg
		bg := style.default_bg
		x_pos := -screen.tab_view_port.offset
		for i := 0; i < TAB_MARGIN; i++ {
			drawStringAtPoint("━", x_pos, 2, fg, bg)
			x_pos++
		}
		for _, tab := range screen.tabs {
			name_fg := fg
			if tab != active_tab {
				name_fg = style.rune_fg
			}
			drawStringAtPoint("╭", x_pos, 0, fg, bg)
			drawStringAtPoint("│", x_pos, 1, fg, bg)
			if tab == active_tab {
				drawStringAtPoint("┙", x_pos, 2, fg, bg)
			} else {
				drawStringAtPoint("┷", x_pos, 2, fg, bg)
			}
			x_pos++

			nameLength := drawStringAtPoint(tab.filename, x_pos+NAME_PADDING, 1, name_fg, bg)
			for i := 0; i < 2*NAME_PADDING+nameLength; i++ {
				drawStringAtPoint("─", x_pos, 0, fg, bg)
				if tab != active_tab {
					drawStringAtPoint("━", x_pos, 2, fg, bg)
				}
				x_pos++
			}
			drawStringAtPoint("╮", x_pos, 0, fg, bg)
			drawStringAtPoint("│", x_pos, 1, fg, bg)
			if tab == active_tab {
				drawStringAtPoint("┕", x_pos, 2, fg, bg)
			} else {
				drawStringAtPoint("┷", x_pos, 2, fg, bg)
			}
			x_pos++
		}
		if width-x_pos > 22 {
			drawStringAtPoint("(?) help", width-20, 1, fg, bg)
		}
		if width-x_pos > 12 {
			drawStringAtPoint("(q)uit", width-10, 1, fg, bg)
		}
		for x_pos < width {
			drawStringAtPoint("━", x_pos, 2, fg, bg)
			x_pos++
		}
		active_tab.drawTab(style, 3)
	} else {
		active_tab.drawTab(style, 0)
	}
}
<commit_msg>Use constants for line-drawing characters<commit_after>package main

import "github.com/nsf/termbox-go"

const NAME_PADDING = 2
const TAB_MARGIN = 3

const THICK_LINE = "━"
const RIGHT_JOINT = "┕"
const LEFT_JOINT = "┙"
const DOUBLE_JOINT = "┷"

/*
const THICK_LINE = "═"
const RIGHT_JOINT = "╘"
const LEFT_JOINT = "╛"
const DOUBLE_JOINT = "╧"
*/

type TabListViewPort struct {
	width  int
	offset int
}

type DataScreen struct {
	tabs          []*DataTab
	tab_view_port TabListViewPort
	active_tab    int
	show_tabs     bool
}

func (screen *DataScreen) initializeWithFiles(files []FileInfo) {
	var tabs []*DataTab
	for _, file := range files {
		tab := NewDataTab(file)
		tabs = append(tabs, &tab)
	}

	screen.tabs = tabs
	screen.show_tabs = true
}

func (screen *DataScreen) receiveEvents(input <-chan termbox.Event, output chan<- int, quit <-chan bool) {
	for _, t := range screen.tabs {
		go func(tab *DataTab) {
			tab.receiveEvents(output)
		}(t)
	}

	for {
		do_quit := false
		select {
		case event := <-input:
			output <- screen.handleKeyEvent(event, output)
		case <-quit:
			do_quit = true
		}
		if do_quit {
			for _, t := range screen.tabs {
				t.quit_channel <- true
			}
			break
		}
	}
}

func (screen *DataScreen) handleKeyEvent(event termbox.Event, output chan<- int) int {
	active_tab := screen.tabs[screen.active_tab]
	if active_tab.field_editor != nil {
		return active_tab.handleKeyEvent(event)
	} else if event.Key == termbox.KeyCtrlP { // color palette
		return PALETTE_SCREEN_INDEX
	} else if event.Ch == '?' { // about
		return ABOUT_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlJ {
		screen.show_tabs = true
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlK {
		screen.show_tabs = false
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlT {
		var new_tabs []*DataTab
		for index, old_tab := range screen.tabs {
			new_tabs = append(new_tabs, old_tab)
			if old_tab == active_tab {
				tab_copy := NewDataTab(FileInfo{filename: old_tab.filename, bytes: old_tab.bytes})
				tab_copy.cursor = old_tab.cursor
				tab_copy.view_port = old_tab.view_port
				tab_copy.cursor.pos = tab_copy.view_port.first_row * tab_copy.view_port.bytes_per_row
				tab_copy.cursor.mode = StringMode
				new_tabs = append(new_tabs, &tab_copy)
				screen.active_tab = index + 1
				go func() {
					(&tab_copy).receiveEvents(output)
				}()
			}
		}
		screen.tabs = new_tabs
		screen.show_tabs = true
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlW {
		active_tab.quit_channel <- true

		if len(screen.tabs) == 1 {
			return EXIT_SCREEN_INDEX
		}

		var new_tabs []*DataTab
		for _, old_tab := range screen.tabs {
			if old_tab != active_tab {
				new_tabs = append(new_tabs, old_tab)
			}
		}
		screen.tabs = new_tabs
		if screen.active_tab >= len(new_tabs) {
			screen.active_tab = len(new_tabs) - 1
		}
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlL && screen.show_tabs {
		if screen.active_tab < len(screen.tabs)-1 {
			screen.active_tab++
		}
		return DATA_SCREEN_INDEX
	} else if event.Key == termbox.KeyCtrlH && screen.show_tabs {
		if screen.active_tab > 0 {
			screen.active_tab--
		}
		return DATA_SCREEN_INDEX
	}
	return active_tab.handleKeyEvent(event)
}

func (screen *DataScreen) performLayout() {
	width, height := termbox.Size()

	for _, tab := range screen.tabs {
		if screen.show_tabs {
			tab.performLayout(width, height-3)
		} else {
			tab.performLayout(width, height)
		}
	}

	screen.tab_view_port.width = width
	tab_pos := TAB_MARGIN
	active_tab_start_pos := 0
	active_tab_name_len := 0
	for index, tab := range screen.tabs {
		if index == screen.active_tab {
			active_tab_start_pos = tab_pos
			for _ = range tab.filename {
				active_tab_name_len++
			}
		}
		tab_pos += 2 + 2*NAME_PADDING
		for _ = range tab.filename {
			tab_pos++
		}
	}
	active_tab_end_pos := active_tab_start_pos + 2 + 2*NAME_PADDING + active_tab_name_len
	if tab_pos+TAB_MARGIN < screen.tab_view_port.offset+width {
		if tab_pos+TAB_MARGIN > width {
			screen.tab_view_port.offset = tab_pos + TAB_MARGIN - width
		} else {
			screen.tab_view_port.offset = 0
		}
	}
	if screen.tab_view_port.offset > active_tab_start_pos-TAB_MARGIN {
		screen.tab_view_port.offset = active_tab_start_pos - TAB_MARGIN
	}
	if screen.tab_view_port.offset+width < active_tab_end_pos+TAB_MARGIN {
		screen.tab_view_port.offset = active_tab_end_pos - width + TAB_MARGIN
	}
}

func (screen *DataScreen) drawScreen(style Style) {
	width, _ := termbox.Size()
	active_tab := screen.tabs[screen.active_tab]
	if screen.show_tabs {
		fg := style.default_fg
		bg := style.default_bg
		x_pos := -screen.tab_view_port.offset
		for i := 0; i < TAB_MARGIN; i++ {
			drawStringAtPoint(THICK_LINE, x_pos, 2, fg, bg)
			x_pos++
		}
		for _, tab := range screen.tabs {
			name_fg := fg
			if tab != active_tab {
				name_fg = style.rune_fg
			}
			drawStringAtPoint("╭", x_pos, 0, fg, bg)
			drawStringAtPoint("│", x_pos, 1, fg, bg)
			if tab == active_tab {
				drawStringAtPoint(LEFT_JOINT, x_pos, 2, fg, bg)
			} else {
				drawStringAtPoint(DOUBLE_JOINT, x_pos, 2, fg, bg)
			}
			x_pos++

			nameLength := drawStringAtPoint(tab.filename, x_pos+NAME_PADDING, 1, name_fg, bg)
			for i := 0; i < 2*NAME_PADDING+nameLength; i++ {
				drawStringAtPoint("─", x_pos, 0, fg, bg)
				if tab != active_tab {
					drawStringAtPoint(THICK_LINE, x_pos, 2, fg, bg)
				}
				x_pos++
			}
			drawStringAtPoint("╮", x_pos, 0, fg, bg)
			drawStringAtPoint("│", x_pos, 1, fg, bg)
			if tab == active_tab {
				drawStringAtPoint(RIGHT_JOINT, x_pos, 2, fg, bg)
			} else {
				drawStringAtPoint(DOUBLE_JOINT, x_pos, 2, fg, bg)
			}
			x_pos++
		}
		if width-x_pos > 22 {
			drawStringAtPoint("(?) help", width-20, 1, fg, bg)
		}
		if width-x_pos > 12 {
			drawStringAtPoint("(q)uit", width-10, 1, fg, bg)
		}
		for x_pos < width {
			drawStringAtPoint(THICK_LINE, x_pos, 2, fg, bg)
			x_pos++
		}
		active_tab.drawTab(style, 3)
	} else {
		active_tab.drawTab(style, 0)
	}
}
<|endoftext|>