<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/MemeLabs/overrustlelogs/common"
)

// TwitchHub ...
type TwitchHub struct {
	chatLock       sync.RWMutex
	chats          []*common.Twitch
	chLock         sync.RWMutex
	channels       []string
	logHandler     func(m <-chan *common.Message)
	admins         map[string]struct{}
	commandChannel string
	quit           chan struct{}
}

// NewTwitchLogger ...
func NewTwitchLogger(f func(m <-chan *common.Message)) *TwitchHub {
	t := &TwitchHub{
		logHandler:     f,
		admins:         make(map[string]struct{}),
		commandChannel: common.GetConfig().Twitch.CommandChannel,
		quit:           make(chan struct{}, 1),
	}

	admins := common.GetConfig().Twitch.Admins
	for _, a := range admins {
		t.admins[a] = struct{}{}
	}

	d, err := ioutil.ReadFile(common.GetConfig().Twitch.ChannelListPath)
	if err != nil {
		log.Fatalf("unable to read channels %s", err)
	}
	if err := json.Unmarshal(d, &t.channels); err != nil {
		log.Fatalf("unable to read channels %s", err)
	}
	return t
}

// Start ...
func (t *TwitchHub) Start() {
	var c int
	for _, channel := range t.channels {
		select {
		case <-t.quit:
			return
		default:
		}
		err := t.join(channel, false)
		if err != nil {
			log.Printf("%v", err)
			continue
		}
		c++
	}
	log.Printf("joined %d chats, wew lad :^)\n", c)
}

// Stop ...
func (t *TwitchHub) Stop() {
	close(t.quit)
	var wg sync.WaitGroup

	t.chatLock.Lock()
	wg.Add(len(t.chats))
	for i, c := range t.chats {
		log.Printf("stopping chat: %d\n", i)
		go c.Stop(&wg)
	}
	t.chatLock.Unlock()
	wg.Wait()
}

func (t *TwitchHub) runCommand(c *common.Twitch, m *common.Message) {
	if _, ok := t.admins[m.Nick]; !ok || m.Type != "MSG" {
		return
	}

	parts := strings.Split(strings.ToLower(m.Data), " ")
	switch parts[0] {
	case "!join":
		if err := t.join(strings.TrimSpace(parts[1]), true); err != nil {
			log.Println(err)
			log.Println(c.Message(m.Channel, err.Error()))
			return
		}
		c.Message(m.Channel, fmt.Sprintf("Logging %s", strings.TrimSpace(parts[1])))
	case "!leave":
		if err := t.leave(parts[1]); err != nil {
			log.Println(err)
			c.Message(m.Channel, fmt.Sprintf("Not logging %s", strings.TrimSpace(parts[1])))
			return
		}
		c.Message(m.Channel, fmt.Sprintf("Leaving %s", parts[1]))
	}
}

func (t *TwitchHub) join(ch string, init bool) error {
	if inSlice(t.channels, ch) && init {
		return fmt.Errorf("already logging %s", ch)
	}
	if !channelExists(ch) && init {
		return fmt.Errorf("%s doesn't exist my dude", ch)
	}
	if init {
		t.addChannel(ch)
		go t.saveChannels()
	}
	t.chatLock.Lock()
	var chat *common.Twitch
	for _, c := range t.chats {
		if len(c.Channels()) >= common.MaxChannelsPerChat {
			continue
		}
		chat = c
	}
	if chat == nil {
		chat = common.NewTwitch()
		chat.Run()
		t.chats = append(t.chats, chat)
		go t.msgHandler(chat)
	}
	t.chatLock.Unlock()
	if err := chat.Join(ch); err != nil {
		return fmt.Errorf("failed to join %s: %v", ch, err)
	}
	log.Println("joined", ch)
	return nil
}

func (t *TwitchHub) msgHandler(c *common.Twitch) {
	messages := make(chan *common.Message, common.MessageBufferSize)
	go t.logHandler(messages)
	for {
		select {
		case <-t.quit:
			close(messages)
			return
		case m := <-c.Messages():
			messages <- m
			if t.commandChannel == m.Channel {
				go t.runCommand(c, m)
			}
		}
	}
}

func (t *TwitchHub) leave(ch string) error {
	t.chatLock.Lock()
	defer t.chatLock.Unlock()
	for _, c := range t.chats {
		if !inSlice(c.Channels(), ch) {
			continue
		}
		if err := c.Leave(ch); err != nil {
			return fmt.Errorf("error leaving %s: %v", ch, err)
		}
		if err := t.removeChannel(ch); err != nil {
			return fmt.Errorf("error removing channel from list: %v", err)
		}
		if err := t.saveChannels(); err != nil {
			log.Printf("error saving channels: %v", err)
		}
		log.Println("leaving", ch)
		return nil
	}
	return fmt.Errorf("%s not found", ch)
}

func (t *TwitchHub) addChannel(ch string) {
	t.chLock.Lock()
	t.channels = append(t.channels, ch)
	t.chLock.Unlock()
}

func (t *TwitchHub) removeChannel(ch string) error {
	t.chLock.Lock()
	defer t.chLock.Unlock()
	sort.Strings(t.channels)
	for i, c := range t.channels {
		if strings.EqualFold(ch, c) {
			t.channels = append(t.channels[:i], t.channels[i+1:]...)
			return nil
		}
	}
	return fmt.Errorf("didn't find %s in the channels list", ch)
}

func (t *TwitchHub) saveChannels() error {
	f, err := os.Create(common.GetConfig().Twitch.ChannelListPath)
	if err != nil {
		log.Printf("error saving channel list %s", err)
		return err
	}
	defer f.Close()

	t.chLock.RLock()
	sort.Strings(t.channels)
	data, err := json.Marshal(t.channels)
	if err != nil {
		log.Printf("error saving channel list %s", err)
		return err
	}
	t.chLock.RUnlock()

	var buf bytes.Buffer
	if err := json.Indent(&buf, data, "", "\t"); err != nil {
		log.Printf("error saving channel list %s", err)
		return err
	}
	f.Write(buf.Bytes())
	return nil
}

var client = http.Client{
	Timeout: 5 * time.Second,
}

// channelExists
func channelExists(ch string) bool {
	req, err := http.NewRequest("GET", "https://api.twitch.tv/kraken/users/"+strings.ToLower(ch), nil)
	if err != nil {
		return false
	}
	req.Header.Add("Client-ID", common.GetConfig().Twitch.ClientID)
	res, err := client.Do(req)
	if err != nil {
		return false
	}
	return res.StatusCode < http.StatusBadRequest
}

func inSlice(slice []string, s string) bool {
	for _, v := range slice {
		if strings.EqualFold(s, v) {
			return true
		}
	}
	return false
}
<commit_msg>fixed leaving channels<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/MemeLabs/overrustlelogs/common"
)

// TwitchHub ...
type TwitchHub struct {
	chatLock       sync.RWMutex
	chats          []*common.Twitch
	chLock         sync.RWMutex
	channels       []string
	logHandler     func(m <-chan *common.Message)
	admins         map[string]struct{}
	commandChannel string
	quit           chan struct{}
}

// NewTwitchLogger ...
func NewTwitchLogger(f func(m <-chan *common.Message)) *TwitchHub {
	t := &TwitchHub{
		logHandler:     f,
		admins:         make(map[string]struct{}),
		commandChannel: common.GetConfig().Twitch.CommandChannel,
		quit:           make(chan struct{}, 1),
	}

	admins := common.GetConfig().Twitch.Admins
	for _, a := range admins {
		t.admins[a] = struct{}{}
	}

	d, err := ioutil.ReadFile(common.GetConfig().Twitch.ChannelListPath)
	if err != nil {
		log.Fatalf("unable to read channels %s", err)
	}
	if err := json.Unmarshal(d, &t.channels); err != nil {
		log.Fatalf("unable to read channels %s", err)
	}
	return t
}

// Start ...
func (t *TwitchHub) Start() {
	var c int
	for _, channel := range t.channels {
		select {
		case <-t.quit:
			return
		default:
		}
		err := t.join(channel, false)
		if err != nil {
			log.Printf("%v", err)
			continue
		}
		c++
	}
	log.Printf("joined %d chats, wew lad :^)\n", c)
}

// Stop ...
func (t *TwitchHub) Stop() {
	close(t.quit)
	var wg sync.WaitGroup

	t.chatLock.Lock()
	wg.Add(len(t.chats))
	for i, c := range t.chats {
		log.Printf("stopping chat: %d\n", i)
		go c.Stop(&wg)
	}
	t.chatLock.Unlock()
	wg.Wait()
}

func (t *TwitchHub) runCommand(c *common.Twitch, m *common.Message) {
	if _, ok := t.admins[m.Nick]; !ok || m.Type != "MSG" {
		return
	}

	parts := strings.Split(strings.ToLower(m.Data), " ")
	switch parts[0] {
	case "!join":
		if err := t.join(strings.TrimSpace(parts[1]), true); err != nil {
			log.Println(err)
			log.Println(c.Message(m.Channel, err.Error()))
			return
		}
		c.Message(m.Channel, fmt.Sprintf("Logging %s", strings.TrimSpace(parts[1])))
	case "!leave":
		if err := t.leave(parts[1]); err != nil {
			log.Println(err)
			c.Message(m.Channel, fmt.Sprintf("Not logging %s", strings.TrimSpace(parts[1])))
			return
		}
		c.Message(m.Channel, fmt.Sprintf("Leaving %s", parts[1]))
	}
}

func (t *TwitchHub) join(ch string, init bool) error {
	if inSlice(t.channels, ch) && init {
		return fmt.Errorf("already logging %s", ch)
	}
	if !channelExists(ch) && init {
		return fmt.Errorf("%s doesn't exist my dude", ch)
	}
	if init {
		t.addChannel(ch)
		go t.saveChannels()
	}
	t.chatLock.Lock()
	var chat *common.Twitch
	for _, c := range t.chats {
		if len(c.Channels()) >= common.MaxChannelsPerChat {
			continue
		}
		chat = c
	}
	if chat == nil {
		chat = common.NewTwitch()
		chat.Run()
		t.chats = append(t.chats, chat)
		go t.msgHandler(chat)
	}
	t.chatLock.Unlock()
	if err := chat.Join(ch); err != nil {
		return fmt.Errorf("failed to join %s: %v", ch, err)
	}
	log.Println("joined", ch)
	return nil
}

func (t *TwitchHub) msgHandler(c *common.Twitch) {
	messages := make(chan *common.Message, common.MessageBufferSize)
	go t.logHandler(messages)
	for {
		select {
		case <-t.quit:
			close(messages)
			return
		case m := <-c.Messages():
			messages <- m
			if t.commandChannel == m.Channel {
				go t.runCommand(c, m)
			}
		}
	}
}

func (t *TwitchHub) leave(ch string) error {
	if err := t.removeChannel(ch); err != nil {
		return err
	}
	if err := t.saveChannels(); err != nil {
		return err
	}

	t.chatLock.Lock()
	defer t.chatLock.Unlock()
	for _, c := range t.chats {
		if !inSlice(c.Channels(), ch) {
			continue
		}
		if err := c.Leave(ch); err != nil {
			return fmt.Errorf("error leaving %s: %v", ch, err)
		}

		log.Println("leaving", ch)
		return nil
	}
	return fmt.Errorf("%s not found", ch)
}

func (t *TwitchHub) addChannel(ch string) {
	t.chLock.Lock()
	t.channels = append(t.channels, ch)
	t.chLock.Unlock()
}

func (t *TwitchHub) removeChannel(ch string) error {
	t.chLock.Lock()
	defer t.chLock.Unlock()
	sort.Strings(t.channels)
	for i, c := range t.channels {
		if strings.EqualFold(ch, c) {
			t.channels = append(t.channels[:i], t.channels[i+1:]...)
			return nil
		}
	}
	return fmt.Errorf("didn't find %s in the channels list", ch)
}

func (t *TwitchHub) saveChannels() error {
	f, err := os.Create(common.GetConfig().Twitch.ChannelListPath)
	if err != nil {
		log.Printf("error saving channel list %s", err)
		return err
	}
	defer f.Close()

	t.chLock.RLock()
	sort.Strings(t.channels)
	data, err := json.Marshal(t.channels)
	if err != nil {
		log.Printf("error saving channel list %s", err)
		return err
	}
	t.chLock.RUnlock()

	var buf bytes.Buffer
	if err := json.Indent(&buf, data, "", "\t"); err != nil {
		log.Printf("error saving channel list %s", err)
		return err
	}
	f.Write(buf.Bytes())
	return nil
}

var client = http.Client{
	Timeout: 5 * time.Second,
}

// channelExists
func channelExists(ch string) bool {
	req, err := http.NewRequest("GET", "https://api.twitch.tv/kraken/users/"+strings.ToLower(ch), nil)
	if err != nil {
		return false
	}
	req.Header.Add("Client-ID", common.GetConfig().Twitch.ClientID)
	res, err := client.Do(req)
	if err != nil {
		return false
	}
	return res.StatusCode < http.StatusBadRequest
}

