<commit_before>package windows

import (
	"errors"
	"os"
	"syscall"
	"unsafe"
)

type SYSTEM_INFO struct {
	ProcessorArchitecture     uint16
	PageSize                  uint32
	MinimumApplicationAddress *byte
	MaximumApplicationAddress *byte
	ActiveProcessorMask       *byte
	NumberOfProcessors        uint32
	ProcessorType             uint32
	AllocationGranularity     uint32
	ProcessorLevel            uint16
	ProcessorRevision         uint16
}

type MEMORYSTATUSEX struct {
	Length               uint32
	MemoryLoad           uint32
	TotalPhys            uint64
	AvailPhys            uint64
	TotalPageFile        uint64
	AvailPageFile        uint64
	TotalVirtual         uint64
	AvailVirtual         uint64
	AvailExtendedVirtual uint64
}

type PDH_FMT_COUNTERVALUE_DOUBLE struct {
	CStatus     uint32
	DoubleValue float64
}

type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
	Name     *uint16
	FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}

const (
	ERROR_SUCCESS      = 0
	DRIVE_REMOVABLE    = 2
	DRIVE_FIXED        = 3
	HKEY_LOCAL_MACHINE = 0x80000002
	RRF_RT_REG_SZ      = 0x00000002
	RRF_RT_REG_DWORD   = 0x00000010
	PDH_FMT_DOUBLE     = 0x00000200
	PDH_INVALID_DATA   = 0xc0000bc6
)

var (
	modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
	modpdh      = syscall.NewLazyDLL("pdh.dll")

	RegGetValue                 = modadvapi32.NewProc("RegGetValueW")
	GetSystemInfo               = modkernel32.NewProc("GetSystemInfo")
	GetTickCount                = modkernel32.NewProc("GetTickCount")
	GetDiskFreeSpaceEx          = modkernel32.NewProc("GetDiskFreeSpaceExW")
	GetLogicalDriveStrings      = modkernel32.NewProc("GetLogicalDriveStringsW")
	GetDriveType                = modkernel32.NewProc("GetDriveTypeW")
	QueryDosDevice              = modkernel32.NewProc("QueryDosDeviceW")
	GetVolumeInformationW       = modkernel32.NewProc("GetVolumeInformationW")
	GlobalMemoryStatusEx        = modkernel32.NewProc("GlobalMemoryStatusEx")
	PdhOpenQuery                = modpdh.NewProc("PdhOpenQuery")
	PdhAddCounter               = modpdh.NewProc("PdhAddCounterW")
	PdhCollectQueryData         = modpdh.NewProc("PdhCollectQueryData")
	PdhGetFormattedCounterValue = modpdh.NewProc("PdhGetFormattedCounterValue")
	PdhCloseQuery               = modpdh.NewProc("PdhCloseQuery")
)

func RegGetInt(hKey uint32, subKey string, value string) (uint32, error) {
	var num, numlen uint32
	numlen = 4
	ret, _, err := RegGetValue.Call(
		uintptr(hKey),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(subKey))),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value))),
		uintptr(RRF_RT_REG_DWORD),
		0,
		uintptr(unsafe.Pointer(&num)),
		uintptr(unsafe.Pointer(&numlen)))
	if ret != ERROR_SUCCESS {
		return 0, err
	}

	return num, nil
}

func RegGetString(hKey uint32, subKey string, value string) (string, error) {
	var bufLen uint32
	RegGetValue.Call(
		uintptr(hKey),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(subKey))),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value))),
		uintptr(RRF_RT_REG_SZ),
		0,
		0,
		uintptr(unsafe.Pointer(&bufLen)))
	if bufLen == 0 {
		return "", errors.New("Can't get size of registry value")
	}

	buf := make([]uint16, bufLen)
	ret, _, err := RegGetValue.Call(
		uintptr(hKey),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(subKey))),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value))),
		uintptr(RRF_RT_REG_SZ),
		0,
		uintptr(unsafe.Pointer(&buf[0])),
		uintptr(unsafe.Pointer(&bufLen)))
	if ret != ERROR_SUCCESS {
		return "", err
	}

	return syscall.UTF16ToString(buf), nil
}

type CounterInfo struct {
	PostName    string
	CounterName string
	Counter     syscall.Handle
}

func CreateQuery() (syscall.Handle, error) {
	var query syscall.Handle
	r, _, err := PdhOpenQuery.Call(0, 0, uintptr(unsafe.Pointer(&query)))
	if r != 0 {
		return 0, err
	}
	return query, nil
}

func CreateCounter(query syscall.Handle, k, v string) (*CounterInfo, error) {
	var counter syscall.Handle
	r, _, err := PdhAddCounter.Call(
		uintptr(query),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v))),
		0,
		uintptr(unsafe.Pointer(&counter)))
	if r != 0 {
		return nil, err
	}
	return &CounterInfo{
		PostName:    k,
		CounterName: v,
		Counter:     counter,
	}, nil
}

func GetAdapterList() (*syscall.IpAdapterInfo, error) {
	b := make([]byte, 1000)
	l := uint32(len(b))
	a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
	err := syscall.GetAdaptersInfo(a, &l)
	if err == syscall.ERROR_BUFFER_OVERFLOW {
		b = make([]byte, l)
		a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
		err = syscall.GetAdaptersInfo(a, &l)
	}
	if err != nil {
		return nil, os.NewSyscallError("GetAdaptersInfo", err)
	}
	return a, nil
}

func BytePtrToString(p *uint8) string {
	a := (*[10000]uint8)(unsafe.Pointer(p))
	i := 0
	for a[i] != 0 {
		i++
	}
	return string(a[:i])
}
<commit_msg>Build constraint<commit_after>// +build windows

package windows

import (
	"errors"
	"os"
	"syscall"
	"unsafe"
)

type SYSTEM_INFO struct {
	ProcessorArchitecture     uint16
	PageSize                  uint32
	MinimumApplicationAddress *byte
	MaximumApplicationAddress *byte
	ActiveProcessorMask       *byte
	NumberOfProcessors        uint32
	ProcessorType             uint32
	AllocationGranularity     uint32
	ProcessorLevel            uint16
	ProcessorRevision         uint16
}

type MEMORYSTATUSEX struct {
	Length               uint32
	MemoryLoad           uint32
	TotalPhys            uint64
	AvailPhys            uint64
	TotalPageFile        uint64
	AvailPageFile        uint64
	TotalVirtual         uint64
	AvailVirtual         uint64
	AvailExtendedVirtual uint64
}

type PDH_FMT_COUNTERVALUE_DOUBLE struct {
	CStatus     uint32
	DoubleValue float64
}

type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
	Name     *uint16
	FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}

const (
	ERROR_SUCCESS      = 0
	DRIVE_REMOVABLE    = 2
	DRIVE_FIXED        = 3
	HKEY_LOCAL_MACHINE = 0x80000002
	RRF_RT_REG_SZ      = 0x00000002
	RRF_RT_REG_DWORD   = 0x00000010
	PDH_FMT_DOUBLE     = 0x00000200
	PDH_INVALID_DATA   = 0xc0000bc6
)

var (
	modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
	modpdh      = syscall.NewLazyDLL("pdh.dll")

	RegGetValue                 = modadvapi32.NewProc("RegGetValueW")
	GetSystemInfo               = modkernel32.NewProc("GetSystemInfo")
	GetTickCount                = modkernel32.NewProc("GetTickCount")
	GetDiskFreeSpaceEx          = modkernel32.NewProc("GetDiskFreeSpaceExW")
	GetLogicalDriveStrings      = modkernel32.NewProc("GetLogicalDriveStringsW")
	GetDriveType                = modkernel32.NewProc("GetDriveTypeW")
	QueryDosDevice              = modkernel32.NewProc("QueryDosDeviceW")
	GetVolumeInformationW       = modkernel32.NewProc("GetVolumeInformationW")
	GlobalMemoryStatusEx        = modkernel32.NewProc("GlobalMemoryStatusEx")
	PdhOpenQuery                = modpdh.NewProc("PdhOpenQuery")
	PdhAddCounter               = modpdh.NewProc("PdhAddCounterW")
	PdhCollectQueryData         = modpdh.NewProc("PdhCollectQueryData")
	PdhGetFormattedCounterValue = modpdh.NewProc("PdhGetFormattedCounterValue")
	PdhCloseQuery               = modpdh.NewProc("PdhCloseQuery")
)

func RegGetInt(hKey uint32, subKey string, value string) (uint32, error) {
	var num, numlen uint32
	numlen = 4
	ret, _, err := RegGetValue.Call(
		uintptr(hKey),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(subKey))),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value))),
		uintptr(RRF_RT_REG_DWORD),
		0,
		uintptr(unsafe.Pointer(&num)),
		uintptr(unsafe.Pointer(&numlen)))
	if ret != ERROR_SUCCESS {
		return 0, err
	}

	return num, nil
}

func RegGetString(hKey uint32, subKey string, value string) (string, error) {
	var bufLen uint32
	RegGetValue.Call(
		uintptr(hKey),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(subKey))),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value))),
		uintptr(RRF_RT_REG_SZ),
		0,
		0,
		uintptr(unsafe.Pointer(&bufLen)))
	if bufLen == 0 {
		return "", errors.New("Can't get size of registry value")
	}

	buf := make([]uint16, bufLen)
	ret, _, err := RegGetValue.Call(
		uintptr(hKey),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(subKey))),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value))),
		uintptr(RRF_RT_REG_SZ),
		0,
		uintptr(unsafe.Pointer(&buf[0])),
		uintptr(unsafe.Pointer(&bufLen)))
	if ret != ERROR_SUCCESS {
		return "", err
	}

	return syscall.UTF16ToString(buf), nil
}

type CounterInfo struct {
	PostName    string
	CounterName string
	Counter     syscall.Handle
}

func CreateQuery() (syscall.Handle, error) {
	var query syscall.Handle
	r, _, err := PdhOpenQuery.Call(0, 0, uintptr(unsafe.Pointer(&query)))
	if r != 0 {
		return 0, err
	}
	return query, nil
}

func CreateCounter(query syscall.Handle, k, v string) (*CounterInfo, error) {
	var counter syscall.Handle
	r, _, err := PdhAddCounter.Call(
		uintptr(query),
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v))),
		0,
		uintptr(unsafe.Pointer(&counter)))
	if r != 0 {
		return nil, err
	}
	return &CounterInfo{
		PostName:    k,
		CounterName: v,
		Counter:     counter,
	}, nil
}

func GetAdapterList() (*syscall.IpAdapterInfo, error) {
	b := make([]byte, 1000)
	l := uint32(len(b))
	a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
	err := syscall.GetAdaptersInfo(a, &l)
	if err == syscall.ERROR_BUFFER_OVERFLOW {
		b = make([]byte, l)
		a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0]))
		err = syscall.GetAdaptersInfo(a, &l)
	}
	if err != nil {
		return nil, os.NewSyscallError("GetAdaptersInfo", err)
	}
	return a, nil
}

func BytePtrToString(p *uint8) string {
	a := (*[10000]uint8)(unsafe.Pointer(p))
	i := 0
	for a[i] != 0 {
		i++
	}
	return string(a[:i])
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"path"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
)

var timeout = time.Second * 30
var apiURL = "https://api.opsgenie.com"

func main() {
	app := cli.NewApp()
	app.Name = path.Base(os.Args[0])
	app.Version = "1.0"
	app.Usage = "Send hartbeats to OpsGenie"
	app.Author = "OpsGenie"
	app.Flags = sharedFlags
	app.Commands = commands
	app.Run(os.Args)
}

var logAndExit = func(msg string) {
	log.Fatal(msg)
}
<commit_msg>set loglevel to warning<commit_after>package main

import (
	"os"
	"path"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
)

var timeout = time.Second * 30
var apiURL = "https://api.opsgenie.com"

