<commit_before>package util

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	"image/png"
	"math"
	"os"
	"strings"
	"time"

	"github.com/ninjasphere/go-ninja/logger"
)

var log = logger.GetLogger("ui/util")

// Approx. framerate of the display
const fps = 30

// How long each frame is displayed for
const frameTime = time.Second / fps

// If a frame's delay is under this duration, we will display it
// for a certain number of frames, rather than for a time.
const adjustDelayUnder = time.Millisecond * 300

type Image interface {
	GetNextFrame() *image.RGBA
	GetPositionFrame(position float64, blend bool) *image.RGBA
}

type SingleImage struct {
	frame *image.RGBA
}

func NewSingleImage(frame *image.RGBA) *SingleImage {
	return &SingleImage{frame}
}

func (i *SingleImage) GetNextFrame() *image.RGBA {
	return i.frame
}

func (i *SingleImage) GetPositionFrame(position float64, blend bool) *image.RGBA {
	return i.frame
}

/* MaskImage wraps an image so that the red level is used as the transparency.
It allows animated gifs to be used as masks, with 255 levels of alpha. */
type MaskImage struct {
	Image Image
}

func (i *MaskImage) GetNextFrame() *image.RGBA {
	in := i.Image.GetNextFrame()
	out := image.NewRGBA(image.Rect(0, 0, 16, 16))

	for pos := 0; (pos + 3) < len(in.Pix); pos = pos + 4 {
		out.Pix[pos+3] = 255 - in.Pix[pos]
	}

	return out
}

func (i *MaskImage) GetPositionFrame(position float64, blend bool) *image.RGBA {
	return i.Image.GetPositionFrame(position, blend)
}

type AnimatedImage struct {
	frameRequest    chan bool
	pos             int
	frames          []*image.RGBA
	delays          []int
	remainingLoops  int
	started         bool
	delayAdjustment float64
}

func (i *AnimatedImage) GetNextFrame() *image.RGBA {

	if !i.started {
		i.started = true
		i.start()
	}

	frame := i.frames[i.pos]

	select {
	case i.frameRequest <- true:
	default:
	}

	return frame
}

func (i *AnimatedImage) start() {

	go func() {
		for {
			delay := time.Duration(float64(i.delays[i.pos])*i.delayAdjustment) * 10 * time.Millisecond

			if delay < adjustDelayUnder {

				// Rounded to nearest int
				framesToDisplay := int(math.Floor((float64(delay) / float64(frameTime)) + 0.5))

				// Show for at least one frame
				if framesToDisplay == 0 {
					framesToDisplay = 1
				}

				//log.Printf("Frame wanted a delay of %d so showing for %d frames", delay, framesToDisplay)

				for x := 0; x < framesToDisplay; x++ {
					// Wait till this frame has been taken
					<-i.frameRequest
				}

			} else {
				// Just sleep. At these amounts noone will notice +-1 frame
				//log.Printf("Sleeping frame %d for %d", i.pos, delay)
				time.Sleep(delay)
			}

			// That was the last frame
			if i.pos == len(i.frames)-1 {

				if i.remainingLoops == -1 {
					// Start again, we are looping forever
					i.pos = 0
				} else if i.remainingLoops > 0 {
					// Start again, we still have at least one loop remaining
					i.pos = 0
					i.remainingLoops--
				} else {
					// We're done, this frame gets shown forever *drops mic*.
					break
				}
			} else {
				i.pos++
			}

		}
	}()

}

// GetPositionFrame returns the frame corresponding to the position given 0....1
func (i *AnimatedImage) GetPositionFrame(position float64, blend bool) *image.RGBA {

	relativePosition := position * float64(len(i.frames)-1)

	previousFrame := int(math.Floor(relativePosition))
	nextFrame := int(math.Ceil(relativePosition))

	framePosition := math.Mod(relativePosition, 1)

	//log.Debugf("GetPositionFrame. Frames:%d Position:%f RelativePosition:%f FramePosition:%f PreviousFrame:%d NextFrame:%d", len(i.frames), position, relativePosition, framePosition, previousFrame, nextFrame)
	if !blend || previousFrame == nextFrame {
		// We can just send back a single frame
		return i.frames[previousFrame]
	}

	maskNext := image.NewUniform(color.Alpha{uint8(255 * framePosition)})

	frame := image.NewRGBA(image.Rect(0, 0, 16, 16))

	draw.Draw(frame, frame.Bounds(), i.frames[previousFrame], image.Point{0, 0}, draw.Src)
	draw.DrawMask(frame, frame.Bounds(), i.frames[nextFrame], image.Point{0, 0}, maskNext, image.Point{0, 0}, draw.Over)

	return frame
}

func LoadImage(src string) Image {
	srcLower := strings.ToLower(src)

	if strings.Contains(srcLower, ".gif") {
		return LoadGif(src)
	} else if strings.Contains(srcLower, ".png") {
		return LoadPng(src)
	} else {
		log.HandleError(fmt.Errorf(src), "Unknown image format")
	}
	return nil
}

func LoadPng(src string) Image {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open png '%s' : %s", src, err)
	}

	img, err := png.Decode(file)
	if err != nil {
		log.Fatalf("PNG decoding failed on image '%s' : %s", src, err)
	}

	return &SingleImage{
		frame: toRGBA(img),
	}
}

func LoadGif(src string) *AnimatedImage {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open gif '%s' : %s", src, err)
	}

	img, err := gif.DecodeAll(file)
	if err != nil {
		log.Fatalf("Gif decoding failed on image '%s' : %s", src, err)
	}

	var frames = []*image.RGBA{}

	for _, frame := range img.Image {
		frames = append(frames, toRGBA(frame))
	}

	loops := img.LoopCount
	if loops == 0 {
		loops = -1
	} else if loops == -1 {
		loops = 0
	}

	return &AnimatedImage{
		frames:          frames,
		delays:          img.Delay,
		remainingLoops:  loops,
		frameRequest:    make(chan bool),
		delayAdjustment: 1.0,
	}
}

func toRGBA(in image.Image) *image.RGBA {
	bounds := in.Bounds()
	out := image.NewRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
	draw.Draw(out, out.Bounds(), in, bounds.Min, draw.Over)
	return out
}

/*

Alt version using different gif decoder... doesn't work

package util

import (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"io/ioutil"
"math"
"os"
"strings"
"time"

"github.com/xanthousphoenix/go-gif"
)

// Approx. framerate of the display
const fps = 30

// How long each frame is displayed for
const frameTime = time.Second / fps

// If a frame's delay is under this duration, we will display it
// for a certain number of frames, rather than for a time.
const adjustDelayUnder = time.Millisecond * 300

type Image interface {
GetNextFrame() *image.RGBA
GetPositionFrame(position float64, blend bool) *image.RGBA
}

type SingleImage struct {
frame *image.RGBA
}

func NewSingleImage(frame *image.RGBA) *SingleImage {
return &SingleImage{frame}
}

func (i *SingleImage) GetNextFrame() *image.RGBA {
return i.frame
}

func (i *SingleImage) GetPositionFrame(position float64, blend bool) *image.RGBA {
return i.frame
}

type AnimatedImage struct {
frameRequest    chan bool
pos             int
frames          []*image.RGBA
delays          []int
remainingLoops  int
started         bool
delayAdjustment float64
}

func (i *AnimatedImage) GetNextFrame() *image.RGBA {

if !i.started {
i.started = true
i.start()
}

frame := i.frames[i.pos]

select {
case i.frameRequest <- true:
default:
}

return frame
}

func (i *AnimatedImage) start() {

go func() {
for {
delay := time.Duration(float64(i.delays[i.pos])*i.delayAdjustment) * 10 * time.Millisecond

if delay < adjustDelayUnder {

// Rounded to nearest int
framesToDisplay := int(math.Floor((float64(delay) / float64(frameTime)) + 0.5))

// Show for at least one frame
if framesToDisplay == 0 {
framesToDisplay = 1
}

//log.Printf("Frame wanted a delay of %d so showing for %d frames", delay, framesToDisplay)

for x := 0; x < framesToDisplay; x++ {
// Wait till this frame has been taken
<-i.frameRequest
}

} else {
// Just sleep. At these amounts noone will notice +-1 frame
//log.Printf("Sleeping frame %d for %d", i.pos, delay)
time.Sleep(delay)
}

// That was the last frame
if i.pos == len(i.frames)-1 {

if i.remainingLoops == -1 {
// Start again, we are looping forever
i.pos = 0
} else if i.remainingLoops > 0 {
// Start again, we still have at least one loop remaining
i.pos = 0
i.remainingLoops--
} else {
// We're done, this frame gets shown forever *drops mic*.
break
}
} else {
i.pos++
}

}
}()

}

// GetPositionFrame returns the frame corresponding to the position given 0....1
func (i *AnimatedImage) GetPositionFrame(position float64, blend bool) *image.RGBA {

relativePosition := position * float64(len(i.frames)-1)

previousFrame := int(math.Floor(relativePosition))
nextFrame := int(math.Ceil(relativePosition))

framePosition := math.Mod(relativePosition, 1)

//log.Debugf("GetPositionFrame. Frames:%d Position:%f RelativePosition:%f FramePosition:%f PreviousFrame:%d NextFrame:%d", len(i.frames), position, relativePosition, framePosition, previousFrame, nextFrame)
if !blend || previousFrame == nextFrame {
// We can just send back a single frame
return i.frames[previousFrame]
}

maskNext := image.NewUniform(color.Alpha{uint8(255 * framePosition)})

frame := image.NewRGBA(image.Rect(0, 0, 16, 16))

draw.Draw(frame, frame.Bounds(), i.frames[previousFrame], image.Point{0, 0}, draw.Src)
draw.DrawMask(frame, frame.Bounds(), i.frames[nextFrame], image.Point{0, 0}, maskNext, image.Point{0, 0}, draw.Over)

return frame
}

func LoadImage(src string) Image {
srcLower := strings.ToLower(src)

if strings.Contains(srcLower, ".gif") {
return LoadGif(src)
} else if strings.Contains(srcLower, ".png") {
return LoadPng(src)
} else {
log.HandleError(fmt.Errorf(src), "Unknown image format")
}
return nil
}

func LoadPng(src string) Image {

file, err := os.Open(src)

if err != nil {
log.Fatalf("Could not open png '%s' : %s", src, err)
}

img, err := png.Decode(file)
if err != nil {
log.Fatalf("PNG decoding failed on image '%s' : %s", src, err)
}

return &SingleImage{
frame: toRGBA(img),
}
}

func LoadGif(src string) *AnimatedImage {

b, err := ioutil.ReadFile(src)
if err != nil {
log.Fatalf("Could not open gif '%s' : %s", src, err)
}

reader := bytes.NewReader(b)

img, err := gif.DecodeAll(reader)
if err != nil {
log.Fatalf("Gif decoding failed on image '%s' : %s", src, err)
}

var delays = []int{}
var frames = []*image.RGBA{}

for _, frame := range img.Frames {
delays = append(delays, int(frame.DelayTime))
frames = append(frames, toRGBA(frame.FrameImage))
}

loops := img.Header.LoopCount
if loops == 0 {
loops = -1
}

return &AnimatedImage{
frames:          frames,
delays:          delays,
remainingLoops:  loops,
frameRequest:    make(chan bool),
delayAdjustment: 1.0,
}
}

func toRGBA(in image.Image) *image.RGBA {
bounds := in.Bounds()
out := image.NewRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
draw.Draw(out, out.Bounds(), in, bounds.Min, draw.Over)
return out
}

*/
<commit_msg>Jpeg support, better support for animated gifs<commit_after>package util

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	"image/jpeg"
	"image/png"
	"math"
	"os"
	"strings"
	"time"

	"github.com/ninjasphere/go-ninja/logger"
)

var log = logger.GetLogger("ui/util")

// Approx. framerate of the display
const fps = 30

// How long each frame is displayed for
const frameTime = time.Second / fps

// If a frame's delay is under this duration, we will display it
// for a certain number of frames, rather than for a time.
const adjustDelayUnder = time.Millisecond * 300

type Image interface {
	GetNextFrame() *image.RGBA
	GetPositionFrame(position float64, blend bool) *image.RGBA
}

type SingleImage struct {
	frame *image.RGBA
}

func NewSingleImage(frame *image.RGBA) *SingleImage {
	return &SingleImage{frame}
}

func NewSingleImageConvert(frame image.Image) *SingleImage {
	return &SingleImage{toRGBA(frame)}
}

func (i *SingleImage) GetNextFrame() *image.RGBA {
	return i.frame
}

func (i *SingleImage) GetPositionFrame(position float64, blend bool) *image.RGBA {
	return i.frame
}

/* MaskImage wraps an image so that the red level is used as the transparency.
It allows animated gifs to be used as masks, with 255 levels of alpha. */
type MaskImage struct {
	Image Image
}