func inSlice(slice []string, s string) bool {
	for _, v := range slice {
		if strings.EqualFold(s, v) {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"time"

	mgl "github.com/go-gl/mathgl/mgl32"
	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event"
	"golang.org/x/mobile/exp/app/debug"
	"golang.org/x/mobile/geom"
	"golang.org/x/mobile/gl"
)

type Shape struct {
	buf     gl.Buffer
	texture gl.Texture

	coordsPerVertex    int
	texCoordsPerVertex int
	vertexCount        int
}

type Shader struct {
	program      gl.Program
	vertCoord    gl.Attrib
	vertTexCoord gl.Attrib

	projection gl.Uniform
	view       gl.Uniform
	model      gl.Uniform
}

type Engine struct {
	shader   Shader
	shape    Shape
	touchLoc geom.Point
	started  time.Time
}

func (e *Engine) Start() {
	var err error

	e.shader.program, err = LoadProgram("shader.v.glsl", "shader.f.glsl")
	if err != nil {
		panic(fmt.Sprintln("LoadProgram failed:", err))
	}

	e.shape.coordsPerVertex = 3
	e.shape.texCoordsPerVertex = 2
	e.shape.vertexCount = len(cubeData) / (e.shape.coordsPerVertex + e.shape.texCoordsPerVertex)

	e.shape.buf = gl.CreateBuffer()
	gl.BindBuffer(gl.ARRAY_BUFFER, e.shape.buf)
	gl.BufferData(gl.ARRAY_BUFFER, EncodeObject(cubeData), gl.STATIC_DRAW)

	e.shader.vertCoord = gl.GetAttribLocation(e.shader.program, "vertCoord")
	e.shader.vertTexCoord = gl.GetAttribLocation(e.shader.program, "vertTexCoord")

	e.shader.projection = gl.GetUniformLocation(e.shader.program, "projection")
	e.shader.view = gl.GetUniformLocation(e.shader.program, "view")
	e.shader.model = gl.GetUniformLocation(e.shader.program, "model")

	e.shape.texture, err = LoadTexture("gopher.png")
	if err != nil {
		panic(fmt.Sprintln("LoadTexture failed:", err))
	}

	e.started = time.Now()
}

func (e *Engine) Stop() {
	gl.DeleteProgram(e.shader.program)
	gl.DeleteBuffer(e.shape.buf)
	gl.DeleteTexture(e.shape.texture)
}

func (e *Engine) Config(new, old event.Config) {
	e.touchLoc = geom.Point{new.Width / 2, new.Height / 2}
}

func (e *Engine) Touch(t event.Touch, c event.Config) {
	e.touchLoc = t.Loc
}

func (e *Engine) Draw(c event.Config) {
	since := time.Now().Sub(e.started)

	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LESS)

	gl.ClearColor(0, 0, 0, 1)
	gl.Clear(gl.COLOR_BUFFER_BIT)
	gl.Clear(gl.DEPTH_BUFFER_BIT)

	gl.UseProgram(e.shader.program)

	// Setup MVP
	var m mgl.Mat4
	m = mgl.Perspective(0.785, float32(c.Width/c.Height), 0.1, 10.0)
	gl.UniformMatrix4fv(e.shader.projection, m[:])

	m = mgl.LookAtV(
		mgl.Vec3{3, 3, 3}, // eye
		mgl.Vec3{0, 0, 0}, // center
		mgl.Vec3{0, 1, 0}, // up
	)
	gl.UniformMatrix4fv(e.shader.view, m[:])

	m = mgl.HomogRotate3D(float32(since.Seconds()), mgl.Vec3{0, 1, 0})
	gl.UniformMatrix4fv(e.shader.model, m[:])

	// Draw our shape
	gl.BindBuffer(gl.ARRAY_BUFFER, e.shape.buf)

	gl.EnableVertexAttribArray(e.shader.vertCoord)
	gl.VertexAttribPointer(e.shader.vertCoord, e.shape.coordsPerVertex, gl.FLOAT, false, 20, 0) // 4 bytes in float, 5 values per vertex

	gl.EnableVertexAttribArray(e.shader.vertTexCoord)
	gl.VertexAttribPointer(e.shader.vertTexCoord, e.shape.texCoordsPerVertex, gl.FLOAT, false, 20, 12)

	gl.BindTexture(gl.TEXTURE_2D, e.shape.texture)

	gl.DrawArrays(gl.TRIANGLES, 0, e.shape.vertexCount)

	gl.DisableVertexAttribArray(e.shader.vertCoord)

	debug.DrawFPS(c)
}

func main() {
	e := Engine{}
	app.Run(app.Callbacks{
		Start:  e.Start,
		Stop:   e.Stop,
		Draw:   e.Draw,
		Touch:  e.Touch,
		Config: e.Config,
	})
}
<commit_msg>More active texture for funsies<commit_after>package main

import (
	"fmt"
	"time"

	mgl "github.com/go-gl/mathgl/mgl32"
	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event"
	"golang.org/x/mobile/exp/app/debug"
	"golang.org/x/mobile/geom"
	"golang.org/x/mobile/gl"
)

type Shape struct {
	buf     gl.Buffer
	texture gl.Texture

	coordsPerVertex    int
	texCoordsPerVertex int
	vertexCount        int
}

type Shader struct {
	program      gl.Program
	vertCoord    gl.Attrib
	vertTexCoord gl.Attrib

	projection gl.Uniform
	view       gl.Uniform
	model      gl.Uniform
}

type Engine struct {
	shader   Shader
	shape    Shape
	touchLoc geom.Point
	started  time.Time
}

func (e *Engine) Start() {
	var err error

	e.shader.program, err = LoadProgram("shader.v.glsl", "shader.f.glsl")
	if err != nil {
		panic(fmt.Sprintln("LoadProgram failed:", err))
	}

	e.shape.coordsPerVertex = 3
	e.shape.texCoordsPerVertex = 2
	e.shape.vertexCount = len(cubeData) / (e.shape.coordsPerVertex + e.shape.texCoordsPerVertex)

	e.shape.buf = gl.CreateBuffer()
	gl.BindBuffer(gl.ARRAY_BUFFER, e.shape.buf)
	gl.BufferData(gl.ARRAY_BUFFER, EncodeObject(cubeData), gl.STATIC_DRAW)

	e.shader.vertCoord = gl.GetAttribLocation(e.shader.program, "vertCoord")
	e.shader.vertTexCoord = gl.GetAttribLocation(e.shader.program, "vertTexCoord")

	e.shader.projection = gl.GetUniformLocation(e.shader.program, "projection")
	e.shader.view = gl.GetUniformLocation(e.shader.program, "view")
	e.shader.model = gl.GetUniformLocation(e.shader.program, "model")

	e.shape.texture, err = LoadTexture("gopher.png")
	if err != nil {
		panic(fmt.Sprintln("LoadTexture failed:", err))
	}

	e.started = time.Now()
}

func (e *Engine) Stop() {
	gl.DeleteProgram(e.shader.program)
	gl.DeleteBuffer(e.shape.buf)
	gl.DeleteTexture(e.shape.texture)
}

func (e *Engine) Config(new, old event.Config) {
	e.touchLoc = geom.Point{new.Width / 2, new.Height / 2}
}

func (e *Engine) Touch(t event.Touch, c event.Config) {
	e.touchLoc = t.Loc
}

func (e *Engine) Draw(c event.Config) {
	since := time.Now().Sub(e.started)

	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LESS)

	gl.ClearColor(0, 0, 0, 1)
	gl.Clear(gl.COLOR_BUFFER_BIT)
	gl.Clear(gl.DEPTH_BUFFER_BIT)

	gl.UseProgram(e.shader.program)

	// Setup MVP
	var m mgl.Mat4
	m = mgl.Perspective(0.785, float32(c.Width/c.Height), 0.1, 10.0)
	gl.UniformMatrix4fv(e.shader.projection, m[:])

	m = mgl.LookAtV(
		mgl.Vec3{3, 3, 3}, // eye
		mgl.Vec3{0, 0, 0}, // center
		mgl.Vec3{0, 1, 0}, // up
	)
	gl.UniformMatrix4fv(e.shader.view, m[:])

	m = mgl.HomogRotate3D(float32(since.Seconds()), mgl.Vec3{0, 1, 0})
	gl.UniformMatrix4fv(e.shader.model, m[:])

	// Draw our shape
	gl.BindBuffer(gl.ARRAY_BUFFER, e.shape.buf)

	gl.EnableVertexAttribArray(e.shader.vertCoord)
	gl.VertexAttribPointer(e.shader.vertCoord, e.shape.coordsPerVertex, gl.FLOAT, false, 20, 0) // 4 bytes in float, 5 values per vertex

	gl.EnableVertexAttribArray(e.shader.vertTexCoord)
	gl.VertexAttribPointer(e.shader.vertTexCoord, e.shape.texCoordsPerVertex, gl.FLOAT, false, 20, 12)

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, e.shape.texture)

	gl.DrawArrays(gl.TRIANGLES, 0, e.shape.vertexCount)

	gl.DisableVertexAttribArray(e.shader.vertCoord)

	debug.DrawFPS(c)
}

func main() {
	e := Engine{}
	app.Run(app.Callbacks{
		Start:  e.Start,
		Stop:   e.Stop,
		Draw:   e.Draw,
		Touch:  e.Touch,
		Config: e.Config,
	})
}
<|endoftext|>


<commit_before>package lrucache

import (
	"errors"
	"math/rand"
	"strconv"
	"sync"
	"testing"
)

type varsize int

func (i varsize) Size() int64 {
	return int64(i)
}

type purgeable struct {
	purged bool
	why    PurgeReason
}

func (x *purgeable) Size() int64 {
	return 1
}

func (x *purgeable) OnPurge(why PurgeReason) {
	x.purged = true
	x.why = why
}

func syncCache(c *Cache) {
	c.Get("imblueifIweregreenIwoulddie")
}

func TestOnPurge_1(t *testing.T) {
	c := New(1)
	defer c.Close()
	var x, y purgeable
	c.Set("x", &x)
	c.Set("y", &y)
	syncCache(c)
	if !x.purged {
		t.Error("Element was not purged from full cache")
	}
	if x.why != CACHEFULL {
		t.Error("Element should have been purged but was deleted")
	}
}

func TestOnPurge_2(t *testing.T) {
	c := New(1)
	defer c.Close()
	var x purgeable
	c.Set("x", &x)
	c.Delete("x")
	syncCache(c)
	if !x.purged {
		t.Error("Element was not deleted from cache")
	}
	if x.why != EXPLICITDELETE {
		t.Error("Element should have been deleted but was purged")
	}
}

// Just test filling a cache with a type that does not implement NotifyPurge
func TestsafeOnPurge(t *testing.T) {
	c := New(1)
	defer c.Close()
	i := varsize(1)
	j := varsize(1)
	c.Set("i", i)
	c.Set("j", j)
	syncCache(c)
}

func TestSize(t *testing.T) {
	c := New(100)
	defer c.Close()
	// sum(0..14) = 105
	for i := 1; i < 15; i++ {
		c.Set(strconv.Itoa(i), varsize(i))
	}
	syncCache(c)
	// At this point, expect {0, 1, 2, 3} to be purged
	if c.Size() != 99 {
		t.Errorf("Unexpected size: %d", c.Size())
	}
	for i := 0; i < 4; i++ {
		if _, err := c.Get(strconv.Itoa(i)); err != ErrNotFound {
			t.Errorf("Expected %d to be purged", i)
		}
	}
	for i := 4; i < 15; i++ {
		if _, err := c.Get(strconv.Itoa(i)); err != nil {
			t.Errorf("Expected %d to be cached", i)
		}
	}
}

func TestOnMiss(t *testing.T) {
	c := New(10)
	defer c.Close()
	// Expected cache misses (arbitrary value)
	misses := map[string]int{}
	for i := 5; i < 10; i++ {
		misses[strconv.Itoa(i)] = 0
	}
	c.OnMiss(func(id string) (Cacheable, error) {
		if _, ok := misses[id]; !ok {
			return nil, nil
		}
		delete(misses, id)
		i, err := strconv.Atoi(id)
		if err != nil {
			return nil, errors.New("Illegal id: " + id)
		}
		return i, nil
	})
	for i := 0; i < 5; i++ {
		c.Set(strconv.Itoa(i), i)
	}
	for i := 0; i < 10; i++ {
		x, err := c.Get(strconv.Itoa(i))
		switch err {
		case nil:
			break
		case ErrNotFound:
			t.Errorf("Unexpected cache miss for %d", i)
			continue
		default:
			t.Fatal(err)
		}
		if j := x.(int); j != i {
			t.Errorf("Illegal cache value: expected %d, got %d", i, j)
		}
	}
	for k := range misses {
		t.Errorf("Expected %s to miss", k)
	}
}

func TestConcurrentOnMiss(t *testing.T) {
	c := New(10)
	defer c.Close()
	ch := make(chan int)
	// If key foo is requested but not cached, read it from the channel
	c.OnMiss(func(id string) (Cacheable, error) {
		if id == "foo" {
			// Indicate that we want a value
			ch <- 0
			return <-ch, nil
		}
		return nil, nil
	})
	go func() {
		c.Get("foo")
	}()
	<-ch
	// Now we know for sure: a goroutine is blocking on c.Get("foo").
	// But other cache operations should be unaffected:
	c.Set("bar", 10)
	// Unlock that poor blocked goroutine
	ch <- 3
	go func() { <-ch; ch <- 10 }()
	result, err := c.Get("foo")
	switch {
	case err != nil:
		t.Error(`Error while fetching "foo":`, err)
	case result != 10:
		t.Error("Expected 10, got:", result)
	}
}