func main() {
	log.SetLevel(log.WarnLevel)
	app := cli.NewApp()
	app.Name = path.Base(os.Args[0])
	app.Version = "1.0"
	app.Usage = "Send hartbeats to OpsGenie"
	app.Author = "OpsGenie"
	app.Flags = sharedFlags
	app.Commands = commands
	app.Run(os.Args)
}

var logAndExit = func(msg string) {
	log.Fatal(msg)
}
<|endoftext|>


<commit_before>package sfml

//#include <SFML/Graphics.h>
import "C"

type RectangleShape struct {
	data *C.sfRectangleShape
}
<commit_msg>Implement convex shape<commit_after>package sfml

//#include <SFML/Graphics.h>
import "C"

import (
	"runtime"
)

type RectangleShape struct {
	data *C.sfRectangleShape
	tex  *Texture
}

func destroyRectangleShape(c *RectangleShape) {
	C.sfRectangleShape_destroy(c.data)
}

func CreateEmptyRectangleShape() *RectangleShape {
	c := C.sfRectangleShape_create()
	if c == nil {
		return nil
	}
	obj := &RectangleShape{data: c}
	runtime.SetFinalizer(obj, destroyRectangleShape)
	return obj
}

func CreateRectangleShape(size Vector2f) *RectangleShape {
	c := C.sfRectangleShape_create()
	if c == nil {
		return nil
	}
	C.sfRectangleShape_setSize(c, cVector2f(&size))
	obj := &RectangleShape{data: c}
	runtime.SetFinalizer(obj, destroyRectangleShape)
	return obj
}

func (c *RectangleShape) Copy() *RectangleShape {
	cs := C.sfRectangleShape_copy(c.data)
	obj := &RectangleShape{cs, c.tex}
	runtime.SetFinalizer(obj, destroyRectangleShape)
	return obj
}

func (c *RectangleShape) SetPosition(position Vector2f) {
	C.sfRectangleShape_setPosition(c.data, cVector2f(&position))
}

func (c *RectangleShape) SetRotation(angle float32) {
	C.sfRectangleShape_setRotation(c.data, C.float(angle))
}

func (c *RectangleShape) SetScale(scale Vector2f) {
	C.sfRectangleShape_setScale(c.data, cVector2f(&scale))
}

func (c *RectangleShape) SetOrigin(origin Vector2f) {
	C.sfRectangleShape_setOrigin(c.data, cVector2f(&origin))
}

func (c *RectangleShape) GetPosition() Vector2f {
	r := C.sfRectangleShape_getPosition(c.data)
	return *goVector2f(&r)
}

func (c *RectangleShape) GetRotation() float32 {
	return float32(C.sfRectangleShape_getRotation(c.data))
}

func (c *RectangleShape) GetScale() Vector2f {
	r := C.sfRectangleShape_getScale(c.data)
	return *goVector2f(&r)
}

func (c *RectangleShape) GetOrigin() Vector2f {
	r := C.sfRectangleShape_getOrigin(c.data)
	return *goVector2f(&r)
}

func (c *RectangleShape) Move(offset Vector2f) {
	C.sfRectangleShape_move(c.data, cVector2f(&offset))
}

func (c *RectangleShape) Rotate(angle float32) {
	C.sfRectangleShape_rotate(c.data, C.float(angle))
}

func (c *RectangleShape) Scale(factors Vector2f) {
	C.sfRectangleShape_scale(c.data, cVector2f(&factors))
}

func (c *RectangleShape) GetTransform() Transform {
	r := C.sfRectangleShape_getTransform(c.data)
	return *goTransform(&r)
}

func (c *RectangleShape) GetInverseTransform() Transform {
	r := C.sfRectangleShape_getInverseTransform(c.data)
	return *goTransform(&r)
}

func (c *RectangleShape) SetTexture(texture *Texture, resetRect bool) {
	C.sfRectangleShape_setTexture(c.data, texture.data, cBool(resetRect))
	c.tex = texture
}

func (c *RectangleShape) GetTexture() *Texture {
	return c.tex
}

func (c *RectangleShape) GetTextureRect() Recti {
	r := C.sfRectangleShape_getTextureRect(c.data)
	return *goRecti(&r)
}

func (c *RectangleShape) SetTextureRect(rect Recti) {
	C.sfRectangleShape_setTextureRect(c.data, cRecti(&rect))
}

func (c *RectangleShape) SetFillColor(color Color) {
	C.sfRectangleShape_setFillColor(c.data, cColor(&color))
}

func (c *RectangleShape) GetFillColor() Color {
	r := C.sfRectangleShape_getFillColor(c.data)
	return *goColor(&r)
}

func (c *RectangleShape) SetOutlineColor(color Color) {
	C.sfRectangleShape_setOutlineColor(c.data, cColor(&color))
}

func (c *RectangleShape) GetOutlineColor() Color {
	r := C.sfRectangleShape_getOutlineColor(c.data)
	return *goColor(&r)
}

func (c *RectangleShape) SetOutlineThickness(thickness float32) {
	C.sfRectangleShape_setOutlineThickness(c.data, C.float(thickness))
}

func (c *RectangleShape) GetOutlineThickness() float32 {
	return float32(C.sfRectangleShape_getOutlineThickness(c.data))
}

func (c *RectangleShape) SetPointCount(count int) {
	C.sfRectangleShape_setPointCount(c.data, C.size_t(count))
}

func (c *RectangleShape) GetPointCount() int {
	return int(C.sfRectangleShape_getPointCount(c.data))
}

func (c *RectangleShape) SetPoint(index int, point Vector2f) {
	C.sfRectangleShape_setPoint(c.data, C.size_t(index), cVector2f(&point))
}

func (c *RectangleShape) GetPoint(index int) Vector2f {
	p := C.sfRectangleShape_getPoint(c.data, C.size_t(index))
	return *goVector2f(&p)
}

func (c *RectangleShape) SetSize(Size Vector2f) {
	C.sfRectangleShape_setSize(c.data, cVector2f(&Size))
}

func (c *RectangleShape) GetSize() Vector2f {
	p := C.sfRectangleShape_getSize(c.data)
	return *goVector2f(&p)
}

func (c *RectangleShape) GetLocalBounds() Rectf {
	r := C.sfRectangleShape_getLocalBounds(c.data)
	return *goRectf(&r)
}

func (c *RectangleShape) GetGlobalBounds() Rectf {
	r := C.sfRectangleShape_getGlobalBounds(c.data)
	return *goRectf(&r)
}

func (c *RectangleShape) Draw(target RenderTarget) {
	switch target.(type) {
	case *RenderTexture:
		C.sfRenderTexture_drawRectangleShape(target.(*RenderTexture).data, c.data, nil)
	case *RenderWindow:
		C.sfRenderWindow_drawRectangleShape(target.(*RenderWindow).data, c.data, nil)
	}
}

func (c *RectangleShape) DrawWithRenderStates(target RenderTarget, states *RenderStates) {
	ss := cRenderStates(states)
	switch target.(type) {
	case *RenderTexture:
		C.sfRenderTexture_drawRectangleShape(target.(*RenderTexture).data, c.data, &ss)
	case *RenderWindow:
		C.sfRenderWindow_drawRectangleShape(target.(*RenderWindow).data, c.data, &ss)
	}
}
<|endoftext|>


<commit_before>package slogger

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"time"
)

// Do not set this to zero or deadlocks might occur
const ROLLING_FILE_APPENDER_CHANNEL_SIZE = 4096

type RollingFileAppender struct {
	MaxFileSize uint64
	file *os.File
	absPath string
	curFileSize uint64
	appendCh chan *Log
	syncCh chan bool
	errHandler func(error)
	headerGenerator func() string
}

func NewRollingFileAppender(filename string, maxFileSize uint64, errHandler func(error), headerGenerator func() string) (*RollingFileAppender, error) {
	if errHandler == nil {
		errHandler = func(err error) { }
	}

	absPath, err := filepath.Abs(filename)
	if err != nil {
		return nil, err
	}

	file, err := os.OpenFile(
		absPath,
		os.O_WRONLY | os.O_APPEND | os.O_CREATE,
		0666,
	)
	if err != nil {
		return nil, err
	}

	fileInfo, err := file.Stat()
	if err != nil {
		return nil, err
	}

	curFileSize := uint64(fileInfo.Size())

	appender := &RollingFileAppender {
		MaxFileSize: maxFileSize,
		file: file,
		absPath: absPath,
		curFileSize: curFileSize,
		appendCh: make(chan *Log, ROLLING_FILE_APPENDER_CHANNEL_SIZE),
		syncCh: make(chan bool),
		errHandler: errHandler,
		headerGenerator: headerGenerator,
	}

	go appender.listenForAppends()

	if curFileSize == 0 {
		appender.logHeader()
	}
	return appender, nil
}

func (self RollingFileAppender) Append(log *Log) error {
	select {
	case self.appendCh <- log:
		// nothing else to do
	default:
		// channel is full. log a warning
		self.appendCh <- fullWarningLog()
		self.appendCh <- log
	}
	return nil
}

func (self RollingFileAppender) Close() error {
	self.waitUntilEmpty()
	return self.file.Close()
}

// These are commented out until I determine as to whether they are thread-safe -Tim

// func (self RollingFileAppender) SetErrHandler(errHandler func(error)) {
// 	self.errHandler = errHandler
// }

// func (self RollingFileAppender) SetHeaderGenerator(headerGenerator func() string) {
// 	self.headerGenerator = headerGenerator
// 	self.logHeader()
// }

func fullWarningLog() *Log {
	return internalWarningLog(
		"appendCh is full. You may want to increase ROLLING_FILE_APPENDER_CHANNEL_SIZE (currently %d).",
		[]interface{}{ROLLING_FILE_APPENDER_CHANNEL_SIZE},
	)
}

func internalWarningLog(messageFmt string, args []interface{}) *Log {
	return simpleLog("RollingFileAppender", WARN, 3, messageFmt, args)
}

func newRotatedFilename(baseFilename string) string {
	now := time.Now()

	return fmt.Sprintf("%s.%d-%02d-%02dT%02d-%02d-%02d",
		baseFilename,
		now.Year(),
		now.Month(),
		now.Day(),
		now.Hour(),
		now.Minute(),
		now.Second())
}

func simpleLog(prefix string, level Level, callerSkip int, messageFmt string, args []interface{}) *Log {
	_, file, line, ok := runtime.Caller(callerSkip)
	if !ok {
		file = "UNKNOWN_FILE"
		line = -1
	}

	return &Log {
		Prefix: prefix,
		Level: level,
		Filename: file,
		Line: line,
		Timestamp: time.Now(),
		messageFmt: messageFmt,
		args: args,
	}
}

func (self RollingFileAppender) listenForAppends() {
	needsSync := false
	for {
		if needsSync {
			select {
			case log := <- self.appendCh:
				self.reallyAppend(log, true)
			default:
				self.file.Sync()
				needsSync = false
			}
		} else {
			select {
			case log := <- self.appendCh:
				self.reallyAppend(log, true)
				needsSync = true
			case <- self.syncCh:
				self.syncCh <- (len(self.appendCh) <= 0)
			}
		}
	}
}

func (self RollingFileAppender) logHeader() {
	if self.headerGenerator != nil {
		header := self.headerGenerator()
		log := simpleLog("header", INFO, 3, header, []interface{}{})

		// do not count header as part of size towards rotation in
		// order to prevent infinite rotation when max size is smaller
		// than header
		self.reallyAppend(log, false)
	}
}

func (self RollingFileAppender) reallyAppend(log *Log, trackSize bool) {
	if self.file == nil {
		self.errHandler(errors.New("I have no logfile to write to!"))
		return
	}

	msg := FormatLog(log)

	n, err := self.file.WriteString(msg)

	if err != nil {
		self.errHandler(fmt.Errorf("Could not log to %s : %s", self.file.Name(), err.Error()))
		return
	}

	if trackSize {
		self.curFileSize += uint64(n)

		if self.curFileSize > self.MaxFileSize {
			self.rotate()
		}
	}
	return
}