func (i *MaskImage) GetNextFrame() *image.RGBA {
	in := i.Image.GetNextFrame()
	out := image.NewRGBA(image.Rect(0, 0, 16, 16))

	for pos := 0; (pos + 3) < len(in.Pix); pos = pos + 4 {
		out.Pix[pos+3] = 255 - in.Pix[pos]
	}

	return out
}

func (i *MaskImage) GetPositionFrame(position float64, blend bool) *image.RGBA {
	return i.Image.GetPositionFrame(position, blend)
}

type AnimatedImage struct {
	frameRequest    chan bool
	pos             int
	frames          []*image.RGBA
	delays          []int
	remainingLoops  int
	started         bool
	delayAdjustment float64
}

func (i *AnimatedImage) GetNextFrame() *image.RGBA {

	if !i.started {
		i.started = true
		i.start()
	}

	frame := i.frames[i.pos]

	select {
	case i.frameRequest <- true:
	default:
	}

	return frame
}

func (i *AnimatedImage) start() {

	go func() {
		for {
			delay := time.Duration(float64(i.delays[i.pos])*i.delayAdjustment) * 10 * time.Millisecond

			if delay < adjustDelayUnder {

				// Rounded to nearest int
				framesToDisplay := int(math.Floor((float64(delay) / float64(frameTime)) + 0.5))

				// Show for at least one frame
				if framesToDisplay == 0 {
					framesToDisplay = 1
				}

				//log.Printf("Frame wanted a delay of %d so showing for %d frames", delay, framesToDisplay)

				for x := 0; x < framesToDisplay; x++ {
					// Wait till this frame has been taken
					<-i.frameRequest
				}

			} else {
				// Just sleep. At these amounts noone will notice +-1 frame
				//log.Printf("Sleeping frame %d for %d", i.pos, delay)
				time.Sleep(delay)
			}

			// That was the last frame
			if i.pos == len(i.frames)-1 {

				if i.remainingLoops == -1 {
					// Start again, we are looping forever
					i.pos = 0
				} else if i.remainingLoops > 0 {
					// Start again, we still have at least one loop remaining
					i.pos = 0
					i.remainingLoops--
				} else {
					// We're done, this frame gets shown forever *drops mic*.
					break
				}
			} else {
				i.pos++
			}

		}
	}()

}

// GetPositionFrame returns the frame corresponding to the position given 0....1
func (i *AnimatedImage) GetPositionFrame(position float64, blend bool) *image.RGBA {

	relativePosition := position * float64(len(i.frames)-1)

	previousFrame := int(math.Floor(relativePosition))
	nextFrame := int(math.Ceil(relativePosition))

	framePosition := math.Mod(relativePosition, 1)

	//log.Debugf("GetPositionFrame. Frames:%d Position:%f RelativePosition:%f FramePosition:%f PreviousFrame:%d NextFrame:%d", len(i.frames), position, relativePosition, framePosition, previousFrame, nextFrame)
	if !blend || previousFrame == nextFrame {
		// We can just send back a single frame
		return i.frames[previousFrame]
	}

	maskNext := image.NewUniform(color.Alpha{uint8(255 * framePosition)})

	frame := image.NewRGBA(image.Rect(0, 0, 16, 16))

	draw.Draw(frame, frame.Bounds(), i.frames[previousFrame], image.Point{0, 0}, draw.Src)
	draw.DrawMask(frame, frame.Bounds(), i.frames[nextFrame], image.Point{0, 0}, maskNext, image.Point{0, 0}, draw.Over)

	return frame
}

func LoadImage(src string) Image {
	srcLower := strings.ToLower(src)

	if strings.Contains(srcLower, ".gif") {
		return LoadGif(src)
	} else if strings.Contains(srcLower, ".png") {
		return LoadPng(src)
	} else if strings.Contains(srcLower, ".jpg") {
		return LoadJpeg(src)
	} else {
		log.HandleError(fmt.Errorf(src), "Unknown image format")
	}
	return nil
}

func LoadPng(src string) Image {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open png '%s' : %s", src, err)
	}

	img, err := png.Decode(file)
	if err != nil {
		log.Fatalf("PNG decoding failed on image '%s' : %s", src, err)
	}

	return &SingleImage{
		frame: toRGBA(img),
	}
}

func LoadJpeg(src string) Image {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open jpeg '%s' : %s", src, err)
	}

	img, err := jpeg.Decode(file)
	if err != nil {
		log.Fatalf("JPG decoding failed on image '%s' : %s", src, err)
	}

	return &SingleImage{
		frame: toRGBA(img),
	}
}

func LoadGif(src string) *AnimatedImage {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open gif '%s' : %s", src, err)
	}

	img, err := gif.DecodeAll(file)
	if err != nil {
		log.Fatalf("Gif decoding failed on image '%s' : %s", src, err)
	}

	var frames = []*image.RGBA{}

	last := image.NewRGBA(image.Rect(0, 0, 16, 16))
	for _, frame := range img.Image {
		last = palettedToRGBA(last, frame)
		frames = append(frames, last)
	}

	loops := img.LoopCount
	if loops == 0 {
		loops = -1
	} else if loops == -1 {
		loops = 0
	}

	return &AnimatedImage{
		frames:          frames,
		delays:          img.Delay,
		remainingLoops:  loops,
		frameRequest:    make(chan bool),
		delayAdjustment: 1.0,
	}
}

func toRGBA(in image.Image) *image.RGBA {
	bounds := in.Bounds()
	out := image.NewRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
	draw.Draw(out, out.Bounds(), in, bounds.Min, draw.Src)
	return out
}

func palettedToRGBA(last *image.RGBA, in *image.Paletted) *image.RGBA {
	out := image.NewRGBA(image.Rect(0, 0, 16, 16))

	draw.Draw(out, out.Bounds(), last, last.Rect.Min, draw.Src)
	draw.Draw(out, in.Bounds(), in, in.Rect.Min, draw.Over)

	return out
}

/*

Alt version using different gif decoder... doesn't work

package util

import (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"io/ioutil"
"math"
"os"
"strings"
"time"

"github.com/xanthousphoenix/go-gif"
)

// Approx. framerate of the display
const fps = 30

// How long each frame is displayed for
const frameTime = time.Second / fps

// If a frame's delay is under this duration, we will display it
// for a certain number of frames, rather than for a time.
const adjustDelayUnder = time.Millisecond * 300

type Image interface {
GetNextFrame() *image.RGBA
GetPositionFrame(position float64, blend bool) *image.RGBA
}

type SingleImage struct {
frame *image.RGBA
}

func NewSingleImage(frame *image.RGBA) *SingleImage {
return &SingleImage{frame}
}

func (i *SingleImage) GetNextFrame() *image.RGBA {
return i.frame
}

func (i *SingleImage) GetPositionFrame(position float64, blend bool) *image.RGBA {
return i.frame
}

type AnimatedImage struct {
frameRequest    chan bool
pos             int
frames          []*image.RGBA
delays          []int
remainingLoops  int
started         bool
delayAdjustment float64
}

func (i *AnimatedImage) GetNextFrame() *image.RGBA {

if !i.started {
i.started = true
i.start()
}

frame := i.frames[i.pos]

select {
case i.frameRequest <- true:
default:
}

return frame
}

func (i *AnimatedImage) start() {

go func() {
for {
delay := time.Duration(float64(i.delays[i.pos])*i.delayAdjustment) * 10 * time.Millisecond

if delay < adjustDelayUnder {

// Rounded to nearest int
framesToDisplay := int(math.Floor((float64(delay) / float64(frameTime)) + 0.5))

// Show for at least one frame
if framesToDisplay == 0 {
framesToDisplay = 1
}

//log.Printf("Frame wanted a delay of %d so showing for %d frames", delay, framesToDisplay)

for x := 0; x < framesToDisplay; x++ {
// Wait till this frame has been taken
<-i.frameRequest
}

} else {
// Just sleep. At these amounts noone will notice +-1 frame
//log.Printf("Sleeping frame %d for %d", i.pos, delay)
time.Sleep(delay)
}

// That was the last frame
if i.pos == len(i.frames)-1 {

if i.remainingLoops == -1 {
// Start again, we are looping forever
i.pos = 0
} else if i.remainingLoops > 0 {
// Start again, we still have at least one loop remaining
i.pos = 0
i.remainingLoops--
} else {
// We're done, this frame gets shown forever *drops mic*.
break
}
} else {
i.pos++
}

}
}()

}

// GetPositionFrame returns the frame corresponding to the position given 0....1
func (i *AnimatedImage) GetPositionFrame(position float64, blend bool) *image.RGBA {

relativePosition := position * float64(len(i.frames)-1)

previousFrame := int(math.Floor(relativePosition))
nextFrame := int(math.Ceil(relativePosition))

framePosition := math.Mod(relativePosition, 1)

//log.Debugf("GetPositionFrame. Frames:%d Position:%f RelativePosition:%f FramePosition:%f PreviousFrame:%d NextFrame:%d", len(i.frames), position, relativePosition, framePosition, previousFrame, nextFrame)
if !blend || previousFrame == nextFrame {
// We can just send back a single frame
return i.frames[previousFrame]
}

maskNext := image.NewUniform(color.Alpha{uint8(255 * framePosition)})

frame := image.NewRGBA(image.Rect(0, 0, 16, 16))

draw.Draw(frame, frame.Bounds(), i.frames[previousFrame], image.Point{0, 0}, draw.Src)
draw.DrawMask(frame, frame.Bounds(), i.frames[nextFrame], image.Point{0, 0}, maskNext, image.Point{0, 0}, draw.Over)

return frame
}

func LoadImage(src string) Image {
srcLower := strings.ToLower(src)

if strings.Contains(srcLower, ".gif") {
return LoadGif(src)
} else if strings.Contains(srcLower, ".png") {
return LoadPng(src)
} else {
log.HandleError(fmt.Errorf(src), "Unknown image format")
}
return nil
}

func LoadPng(src string) Image {

file, err := os.Open(src)

if err != nil {
log.Fatalf("Could not open png '%s' : %s", src, err)
}

img, err := png.Decode(file)
if err != nil {
log.Fatalf("PNG decoding failed on image '%s' : %s", src, err)
}

return &SingleImage{
frame: toRGBA(img),
}
}

func LoadGif(src string) *AnimatedImage {

b, err := ioutil.ReadFile(src)
if err != nil {
log.Fatalf("Could not open gif '%s' : %s", src, err)
}

reader := bytes.NewReader(b)

img, err := gif.DecodeAll(reader)
if err != nil {
log.Fatalf("Gif decoding failed on image '%s' : %s", src, err)
}

var delays = []int{}
var frames = []*image.RGBA{}

for _, frame := range img.Frames {
delays = append(delays, int(frame.DelayTime))
frames = append(frames, toRGBA(frame.FrameImage))
}

loops := img.Header.LoopCount
if loops == 0 {
loops = -1
}

return &AnimatedImage{
frames:          frames,
delays:          delays,
remainingLoops:  loops,
frameRequest:    make(chan bool),
delayAdjustment: 1.0,
}
}

func toRGBA(in image.Image) *image.RGBA {
bounds := in.Bounds()
out := image.NewRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
draw.Draw(out, out.Bounds(), in, bounds.Min, draw.Over)
return out
}

*/
<|endoftext|>


<commit_before>// Map chunks

package chunkymonkey

import (
    "bytes"
    "io"
    "os"
    "log"
    "path"

    "nbt/nbt"
    "chunkymonkey/proto"
    .   "chunkymonkey/types"
)

// A chunk is slice of the world map
type Chunk struct {
    mgr        *ChunkManager
    XZ         ChunkXZ
    Blocks     []byte
    BlockData  []byte
    BlockLight []byte
    SkyLight   []byte
    HeightMap  []byte
}

func blockIndex(subLoc *SubChunkXYZ) (index int32, shift byte, err bool) {
    if subLoc.X < 0 || subLoc.Y < 0 || subLoc.Z < 0 || subLoc.X >= ChunkSizeX || subLoc.Y >= ChunkSizeY || subLoc.Z >= ChunkSizeZ {
        err = true
        index = 0
    } else {
        err = false

        index = int32(subLoc.Y + (subLoc.Z * ChunkSizeY) + (subLoc.X * ChunkSizeY * ChunkSizeZ))

        if index%2 == 0 {
            // Low nibble
            shift = 0
        } else {
            // High nibble
            shift = 4
        }
    }
    return
}