func TestZeroSize(t *testing.T) {
	c := New(2)
	defer c.Close()
	c.Set("a", varsize(0))
	c.Set("b", varsize(1))
	c.Set("c", varsize(2))
	if _, err := c.Get("a"); err != nil {
		t.Error("Purged element with size=0; should have left in cache")
	}
	c.Delete("a")
	c.Set("d", varsize(2))
	if _, err := c.Get("c"); err != ErrNotFound {
		t.Error("Kept `c' around for too long after removing empty element")
	}
	if _, err := c.Get("d"); err != nil {
		t.Error("Failed to cache `d' after removing empty element")
	}
}

func benchmarkGet(b *testing.B, conc int) {
	b.StopTimer()
	// Size doesn't matter (that's what she said)
	c := New(1000)
	defer c.Close()
	c.Set("x", 1)
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/conc; i++ {
				c.Get("x")
			}
			syncCache(c)
			wg.Done()
		}()
	}
	wg.Wait()
}

func benchmarkSet(b *testing.B, conc int) {
	b.StopTimer()
	// Size matters.
	c := New(int64(b.N) / 4)
	defer c.Close()
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/conc; i++ {
				c.Set(strconv.Itoa(i), i)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	syncCache(c)
}

func benchmarkAll(b *testing.B, conc int) {
	b.StopTimer()
	// Size is definitely important, but what is the right size?
	c := New(int64(b.N) / 4)
	defer c.Close()
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/3/conc; i++ {
				c.Set(strconv.Itoa(rand.Int()), 1)
				c.Get(strconv.Itoa(rand.Int()))
				c.Delete(strconv.Itoa(rand.Int()))
			}
			wg.Done()
		}()
	}
	wg.Wait()
	syncCache(c)
}

func BenchmarkGet(b *testing.B) {
	benchmarkGet(b, 1)
}

func Benchmark5ConcurrentGet(b *testing.B) {
	benchmarkGet(b, 5)
}

func BenchmarkSet(b *testing.B) {
	benchmarkSet(b, 1)
}

func Benchmark5ConcurrentSet(b *testing.B) {
	benchmarkSet(b, 5)
}

func BenchmarkAll(b *testing.B) {
	benchmarkAll(b, 1)
}

func Benchmark5ConcurrentAll(b *testing.B) {
	benchmarkAll(b, 5)
}
<commit_msg>Extra concurrency benchmarks (1, 10, 100, 1K, 10K)<commit_after>package lrucache

import (
	"errors"
	"math/rand"
	"strconv"
	"sync"
	"testing"
)

type varsize int

func (i varsize) Size() int64 {
	return int64(i)
}

type purgeable struct {
	purged bool
	why    PurgeReason
}

func (x *purgeable) Size() int64 {
	return 1
}

func (x *purgeable) OnPurge(why PurgeReason) {
	x.purged = true
	x.why = why
}

func syncCache(c *Cache) {
	c.Get("imblueifIweregreenIwoulddie")
}

func TestOnPurge_1(t *testing.T) {
	c := New(1)
	defer c.Close()
	var x, y purgeable
	c.Set("x", &x)
	c.Set("y", &y)
	syncCache(c)
	if !x.purged {
		t.Error("Element was not purged from full cache")
	}
	if x.why != CACHEFULL {
		t.Error("Element should have been purged but was deleted")
	}
}

func TestOnPurge_2(t *testing.T) {
	c := New(1)
	defer c.Close()
	var x purgeable
	c.Set("x", &x)
	c.Delete("x")
	syncCache(c)
	if !x.purged {
		t.Error("Element was not deleted from cache")
	}
	if x.why != EXPLICITDELETE {
		t.Error("Element should have been deleted but was purged")
	}
}

// Just test filling a cache with a type that does not implement NotifyPurge
func TestsafeOnPurge(t *testing.T) {
	c := New(1)
	defer c.Close()
	i := varsize(1)
	j := varsize(1)
	c.Set("i", i)
	c.Set("j", j)
	syncCache(c)
}

func TestSize(t *testing.T) {
	c := New(100)
	defer c.Close()
	// sum(0..14) = 105
	for i := 1; i < 15; i++ {
		c.Set(strconv.Itoa(i), varsize(i))
	}
	syncCache(c)
	// At this point, expect {0, 1, 2, 3} to be purged
	if c.Size() != 99 {
		t.Errorf("Unexpected size: %d", c.Size())
	}
	for i := 0; i < 4; i++ {
		if _, err := c.Get(strconv.Itoa(i)); err != ErrNotFound {
			t.Errorf("Expected %d to be purged", i)
		}
	}
	for i := 4; i < 15; i++ {
		if _, err := c.Get(strconv.Itoa(i)); err != nil {
			t.Errorf("Expected %d to be cached", i)
		}
	}
}

func TestOnMiss(t *testing.T) {
	c := New(10)
	defer c.Close()
	// Expected cache misses (arbitrary value)
	misses := map[string]int{}
	for i := 5; i < 10; i++ {
		misses[strconv.Itoa(i)] = 0
	}
	c.OnMiss(func(id string) (Cacheable, error) {
		if _, ok := misses[id]; !ok {
			return nil, nil
		}
		delete(misses, id)
		i, err := strconv.Atoi(id)
		if err != nil {
			return nil, errors.New("Illegal id: " + id)
		}
		return i, nil
	})
	for i := 0; i < 5; i++ {
		c.Set(strconv.Itoa(i), i)
	}
	for i := 0; i < 10; i++ {
		x, err := c.Get(strconv.Itoa(i))
		switch err {
		case nil:
			break
		case ErrNotFound:
			t.Errorf("Unexpected cache miss for %d", i)
			continue
		default:
			t.Fatal(err)
		}
		if j := x.(int); j != i {
			t.Errorf("Illegal cache value: expected %d, got %d", i, j)
		}
	}
	for k := range misses {
		t.Errorf("Expected %s to miss", k)
	}
}

func TestConcurrentOnMiss(t *testing.T) {
	c := New(10)
	defer c.Close()
	ch := make(chan int)
	// If key foo is requested but not cached, read it from the channel
	c.OnMiss(func(id string) (Cacheable, error) {
		if id == "foo" {
			// Indicate that we want a value
			ch <- 0
			return <-ch, nil
		}
		return nil, nil
	})
	go func() {
		c.Get("foo")
	}()
	<-ch
	// Now we know for sure: a goroutine is blocking on c.Get("foo").
	// But other cache operations should be unaffected:
	c.Set("bar", 10)
	// Unlock that poor blocked goroutine
	ch <- 3
	go func() { <-ch; ch <- 10 }()
	result, err := c.Get("foo")
	switch {
	case err != nil:
		t.Error(`Error while fetching "foo":`, err)
	case result != 10:
		t.Error("Expected 10, got:", result)
	}
}

func TestZeroSize(t *testing.T) {
	c := New(2)
	defer c.Close()
	c.Set("a", varsize(0))
	c.Set("b", varsize(1))
	c.Set("c", varsize(2))
	if _, err := c.Get("a"); err != nil {
		t.Error("Purged element with size=0; should have left in cache")
	}
	c.Delete("a")
	c.Set("d", varsize(2))
	if _, err := c.Get("c"); err != ErrNotFound {
		t.Error("Kept `c' around for too long after removing empty element")
	}
	if _, err := c.Get("d"); err != nil {
		t.Error("Failed to cache `d' after removing empty element")
	}
}

func benchmarkGet(b *testing.B, conc int) {
	b.StopTimer()
	// Size doesn't matter (that's what she said)
	c := New(1000)
	defer c.Close()
	c.Set("x", 1)
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/conc; i++ {
				c.Get("x")
			}
			syncCache(c)
			wg.Done()
		}()
	}
	wg.Wait()
}

func benchmarkSet(b *testing.B, conc int) {
	b.StopTimer()
	// Size matters.
	c := New(int64(b.N) / 4)
	defer c.Close()
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/conc; i++ {
				c.Set(strconv.Itoa(i), i)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	syncCache(c)
}

func benchmarkAll(b *testing.B, conc int) {
	b.StopTimer()
	// Size is definitely important, but what is the right size?
	c := New(int64(b.N) / 4)
	defer c.Close()
	syncCache(c)
	var wg sync.WaitGroup
	wg.Add(conc)
	b.StartTimer()
	for i := 0; i < conc; i++ {
		go func() {
			for i := 0; i < b.N/3/conc; i++ {
				c.Set(strconv.Itoa(rand.Int()), 1)
				c.Get(strconv.Itoa(rand.Int()))
				c.Delete(strconv.Itoa(rand.Int()))
			}
			wg.Done()
		}()
	}
	wg.Wait()
	syncCache(c)
}

func BenchmarkGet(b *testing.B) {
	benchmarkGet(b, 1)
}

func Benchmark10ConcurrentGet(b *testing.B) {
	benchmarkGet(b, 10)
}

func Benchmark100ConcurrentGet(b *testing.B) {
	benchmarkGet(b, 100)
}

func Benchmark1KConcurrentGet(b *testing.B) {
	benchmarkGet(b, 1000)
}

func Benchmark10KConcurrentGet(b *testing.B) {
	benchmarkGet(b, 10000)
}

func BenchmarkSet(b *testing.B) {
	benchmarkSet(b, 1)
}

func Benchmark10ConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10)
}

func Benchmark100ConcurrentSet(b *testing.B) {
	benchmarkSet(b, 100)
}

func Benchmark1KConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10000)
}

func Benchmark10KConcurrentSet(b *testing.B) {
	benchmarkSet(b, 10000)
}

func BenchmarkAll(b *testing.B) {
	benchmarkAll(b, 1)
}

func Benchmark10ConcurrentAll(b *testing.B) {
	benchmarkAll(b, 10)
}

func Benchmark100ConcurrentAll(b *testing.B) {
	benchmarkAll(b, 100)
}

func Benchmark1K(b *testing.B) {
	benchmarkAll(b, 1000)
}

func Benchmark10KConcurrentAll(b *testing.B) {
	benchmarkAll(b, 10000)
}
<|endoftext|>


<commit_before>package lrucache

import (
	"runtime"
	"strconv"
	"testing"
)

type flatsize int

func (i flatsize) Size() int64 {
	return 1
}

type varsize int

func (i varsize) Size() int64 {
	return int64(i)
}

type purgeable struct {
	purged bool
	how    bool
}

func (x *purgeable) Size() int64 {
	return 1
}

func (x *purgeable) OnPurge(d bool) {
	x.purged = true
	x.how = d
}

// Wait until a cache has processed all its operations
func sync(c *Cache) {
	ch := make(chan bool)
	c.opChan <- reqPing(ch)
	<-ch
}

func TestOnPurge_1(t *testing.T) {
	c := New(1)
	var x, y purgeable
	c.Set("x", &x)
	c.Set("y", &y)
	sync(c)
	if !x.purged {
		t.Error("Element was not purged from full cache")
	}
	if x.how {
		t.Error("Element should have been purged but was deleted")
	}
}

func TestOnPurge_2(t *testing.T) {
	c := New(1)
	var x purgeable
	c.Set("x", &x)
	c.Delete("x")
	sync(c)
	if !x.purged {
		t.Error("Element was not deleted from cache")
	}
	if !x.how {
		t.Error("Element should have been deleted but was purged")
	}
}

// Just test filling a cache with a type that does not implement NotifyPurge
func TestsafeOnPurge(t *testing.T) {
	c := New(1)
	i := varsize(1)
	j := varsize(1)
	c.Set("i", i)
	c.Set("j", j)
	sync(c)
}

func TestSize(t *testing.T) {
	c := New(100)
	// sum(0..14) = 105
	for i := 1; i < 15; i++ {
		c.Set(strconv.Itoa(i), varsize(i))
	}
	sync(c)
	// At this point, expect {0, 1, 2, 3} to be purged
	if c.Size() != 99 {
		t.Errorf("Unexpected size: %d", c.Size())
	}
	for i := 0; i < 4; i++ {
		if _, ok := c.Get(strconv.Itoa(i)); ok {
			t.Errorf("Expected %d to be purged", i)
		}
	}
	for i := 4; i < 15; i++ {
		if _, ok := c.Get(strconv.Itoa(i)); !ok {
			t.Errorf("Expected %d to be cached", i)
		}
	}
}

func TestOnMiss(t *testing.T) {
	c := New(10)
	// Expected cache misses (arbitrary value)
	misses := map[string]int{}
	for i := 5; i < 10; i++ {
		misses[strconv.Itoa(i)] = 0
	}
	c.OnMiss(func(id string) Cacheable {
		if _, ok := misses[id]; !ok {
			t.Errorf("Unexpected cache miss for %q", id)
		} else {
			delete(misses, id)
		}
		i, err := strconv.Atoi(id)
		if err != nil {
			t.Fatalf("Illegal id: %q", id)
		}
		return flatsize(flatsize(i))
	})
	for i := 0; i < 5; i++ {
		c.Set(strconv.Itoa(i), flatsize(i))
	}
	for i := 0; i < 10; i++ {
		x, ok := c.Get(strconv.Itoa(i))
		if !ok {
			t.Errorf("Unexpected not-found Get for %d", i)
			continue
		}
		if j := x.(flatsize); int(j) != i {
			t.Errorf("Illegal cache value: expected %d, got %d", i, j)
		}
	}
	for k := range misses {
		t.Errorf("Expected %s to miss", k)
	}
}