// returns true on success, false otherwise
func (self RollingFileAppender) renameLogFile(oldFilename, newFilename string) bool {
	err := os.Rename(oldFilename, newFilename)
	if err != nil {
		self.errHandler(fmt.Errorf(
			"Error while renaming %s to %s . Will reopen. : %s",
			oldFilename, newFilename, err.Error()))

		file, err := os.OpenFile(oldFilename, os.O_RDWR, 0666)

		if err == nil {
			self.file = file
		} else {
			self.curFileSize = 0
			self.file = nil
			self.errHandler(fmt.Errorf(
				"Error while reopening %s after failing to rename. Further logging will fail: %s",
				oldFilename, err.Error()))
		}
		return false
	}
	self.curFileSize = 0
	return true
}

func (self RollingFileAppender) rotate() {
	// close current log
	if err := self.file.Close(); err != nil {
		self.errHandler(fmt.Errorf(
			"Error while closing %s : %s" , self.absPath, err.Error()))
	}

	// rename old log
	if !self.renameLogFile(self.absPath, newRotatedFilename(self.absPath)) {
		return
	}

	// create new log
	file, err := os.Create(self.absPath)

	if err != nil {
		self.file = nil
		self.errHandler(fmt.Errorf(
			"Failed to create %s . Further logging will fail. : %s",
			self.absPath, err.Error()))
		return
	}

	self.file = file
	self.logHeader()
	return
}

func (self RollingFileAppender) waitUntilEmpty() {
	self.syncCh <- true
	for !(<- self.syncCh) {
		self.syncCh <- true
	}
}

<commit_msg>have RollingFileAppender log header upon construction regardless if it's a new file<commit_after>package slogger

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"time"
)

// Do not set this to zero or deadlocks might occur
const ROLLING_FILE_APPENDER_CHANNEL_SIZE = 4096

type RollingFileAppender struct {
	MaxFileSize uint64
	file *os.File
	absPath string
	curFileSize uint64
	appendCh chan *Log
	syncCh chan bool
	errHandler func(error)
	headerGenerator func() string
}

func NewRollingFileAppender(filename string, maxFileSize uint64, errHandler func(error), headerGenerator func() string) (*RollingFileAppender, error) {
	if errHandler == nil {
		errHandler = func(err error) { }
	}

	absPath, err := filepath.Abs(filename)
	if err != nil {
		return nil, err
	}

	file, err := os.OpenFile(
		absPath,
		os.O_WRONLY | os.O_APPEND | os.O_CREATE,
		0666,
	)
	if err != nil {
		return nil, err
	}

	fileInfo, err := file.Stat()
	if err != nil {
		return nil, err
	}

	curFileSize := uint64(fileInfo.Size())

	appender := &RollingFileAppender {
		MaxFileSize: maxFileSize,
		file: file,
		absPath: absPath,
		curFileSize: curFileSize,
		appendCh: make(chan *Log, ROLLING_FILE_APPENDER_CHANNEL_SIZE),
		syncCh: make(chan bool),
		errHandler: errHandler,
		headerGenerator: headerGenerator,
	}

	go appender.listenForAppends()
	appender.logHeader()
	return appender, nil
}

func (self RollingFileAppender) Append(log *Log) error {
	select {
	case self.appendCh <- log:
		// nothing else to do
	default:
		// channel is full. log a warning
		self.appendCh <- fullWarningLog()
		self.appendCh <- log
	}
	return nil
}

func (self RollingFileAppender) Close() error {
	self.waitUntilEmpty()
	return self.file.Close()
}

// These are commented out until I determine as to whether they are thread-safe -Tim

// func (self RollingFileAppender) SetErrHandler(errHandler func(error)) {
// 	self.errHandler = errHandler
// }

// func (self RollingFileAppender) SetHeaderGenerator(headerGenerator func() string) {
// 	self.headerGenerator = headerGenerator
// 	self.logHeader()
// }

func fullWarningLog() *Log {
	return internalWarningLog(
		"appendCh is full. You may want to increase ROLLING_FILE_APPENDER_CHANNEL_SIZE (currently %d).",
		[]interface{}{ROLLING_FILE_APPENDER_CHANNEL_SIZE},
	)
}

func internalWarningLog(messageFmt string, args []interface{}) *Log {
	return simpleLog("RollingFileAppender", WARN, 3, messageFmt, args)
}

func newRotatedFilename(baseFilename string) string {
	now := time.Now()

	return fmt.Sprintf("%s.%d-%02d-%02dT%02d-%02d-%02d",
		baseFilename,
		now.Year(),
		now.Month(),
		now.Day(),
		now.Hour(),
		now.Minute(),
		now.Second())
}

func simpleLog(prefix string, level Level, callerSkip int, messageFmt string, args []interface{}) *Log {
	_, file, line, ok := runtime.Caller(callerSkip)
	if !ok {
		file = "UNKNOWN_FILE"
		line = -1
	}

	return &Log {
		Prefix: prefix,
		Level: level,
		Filename: file,
		Line: line,
		Timestamp: time.Now(),
		messageFmt: messageFmt,
		args: args,
	}
}

func (self RollingFileAppender) listenForAppends() {
	needsSync := false
	for {
		if needsSync {
			select {
			case log := <- self.appendCh:
				self.reallyAppend(log, true)
			default:
				self.file.Sync()
				needsSync = false
			}
		} else {
			select {
			case log := <- self.appendCh:
				self.reallyAppend(log, true)
				needsSync = true
			case <- self.syncCh:
				self.syncCh <- (len(self.appendCh) <= 0)
			}
		}
	}
}

func (self RollingFileAppender) logHeader() {
	if self.headerGenerator != nil {
		header := self.headerGenerator()
		log := simpleLog("header", INFO, 3, header, []interface{}{})

		// do not count header as part of size towards rotation in
		// order to prevent infinite rotation when max size is smaller
		// than header
		self.reallyAppend(log, false)
	}
}

func (self RollingFileAppender) reallyAppend(log *Log, trackSize bool) {
	if self.file == nil {
		self.errHandler(errors.New("I have no logfile to write to!"))
		return
	}

	msg := FormatLog(log)

	n, err := self.file.WriteString(msg)

	if err != nil {
		self.errHandler(fmt.Errorf("Could not log to %s : %s", self.file.Name(), err.Error()))
		return
	}

	if trackSize {
		self.curFileSize += uint64(n)

		if self.curFileSize > self.MaxFileSize {
			self.rotate()
		}
	}
	return
}

// returns true on success, false otherwise
func (self RollingFileAppender) renameLogFile(oldFilename, newFilename string) bool {
	err := os.Rename(oldFilename, newFilename)
	if err != nil {
		self.errHandler(fmt.Errorf(
			"Error while renaming %s to %s . Will reopen. : %s",
			oldFilename, newFilename, err.Error()))

		file, err := os.OpenFile(oldFilename, os.O_RDWR, 0666)

		if err == nil {
			self.file = file
		} else {
			self.curFileSize = 0
			self.file = nil
			self.errHandler(fmt.Errorf(
				"Error while reopening %s after failing to rename. Further logging will fail: %s",
				oldFilename, err.Error()))
		}
		return false
	}
	self.curFileSize = 0
	return true
}

func (self RollingFileAppender) rotate() {
	// close current log
	if err := self.file.Close(); err != nil {
		self.errHandler(fmt.Errorf(
			"Error while closing %s : %s" , self.absPath, err.Error()))
	}

	// rename old log
	if !self.renameLogFile(self.absPath, newRotatedFilename(self.absPath)) {
		return
	}

	// create new log
	file, err := os.Create(self.absPath)

	if err != nil {
		self.file = nil
		self.errHandler(fmt.Errorf(
			"Failed to create %s . Further logging will fail. : %s",
			self.absPath, err.Error()))
		return
	}

	self.file = file
	self.logHeader()
	return
}

func (self RollingFileAppender) waitUntilEmpty() {
	self.syncCh <- true
	for !(<- self.syncCh) {
		self.syncCh <- true
	}
}

<|endoftext|>


<commit_before><commit_msg>escape filename in webseed url<commit_after><|endoftext|>


<commit_before>package apns

import (
	"errors"
	"github.com/sideshow/apns2"
	"github.com/smancke/guble/server/connector"
)

const (
	// deviceIDKey is the key name set on the route params to identify the application
	deviceIDKey = "device_token"
	userIDKey   = "user_id"
)

var (
	errPusherInvalidParams = errors.New("Invalid parameters of APNS Pusher")
)

type sender struct {
	client   Pusher
	appTopic string
}

func NewSender(config Config) (connector.Sender, error) {
	pusher, err := newPusher(config)
	if err != nil {
		logger.WithField("error", err.Error()).Error("APNS Pusher creation error")
		return nil, err
	}
	return NewSenderUsingPusher(pusher, *config.AppTopic)
}

func NewSenderUsingPusher(pusher Pusher, appTopic string) (connector.Sender, error) {
	if pusher == nil || appTopic == "" {
		return nil, errPusherInvalidParams
	}
	return &sender{
		client:   pusher,
		appTopic: appTopic,
	}, nil
}

func (s sender) Send(request connector.Request) (interface{}, error) {
	logger.WithField("payload", string(request.Message().Body)).
		WithField("devicetoken", request.Subscriber().Route().Get(deviceIDKey)).
		WithField("apptopic", s.appTopic).
		Debug("Trying to push a message to APNS")
	return s.client.Push(&apns2.Notification{
		Priority:    apns2.PriorityHigh,
		Topic:       s.appTopic,
		DeviceToken: request.Subscriber().Route().Get(deviceIDKey),
		Payload:     request.Message().Body,
	})
}
<commit_msg>more logging in APNS<commit_after>package apns

import (
	"errors"
	"github.com/sideshow/apns2"
	"github.com/smancke/guble/server/connector"
)

const (
	// deviceIDKey is the key name set on the route params to identify the application
	deviceIDKey = "device_token"
	userIDKey   = "user_id"
)

var (
	errPusherInvalidParams = errors.New("Invalid parameters of APNS Pusher")
)

type sender struct {
	client   Pusher
	appTopic string
}

func NewSender(config Config) (connector.Sender, error) {
	pusher, err := newPusher(config)
	if err != nil {
		logger.WithField("error", err.Error()).Error("APNS Pusher creation error")
		return nil, err
	}
	return NewSenderUsingPusher(pusher, *config.AppTopic)
}

func NewSenderUsingPusher(pusher Pusher, appTopic string) (connector.Sender, error) {
	if pusher == nil || appTopic == "" {
		return nil, errPusherInvalidParams
	}
	return &sender{
		client:   pusher,
		appTopic: appTopic,
	}, nil
}

func (s sender) Send(request connector.Request) (interface{}, error) {
	deviceToken := request.Subscriber().Route().Get(deviceIDKey)
	logger.WithField("deviceToken", deviceToken).Debug("Trying to push a message to APNS")
	return s.client.Push(&apns2.Notification{
		Priority:    apns2.PriorityHigh,
		Topic:       s.appTopic,
		DeviceToken: deviceToken,
		Payload:     request.Message().Body,
	})
}
<|endoftext|>


<commit_before>package server

import (
	"container/list"
	"testing"

	"github.com/Vladimiroff/vec2d"

	"warcluster/entities"
	"warcluster/server/response"
)

var cp = NewClientPool(16)

var planet = entities.Planet{
	Name:     "GOP6720",
	Position: &vec2d.Vector{2, 2},
}

var player1 = entities.Player{
	Username:       "gophie",
	RaceID:         1,
	TwitterID:      "gop",
	HomePlanet:     "planet.GOP6720",
	ScreenSize:     []uint64{1, 1},
	ScreenPosition: &vec2d.Vector{2, 2},
}