// Sets a block and its data. Returns true if the block was not changed.
func (chunk *Chunk) SetBlock(subLoc *SubChunkXYZ, blockType BlockID, blockMetadata byte) (err bool) {
    index, shift, err := blockIndex(subLoc)
    if err {
        return
    }

    chunk.Blocks[index] = byte(blockType)

    mask := byte(0x0f) << shift
    twoBlockData := chunk.BlockData[index/2]
    twoBlockData = ((blockMetadata << shift) & mask) | (twoBlockData & ^mask)
    chunk.BlockData[index/2] = twoBlockData

    // Tell players that the block was destroyed
    packet := &bytes.Buffer{}
    proto.WriteBlockChange(packet, chunk.XZ.ToBlockXYZ(subLoc), BlockIDAir, 0)
    chunk.mgr.game.MulticastChunkPacket(packet.Bytes(), chunk.XZ)

    return
}

// Returns information about the block at the given location. err is true if
// subLoc is outside of the chunk.
func (chunk *Chunk) GetBlock(subLoc *SubChunkXYZ) (blockType BlockID, err bool) {
    index, _, err := blockIndex(subLoc)
    if err {
        return
    }

    blockType = BlockID(chunk.Blocks[index])
    return
}

// Send chunk data down network connection
func (chunk *Chunk) SendChunkData(writer io.Writer) (err os.Error) {
    return proto.WriteMapChunk(writer, &chunk.XZ, chunk.Blocks, chunk.BlockData, chunk.BlockLight, chunk.SkyLight)
}

// Load a chunk from its NBT representation
func loadChunk(reader io.Reader) (chunk *Chunk, err os.Error) {
    level, err := nbt.Read(reader)
    if err != nil {
        return
    }

    chunk = &Chunk{
        XZ: ChunkXZ{
            X:  ChunkCoord(level.Lookup("/Level/xPos").(*nbt.Int).Value),
            Z:  ChunkCoord(level.Lookup("/Level/zPos").(*nbt.Int).Value),
        },
        Blocks:     level.Lookup("/Level/Blocks").(*nbt.ByteArray).Value,
        BlockData:  level.Lookup("/Level/Data").(*nbt.ByteArray).Value,
        SkyLight:   level.Lookup("/Level/SkyLight").(*nbt.ByteArray).Value,
        BlockLight: level.Lookup("/Level/BlockLight").(*nbt.ByteArray).Value,
        HeightMap:  level.Lookup("/Level/HeightMap").(*nbt.ByteArray).Value,
    }
    return
}

// ChunkManager contains all chunks and can look them up
type ChunkManager struct {
    game      *Game
    worldPath string
    chunks    map[uint64]*Chunk
}

func NewChunkManager(worldPath string) *ChunkManager {
    return &ChunkManager{
        worldPath: worldPath,
        chunks:    make(map[uint64]*Chunk),
    }
}

func base36Encode(n int32) (s string) {
    alphabet := "0123456789abcdefghijklmnopqrstuvwxyz"
    negative := false

    if n < 0 {
        n = -n
        negative = true
    }
    if n == 0 {
        return "0"
    }

    for n != 0 {
        i := n % int32(len(alphabet))
        n /= int32(len(alphabet))
        s = string(alphabet[i:i+1]) + s
    }
    if negative {
        s = "-" + s
    }
    return
}

func (mgr *ChunkManager) chunkPath(loc ChunkXZ) string {
    return path.Join(mgr.worldPath, base36Encode(int32(loc.X&63)), base36Encode(int32(loc.Z&63)),
        "c."+base36Encode(int32(loc.X))+"."+base36Encode(int32(loc.Z))+".dat")
}

// Get a chunk at given coordinates
func (mgr *ChunkManager) Get(loc ChunkXZ) (chunk *Chunk) {
    // FIXME this function looks subject to race conditions with itself
    key := uint64(loc.X)<<32 | uint64(uint32(loc.Z))
    chunk, ok := mgr.chunks[key]
    if ok {
        return
    }

    file, err := os.Open(mgr.chunkPath(loc), os.O_RDONLY, 0)
    if err != nil {
        log.Exit("ChunkManager.Get: ", err.String())
    }

    chunk, err = loadChunk(file)
    chunk.mgr = mgr
    file.Close()
    if err != nil {
        log.Exit("ChunkManager.loadChunk: ", err.String())
    }

    mgr.chunks[key] = chunk
    return
}

// Return a channel to iterate over all chunks within a chunk's radius
func (mgr *ChunkManager) ChunksInRadius(loc *ChunkXZ) (c chan *Chunk) {
    c = make(chan *Chunk)
    go func() {
        curChunkXZ := ChunkXZ{0, 0}
        for z := loc.Z - ChunkRadius; z <= loc.Z+ChunkRadius; z++ {
            for x := loc.X - ChunkRadius; x <= loc.X+ChunkRadius; x++ {
                curChunkXZ.X, curChunkXZ.Z = x, z
                c <- mgr.Get(curChunkXZ)
            }
        }
        close(c)
    }()
    return
}

// Return a channel to iterate over all chunks within a player's radius
func (mgr *ChunkManager) ChunksInPlayerRadius(player *Player) chan *Chunk {
    playerChunkXZ := player.position.ToChunkXZ()
    return mgr.ChunksInRadius(&playerChunkXZ)
}
<commit_msg>Fix for block setting telling clients that block was destroyed.<commit_after>// Map chunks

package chunkymonkey

import (
    "bytes"
    "io"
    "os"
    "log"
    "path"

    "nbt/nbt"
    "chunkymonkey/proto"
    .   "chunkymonkey/types"
)

// A chunk is slice of the world map
type Chunk struct {
    mgr        *ChunkManager
    XZ         ChunkXZ
    Blocks     []byte
    BlockData  []byte
    BlockLight []byte
    SkyLight   []byte
    HeightMap  []byte
}

func blockIndex(subLoc *SubChunkXYZ) (index int32, shift byte, err bool) {
    if subLoc.X < 0 || subLoc.Y < 0 || subLoc.Z < 0 || subLoc.X >= ChunkSizeX || subLoc.Y >= ChunkSizeY || subLoc.Z >= ChunkSizeZ {
        err = true
        index = 0
    } else {
        err = false

        index = int32(subLoc.Y + (subLoc.Z * ChunkSizeY) + (subLoc.X * ChunkSizeY * ChunkSizeZ))

        if index%2 == 0 {
            // Low nibble
            shift = 0
        } else {
            // High nibble
            shift = 4
        }
    }
    return
}

// Sets a block and its data. Returns true if the block was not changed.
func (chunk *Chunk) SetBlock(subLoc *SubChunkXYZ, blockType BlockID, blockMetadata byte) (err bool) {
    index, shift, err := blockIndex(subLoc)
    if err {
        return
    }

    chunk.Blocks[index] = byte(blockType)

    mask := byte(0x0f) << shift
    twoBlockData := chunk.BlockData[index/2]
    twoBlockData = ((blockMetadata << shift) & mask) | (twoBlockData & ^mask)
    chunk.BlockData[index/2] = twoBlockData

    // Tell players that the block was destroyed
    packet := &bytes.Buffer{}
    proto.WriteBlockChange(packet, chunk.XZ.ToBlockXYZ(subLoc), blockType, blockMetadata)
    chunk.mgr.game.MulticastChunkPacket(packet.Bytes(), chunk.XZ)

    return
}

// Returns information about the block at the given location. err is true if
// subLoc is outside of the chunk.
func (chunk *Chunk) GetBlock(subLoc *SubChunkXYZ) (blockType BlockID, err bool) {
    index, _, err := blockIndex(subLoc)
    if err {
        return
    }

    blockType = BlockID(chunk.Blocks[index])
    return
}

// Send chunk data down network connection
func (chunk *Chunk) SendChunkData(writer io.Writer) (err os.Error) {
    return proto.WriteMapChunk(writer, &chunk.XZ, chunk.Blocks, chunk.BlockData, chunk.BlockLight, chunk.SkyLight)
}

// Load a chunk from its NBT representation
func loadChunk(reader io.Reader) (chunk *Chunk, err os.Error) {
    level, err := nbt.Read(reader)
    if err != nil {
        return
    }

    chunk = &Chunk{
        XZ: ChunkXZ{
            X:  ChunkCoord(level.Lookup("/Level/xPos").(*nbt.Int).Value),
            Z:  ChunkCoord(level.Lookup("/Level/zPos").(*nbt.Int).Value),
        },
        Blocks:     level.Lookup("/Level/Blocks").(*nbt.ByteArray).Value,
        BlockData:  level.Lookup("/Level/Data").(*nbt.ByteArray).Value,
        SkyLight:   level.Lookup("/Level/SkyLight").(*nbt.ByteArray).Value,
        BlockLight: level.Lookup("/Level/BlockLight").(*nbt.ByteArray).Value,
        HeightMap:  level.Lookup("/Level/HeightMap").(*nbt.ByteArray).Value,
    }
    return
}

// ChunkManager contains all chunks and can look them up
type ChunkManager struct {
    game      *Game
    worldPath string
    chunks    map[uint64]*Chunk
}

func NewChunkManager(worldPath string) *ChunkManager {
    return &ChunkManager{
        worldPath: worldPath,
        chunks:    make(map[uint64]*Chunk),
    }
}

func base36Encode(n int32) (s string) {
    alphabet := "0123456789abcdefghijklmnopqrstuvwxyz"
    negative := false

    if n < 0 {
        n = -n
        negative = true
    }
    if n == 0 {
        return "0"
    }

    for n != 0 {
        i := n % int32(len(alphabet))
        n /= int32(len(alphabet))
        s = string(alphabet[i:i+1]) + s
    }
    if negative {
        s = "-" + s
    }
    return
}

func (mgr *ChunkManager) chunkPath(loc ChunkXZ) string {
    return path.Join(mgr.worldPath, base36Encode(int32(loc.X&63)), base36Encode(int32(loc.Z&63)),
        "c."+base36Encode(int32(loc.X))+"."+base36Encode(int32(loc.Z))+".dat")
}

// Get a chunk at given coordinates
func (mgr *ChunkManager) Get(loc ChunkXZ) (chunk *Chunk) {
    // FIXME this function looks subject to race conditions with itself
    key := uint64(loc.X)<<32 | uint64(uint32(loc.Z))
    chunk, ok := mgr.chunks[key]
    if ok {
        return
    }

    file, err := os.Open(mgr.chunkPath(loc), os.O_RDONLY, 0)
    if err != nil {
        log.Exit("ChunkManager.Get: ", err.String())
    }

    chunk, err = loadChunk(file)
    chunk.mgr = mgr
    file.Close()
    if err != nil {
        log.Exit("ChunkManager.loadChunk: ", err.String())
    }

    mgr.chunks[key] = chunk
    return
}

// Return a channel to iterate over all chunks within a chunk's radius
func (mgr *ChunkManager) ChunksInRadius(loc *ChunkXZ) (c chan *Chunk) {
    c = make(chan *Chunk)
    go func() {
        curChunkXZ := ChunkXZ{0, 0}
        for z := loc.Z - ChunkRadius; z <= loc.Z+ChunkRadius; z++ {
            for x := loc.X - ChunkRadius; x <= loc.X+ChunkRadius; x++ {
                curChunkXZ.X, curChunkXZ.Z = x, z
                c <- mgr.Get(curChunkXZ)
            }
        }
        close(c)
    }()
    return
}

// Return a channel to iterate over all chunks within a player's radius
func (mgr *ChunkManager) ChunksInPlayerRadius(player *Player) chan *Chunk {
    playerChunkXZ := player.position.ToChunkXZ()
    return mgr.ChunksInRadius(&playerChunkXZ)
}
<|endoftext|>


<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 main