func TestConcurrentOnMiss(t *testing.T) {
	c := New(10)
	ch := make(chan flatsize)
	// If key foo is requested but not cached, read it from the channel
	c.OnMiss(func(id string) Cacheable {
		if id == "foo" {
			// Indicate that we want a value
			ch <- flatsize(0)
			// To be perfectly honest: I do not understand why this scheduler
			// call is necessary. Channel operations are not enough, here? If
			// this Gosched() is left out, a deadlock occurs. Why? What is the
			// idiomatic way to do this?
			runtime.Gosched()
			return <-ch
		}
		return nil
	})
	go func() {
		c.Get("foo")
	}()
	<-ch
	// Now we know for sure: a goroutine is blocking on c.Get("foo").
	// But other cache operations should be unaffected:
	c.Set("bar", flatsize(10))
	// Unlock that poor blocked goroutine
	ch <- flatsize(10)
	if result, ok := c.Get("foo"); !ok || result != flatsize(10) {
		t.Errorf("Expected 10, got: %d", result)
	}
}
<commit_msg>Rename sync() function to avoid clash with package<commit_after>package lrucache

import (
	"runtime"
	"strconv"
	"testing"
)

type flatsize int

func (i flatsize) Size() int64 {
	return 1
}

type varsize int

func (i varsize) Size() int64 {
	return int64(i)
}

type purgeable struct {
	purged bool
	how    bool
}

func (x *purgeable) Size() int64 {
	return 1
}

func (x *purgeable) OnPurge(d bool) {
	x.purged = true
	x.how = d
}

// Wait until a cache has processed all its operations
func syncChan(c *Cache) {
	ch := make(chan bool)
	c.opChan <- reqPing(ch)
	<-ch
}

func TestOnPurge_1(t *testing.T) {
	c := New(1)
	var x, y purgeable
	c.Set("x", &x)
	c.Set("y", &y)
	syncChan(c)
	if !x.purged {
		t.Error("Element was not purged from full cache")
	}
	if x.how {
		t.Error("Element should have been purged but was deleted")
	}
}

func TestOnPurge_2(t *testing.T) {
	c := New(1)
	var x purgeable
	c.Set("x", &x)
	c.Delete("x")
	syncChan(c)
	if !x.purged {
		t.Error("Element was not deleted from cache")
	}
	if !x.how {
		t.Error("Element should have been deleted but was purged")
	}
}

// Just test filling a cache with a type that does not implement NotifyPurge
func TestsafeOnPurge(t *testing.T) {
	c := New(1)
	i := varsize(1)
	j := varsize(1)
	c.Set("i", i)
	c.Set("j", j)
	syncChan(c)
}

func TestSize(t *testing.T) {
	c := New(100)
	// sum(0..14) = 105
	for i := 1; i < 15; i++ {
		c.Set(strconv.Itoa(i), varsize(i))
	}
	syncChan(c)
	// At this point, expect {0, 1, 2, 3} to be purged
	if c.Size() != 99 {
		t.Errorf("Unexpected size: %d", c.Size())
	}
	for i := 0; i < 4; i++ {
		if _, ok := c.Get(strconv.Itoa(i)); ok {
			t.Errorf("Expected %d to be purged", i)
		}
	}
	for i := 4; i < 15; i++ {
		if _, ok := c.Get(strconv.Itoa(i)); !ok {
			t.Errorf("Expected %d to be cached", i)
		}
	}
}

func TestOnMiss(t *testing.T) {
	c := New(10)
	// Expected cache misses (arbitrary value)
	misses := map[string]int{}
	for i := 5; i < 10; i++ {
		misses[strconv.Itoa(i)] = 0
	}
	c.OnMiss(func(id string) Cacheable {
		if _, ok := misses[id]; !ok {
			t.Errorf("Unexpected cache miss for %q", id)
		} else {
			delete(misses, id)
		}
		i, err := strconv.Atoi(id)
		if err != nil {
			t.Fatalf("Illegal id: %q", id)
		}
		return flatsize(flatsize(i))
	})
	for i := 0; i < 5; i++ {
		c.Set(strconv.Itoa(i), flatsize(i))
	}
	for i := 0; i < 10; i++ {
		x, ok := c.Get(strconv.Itoa(i))
		if !ok {
			t.Errorf("Unexpected not-found Get for %d", i)
			continue
		}
		if j := x.(flatsize); int(j) != i {
			t.Errorf("Illegal cache value: expected %d, got %d", i, j)
		}
	}
	for k := range misses {
		t.Errorf("Expected %s to miss", k)
	}
}

func TestConcurrentOnMiss(t *testing.T) {
	c := New(10)
	ch := make(chan flatsize)
	// If key foo is requested but not cached, read it from the channel
	c.OnMiss(func(id string) Cacheable {
		if id == "foo" {
			// Indicate that we want a value
			ch <- flatsize(0)
			// To be perfectly honest: I do not understand why this scheduler
			// call is necessary. Channel operations are not enough, here? If
			// this Gosched() is left out, a deadlock occurs. Why? What is the
			// idiomatic way to do this?
			runtime.Gosched()
			return <-ch
		}
		return nil
	})
	go func() {
		c.Get("foo")
	}()
	<-ch
	// Now we know for sure: a goroutine is blocking on c.Get("foo").
	// But other cache operations should be unaffected:
	c.Set("bar", flatsize(10))
	// Unlock that poor blocked goroutine
	ch <- flatsize(10)
	if result, ok := c.Get("foo"); !ok || result != flatsize(10) {
		t.Errorf("Expected 10, got: %d", result)
	}
}
<|endoftext|>


<commit_before>package logger

import (
	"encoding/json"
	"log"

	"github.com/bsphere/le_go"
	"github.com/emreler/finch/config"
)

const (
	levelInfo  = "INFO"
	levelError = "ERROR"
)

// Logger .
type Logger struct {
	conn *le_go.Logger
}

// LogMessage .
type LogMessage struct {
	Level   string `json:"level"`
	Message string `json:"message"`
}

// NewLogger .
func NewLogger(token config.LogentriesConfig) *Logger {
	le, err := le_go.Connect(string(token))

	if err != nil {
		panic(err)
	}

	log.Println("Connected to Logentries")

	return &Logger{le}
}

// Info .
func (l *Logger) Info(data interface{}) {
	var j []byte
	if str, ok := data.(string); ok {
		logMsg := &LogMessage{
			Level:   levelInfo,
			Message: str,
		}

		j, _ = json.Marshal(logMsg)

		l.conn.Println(j)
	} else {
		jstring, _ := json.Marshal(data)

		logMsg := &LogMessage{
			Level:   levelInfo,
			Message: string(jstring),
		}

		j, _ = json.Marshal(logMsg)

		l.conn.Println(string(j))
	}
}

func (l *Logger) Error(err error) {
	logMsg := &LogMessage{
		Level:   levelError,
		Message: err.Error(),
	}

	j, _ := json.Marshal(logMsg)

	l.conn.Println(string(j))
}
<commit_msg>Convert byte array to string<commit_after>package logger

import (
	"encoding/json"
	"log"

	"github.com/bsphere/le_go"
	"github.com/emreler/finch/config"
)

const (
	levelInfo  = "INFO"
	levelError = "ERROR"
)

// Logger .
type Logger struct {
	conn *le_go.Logger
}

// LogMessage .
type LogMessage struct {
	Level   string `json:"level"`
	Message string `json:"message"`
}

// NewLogger .
func NewLogger(token config.LogentriesConfig) *Logger {
	le, err := le_go.Connect(string(token))

	if err != nil {
		panic(err)
	}

	log.Println("Connected to Logentries")

	return &Logger{le}
}

// Info .
func (l *Logger) Info(data interface{}) {
	var j []byte
	if str, ok := data.(string); ok {
		logMsg := &LogMessage{
			Level:   levelInfo,
			Message: str,
		}

		j, _ = json.Marshal(logMsg)

		l.conn.Println(string(j))
	} else {
		jstring, _ := json.Marshal(data)

		logMsg := &LogMessage{
			Level:   levelInfo,
			Message: string(jstring),
		}

		j, _ = json.Marshal(logMsg)

		l.conn.Println(string(j))
	}
}

func (l *Logger) Error(err error) {
	logMsg := &LogMessage{
		Level:   levelError,
		Message: err.Error(),
	}

	j, _ := json.Marshal(logMsg)

	l.conn.Println(string(j))
}
<|endoftext|>


<commit_before>package logger

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/satori/go.uuid"
)

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*handler)

// Internal handler
type handler struct {
	name      string
	formatter logrus.Formatter
	out       io.Writer
}

// AppName allows to set the application name to log
func AppName(name string) option {
	return func(l *handler) {
		l.name = name
	}
}

// Formatter allows to set a custom log formatter
func Formatter(formatter logrus.Formatter) option {
	return func(l *handler) {
		l.formatter = formatter
	}
}

// Output allows setting an output writer for logging to be written to
func Output(out io.Writer) option {
	return func(l *handler) {
		l.out = out
	}
}

// Handler does HTTP request logging
func Handler(h http.Handler, opts ...option) http.Handler {
	// Default options
	handler := &handler{
		name:      "unknown",
		formatter: &logrus.TextFormatter{DisableColors: true},
		out:       os.Stdout,
	}

	for _, opt := range opts {
		opt(handler)
	}

	log := logrus.New()
	log.Formatter = handler.formatter
	log.Out = handler.out

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		requestID := uuid.NewV4().String()
		w.Header().Set("RequestID", requestID)

		log.WithFields(logrus.Fields{
			"request-id": requestID,
			"remote":     r.RemoteAddr,
			"method":     r.Method,
			"request":    r.RequestURI,
		}).Infof("%s started", requestID)

		res := NewResponseWriter(w)
		h.ServeHTTP(res, r)

		latency := time.Since(start)
		log.WithFields(logrus.Fields{
			"request-id": requestID,
			"remote":     r.RemoteAddr,
			"method":     r.Method,
			"request":    r.RequestURI,
			"status":     res.Status(),
			"size":       res.Size(),
			"took":       latency,
			fmt.Sprintf("measure#%s.latency", handler.name): latency.Nanoseconds(),
		}).Infof("%s completed", requestID)
	})
}
<commit_msg>Removes logrus for Go's log package. Closes #1<commit_after>package logger

import (
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/satori/go.uuid"
)

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*handler)

// Internal handler
type handler struct {
	name   string
	format string
	out    io.Writer
}

// AppName allows to set the application name to log.
func AppName(name string) option {
	return func(l *handler) {
		l.name = name
	}
}

// Format allows to set a custom log format. Although, the timestamp is always logged at the beginning.
// This handler is a bit opinionated.
//
// Directives:
//
// {remote_user}		: Remote user if Basic Auth credentials were sent
// {remote_ip}			: Remote IP address.
// {latency}			: The time taken to serve the request, in microseconds.
// {latency_human}		: The time taken to serve the request, human readable.
// {id}					: The request ID.
// {host}				: The Host header sent to the server
// {method}				: The request method. Ex: GET, POST, DELETE, etc.
// {url}				: The URL path requested.
// {query}				: Request's query string
// {rxbytes}			: Bytes received without headers
// {txbytes}			: Bytes sent, excluding HTTP headers.
// {status}				: Status sent to the client
// {useragent}			: User Agent
// {appname}			: The application name for this server
// {referer}			: The site from where the request came from
//
func Format(format string) option {
	return func(l *handler) {
		l.format = format
	}
}

// Output allows setting an output writer for logging to be written to
func Output(out io.Writer) option {
	return func(l *handler) {
		l.out = out
	}
}

// Handler does HTTP request logging
func Handler(h http.Handler, opts ...option) http.Handler {
	// Default options
	handler := &handler{
		name:   "unknown",
		format: `{appname} {id} remote_ip={remote_ip} {method} "{host}{url}?{query}" rxbytes={rxbytes} status={status} latency_human={latency_human} latency={latency} txbytes={txbytes}`,
		out:    os.Stdout,
	}

	for _, opt := range opts {
		opt(handler)
	}

	log.SetOutput(handler.out)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		// If there is a request ID already, we use it to keep the transaction
		// traceable. If not, we generate a new request ID.
		reqID := w.Header().Get("RequestID")
		if reqID == "" {
			reqID = uuid.NewV4().String()
		}

		w.Header().Set("RequestID", reqID)

		log.Print(applyLogFormat(handler.format, handler.name, -1, w, r))

		res := NewResponseWriter(w)
		h.ServeHTTP(res, r)

		latency := time.Since(start)
		log.Print(applyLogFormat(handler.format, handler.name, latency, res, r))
	})
}