var player2 = entities.Player{
	Username:       "snoopy",
	RaceID:         1,
	TwitterID:      "snoop",
	HomePlanet:     "planet.SNO6750",
	ScreenSize:     []uint64{1, 1},
	ScreenPosition: &vec2d.Vector{2, 8},
}

var (
	client1 = *NewFakeClient(&player1)
	client2 = *NewFakeClient(&player1)
	client3 = *NewFakeClient(&player2)
	client4 = *NewFakeClient(&player2)
)

func TestAddClientToClientPool(t *testing.T) {
	cp.pool = make(map[string]*list.List)

	l := len(cp.pool)
	cp.Add(&client1)
	if len(cp.pool) != l+1 {
		t.Fail()
	}
	cp.Remove(&client1)
}

func TestCloseClientSessionWithMoreThanOneSessions(t *testing.T) {
	cp.pool = make(map[string]*list.List)

	cp.Add(&client1)
	cp.Add(&client2)
	l := len(cp.pool)

	cp.Remove(&client1)
	if len(cp.pool) != l {
		t.Errorf("Expected %d received %d", l, len(cp.pool))
	}
	cp.Remove(&client2)
}

func TestCloseLastClientSessionAndRemoveIt(t *testing.T) {
	cp.pool = make(map[string]*list.List)

	cp.Add(&client3)
	l := len(cp.pool)

	cp.Remove(&client3)
	if len(cp.pool) != l-1 {
		t.Errorf("Expected %d received %d", l-1, len(cp.pool))
	}
}

func TestRemoveUnexistingClient(t *testing.T) {
	cp.pool = make(map[string]*list.List)

	cp.Remove(&client1)
	if len(cp.pool) != 0 {
		t.Fail()
	}
}

func TestSendMessageToSession(t *testing.T) {
	cp.pool = make(map[string]*list.List)
	resp := response.NewSendMissions()
	cp.Send(&player1, resp)

	cp.Add(&client1)
	cp.Add(&client2)
	cp.Add(&client3)

	l1 := len(client1.codec.(*fakeCodec).Messages)
	l2 := len(client2.codec.(*fakeCodec).Messages)
	l3 := len(client3.codec.(*fakeCodec).Messages)
	cp.Send(&player1, resp)

	if len(client1.codec.(*fakeCodec).Messages) != l1+1 {
		t.Errorf("%d", len(client1.codec.(*fakeCodec).Messages))
	}

	if len(client2.codec.(*fakeCodec).Messages) != l2+1 {
		t.Fail()
	}

	if len(client3.codec.(*fakeCodec).Messages) != l3 {
		t.Fail()
	}
}

func TestPlayer(t *testing.T) {
	cp.pool = make(map[string]*list.List)
	cp.Add(&client1)

	if player, err := cp.Player("gophie"); player == nil || err != nil {
		t.Error(err)
	}

	if player, err := cp.Player("snoopy"); player != nil || err == nil {
		t.Errorf("Received %v as player, nil expected", player.Username)
	}
}

func TestStackingStateChanges(t *testing.T) {
	cp := NewClientPool(1)
	cp.ticker.Stop()
	client1.codec.(*fakeCodec).Messages = make([][]byte, 0)
	cp.Add(&client1)

	client1.pushStateChange(&planet)
	if len(client1.codec.(*fakeCodec).Messages) != 0 {
		t.Error("Client received messages  without a tick", len(client1.codec.(*fakeCodec).Messages))
	}

	if client1.stateChange == nil {
		t.Error("Client has no stacked planets")
	}

	if len(client1.stateChange.RawPlanets) != 1 {
		t.Errorf("Client has %d stacked planets instead of 1")
	}
}

func TestBroadcast(t *testing.T) {
	cp := NewClientPool(3)
	cp.ticker.Stop()
	cp.Add(&client1)
	cp.Add(&client2)
	cp.Add(&client3)

	client1.MoveToAreas([]string{"area:1:1"})
	client2.MoveToAreas([]string{"area:1:1"})
	client3.MoveToAreas([]string{"area:2:1"})

	cp.Broadcast(&planet)
	if client1.stateChange == nil {
		t.Error("Client1 has no stacked planets")
	}

	if len(client1.stateChange.RawPlanets) != 1 {
		t.Errorf("Client1 has %d stacked planets instead of 1")
	}

	if client2.stateChange == nil {
		t.Error("Client2 has no stacked planets")
	}

	if len(client2.stateChange.RawPlanets) != 1 {
		t.Errorf("Client2 has %d stacked planets instead of 1")
	}

	if client3.stateChange != nil {
		t.Error("Client3 has stacked planets")
	}
}
<commit_msg>Hit several edge cases in client pool's tests<commit_after>package server

import (
	"container/list"
	"testing"

	"github.com/Vladimiroff/vec2d"

	"warcluster/entities"
	"warcluster/server/response"
)

var (
	cp = NewClientPool(16)

	planet = entities.Planet{
		Name:     "GOP6720",
		Position: &vec2d.Vector{2, 2},
	}

	sun = entities.Sun{
		Name:     "GOP672",
		Username: "gophie",
		Position: vec2d.New(20, 20),
	}
)

var player1 = entities.Player{
	Username:       "gophie",
	RaceID:         1,
	TwitterID:      "gop",
	HomePlanet:     "planet.GOP6720",
	ScreenSize:     []uint64{1, 1},
	ScreenPosition: &vec2d.Vector{2, 2},
}

var player2 = entities.Player{
	Username:       "snoopy",
	RaceID:         1,
	TwitterID:      "snoop",
	HomePlanet:     "planet.SNO6750",
	ScreenSize:     []uint64{1, 1},
	ScreenPosition: &vec2d.Vector{2, 8},
}

var (
	client1 = *NewFakeClient(&player1)
	client2 = *NewFakeClient(&player1)
	client3 = *NewFakeClient(&player2)
	client4 = *NewFakeClient(&player2)
)

func TestAddClientToClientPool(t *testing.T) {
	cp.pool = make(map[string]*list.List)

	l := len(cp.pool)
	cp.Add(&client1)
	if len(cp.pool) != l+1 {
		t.Fail()
	}
	client1.MoveToAreas([]string{"area:1:1"})
	cp.Remove(&client1)
}

func TestCloseClientSessionWithMoreThanOneSessions(t *testing.T) {
	cp.pool = make(map[string]*list.List)

	cp.Add(&client1)
	cp.Add(&client2)
	l := len(cp.pool)

	cp.Remove(&client1)
	if len(cp.pool) != l {
		t.Errorf("Expected %d received %d", l, len(cp.pool))
	}
	cp.Remove(&client2)
}

func TestCloseLastClientSessionAndRemoveIt(t *testing.T) {
	cp.pool = make(map[string]*list.List)

	cp.Add(&client3)
	l := len(cp.pool)

	cp.Remove(&client3)
	if len(cp.pool) != l-1 {
		t.Errorf("Expected %d received %d", l-1, len(cp.pool))
	}
}

func TestRemoveUnexistingClient(t *testing.T) {
	cp.pool = make(map[string]*list.List)

	cp.Remove(&client1)
	if len(cp.pool) != 0 {
		t.Fail()
	}
}

func TestSendMessageToSession(t *testing.T) {
	cp.pool = make(map[string]*list.List)
	resp := response.NewSendMissions()
	cp.Send(&player1, resp)

	cp.Add(&client1)
	cp.Add(&client2)
	cp.Add(&client3)

	l1 := len(client1.codec.(*fakeCodec).Messages)
	l2 := len(client2.codec.(*fakeCodec).Messages)
	l3 := len(client3.codec.(*fakeCodec).Messages)
	cp.Send(&player1, resp)

	if len(client1.codec.(*fakeCodec).Messages) != l1+1 {
		t.Errorf("%d", len(client1.codec.(*fakeCodec).Messages))
	}

	if len(client2.codec.(*fakeCodec).Messages) != l2+1 {
		t.Fail()
	}

	if len(client3.codec.(*fakeCodec).Messages) != l3 {
		t.Fail()
	}
}

func TestPlayer(t *testing.T) {
	cp.pool = make(map[string]*list.List)
	cp.Add(&client1)

	if player, err := cp.Player("gophie"); player == nil || err != nil {
		t.Error(err)
	}

	if player, err := cp.Player("snoopy"); player != nil || err == nil {
		t.Errorf("Received %v as player, nil expected", player.Username)
	}
}

func TestStackingStateChanges(t *testing.T) {
	cp := NewClientPool(1)
	cp.ticker.Stop()
	client1.codec.(*fakeCodec).Messages = make([][]byte, 0)
	cp.Add(&client1)

	client1.pushStateChange(&planet)
	if len(client1.codec.(*fakeCodec).Messages) != 0 {
		t.Error("Client received messages  without a tick", len(client1.codec.(*fakeCodec).Messages))
	}

	if client1.stateChange == nil {
		t.Error("Client has no stacked planets")
	}

	if len(client1.stateChange.RawPlanets) != 1 {
		t.Errorf("Client has %d stacked planets instead of 1")
	}
}

func TestBroadcast(t *testing.T) {
	cp := NewClientPool(3)
	cp.ticker.Stop()
	cp.Add(&client1)
	cp.Add(&client2)
	cp.Add(&client3)

	client1.MoveToAreas([]string{"area:1:1"})
	client2.MoveToAreas([]string{"area:1:1"})
	client3.MoveToAreas([]string{"area:2:1"})

	cp.Broadcast(&sun)
	cp.Broadcast(&planet)
	if client1.stateChange == nil {
		t.Error("Client1 has no stacked planets")
	}

	if len(client1.stateChange.RawPlanets) != 1 {
		t.Errorf("Client1 has %d stacked planets instead of 1", len(client1.stateChange.RawPlanets))
	}

	if client2.stateChange == nil {
		t.Error("Client2 has no stacked planets")
	}

	if len(client2.stateChange.RawPlanets) != 1 {
		t.Errorf("Client2 has %d stacked planets instead of 1", len(client2.stateChange.RawPlanets))
	}

	if client3.stateChange != nil {
		t.Error("Client3 has stacked planets")
	}

	// Just to make sure nothing breaks with ghost users
	client3.MoveToAreas([]string{"area:1:1"})
	delete(cp.pool, client3.Player.Username)
	cp.Broadcast(&sun)
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"text/template"
	"time"

	"github.com/coreos/fleet/schema"
	"github.com/coreos/fleet/unit"
)

const (
	// destroyPreviousCheckTimeout is the amount of time to attempt checking for
	// the new version of the service to boot.  If checking exceeds this time, the
	// previous version will not be destroyed.
	destroyPreviousCheckTimeout time.Duration = 5 * time.Minute

	// destroyPreviousCheckDelay is the amount of to wait between checks for the
	// boot completion of the new version.
	destroyPreviousCheckDelay time.Duration = 1 * time.Second
)

// DeploysResource is the HTTP resource responsible for creating and destroying
// deployments of services.
type DeploysResource struct {
	Fleet       FleetClient
	ImagePrefix string
}

// DeployRequest is the wrapper struct used to deserialize the JSON payload that
// is sent for creating a new deploy.
type DeployRequest struct {
	Deploy Deploy `json:"deploy"`
}

// Deploy is the struct that defines all the options for creating a new deploy
// and is wrapped by DeployRequest and deserialized in the Create function.
type Deploy struct {
	Version         string `json:"version"`
	DestroyPrevious bool   `json:"destroy_previous"`
	Timestamp       string `json:"timestamp,omitempty"`
	InstanceCount   int    `json:"instance_count,omitempty"`
}

// UnitTemplate is the view model that is passed to the template parser that
// renders a unit file.
type UnitTemplate struct {
	Name        string
	Version     string
	ImagePrefix string
	Timestamp   string
}