import (
	"cmd/internal/notsha256"
	"flag"
	"fmt"
	"go/build"
	"internal/platform"
	"internal/testenv"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

var tmp, exe string // populated by buildObjdump

func TestMain(m *testing.M) {
	if !testenv.HasGoBuild() {
		return
	}

	var exitcode int
	if err := buildObjdump(); err == nil {
		exitcode = m.Run()
	} else {
		fmt.Println(err)
		exitcode = 1
	}
	os.RemoveAll(tmp)
	os.Exit(exitcode)
}

func buildObjdump() error {
	var err error
	tmp, err = os.MkdirTemp("", "TestObjDump")
	if err != nil {
		return fmt.Errorf("TempDir failed: %v", err)
	}

	exe = filepath.Join(tmp, "testobjdump.exe")
	gotool, err := testenv.GoTool()
	if err != nil {
		return err
	}
	out, err := exec.Command(gotool, "build", "-o", exe, "cmd/objdump").CombinedOutput()
	if err != nil {
		os.RemoveAll(tmp)
		return fmt.Errorf("go build -o %v cmd/objdump: %v\n%s", exe, err, string(out))
	}

	return nil
}

var x86Need = []string{ // for both 386 and AMD64
	"JMP main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var amd64GnuNeed = []string{
	"jmp",
	"callq",
	"cmpb",
}

var i386GnuNeed = []string{
	"jmp",
	"call",
	"cmp",
}

var armNeed = []string{
	"B main.main(SB)",
	"BL main.Println(SB)",
	"RET",
}

var arm64Need = []string{
	"JMP main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var armGnuNeed = []string{ // for both ARM and AMR64
	"ldr",
	"bl",
	"cmp",
}

var ppcNeed = []string{
	"BR main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var ppcPIENeed = []string{
	"BR",
	"CALL",
	"RET",
}

var ppcGnuNeed = []string{
	"mflr",
	"lbz",
	"beq",
}

func mustHaveDisasm(t *testing.T) {
	switch runtime.GOARCH {
	case "loong64":
		t.Skipf("skipping on %s", runtime.GOARCH)
	case "mips", "mipsle", "mips64", "mips64le":
		t.Skipf("skipping on %s, issue 12559", runtime.GOARCH)
	case "riscv64":
		t.Skipf("skipping on %s, issue 36738", runtime.GOARCH)
	case "s390x":
		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
	}
}

var target = flag.String("target", "", "test disassembly of `goos/goarch` binary")

// objdump is fully cross platform: it can handle binaries
// from any known operating system and architecture.
// We could in principle add binaries to testdata and check
// all the supported systems during this test. However, the
// binaries would be about 1 MB each, and we don't want to
// add that much junk to the hg repository. Instead, build a
// binary for the current system (only) and test that objdump
// can handle that one.

func testDisasm(t *testing.T, srcfname string, printCode bool, printGnuAsm bool, flags ...string) {
	mustHaveDisasm(t)
	goarch := runtime.GOARCH
	if *target != "" {
		f := strings.Split(*target, "/")
		if len(f) != 2 {
			t.Fatalf("-target argument must be goos/goarch")
		}
		defer os.Setenv("GOOS", os.Getenv("GOOS"))
		defer os.Setenv("GOARCH", os.Getenv("GOARCH"))
		os.Setenv("GOOS", f[0])
		os.Setenv("GOARCH", f[1])
		goarch = f[1]
	}

	hash := notsha256.Sum256([]byte(fmt.Sprintf("%v-%v-%v-%v", srcfname, flags, printCode, printGnuAsm)))
	hello := filepath.Join(tmp, fmt.Sprintf("hello-%x.exe", hash))
	args := []string{"build", "-o", hello}
	args = append(args, flags...)
	args = append(args, srcfname)
	cmd := exec.Command(testenv.GoToolPath(t), args...)
	// "Bad line" bug #36683 is sensitive to being run in the source directory.
	cmd.Dir = "testdata"
	// Ensure that the source file location embedded in the binary matches our
	// actual current GOROOT, instead of GOROOT_FINAL if set.
	cmd.Env = append(os.Environ(), "GOROOT_FINAL=")
	t.Logf("Running %v", cmd.Args)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go build %s: %v\n%s", srcfname, err, out)
	}
	need := []string{
		"TEXT main.main(SB)",
	}

	if printCode {
		need = append(need, `	Println("hello, world")`)
	} else {
		need = append(need, srcfname+":6")
	}

	switch goarch {
	case "amd64", "386":
		need = append(need, x86Need...)
	case "arm":
		need = append(need, armNeed...)
	case "arm64":
		need = append(need, arm64Need...)
	case "ppc64", "ppc64le":
		var pie bool
		for _, flag := range flags {
			if flag == "-buildmode=pie" {
				pie = true
				break
			}
		}
		if pie {
			// In PPC64 PIE binaries we use a "local entry point" which is
			// function symbol address + 8. Currently we don't symbolize that.
			// Expect a different output.
			need = append(need, ppcPIENeed...)
		} else {
			need = append(need, ppcNeed...)
		}
	}

	if printGnuAsm {
		switch goarch {
		case "amd64":
			need = append(need, amd64GnuNeed...)
		case "386":
			need = append(need, i386GnuNeed...)
		case "arm", "arm64":
			need = append(need, armGnuNeed...)
		case "ppc64", "ppc64le":
			need = append(need, ppcGnuNeed...)
		}
	}
	args = []string{
		"-s", "main.main",
		hello,
	}

	if printCode {
		args = append([]string{"-S"}, args...)
	}

	if printGnuAsm {
		args = append([]string{"-gnu"}, args...)
	}
	cmd = exec.Command(exe, args...)
	cmd.Dir = "testdata" // "Bad line" bug #36683 is sensitive to being run in the source directory
	out, err = cmd.CombinedOutput()
	t.Logf("Running %v", cmd.Args)

	if err != nil {
		exename := srcfname[:len(srcfname)-len(filepath.Ext(srcfname))] + ".exe"
		t.Fatalf("objdump %q: %v\n%s", exename, err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if goarch == "386" {
		if strings.Contains(text, "(IP)") {
			t.Errorf("disassembly contains PC-Relative addressing on 386")
			ok = false
		}
	}

	if !ok || testing.Verbose() {
		t.Logf("full disassembly:\n%s", text)
	}
}

func testGoAndCgoDisasm(t *testing.T, printCode bool, printGnuAsm bool) {
	t.Parallel()
	testDisasm(t, "fmthello.go", printCode, printGnuAsm)
	if build.Default.CgoEnabled {
		testDisasm(t, "fmthellocgo.go", printCode, printGnuAsm)
	}
}

func TestDisasm(t *testing.T) {
	testGoAndCgoDisasm(t, false, false)
}

func TestDisasmCode(t *testing.T) {
	testGoAndCgoDisasm(t, true, false)
}

func TestDisasmGnuAsm(t *testing.T) {
	testGoAndCgoDisasm(t, false, true)
}

func TestDisasmExtld(t *testing.T) {
	testenv.MustHaveCGO(t)
	switch runtime.GOOS {
	case "plan9", "windows":
		t.Skipf("skipping on %s", runtime.GOOS)
	}
	t.Parallel()
	testDisasm(t, "fmthello.go", false, false, "-ldflags=-linkmode=external")
}

func TestDisasmPIE(t *testing.T) {
	if !platform.BuildModeSupported("gc", "pie", runtime.GOOS, runtime.GOARCH) {
		t.Skipf("skipping on %s/%s, PIE buildmode not supported", runtime.GOOS, runtime.GOARCH)
	}
	t.Parallel()
	testDisasm(t, "fmthello.go", false, false, "-buildmode=pie")
}

func TestDisasmGoobj(t *testing.T) {
	mustHaveDisasm(t)

	importcfgfile := filepath.Join(tmp, "hello.importcfg")
	testenv.WriteImportcfg(t, importcfgfile, nil)

	hello := filepath.Join(tmp, "hello.o")
	args := []string{"tool", "compile", "-p=main", "-importcfg=" + importcfgfile, "-o", hello}
	args = append(args, "testdata/fmthello.go")
	out, err := exec.Command(testenv.GoToolPath(t), args...).CombinedOutput()
	if err != nil {
		t.Fatalf("go tool compile fmthello.go: %v\n%s", err, out)
	}
	need := []string{
		"main(SB)",
		"fmthello.go:6",
	}

	args = []string{
		"-s", "main",
		hello,
	}

	out, err = exec.Command(exe, args...).CombinedOutput()
	if err != nil {
		t.Fatalf("objdump fmthello.o: %v\n%s", err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if runtime.GOARCH == "386" {
		if strings.Contains(text, "(IP)") {
			t.Errorf("disassembly contains PC-Relative addressing on 386")
			ok = false
		}
	}
	if !ok {
		t.Logf("full disassembly:\n%s", text)
	}
}

func TestGoobjFileNumber(t *testing.T) {
	// Test that file table in Go object file is parsed correctly.
	testenv.MustHaveGoBuild(t)
	mustHaveDisasm(t)

	t.Parallel()

	tmpdir, err := os.MkdirTemp("", "TestGoobjFileNumber")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)

	obj := filepath.Join(tmpdir, "p.a")
	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", obj)
	cmd.Dir = filepath.Join("testdata/testfilenum")
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("build failed: %v\n%s", err, out)
	}

	cmd = exec.Command(exe, obj)
	out, err = cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("objdump failed: %v\n%s", err, out)
	}

	text := string(out)
	for _, s := range []string{"a.go", "b.go", "c.go"} {
		if !strings.Contains(text, s) {
			t.Errorf("output missing '%s'", s)
		}
	}

	if t.Failed() {
		t.Logf("output:\n%s", text)
	}
}

func TestGoObjOtherVersion(t *testing.T) {
	testenv.MustHaveExec(t)
	t.Parallel()

	obj := filepath.Join("testdata", "go116.o")
	cmd := exec.Command(exe, obj)
	out, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatalf("objdump go116.o succeeded unexpectedly")
	}
	if !strings.Contains(string(out), "go object of a different version") {
		t.Errorf("unexpected error message:\n%s", out)
	}
}
<commit_msg>cmd/objdump: skip PIE test when cgo is disabled and PIE needs external linking<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 main