func applyLogFormat(format, appname string, latency time.Duration, w http.ResponseWriter, r *http.Request) string {
	reqID := w.Header().Get("RequestID")

	if strings.Index(format, "{appname}") > -1 {
		format = strings.Replace(format, "{appname}", appname, -1)
	}

	if strings.Index(format, "{remote_ip}") > -1 {
		format = strings.Replace(format, "{remote_ip}", strings.Split(r.RemoteAddr, ":")[0], -1)
	}

	if strings.Index(format, "{remote_user}") > -1 {
		user, _, _ := r.BasicAuth()
		if user == "" {
			user = r.URL.User.Username()
		}
		format = strings.Replace(format, "{remote_user}", user, -1)
	}

	if strings.Index(format, "{latency_human}") > -1 {
		l := "..."
		if latency > -1 {
			l = latency.String()
		}
		format = strings.Replace(format, "{latency_human}", l, -1)
	}

	if strings.Index(format, "{latency}") > -1 {
		l := "..."
		if latency > -1 {
			l = strconv.FormatInt(latency.Nanoseconds(), 10)
		}
		format = strings.Replace(format, "{latency}", l, -1)
	}

	if strings.Index(format, "{id}") > -1 {
		format = strings.Replace(format, "{id}", reqID, -1)
	}

	if strings.Index(format, "{method}") > -1 {
		format = strings.Replace(format, "{method}", r.Method, -1)
	}

	if strings.Index(format, "{url}") > -1 {
		format = strings.Replace(format, "{url}", r.URL.Path, -1)
	}

	if strings.Index(format, "{query}") > -1 {
		format = strings.Replace(format, "{query}", r.URL.RawQuery, -1)
	}

	if strings.Index(format, "{rxbytes}") > -1 {
		format = strings.Replace(format, "{rxbytes}", strconv.FormatInt(r.ContentLength, 10), -1)
	}

	if strings.Index(format, "{txbytes}") > -1 {
		size := "..."
		if v, ok := w.(ResponseWriter); ok {
			size = strconv.Itoa(v.Size())
		}
		format = strings.Replace(format, "{txbytes}", size, -1)
	}

	if strings.Index(format, "{status}") > -1 {
		status := "..."
		if v, ok := w.(ResponseWriter); ok {
			status = strconv.Itoa(v.Status())
		}
		format = strings.Replace(format, "{status}", status, -1)
	}

	if strings.Index(format, "{useragent}") > -1 {
		format = strings.Replace(format, "{useragent}", r.UserAgent(), -1)
	}

	if strings.Index(format, "{host}") > -1 {
		format = strings.Replace(format, "{host}", r.Host, -1)
	}

	if strings.Index(format, "{referer}") > -1 {
		format = strings.Replace(format, "{referer}", r.Referer(), -1)
	}

	return format
}
<|endoftext|>


<commit_before>package logger

import (
	"bytes"
	"log"
	"net/http"

	"go.uber.org/zap"
)

var Log = setUpDefaultLogger().Sugar()

func setUpDefaultLogger() *zap.Logger {
	logger, e := zap.NewDevelopment()
	if e != nil {
		panic(e)
	}
	return logger
}

func SetLogger(logger *zap.Logger) {
	Log = logger.Sugar()
}

func From(r *http.Request) *zap.SugaredLogger {
	log := r.Context().Value("logger")
	if log != nil {
		return log.(*zap.SugaredLogger)
	} else {
		return Log
	}
}

const (
	_stdLogDefaultDepth = 2
	_loggerWriterDepth  = 1
)

// Copied from go.uber.org/zap/global.go and changed to use Error instead of Info:
func NewStdLog(l *zap.Logger) *log.Logger {
	return log.New(&loggerWriter{l.WithOptions(
		zap.AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth),
	)}, "" /* prefix */, 0 /* flags */)
}

type loggerWriter struct{ logger *zap.Logger }

func (l *loggerWriter) Write(p []byte) (int, error) {
	p = bytes.TrimSpace(p)
	l.logger.Error(string(p))
	return len(p), nil
}
<commit_msg>Minor refactoring: moving constants to where they're used<commit_after>package logger

import (
	"bytes"
	"log"
	"net/http"

	"go.uber.org/zap"
)

var Log = setUpDefaultLogger().Sugar()

func setUpDefaultLogger() *zap.Logger {
	logger, e := zap.NewDevelopment()
	if e != nil {
		panic(e)
	}
	return logger
}

func SetLogger(logger *zap.Logger) {
	Log = logger.Sugar()
}

func From(r *http.Request) *zap.SugaredLogger {
	log := r.Context().Value("logger")
	if log != nil {
		return log.(*zap.SugaredLogger)
	} else {
		return Log
	}
}

// Copied from go.uber.org/zap/global.go and changed to use Error instead of Info:
func NewStdLog(l *zap.Logger) *log.Logger {
	const (
		_stdLogDefaultDepth = 2
		_loggerWriterDepth  = 1
	)
	return log.New(&loggerWriter{l.WithOptions(
		zap.AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth),
	)}, "" /* prefix */, 0 /* flags */)
}

type loggerWriter struct{ logger *zap.Logger }

func (l *loggerWriter) Write(p []byte) (int, error) {
	p = bytes.TrimSpace(p)
	l.logger.Error(string(p))
	return len(p), nil
}
<|endoftext|>


<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package logger

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/getgauge/gauge/plugin/pluginInfo"
	"github.com/getgauge/gauge/version"

	"runtime"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/config"
	"github.com/natefinch/lumberjack"
	"github.com/op/go-logging"
)

// Channel specifies the logging channel. Can be one of CLI, API or LSP
type channel int

const (
	logsDirectory    = "logs_directory"
	logs             = "logs"
	gaugeLogFileName = "gauge.log"
	apiLogFileName   = "api.log"
	LspLogFileName   = "lsp.log"
	// CLI indicates gauge is used as a CLI.
	CLI channel = iota
	// API indicates gauge is in daemon mode. Used in IDEs.
	API
	// LSP indicates that gauge is acting as an LSP server.
	LSP
)

var level logging.Level
var activeLogger *logging.Logger
var fileLogFormat = logging.MustStringFormatter("%{time:15:04:05.000} %{message}")
var isLSP bool
var initialized bool
var ActiveLogFile string
var machineReadable bool

// Info logs INFO messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Info(stdout bool, msg string) {
	Infof(stdout, msg)
}

// Infof logs INFO messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Infof(stdout bool, msg string, args ...interface{}) {
	if !initialized {
		return
	}
	write(stdout, msg, args...)
	activeLogger.Infof(msg, args...)
}

// Error logs ERROR messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Error(stdout bool, msg string) {
	Errorf(stdout, msg)
}

// Errorf logs ERROR messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Errorf(stdout bool, msg string, args ...interface{}) {
	if !initialized {
		fmt.Fprintf(os.Stderr, msg, args)
		return
	}
	write(stdout, msg, args...)
	activeLogger.Errorf(msg, args...)
}

// Warning logs WARNING messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Warning(stdout bool, msg string) {
	Warningf(stdout, msg)
}

// Warningf logs WARNING messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Warningf(stdout bool, msg string, args ...interface{}) {
	if !initialized {
		return
	}
	write(stdout, msg, args...)
	activeLogger.Warningf(msg, args...)
}

// Fatal logs CRITICAL messages and exits. stdout flag indicates if message is to be written to stdout in addition to log.
func Fatal(stdout bool, msg string) {
	Fatalf(stdout, msg)
}

// Fatalf logs CRITICAL messages and exits. stdout flag indicates if message is to be written to stdout in addition to log.
func Fatalf(stdout bool, msg string, args ...interface{}) {
	message := getErrorText(msg, args...)
	if !initialized {
		fmt.Fprintf(os.Stderr, msg, args)
		return
	}
	write(stdout, message)
	activeLogger.Fatalf(msg, args...)
}

// Debug logs DEBUG messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Debug(stdout bool, msg string) {
	Debugf(stdout, msg)
}

// Debugf logs DEBUG messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Debugf(stdout bool, msg string, args ...interface{}) {
	if !initialized {
		return
	}
	activeLogger.Debugf(msg, args...)
	if level == logging.DEBUG {
		write(stdout, msg, args...)
	}
}

func getErrorText(msg string, args ...interface{}) string {
	env := []string{runtime.GOOS, version.FullVersion()}
	if version.GetCommitHash() != "" {
		env = append(env, version.GetCommitHash())
	}
	envText := strings.Join(env, ", ")
	return fmt.Sprintf(`Error ----------------------------------

%s

Get Support ----------------------------
	Docs:          https://docs.gauge.org
	Bugs:          https://github.com/getgauge/gauge/issues
	Chat:          https://gitter.im/getgauge/chat

Your Environment Information -----------
	%s
	%s`, fmt.Sprintf(msg, args...),
		envText,
		getPluginVersions())
}

func getPluginVersions() string {
	pis, err := pluginInfo.GetAllInstalledPluginsWithVersion()
	if err != nil {
		return fmt.Sprintf("Could not retrieve plugin information.")
	}
	pluginVersions := make([]string, 0, 0)
	for _, pi := range pis {
		pluginVersions = append(pluginVersions, fmt.Sprintf(`%s (%s)`, pi.Name, pi.Version))
	}
	return strings.Join(pluginVersions, ", ")
}

func write(stdout bool, msg string, args ...interface{}) {
	if !isLSP && stdout {
		if machineReadable {
			strs := strings.Split(fmt.Sprintf(msg, args...), "\n")
			for _, m := range strs {
				fmt.Printf("{\"type\": \"out\", \"message\": \"%s\"}\n", strings.Trim(m, "\n "))
			}
		} else {
			fmt.Println(fmt.Sprintf(msg, args...))
		}
	}
}

// Initialize initializes the logger object
func Initialize(isMachineReadable bool, logLevel string, c channel) {
	initialized = true
	level = loggingLevel(logLevel)
	activeLogger = logger(c)
	machineReadable = isMachineReadable
}

func logger(c channel) *logging.Logger {
	var l *logging.Logger
	switch c {
	case LSP:
		l = logging.MustGetLogger("gauge-lsp")
		initFileLogger(LspLogFileName, l)
		isLSP = true
		break
	case API:
		l = logging.MustGetLogger("gauge-api")
		initFileLogger(apiLogFileName, l)
		break
	default:
		l = logging.MustGetLogger("gauge")
		initFileLogger(gaugeLogFileName, l)
	}
	return l
}

func initFileLogger(logFileName string, fileLogger *logging.Logger) {
	var backend logging.Backend
	ActiveLogFile = getLogFile(logFileName)
	backend = createFileLogger(ActiveLogFile, 10)
	fileFormatter := logging.NewBackendFormatter(backend, fileLogFormat)
	fileLoggerLeveled := logging.AddModuleLevel(fileFormatter)
	fileLoggerLeveled.SetLevel(logging.DEBUG, "")

	fileLogger.SetBackend(fileLoggerLeveled)
}

func createFileLogger(name string, size int) logging.Backend {
	return logging.NewLogBackend(&lumberjack.Logger{
		Filename:   name,
		MaxSize:    size, // megabytes
		MaxBackups: 3,
		MaxAge:     28, //days
	}, "", 0)
}

func addLogsDirPath(logFileName string) string {
	customLogsDir := os.Getenv(logsDirectory)
	if customLogsDir == "" {
		return filepath.Join(logs, logFileName)
	}
	return filepath.Join(customLogsDir, logFileName)
}

func getLogFile(fileName string) string {
	if filepath.IsAbs(fileName) {
		return fileName
	}
	fileName = addLogsDirPath(fileName)
	if config.ProjectRoot != "" {
		return filepath.Join(config.ProjectRoot, fileName)
	}
	gaugeHome, err := common.GetGaugeHomeDirectory()
	if err != nil {
		return fileName
	}
	return filepath.Join(gaugeHome, fileName)
}

func loggingLevel(logLevel string) logging.Level {
	if logLevel != "" {
		switch strings.ToLower(logLevel) {
		case "debug":
			return logging.DEBUG
		case "info":
			return logging.INFO
		case "warning":
			return logging.WARNING
		case "error":
			return logging.ERROR
		}
	}
	return logging.INFO
}

// HandleWarningMessages logs multiple messages in WARNING mode
func HandleWarningMessages(stdout bool, warnings []string) {
	for _, warning := range warnings {
		Warning(stdout, warning)
	}
}
<commit_msg>Update logger to work with go 1.11<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package logger

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/getgauge/gauge/plugin/pluginInfo"
	"github.com/getgauge/gauge/version"

	"runtime"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/config"
	"github.com/natefinch/lumberjack"
	"github.com/op/go-logging"
)

// Channel specifies the logging channel. Can be one of CLI, API or LSP
type channel int

const (
	logsDirectory    = "logs_directory"
	logs             = "logs"
	gaugeLogFileName = "gauge.log"
	apiLogFileName   = "api.log"
	LspLogFileName   = "lsp.log"
	// CLI indicates gauge is used as a CLI.
	CLI channel = iota
	// API indicates gauge is in daemon mode. Used in IDEs.
	API
	// LSP indicates that gauge is acting as an LSP server.
	LSP
)

var level logging.Level
var activeLogger *logging.Logger
var fileLogFormat = logging.MustStringFormatter("%{time:15:04:05.000} %{message}")
var isLSP bool
var initialized bool
var ActiveLogFile string
var machineReadable bool

// Info logs INFO messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Info(stdout bool, msg string) {
	Infof(stdout, msg)
}

// Infof logs INFO messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Infof(stdout bool, msg string, args ...interface{}) {
	if !initialized {
		return
	}
	write(stdout, msg, args...)
	activeLogger.Infof(msg, args...)
}

// Error logs ERROR messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Error(stdout bool, msg string) {
	Errorf(stdout, msg)
}

// Errorf logs ERROR messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Errorf(stdout bool, msg string, args ...interface{}) {
	if !initialized {
		fmt.Fprintf(os.Stderr, msg, args...)
		return
	}
	write(stdout, msg, args...)
	activeLogger.Errorf(msg, args...)
}

// Warning logs WARNING messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Warning(stdout bool, msg string) {
	Warningf(stdout, msg)
}

// Warningf logs WARNING messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Warningf(stdout bool, msg string, args ...interface{}) {
	if !initialized {
		return
	}
	write(stdout, msg, args...)
	activeLogger.Warningf(msg, args...)
}

// Fatal logs CRITICAL messages and exits. stdout flag indicates if message is to be written to stdout in addition to log.
func Fatal(stdout bool, msg string) {
	Fatalf(stdout, msg)
}