// Create is the POST endpoint for kicking off a new deployment of the service
// and version provided.  It uses these parameters to spin up tasks that will
// asyncronously start new units via Fleet and optionally wait for units to
// complete launching so that it can destroy old versions of the service that
// are no longer desired.
//
// This function assumes that it is nested inside `/services/{name}`
// and that Tigertonic is extracting the service name and providing it via query
// params.
func (dr *DeploysResource) Create(u *url.URL, h http.Header, req *DeployRequest) (int, http.Header, interface{}, error) {
	serviceName := u.Query().Get("name")

	if req.Deploy.Timestamp == "" {
		req.Deploy.Timestamp = time.Now().UTC().Format("2006.01.02-15.04.05")
	}

	units, err := dr.Fleet.Units()
	if err != nil {
		log.Println(err)
		return http.StatusInternalServerError, nil, nil, err
	}

	previousVersions := FindServiceVersions(serviceName, units)
	previousUnits := FindServiceUnits(serviceName, "", units)

	if req.Deploy.DestroyPrevious {
		if len(previousVersions) > 1 {
			return http.StatusBadRequest, nil, nil, errors.New("Too many versions are running.  Destroying previous units is not supported when more than one version is currently running.")
		}

		if req.Deploy.InstanceCount != 0 && req.Deploy.InstanceCount < len(previousUnits) {
			return http.StatusBadRequest, nil, nil, errors.New("A greater number of instances than what was specified is already running.  Make sure this number is less than or equal to the number already running or disable destroying previous units.")
		}

		for _, unit := range previousUnits {
			rangeFleetName := fleetServiceName(serviceName, unit.Version, unit.Timestamp, unit.Instance)
			newFleetName := fleetServiceName(serviceName, req.Deploy.Version, req.Deploy.Timestamp, unit.Instance)
			log.Printf("Launching watcher for %s.\n", rangeFleetName)
			go dr.destroyPrevious(rangeFleetName, newFleetName, destroyPreviousCheckDelay)
		}
	}

	instanceCount := determineNumberOfInstances(req.Deploy.InstanceCount, len(previousVersions), len(previousUnits))
	err = dr.startUnits(serviceName, req.Deploy.Version, req.Deploy.Timestamp, instanceCount)
	if err != nil {
		return http.StatusInternalServerError, nil, nil, err
	}

	return http.StatusCreated, nil, nil, nil
}

// Destroy is the DELETE endpoint for destroying the units associated with
// the service name and version provided.  It will destroy all instances of a
// unit that exists within Fleet.  If a timestamp query parameter is provided,
// only units that match that timestamp will be destroyed.
//
// This function assumes that it is nested inside
// `/services/{name}/versions/{version}` and that Tigertonic is extracting the
// service name/version and providing it via query params.
func (dr *DeploysResource) Destroy(u *url.URL, h http.Header, req interface{}) (int, http.Header, interface{}, error) {
	serviceName := u.Query().Get("name")
	version := u.Query().Get("version")

	units, err := dr.Fleet.Units()
	if err != nil {
		log.Println(err)
		return http.StatusInternalServerError, nil, nil, err
	}
	serviceUnits := FindServiceUnits(serviceName, version, units)

	for _, unit := range serviceUnits {
		if shouldDestroyUnit(u.Query().Get("timestamp"), unit.Timestamp) {
			err := dr.Fleet.DestroyUnit(fleetServiceName(serviceName, unit.Version, unit.Timestamp, unit.Instance))
			if err != nil {
				return http.StatusInternalServerError, nil, nil, err
			}
		}
	}

	return http.StatusNoContent, nil, nil, nil
}

// startUnits is a helper function for ensuring that Fleet has all the units
// configured and for launching those units.
func (dr *DeploysResource) startUnits(serviceName string, version string, timestamp string, instanceCount int) error {
	options := getUnitOptions(UnitTemplate{serviceName, version, dr.ImagePrefix, timestamp})

	// Make sure all units exist before we start setting their target states
	for i := 1; i <= instanceCount; i++ {
		fleetUnit := fleetServiceName(serviceName, version, timestamp, strconv.Itoa(i))
		log.Printf("Creating %s.\n", fleetUnit)
		err := dr.Fleet.CreateUnit(&schema.Unit{Name: fleetUnit, Options: options})
		if err != nil {
			return err
		}
	}

	// Now that all the units exist, we can launch each of them
	for i := 1; i <= instanceCount; i++ {
		fleetUnit := fleetServiceName(serviceName, version, timestamp, strconv.Itoa(i))
		log.Printf("Launching %s.\n", fleetUnit)
		err := dr.Fleet.SetUnitTargetState(fleetUnit, "launched")
		if err != nil {
			return err
		}
	}

	return nil
}

// determineNumberOfInstances is a helper function to either return the number
// of instances specified or provide a default value based on the number of
// running versions and units.
func determineNumberOfInstances(instanceCount int, numberOfVersions int, numberOfUnits int) int {
	if instanceCount != 0 {
		return instanceCount
	}

	if numberOfVersions == 1 {
		return numberOfUnits
	} else {
		return 1
	}
}

// getUnitOptions renders the unit file and converts it to an array of
// UnitOption structs.
func getUnitOptions(unitViewTemplate UnitTemplate) []*schema.UnitOption {
	var unitTemplate bytes.Buffer
	t, _ := template.New("test").Parse(dockerUnitTemplate)
	t.Execute(&unitTemplate, unitViewTemplate)

	unitFile, _ := unit.NewUnitFile(unitTemplate.String())

	return schema.MapUnitFileToSchemaUnitOptions(unitFile)
}

// fleetServiceName generates a fleet unit name with the service name, version,
// and instance encoded within it.
func fleetServiceName(name string, version string, timestamp string, instance string) string {
	return fmt.Sprintf("%s:%s:%s@%s.service", name, version, timestamp, instance)
}

// shouldDestroyUnit takes an optional input from the query string and, if
// specified, ensures that it matches the unitTimestamp.  If the optional input
// is left blank, we'll return true.  If the optional input is present and it
// doesn't match the timestamp, we'll return false.
func shouldDestroyUnit(blankOrTimestampToMatch string, unitTimestamp string) bool {
	if blankOrTimestampToMatch == "" {
		return true
	} else if blankOrTimestampToMatch == unitTimestamp {
		return true
	}
	return false
}

// destroyPrevious is responsible for watching for a new version of a service to
// complete launching on the `destroyPreviousCheckDelay` interval so that it can
// fire off a request to destroy the previous version.  If this doesn't complete
// before the destroyPreviousCheckTimeout, the attempt will be abandoned.
func (dr *DeploysResource) destroyPrevious(previousFleetUnit string, currentFleetUnit string, checkDelay time.Duration) {
	timeoutChan := make(chan bool, 1)

	go func() {
		time.Sleep(destroyPreviousCheckTimeout)
		timeoutChan <- true
	}()

	for {
		startCheck := time.After(checkDelay)

		select {
		case <-startCheck:
			log.Printf("Checking if %s has finished launching...\n", currentFleetUnit)
			states, err := dr.Fleet.UnitStates()
			if err != nil {
				log.Println(err)
				break
			}
			for _, state := range states {
				if state.Name == currentFleetUnit {
					if state.SystemdSubState == "running" {
						log.Printf("%s has launched, destroying %s.\n", currentFleetUnit, previousFleetUnit)
						err := dr.Fleet.DestroyUnit(previousFleetUnit)
						if err != nil {
							log.Println(err)
						}
						return
					}
					if state.SystemdSubState == "failed" {
						log.Printf("%s failed to launch, bailing out.\n", currentFleetUnit)
						return
					}
					log.Printf("%s isn't running (currently %s).  Trying again in %s.\n", currentFleetUnit, state.SystemdSubState, destroyPreviousCheckDelay)
				}
			}
		case <-timeoutChan:
			log.Printf("Timed out trying to destroy %s after %s.\n", previousFleetUnit, destroyPreviousCheckTimeout)
			return
		}
	}
}
<commit_msg>Clarify shouldDestroyUnit documentation.<commit_after>package server

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"text/template"
	"time"

	"github.com/coreos/fleet/schema"
	"github.com/coreos/fleet/unit"
)

const (
	// destroyPreviousCheckTimeout is the amount of time to attempt checking for
	// the new version of the service to boot.  If checking exceeds this time, the
	// previous version will not be destroyed.
	destroyPreviousCheckTimeout time.Duration = 5 * time.Minute

	// destroyPreviousCheckDelay is the amount of to wait between checks for the
	// boot completion of the new version.
	destroyPreviousCheckDelay time.Duration = 1 * time.Second
)

// DeploysResource is the HTTP resource responsible for creating and destroying
// deployments of services.
type DeploysResource struct {
	Fleet       FleetClient
	ImagePrefix string
}

// DeployRequest is the wrapper struct used to deserialize the JSON payload that
// is sent for creating a new deploy.
type DeployRequest struct {
	Deploy Deploy `json:"deploy"`
}

// Deploy is the struct that defines all the options for creating a new deploy
// and is wrapped by DeployRequest and deserialized in the Create function.
type Deploy struct {
	Version         string `json:"version"`
	DestroyPrevious bool   `json:"destroy_previous"`
	Timestamp       string `json:"timestamp,omitempty"`
	InstanceCount   int    `json:"instance_count,omitempty"`
}

// UnitTemplate is the view model that is passed to the template parser that
// renders a unit file.
type UnitTemplate struct {
	Name        string
	Version     string
	ImagePrefix string
	Timestamp   string
}

// Create is the POST endpoint for kicking off a new deployment of the service
// and version provided.  It uses these parameters to spin up tasks that will
// asyncronously start new units via Fleet and optionally wait for units to
// complete launching so that it can destroy old versions of the service that
// are no longer desired.
//
// This function assumes that it is nested inside `/services/{name}`
// and that Tigertonic is extracting the service name and providing it via query
// params.
func (dr *DeploysResource) Create(u *url.URL, h http.Header, req *DeployRequest) (int, http.Header, interface{}, error) {
	serviceName := u.Query().Get("name")

	if req.Deploy.Timestamp == "" {
		req.Deploy.Timestamp = time.Now().UTC().Format("2006.01.02-15.04.05")
	}

	units, err := dr.Fleet.Units()
	if err != nil {
		log.Println(err)
		return http.StatusInternalServerError, nil, nil, err
	}

	previousVersions := FindServiceVersions(serviceName, units)
	previousUnits := FindServiceUnits(serviceName, "", units)

	if req.Deploy.DestroyPrevious {
		if len(previousVersions) > 1 {
			return http.StatusBadRequest, nil, nil, errors.New("Too many versions are running.  Destroying previous units is not supported when more than one version is currently running.")
		}

		if req.Deploy.InstanceCount != 0 && req.Deploy.InstanceCount < len(previousUnits) {
			return http.StatusBadRequest, nil, nil, errors.New("A greater number of instances than what was specified is already running.  Make sure this number is less than or equal to the number already running or disable destroying previous units.")
		}

		for _, unit := range previousUnits {
			rangeFleetName := fleetServiceName(serviceName, unit.Version, unit.Timestamp, unit.Instance)
			newFleetName := fleetServiceName(serviceName, req.Deploy.Version, req.Deploy.Timestamp, unit.Instance)
			log.Printf("Launching watcher for %s.\n", rangeFleetName)
			go dr.destroyPrevious(rangeFleetName, newFleetName, destroyPreviousCheckDelay)
		}
	}

	instanceCount := determineNumberOfInstances(req.Deploy.InstanceCount, len(previousVersions), len(previousUnits))
	err = dr.startUnits(serviceName, req.Deploy.Version, req.Deploy.Timestamp, instanceCount)
	if err != nil {
		return http.StatusInternalServerError, nil, nil, err
	}

	return http.StatusCreated, nil, nil, nil
}