import (
	"cmd/internal/notsha256"
	"flag"
	"fmt"
	"go/build"
	"internal/platform"
	"internal/testenv"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

var tmp, exe string // populated by buildObjdump

func TestMain(m *testing.M) {
	if !testenv.HasGoBuild() {
		return
	}

	var exitcode int
	if err := buildObjdump(); err == nil {
		exitcode = m.Run()
	} else {
		fmt.Println(err)
		exitcode = 1
	}
	os.RemoveAll(tmp)
	os.Exit(exitcode)
}

func buildObjdump() error {
	var err error
	tmp, err = os.MkdirTemp("", "TestObjDump")
	if err != nil {
		return fmt.Errorf("TempDir failed: %v", err)
	}

	exe = filepath.Join(tmp, "testobjdump.exe")
	gotool, err := testenv.GoTool()
	if err != nil {
		return err
	}
	out, err := exec.Command(gotool, "build", "-o", exe, "cmd/objdump").CombinedOutput()
	if err != nil {
		os.RemoveAll(tmp)
		return fmt.Errorf("go build -o %v cmd/objdump: %v\n%s", exe, err, string(out))
	}

	return nil
}

var x86Need = []string{ // for both 386 and AMD64
	"JMP main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var amd64GnuNeed = []string{
	"jmp",
	"callq",
	"cmpb",
}

var i386GnuNeed = []string{
	"jmp",
	"call",
	"cmp",
}

var armNeed = []string{
	"B main.main(SB)",
	"BL main.Println(SB)",
	"RET",
}

var arm64Need = []string{
	"JMP main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var armGnuNeed = []string{ // for both ARM and AMR64
	"ldr",
	"bl",
	"cmp",
}

var ppcNeed = []string{
	"BR main.main(SB)",
	"CALL main.Println(SB)",
	"RET",
}

var ppcPIENeed = []string{
	"BR",
	"CALL",
	"RET",
}

var ppcGnuNeed = []string{
	"mflr",
	"lbz",
	"beq",
}

func mustHaveDisasm(t *testing.T) {
	switch runtime.GOARCH {
	case "loong64":
		t.Skipf("skipping on %s", runtime.GOARCH)
	case "mips", "mipsle", "mips64", "mips64le":
		t.Skipf("skipping on %s, issue 12559", runtime.GOARCH)
	case "riscv64":
		t.Skipf("skipping on %s, issue 36738", runtime.GOARCH)
	case "s390x":
		t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
	}
}

var target = flag.String("target", "", "test disassembly of `goos/goarch` binary")

// objdump is fully cross platform: it can handle binaries
// from any known operating system and architecture.
// We could in principle add binaries to testdata and check
// all the supported systems during this test. However, the
// binaries would be about 1 MB each, and we don't want to
// add that much junk to the hg repository. Instead, build a
// binary for the current system (only) and test that objdump
// can handle that one.

func testDisasm(t *testing.T, srcfname string, printCode bool, printGnuAsm bool, flags ...string) {
	mustHaveDisasm(t)
	goarch := runtime.GOARCH
	if *target != "" {
		f := strings.Split(*target, "/")
		if len(f) != 2 {
			t.Fatalf("-target argument must be goos/goarch")
		}
		defer os.Setenv("GOOS", os.Getenv("GOOS"))
		defer os.Setenv("GOARCH", os.Getenv("GOARCH"))
		os.Setenv("GOOS", f[0])
		os.Setenv("GOARCH", f[1])
		goarch = f[1]
	}

	hash := notsha256.Sum256([]byte(fmt.Sprintf("%v-%v-%v-%v", srcfname, flags, printCode, printGnuAsm)))
	hello := filepath.Join(tmp, fmt.Sprintf("hello-%x.exe", hash))
	args := []string{"build", "-o", hello}
	args = append(args, flags...)
	args = append(args, srcfname)
	cmd := exec.Command(testenv.GoToolPath(t), args...)
	// "Bad line" bug #36683 is sensitive to being run in the source directory.
	cmd.Dir = "testdata"
	// Ensure that the source file location embedded in the binary matches our
	// actual current GOROOT, instead of GOROOT_FINAL if set.
	cmd.Env = append(os.Environ(), "GOROOT_FINAL=")
	t.Logf("Running %v", cmd.Args)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go build %s: %v\n%s", srcfname, err, out)
	}
	need := []string{
		"TEXT main.main(SB)",
	}

	if printCode {
		need = append(need, `	Println("hello, world")`)
	} else {
		need = append(need, srcfname+":6")
	}

	switch goarch {
	case "amd64", "386":
		need = append(need, x86Need...)
	case "arm":
		need = append(need, armNeed...)
	case "arm64":
		need = append(need, arm64Need...)
	case "ppc64", "ppc64le":
		var pie bool
		for _, flag := range flags {
			if flag == "-buildmode=pie" {
				pie = true
				break
			}
		}
		if pie {
			// In PPC64 PIE binaries we use a "local entry point" which is
			// function symbol address + 8. Currently we don't symbolize that.
			// Expect a different output.
			need = append(need, ppcPIENeed...)
		} else {
			need = append(need, ppcNeed...)
		}
	}

	if printGnuAsm {
		switch goarch {
		case "amd64":
			need = append(need, amd64GnuNeed...)
		case "386":
			need = append(need, i386GnuNeed...)
		case "arm", "arm64":
			need = append(need, armGnuNeed...)
		case "ppc64", "ppc64le":
			need = append(need, ppcGnuNeed...)
		}
	}
	args = []string{
		"-s", "main.main",
		hello,
	}

	if printCode {
		args = append([]string{"-S"}, args...)
	}

	if printGnuAsm {
		args = append([]string{"-gnu"}, args...)
	}
	cmd = exec.Command(exe, args...)
	cmd.Dir = "testdata" // "Bad line" bug #36683 is sensitive to being run in the source directory
	out, err = cmd.CombinedOutput()
	t.Logf("Running %v", cmd.Args)

	if err != nil {
		exename := srcfname[:len(srcfname)-len(filepath.Ext(srcfname))] + ".exe"
		t.Fatalf("objdump %q: %v\n%s", exename, err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if goarch == "386" {
		if strings.Contains(text, "(IP)") {
			t.Errorf("disassembly contains PC-Relative addressing on 386")
			ok = false
		}
	}

	if !ok || testing.Verbose() {
		t.Logf("full disassembly:\n%s", text)
	}
}

func testGoAndCgoDisasm(t *testing.T, printCode bool, printGnuAsm bool) {
	t.Parallel()
	testDisasm(t, "fmthello.go", printCode, printGnuAsm)
	if build.Default.CgoEnabled {
		testDisasm(t, "fmthellocgo.go", printCode, printGnuAsm)
	}
}

func TestDisasm(t *testing.T) {
	testGoAndCgoDisasm(t, false, false)
}

func TestDisasmCode(t *testing.T) {
	testGoAndCgoDisasm(t, true, false)
}

func TestDisasmGnuAsm(t *testing.T) {
	testGoAndCgoDisasm(t, false, true)
}

func TestDisasmExtld(t *testing.T) {
	testenv.MustHaveCGO(t)
	switch runtime.GOOS {
	case "plan9", "windows":
		t.Skipf("skipping on %s", runtime.GOOS)
	}
	t.Parallel()
	testDisasm(t, "fmthello.go", false, false, "-ldflags=-linkmode=external")
}

func TestDisasmPIE(t *testing.T) {
	if !platform.BuildModeSupported("gc", "pie", runtime.GOOS, runtime.GOARCH) {
		t.Skipf("skipping on %s/%s, PIE buildmode not supported", runtime.GOOS, runtime.GOARCH)
	}
	if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
		// require cgo on platforms that PIE needs external linking
		testenv.MustHaveCGO(t)
	}
	t.Parallel()
	testDisasm(t, "fmthello.go", false, false, "-buildmode=pie")
}

func TestDisasmGoobj(t *testing.T) {
	mustHaveDisasm(t)

	importcfgfile := filepath.Join(tmp, "hello.importcfg")
	testenv.WriteImportcfg(t, importcfgfile, nil)

	hello := filepath.Join(tmp, "hello.o")
	args := []string{"tool", "compile", "-p=main", "-importcfg=" + importcfgfile, "-o", hello}
	args = append(args, "testdata/fmthello.go")
	out, err := exec.Command(testenv.GoToolPath(t), args...).CombinedOutput()
	if err != nil {
		t.Fatalf("go tool compile fmthello.go: %v\n%s", err, out)
	}
	need := []string{
		"main(SB)",
		"fmthello.go:6",
	}

	args = []string{
		"-s", "main",
		hello,
	}

	out, err = exec.Command(exe, args...).CombinedOutput()
	if err != nil {
		t.Fatalf("objdump fmthello.o: %v\n%s", err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if runtime.GOARCH == "386" {
		if strings.Contains(text, "(IP)") {
			t.Errorf("disassembly contains PC-Relative addressing on 386")
			ok = false
		}
	}
	if !ok {
		t.Logf("full disassembly:\n%s", text)
	}
}

func TestGoobjFileNumber(t *testing.T) {
	// Test that file table in Go object file is parsed correctly.
	testenv.MustHaveGoBuild(t)
	mustHaveDisasm(t)

	t.Parallel()

	tmpdir, err := os.MkdirTemp("", "TestGoobjFileNumber")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)

	obj := filepath.Join(tmpdir, "p.a")
	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", obj)
	cmd.Dir = filepath.Join("testdata/testfilenum")
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("build failed: %v\n%s", err, out)
	}

	cmd = exec.Command(exe, obj)
	out, err = cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("objdump failed: %v\n%s", err, out)
	}

	text := string(out)
	for _, s := range []string{"a.go", "b.go", "c.go"} {
		if !strings.Contains(text, s) {
			t.Errorf("output missing '%s'", s)
		}
	}

	if t.Failed() {
		t.Logf("output:\n%s", text)
	}
}

func TestGoObjOtherVersion(t *testing.T) {
	testenv.MustHaveExec(t)
	t.Parallel()

	obj := filepath.Join("testdata", "go116.o")
	cmd := exec.Command(exe, obj)
	out, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatalf("objdump go116.o succeeded unexpectedly")
	}
	if !strings.Contains(string(out), "go object of a different version") {
		t.Errorf("unexpected error message:\n%s", out)
	}
}
<|endoftext|>


<commit_before>package tracking

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

const (
	host       = "http://api.mixpanel.com"
	trackPath  = "track"
	engagePath = "engage"
)

// engage constants
const (
	EngageSet     = "set"
	EngageSetOnce = "set_once"
	EngageAdd     = "add"
	EngageAppend  = "append"
	EngageUnion   = "union"
	EngageUnset   = "unset"
	EngageDelete  = "delete"
)

type client struct {
	token string
}

type eventData struct {
	Event string                 `json:"event"`
	Props map[string]interface{} `json:"properties"`
}

type UserEvent struct {
	DistinctId int64
	Name       string
	Props      map[string]interface{}
}

type UserEventStringed struct {
	DistinctId string
	Name       string
	Props      map[string]interface{}
}

type engageData struct {
	Token   string      `json:"$token"`
	Time    int64       `json:"$time"`
	Id      int64       `json:"$distinct_id"`
	Ip      string      `json:"$ip,omitempty"`
	Set     interface{} `json:"$set,omitempty"`
	SetOnce interface{} `json:"$set_once,omitempty"`
	Add     interface{} `json:"$add,omitempty"`
	Append  interface{} `json:"$append,omitempty"`
	Union   interface{} `json:"$union,omitempty"`
	Unset   interface{} `json:"$unset,omitempty"`
	Delete  interface{} `json:"$delete,omitempty"`
}

type engageDataStringed struct {
	Token   string      `json:"$token"`
	Time    int64       `json:"$time"`
	Id      string      `json:"$distinct_id"`
	Ip      string      `json:"$ip,omitempty"`
	Set     interface{} `json:"$set,omitempty"`
	SetOnce interface{} `json:"$set_once,omitempty"`
	Add     interface{} `json:"$add,omitempty"`
	Append  interface{} `json:"$append,omitempty"`
	Union   interface{} `json:"$union,omitempty"`
	Unset   interface{} `json:"$unset,omitempty"`
	Delete  interface{} `json:"$delete,omitempty"`
}

func New(token string) *client {
	return &client{
		token: token,
	}
}