// Fatalf logs CRITICAL messages and exits. stdout flag indicates if message is to be written to stdout in addition to log.
func Fatalf(stdout bool, msg string, args ...interface{}) {
	message := getErrorText(msg, args...)
	if !initialized {
		fmt.Fprintf(os.Stderr, msg, args...)
		return
	}
	write(stdout, message)
	activeLogger.Fatalf(msg, args...)
}

// Debug logs DEBUG messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Debug(stdout bool, msg string) {
	Debugf(stdout, msg)
}

// Debugf logs DEBUG messages. stdout flag indicates if message is to be written to stdout in addition to log.
func Debugf(stdout bool, msg string, args ...interface{}) {
	if !initialized {
		return
	}
	activeLogger.Debugf(msg, args...)
	if level == logging.DEBUG {
		write(stdout, msg, args...)
	}
}

func getErrorText(msg string, args ...interface{}) string {
	env := []string{runtime.GOOS, version.FullVersion()}
	if version.GetCommitHash() != "" {
		env = append(env, version.GetCommitHash())
	}
	envText := strings.Join(env, ", ")
	return fmt.Sprintf(`Error ----------------------------------

%s

Get Support ----------------------------
	Docs:          https://docs.gauge.org
	Bugs:          https://github.com/getgauge/gauge/issues
	Chat:          https://gitter.im/getgauge/chat

Your Environment Information -----------
	%s
	%s`, fmt.Sprintf(msg, args...),
		envText,
		getPluginVersions())
}

func getPluginVersions() string {
	pis, err := pluginInfo.GetAllInstalledPluginsWithVersion()
	if err != nil {
		return fmt.Sprintf("Could not retrieve plugin information.")
	}
	pluginVersions := make([]string, 0, 0)
	for _, pi := range pis {
		pluginVersions = append(pluginVersions, fmt.Sprintf(`%s (%s)`, pi.Name, pi.Version))
	}
	return strings.Join(pluginVersions, ", ")
}

func write(stdout bool, msg string, args ...interface{}) {
	if !isLSP && stdout {
		if machineReadable {
			strs := strings.Split(fmt.Sprintf(msg, args...), "\n")
			for _, m := range strs {
				fmt.Printf("{\"type\": \"out\", \"message\": \"%s\"}\n", strings.Trim(m, "\n "))
			}
		} else {
			fmt.Println(fmt.Sprintf(msg, args...))
		}
	}
}

// Initialize initializes the logger object
func Initialize(isMachineReadable bool, logLevel string, c channel) {
	initialized = true
	level = loggingLevel(logLevel)
	activeLogger = logger(c)
	machineReadable = isMachineReadable
}

func logger(c channel) *logging.Logger {
	var l *logging.Logger
	switch c {
	case LSP:
		l = logging.MustGetLogger("gauge-lsp")
		initFileLogger(LspLogFileName, l)
		isLSP = true
		break
	case API:
		l = logging.MustGetLogger("gauge-api")
		initFileLogger(apiLogFileName, l)
		break
	default:
		l = logging.MustGetLogger("gauge")
		initFileLogger(gaugeLogFileName, l)
	}
	return l
}

func initFileLogger(logFileName string, fileLogger *logging.Logger) {
	var backend logging.Backend
	ActiveLogFile = getLogFile(logFileName)
	backend = createFileLogger(ActiveLogFile, 10)
	fileFormatter := logging.NewBackendFormatter(backend, fileLogFormat)
	fileLoggerLeveled := logging.AddModuleLevel(fileFormatter)
	fileLoggerLeveled.SetLevel(logging.DEBUG, "")

	fileLogger.SetBackend(fileLoggerLeveled)
}

func createFileLogger(name string, size int) logging.Backend {
	return logging.NewLogBackend(&lumberjack.Logger{
		Filename:   name,
		MaxSize:    size, // megabytes
		MaxBackups: 3,
		MaxAge:     28, //days
	}, "", 0)
}

func addLogsDirPath(logFileName string) string {
	customLogsDir := os.Getenv(logsDirectory)
	if customLogsDir == "" {
		return filepath.Join(logs, logFileName)
	}
	return filepath.Join(customLogsDir, logFileName)
}

func getLogFile(fileName string) string {
	if filepath.IsAbs(fileName) {
		return fileName
	}
	fileName = addLogsDirPath(fileName)
	if config.ProjectRoot != "" {
		return filepath.Join(config.ProjectRoot, fileName)
	}
	gaugeHome, err := common.GetGaugeHomeDirectory()
	if err != nil {
		return fileName
	}
	return filepath.Join(gaugeHome, fileName)
}

func loggingLevel(logLevel string) logging.Level {
	if logLevel != "" {
		switch strings.ToLower(logLevel) {
		case "debug":
			return logging.DEBUG
		case "info":
			return logging.INFO
		case "warning":
			return logging.WARNING
		case "error":
			return logging.ERROR
		}
	}
	return logging.INFO
}

// HandleWarningMessages logs multiple messages in WARNING mode
func HandleWarningMessages(stdout bool, warnings []string) {
	for _, warning := range warnings {
		Warning(stdout, warning)
	}
}
<|endoftext|>


<commit_before>package login

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"sync"

	"github.com/almighty/almighty-core/account"
	"github.com/almighty/almighty-core/app"
	"github.com/almighty/almighty-core/jsonapi"
	"github.com/almighty/almighty-core/token"
	"github.com/goadesign/goa"
	uuid "github.com/satori/go.uuid"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
)

const (
	// InvalidCodeError could occure when the OAuth Exchange with GitHub return no valid AccessToken
	InvalidCodeError string = "Invalid OAuth2.0 code"
	// PrimaryEmailNotFoundError could occure if no primary email was returned by GitHub
	PrimaryEmailNotFoundError string = "Primary email not found"
)

// Service defines the basic entrypoint required to perform a remote oauth login
type Service interface {
	Perform(ctx *app.AuthorizeLoginContext) error
}

// NewGitHubOAuth creates a new login.Service capable of using GitHub for authorization
func NewGitHubOAuth(config *oauth2.Config, identities account.IdentityRepository, users account.UserRepository, tokenManager token.Manager) Service {
	return &gitHubOAuth{
		config:       config,
		identities:   identities,
		users:        users,
		tokenManager: tokenManager,
	}
}

type gitHubOAuth struct {
	config       *oauth2.Config
	identities   account.IdentityRepository
	users        account.UserRepository
	tokenManager token.Manager
}

// TEMP: This will leak memory in the long run with many 'failed' redirect attemts
var stateReferer = map[string]string{}
var mapLock sync.RWMutex

func (gh *gitHubOAuth) Perform(ctx *app.AuthorizeLoginContext) error {
	state := ctx.Params.Get("state")
	code := ctx.Params.Get("code")
	referer := ctx.RequestData.Header.Get("Referer")

	if code != "" {
		// After redirect from oauth provider

		// validate known state
		var knownReferer string
		defer func() {
			delete(stateReferer, state)
		}()

		knownReferer = stateReferer[state]
		if state == "" || knownReferer == "" {
			jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrUnauthorized("State or known referer was empty"))
			return ctx.Unauthorized(jerrors)
		}

		ghtoken, err := gh.config.Exchange(ctx, code)

		/*

			In case of invalid code, this is what we get in the ghtoken object

			&oauth2.Token{AccessToken:"", TokenType:"", RefreshToken:"", Expiry:time.Time{sec:0, nsec:0, loc:(*time.Location)(nil)}, raw:url.Values{"error":[]string{"bad_verification_code"}, "error_description":[]string{"The code passed is incorrect or expired."}, "error_uri":[]string{"https://developer.github.com/v3/oauth/#bad-verification-code"}}}

		*/

		if err != nil || ghtoken.AccessToken == "" {
			fmt.Println(err)
			ctx.ResponseData.Header().Set("Location", knownReferer+"?error="+InvalidCodeError)
			return ctx.TemporaryRedirect()
		}

		emails, err := gh.getUserEmails(ctx, ghtoken)
		fmt.Println(emails)

		primaryEmail := filterPrimaryEmail(emails)
		if primaryEmail == "" {
			fmt.Println("No primary email found?! ", emails)
			ctx.ResponseData.Header().Set("Location", knownReferer+"?error="+PrimaryEmailNotFoundError)
			return ctx.TemporaryRedirect()
		}
		users, err := gh.users.Query(account.UserByEmails([]string{primaryEmail}), account.UserWithIdentity())
		if err != nil {
			ctx.ResponseData.Header().Set("Location", knownReferer+"?error=Associated user not found "+err.Error())
			return ctx.TemporaryRedirect()
		}
		var identity account.Identity
		if len(users) == 0 {
			// No User found, create new User and Identity
			ghUser, err := gh.getUser(ctx, ghtoken)
			if err != nil {
				fmt.Println(err)
				jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrUnauthorized(err.Error()))
				return ctx.Unauthorized(jerrors)
			}
			fmt.Println(ghUser)

			identity = createIdentity(*ghUser)
			gh.identities.Create(ctx, &identity)
			gh.users.Create(ctx, &account.User{Email: primaryEmail, Identity: identity})
		} else {
			identity = users[0].Identity
		}

		fmt.Println("Identity: ", identity)

		// register other emails in User table?

		// generate token
		almtoken, err := gh.tokenManager.Generate(identity)
		if err != nil {
			fmt.Println("Failed to generate token", err)
			jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrUnauthorized(err.Error()))
			return ctx.Unauthorized(jerrors)
		}

		ctx.ResponseData.Header().Set("Location", knownReferer+"?token="+almtoken)
		return ctx.TemporaryRedirect()
	}

	// First time access, redirect to oauth provider

	// store referer id to state for redirect later
	fmt.Println("Got Request from: ", referer)
	state = uuid.NewV4().String()

	mapLock.Lock()
	defer mapLock.Unlock()

	stateReferer[state] = referer

	redirectURL := gh.config.AuthCodeURL(state, oauth2.AccessTypeOnline)
	ctx.ResponseData.Header().Set("Location", redirectURL)
	return ctx.TemporaryRedirect()
}

func (gh gitHubOAuth) getUserEmails(ctx context.Context, token *oauth2.Token) ([]ghEmail, error) {
	client := gh.config.Client(ctx, token)
	resp, err := client.Get("https://api.github.com/user/emails")
	if err != nil {
		return nil, err
	}

	var emails []ghEmail
	json.NewDecoder(resp.Body).Decode(&emails)
	return emails, nil
}

func (gh gitHubOAuth) getUser(ctx context.Context, token *oauth2.Token) (*ghUser, error) {
	client := gh.config.Client(ctx, token)
	resp, err := client.Get("https://api.github.com/user")
	if err != nil {
		return nil, err
	}

	var user ghUser
	json.NewDecoder(resp.Body).Decode(&user)
	return &user, nil
}

func createIdentity(ghUser ghUser) account.Identity {
	// Use login as name if 'name' is not set #391
	name := ghUser.Name
	if name == "" {
		name = ghUser.Login
	}
	return account.Identity{
		FullName: name,
		ImageURL: ghUser.AvatarURL,
	}
}

func filterPrimaryEmail(emails []ghEmail) string {
	for _, email := range emails {
		if email.Primary {
			return email.Email
		}
	}
	return ""
}

// ghEmail represents the needed response from api.github.com/user/emails
type ghEmail struct {
	Email    string `json:"email"`
	Primary  bool   `json:"primary"`
	Verified bool   `json:"verified"`
}

// ghUser represents the needed response from api.github.com/user
type ghUser struct {
	Name      string `json:"name"`
	Login     string `json:"login"`
	AvatarURL string `json:"avatar_url"`
}

// ContextIdentity returns the identity's ID found in given context
// Uses tokenManager.Locate to fetch the identity of currently logged in user
func ContextIdentity(ctx context.Context) (string, error) {
	tm := ReadTokenManagerFromContext(ctx)
	if tm == nil {
		return "", errors.New("Missing token manager")
	}
	uuid, err := tm.Locate(ctx)
	if err != nil {
		// TODO : need a way to define user as Guest
		fmt.Println("Geust User")
		return "", err
	}
	return uuid.String(), nil
}

type contextTMKey int

const (
	//contextTokenManagerKey is a key that will be used to put and to get `tokenManager` from goa.context
	contextTokenManagerKey contextTMKey = iota + 1
)

//ReadTokenManagerFromContext returns tokenManager from context.
// Must have been set by ContextWithTokenManager ONLY
func ReadTokenManagerFromContext(ctx context.Context) token.Manager {
	tm := ctx.Value(contextTokenManagerKey)
	if tm != nil {
		return tm.(token.Manager)
	}
	return nil
}

// ContextWithTokenManager injects tokenManager in the context for every incoming request
// Accepts Token.Manager in order to make sure that correct object is set in the context.
// Only other possible value is nil
func ContextWithTokenManager(ctx context.Context, tm token.Manager) context.Context {
	return context.WithValue(ctx, contextTokenManagerKey, tm)
}

// InjectTokenManager is a middleware responsible for setting up tokenManager in the context for every request.
func InjectTokenManager(tokenManager token.Manager) goa.Middleware {
	return func(h goa.Handler) goa.Handler {
		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
			ctxWithTM := ContextWithTokenManager(ctx, tokenManager)
			return h(ctxWithTM, rw, req)
		}
	}
}
<commit_msg>Stored Github User information should be updated (#537) (#594)<commit_after>package login

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"sync"

	"github.com/almighty/almighty-core/account"
	"github.com/almighty/almighty-core/app"
	"github.com/almighty/almighty-core/jsonapi"
	"github.com/almighty/almighty-core/token"
	"github.com/goadesign/goa"
	uuid "github.com/satori/go.uuid"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
)