// Destroy is the DELETE endpoint for destroying the units associated with
// the service name and version provided.  It will destroy all instances of a
// unit that exists within Fleet.  If a timestamp query parameter is provided,
// only units that match that timestamp will be destroyed.
//
// This function assumes that it is nested inside
// `/services/{name}/versions/{version}` and that Tigertonic is extracting the
// service name/version and providing it via query params.
func (dr *DeploysResource) Destroy(u *url.URL, h http.Header, req interface{}) (int, http.Header, interface{}, error) {
	serviceName := u.Query().Get("name")
	version := u.Query().Get("version")

	units, err := dr.Fleet.Units()
	if err != nil {
		log.Println(err)
		return http.StatusInternalServerError, nil, nil, err
	}
	serviceUnits := FindServiceUnits(serviceName, version, units)

	for _, unit := range serviceUnits {
		if shouldDestroyUnit(u.Query().Get("timestamp"), unit.Timestamp) {
			err := dr.Fleet.DestroyUnit(fleetServiceName(serviceName, unit.Version, unit.Timestamp, unit.Instance))
			if err != nil {
				return http.StatusInternalServerError, nil, nil, err
			}
		}
	}

	return http.StatusNoContent, nil, nil, nil
}

// startUnits is a helper function for ensuring that Fleet has all the units
// configured and for launching those units.
func (dr *DeploysResource) startUnits(serviceName string, version string, timestamp string, instanceCount int) error {
	options := getUnitOptions(UnitTemplate{serviceName, version, dr.ImagePrefix, timestamp})

	// Make sure all units exist before we start setting their target states
	for i := 1; i <= instanceCount; i++ {
		fleetUnit := fleetServiceName(serviceName, version, timestamp, strconv.Itoa(i))
		log.Printf("Creating %s.\n", fleetUnit)
		err := dr.Fleet.CreateUnit(&schema.Unit{Name: fleetUnit, Options: options})
		if err != nil {
			return err
		}
	}

	// Now that all the units exist, we can launch each of them
	for i := 1; i <= instanceCount; i++ {
		fleetUnit := fleetServiceName(serviceName, version, timestamp, strconv.Itoa(i))
		log.Printf("Launching %s.\n", fleetUnit)
		err := dr.Fleet.SetUnitTargetState(fleetUnit, "launched")
		if err != nil {
			return err
		}
	}

	return nil
}

// determineNumberOfInstances is a helper function to either return the number
// of instances specified or provide a default value based on the number of
// running versions and units.
func determineNumberOfInstances(instanceCount int, numberOfVersions int, numberOfUnits int) int {
	if instanceCount != 0 {
		return instanceCount
	}

	if numberOfVersions == 1 {
		return numberOfUnits
	} else {
		return 1
	}
}

// getUnitOptions renders the unit file and converts it to an array of
// UnitOption structs.
func getUnitOptions(unitViewTemplate UnitTemplate) []*schema.UnitOption {
	var unitTemplate bytes.Buffer
	t, _ := template.New("test").Parse(dockerUnitTemplate)
	t.Execute(&unitTemplate, unitViewTemplate)

	unitFile, _ := unit.NewUnitFile(unitTemplate.String())

	return schema.MapUnitFileToSchemaUnitOptions(unitFile)
}

// fleetServiceName generates a fleet unit name with the service name, version,
// and instance encoded within it.
func fleetServiceName(name string, version string, timestamp string, instance string) string {
	return fmt.Sprintf("%s:%s:%s@%s.service", name, version, timestamp, instance)
}

// shouldDestroyUnit takes an optional timestamp (from the query string) and, if
// specified, ensures that it matches the unitTimestamp.  If the optional
// timestamp is left blank, we'll return true.  If the optional timestamp is
// present and it doesn't match the timestamp, we'll return false.
func shouldDestroyUnit(blankOrTimestampToMatch string, unitTimestamp string) bool {
	if blankOrTimestampToMatch == "" {
		return true
	} else if blankOrTimestampToMatch == unitTimestamp {
		return true
	}
	return false
}

// destroyPrevious is responsible for watching for a new version of a service to
// complete launching on the `destroyPreviousCheckDelay` interval so that it can
// fire off a request to destroy the previous version.  If this doesn't complete
// before the destroyPreviousCheckTimeout, the attempt will be abandoned.
func (dr *DeploysResource) destroyPrevious(previousFleetUnit string, currentFleetUnit string, checkDelay time.Duration) {
	timeoutChan := make(chan bool, 1)

	go func() {
		time.Sleep(destroyPreviousCheckTimeout)
		timeoutChan <- true
	}()

	for {
		startCheck := time.After(checkDelay)

		select {
		case <-startCheck:
			log.Printf("Checking if %s has finished launching...\n", currentFleetUnit)
			states, err := dr.Fleet.UnitStates()
			if err != nil {
				log.Println(err)
				break
			}
			for _, state := range states {
				if state.Name == currentFleetUnit {
					if state.SystemdSubState == "running" {
						log.Printf("%s has launched, destroying %s.\n", currentFleetUnit, previousFleetUnit)
						err := dr.Fleet.DestroyUnit(previousFleetUnit)
						if err != nil {
							log.Println(err)
						}
						return
					}
					if state.SystemdSubState == "failed" {
						log.Printf("%s failed to launch, bailing out.\n", currentFleetUnit)
						return
					}
					log.Printf("%s isn't running (currently %s).  Trying again in %s.\n", currentFleetUnit, state.SystemdSubState, destroyPreviousCheckDelay)
				}
			}
		case <-timeoutChan:
			log.Printf("Timed out trying to destroy %s after %s.\n", previousFleetUnit, destroyPreviousCheckTimeout)
			return
		}
	}
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/Shyp/rickover/rest"
	"github.com/Shyp/rickover/test"
)

func newSSAServer() (*SharedSecretAuthorizer, http.Handler) {
	ssa := NewSharedSecretAuthorizer()
	return ssa, Get(ssa)
}

var empty = json.RawMessage([]byte("{}"))

func Test401NoCredentials(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	DefaultServer.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusUnauthorized)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Unauthorized. Please include your API credentials")
	test.AssertEquals(t, e.ID, "unauthorized")
}

func Test401UnknownUser(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	req.SetBasicAuth("unknown-user", "foobar")
	_, server := newSSAServer()
	server.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusForbidden)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Username or password are invalid. Please double check your credentials")
	test.AssertEquals(t, e.ID, "forbidden")
}

func Test401UnknownPassword(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	ssa, server := newSSAServer()
	ssa.AddUser("401-unknown-password", "right_password")
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	req.SetBasicAuth("401-unknown-password", "wrong_password")
	server.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusForbidden)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Incorrect password for user 401-unknown-password")
	test.AssertEquals(t, e.ID, "incorrect_password")
}

func Test400NoBody(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	ssa, server := newSSAServer()
	ssa.AddUser("test", "password")
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_123", nil)
	req.SetBasicAuth("test", "password")
	server.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusBadRequest)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Missing required field: data")
	test.AssertEquals(t, e.ID, "missing_parameter")
	test.AssertEquals(t, e.Instance, "/v1/jobs/echo/job_123")
}

func Test400EmptyBody(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	var v interface{}
	err := json.Unmarshal([]byte("{}"), &v)
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(v)
	ssa, server := newSSAServer()
	ssa.AddUser("test", "password")
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_123", b)
	req.SetBasicAuth("test", "password")
	server.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusBadRequest)
	var e rest.Error
	err = json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Missing required field: data")
	test.AssertEquals(t, e.ID, "missing_parameter")
	test.AssertEquals(t, e.Instance, "/v1/jobs/echo/job_123")
}

func Test400InvalidUUID(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	DefaultAuthorizer.AddUser("test", "password")
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_123", b)
	req.SetBasicAuth("test", "password")
	DefaultServer.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusBadRequest)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Could not parse \"job_123\" as a UUID with a prefix")
	test.AssertEquals(t, e.ID, "invalid_uuid")
}

// Would be great to 400 this but it's difficult with some of the route
// overlapping we have in place.
func Test404WrongPrefix(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/usr_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	req.SetBasicAuth("test", "password")
	DefaultServer.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusNotFound)
}

func Test413TooLargeJSON(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	// 4 bytes per record - the value and the quotes around it.
	var bigarr [100 * 256]string
	for i := range bigarr {
		bigarr[i] = "a"
	}
	bits, _ := json.Marshal(bigarr)
	ejr := &EnqueueJobRequest{
		Data: json.RawMessage(bits),
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	test.Assert(t, len(b.Bytes()) > 100*1024, fmt.Sprintf("%d", len(b.Bytes())))
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	req.SetBasicAuth("test", "password")
	DefaultServer.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusRequestEntityTooLarge)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Data parameter is too large (100KB max)")
	test.AssertEquals(t, e.ID, "entity_too_large")
}
<commit_msg>Fix race introduced by parallelizing tests<commit_after>package server

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/Shyp/rickover/rest"
	"github.com/Shyp/rickover/test"
)

func newSSAServer() (*SharedSecretAuthorizer, http.Handler) {
	ssa := NewSharedSecretAuthorizer()
	return ssa, Get(ssa)
}

var empty = json.RawMessage([]byte("{}"))

func Test401NoCredentials(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	Get(u).ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusUnauthorized)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Unauthorized. Please include your API credentials")
	test.AssertEquals(t, e.ID, "unauthorized")
}

func Test401UnknownUser(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	req.SetBasicAuth("unknown-user", "foobar")
	_, server := newSSAServer()
	server.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusForbidden)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Username or password are invalid. Please double check your credentials")
	test.AssertEquals(t, e.ID, "forbidden")
}

func Test401UnknownPassword(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	ssa, server := newSSAServer()
	ssa.AddUser("401-unknown-password", "right_password")
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	req.SetBasicAuth("401-unknown-password", "wrong_password")
	server.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusForbidden)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Incorrect password for user 401-unknown-password")
	test.AssertEquals(t, e.ID, "incorrect_password")
}

func Test400NoBody(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	ssa, server := newSSAServer()
	ssa.AddUser("test", "password")
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_123", nil)
	req.SetBasicAuth("test", "password")
	server.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusBadRequest)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Missing required field: data")
	test.AssertEquals(t, e.ID, "missing_parameter")
	test.AssertEquals(t, e.Instance, "/v1/jobs/echo/job_123")
}

func Test400EmptyBody(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	var v interface{}
	err := json.Unmarshal([]byte("{}"), &v)
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(v)
	ssa, server := newSSAServer()
	ssa.AddUser("test", "password")
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_123", b)
	req.SetBasicAuth("test", "password")
	server.ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusBadRequest)
	var e rest.Error
	err = json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Missing required field: data")
	test.AssertEquals(t, e.ID, "missing_parameter")
	test.AssertEquals(t, e.Instance, "/v1/jobs/echo/job_123")
}

func Test400InvalidUUID(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_123", b)
	req.SetBasicAuth("test", "password")
	Get(u).ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusBadRequest)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Could not parse \"job_123\" as a UUID with a prefix")
	test.AssertEquals(t, e.ID, "invalid_uuid")
}

// Would be great to 400 this but it's difficult with some of the route
// overlapping we have in place.
func Test404WrongPrefix(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	ejr := &EnqueueJobRequest{
		Data: empty,
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/usr_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	req.SetBasicAuth("test", "password")
	Get(u).ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusNotFound)
}