func (mp *client) Track(event UserEvent, queryParams ...map[string]interface{}) error {
	u, err := mp.CreateLink(event, queryParams...)
	if err != nil {
		return err
	}

	// send request
	resp, err := http.Get(u)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

func (mp *client) CreateLink(event UserEvent, queryParams ...map[string]interface{}) (string, error) {
	data := &eventData{
		Event: event.Name,
		Props: map[string]interface{}{
			"time":        time.Now().Unix(), // default to now, can be overwritten by props
			"token":       mp.token,
			"distinct_id": strconv.FormatInt(event.DistinctId, 10),
		},
	}
	for k, v := range event.Props {
		data.Props[k] = v
	}

	marshaledData, err := json.Marshal(data)
	if err != nil {
		return "", err
	}

	u := fmt.Sprintf("%s/%s/?data=%s", host, trackPath,
		base64.StdEncoding.EncodeToString(marshaledData))

	parameters := url.Values{}
	// iterate over any query parameters
	for _, val := range queryParams {
		for k, v := range val {
			if str, ok := v.(string); ok {
				/* act on str */
				parameters.Add(k, str)
			} else {
				/* not string - int? */
				if in, ok := v.(int); ok {
					parameters.Add(k, strconv.Itoa(in))
				} else {
					continue
				}
			}
		}
	}
	// append encoded params to url if any
	if qs := parameters.Encode(); qs != "" {
		u += "&" + qs
	}

	return u, nil
}

func (mp *client) TrackBatch(events []UserEvent, queryParams ...map[string]interface{}) error {
	// enforce max batch size to 50 (mixpanel)
	maxBatch := 50
	if len(events) > maxBatch {
		moreEvents := events[maxBatch:]
		events = events[:maxBatch]
		defer mp.TrackBatch(moreEvents, queryParams...)
	}
	data := make([]*eventData, 0, maxBatch)
	for _, event := range events {
		d := &eventData{
			Event: event.Name,
			Props: map[string]interface{}{
				"time":        time.Now().Unix(), // default to now, can be overwritten by props
				"token":       mp.token,
				"distinct_id": strconv.FormatInt(event.DistinctId, 10),
			},
		}
		for k, v := range event.Props {
			d.Props[k] = v
		}
		data = append(data, d)
	}

	marshaledData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	u := fmt.Sprintf("%s/%s", host, trackPath)
	encodedData := "data=" + base64.StdEncoding.EncodeToString(marshaledData)

	parameters := url.Values{}
	// iterate over any query parameters
	for _, val := range queryParams {
		for k, v := range val {
			if str, ok := v.(string); ok {
				/* act on str */
				parameters.Add(k, str)
			} else {
				/* not string - int? */
				if in, ok := v.(int); ok {
					parameters.Add(k, strconv.Itoa(in))
				} else {
					continue
				}
			}
		}
	}
	// append encoded params to url if any
	if qs := parameters.Encode(); qs != "" {
		u += "?" + qs
	}
	// send request
	resp, err := http.Post(u, "application/x-www-form-urlencoded", bytes.NewBufferString(encodedData))
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

func (mp *client) Engage(uid int64, p map[string]interface{}, ip string) error {
	profileData := &engageData{
		Token: mp.token,
		Time:  time.Now().Unix(),
	}
	if uid != 0 {
		profileData.Id = uid
	}
	if ip != "" {
		profileData.Ip = ip
	}
	// should probably just add separate methods for each of these
	for k, v := range p {
		switch k {
		case EngageSet:
			profileData.Set = v
			break
		case EngageSetOnce:
			profileData.SetOnce = v
			break
		case EngageAdd:
			profileData.Add = v
			break
		case EngageAppend:
			profileData.Append = v
			break
		case EngageUnion:
			profileData.Union = v
			break
		case EngageUnset:
			profileData.Unset = v
			break
		case EngageDelete:
			profileData.Delete = v
			break
		}
	}

	marshalledData, err := json.Marshal(profileData)
	if err != nil {
		return err
	}

	url := fmt.Sprintf("%s/%s/?data=%s", host, engagePath, base64.StdEncoding.EncodeToString(marshalledData))

	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

func (mp *client) EngageString(id string, p map[string]interface{}, ip string) error {
	profileData := &engageDataStringed{
		Id:    id,
		Token: mp.token,
		Time:  time.Now().Unix(),
	}
	if ip != "" {
		profileData.Ip = ip
	}
	// should probably just add separate methods for each of these
	for k, v := range p {
		switch k {
		case EngageSet:
			profileData.Set = v
			break
		case EngageSetOnce:
			profileData.SetOnce = v
			break
		case EngageAdd:
			profileData.Add = v
			break
		case EngageAppend:
			profileData.Append = v
			break
		case EngageUnion:
			profileData.Union = v
			break
		case EngageUnset:
			profileData.Unset = v
			break
		case EngageDelete:
			profileData.Delete = v
			break
		}
	}

	marshalledData, err := json.Marshal(profileData)
	if err != nil {
		return err
	}

	url := fmt.Sprintf("%s/%s/?data=%s", host, engagePath, base64.StdEncoding.EncodeToString(marshalledData))

	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

func (mp *client) TrackString(event UserEventStringed, queryParams ...map[string]interface{}) error {
	data := &eventData{
		Event: event.Name,
		Props: map[string]interface{}{
			"time":        time.Now().Unix(), // default to now, can be overwritten by props
			"token":       mp.token,
			"distinct_id": event.DistinctId,
		},
	}
	for k, v := range event.Props {
		data.Props[k] = v
	}

	marshaledData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	u := fmt.Sprintf("%s/%s/?data=%s", host, trackPath,
		base64.StdEncoding.EncodeToString(marshaledData))

	parameters := url.Values{}
	// iterate over any query parameters
	for _, val := range queryParams {
		for k, v := range val {
			if str, ok := v.(string); ok {
				/* act on str */
				parameters.Add(k, str)
			} else {
				/* not string - int? */
				if in, ok := v.(int); ok {
					parameters.Add(k, strconv.Itoa(in))
				} else {
					continue
				}
			}
		}
	}
	// append encoded params to url if any
	if qs := parameters.Encode(); qs != "" {
		u += "&" + qs
	}
	// send request
	resp, err := http.Get(u)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}
<commit_msg>don't want to set time on links!!<commit_after>package tracking

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

const (
	host       = "http://api.mixpanel.com"
	trackPath  = "track"
	engagePath = "engage"
)

// engage constants
const (
	EngageSet     = "set"
	EngageSetOnce = "set_once"
	EngageAdd     = "add"
	EngageAppend  = "append"
	EngageUnion   = "union"
	EngageUnset   = "unset"
	EngageDelete  = "delete"
)

type client struct {
	token string
}

type eventData struct {
	Event string                 `json:"event"`
	Props map[string]interface{} `json:"properties"`
}

type UserEvent struct {
	DistinctId int64
	Name       string
	Props      map[string]interface{}
}

type UserEventStringed struct {
	DistinctId string
	Name       string
	Props      map[string]interface{}
}

type engageData struct {
	Token   string      `json:"$token"`
	Time    int64       `json:"$time"`
	Id      int64       `json:"$distinct_id"`
	Ip      string      `json:"$ip,omitempty"`
	Set     interface{} `json:"$set,omitempty"`
	SetOnce interface{} `json:"$set_once,omitempty"`
	Add     interface{} `json:"$add,omitempty"`
	Append  interface{} `json:"$append,omitempty"`
	Union   interface{} `json:"$union,omitempty"`
	Unset   interface{} `json:"$unset,omitempty"`
	Delete  interface{} `json:"$delete,omitempty"`
}

type engageDataStringed struct {
	Token   string      `json:"$token"`
	Time    int64       `json:"$time"`
	Id      string      `json:"$distinct_id"`
	Ip      string      `json:"$ip,omitempty"`
	Set     interface{} `json:"$set,omitempty"`
	SetOnce interface{} `json:"$set_once,omitempty"`
	Add     interface{} `json:"$add,omitempty"`
	Append  interface{} `json:"$append,omitempty"`
	Union   interface{} `json:"$union,omitempty"`
	Unset   interface{} `json:"$unset,omitempty"`
	Delete  interface{} `json:"$delete,omitempty"`
}

func New(token string) *client {
	return &client{
		token: token,
	}
}

func (mp *client) Track(event UserEvent, queryParams ...map[string]interface{}) error {
	u, err := mp.CreateLink(event, queryParams...)
	if err != nil {
		return err
	}

	// send request
	resp, err := http.Get(u)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

func (mp *client) CreateLink(event UserEvent, queryParams ...map[string]interface{}) (string, error) {
	data := &eventData{
		Event: event.Name,
		Props: map[string]interface{}{
			"token":       mp.token,
			"distinct_id": strconv.FormatInt(event.DistinctId, 10),
		},
	}
	for k, v := range event.Props {
		data.Props[k] = v
	}

	marshaledData, err := json.Marshal(data)
	if err != nil {
		return "", err
	}

	u := fmt.Sprintf("%s/%s/?data=%s", host, trackPath,
		base64.StdEncoding.EncodeToString(marshaledData))

	parameters := url.Values{}
	// iterate over any query parameters
	for _, val := range queryParams {
		for k, v := range val {
			if str, ok := v.(string); ok {
				/* act on str */
				parameters.Add(k, str)
			} else {
				/* not string - int? */
				if in, ok := v.(int); ok {
					parameters.Add(k, strconv.Itoa(in))
				} else {
					continue
				}
			}
		}
	}
	// append encoded params to url if any
	if qs := parameters.Encode(); qs != "" {
		u += "&" + qs
	}

	return u, nil
}

func (mp *client) TrackBatch(events []UserEvent, queryParams ...map[string]interface{}) error {
	// enforce max batch size to 50 (mixpanel)
	maxBatch := 50
	if len(events) > maxBatch {
		moreEvents := events[maxBatch:]
		events = events[:maxBatch]
		defer mp.TrackBatch(moreEvents, queryParams...)
	}
	data := make([]*eventData, 0, maxBatch)
	for _, event := range events {
		d := &eventData{
			Event: event.Name,
			Props: map[string]interface{}{
				"time":        time.Now().Unix(), // default to now, can be overwritten by props
				"token":       mp.token,
				"distinct_id": strconv.FormatInt(event.DistinctId, 10),
			},
		}
		for k, v := range event.Props {
			d.Props[k] = v
		}
		data = append(data, d)
	}

	marshaledData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	u := fmt.Sprintf("%s/%s", host, trackPath)
	encodedData := "data=" + base64.StdEncoding.EncodeToString(marshaledData)

	parameters := url.Values{}
	// iterate over any query parameters
	for _, val := range queryParams {
		for k, v := range val {
			if str, ok := v.(string); ok {
				/* act on str */
				parameters.Add(k, str)
			} else {
				/* not string - int? */
				if in, ok := v.(int); ok {
					parameters.Add(k, strconv.Itoa(in))
				} else {
					continue
				}
			}
		}
	}
	// append encoded params to url if any
	if qs := parameters.Encode(); qs != "" {
		u += "?" + qs
	}
	// send request
	resp, err := http.Post(u, "application/x-www-form-urlencoded", bytes.NewBufferString(encodedData))
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

func (mp *client) Engage(uid int64, p map[string]interface{}, ip string) error {
	profileData := &engageData{
		Token: mp.token,
		Time:  time.Now().Unix(),
	}
	if uid != 0 {
		profileData.Id = uid
	}
	if ip != "" {
		profileData.Ip = ip
	}
	// should probably just add separate methods for each of these
	for k, v := range p {
		switch k {
		case EngageSet:
			profileData.Set = v
			break
		case EngageSetOnce:
			profileData.SetOnce = v
			break
		case EngageAdd:
			profileData.Add = v
			break
		case EngageAppend:
			profileData.Append = v
			break
		case EngageUnion:
			profileData.Union = v
			break
		case EngageUnset:
			profileData.Unset = v
			break
		case EngageDelete:
			profileData.Delete = v
			break
		}
	}

	marshalledData, err := json.Marshal(profileData)
	if err != nil {
		return err
	}

	url := fmt.Sprintf("%s/%s/?data=%s", host, engagePath, base64.StdEncoding.EncodeToString(marshalledData))

	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

func (mp *client) EngageString(id string, p map[string]interface{}, ip string) error {
	profileData := &engageDataStringed{
		Id:    id,
		Token: mp.token,
		Time:  time.Now().Unix(),
	}
	if ip != "" {
		profileData.Ip = ip
	}
	// should probably just add separate methods for each of these
	for k, v := range p {
		switch k {
		case EngageSet:
			profileData.Set = v
			break
		case EngageSetOnce:
			profileData.SetOnce = v
			break
		case EngageAdd:
			profileData.Add = v
			break
		case EngageAppend:
			profileData.Append = v
			break
		case EngageUnion:
			profileData.Union = v
			break
		case EngageUnset:
			profileData.Unset = v
			break
		case EngageDelete:
			profileData.Delete = v
			break
		}
	}

	marshalledData, err := json.Marshal(profileData)
	if err != nil {
		return err
	}

	url := fmt.Sprintf("%s/%s/?data=%s", host, engagePath, base64.StdEncoding.EncodeToString(marshalledData))

	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}

func (mp *client) TrackString(event UserEventStringed, queryParams ...map[string]interface{}) error {
	data := &eventData{
		Event: event.Name,
		Props: map[string]interface{}{
			"time":        time.Now().Unix(), // default to now, can be overwritten by props
			"token":       mp.token,
			"distinct_id": event.DistinctId,
		},
	}
	for k, v := range event.Props {
		data.Props[k] = v
	}

	marshaledData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	u := fmt.Sprintf("%s/%s/?data=%s", host, trackPath,
		base64.StdEncoding.EncodeToString(marshaledData))

	parameters := url.Values{}
	// iterate over any query parameters
	for _, val := range queryParams {
		for k, v := range val {
			if str, ok := v.(string); ok {
				/* act on str */
				parameters.Add(k, str)
			} else {
				/* not string - int? */
				if in, ok := v.(int); ok {
					parameters.Add(k, strconv.Itoa(in))
				} else {
					continue
				}
			}
		}
	}
	// append encoded params to url if any
	if qs := parameters.Encode(); qs != "" {
		u += "&" + qs
	}
	// send request
	resp, err := http.Get(u)
	if err != nil {
		return err
	}
	resp.Body.Close()
	return nil
}
<|endoftext|>


<commit_before>package tradeoffer

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/Philipp15b/go-steam/community"
	"github.com/Philipp15b/go-steam/economy/inventory"
	"github.com/Philipp15b/go-steam/netutil"
	"github.com/Philipp15b/go-steam/steamid"
	"net/http"
	"strconv"
)

type APIKey string

const apiUrl = "https://api.steampowered.com/IEconService/%s/v%d"

type Client struct {
	client    *http.Client
	key       APIKey
	sessionId string
}

func NewClient(key APIKey, sessionId, steamLogin, steamLoginSecure string) *Client {
	c := &Client{
		new(http.Client),
		key,
		sessionId,
	}
	community.SetCookies(c.client, sessionId, steamLogin, steamLoginSecure)
	return c
}

func (c *Client) GetOffers() (*TradeOffers, error) {
	resp, err := c.client.Get(fmt.Sprintf(apiUrl, "GetTradeOffers", 1) + "?" + netutil.ToUrlValues(map[string]string{
		"key":                 string(c.key),
		"get_sent_offers":     "1",
		"get_received_offers": "1",
	}).Encode())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	t := new(struct {
		Response *TradeOffers
	})
	err = json.NewDecoder(resp.Body).Decode(t)
	if err != nil {
		return nil, err
	}
	return t.Response, nil
}

type actionResult struct {
	Success bool
	Error   string
}

func (c *Client) action(method string, version uint, id TradeOfferId) error {
	resp, err := c.client.Do(netutil.NewPostForm(fmt.Sprintf(apiUrl, method, version), netutil.ToUrlValues(map[string]string{
		"key":          string(c.key),
		"tradeofferid": strconv.FormatUint(uint64(id), 10),
	})))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New(method + " error: status code not 200")
	}
	return nil
}

func (c *Client) Decline(id TradeOfferId) error {
	return c.action("DeclineTradeOffer", 1, id)
}

func (c *Client) Cancel(id TradeOfferId) error {
	return c.action("CancelTradeOffer", 1, id)
}

func (c *Client) Accept(id TradeOfferId) error {
	resp, err := c.client.PostForm(fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d/accept", id), netutil.ToUrlValues(map[string]string{
		"sessionid":    c.sessionId,
		"serverid":     "1",
		"tradeofferid": strconv.FormatUint(uint64(id), 10),
	}))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("accept error: status code not 200")
	}
	return nil
}

type TradeItem struct {
	AppId     uint32
	ContextId uint64
	Amount    uint
	AssetId   uint64
}

// Sends a new trade offer to the given Steam user. You can optionally specify an access token if you've got one.
// In addition, `countered` can be non-nil, indicating the trade offer this is a counter for.
func (c *Client) Create(other steamid.SteamId, accessToken *string, myItems, theirItems []TradeItem, countered *TradeOfferId, message string) error {
	to := map[string]interface{}{
		"newversion": "true",
		"version":    "2",
		"me": map[string]interface{}{
			"assets":   myItems,
			"currency": make([]struct{}, 0),
			"ready":    "false",
		},
		"them": map[string]interface{}{
			"assets":   theirItems,
			"currency": make([]struct{}, 0),
			"ready":    "false",
		},
	}

	jto, err := json.Marshal(to)
	if err != nil {
		panic(err)
	}

	data := map[string]string{
		"sessionid":         c.sessionId,
		"serverid":          "1",
		"partner":           fmt.Sprintf("%d", other),
		"tradeoffermessage": message,
		"json_tradeoffer":   string(jto),
	}

	var referer string
	if countered != nil {
		referer = fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d/", *countered)
		data["tradeofferid_countered"] = fmt.Sprintf("%d", *countered)
	} else {
		referer = fmt.Sprintf("https://steamcommunity.com/tradeoffer/new?partner=%d", other)
	}

	req := netutil.NewPostForm("https://steamcommunity.com/tradeoffer/new/send", netutil.ToUrlValues(data))
	req.Header.Add("Referer", referer)

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("accept error: status code not 200")
	}
	return nil
}

func (c *Client) GetOwnInventory(contextId uint64, appId uint32) (*inventory.Inventory, error) {
	return inventory.GetOwnInventory(c.client, contextId, appId)
}

func (c *Client) GetTheirInventory(other steamid.SteamId, contextId uint64, appId uint32) (*inventory.Inventory, error) {
	return inventory.GetFullInventory(func() (*inventory.PartialInventory, error) {
		return c.getPartialTheirInventory(other, contextId, appId, nil)
	}, func(start uint) (*inventory.PartialInventory, error) {
		return c.getPartialTheirInventory(other, contextId, appId, &start)
	})
}

func (c *Client) getPartialTheirInventory(other steamid.SteamId, contextId uint64, appId uint32, start *uint) (*inventory.PartialInventory, error) {
	data := map[string]string{
		"sessionid": c.sessionId,
		"partner":   fmt.Sprintf("%d", other),
		"contextid": strconv.FormatUint(contextId, 10),
		"appid":     strconv.FormatUint(uint64(appId), 10),
	}
	if start != nil {
		data["start"] = strconv.FormatUint(uint64(*start), 10)
	}

	const baseUrl = "https://steamcommunity.com/tradeoffer/new/"
	req, err := http.NewRequest("GET", baseUrl+"partnerinventory/?"+netutil.ToUrlValues(data).Encode(), nil)
	if err != nil {
		panic(err)
	}
	req.Header.Add("Referer", baseUrl+"?partner="+fmt.Sprintf("%d", other))

	return inventory.DoInventoryRequest(c.client, req)
}
<commit_msg>Fix trade offers, mostly incorrect types/vars<commit_after>package tradeoffer

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/Philipp15b/go-steam/community"
	"github.com/Philipp15b/go-steam/economy/inventory"
	"github.com/Philipp15b/go-steam/netutil"
	"github.com/Philipp15b/go-steam/steamid"
	"net/http"
	"strconv"
)