const (
	// InvalidCodeError could occure when the OAuth Exchange with GitHub return no valid AccessToken
	InvalidCodeError string = "Invalid OAuth2.0 code"
	// PrimaryEmailNotFoundError could occure if no primary email was returned by GitHub
	PrimaryEmailNotFoundError string = "Primary email not found"
)

// Service defines the basic entrypoint required to perform a remote oauth login
type Service interface {
	Perform(ctx *app.AuthorizeLoginContext) error
}

// NewGitHubOAuth creates a new login.Service capable of using GitHub for authorization
func NewGitHubOAuth(config *oauth2.Config, identities account.IdentityRepository, users account.UserRepository, tokenManager token.Manager) Service {
	return &gitHubOAuth{
		config:       config,
		identities:   identities,
		users:        users,
		tokenManager: tokenManager,
	}
}

type gitHubOAuth struct {
	config       *oauth2.Config
	identities   account.IdentityRepository
	users        account.UserRepository
	tokenManager token.Manager
}

// TEMP: This will leak memory in the long run with many 'failed' redirect attemts
var stateReferer = map[string]string{}
var mapLock sync.RWMutex

func (gh *gitHubOAuth) Perform(ctx *app.AuthorizeLoginContext) error {
	state := ctx.Params.Get("state")
	code := ctx.Params.Get("code")
	referer := ctx.RequestData.Header.Get("Referer")

	if code != "" {
		// After redirect from oauth provider

		// validate known state
		var knownReferer string
		defer func() {
			delete(stateReferer, state)
		}()

		knownReferer = stateReferer[state]
		if state == "" || knownReferer == "" {
			jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrUnauthorized("State or known referer was empty"))
			return ctx.Unauthorized(jerrors)
		}

		ghtoken, err := gh.config.Exchange(ctx, code)

		/*

			In case of invalid code, this is what we get in the ghtoken object

			&oauth2.Token{AccessToken:"", TokenType:"", RefreshToken:"", Expiry:time.Time{sec:0, nsec:0, loc:(*time.Location)(nil)}, raw:url.Values{"error":[]string{"bad_verification_code"}, "error_description":[]string{"The code passed is incorrect or expired."}, "error_uri":[]string{"https://developer.github.com/v3/oauth/#bad-verification-code"}}}

		*/

		if err != nil || ghtoken.AccessToken == "" {
			fmt.Println(err)
			ctx.ResponseData.Header().Set("Location", knownReferer+"?error="+InvalidCodeError)
			return ctx.TemporaryRedirect()
		}

		emails, err := gh.getUserEmails(ctx, ghtoken)
		fmt.Println(emails)

		primaryEmail := filterPrimaryEmail(emails)
		if primaryEmail == "" {
			fmt.Println("No primary email found?! ", emails)
			ctx.ResponseData.Header().Set("Location", knownReferer+"?error="+PrimaryEmailNotFoundError)
			return ctx.TemporaryRedirect()
		}
		users, err := gh.users.Query(account.UserByEmails([]string{primaryEmail}), account.UserWithIdentity())
		if err != nil {
			ctx.ResponseData.Header().Set("Location", knownReferer+"?error=Associated user not found "+err.Error())
			return ctx.TemporaryRedirect()
		}
		var identity account.Identity
		ghUser, err := gh.getUser(ctx, ghtoken)
		if err != nil {
			fmt.Println(err)
			jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrUnauthorized(err.Error()))
			return ctx.Unauthorized(jerrors)
		}
		fmt.Println(ghUser)
		if len(users) == 0 {
			// No User found, create new User and Identity
			identity = createIdentity(*ghUser)
			gh.identities.Create(ctx, &identity)
			gh.users.Create(ctx, &account.User{Email: primaryEmail, Identity: identity})
		} else {
			identity = users[0].Identity
			// let's update the current identity with the fullname and avatar from GitHub,
			// in case the user changed them since the last time he logged in here
			gh.identities.Save(ctx, &identity)
		}

		fmt.Println("Identity: ", identity)

		// register other emails in User table?

		// generate token
		almtoken, err := gh.tokenManager.Generate(identity)
		if err != nil {
			fmt.Println("Failed to generate token", err)
			jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrUnauthorized(err.Error()))
			return ctx.Unauthorized(jerrors)
		}

		ctx.ResponseData.Header().Set("Location", knownReferer+"?token="+almtoken)
		return ctx.TemporaryRedirect()
	}

	// First time access, redirect to oauth provider

	// store referer id to state for redirect later
	fmt.Println("Got Request from: ", referer)
	state = uuid.NewV4().String()

	mapLock.Lock()
	defer mapLock.Unlock()

	stateReferer[state] = referer

	redirectURL := gh.config.AuthCodeURL(state, oauth2.AccessTypeOnline)
	ctx.ResponseData.Header().Set("Location", redirectURL)
	return ctx.TemporaryRedirect()
}

func (gh gitHubOAuth) getUserEmails(ctx context.Context, token *oauth2.Token) ([]ghEmail, error) {
	client := gh.config.Client(ctx, token)
	resp, err := client.Get("https://api.github.com/user/emails")
	if err != nil {
		return nil, err
	}

	var emails []ghEmail
	json.NewDecoder(resp.Body).Decode(&emails)
	return emails, nil
}

func (gh gitHubOAuth) getUser(ctx context.Context, token *oauth2.Token) (*ghUser, error) {
	client := gh.config.Client(ctx, token)
	resp, err := client.Get("https://api.github.com/user")
	if err != nil {
		return nil, err
	}

	var user ghUser
	json.NewDecoder(resp.Body).Decode(&user)
	return &user, nil
}

func createIdentity(ghUser ghUser) account.Identity {
	// Use login as name if 'name' is not set #391
	name := ghUser.Name
	if name == "" {
		name = ghUser.Login
	}
	return account.Identity{
		FullName: name,
		ImageURL: ghUser.AvatarURL,
	}
}

func filterPrimaryEmail(emails []ghEmail) string {
	for _, email := range emails {
		if email.Primary {
			return email.Email
		}
	}
	return ""
}

// ghEmail represents the needed response from api.github.com/user/emails
type ghEmail struct {
	Email    string `json:"email"`
	Primary  bool   `json:"primary"`
	Verified bool   `json:"verified"`
}

// ghUser represents the needed response from api.github.com/user
type ghUser struct {
	Name      string `json:"name"`
	Login     string `json:"login"`
	AvatarURL string `json:"avatar_url"`
}

// ContextIdentity returns the identity's ID found in given context
// Uses tokenManager.Locate to fetch the identity of currently logged in user
func ContextIdentity(ctx context.Context) (string, error) {
	tm := ReadTokenManagerFromContext(ctx)
	if tm == nil {
		return "", errors.New("Missing token manager")
	}
	uuid, err := tm.Locate(ctx)
	if err != nil {
		// TODO : need a way to define user as Guest
		fmt.Println("Geust User")
		return "", err
	}
	return uuid.String(), nil
}

type contextTMKey int

const (
	//contextTokenManagerKey is a key that will be used to put and to get `tokenManager` from goa.context
	contextTokenManagerKey contextTMKey = iota + 1
)

//ReadTokenManagerFromContext returns tokenManager from context.
// Must have been set by ContextWithTokenManager ONLY
func ReadTokenManagerFromContext(ctx context.Context) token.Manager {
	tm := ctx.Value(contextTokenManagerKey)
	if tm != nil {
		return tm.(token.Manager)
	}
	return nil
}

// ContextWithTokenManager injects tokenManager in the context for every incoming request
// Accepts Token.Manager in order to make sure that correct object is set in the context.
// Only other possible value is nil
func ContextWithTokenManager(ctx context.Context, tm token.Manager) context.Context {
	return context.WithValue(ctx, contextTokenManagerKey, tm)
}

// InjectTokenManager is a middleware responsible for setting up tokenManager in the context for every request.
func InjectTokenManager(tokenManager token.Manager) goa.Middleware {
	return func(h goa.Handler) goa.Handler {
		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
			ctxWithTM := ContextWithTokenManager(ctx, tokenManager)
			return h(ctxWithTM, rw, req)
		}
	}
}
<|endoftext|>


<commit_before>package posplay

import (
	"time"

	"github.com/thoas/go-funk"

	"github.com/underlx/disturbancesmlx/dataobjects"
)

func init() {
	dataobjects.RegisterPPAchievementStrategy(new(ReachLevelAchievementStrategy))
	dataobjects.RegisterPPAchievementStrategy(new(VisitStationsAchievementStrategy))
}

// StubAchievementStrategy partially implements dataobjects.PPAchievementStrategy
type StubAchievementStrategy struct{}

// ID is not implemented on purpose (to force strategies to specify theirs)

// HandleTrip implements dataobjects.PPAchievementStrategy
func (s *StubAchievementStrategy) HandleTrip(context *dataobjects.PPAchievementContext, trip *dataobjects.Trip) error {
	return nil
}

// HandleTripEdit implements dataobjects.PPAchievementStrategy
func (s *StubAchievementStrategy) HandleTripEdit(context *dataobjects.PPAchievementContext, trip *dataobjects.Trip) error {
	return nil
}

// HandleXPTransaction implements dataobjects.PPAchievementStrategy
func (s *StubAchievementStrategy) HandleXPTransaction(context *dataobjects.PPAchievementContext, transaction *dataobjects.PPXPTransaction, actualValueDiff int) error {
	return nil
}

// Progress implements dataobjects.PPAchievementStrategy
// If total == 0: this achievement has no progress, it's "all or nothing"
// If total < 0: this achievement is still locked for the user
func (s *StubAchievementStrategy) Progress(context *dataobjects.PPAchievementContext) (current, total int, err error) {
	return 0, -1, nil
}

// ReachLevelAchievementStrategy is an achievement strategy that rewards users when they reach a specified level
type ReachLevelAchievementStrategy struct {
	StubAchievementStrategy
}

// ID returns the ID for this PPAchievementStrategy
func (s *ReachLevelAchievementStrategy) ID() string {
	return "reach_level"
}

// HandleXPTransaction implements dataobjects.PPAchievementStrategy
func (s *ReachLevelAchievementStrategy) HandleXPTransaction(context *dataobjects.PPAchievementContext, transaction *dataobjects.PPXPTransaction, actualValueDiff int) error {
	tx, err := context.Node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	existingData, err := context.Player.Achievement(tx, context.Achievement.ID)
	if err == nil && existingData.Achieved {
		// the player already has this achievement
		return tx.Commit()
	}

	curXP, present := context.StrategyOwnCache.Load(context.Player.DiscordID)
	if !present {
		curXP, err = context.Player.XPBalance(tx)
		if err != nil {
			return err
		}

		context.StrategyOwnCache.Store(context.Player.DiscordID, curXP.(int))
	}

	prevXP := curXP.(int) - actualValueDiff

	curLevel, _ := dataobjects.PosPlayPlayerLevel(curXP.(int))
	prevLevel, _ := dataobjects.PosPlayPlayerLevel(prevXP)

	var config map[string]interface{}
	context.Achievement.UnmarshalConfig(&config)
	achievementLevel := int(config["level"].(float64))

	if curLevel > prevLevel && prevLevel < achievementLevel && curLevel >= achievementLevel {
		// ensure the achievement reward tx always appears after the cause by adding 1 ms
		err = achieveAchievement(tx, context.Player, context.Achievement, transaction.Time.Add(1*time.Millisecond))
		if err != nil {
			return err
		}
	}
	return tx.Commit()
}

// Progress implements dataobjects.PPAchievementStrategy
func (s *ReachLevelAchievementStrategy) Progress(context *dataobjects.PPAchievementContext) (current, total int, err error) {
	var config map[string]interface{}
	context.Achievement.UnmarshalConfig(&config)
	achievementLevel := int(config["level"].(float64))

	curXP, present := context.StrategyOwnCache.Load(context.Player.DiscordID)
	if !present {
		tx, err := context.Node.Beginx()
		if err != nil {
			return 0, 0, err
		}
		defer tx.Commit() // read-only tx

		curXP, err = context.Player.XPBalance(tx)
		if err != nil {
			return 0, 0, err
		}

		context.StrategyOwnCache.Store(context.Player.DiscordID, curXP.(int))
	}
	curLevel, _ := dataobjects.PosPlayPlayerLevel(curXP.(int))

	if curLevel > achievementLevel {
		curLevel = achievementLevel
	}
	return curLevel, achievementLevel, nil
}

// VisitStationsAchievementStrategy is an achievement strategy that rewards users when they visit certain stations
type VisitStationsAchievementStrategy struct {
	StubAchievementStrategy
}

type visitStationsConfig struct {
	Stations   []string `json:"stations"`
	SingleTrip bool     `json:"singleTrip"`
}

type visitStationsExtra struct {
	VisitedStations []string `json:"visitedStations"`
}

// ID returns the ID for this PPAchievementStrategy
func (s *VisitStationsAchievementStrategy) ID() string {
	return "visit_stations"
}