func Test413TooLargeJSON(t *testing.T) {
	t.Parallel()
	w := httptest.NewRecorder()
	// 4 bytes per record - the value and the quotes around it.
	var bigarr [100 * 256]string
	for i := range bigarr {
		bigarr[i] = "a"
	}
	bits, _ := json.Marshal(bigarr)
	ejr := &EnqueueJobRequest{
		Data: json.RawMessage(bits),
	}
	b := new(bytes.Buffer)
	json.NewEncoder(b).Encode(ejr)
	test.Assert(t, len(b.Bytes()) > 100*1024, fmt.Sprintf("%d", len(b.Bytes())))
	req, _ := http.NewRequest("PUT", "/v1/jobs/echo/job_6740b44e-13b9-475d-af06-979627e0e0d6", b)
	req.SetBasicAuth("test", "password")
	Get(u).ServeHTTP(w, req)
	test.AssertEquals(t, w.Code, http.StatusRequestEntityTooLarge)
	var e rest.Error
	err := json.Unmarshal(w.Body.Bytes(), &e)
	test.AssertNotError(t, err, "")
	test.AssertEquals(t, e.Title, "Data parameter is too large (100KB max)")
	test.AssertEquals(t, e.ID, "entity_too_large")
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"github.com/dimfeld/httptreemux"
	"gopkg.in/go-playground/colors.v1"

	"github.com/terranodo/tegola"
	"github.com/terranodo/tegola/mapbox/style"
)

type HandleMapStyle struct {
	//	required
	mapName string
	//	the requests extension defaults to "json"
	extension string
}

//	returns details about a map according to the
//	tileJSON spec (https://github.com/mapbox/tilejson-spec/tree/master/2.1.0)
//
//	URI scheme: /capabilities/:map_name.json
//		map_name - map name in the config file
func (req HandleMapStyle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var err error
	var rScheme string
	//	check if the request is http or https. the scheme is needed for the TileURLs and
	//	r.URL.Scheme can be empty if a relative request is issued from the client. (i.e. GET /foo.html)
	if r.TLS != nil {
		rScheme = "https://"
	} else {
		rScheme = "http://"
	}

	params := httptreemux.ContextParams(r.Context())

	//	read the map_name value from the request
	mapName := params["map_name"]
	mapNameParts := strings.Split(mapName, ".")

	req.mapName = mapNameParts[0]
	//	check if we have a provided extension
	if len(mapNameParts) > 2 {
		req.extension = mapNameParts[len(mapNameParts)-1]
	} else {
		req.extension = "json"
	}

	//	lookup our Map
	m, ok := maps[req.mapName]
	if !ok {
		log.Printf("map (%v) not configured. check your config file", req.mapName)
		http.Error(w, "map ("+req.mapName+") not configured. check your config file", http.StatusNotFound)
		return
	}

	debug := r.URL.Query().Get("debug")

	sourceURL := fmt.Sprintf("%v%v/capabilities/%v.json", rScheme, hostName(r), req.mapName)
	if debug == "true" {
		sourceURL += "?debug=true"
	}

	mapboxStyle := style.Root{
		Name:    m.Name,
		Version: style.Version,
		Center:  [2]float64{m.Center[0], m.Center[1]},
		Zoom:    m.Center[2],
		Sources: map[string]style.Source{
			req.mapName: style.Source{
				Type: style.SourceTypeVector,
				URL:  sourceURL,
			},
		},
		Layers: []style.Layer{},
	}
	//	if we have a debug param create a layer style
	if debug == "true" {
		debugTileOutline := style.Layer{
			ID:          "debug-tile-outline",
			Source:      req.mapName,
			SourceLayer: "debug-tile-outline",
			Layout: &style.LayerLayout{
				Visibility: style.LayoutVisible,
			},
			Type: style.LayerTypeLine,
			Paint: &style.LayerPaint{
				LineColor: stringToColorHex("debug"),
			},
		}

		mapboxStyle.Layers = append(mapboxStyle.Layers, debugTileOutline)

		debugTileCenter := style.Layer{
			ID:          "debug-tile-center",
			Source:      req.mapName,
			SourceLayer: "debug-tile-center",
			Layout: &style.LayerLayout{
				Visibility: style.LayoutVisible,
			},
			Type: style.LayerTypeCircle,
			Paint: &style.LayerPaint{
				CircleRadius: 3,
				CircleColor:  stringToColorHex("debug"),
			},
		}

		mapboxStyle.Layers = append(mapboxStyle.Layers, debugTileCenter)
	}

	//	determing the min and max zoom for this map
	for _, l := range m.Layers {
		//	build our vector layer details
		layer := style.Layer{
			ID:          l.Name,
			Source:      req.mapName,
			SourceLayer: l.Name,
			Layout: &style.LayerLayout{
				Visibility: style.LayoutVisible,
			},
		}

		//	chose our paint type based on the geometry type
		switch l.GeomType.(type) {
		case tegola.Point, tegola.Point3, tegola.MultiPoint:
			layer.Type = style.LayerTypeCircle
			layer.Paint = &style.LayerPaint{
				CircleRadius: 3,
				CircleColor:  stringToColorHex(l.Name),
			}
		case tegola.LineString, tegola.MultiLine:
			layer.Type = style.LayerTypeLine
			layer.Paint = &style.LayerPaint{
				LineColor: stringToColorHex(l.Name),
			}
		case tegola.Polygon, tegola.MultiPolygon:
			layer.Type = style.LayerTypeFill
			hexColor := stringToColorHex(l.Name)

			hex, err := colors.ParseHEX(hexColor)
			if err != nil {
				log.Println("error parsing hex color (%v)", hexColor)
				hex, _ = colors.ParseHEX("#fff") //	default to white on error
			}

			rgba := hex.ToRGBA()
			//	set the opacity to 10%
			rgba.A = 0.10

			layer.Paint = &style.LayerPaint{
				FillColor:        rgba.String(),
				FillOutlineColor: hexColor,
			}
		default:
			log.Printf("layer (%v) has unsupported geometry type (%v)", l.Name, l.GeomType)
		}

		//	add our layer to our tile layer response
		mapboxStyle.Layers = append(mapboxStyle.Layers, layer)
	}

	//	TODO: how configurable do we want the CORS policy to be?
	//	set CORS header
	w.Header().Add("Access-Control-Allow-Origin", "*")

	//	mimetype for protocol buffers
	w.Header().Add("Content-Type", "application/json")

	if err = json.NewEncoder(w).Encode(mapboxStyle); err != nil {
		log.Printf("error encoding tileJSON for map (%v)", req.mapName)
	}
}

//	port of https://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-javascript
func stringToColorHex(str string) string {
	var hash uint
	for i := range []rune(str) {
		hash = uint(str[i]) + ((hash << 5) - hash)
	}
	var color string
	for i := 0; i < 3; i++ {
		value := (hash >> (uint(i) * 8)) & 0xFF
		val := "00" + strconv.FormatUint(uint64(value), 16)
		color += val[len(val)-2:]
	}
	return "#" + color
}
<commit_msg>implemented request scheme detection in server style endpoint<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"github.com/dimfeld/httptreemux"
	"gopkg.in/go-playground/colors.v1"

	"github.com/terranodo/tegola"
	"github.com/terranodo/tegola/mapbox/style"
)

type HandleMapStyle struct {
	//	required
	mapName string
	//	the requests extension defaults to "json"
	extension string
}

//	returns details about a map according to the
//	tileJSON spec (https://github.com/mapbox/tilejson-spec/tree/master/2.1.0)
//
//	URI scheme: /capabilities/:map_name.json
//		map_name - map name in the config file
func (req HandleMapStyle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var err error

	params := httptreemux.ContextParams(r.Context())

	//	read the map_name value from the request
	mapName := params["map_name"]
	mapNameParts := strings.Split(mapName, ".")

	req.mapName = mapNameParts[0]
	//	check if we have a provided extension
	if len(mapNameParts) > 2 {
		req.extension = mapNameParts[len(mapNameParts)-1]
	} else {
		req.extension = "json"
	}

	//	lookup our Map
	m, ok := maps[req.mapName]
	if !ok {
		log.Printf("map (%v) not configured. check your config file", req.mapName)
		http.Error(w, "map ("+req.mapName+") not configured. check your config file", http.StatusNotFound)
		return
	}

	debug := r.URL.Query().Get("debug")

	sourceURL := fmt.Sprintf("%v://%v/capabilities/%v.json", scheme(r), hostName(r), req.mapName)
	if debug == "true" {
		sourceURL += "?debug=true"
	}

	mapboxStyle := style.Root{
		Name:    m.Name,
		Version: style.Version,
		Center:  [2]float64{m.Center[0], m.Center[1]},
		Zoom:    m.Center[2],
		Sources: map[string]style.Source{
			req.mapName: style.Source{
				Type: style.SourceTypeVector,
				URL:  sourceURL,
			},
		},
		Layers: []style.Layer{},
	}
	//	if we have a debug param create a layer style
	if debug == "true" {
		debugTileOutline := style.Layer{
			ID:          "debug-tile-outline",
			Source:      req.mapName,
			SourceLayer: "debug-tile-outline",
			Layout: &style.LayerLayout{
				Visibility: style.LayoutVisible,
			},
			Type: style.LayerTypeLine,
			Paint: &style.LayerPaint{
				LineColor: stringToColorHex("debug"),
			},
		}

		mapboxStyle.Layers = append(mapboxStyle.Layers, debugTileOutline)

		debugTileCenter := style.Layer{
			ID:          "debug-tile-center",
			Source:      req.mapName,
			SourceLayer: "debug-tile-center",
			Layout: &style.LayerLayout{
				Visibility: style.LayoutVisible,
			},
			Type: style.LayerTypeCircle,
			Paint: &style.LayerPaint{
				CircleRadius: 3,
				CircleColor:  stringToColorHex("debug"),
			},
		}

		mapboxStyle.Layers = append(mapboxStyle.Layers, debugTileCenter)
	}

	//	determing the min and max zoom for this map
	for _, l := range m.Layers {
		//	build our vector layer details
		layer := style.Layer{
			ID:          l.Name,
			Source:      req.mapName,
			SourceLayer: l.Name,
			Layout: &style.LayerLayout{
				Visibility: style.LayoutVisible,
			},
		}

		//	chose our paint type based on the geometry type
		switch l.GeomType.(type) {
		case tegola.Point, tegola.Point3, tegola.MultiPoint:
			layer.Type = style.LayerTypeCircle
			layer.Paint = &style.LayerPaint{
				CircleRadius: 3,
				CircleColor:  stringToColorHex(l.Name),
			}
		case tegola.LineString, tegola.MultiLine:
			layer.Type = style.LayerTypeLine
			layer.Paint = &style.LayerPaint{
				LineColor: stringToColorHex(l.Name),
			}
		case tegola.Polygon, tegola.MultiPolygon:
			layer.Type = style.LayerTypeFill
			hexColor := stringToColorHex(l.Name)

			hex, err := colors.ParseHEX(hexColor)
			if err != nil {
				log.Println("error parsing hex color (%v)", hexColor)
				hex, _ = colors.ParseHEX("#fff") //	default to white on error
			}

			rgba := hex.ToRGBA()
			//	set the opacity to 10%
			rgba.A = 0.10

			layer.Paint = &style.LayerPaint{
				FillColor:        rgba.String(),
				FillOutlineColor: hexColor,
			}
		default:
			log.Printf("layer (%v) has unsupported geometry type (%v)", l.Name, l.GeomType)
		}

		//	add our layer to our tile layer response
		mapboxStyle.Layers = append(mapboxStyle.Layers, layer)
	}

	//	TODO: how configurable do we want the CORS policy to be?
	//	set CORS header
	w.Header().Add("Access-Control-Allow-Origin", "*")

	//	mimetype for protocol buffers
	w.Header().Add("Content-Type", "application/json")

	if err = json.NewEncoder(w).Encode(mapboxStyle); err != nil {
		log.Printf("error encoding tileJSON for map (%v)", req.mapName)
	}
}

//	port of https://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-javascript
func stringToColorHex(str string) string {
	var hash uint
	for i := range []rune(str) {
		hash = uint(str[i]) + ((hash << 5) - hash)
	}
	var color string
	for i := 0; i < 3; i++ {
		value := (hash >> (uint(i) * 8)) & 0xFF
		val := "00" + strconv.FormatUint(uint64(value), 16)
		color += val[len(val)-2:]
	}
	return "#" + color
}
<|endoftext|>