type APIKey string

const apiUrl = "https://api.steampowered.com/IEconService/%s/v%d"

type Client struct {
	client    *http.Client
	key       APIKey
	sessionId string
}

func NewClient(key APIKey, sessionId, steamLogin, steamLoginSecure string) *Client {
	c := &Client{
		new(http.Client),
		key,
		sessionId,
	}
	community.SetCookies(c.client, sessionId, steamLogin, steamLoginSecure)
	return c
}

func (c *Client) GetOffers() (*TradeOffers, error) {
	resp, err := c.client.Get(fmt.Sprintf(apiUrl, "GetTradeOffers", 1) + "?" + netutil.ToUrlValues(map[string]string{
		"key":                 string(c.key),
		"get_sent_offers":     "1",
		"get_received_offers": "1",
	}).Encode())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	t := new(struct {
		Response *TradeOffers
	})
	err = json.NewDecoder(resp.Body).Decode(t)
	if err != nil {
		return nil, err
	}
	return t.Response, nil
}

type actionResult struct {
	Success bool
	Error   string
}

func (c *Client) action(method string, version uint, id TradeOfferId) error {
	resp, err := c.client.Do(netutil.NewPostForm(fmt.Sprintf(apiUrl, method, version), netutil.ToUrlValues(map[string]string{
		"key":          string(c.key),
		"tradeofferid": strconv.FormatUint(uint64(id), 10),
	})))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New(method + " error: status code not 200")
	}
	return nil
}

func (c *Client) Decline(id TradeOfferId) error {
	return c.action("DeclineTradeOffer", 1, id)
}

func (c *Client) Cancel(id TradeOfferId) error {
	return c.action("CancelTradeOffer", 1, id)
}

func (c *Client) Accept(id TradeOfferId) error {
	resp, err := c.client.PostForm(fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d/accept", id), netutil.ToUrlValues(map[string]string{
		"sessionid":    c.sessionId,
		"serverid":     "1",
		"tradeofferid": strconv.FormatUint(uint64(id), 10),
	}))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("accept error: status code not 200")
	}
	return nil
}

type TradeItem struct {
	AppId     uint32 `json:"appid"`
	ContextId uint64 `json:"contextid"`
	Amount    uint   `json:"amount"`
	AssetId   uint64 `json:"assetid"`
}