// HandleTrip implements dataobjects.PPAchievementStrategy
func (s *VisitStationsAchievementStrategy) HandleTrip(context *dataobjects.PPAchievementContext, trip *dataobjects.Trip) error {
	tx, err := context.Node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	existingData, err := context.Player.Achievement(tx, context.Achievement.ID)
	if err == nil && existingData.Achieved {
		// the player already has this achievement
		return tx.Commit()
	}

	var config visitStationsConfig
	context.Achievement.UnmarshalConfig(&config)

	stationsLeftToVisit := config.Stations
	if !config.SingleTrip && existingData != nil {
		var extra visitStationsExtra
		existingData.UnmarshalExtra(&extra)

		stationsLeftToVisit = funk.FilterString(stationsLeftToVisit, func(x string) bool {
			return !funk.ContainsString(extra.VisitedStations, x)
		})
	}

	visitedInThisTrip := make(map[string]bool)
	for _, use := range trip.StationUses {
		if !use.Manual {
			visitedInThisTrip[use.Station.ID] = true
		}
	}

	stationsLeftToVisit = funk.FilterString(stationsLeftToVisit, func(x string) bool {
		return !visitedInThisTrip[x]
	})

	if !config.SingleTrip {
		// store progress back
		extra := visitStationsExtra{}
		extra.VisitedStations = funk.FilterString(config.Stations, func(x string) bool {
			return !funk.ContainsString(stationsLeftToVisit, x)
		})
		if existingData == nil {
			existingData = &dataobjects.PPPlayerAchievement{
				DiscordID:   context.Player.DiscordID,
				Achievement: context.Achievement,
			}
		}
		err = existingData.MarshalExtra(extra)
		if err != nil {
			return err
		}
		err = existingData.Update(tx)
		if err != nil {
			return err
		}
	}

	if len(stationsLeftToVisit) == 0 {
		// ensure the achievement reward tx always appears after the cause by adding 1 ms
		err = achieveAchievement(tx, context.Player, context.Achievement, trip.SubmitTime.Add(1*time.Millisecond))
		if err != nil {
			return err
		}
	}
	return tx.Commit()
}

// Progress implements dataobjects.PPAchievementStrategy
func (s *VisitStationsAchievementStrategy) Progress(context *dataobjects.PPAchievementContext) (current, total int, err error) {
	tx, err := context.Node.Beginx()
	if err != nil {
		return 0, -1, err
	}
	defer tx.Commit() // read-only tx

	var config visitStationsConfig
	context.Achievement.UnmarshalConfig(&config)

	if config.SingleTrip {
		// achievements configured like this are all-or-nothing
		return 0, 0, nil
	}

	existingData, err := context.Player.Achievement(tx, context.Achievement.ID)
	if err != nil {
		// no existing data, player still has everything left to do
		return 0, len(config.Stations), nil
	}
	var extra visitStationsExtra
	existingData.UnmarshalExtra(&extra)

	return len(extra.VisitedStations), len(config.Stations), nil
}
<commit_msg>PosPlay: implement visit_throughout_line achievement strategy<commit_after>package posplay

import (
	"strings"
	"time"

	"github.com/thoas/go-funk"

	"github.com/underlx/disturbancesmlx/dataobjects"
)

func init() {
	dataobjects.RegisterPPAchievementStrategy(new(ReachLevelAchievementStrategy))
	dataobjects.RegisterPPAchievementStrategy(new(VisitStationsAchievementStrategy))
	dataobjects.RegisterPPAchievementStrategy(new(VisitThroughoutLineAchievementStrategy))
}

// StubAchievementStrategy partially implements dataobjects.PPAchievementStrategy
type StubAchievementStrategy struct{}

// ID is not implemented on purpose (to force strategies to specify theirs)

// HandleTrip implements dataobjects.PPAchievementStrategy
func (s *StubAchievementStrategy) HandleTrip(context *dataobjects.PPAchievementContext, trip *dataobjects.Trip) error {
	return nil
}

// HandleTripEdit implements dataobjects.PPAchievementStrategy
func (s *StubAchievementStrategy) HandleTripEdit(context *dataobjects.PPAchievementContext, trip *dataobjects.Trip) error {
	return nil
}

// HandleXPTransaction implements dataobjects.PPAchievementStrategy
func (s *StubAchievementStrategy) HandleXPTransaction(context *dataobjects.PPAchievementContext, transaction *dataobjects.PPXPTransaction, actualValueDiff int) error {
	return nil
}

// Progress implements dataobjects.PPAchievementStrategy
// If total == 0: this achievement has no progress, it's "all or nothing"
// If total < 0: this achievement is still locked for the user
func (s *StubAchievementStrategy) Progress(context *dataobjects.PPAchievementContext) (current, total int, err error) {
	return 0, -1, nil
}

// ReachLevelAchievementStrategy is an achievement strategy that rewards users when they reach a specified level
type ReachLevelAchievementStrategy struct {
	StubAchievementStrategy
}

// ID returns the ID for this PPAchievementStrategy
func (s *ReachLevelAchievementStrategy) ID() string {
	return "reach_level"
}

// HandleXPTransaction implements dataobjects.PPAchievementStrategy
func (s *ReachLevelAchievementStrategy) HandleXPTransaction(context *dataobjects.PPAchievementContext, transaction *dataobjects.PPXPTransaction, actualValueDiff int) error {
	tx, err := context.Node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	existingData, err := context.Player.Achievement(tx, context.Achievement.ID)
	if err == nil && existingData.Achieved {
		// the player already has this achievement
		return tx.Commit()
	}

	curXP, present := context.StrategyOwnCache.Load(context.Player.DiscordID)
	if !present {
		curXP, err = context.Player.XPBalance(tx)
		if err != nil {
			return err
		}

		context.StrategyOwnCache.Store(context.Player.DiscordID, curXP.(int))
	}

	prevXP := curXP.(int) - actualValueDiff

	curLevel, _ := dataobjects.PosPlayPlayerLevel(curXP.(int))
	prevLevel, _ := dataobjects.PosPlayPlayerLevel(prevXP)

	var config map[string]interface{}
	context.Achievement.UnmarshalConfig(&config)
	achievementLevel := int(config["level"].(float64))

	if curLevel > prevLevel && prevLevel < achievementLevel && curLevel >= achievementLevel {
		// ensure the achievement reward tx always appears after the cause by adding 1 ms
		err = achieveAchievement(tx, context.Player, context.Achievement, transaction.Time.Add(1*time.Millisecond))
		if err != nil {
			return err
		}
	}
	return tx.Commit()
}

// Progress implements dataobjects.PPAchievementStrategy
func (s *ReachLevelAchievementStrategy) Progress(context *dataobjects.PPAchievementContext) (current, total int, err error) {
	var config map[string]interface{}
	context.Achievement.UnmarshalConfig(&config)
	achievementLevel := int(config["level"].(float64))

	curXP, present := context.StrategyOwnCache.Load(context.Player.DiscordID)
	if !present {
		tx, err := context.Node.Beginx()
		if err != nil {
			return 0, 0, err
		}
		defer tx.Commit() // read-only tx

		curXP, err = context.Player.XPBalance(tx)
		if err != nil {
			return 0, 0, err
		}

		context.StrategyOwnCache.Store(context.Player.DiscordID, curXP.(int))
	}
	curLevel, _ := dataobjects.PosPlayPlayerLevel(curXP.(int))

	if curLevel > achievementLevel {
		curLevel = achievementLevel
	}
	return curLevel, achievementLevel, nil
}

// VisitStationsAchievementStrategy is an achievement strategy that rewards users when they visit certain stations
type VisitStationsAchievementStrategy struct {
	StubAchievementStrategy
}

type visitStationsConfig struct {
	Stations   []string `json:"stations"`
	SingleTrip bool     `json:"singleTrip"`
}

type visitStationsExtra struct {
	VisitedStations []string `json:"visitedStations"`
}

// ID returns the ID for this PPAchievementStrategy
func (s *VisitStationsAchievementStrategy) ID() string {
	return "visit_stations"
}

// HandleTrip implements dataobjects.PPAchievementStrategy
func (s *VisitStationsAchievementStrategy) HandleTrip(context *dataobjects.PPAchievementContext, trip *dataobjects.Trip) error {
	tx, err := context.Node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	existingData, err := context.Player.Achievement(tx, context.Achievement.ID)
	if err == nil && existingData.Achieved {
		// the player already has this achievement
		return tx.Commit()
	}

	var config visitStationsConfig
	context.Achievement.UnmarshalConfig(&config)

	stationsLeftToVisit := config.Stations
	if !config.SingleTrip && existingData != nil {
		var extra visitStationsExtra
		existingData.UnmarshalExtra(&extra)

		stationsLeftToVisit = funk.FilterString(stationsLeftToVisit, func(x string) bool {
			return !funk.ContainsString(extra.VisitedStations, x)
		})
	}

	visitedInThisTrip := make(map[string]bool)
	for _, use := range trip.StationUses {
		if !use.Manual {
			visitedInThisTrip[use.Station.ID] = true
		}
	}

	stationsLeftToVisit = funk.FilterString(stationsLeftToVisit, func(x string) bool {
		return !visitedInThisTrip[x]
	})

	if !config.SingleTrip {
		// store progress back
		extra := visitStationsExtra{}
		extra.VisitedStations = funk.FilterString(config.Stations, func(x string) bool {
			return !funk.ContainsString(stationsLeftToVisit, x)
		})
		if existingData == nil {
			existingData = &dataobjects.PPPlayerAchievement{
				DiscordID:   context.Player.DiscordID,
				Achievement: context.Achievement,
			}
		}
		err = existingData.MarshalExtra(extra)
		if err != nil {
			return err
		}
		err = existingData.Update(tx)
		if err != nil {
			return err
		}
	}

	if len(stationsLeftToVisit) == 0 {
		// ensure the achievement reward tx always appears after the cause by adding 1 ms
		err = achieveAchievement(tx, context.Player, context.Achievement, trip.SubmitTime.Add(1*time.Millisecond))
		if err != nil {
			return err
		}
	}
	return tx.Commit()
}

// Progress implements dataobjects.PPAchievementStrategy
func (s *VisitStationsAchievementStrategy) Progress(context *dataobjects.PPAchievementContext) (current, total int, err error) {
	tx, err := context.Node.Beginx()
	if err != nil {
		return 0, -1, err
	}
	defer tx.Commit() // read-only tx

	var config visitStationsConfig
	context.Achievement.UnmarshalConfig(&config)

	if config.SingleTrip {
		// achievements configured like this are all-or-nothing
		return 0, 0, nil
	}

	existingData, err := context.Player.Achievement(tx, context.Achievement.ID)
	if err != nil {
		// no existing data, player still has everything left to do
		return 0, len(config.Stations), nil
	}
	var extra visitStationsExtra
	existingData.UnmarshalExtra(&extra)

	return len(extra.VisitedStations), len(config.Stations), nil
}

// VisitThroughoutLineAchievementStrategy is an achievement strategy that rewards users when they visit one line from one end to another in a single trip
type VisitThroughoutLineAchievementStrategy struct {
	StubAchievementStrategy
}

type visitThroughoutLineConfig struct {
	Line      string `json:"line"`      // line ID or "*"
	Direction string `json:"direction"` // "ascending", "descending" or "*"
}

// ID returns the ID for this PPAchievementStrategy
func (s *VisitThroughoutLineAchievementStrategy) ID() string {
	return "visit_throughout_line"
}

// HandleTrip implements dataobjects.PPAchievementStrategy
func (s *VisitThroughoutLineAchievementStrategy) HandleTrip(context *dataobjects.PPAchievementContext, trip *dataobjects.Trip) error {
	tx, err := context.Node.Beginx()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	existingData, err := context.Player.Achievement(tx, context.Achievement.ID)
	if err == nil && existingData.Achieved {
		// the player already has this achievement
		return tx.Commit()
	}

	var config visitThroughoutLineConfig
	context.Achievement.UnmarshalConfig(&config)

	possibleLines := []*dataobjects.Line{}
	if config.Line == "*" {
		possibleLines, err = dataobjects.GetLines(tx)
		if err != nil {
			return err
		}
	} else {
		line, err := dataobjects.GetLine(tx, config.Line)
		if err != nil {
			return err
		}
		possibleLines = []*dataobjects.Line{line}
	}
	possibleNeedles := []string{}
	for _, line := range possibleLines {
		stations, err := line.Stations(tx)
		if err != nil {
			return err
		}
		closed := make(map[string]bool)
		for _, station := range stations {
			closed[station.ID], err = station.Closed(tx)
			if err != nil {
				return err
			}
		}

		switch config.Direction {
		case "ascending", "up", "*":
			needle := ""
			for _, station := range stations {
				if !closed[station.ID] {
					needle += station.ID + "|"
				}
			}
			possibleNeedles = append(possibleNeedles, needle)
			if config.Direction == "*" {
				goto descending
			}
		descending:
			fallthrough
		case "descending", "down":
			stations = funk.Reverse(stations).([]*dataobjects.Station)
			needle := ""
			for _, station := range stations {
				if !closed[station.ID] {
					needle += station.ID + "|"
				}
			}
			possibleNeedles = append(possibleNeedles, needle)
		}
	}

	haystack := ""
	for _, uses := range trip.StationUses {
		haystack += uses.Station.ID + "|"
	}

	found := false
	for _, needle := range possibleNeedles {
		if strings.Contains(haystack, needle) {
			found = true
			break
		}
	}

	if found {
		// ensure the achievement reward tx always appears after the cause by adding 1 ms
		err = achieveAchievement(tx, context.Player, context.Achievement, trip.SubmitTime.Add(1*time.Millisecond))
		if err != nil {
			return err
		}
	}

	return tx.Commit()
}

// Progress implements dataobjects.PPAchievementStrategy
func (s *VisitThroughoutLineAchievementStrategy) Progress(context *dataobjects.PPAchievementContext) (current, total int, err error) {
	return 0, 0, nil
}
<|endoftext|>