<commit_before>// Package creator contains the creator service that should create a new entry in the database for a github repo
// that is not already in there.
package creator

import (
	"fmt"
	"log"
	"sort"
	"strconv"
	"time"

	"github.com/evermax/stargraph/api"
	"github.com/evermax/stargraph/github"
	"github.com/evermax/stargraph/lib/store"
	"github.com/evermax/stargraph/service"
	"github.com/streadway/amqp"
)

// Creator contains the database to use, the type of service (creator)
// and the job queue to send job to workers. It implements the service.SWorker interface.
type Creator struct {
	t        string
	db       store.Store
	jobQueue chan service.Job
}

// NewCreator creates a new creator
func NewCreator(db store.Store) Creator {
	// connect to the AMQP server
	return Creator{
		t:  service.CreatorName,
		db: db,
	}
}

// Type will return the string "creator" to implement the interface.
func (c Creator) Type() string {
	return c.t
}

func (c Creator) JobQueue() chan service.Job {
	return c.jobQueue
}

// Run create a connection to the AMQP server and listen to incoming requests
// To create Github repository graphs.
func (c Creator) Run(amqpURL, addQueueN string) error {

	// Dial connection to the AMQP server
	conn, err := amqp.Dial(amqpURL)
	if err != nil {
		return fmt.Errorf("Failed to connect to AMQP with url %s: %v", amqpURL, err)
	}
	defer conn.Close()

	// Open a channel of communication through the connection
	ch, err := conn.Channel()
	if err != nil {
		return fmt.Errorf("Failed to open a channel: %v", err)
	}
	defer ch.Close()

	// Declare a queue
	q, err := ch.QueueDeclare(
		addQueueN, // name
		true,      // durable
		false,     // delete when unused
		false,     // exclusive
		false,     // no-wait
		nil,       // arguments
	)
	if err != nil {
		return fmt.Errorf("Failed to declare a queue: %v", err)
	}

	err = ch.Qos(
		1,     // prefetch count
		0,     // prefetch size
		false, // global
	)
	if err != nil {
		return fmt.Errorf("Failed to set QoS: %v", err)
	}

	// Register as a consumer of the queue and
	// return a channel that brings the incoming messages
	msgs, err := ch.Consume(
		q.Name, // queue
		"",     // consumer
		false,  // auto-ack
		false,  // exclusive
		false,  // no-local
		false,  // no-wait
		nil,    // args
	)

	if err != nil {
		return fmt.Errorf("Failed to register as consumer: %v", err)
	}

	forever := make(chan bool)
	go func() {
		for d := range msgs {
			log.Printf("Received a message: %s", d.Body)

			err := c.creatorWork(d.Body)
			if err == store.ErrAlreadyExist {
				d.Ack(false)
				log.Printf("WARN: Asked to recreate %s, aborting", d.Body)
				continue
			}
			if err != nil {
				// log it
				d.Nack(false, true)
				continue
			}

			d.Ack(false)
			log.Printf("Done")
		}
		forever <- true
	}()
	<-forever
	return nil
}

func (c Creator) creatorWork(body []byte) error {
	apiJob, err := api.Unmarshal(body)
	if err != nil {
		return fmt.Errorf("Umarshalling error with %s: %v", body, err)
	}

	repoInfo := github.RepoInfo{
		WorkedOn: true,
	}

	// Create the repository on the store, claim the work
	key, err := c.db.AddRepo(repoInfo)
	if err != nil {
		return fmt.Errorf("Adding to store error with %s: %v", body, err)
	}

	timestamps, err := GetAllTimestamps(c.jobQueue, 100, apiJob.Token, repoInfo)
	if err != nil {
		return fmt.Errorf("Error with %s: %v", body, err)
	}

	lastStar := timestamps[len(timestamps)-1]

	repoInfo.Timestamps = timestamps

	repoInfo.WorkedOn = false
	repoInfo.LastUpdate = time.Now().Format(time.RFC3339)
	repoInfo.LastStarDate = time.Unix(lastStar, 0).Format(time.RFC3339)
	err = c.db.PutRepo(repoInfo, key)
	if err != nil {
		return fmt.Errorf("Put to store error with %s: %v", body, err)
	}

	// TODO: Think if this could be done on the fly first
	// TODO: lib.CanvasJS(timestamps, repoInfo, buffer)
	// Then send the buffer to the database
	// TODO: wrap that into the dbaccess file service.Objects.Insert(*bucketName, object).Media(file).Do()
	// https://cloud.google.com/storage/docs/json_api/v1/json-api-go-samples
	return nil
}

// GetAllTimestamps will get the timestamps for all the stars of the passed repository.
// It will use the perPage number and the Github API token to make a number of queries the the Github API.
// The jobQueue is used to have a pool of workers that will make one API call at a time each.
// The service itself would typically share ressources with several other services.
func GetAllTimestamps(jobQueue chan service.Job, perPage int, token string, repoInfo github.IRepoInfo) ([]int64, error) {
	// calculate the number of calls to make to Github API
	numberOfAPICall := repoInfo.StarCount() / perPage
	// don't forget to add the possible incomplete page
	if repoInfo.StarCount()%perPage > 0 {
		numberOfAPICall++
	}

	url := repoInfo.URL() + "?per_page=" + strconv.Itoa(perPage)

	// create the timestamp array that will be used to
	// agregate all the timestamps that the main routine gets
	// from the workers
	var timestamps []int64
	stampsChan := make(chan []int64, 8)
	defer close(stampsChan)

	// create error channel to send the errors
	// from the goroutines and the master
	errchan := make(chan error)
	defer close(errchan)

	var j int
	var err error
	// Put jobs to make API calls in the job queue
	for i := 0; i < numberOfAPICall; i++ {
		jobQueue <- service.Job{
			Num:               i + 1,
			ApiURL:            url,
			ApiToken:          token,
			ErrorChannel:      errchan,
			TimestampsChannel: stampsChan,
		}
	}
L:
	for {
		select {
		case err = <-errchan:
			j++
			if j >= numberOfAPICall {
				break L
			}

		case stamps := <-stampsChan:
			timestamps = append(timestamps, stamps...)
			j++
			if j >= numberOfAPICall {
				break L
			}
		}
	}
	sort.Sort(sortableTimestamps(timestamps))

	return timestamps, err
}

type sortableTimestamps []int64

func (s sortableTimestamps) Len() int           { return len(s) }
func (s sortableTimestamps) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s sortableTimestamps) Less(i, j int) bool { return s[i] < s[j] }
<commit_msg>Make creator use mq.MessageQueue instead of amqp directly<commit_after>// Package creator contains the creator service that should create a new entry in the database for a github repo
// that is not already in there.
package creator

import (
	"fmt"
	"log"
	"sort"
	"strconv"
	"time"

	"github.com/evermax/stargraph/api"
	"github.com/evermax/stargraph/github"
	"github.com/evermax/stargraph/lib/mq"
	"github.com/evermax/stargraph/lib/store"
	"github.com/evermax/stargraph/service"
)

// Creator contains the database to use, the type of service (creator)
// and the job queue to send job to workers. It implements the service.SWorker interface.
type Creator struct {
	t         string
	db        store.Store
	messageQ  mq.MessageQueue
	jobQueue  chan service.Job
	queueName string
}

// NewCreator creates a new creator
func NewCreator(db store.Store, queue mq.MessageQueue) Creator {
	// connect to the AMQP server
	return Creator{
		t:        service.CreatorName,
		db:       db,
		messageQ: queue,
	}
}

// Type will return the string "creator" to implement the interface.
func (c Creator) Type() string {
	return c.t
}

func (c Creator) JobQueue() chan service.Job {
	return c.jobQueue
}

// Run create a connection to the AMQP server and listen to incoming requests
// To create Github repository graphs.
func (c Creator) Run() error {
	return c.messageQ.Consume(c.queueName, c.receiveMessage)
}

func (c Creator) receiveMessage(d mq.Delivery, forever chan bool) {
	log.Printf("Received a message: %s", d.Body)

	err := c.creatorWork(d.Body())
	if err == store.ErrAlreadyExist {
		d.Ack(false)
		log.Printf("WARN: Asked to recreate %s, aborting", d.Body)
		return
	}
	if err != nil {
		// log it
		d.Nack(false, true)
		return
	}

	d.Ack(false)
	log.Printf("Done")
}

func (c Creator) creatorWork(body []byte) error {
	apiJob, err := api.Unmarshal(body)
	if err != nil {
		return fmt.Errorf("Umarshalling error with %s: %v", body, err)
	}

	repoInfo := github.RepoInfo{
		WorkedOn: true,
	}

	// Create the repository on the store, claim the work
	key, err := c.db.AddRepo(repoInfo)
	if err != nil {
		return fmt.Errorf("Adding to store error with %s: %v", body, err)
	}

	timestamps, err := GetAllTimestamps(c.jobQueue, 100, apiJob.Token, repoInfo)
	if err != nil {
		return fmt.Errorf("Error with %s: %v", body, err)
	}

	lastStar := timestamps[len(timestamps)-1]

	repoInfo.Timestamps = timestamps

	repoInfo.WorkedOn = false
	repoInfo.LastUpdate = time.Now().Format(time.RFC3339)
	repoInfo.LastStarDate = time.Unix(lastStar, 0).Format(time.RFC3339)
	err = c.db.PutRepo(repoInfo, key)
	if err != nil {
		return fmt.Errorf("Put to store error with %s: %v", body, err)
	}

	// TODO: Think if this could be done on the fly first
	// TODO: lib.CanvasJS(timestamps, repoInfo, buffer)
	// Then send the buffer to the database
	// TODO: wrap that into the dbaccess file service.Objects.Insert(*bucketName, object).Media(file).Do()
	// https://cloud.google.com/storage/docs/json_api/v1/json-api-go-samples
	return nil
}

// GetAllTimestamps will get the timestamps for all the stars of the passed repository.
// It will use the perPage number and the Github API token to make a number of queries the the Github API.
// The jobQueue is used to have a pool of workers that will make one API call at a time each.
// The service itself would typically share ressources with several other services.
func GetAllTimestamps(jobQueue chan service.Job, perPage int, token string, repoInfo github.IRepoInfo) ([]int64, error) {
	// calculate the number of calls to make to Github API
	numberOfAPICall := repoInfo.StarCount() / perPage
	// don't forget to add the possible incomplete page
	if repoInfo.StarCount()%perPage > 0 {
		numberOfAPICall++
	}

	url := repoInfo.URL() + "?per_page=" + strconv.Itoa(perPage)

	// create the timestamp array that will be used to
	// agregate all the timestamps that the main routine gets
	// from the workers
	var timestamps []int64
	stampsChan := make(chan []int64, 8)
	defer close(stampsChan)

	// create error channel to send the errors
	// from the goroutines and the master
	errchan := make(chan error)
	defer close(errchan)

	var j int
	var err error
	// Put jobs to make API calls in the job queue
	for i := 0; i < numberOfAPICall; i++ {
		jobQueue <- service.Job{
			Num:               i + 1,
			ApiURL:            url,
			ApiToken:          token,
			ErrorChannel:      errchan,
			TimestampsChannel: stampsChan,
		}
	}
L:
	for {
		select {
		case err = <-errchan:
			j++
			if j >= numberOfAPICall {
				break L
			}

		case stamps := <-stampsChan:
			timestamps = append(timestamps, stamps...)
			j++
			if j >= numberOfAPICall {
				break L
			}
		}
	}
	sort.Sort(sortableTimestamps(timestamps))

	return timestamps, err
}

type sortableTimestamps []int64

func (s sortableTimestamps) Len() int           { return len(s) }
func (s sortableTimestamps) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s sortableTimestamps) Less(i, j int) bool { return s[i] < s[j] }
<|endoftext|>