// Sends a new trade offer to the given Steam user. You can optionally specify an access token if you've got one.
// In addition, `countered` can be non-nil, indicating the trade offer this is a counter for.
func (c *Client) Create(other steamid.SteamId, accessToken *string, myItems, theirItems []TradeItem, countered *TradeOfferId, message string) error {
	to := map[string]interface{}{
		"newversion": true,
		"version":    "3",
		"me": map[string]interface{}{
			"assets":   myItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   theirItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	jto, err := json.Marshal(to)
	if err != nil {
		panic(err)
	}

	data := map[string]string{
		"sessionid":                 c.sessionId,
		"serverid":                  "1",
		"partner":                   fmt.Sprintf("%d", other),
		"tradeoffermessage":         message,
		"json_tradeoffer":           string(jto),
		"captcha":                   "",
		"trade_offer_create_params": "{}",
	}

	var referer string
	if countered != nil {
		referer = fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d/", *countered)
		data["tradeofferid_countered"] = fmt.Sprintf("%d", *countered)
	} else {
		referer = fmt.Sprintf("https://steamcommunity.com/tradeoffer/new?partner=%d", other)
	}

	req := netutil.NewPostForm("https://steamcommunity.com/tradeoffer/new/send", netutil.ToUrlValues(data))
	req.Header.Add("Referer", referer)

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("create error: status code not 200")
	}
	return nil
}

func (c *Client) GetOwnInventory(contextId uint64, appId uint32) (*inventory.Inventory, error) {
	return inventory.GetOwnInventory(c.client, contextId, appId)
}

func (c *Client) GetTheirInventory(other steamid.SteamId, contextId uint64, appId uint32) (*inventory.Inventory, error) {
	return inventory.GetFullInventory(func() (*inventory.PartialInventory, error) {
		return c.getPartialTheirInventory(other, contextId, appId, nil)
	}, func(start uint) (*inventory.PartialInventory, error) {
		return c.getPartialTheirInventory(other, contextId, appId, &start)
	})
}

func (c *Client) getPartialTheirInventory(other steamid.SteamId, contextId uint64, appId uint32, start *uint) (*inventory.PartialInventory, error) {
	data := map[string]string{
		"sessionid": c.sessionId,
		"partner":   fmt.Sprintf("%d", other),
		"contextid": strconv.FormatUint(contextId, 10),
		"appid":     strconv.FormatUint(uint64(appId), 10),
	}
	if start != nil {
		data["start"] = strconv.FormatUint(uint64(*start), 10)
	}

	const baseUrl = "https://steamcommunity.com/tradeoffer/new/"
	req, err := http.NewRequest("GET", baseUrl+"partnerinventory/?"+netutil.ToUrlValues(data).Encode(), nil)
	if err != nil {
		panic(err)
	}
	req.Header.Add("Referer", baseUrl+"?partner="+fmt.Sprintf("%d", other))

	return inventory.DoInventoryRequest(c.client, req)
}
<|endoftext|>


<commit_before>package indicsoundex

import "testing"

func TestCalculate(t *testing.T) {
	inArray := []string{"vasudeva", "kamath", "ವಾಸುದೇವ", "वासुदॆव"}
	outArray := []string{"v231", "k53", "ವASCKDR0", "वASCKDR0"}

	for index, value := range inArray {
		if x, output := Calculate(value, 8), outArray[index]; x != output {
			t.Errorf("Calculate(%s) = %s was expecting %s", value,
				x, output)
		}
	}
}

func TestCompare(t *testing.T) {
	inArray := []string{"vasudev", "ವಾಸುದೇವ", "वासुदॆव", "vasudev",
		"vasudev"}
	outArray := []string{"kamath", "വാസുദേവ", "వాసుదేవ", "vaasudev",
		"vasudev"}
	resultArray := []int{SOUNDEX_STRINGS_SOUNDS_DIFFERENT,
		SOUNDEX_STRINGS_SOUNDS_ALIKE,
		SOUNDEX_STRINGS_SOUNDS_ALIKE,
		SOUNDEX_STRINGS_SOUNDSS_ALIKE,
		SOUNDEX_SAME_STRING}

	for index, value := range inArray {
		if x := Compare(value, outArray[index]); x != resultArray[index] {
			t.Errorf("Compare(%s, %s) = %d was expecting %d", value,
				outArray[index], x, resultArray[index])
		}
	}
}
<commit_msg>Typo fix in constant name<commit_after>package indicsoundex

import "testing"

func TestCalculate(t *testing.T) {
	inArray := []string{"vasudeva", "kamath", "ವಾಸುದೇವ", "वासुदॆव"}
	outArray := []string{"v231", "k53", "ವASCKDR0", "वASCKDR0"}

	for index, value := range inArray {
		if x, output := Calculate(value, 8), outArray[index]; x != output {
			t.Errorf("Calculate(%s) = %s was expecting %s", value,
				x, output)
		}
	}
}

func TestCompare(t *testing.T) {
	inArray := []string{"vasudev", "ವಾಸುದೇವ", "वासुदॆव", "vasudev",
		"vasudev"}
	outArray := []string{"kamath", "വാസുദേവ", "వాసుదేవ", "vaasudev",
		"vasudev"}
	resultArray := []int{SOUNDEX_STRINGS_SOUNDS_DIFFERENT,
		SOUNDEX_STRINGS_SOUNDS_ALIKE,
		SOUNDEX_STRINGS_SOUNDS_ALIKE,
		SOUNDEX_STRINGS_SOUNDS_ALIKE,
		SOUNDEX_SAME_STRING}

	for index, value := range inArray {
		if x := Compare(value, outArray[index]); x != resultArray[index] {
			t.Errorf("Compare(%s, %s) = %d was expecting %d", value,
				outArray[index], x, resultArray[index])
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2019 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package azure

import (
	"github.com/Azure/go-autorest/autorest"
	"github.com/GoogleCloudPlatform/terraformer/terraformutils"
	"github.com/hashicorp/go-azure-helpers/authentication"
	"strings"
)

type AzureService struct { //nolint
	terraformutils.Service
}

func (az *AzureService) getClientArgs() (subscriptionID string, resourceGroup string, authorizer autorest.Authorizer) {
	subs := az.Args["config"].(authentication.Config).SubscriptionID
	auth := az.Args["authorizer"].(autorest.Authorizer)
	resg := az.Args["resource_group"].(string)
	return subs, resg, auth
}

func (az *AzureService) AppendSimpleResource(id string, resourceName string, resourceType string) {
	newResource := terraformutils.NewSimpleResource(id, resourceName, resourceType, az.ProviderName, []string{})
	az.Resources = append(az.Resources, newResource)
}

func (az *AzureService) AppendSimpleResourceWithDuplicateCheck(id string, resourceName string, resourceType string) {
	tferexist, _ := az.DuplicateCheck(id, resourceName, resourceType)
	if !tferexist {
		resourceName = resourceName + "_" + GenerateRandomString(6)
	}
	newResource := terraformutils.NewSimpleResource(id, resourceName, resourceType, az.ProviderName, []string{})
	az.Resources = append(az.Resources, newResource)
}

// This method checks if same resource name(tfer) exists with
// same id
func (az *AzureService) DuplicateCheck(id string, resourceName string, resourceType string) (bool, bool) {
	var tferexist, idexist bool
	tferName := terraformutils.TfSanitize(resourceName)
	for _, resource := range az.Resources {
		if tferName == resource.ResourceName {
			if id == resource.InstanceState.ID {
				tferexist = true
				idexist = true
			} else {
				tferexist = true
				idexist = false
			}
		}
	}
	return tferexist, idexist
}

func (az *AzureService) appendSimpleAssociation(id string, linkedResourceName string, resourceName *string, resourceType string, attributes map[string]string) {
	var resourceName2 string
	if resourceName != nil {
		resourceName2 = *resourceName
	} else {
		resourceName0 := strings.ReplaceAll(resourceType, "azurerm_", "")
		resourceName1 := resourceName0[strings.IndexByte(resourceName0, '_'):]
		resourceName2 = linkedResourceName + resourceName1
	}
	newResource := terraformutils.NewResource(
		id, resourceName2, resourceType, az.ProviderName, attributes,
		[]string{"name"},
		map[string]interface{}{},
	)
	az.Resources = append(az.Resources, newResource)
}
<commit_msg>For Duplicate resource issue - Using id instead of random number (#1158)<commit_after>// Copyright 2019 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package azure

import (
	"github.com/Azure/go-autorest/autorest"
	"github.com/GoogleCloudPlatform/terraformer/terraformutils"
	"github.com/hashicorp/go-azure-helpers/authentication"
	"strings"
)

type AzureService struct { //nolint
	terraformutils.Service
}

func (az *AzureService) getClientArgs() (subscriptionID string, resourceGroup string, authorizer autorest.Authorizer) {
	subs := az.Args["config"].(authentication.Config).SubscriptionID
	auth := az.Args["authorizer"].(autorest.Authorizer)
	resg := az.Args["resource_group"].(string)
	return subs, resg, auth
}

func (az *AzureService) AppendSimpleResource(id string, resourceName string, resourceType string) {
	newResource := terraformutils.NewSimpleResource(id, resourceName, resourceType, az.ProviderName, []string{})
	az.Resources = append(az.Resources, newResource)
}

func (az *AzureService) AppendSimpleResourceWithDuplicateCheck(id string, resourceName string, resourceType string) {
	tferexist, _ := az.DuplicateCheck(id, resourceName, resourceType)
	if !tferexist {
		resourceName = resourceName + "_" + id
	}
	newResource := terraformutils.NewSimpleResource(id, resourceName, resourceType, az.ProviderName, []string{})
	az.Resources = append(az.Resources, newResource)
}

// This method checks if same resource name(tfer) exists with
// same id
func (az *AzureService) DuplicateCheck(id string, resourceName string, resourceType string) (bool, bool) {
	var tferexist, idexist bool
	tferName := terraformutils.TfSanitize(resourceName)
	for _, resource := range az.Resources {
		if tferName == resource.ResourceName {
			if id == resource.InstanceState.ID {
				tferexist = true
				idexist = true
			} else {
				tferexist = true
				idexist = false
			}
		}
	}
	return tferexist, idexist
}

func (az *AzureService) appendSimpleAssociation(id string, linkedResourceName string, resourceName *string, resourceType string, attributes map[string]string) {
	var resourceName2 string
	if resourceName != nil {
		resourceName2 = *resourceName
	} else {
		resourceName0 := strings.ReplaceAll(resourceType, "azurerm_", "")
		resourceName1 := resourceName0[strings.IndexByte(resourceName0, '_'):]
		resourceName2 = linkedResourceName + resourceName1
	}
	newResource := terraformutils.NewResource(
		id, resourceName2, resourceType, az.ProviderName, attributes,
		[]string{"name"},
		map[string]interface{}{},
	)
	az.Resources = append(az.Resources, newResource)
}
<|endoftext|>


<commit_before>// Copyright 2016 tsuru 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 dockercommon

import (
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/fsouza/go-dockerclient"
	"github.com/pkg/errors"
	"github.com/tsuru/config"
	"github.com/tsuru/tsuru/app/image"
	"github.com/tsuru/tsuru/net"
	"github.com/tsuru/tsuru/provision"
)

type Client interface {
	PushImage(docker.PushImageOptions, docker.AuthConfiguration) error
	InspectImage(string) (*docker.Image, error)
	TagImage(string, docker.TagImageOptions) error
	UploadToContainer(string, docker.UploadToContainerOptions) error
	CommitContainer(docker.CommitContainerOptions) (*docker.Image, error)
}

func UploadToContainer(client Client, contID string, archiveFile io.Reader, fileSize int64) (string, string, error) {
	dirPath := "/home/application/"
	fileName := "archive.tar.gz"
	reader := addDeployTarFile(archiveFile, fileSize, fileName)
	uploadOpts := docker.UploadToContainerOptions{
		InputStream: reader,
		Path:        dirPath,
	}
	err := client.UploadToContainer(contID, uploadOpts)
	if err != nil {
		return "", "", err
	}
	image, err := client.CommitContainer(docker.CommitContainerOptions{
		Container: contID,
	})
	if err != nil {
		return "", "", err
	}
	return image.ID, fmt.Sprintf("file://%s%s", dirPath, fileName), nil
}

type PrepareImageArgs struct {
	Client      Client
	App         provision.App
	ProcfileRaw string
	ImageId     string
	AuthConfig  docker.AuthConfiguration
	Out         io.Writer
}

func PrepareImageForDeploy(args PrepareImageArgs) (string, error) {
	fmt.Fprintf(args.Out, "---- Inspecting image %q ----\n", args.ImageId)
	procfile := image.GetProcessesFromProcfile(args.ProcfileRaw)
	imageInspect, err := args.Client.InspectImage(args.ImageId)
	if err != nil {
		return "", err
	}
	if len(procfile) == 0 {
		fmt.Fprintln(args.Out, "  ---> Procfile not found, using entrypoint and cmd")
		procfile["web"] = append(imageInspect.Config.Entrypoint, imageInspect.Config.Cmd...)
	}
	for k, v := range procfile {
		fmt.Fprintf(args.Out, "  ---> Process %q found with commands: %q\n", k, v)
	}
	newImage, err := image.AppNewImageName(args.App.GetName())
	if err != nil {
		return "", err
	}
	imageInfo := strings.Split(newImage, ":")
	repo, tag := strings.Join(imageInfo[:len(imageInfo)-1], ":"), imageInfo[len(imageInfo)-1]
	err = args.Client.TagImage(args.ImageId, docker.TagImageOptions{Repo: repo, Tag: tag, Force: true})
	if err != nil {
		return "", err
	}
	registry, err := config.GetString("docker:registry")
	if err != nil {
		return "", err
	}
	fmt.Fprintf(args.Out, "---- Pushing image %q to tsuru ----\n", newImage)
	pushOpts := docker.PushImageOptions{
		Name:              repo,
		Tag:               tag,
		Registry:          registry,
		OutputStream:      args.Out,
		InactivityTimeout: net.StreamInactivityTimeout,
		RawJSONStream:     true,
	}
	err = args.Client.PushImage(pushOpts, args.AuthConfig)
	if err != nil {
		return "", err
	}
	imageData := image.ImageMetadata{
		Name:      newImage,
		Processes: procfile,
	}
	if len(imageInspect.Config.ExposedPorts) > 1 {
		return "", errors.New("Too many ports. You should especify which one you want to.")
	}
	for k := range imageInspect.Config.ExposedPorts {
		imageData.ExposedPort = string(k)
	}
	err = imageData.Save()
	if err != nil {
		return "", err
	}
	return newImage, nil
}

func WaitDocker(client *docker.Client) error {
	timeout, _ := config.GetInt("docker:api-timeout")
	if timeout == 0 {
		timeout = 600
	}
	timeoutChan := time.After(time.Duration(timeout) * time.Second)
	pong := make(chan error, 1)
	exit := make(chan struct{})
	go func() {
		for {
			err := client.Ping()
			if err == nil {
				pong <- nil
				return
			}
			if e, ok := err.(*docker.Error); ok && e.Status > 499 {
				pong <- err
				return
			}
			select {
			case <-exit:
				return
			case <-time.After(time.Second):
			}
		}
	}()
	select {
	case err := <-pong:
		return err
	case <-timeoutChan:
		close(exit)
		return errors.Errorf("Docker API at %q didn't respond after %d seconds", client.Endpoint(), timeout)
	}
}
<commit_msg>provision/dockercommon: use short timeout on Ping request<commit_after>// Copyright 2016 tsuru 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 dockercommon

import (
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/fsouza/go-dockerclient"
	"github.com/pkg/errors"
	"github.com/tsuru/config"
	"github.com/tsuru/tsuru/app/image"
	"github.com/tsuru/tsuru/net"
	"github.com/tsuru/tsuru/provision"
	"golang.org/x/net/context"
)

type Client interface {
	PushImage(docker.PushImageOptions, docker.AuthConfiguration) error
	InspectImage(string) (*docker.Image, error)
	TagImage(string, docker.TagImageOptions) error
	UploadToContainer(string, docker.UploadToContainerOptions) error
	CommitContainer(docker.CommitContainerOptions) (*docker.Image, error)
}

func UploadToContainer(client Client, contID string, archiveFile io.Reader, fileSize int64) (string, string, error) {
	dirPath := "/home/application/"
	fileName := "archive.tar.gz"
	reader := addDeployTarFile(archiveFile, fileSize, fileName)
	uploadOpts := docker.UploadToContainerOptions{
		InputStream: reader,
		Path:        dirPath,
	}
	err := client.UploadToContainer(contID, uploadOpts)
	if err != nil {
		return "", "", err
	}
	image, err := client.CommitContainer(docker.CommitContainerOptions{
		Container: contID,
	})
	if err != nil {
		return "", "", err
	}
	return image.ID, fmt.Sprintf("file://%s%s", dirPath, fileName), nil
}

type PrepareImageArgs struct {
	Client      Client
	App         provision.App
	ProcfileRaw string
	ImageId     string
	AuthConfig  docker.AuthConfiguration
	Out         io.Writer
}

func PrepareImageForDeploy(args PrepareImageArgs) (string, error) {
	fmt.Fprintf(args.Out, "---- Inspecting image %q ----\n", args.ImageId)
	procfile := image.GetProcessesFromProcfile(args.ProcfileRaw)
	imageInspect, err := args.Client.InspectImage(args.ImageId)
	if err != nil {
		return "", err
	}
	if len(procfile) == 0 {
		fmt.Fprintln(args.Out, "  ---> Procfile not found, using entrypoint and cmd")
		procfile["web"] = append(imageInspect.Config.Entrypoint, imageInspect.Config.Cmd...)
	}
	for k, v := range procfile {
		fmt.Fprintf(args.Out, "  ---> Process %q found with commands: %q\n", k, v)
	}
	newImage, err := image.AppNewImageName(args.App.GetName())
	if err != nil {
		return "", err
	}
	imageInfo := strings.Split(newImage, ":")
	repo, tag := strings.Join(imageInfo[:len(imageInfo)-1], ":"), imageInfo[len(imageInfo)-1]
	err = args.Client.TagImage(args.ImageId, docker.TagImageOptions{Repo: repo, Tag: tag, Force: true})
	if err != nil {
		return "", err
	}
	registry, err := config.GetString("docker:registry")
	if err != nil {
		return "", err
	}
	fmt.Fprintf(args.Out, "---- Pushing image %q to tsuru ----\n", newImage)
	pushOpts := docker.PushImageOptions{
		Name:              repo,
		Tag:               tag,
		Registry:          registry,
		OutputStream:      args.Out,
		InactivityTimeout: net.StreamInactivityTimeout,
		RawJSONStream:     true,
	}
	err = args.Client.PushImage(pushOpts, args.AuthConfig)
	if err != nil {
		return "", err
	}
	imageData := image.ImageMetadata{
		Name:      newImage,
		Processes: procfile,
	}
	if len(imageInspect.Config.ExposedPorts) > 1 {
		return "", errors.New("Too many ports. You should especify which one you want to.")
	}
	for k := range imageInspect.Config.ExposedPorts {
		imageData.ExposedPort = string(k)
	}
	err = imageData.Save()
	if err != nil {
		return "", err
	}
	return newImage, nil
}

func WaitDocker(client *docker.Client) error {
	timeout, _ := config.GetInt("docker:api-timeout")
	if timeout == 0 {
		timeout = 600
	}
	timeoutChan := time.After(time.Duration(timeout) * time.Second)
	pong := make(chan error, 1)
	exit := make(chan struct{})
	go func() {
		for {
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			err := client.PingWithContext(ctx)
			cancel()
			if err == nil {
				pong <- nil
				return
			}
			if e, ok := err.(*docker.Error); ok && e.Status > 499 {
				pong <- err
				return
			}
			select {
			case <-exit:
				return
			case <-time.After(time.Second):
			}
		}
	}()
	select {
	case err := <-pong:
		return err
	case <-timeoutChan:
		close(exit)
		return errors.Errorf("Docker API at %q didn't respond after %d seconds", client.Endpoint(), timeout)
	}
}
<|endoftext|>