<commit_before>package main

import (
	"errors"
	"fmt"
)

type player struct {
	LOS         map[position]bool
	Rays        rayMap
	Pos         position
	HP          int
	MP          int
	Consumables map[consumable]int
	Gold        int
	Target      position
	Statuses    map[status]int
	Armour      armour
	Weapon      weapon
	Shield      shield
	Aptitudes   map[aptitude]bool
	Rods        map[rod]*rodProps
}

func (p *player) HPMax() int {
	hpmax := 40
	if p.Aptitudes[AptHealthy] {
		hpmax += 10
	}
	return hpmax
}

func (p *player) MPMax() int {
	mpmax := 10
	if p.Aptitudes[AptMagic] {
		mpmax += 5
	}
	return mpmax
}

func (p *player) Accuracy() int {
	acc := 15
	if p.Aptitudes[AptAccurate] {
		acc += 3
	}
	return acc
}

func (p *player) Armor() int {
	ar := 0
	switch p.Armour {
	case LeatherArmour:
		ar += 3
	case ChainMail:
		ar += 4
	case PlateArmour:
		ar += 6
	}
	if p.Aptitudes[AptScales] {
		ar += 2
	}
	if p.HasStatus(StatusLignification) {
		ar = 9 + ar/2
	}
	if p.HasStatus(StatusCorrosion) {
		ar -= 2 * p.Statuses[StatusCorrosion]
		if ar < 0 {
			ar = 0
		}
	}
	return ar
}

func (p *player) Attack() int {
	attack := p.Weapon.Attack()
	if p.Aptitudes[AptStrong] {
		attack += attack / 5
	}
	if p.HasStatus(StatusCorrosion) {
		penalty := p.Statuses[StatusCorrosion]
		if penalty > 5 {
			penalty = 5
		}
		attack -= penalty
	}
	return attack
}

func (p *player) Block() int {
	block := p.Shield.Block()
	if p.HasStatus(StatusDisabledShield) {
		block /= 3
	}
	return block
}

func (p *player) Evasion() int {
	ev := 15
	if p.Aptitudes[AptAgile] {
		ev += 3
	}
	if p.HasStatus(StatusAgile) {
		ev += 7
	}
	return ev
}

func (p *player) HasStatus(st status) bool {
	return p.Statuses[st] > 0
}

func (p *player) AptitudeCount() int {
	count := 0
	for _, b := range p.Aptitudes {
		if b {
			count++
		}
	}
	return count
}

func (g *game) AutoToDir(ev event) bool {
	if g.MonsterInLOS() == nil {
		err := g.MovePlayer(g.Player.Pos.To(*g.AutoDir), ev)
		if err != nil {
			g.Print(err.Error())
			g.AutoDir = nil
			return false
		}
		return true
	}
	g.AutoDir = nil
	return false
}

func (g *game) GoToDir(dir direction, ev event) error {
	if g.MonsterInLOS() != nil {
		g.AutoDir = nil
		return errors.New("You cannot travel while there are monsters in view.")
	}
	err := g.MovePlayer(g.Player.Pos.To(dir), ev)
	if err != nil {
		return err
	}
	g.AutoDir = &dir
	return nil
}

func (g *game) MoveToTarget(ev event) bool {
	if g.MonsterInLOS() == nil {
		path := g.PlayerPath(g.Player.Pos, *g.AutoTarget)
		if len(path) > 1 {
			err := g.MovePlayer(path[len(path)-2], ev)
			if err != nil {
				g.Print(err.Error())
				g.AutoTarget = nil
				return false
			}
			return true
		}
	}
	g.AutoTarget = nil
	return false
}

func (g *game) WaitTurn(ev event) {
	// XXX Really wait for 10 ?
	g.ScummingAction(ev)
	ev.Renew(g, 10)
}

func (g *game) ExistsMonster() bool {
	for _, mons := range g.Monsters {
		if mons.Exists() {
			return true
		}
	}
	return false
}

func (g *game) ScummingAction(ev event) {
	if g.Player.HP == g.Player.HPMax() && g.Player.MP == g.Player.MPMax() {
		g.Scumming++
	}
	if g.Scumming == 100 {
		if g.ExistsMonster() {
			g.Print("You feel a little bored.")
		}
	}
	if g.Scumming > 120 {
		if !g.ExistsMonster() {
			g.Scumming = 0
			return
		}
		g.Player.HP = g.Player.HP / 2
		if RandInt(2) == 0 {
			g.MakeNoise(100, g.Player.Pos)
			neighbors := g.Dungeon.Neighbors(g.Player.Pos)
			for _, pos := range neighbors {
				if RandInt(3) != 0 {
					g.Dungeon.SetCell(pos, FreeCell)
				}
			}
			g.Print("You hear a terrible explosion coming from the ground. You are lignified.")
			g.Player.Statuses[StatusLignification]++
			g.PushEvent(&simpleEvent{ERank: ev.Rank() + 240 + RandInt(10), EAction: LignificationEnd})
		} else {
			delay := 20 + RandInt(5)
			g.Player.Statuses[StatusTele]++
			g.PushEvent(&simpleEvent{ERank: ev.Rank() + delay, EAction: Teleportation})
			g.Print("Something hurt you! You feel unstable.")
		}
		g.Scumming = 0
	}
}

func (g *game) FairAction() {
	g.Scumming -= 10
	if g.Scumming < 0 {
		g.Scumming = 0
	}
}

func (g *game) Rest(ev event) error {
	if g.MonsterInLOS() != nil {
		return fmt.Errorf("You cannot sleep while monsters are in view.")
	}
	if g.Player.HP == g.Player.HPMax() && g.Player.MP == g.Player.MPMax() && !g.Player.HasStatus(StatusExhausted) &&
		!g.Player.HasStatus(StatusConfusion) && !g.Player.HasStatus(StatusLignification) {
		return errors.New("You do not need to rest.")
	}
	g.WaitTurn(ev)
	g.Resting = true
	return nil
}

func (g *game) Equip(ev event) error {
	if eq, ok := g.Equipables[g.Player.Pos]; ok {
		eq.Equip(g)
		ev.Renew(g, 10)
		return nil
	}
	return errors.New("Found nothing to equip here.")
}

func (g *game) Teleportation(ev event) {
	var pos position
	i := 0
	count := 0
	for {
		count++
		if count > 1000 {
			panic("Teleportation")
		}
		pos = g.FreeCell()
		if pos.Distance(g.Player.Pos) < 15 && i < 1000 {
			i++
			continue
		}
		break

	}
	g.Player.Statuses[StatusTele]--
	if g.Dungeon.Valid(pos) {
		// should always happen
		g.Player.Pos = pos
		g.Print("You feel yourself teleported away.")
		g.CollectGround()
		g.ComputeLOS()
		g.MakeMonstersAware()
	} else {
		g.Print("Something went wrong with the teleportation.")
	}
}

func (g *game) CollectGround() {
	pos := g.Player.Pos
	if g.Gold[pos] > 0 {
		g.Player.Gold += g.Gold[pos]
		g.Printf("You pick up %d gold.", g.Gold[pos])
		delete(g.Gold, pos)
	}
	if c, ok := g.Collectables[pos]; ok && c != nil {
		g.Player.Consumables[c.Consumable] += c.Quantity
		delete(g.Collectables, pos)
		if c.Quantity > 1 {
			g.Printf("You take %d %s.", c.Quantity, c.Consumable.Plural())
		} else {
			g.Printf("You take %s.", Indefinite(c.Consumable.String(), false))
		}
	}
	if r, ok := g.Rods[pos]; ok {
		g.Player.Rods[r] = &rodProps{Charge: r.MaxCharge() - 1}
		delete(g.Rods, pos)
		g.Printf("You take a %s.", r)
		g.StoryPrintf("You found and took a %s.", r)
	}
}

func (g *game) MovePlayer(pos position, ev event) error {
	if !g.Dungeon.Valid(pos) || g.Dungeon.Cell(pos).T == WallCell {
		return errors.New("You cannot move there.")
	}
	if g.Player.HasStatus(StatusConfusion) {
		switch pos.Dir(g.Player.Pos) {
		case E, N, W, S:
		default:
			return errors.New("You cannot use diagonal movements while confused.")
		}
	}
	delay := 10
	switch g.Dungeon.Cell(pos).T {
	case FreeCell:
		mons, _ := g.MonsterAt(pos)
		if !mons.Exists() {
			if g.Player.HasStatus(StatusLignification) {
				return errors.New("You cannot move while lignified")
			}
			g.Player.Pos = pos
			g.CollectGround()
			g.ComputeLOS()
			if g.Autoexploring {
				mons := g.MonsterInLOS()
				if mons.Exists() {
					g.Printf("You see %s (%v).", Indefinite(mons.Kind.String(), false), mons.State)
				}
				g.FairAction()
			} else {
				g.ScummingAction(ev)
			}
			g.MakeMonstersAware()
			if g.Player.Aptitudes[AptFast] {
				// only fast for movement
				delay -= 2
			}
			if g.Player.HasStatus(StatusSwift) {
				// only fast for movement
				delay -= 3
			}
		} else {
			g.FairAction()
			g.AttackMonster(mons, ev)
		}
	}
	if g.Player.HasStatus(StatusBerserk) {
		delay -= 3
	}
	if g.Player.HasStatus(StatusSlow) {
		delay += 3
	}
	ev.Renew(g, delay)
	return nil
}

func (g *game) HealPlayer(ev event) {
	if g.Player.HP < g.Player.HPMax() {
		g.Player.HP++
	}
	delay := 50
	if g.Player.Aptitudes[AptRegen] {
		delay = 25
	}
	ev.Renew(g, delay)
}

func (g *game) MPRegen(ev event) {
	if g.Player.MP < g.Player.MPMax() {
		g.Player.MP++
	}
	delay := 100
	ev.Renew(g, delay)
}

func (g *game) Smoke(ev event) {
	dij := &normalPath{game: g}
	nm := Dijkstra(dij, []position{g.Player.Pos}, 2)
	for pos := range nm {
		_, ok := g.Clouds[pos]
		if !ok {
			g.Clouds[pos] = CloudFog
			g.PushEvent(&cloudEvent{ERank: ev.Rank() + 100 + RandInt(100), EAction: CloudEnd, Pos: pos})
		}
	}
	g.Player.Statuses[StatusSwift]++
	g.PushEvent(&simpleEvent{ERank: ev.Rank() + 20 + RandInt(10), EAction: HasteEnd})
	g.ComputeLOS()
	g.Print("You feel an energy burst and smoking coming out from you.")
}
<commit_msg>log when standing over static object (resolve #8)<commit_after>package main

import (
	"errors"
	"fmt"
)

type player struct {
	LOS         map[position]bool
	Rays        rayMap
	Pos         position
	HP          int
	MP          int
	Consumables map[consumable]int
	Gold        int
	Target      position
	Statuses    map[status]int
	Armour      armour
	Weapon      weapon
	Shield      shield
	Aptitudes   map[aptitude]bool
	Rods        map[rod]*rodProps
}

func (p *player) HPMax() int {
	hpmax := 40
	if p.Aptitudes[AptHealthy] {
		hpmax += 10
	}
	return hpmax
}

func (p *player) MPMax() int {
	mpmax := 10
	if p.Aptitudes[AptMagic] {
		mpmax += 5
	}
	return mpmax
}

func (p *player) Accuracy() int {
	acc := 15
	if p.Aptitudes[AptAccurate] {
		acc += 3
	}
	return acc
}

func (p *player) Armor() int {
	ar := 0
	switch p.Armour {
	case LeatherArmour:
		ar += 3
	case ChainMail:
		ar += 4
	case PlateArmour:
		ar += 6
	}
	if p.Aptitudes[AptScales] {
		ar += 2
	}
	if p.HasStatus(StatusLignification) {
		ar = 9 + ar/2
	}
	if p.HasStatus(StatusCorrosion) {
		ar -= 2 * p.Statuses[StatusCorrosion]
		if ar < 0 {
			ar = 0
		}
	}
	return ar
}

func (p *player) Attack() int {
	attack := p.Weapon.Attack()
	if p.Aptitudes[AptStrong] {
		attack += attack / 5
	}
	if p.HasStatus(StatusCorrosion) {
		penalty := p.Statuses[StatusCorrosion]
		if penalty > 5 {
			penalty = 5
		}
		attack -= penalty
	}
	return attack
}

func (p *player) Block() int {
	block := p.Shield.Block()
	if p.HasStatus(StatusDisabledShield) {
		block /= 3
	}
	return block
}

func (p *player) Evasion() int {
	ev := 15
	if p.Aptitudes[AptAgile] {
		ev += 3
	}
	if p.HasStatus(StatusAgile) {
		ev += 7
	}
	return ev
}

func (p *player) HasStatus(st status) bool {
	return p.Statuses[st] > 0
}

func (p *player) AptitudeCount() int {
	count := 0
	for _, b := range p.Aptitudes {
		if b {
			count++
		}
	}
	return count
}

func (g *game) AutoToDir(ev event) bool {
	if g.MonsterInLOS() == nil {
		err := g.MovePlayer(g.Player.Pos.To(*g.AutoDir), ev)
		if err != nil {
			g.Print(err.Error())
			g.AutoDir = nil
			return false
		}
		return true
	}
	g.AutoDir = nil
	return false
}

func (g *game) GoToDir(dir direction, ev event) error {
	if g.MonsterInLOS() != nil {
		g.AutoDir = nil
		return errors.New("You cannot travel while there are monsters in view.")
	}
	err := g.MovePlayer(g.Player.Pos.To(dir), ev)
	if err != nil {
		return err
	}
	g.AutoDir = &dir
	return nil
}

func (g *game) MoveToTarget(ev event) bool {
	if g.MonsterInLOS() == nil {
		path := g.PlayerPath(g.Player.Pos, *g.AutoTarget)
		if len(path) > 1 {
			err := g.MovePlayer(path[len(path)-2], ev)
			if err != nil {
				g.Print(err.Error())
				g.AutoTarget = nil
				return false
			}
			return true
		}
	}
	g.AutoTarget = nil
	return false
}

func (g *game) WaitTurn(ev event) {
	// XXX Really wait for 10 ?
	g.ScummingAction(ev)
	ev.Renew(g, 10)
}

func (g *game) ExistsMonster() bool {
	for _, mons := range g.Monsters {
		if mons.Exists() {
			return true
		}
	}
	return false
}

func (g *game) ScummingAction(ev event) {
	if g.Player.HP == g.Player.HPMax() && g.Player.MP == g.Player.MPMax() {
		g.Scumming++
	}
	if g.Scumming == 100 {
		if g.ExistsMonster() {
			g.Print("You feel a little bored.")
		}
	}
	if g.Scumming > 120 {
		if !g.ExistsMonster() {
			g.Scumming = 0
			return
		}
		g.Player.HP = g.Player.HP / 2
		if RandInt(2) == 0 {
			g.MakeNoise(100, g.Player.Pos)
			neighbors := g.Dungeon.Neighbors(g.Player.Pos)
			for _, pos := range neighbors {
				if RandInt(3) != 0 {
					g.Dungeon.SetCell(pos, FreeCell)
				}
			}
			g.Print("You hear a terrible explosion coming from the ground. You are lignified.")
			g.Player.Statuses[StatusLignification]++
			g.PushEvent(&simpleEvent{ERank: ev.Rank() + 240 + RandInt(10), EAction: LignificationEnd})
		} else {
			delay := 20 + RandInt(5)
			g.Player.Statuses[StatusTele]++
			g.PushEvent(&simpleEvent{ERank: ev.Rank() + delay, EAction: Teleportation})
			g.Print("Something hurt you! You feel unstable.")
		}
		g.Scumming = 0
	}
}

func (g *game) FairAction() {
	g.Scumming -= 10
	if g.Scumming < 0 {
		g.Scumming = 0
	}
}

func (g *game) Rest(ev event) error {
	if g.MonsterInLOS() != nil {
		return fmt.Errorf("You cannot sleep while monsters are in view.")
	}
	if g.Player.HP == g.Player.HPMax() && g.Player.MP == g.Player.MPMax() && !g.Player.HasStatus(StatusExhausted) &&
		!g.Player.HasStatus(StatusConfusion) && !g.Player.HasStatus(StatusLignification) {
		return errors.New("You do not need to rest.")
	}
	g.WaitTurn(ev)
	g.Resting = true
	return nil
}

func (g *game) Equip(ev event) error {
	if eq, ok := g.Equipables[g.Player.Pos]; ok {
		eq.Equip(g)
		ev.Renew(g, 10)
		return nil
	}
	return errors.New("Found nothing to equip here.")
}

func (g *game) Teleportation(ev event) {
	var pos position
	i := 0
	count := 0
	for {
		count++
		if count > 1000 {
			panic("Teleportation")
		}
		pos = g.FreeCell()
		if pos.Distance(g.Player.Pos) < 15 && i < 1000 {
			i++
			continue
		}
		break

	}
	g.Player.Statuses[StatusTele]--
	if g.Dungeon.Valid(pos) {
		// should always happen
		g.Player.Pos = pos
		g.Print("You feel yourself teleported away.")
		g.CollectGround()
		g.ComputeLOS()
		g.MakeMonstersAware()
	} else {
		g.Print("Something went wrong with the teleportation.")
	}
}

func (g *game) CollectGround() {
	pos := g.Player.Pos
	if g.Gold[pos] > 0 {
		g.Player.Gold += g.Gold[pos]
		g.Printf("You pick up %d gold.", g.Gold[pos])
		delete(g.Gold, pos)
	}
	if c, ok := g.Collectables[pos]; ok && c != nil {
		g.Player.Consumables[c.Consumable] += c.Quantity
		delete(g.Collectables, pos)
		if c.Quantity > 1 {
			g.Printf("You take %d %s.", c.Quantity, c.Consumable.Plural())
		} else {
			g.Printf("You take %s.", Indefinite(c.Consumable.String(), false))
		}
	}
	if r, ok := g.Rods[pos]; ok {
		g.Player.Rods[r] = &rodProps{Charge: r.MaxCharge() - 1}
		delete(g.Rods, pos)
		g.Printf("You take a %s.", r)
		g.StoryPrintf("You found and took a %s.", r)
	}
	if eq, ok := g.Equipables[pos]; ok {
		g.Printf("You stand over %s.", Indefinite(eq.String(), false))
	} else if g.Stairs[pos] {
		g.Print("You stand over stairs.")
	} else if g.Doors[pos] {
		g.Print("You stand at the door.")
	}
}

func (g *game) MovePlayer(pos position, ev event) error {
	if !g.Dungeon.Valid(pos) || g.Dungeon.Cell(pos).T == WallCell {
		return errors.New("You cannot move there.")
	}
	if g.Player.HasStatus(StatusConfusion) {
		switch pos.Dir(g.Player.Pos) {
		case E, N, W, S:
		default:
			return errors.New("You cannot use diagonal movements while confused.")
		}
	}
	delay := 10
	switch g.Dungeon.Cell(pos).T {
	case FreeCell:
		mons, _ := g.MonsterAt(pos)
		if !mons.Exists() {
			if g.Player.HasStatus(StatusLignification) {
				return errors.New("You cannot move while lignified")
			}
			g.Player.Pos = pos
			g.CollectGround()
			g.ComputeLOS()
			if g.Autoexploring {
				mons := g.MonsterInLOS()
				if mons.Exists() {
					g.Printf("You see %s (%v).", Indefinite(mons.Kind.String(), false), mons.State)
				}
				g.FairAction()
			} else {
				g.ScummingAction(ev)
			}
			g.MakeMonstersAware()
			if g.Player.Aptitudes[AptFast] {
				// only fast for movement
				delay -= 2
			}
			if g.Player.HasStatus(StatusSwift) {
				// only fast for movement
				delay -= 3
			}
		} else {
			g.FairAction()
			g.AttackMonster(mons, ev)
		}
	}
	if g.Player.HasStatus(StatusBerserk) {
		delay -= 3
	}
	if g.Player.HasStatus(StatusSlow) {
		delay += 3
	}
	ev.Renew(g, delay)
	return nil
}

func (g *game) HealPlayer(ev event) {
	if g.Player.HP < g.Player.HPMax() {
		g.Player.HP++
	}
	delay := 50
	if g.Player.Aptitudes[AptRegen] {
		delay = 25
	}
	ev.Renew(g, delay)
}

func (g *game) MPRegen(ev event) {
	if g.Player.MP < g.Player.MPMax() {
		g.Player.MP++
	}
	delay := 100
	ev.Renew(g, delay)
}

func (g *game) Smoke(ev event) {
	dij := &normalPath{game: g}
	nm := Dijkstra(dij, []position{g.Player.Pos}, 2)
	for pos := range nm {
		_, ok := g.Clouds[pos]
		if !ok {
			g.Clouds[pos] = CloudFog
			g.PushEvent(&cloudEvent{ERank: ev.Rank() + 100 + RandInt(100), EAction: CloudEnd, Pos: pos})
		}
	}
	g.Player.Statuses[StatusSwift]++
	g.PushEvent(&simpleEvent{ERank: ev.Rank() + 20 + RandInt(10), EAction: HasteEnd})
	g.ComputeLOS()
	g.Print("You feel an energy burst and smoking coming out from you.")
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"log"
	"net"
	"bytes"
)

const (
	chunkRadius = 10
)

type Player struct {
	Entity
	game         *Game
	conn         net.Conn
	name         string
	position     XYZ
	orientation  Orientation
	currentItem  int16
	txQueue      chan []byte
}

func StartPlayer(game *Game, conn net.Conn, name string) {
	player := &Player{
		game:        game,
		conn:        conn,
		name:        name,
		position:    StartPosition,
		orientation: Orientation{0, 0},
		txQueue:     make(chan []byte, 128),
	}

	go player.ReceiveLoop()
	go player.TransmitLoop()

	game.Enqueue(func(game *Game) {
		game.AddPlayer(player)
		player.postLogin()
	})
}

func (player *Player) PacketKeepAlive() {
}

func (player *Player) PacketChatMessage(message string) {
	log.Stderrf("PacketChatMessage message=%s", message)

	player.game.Enqueue(func(game *Game) { game.SendChatMessage(message) })
}

func (player *Player) PacketFlying(flying bool) {
}

func (player *Player) PacketPlayerPosition(position *XYZ, stance float64, flying bool) {
	log.Stderrf("PacketPlayerPosition position=(%.2f, %.2f, %.2f) stance=%.2f flying=%v",
		position.x, position.y, position.z, stance, flying)
}

func (player *Player) PacketPlayerLook(orientation *Orientation, flying bool) {
	player.game.Enqueue(func (game *Game) {
		buf := &bytes.Buffer{}
		WriteEntityLook(buf, player.EntityID, orientation)
		game.MulticastPacket(buf.Bytes(), player)
	})
}

func (player *Player) PacketPlayerDigging(status byte, x int32, y byte, z int32, face byte) {
	log.Stderrf("PacketPlayerDigging status=%d x=%d y=%d z=%d face=%d",
		status, x, y, z, face)
}

func (player *Player) PacketPlayerBlockPlacement(blockItemID int16, x int32, y byte, z int32, direction byte) {
	log.Stderrf("PacketPlayerBlockPlacement blockItemID=%d x=%d y=%d z=%d direction=%d",
		blockItemID, x, y, z, direction)
}

func (player *Player) PacketHoldingChange(blockItemID int16) {
	log.Stderrf("PacketHoldingChange blockItemID=%d", blockItemID)
}

func (player *Player) PacketArmAnimation(forward bool) {
	log.Stderrf("PacketArmAnimation forward=%v", forward)
}

func (player *Player) PacketDisconnect(reason string) {
	log.Stderrf("PacketDisconnect reason=%s", reason)
	player.game.Enqueue(func(game *Game) { game.RemovePlayer(player) })
}

func (player *Player) ReceiveLoop() {
	for {
		err := ReadPacket(player.conn, player)
		if err != nil {
			log.Stderr("ReceiveLoop failed: ", err.String())
			return
		}
	}
}

func (player *Player) TransmitLoop() {
	for {
		bs := <-player.txQueue
		_, err := player.conn.Write(bs)
		if err != nil {
			log.Stderr("TransmitLoop failed: ", err.String())
			return
		}
	}
}

func (player *Player) sendChunks(writer io.Writer) {
	playerX := int32(player.position.x) / ChunkSizeX
	playerZ := int32(player.position.z) / ChunkSizeZ

	for z := playerZ - chunkRadius; z < playerZ + chunkRadius; z++ {
		for x := playerX - chunkRadius; x < playerX + chunkRadius; x++ {
			WritePreChunk(writer, x, z, true)
		}
	}

	for z := playerZ - chunkRadius; z < playerZ + chunkRadius; z++ {
		for x := playerX - chunkRadius; x < playerX + chunkRadius; x++ {
			chunk := player.game.chunkManager.Get(x, z)
			WriteMapChunk(writer, chunk)
		}
	}
}

func (player *Player) TransmitPacket(packet []byte) {
	player.txQueue <- packet
}

func (player *Player) postLogin() {
	buf := &bytes.Buffer{}
	WriteSpawnPosition(buf, &player.position)
	player.sendChunks(buf)
	WritePlayerInventory(buf)
	WritePlayerPositionLook(buf, &player.position, &player.orientation,
		0, false)
	player.TransmitPacket(buf.Bytes())
}
<commit_msg>player: Send player position<commit_after>package main

import (
	"io"
	"log"
	"net"
	"math"
	"bytes"
)

const (
	chunkRadius = 10
)

type Player struct {
	Entity
	game         *Game
	conn         net.Conn
	name         string
	position     XYZ
	orientation  Orientation
	currentItem  int16
	txQueue      chan []byte
}

func StartPlayer(game *Game, conn net.Conn, name string) {
	player := &Player{
		game:        game,
		conn:        conn,
		name:        name,
		position:    StartPosition,
		orientation: Orientation{0, 0},
		txQueue:     make(chan []byte, 128),
	}

	go player.ReceiveLoop()
	go player.TransmitLoop()

	game.Enqueue(func(game *Game) {
		game.AddPlayer(player)
		player.postLogin()
	})
}

func (player *Player) PacketKeepAlive() {
}

func (player *Player) PacketChatMessage(message string) {
	log.Stderrf("PacketChatMessage message=%s", message)

	player.game.Enqueue(func(game *Game) { game.SendChatMessage(message) })
}

func (player *Player) PacketFlying(flying bool) {
}

func (player *Player) PacketPlayerPosition(position *XYZ, stance float64, flying bool) {
	log.Stderrf("PacketPlayerPosition position=(%.2f, %.2f, %.2f) stance=%.2f flying=%v",
		position.x, position.y, position.z, stance, flying)

	player.game.Enqueue(func (game *Game) {
		var delta = XYZ{position.x - player.position.x,
		                position.y - player.position.y,
		                position.z - player.position.z}
		distance := math.Sqrt(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z)
		if distance > 10 {
			log.Stderrf("Discarding player position that is too far removed (%.2f, %.2f, %.2f)",
				position.x, position.y, position.z)
			return
		}

		player.position = *position

		buf := &bytes.Buffer{}
		WriteEntityTeleport(buf, player.EntityID, &player.position, &player.orientation)
		game.MulticastPacket(buf.Bytes(), player)
	})
}

func (player *Player) PacketPlayerLook(orientation *Orientation, flying bool) {
	player.game.Enqueue(func (game *Game) {
		// TODO input validation
		player.orientation = *orientation

		buf := &bytes.Buffer{}
		WriteEntityLook(buf, player.EntityID, orientation)
		game.MulticastPacket(buf.Bytes(), player)
	})
}

func (player *Player) PacketPlayerDigging(status byte, x int32, y byte, z int32, face byte) {
	log.Stderrf("PacketPlayerDigging status=%d x=%d y=%d z=%d face=%d",
		status, x, y, z, face)
}

func (player *Player) PacketPlayerBlockPlacement(blockItemID int16, x int32, y byte, z int32, direction byte) {
	log.Stderrf("PacketPlayerBlockPlacement blockItemID=%d x=%d y=%d z=%d direction=%d",
		blockItemID, x, y, z, direction)
}

func (player *Player) PacketHoldingChange(blockItemID int16) {
	log.Stderrf("PacketHoldingChange blockItemID=%d", blockItemID)
}

func (player *Player) PacketArmAnimation(forward bool) {
	log.Stderrf("PacketArmAnimation forward=%v", forward)
}

func (player *Player) PacketDisconnect(reason string) {
	log.Stderrf("PacketDisconnect reason=%s", reason)
	player.game.Enqueue(func(game *Game) { game.RemovePlayer(player) })
}

func (player *Player) ReceiveLoop() {
	for {
		err := ReadPacket(player.conn, player)
		if err != nil {
			log.Stderr("ReceiveLoop failed: ", err.String())
			return
		}
	}
}

func (player *Player) TransmitLoop() {
	for {
		bs := <-player.txQueue
		_, err := player.conn.Write(bs)
		if err != nil {
			log.Stderr("TransmitLoop failed: ", err.String())
			return
		}
	}
}

func (player *Player) sendChunks(writer io.Writer) {
	playerX := int32(player.position.x) / ChunkSizeX
	playerZ := int32(player.position.z) / ChunkSizeZ

	for z := playerZ - chunkRadius; z < playerZ + chunkRadius; z++ {
		for x := playerX - chunkRadius; x < playerX + chunkRadius; x++ {
			WritePreChunk(writer, x, z, true)
		}
	}

	for z := playerZ - chunkRadius; z < playerZ + chunkRadius; z++ {
		for x := playerX - chunkRadius; x < playerX + chunkRadius; x++ {
			chunk := player.game.chunkManager.Get(x, z)
			WriteMapChunk(writer, chunk)
		}
	}
}

func (player *Player) TransmitPacket(packet []byte) {
	player.txQueue <- packet
}

func (player *Player) postLogin() {
	buf := &bytes.Buffer{}
	WriteSpawnPosition(buf, &player.position)
	player.sendChunks(buf)
	WritePlayerInventory(buf)
	WritePlayerPositionLook(buf, &player.position, &player.orientation,
		0, false)
	player.TransmitPacket(buf.Bytes())
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package process

import (
	"fmt"
	"os"
	"runtime"
	"syscall"
	"time"

	"github.com/keybase/go-ps"
)

// Log is the logging interface for the process package
type Log interface {
	Debugf(s string, args ...interface{})
	Infof(s string, args ...interface{})
	Warningf(s string, args ...interface{})
	Errorf(s string, args ...interface{})
}

type processesFn func() ([]ps.Process, error)
type breakFn func([]ps.Process) bool

// FindProcesses returns processes containing string matching process path
func FindProcesses(matcher Matcher, wait time.Duration, delay time.Duration, log Log) ([]ps.Process, error) {
	breakFn := func(procs []ps.Process) bool {
		return len(procs) > 0
	}
	return findProcesses(matcher, breakFn, wait, delay, log)
}

// WaitForExit returns processes (if any) that are still running after wait
func WaitForExit(matcher Matcher, wait time.Duration, delay time.Duration, log Log) ([]ps.Process, error) {
	breakFn := func(procs []ps.Process) bool {
		return len(procs) == 0
	}
	return findProcesses(matcher, breakFn, wait, delay, log)
}

func findProcesses(matcher Matcher, breakFn breakFn, wait time.Duration, delay time.Duration, log Log) ([]ps.Process, error) {
	start := time.Now()
	for {
		log.Debugf("Find process %s (%s < %s)", matcher.match, time.Since(start), wait)
		procs, err := findProcessesWithFn(ps.Processes, matcher.Fn(), 0)
		if err != nil {
			return nil, err
		}
		if breakFn(procs) {
			return procs, nil
		}
		if time.Since(start) >= wait {
			break
		}
		time.Sleep(delay)
	}
	return nil, nil
}

// findProcessWithPID returns a process for a pid.
// Consider using os.FindProcess instead if suitable since this may be
// inefficient.
func findProcessWithPID(pid int) (ps.Process, error) {
	matchPID := func(p ps.Process) bool { return p.Pid() == pid }
	procs, err := findProcessesWithFn(ps.Processes, matchPID, 1)
	if err != nil {
		return nil, err
	}
	if len(procs) == 0 {
		return nil, nil
	}
	return procs[0], nil
}

// Currently findProcessWithPID is only used in tests, ignore deadcode warning
var _ = findProcessWithPID

// findProcessesWithFn finds processes using match function.
// If max is != 0, then we will return that max number of processes.
func findProcessesWithFn(fn processesFn, matchFn MatchFn, max int) ([]ps.Process, error) {
	processes, err := fn()
	if err != nil {
		return nil, fmt.Errorf("Error listing processes: %s", err)
	}
	if processes == nil {
		return nil, nil
	}
	procs := []ps.Process{}
	for _, p := range processes {
		if matchFn(p) {
			procs = append(procs, p)
		}
		if max != 0 && len(procs) >= max {
			break
		}
	}
	return procs, nil
}

// FindPIDsWithMatchFn returns pids for processes matching function
func FindPIDsWithMatchFn(matchFn MatchFn, log Log) ([]int, error) {
	return findPIDsWithFn(ps.Processes, matchFn, log)
}

func findPIDsWithFn(fn processesFn, matchFn MatchFn, log Log) ([]int, error) {
	procs, err := findProcessesWithFn(fn, matchFn, 0)
	if err != nil {
		return nil, err
	}
	pids := []int{}
	for _, p := range procs {
		pids = append(pids, p.Pid())
	}
	return pids, nil
}

// TerminateAll stops all processes with executable names that contains the matching string.
// It returns the pids that were terminated.
// This method only logs errors, if you need error handling, you can should use a different implementation.
func TerminateAll(matcher Matcher, killDelay time.Duration, log Log) []int {
	return TerminateAllWithProcessesFn(ps.Processes, matcher.Fn(), killDelay, log)
}

// TerminateAllWithProcessesFn stops processes processesFn that satify the matchFn.
// It returns the pids that were terminated.
// This method only logs errors, if you need error handling, you can should use a different implementation.
func TerminateAllWithProcessesFn(fn processesFn, matchFn MatchFn, killDelay time.Duration, log Log) (pids []int) {
	pids, err := findPIDsWithFn(fn, matchFn, log)
	if err != nil {
		log.Errorf("Error finding process: %s", err)
		return
	}
	if len(pids) == 0 {
		return
	}
	for _, pid := range pids {
		if err := TerminatePID(pid, killDelay, log); err != nil {
			log.Errorf("Error terminating %d: %s", pid, err)
		}
	}
	return
}

// TerminatePID is an overly simple way to terminate a PID.
// On darwin and linux, it calls SIGTERM, then waits a killDelay and then calls
// SIGKILL. We don't mind if we call SIGKILL on an already terminated process,
// since there could be a race anyway where the process exits right after we
// check if it's still running but before the SIGKILL.
// The killDelay is not used on windows.
func TerminatePID(pid int, killDelay time.Duration, log Log) error {
	log.Debugf("Searching OS for %d", pid)
	process, err := os.FindProcess(pid)
	if err != nil {
		return fmt.Errorf("Error finding OS process: %s", err)
	}
	if process == nil {
		return fmt.Errorf("No process found with pid %d", pid)
	}

	// Sending SIGTERM is not supported on windows, so we can use process.Kill()
	if runtime.GOOS == "windows" {
		return process.Kill()
	}

	log.Debugf("Terminating: %#v", process)
	err = process.Signal(syscall.SIGTERM)
	if err != nil {
		log.Warningf("Error sending terminate: %s", err)
	}
	time.Sleep(killDelay)
	// Ignore SIGKILL error since it will be that the process wasn't running if
	// the terminate above succeeded. If terminate didn't succeed above, then
	// this SIGKILL is a measure of last resort, and an error would signify that
	// something in the environment has gone terribly wrong.
	_ = process.Kill()
	return err
}
<commit_msg>Logging around terminate killDelay sleep (#109)<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package process

import (
	"fmt"
	"os"
	"runtime"
	"syscall"
	"time"

	"github.com/keybase/go-ps"
)

// Log is the logging interface for the process package
type Log interface {
	Debugf(s string, args ...interface{})
	Infof(s string, args ...interface{})
	Warningf(s string, args ...interface{})
	Errorf(s string, args ...interface{})
}

type processesFn func() ([]ps.Process, error)
type breakFn func([]ps.Process) bool

// FindProcesses returns processes containing string matching process path
func FindProcesses(matcher Matcher, wait time.Duration, delay time.Duration, log Log) ([]ps.Process, error) {
	breakFn := func(procs []ps.Process) bool {
		return len(procs) > 0
	}
	return findProcesses(matcher, breakFn, wait, delay, log)
}

// WaitForExit returns processes (if any) that are still running after wait
func WaitForExit(matcher Matcher, wait time.Duration, delay time.Duration, log Log) ([]ps.Process, error) {
	breakFn := func(procs []ps.Process) bool {
		return len(procs) == 0
	}
	return findProcesses(matcher, breakFn, wait, delay, log)
}

func findProcesses(matcher Matcher, breakFn breakFn, wait time.Duration, delay time.Duration, log Log) ([]ps.Process, error) {
	start := time.Now()
	for {
		log.Debugf("Find process %s (%s < %s)", matcher.match, time.Since(start), wait)
		procs, err := findProcessesWithFn(ps.Processes, matcher.Fn(), 0)
		if err != nil {
			return nil, err
		}
		if breakFn(procs) {
			return procs, nil
		}
		if time.Since(start) >= wait {
			break
		}
		time.Sleep(delay)
	}
	return nil, nil
}

// findProcessWithPID returns a process for a pid.
// Consider using os.FindProcess instead if suitable since this may be
// inefficient.
func findProcessWithPID(pid int) (ps.Process, error) {
	matchPID := func(p ps.Process) bool { return p.Pid() == pid }
	procs, err := findProcessesWithFn(ps.Processes, matchPID, 1)
	if err != nil {
		return nil, err
	}
	if len(procs) == 0 {
		return nil, nil
	}
	return procs[0], nil
}

// Currently findProcessWithPID is only used in tests, ignore deadcode warning
var _ = findProcessWithPID

// findProcessesWithFn finds processes using match function.
// If max is != 0, then we will return that max number of processes.
func findProcessesWithFn(fn processesFn, matchFn MatchFn, max int) ([]ps.Process, error) {
	processes, err := fn()
	if err != nil {
		return nil, fmt.Errorf("Error listing processes: %s", err)
	}
	if processes == nil {
		return nil, nil
	}
	procs := []ps.Process{}
	for _, p := range processes {
		if matchFn(p) {
			procs = append(procs, p)
		}
		if max != 0 && len(procs) >= max {
			break
		}
	}
	return procs, nil
}

// FindPIDsWithMatchFn returns pids for processes matching function
func FindPIDsWithMatchFn(matchFn MatchFn, log Log) ([]int, error) {
	return findPIDsWithFn(ps.Processes, matchFn, log)
}

func findPIDsWithFn(fn processesFn, matchFn MatchFn, log Log) ([]int, error) {
	procs, err := findProcessesWithFn(fn, matchFn, 0)
	if err != nil {
		return nil, err
	}
	pids := []int{}
	for _, p := range procs {
		pids = append(pids, p.Pid())
	}
	return pids, nil
}

// TerminateAll stops all processes with executable names that contains the matching string.
// It returns the pids that were terminated.
// This method only logs errors, if you need error handling, you can should use a different implementation.
func TerminateAll(matcher Matcher, killDelay time.Duration, log Log) []int {
	return TerminateAllWithProcessesFn(ps.Processes, matcher.Fn(), killDelay, log)
}

// TerminateAllWithProcessesFn stops processes processesFn that satify the matchFn.
// It returns the pids that were terminated.
// This method only logs errors, if you need error handling, you can should use a different implementation.
func TerminateAllWithProcessesFn(fn processesFn, matchFn MatchFn, killDelay time.Duration, log Log) (pids []int) {
	pids, err := findPIDsWithFn(fn, matchFn, log)
	if err != nil {
		log.Errorf("Error finding process: %s", err)
		return
	}
	if len(pids) == 0 {
		return
	}
	for _, pid := range pids {
		if err := TerminatePID(pid, killDelay, log); err != nil {
			log.Errorf("Error terminating %d: %s", pid, err)
		}
	}
	return
}

// TerminatePID is an overly simple way to terminate a PID.
// On darwin and linux, it calls SIGTERM, then waits a killDelay and then calls
// SIGKILL. We don't mind if we call SIGKILL on an already terminated process,
// since there could be a race anyway where the process exits right after we
// check if it's still running but before the SIGKILL.
// The killDelay is not used on windows.
func TerminatePID(pid int, killDelay time.Duration, log Log) error {
	log.Debugf("Searching OS for %d", pid)
	process, err := os.FindProcess(pid)
	if err != nil {
		return fmt.Errorf("Error finding OS process: %s", err)
	}
	if process == nil {
		return fmt.Errorf("No process found with pid %d", pid)
	}

	// Sending SIGTERM is not supported on windows, so we can use process.Kill()
	if runtime.GOOS == "windows" {
		return process.Kill()
	}

	log.Debugf("Terminating: %#v", process)
	err = process.Signal(syscall.SIGTERM)
	if err != nil {
		log.Warningf("Error sending terminate: %s", err)
	}
	log.Debugf("Waiting %s", killDelay)
	time.Sleep(killDelay)
	log.Debugf("Done waiting")
	// Ignore SIGKILL error since it will be that the process wasn't running if
	// the terminate above succeeded. If terminate didn't succeed above, then
	// this SIGKILL is a measure of last resort, and an error would signify that
	// something in the environment has gone terribly wrong.
	_ = process.Kill()
	return err
}
<|endoftext|>


<commit_before>package profile

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"runtime/trace"
	"sync"
	"time"

	"github.com/NebulousLabs/Sia/persist"
)

// There's a global lock on cpu and memory profiling, because I'm not sure what
// happens if multiple threads call each at the same time. This lock might be
// unnecessary.
var (
	cpuActive   bool
	cpuLock     sync.Mutex
	memActive   bool
	memLock     sync.Mutex
	traceActive bool
	traceLock   sync.Mutex
)

// StartCPUProfile starts cpu profiling. An error will be returned if a cpu
// profiler is already running.
func StartCPUProfile(profileDir, identifier string) error {
	// Lock the cpu profile lock so that only one profiler is running at a
	// time.
	cpuLock.Lock()
	if cpuActive {
		cpuLock.Unlock()
		return errors.New("cannot start cpu profiler, a profiler is already running")
	}
	cpuActive = true
	cpuLock.Unlock()

	// Start profiling into the profile dir, using the identifer. The timestamp
	// of the start time of the profiling will be included in the filename.
	cpuProfileFile, err := os.Create(filepath.Join(profileDir, "cpu-profile-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".prof"))
	if err != nil {
		return err
	}
	pprof.StartCPUProfile(cpuProfileFile)
	return nil
}

// StopCPUProfile stops cpu profiling.
func StopCPUProfile() {
	cpuLock.Lock()
	if cpuActive {
		pprof.StopCPUProfile()
		cpuActive = false
	}
	cpuLock.Unlock()
}

// SaveMemProfile saves the current memory structure of the program. An error
// will be returned if memory profiling is already in progress. Unlike for cpu
// profiling, there is no 'stopMemProfile' call - everything happens at once.
func SaveMemProfile(profileDir, identifier string) error {
	memLock.Lock()
	if memActive {
		memLock.Unlock()
		return errors.New("cannot start memory profiler, a memory profiler is already running")
	}
	memActive = true
	memLock.Unlock()

	// Save the memory profile.
	memFile, err := os.Create(filepath.Join(profileDir, "mem-profile-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".prof"))
	if err != nil {
		return err
	}
	pprof.WriteHeapProfile(memFile)

	memLock.Lock()
	memActive = false
	memLock.Unlock()
	return nil
}

// StartTrace starts trace. An error will be returned if a trace
// is already running.
func StartTrace(traceDir, identifier string) error {
	// Lock the trace lock so that only one profiler is running at a
	// time.
	traceLock.Lock()
	if traceActive {
		traceLock.Unlock()
		return errors.New("cannot start trace, it is already running")
	}
	traceActive = true
	traceLock.Unlock()

	// Start trace into the trace dir, using the identifer. The timestamp
	// of the start time of the trace will be included in the filename.
	traceFile, err := os.Create(filepath.Join(traceDir, "trace-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".trace"))
	if err != nil {
		return err
	}
	return trace.Start(traceFile)
}

// StopTrace stops trace.
func StopTrace() {
	traceLock.Lock()
	if traceActive {
		trace.Stop()
		traceActive = false
	}
	traceLock.Unlock()
}

func startContinuousLog(dir string, restart func()) {
	// Create the folder for all of the profiling results.
	err := os.MkdirAll(dir, 0700)
	if err != nil {
		fmt.Println(err)
		return
	}
	// Continuously log statistics about the running Sia application.
	go func() {
		// Create the logger.
		log, err := persist.NewFileLogger(filepath.Join(dir, "continuousStats.log"))
		if err != nil {
			fmt.Println("Stats logging failed:", err)
			return
		}
		// Collect statistics in an infinite loop.
		sleepTime := time.Second * 20
		for {
			// Sleep for an exponential amount of time each iteration, this
			// keeps the size of the log small while still providing lots of
			// information.
			restart()
			time.Sleep(sleepTime)
			sleepTime = time.Duration(1.5 * float64(sleepTime))

			var m runtime.MemStats
			runtime.ReadMemStats(&m)
			log.Printf("\n\tGoroutines: %v\n\tAlloc: %v\n\tTotalAlloc: %v\n\tHeapAlloc: %v\n\tHeapSys: %v\n", runtime.NumGoroutine(), m.Alloc, m.TotalAlloc, m.HeapAlloc, m.HeapSys)
		}
	}()
}

// StartContinuousProfile will continuously print statistics about the cpu
// usage, memory usage, and runtime stats of the program, and run an execution
// logger. Select one (recommended) or more functionalities by passing the
// corresponding flag(s)
func StartContinuousProfile(profileDir string, profileCPU bool, profileMem bool, profileTrace bool) {
	startContinuousLog(profileDir, func() {
		if profileCPU {
			StopCPUProfile()
			StartCPUProfile(profileDir, "continuousProfileCPU")
		}
		if profileMem {
			SaveMemProfile(profileDir, "continuousProfileMem")
		}
		if profileTrace {
			StopTrace()
			StartTrace(profileDir, "continuousProfileTrace")
		}
	})
}
<commit_msg>add docstring to startContinuousLog<commit_after>package profile

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"runtime/trace"
	"sync"
	"time"

	"github.com/NebulousLabs/Sia/persist"
)

// There's a global lock on cpu and memory profiling, because I'm not sure what
// happens if multiple threads call each at the same time. This lock might be
// unnecessary.
var (
	cpuActive   bool
	cpuLock     sync.Mutex
	memActive   bool
	memLock     sync.Mutex
	traceActive bool
	traceLock   sync.Mutex
)

// StartCPUProfile starts cpu profiling. An error will be returned if a cpu
// profiler is already running.
func StartCPUProfile(profileDir, identifier string) error {
	// Lock the cpu profile lock so that only one profiler is running at a
	// time.
	cpuLock.Lock()
	if cpuActive {
		cpuLock.Unlock()
		return errors.New("cannot start cpu profiler, a profiler is already running")
	}
	cpuActive = true
	cpuLock.Unlock()

	// Start profiling into the profile dir, using the identifer. The timestamp
	// of the start time of the profiling will be included in the filename.
	cpuProfileFile, err := os.Create(filepath.Join(profileDir, "cpu-profile-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".prof"))
	if err != nil {
		return err
	}
	pprof.StartCPUProfile(cpuProfileFile)
	return nil
}

// StopCPUProfile stops cpu profiling.
func StopCPUProfile() {
	cpuLock.Lock()
	if cpuActive {
		pprof.StopCPUProfile()
		cpuActive = false
	}
	cpuLock.Unlock()
}

// SaveMemProfile saves the current memory structure of the program. An error
// will be returned if memory profiling is already in progress. Unlike for cpu
// profiling, there is no 'stopMemProfile' call - everything happens at once.
func SaveMemProfile(profileDir, identifier string) error {
	memLock.Lock()
	if memActive {
		memLock.Unlock()
		return errors.New("cannot start memory profiler, a memory profiler is already running")
	}
	memActive = true
	memLock.Unlock()

	// Save the memory profile.
	memFile, err := os.Create(filepath.Join(profileDir, "mem-profile-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".prof"))
	if err != nil {
		return err
	}
	pprof.WriteHeapProfile(memFile)

	memLock.Lock()
	memActive = false
	memLock.Unlock()
	return nil
}

// StartTrace starts trace. An error will be returned if a trace
// is already running.
func StartTrace(traceDir, identifier string) error {
	// Lock the trace lock so that only one profiler is running at a
	// time.
	traceLock.Lock()
	if traceActive {
		traceLock.Unlock()
		return errors.New("cannot start trace, it is already running")
	}
	traceActive = true
	traceLock.Unlock()

	// Start trace into the trace dir, using the identifer. The timestamp
	// of the start time of the trace will be included in the filename.
	traceFile, err := os.Create(filepath.Join(traceDir, "trace-"+identifier+"-"+time.Now().Format(time.RFC3339Nano)+".trace"))
	if err != nil {
		return err
	}
	return trace.Start(traceFile)
}

// StopTrace stops trace.
func StopTrace() {
	traceLock.Lock()
	if traceActive {
		trace.Stop()
		traceActive = false
	}
	traceLock.Unlock()
}

// startContinuousLog creates dir and saves inexpensive logs periodically.
// It also runs the restart function periodically.
func startContinuousLog(dir string, restart func()) {
	// Create the folder for all of the profiling results.
	err := os.MkdirAll(dir, 0700)
	if err != nil {
		fmt.Println(err)
		return
	}
	// Continuously log statistics about the running Sia application.
	go func() {
		// Create the logger.
		log, err := persist.NewFileLogger(filepath.Join(dir, "continuousStats.log"))
		if err != nil {
			fmt.Println("Stats logging failed:", err)
			return
		}
		// Collect statistics in an infinite loop.
		sleepTime := time.Second * 20
		for {
			// Sleep for an exponential amount of time each iteration, this
			// keeps the size of the log small while still providing lots of
			// information.
			restart()
			time.Sleep(sleepTime)
			sleepTime = time.Duration(1.5 * float64(sleepTime))

			var m runtime.MemStats
			runtime.ReadMemStats(&m)
			log.Printf("\n\tGoroutines: %v\n\tAlloc: %v\n\tTotalAlloc: %v\n\tHeapAlloc: %v\n\tHeapSys: %v\n", runtime.NumGoroutine(), m.Alloc, m.TotalAlloc, m.HeapAlloc, m.HeapSys)
		}
	}()
}

// StartContinuousProfile will continuously print statistics about the cpu
// usage, memory usage, and runtime stats of the program, and run an execution
// logger. Select one (recommended) or more functionalities by passing the
// corresponding flag(s)
func StartContinuousProfile(profileDir string, profileCPU bool, profileMem bool, profileTrace bool) {
	startContinuousLog(profileDir, func() {
		if profileCPU {
			StopCPUProfile()
			StartCPUProfile(profileDir, "continuousProfileCPU")
		}
		if profileMem {
			SaveMemProfile(profileDir, "continuousProfileMem")
		}
		if profileTrace {
			StopTrace()
			StartTrace(profileDir, "continuousProfileTrace")
		}
	})
}
<|endoftext|>


<commit_before>// Package profile is for specific profiles
// @todo this package is the definition of cruft and
// should be rewritten in a more elegant way
package profile

import (
	"fmt"
	"os"

	"github.com/micro/micro/v3/service/auth/jwt"
	"github.com/micro/micro/v3/service/auth/noop"
	"github.com/micro/micro/v3/service/broker"
	memBroker "github.com/micro/micro/v3/service/broker/memory"
	"github.com/micro/micro/v3/service/build/golang"
	"github.com/micro/micro/v3/service/client"
	"github.com/micro/micro/v3/service/config"
	storeConfig "github.com/micro/micro/v3/service/config/store"
	evStore "github.com/micro/micro/v3/service/events/store"
	memStream "github.com/micro/micro/v3/service/events/stream/memory"
	"github.com/micro/micro/v3/service/logger"
	"github.com/micro/micro/v3/service/registry"
	"github.com/micro/micro/v3/service/registry/mdns"
	"github.com/micro/micro/v3/service/registry/memory"
	"github.com/micro/micro/v3/service/router"
	k8sRouter "github.com/micro/micro/v3/service/router/kubernetes"
	regRouter "github.com/micro/micro/v3/service/router/registry"
	"github.com/micro/micro/v3/service/runtime/kubernetes"
	"github.com/micro/micro/v3/service/runtime/local"
	"github.com/micro/micro/v3/service/server"
	"github.com/micro/micro/v3/service/store/file"
	mem "github.com/micro/micro/v3/service/store/memory"
	"github.com/urfave/cli/v2"

	inAuth "github.com/micro/micro/v3/internal/auth"
	"github.com/micro/micro/v3/internal/user"
	microAuth "github.com/micro/micro/v3/service/auth"
	microBuilder "github.com/micro/micro/v3/service/build"
	microEvents "github.com/micro/micro/v3/service/events"
	microRegistry "github.com/micro/micro/v3/service/registry"
	microRouter "github.com/micro/micro/v3/service/router"
	microRuntime "github.com/micro/micro/v3/service/runtime"
	microStore "github.com/micro/micro/v3/service/store"
)

// profiles which when called will configure micro to run in that environment
var profiles = map[string]*Profile{
	// built in profiles
	"client":     Client,
	"service":    Service,
	"test":       Test,
	"local":      Local,
	"kubernetes": Kubernetes,
}

// Profile configures an environment
type Profile struct {
	// name of the profile
	Name string
	// function used for setup
	Setup func(*cli.Context) error
	// TODO: presetup dependencies
	// e.g start resources
}

// Register a profile
func Register(name string, p *Profile) error {
	if _, ok := profiles[name]; ok {
		return fmt.Errorf("profile %s already exists", name)
	}
	profiles[name] = p
	return nil
}

// Load a profile
func Load(name string) (*Profile, error) {
	v, ok := profiles[name]
	if !ok {
		return nil, fmt.Errorf("profile %s does not exist", name)
	}
	return v, nil
}

// Client profile is for any entrypoint that behaves as a client
var Client = &Profile{
	Name:  "client",
	Setup: func(ctx *cli.Context) error { return nil },
}

// Local profile to run locally
var Local = &Profile{
	Name: "local",
	Setup: func(ctx *cli.Context) error {
		microAuth.DefaultAuth = jwt.NewAuth()
		microStore.DefaultStore = file.NewStore()
		SetupConfigSecretKey(ctx)
		config.DefaultConfig, _ = storeConfig.NewConfig(microStore.DefaultStore, "")
		SetupBroker(memBroker.NewBroker())
		SetupRegistry(mdns.NewRegistry())
		SetupJWT(ctx)

		// use the local runtime, note: the local runtime is designed to run source code directly so
		// the runtime builder should NOT be set when using this implementation
		microRuntime.DefaultRuntime = local.NewRuntime()

		var err error
		microEvents.DefaultStream, err = memStream.NewStream()
		if err != nil {
			logger.Fatalf("Error configuring stream: %v", err)
		}
		microEvents.DefaultStore = evStore.NewStore(
			evStore.WithStore(microStore.DefaultStore),
		)

		microStore.DefaultBlobStore, err = file.NewBlobStore()
		if err != nil {
			logger.Fatalf("Error configuring file blob store: %v", err)
		}

		return nil
	},
}

// Kubernetes profile to run on kubernetes with zero deps. Designed for use with the micro helm chart
var Kubernetes = &Profile{
	Name: "kubernetes",
	Setup: func(ctx *cli.Context) (err error) {
		microAuth.DefaultAuth = jwt.NewAuth()
		SetupJWT(ctx)

		microRuntime.DefaultRuntime = kubernetes.NewRuntime()
		microBuilder.DefaultBuilder, err = golang.NewBuilder()
		if err != nil {
			logger.Fatalf("Error configuring golang builder: %v", err)
		}

		microEvents.DefaultStream, err = memStream.NewStream()
		if err != nil {
			logger.Fatalf("Error configuring stream: %v", err)
		}

		microStore.DefaultStore = file.NewStore(file.WithDir("/store"))
		microStore.DefaultBlobStore, err = file.NewBlobStore(file.WithDir("/store/blob"))
		if err != nil {
			logger.Fatalf("Error configuring file blob store: %v", err)
		}

		// the registry service uses the memory registry, the other core services will use the default
		// rpc client and call the registry service
		if ctx.Args().Get(1) == "registry" {
			SetupRegistry(memory.NewRegistry())
		}

		// the broker service uses the memory broker, the other core services will use the default
		// rpc client and call the broker service
		if ctx.Args().Get(1) == "broker" {
			SetupBroker(memBroker.NewBroker())
		}

		config.DefaultConfig, err = storeConfig.NewConfig(microStore.DefaultStore, "")
		if err != nil {
			logger.Fatalf("Error configuring config: %v", err)
		}
		SetupConfigSecretKey(ctx)

		microRouter.DefaultRouter = k8sRouter.NewRouter()
		client.DefaultClient.Init(client.Router(microRouter.DefaultRouter))
		return nil
	},
}

// Service is the default for any services run
var Service = &Profile{
	Name:  "service",
	Setup: func(ctx *cli.Context) error { return nil },
}

// Test profile is used for the go test suite
var Test = &Profile{
	Name: "test",
	Setup: func(ctx *cli.Context) error {
		microAuth.DefaultAuth = noop.NewAuth()
		microStore.DefaultStore = mem.NewStore()
		microStore.DefaultBlobStore, _ = file.NewBlobStore()
		config.DefaultConfig, _ = storeConfig.NewConfig(microStore.DefaultStore, "")
		SetupRegistry(memory.NewRegistry())
		return nil
	},
}

// SetupRegistry configures the registry
func SetupRegistry(reg registry.Registry) {
	microRegistry.DefaultRegistry = reg
	microRouter.DefaultRouter = regRouter.NewRouter(router.Registry(reg))
	client.DefaultClient.Init(client.Registry(reg))
	server.DefaultServer.Init(server.Registry(reg))
}

// SetupBroker configures the broker
func SetupBroker(b broker.Broker) {
	broker.DefaultBroker = b
	client.DefaultClient.Init(client.Broker(b))
	server.DefaultServer.Init(server.Broker(b))
}

// SetupJWT configures the default internal system rules
func SetupJWT(ctx *cli.Context) {
	for _, rule := range inAuth.SystemRules {
		if err := microAuth.DefaultAuth.Grant(rule); err != nil {
			logger.Fatal("Error creating default rule: %v", err)
		}
	}
}

func SetupConfigSecretKey(ctx *cli.Context) {
	key := ctx.String("config_secret_key")
	if len(key) == 0 {
		k, err := user.GetConfigSecretKey()
		if err != nil {
			logger.Fatal("Error getting config secret: %v", err)
		}
		os.Setenv("MICRO_CONFIG_SECRET_KEY", k)
	}
}
<commit_msg>profile: use ~/.micro/server for local filestore (#1569)<commit_after>// Package profile is for specific profiles
// @todo this package is the definition of cruft and
// should be rewritten in a more elegant way
package profile

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

	"github.com/micro/micro/v3/service/auth/jwt"
	"github.com/micro/micro/v3/service/auth/noop"
	"github.com/micro/micro/v3/service/broker"
	memBroker "github.com/micro/micro/v3/service/broker/memory"
	"github.com/micro/micro/v3/service/build/golang"
	"github.com/micro/micro/v3/service/client"
	"github.com/micro/micro/v3/service/config"
	storeConfig "github.com/micro/micro/v3/service/config/store"
	evStore "github.com/micro/micro/v3/service/events/store"
	memStream "github.com/micro/micro/v3/service/events/stream/memory"
	"github.com/micro/micro/v3/service/logger"
	"github.com/micro/micro/v3/service/registry"
	"github.com/micro/micro/v3/service/registry/mdns"
	"github.com/micro/micro/v3/service/registry/memory"
	"github.com/micro/micro/v3/service/router"
	k8sRouter "github.com/micro/micro/v3/service/router/kubernetes"
	regRouter "github.com/micro/micro/v3/service/router/registry"
	"github.com/micro/micro/v3/service/runtime/kubernetes"
	"github.com/micro/micro/v3/service/runtime/local"
	"github.com/micro/micro/v3/service/server"
	"github.com/micro/micro/v3/service/store/file"
	mem "github.com/micro/micro/v3/service/store/memory"
	"github.com/urfave/cli/v2"

	inAuth "github.com/micro/micro/v3/internal/auth"
	"github.com/micro/micro/v3/internal/user"
	microAuth "github.com/micro/micro/v3/service/auth"
	microBuilder "github.com/micro/micro/v3/service/build"
	microEvents "github.com/micro/micro/v3/service/events"
	microRegistry "github.com/micro/micro/v3/service/registry"
	microRouter "github.com/micro/micro/v3/service/router"
	microRuntime "github.com/micro/micro/v3/service/runtime"
	microStore "github.com/micro/micro/v3/service/store"
)

// profiles which when called will configure micro to run in that environment
var profiles = map[string]*Profile{
	// built in profiles
	"client":     Client,
	"service":    Service,
	"test":       Test,
	"local":      Local,
	"kubernetes": Kubernetes,
}

// Profile configures an environment
type Profile struct {
	// name of the profile
	Name string
	// function used for setup
	Setup func(*cli.Context) error
	// TODO: presetup dependencies
	// e.g start resources
}

// Register a profile
func Register(name string, p *Profile) error {
	if _, ok := profiles[name]; ok {
		return fmt.Errorf("profile %s already exists", name)
	}
	profiles[name] = p
	return nil
}

// Load a profile
func Load(name string) (*Profile, error) {
	v, ok := profiles[name]
	if !ok {
		return nil, fmt.Errorf("profile %s does not exist", name)
	}
	return v, nil
}

// Client profile is for any entrypoint that behaves as a client
var Client = &Profile{
	Name:  "client",
	Setup: func(ctx *cli.Context) error { return nil },
}

// Local profile to run locally
var Local = &Profile{
	Name: "local",
	Setup: func(ctx *cli.Context) error {
		microAuth.DefaultAuth = jwt.NewAuth()
		microStore.DefaultStore = file.NewStore(file.WithDir(filepath.Join(user.Dir, "server", "store")))
		SetupConfigSecretKey(ctx)
		config.DefaultConfig, _ = storeConfig.NewConfig(microStore.DefaultStore, "")
		SetupBroker(memBroker.NewBroker())
		SetupRegistry(mdns.NewRegistry())
		SetupJWT(ctx)

		// use the local runtime, note: the local runtime is designed to run source code directly so
		// the runtime builder should NOT be set when using this implementation
		microRuntime.DefaultRuntime = local.NewRuntime()

		var err error
		microEvents.DefaultStream, err = memStream.NewStream()
		if err != nil {
			logger.Fatalf("Error configuring stream: %v", err)
		}
		microEvents.DefaultStore = evStore.NewStore(
			evStore.WithStore(microStore.DefaultStore),
		)

		microStore.DefaultBlobStore, err = file.NewBlobStore()
		if err != nil {
			logger.Fatalf("Error configuring file blob store: %v", err)
		}

		return nil
	},
}

// Kubernetes profile to run on kubernetes with zero deps. Designed for use with the micro helm chart
var Kubernetes = &Profile{
	Name: "kubernetes",
	Setup: func(ctx *cli.Context) (err error) {
		microAuth.DefaultAuth = jwt.NewAuth()
		SetupJWT(ctx)

		microRuntime.DefaultRuntime = kubernetes.NewRuntime()
		microBuilder.DefaultBuilder, err = golang.NewBuilder()
		if err != nil {
			logger.Fatalf("Error configuring golang builder: %v", err)
		}

		microEvents.DefaultStream, err = memStream.NewStream()
		if err != nil {
			logger.Fatalf("Error configuring stream: %v", err)
		}

		microStore.DefaultStore = file.NewStore(file.WithDir("/store"))
		microStore.DefaultBlobStore, err = file.NewBlobStore(file.WithDir("/store/blob"))
		if err != nil {
			logger.Fatalf("Error configuring file blob store: %v", err)
		}

		// the registry service uses the memory registry, the other core services will use the default
		// rpc client and call the registry service
		if ctx.Args().Get(1) == "registry" {
			SetupRegistry(memory.NewRegistry())
		}

		// the broker service uses the memory broker, the other core services will use the default
		// rpc client and call the broker service
		if ctx.Args().Get(1) == "broker" {
			SetupBroker(memBroker.NewBroker())
		}

		config.DefaultConfig, err = storeConfig.NewConfig(microStore.DefaultStore, "")
		if err != nil {
			logger.Fatalf("Error configuring config: %v", err)
		}
		SetupConfigSecretKey(ctx)

		microRouter.DefaultRouter = k8sRouter.NewRouter()
		client.DefaultClient.Init(client.Router(microRouter.DefaultRouter))
		return nil
	},
}

// Service is the default for any services run
var Service = &Profile{
	Name:  "service",
	Setup: func(ctx *cli.Context) error { return nil },
}

// Test profile is used for the go test suite
var Test = &Profile{
	Name: "test",
	Setup: func(ctx *cli.Context) error {
		microAuth.DefaultAuth = noop.NewAuth()
		microStore.DefaultStore = mem.NewStore()
		microStore.DefaultBlobStore, _ = file.NewBlobStore()
		config.DefaultConfig, _ = storeConfig.NewConfig(microStore.DefaultStore, "")
		SetupRegistry(memory.NewRegistry())
		return nil
	},
}

// SetupRegistry configures the registry
func SetupRegistry(reg registry.Registry) {
	microRegistry.DefaultRegistry = reg
	microRouter.DefaultRouter = regRouter.NewRouter(router.Registry(reg))
	client.DefaultClient.Init(client.Registry(reg))
	server.DefaultServer.Init(server.Registry(reg))
}

// SetupBroker configures the broker
func SetupBroker(b broker.Broker) {
	broker.DefaultBroker = b
	client.DefaultClient.Init(client.Broker(b))
	server.DefaultServer.Init(server.Broker(b))
}

// SetupJWT configures the default internal system rules
func SetupJWT(ctx *cli.Context) {
	for _, rule := range inAuth.SystemRules {
		if err := microAuth.DefaultAuth.Grant(rule); err != nil {
			logger.Fatal("Error creating default rule: %v", err)
		}
	}
}

func SetupConfigSecretKey(ctx *cli.Context) {
	key := ctx.String("config_secret_key")
	if len(key) == 0 {
		k, err := user.GetConfigSecretKey()
		if err != nil {
			logger.Fatal("Error getting config secret: %v", err)
		}
		os.Setenv("MICRO_CONFIG_SECRET_KEY", k)
	}
}
<|endoftext|>


<commit_before>package utp

/*
#include "utp.h"
*/
import "C"
import (
	"context"
	"errors"
	"net"
	"time"
)

type Socket struct {
	pc            net.PacketConn
	ctx           *C.utp_context
	backlog       chan *Conn
	closed        bool
	conns         map[*C.utp_socket]*Conn
	nonUtpReads   chan packet
	writeDeadline time.Time
	readDeadline  time.Time
}

var (
	_ net.PacketConn = (*Socket)(nil)
)

type packet struct {
	b    []byte
	from net.Addr
}

func NewSocket(network, addr string) (*Socket, error) {
	pc, err := net.ListenPacket(network, addr)
	if err != nil {
		return nil, err
	}
	mu.Lock()
	defer mu.Unlock()
	ctx := C.utp_init(2)
	if ctx == nil {
		panic(ctx)
	}
	ctx.setCallbacks()
	if utpLogging {
		ctx.setOption(C.UTP_LOG_NORMAL, 1)
		ctx.setOption(C.UTP_LOG_MTU, 1)
		ctx.setOption(C.UTP_LOG_DEBUG, 1)
	}
	s := &Socket{
		pc:          pc,
		ctx:         ctx,
		backlog:     make(chan *Conn, 5),
		conns:       make(map[*C.utp_socket]*Conn),
		nonUtpReads: make(chan packet, 100),
	}
	libContextToSocket[ctx] = s
	go s.timeoutChecker()
	go s.packetReader()
	return s, nil
}

func (s *Socket) onLibSocketDestroyed(ls *C.utp_socket) {
	delete(s.conns, ls)
}

func (s *Socket) newConn(us *C.utp_socket) *Conn {
	c := &Conn{
		s: us,
	}
	c.cond.L = &mu
	s.conns[us] = c
	c.writeDeadlineTimer = time.AfterFunc(-1, c.cond.Broadcast)
	c.readDeadlineTimer = time.AfterFunc(-1, c.cond.Broadcast)
	return c
}

var reads int

func (s *Socket) packetReader() {
	var b [0x1000]byte
	for {
		n, addr, err := s.pc.ReadFrom(b[:])
		if err != nil {
			mu.Lock()
			closed := s.closed
			mu.Unlock()
			if closed {
				return
			}
			panic(err)
		}
		sa, sal := netAddrToLibSockaddr(addr)
		func() {
			mu.Lock()
			defer mu.Unlock()
			reads++
			// log.Printf("received %d bytes, %d packets", n, reads)
			if s.closed {
				return
			}
			if C.utp_process_udp(s.ctx, (*C.byte)(&b[0]), C.size_t(n), sa, sal) != 0 {
				socketUtpPacketsReceived.Add(1)
			} else {
				s.onReadNonUtp(b[:n], addr)
			}
			C.utp_issue_deferred_acks(s.ctx)
			C.utp_check_timeouts(s.ctx)
		}()
	}
}

func (s *Socket) timeoutChecker() {
	for {
		mu.Lock()
		if s.closed {
			mu.Unlock()
			return
		}
		// C.utp_issue_deferred_acks(s.ctx)
		C.utp_check_timeouts(s.ctx)
		mu.Unlock()
		time.Sleep(500 * time.Millisecond)
	}
}

func (me *Socket) Close() error {
	mu.Lock()
	defer mu.Unlock()
	if me.closed {
		return nil
	}
	C.utp_destroy(me.ctx)
	me.ctx = nil
	me.pc.Close()
	close(me.backlog)
	close(me.nonUtpReads)
	me.closed = true
	return nil
}

func (me *Socket) Addr() net.Addr {
	return me.pc.LocalAddr()
}

func (me *Socket) LocalAddr() net.Addr {
	return me.pc.LocalAddr()
}

func (s *Socket) Accept() (net.Conn, error) {
	nc, ok := <-s.backlog
	if !ok {
		return nil, errors.New("closed")
	}
	return nc, nil
}

func (s *Socket) Dial(addr string) (net.Conn, error) {
	return s.DialTimeout(addr, 0)
}

func (s *Socket) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
	ctx := context.Background()
	if timeout != 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}
	return s.DialContext(ctx, addr)
}

func (s *Socket) DialContext(ctx context.Context, addr string) (net.Conn, error) {
	ua, err := net.ResolveUDPAddr(s.Addr().Network(), addr)
	if err != nil {
		panic(err)
	}
	sa, sl := netAddrToLibSockaddr(ua)
	mu.Lock()
	c := s.newConn(C.utp_create_socket(s.ctx))
	C.utp_connect(c.s, sa, sl)
	defer mu.Unlock()
	err = c.waitForConnect(ctx)
	if err != nil {
		c.close()
		return nil, err
	}
	return c, nil
}

func (s *Socket) pushBacklog(c *Conn) {
	select {
	case s.backlog <- c:
	default:
		c.close()
	}
}

func (s *Socket) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
	p, ok := <-s.nonUtpReads
	if !ok {
		err = errors.New("closed")
		return
	}
	n = copy(b, p.b)
	addr = p.from
	return
}

func (s *Socket) onReadNonUtp(b []byte, from net.Addr) {
	socketNonUtpPacketsReceived.Add(1)
	select {
	case s.nonUtpReads <- packet{b, from}:
	default:
		// log.Printf("dropped non utp packet: no room in buffer")
		nonUtpPacketsDropped.Add(1)
	}
}

func (s *Socket) SetReadDeadline(t time.Time) error {
	return errors.New("not implemented")
}

func (s *Socket) SetWriteDeadline(t time.Time) error {
	return errors.New("not implemented")
}

func (s *Socket) SetDeadline(t time.Time) error {
	return errors.New("not implemented")
}

func (s *Socket) WriteTo(b []byte, addr net.Addr) (int, error) {
	return s.pc.WriteTo(b, addr)
}

func (s *Socket) ReadBufferLen() int {
	mu.Lock()
	defer mu.Unlock()
	return int(C.utp_context_get_option(s.ctx, C.UTP_RCVBUF))
}

func (s *Socket) WriteBufferLen() int {
	mu.Lock()
	defer mu.Unlock()
	return int(C.utp_context_get_option(s.ctx, C.UTP_SNDBUF))
}

func (s *Socket) SetWriteBufferLen(len int) {
	mu.Lock()
	defer mu.Unlock()
	i := C.utp_context_set_option(s.ctx, C.UTP_SNDBUF, C.int(len))
	if i != 0 {
		panic(i)
	}
}
<commit_msg>Panic if you try to use deadline functions on Socket<commit_after>package utp

/*
#include "utp.h"
*/
import "C"
import (
	"context"
	"errors"
	"net"
	"time"
)

type Socket struct {
	pc            net.PacketConn
	ctx           *C.utp_context
	backlog       chan *Conn
	closed        bool
	conns         map[*C.utp_socket]*Conn
	nonUtpReads   chan packet
	writeDeadline time.Time
	readDeadline  time.Time
}

var (
	_ net.PacketConn = (*Socket)(nil)
)

type packet struct {
	b    []byte
	from net.Addr
}

func NewSocket(network, addr string) (*Socket, error) {
	pc, err := net.ListenPacket(network, addr)
	if err != nil {
		return nil, err
	}
	mu.Lock()
	defer mu.Unlock()
	ctx := C.utp_init(2)
	if ctx == nil {
		panic(ctx)
	}
	ctx.setCallbacks()
	if utpLogging {
		ctx.setOption(C.UTP_LOG_NORMAL, 1)
		ctx.setOption(C.UTP_LOG_MTU, 1)
		ctx.setOption(C.UTP_LOG_DEBUG, 1)
	}
	s := &Socket{
		pc:          pc,
		ctx:         ctx,
		backlog:     make(chan *Conn, 5),
		conns:       make(map[*C.utp_socket]*Conn),
		nonUtpReads: make(chan packet, 100),
	}
	libContextToSocket[ctx] = s
	go s.timeoutChecker()
	go s.packetReader()
	return s, nil
}

func (s *Socket) onLibSocketDestroyed(ls *C.utp_socket) {
	delete(s.conns, ls)
}

func (s *Socket) newConn(us *C.utp_socket) *Conn {
	c := &Conn{
		s: us,
	}
	c.cond.L = &mu
	s.conns[us] = c
	c.writeDeadlineTimer = time.AfterFunc(-1, c.cond.Broadcast)
	c.readDeadlineTimer = time.AfterFunc(-1, c.cond.Broadcast)
	return c
}

var reads int

func (s *Socket) packetReader() {
	var b [0x1000]byte
	for {
		n, addr, err := s.pc.ReadFrom(b[:])
		if err != nil {
			mu.Lock()
			closed := s.closed
			mu.Unlock()
			if closed {
				return
			}
			panic(err)
		}
		sa, sal := netAddrToLibSockaddr(addr)
		func() {
			mu.Lock()
			defer mu.Unlock()
			reads++
			// log.Printf("received %d bytes, %d packets", n, reads)
			if s.closed {
				return
			}
			if C.utp_process_udp(s.ctx, (*C.byte)(&b[0]), C.size_t(n), sa, sal) != 0 {
				socketUtpPacketsReceived.Add(1)
			} else {
				s.onReadNonUtp(b[:n], addr)
			}
			C.utp_issue_deferred_acks(s.ctx)
			C.utp_check_timeouts(s.ctx)
		}()
	}
}

func (s *Socket) timeoutChecker() {
	for {
		mu.Lock()
		if s.closed {
			mu.Unlock()
			return
		}
		// C.utp_issue_deferred_acks(s.ctx)
		C.utp_check_timeouts(s.ctx)
		mu.Unlock()
		time.Sleep(500 * time.Millisecond)
	}
}

func (me *Socket) Close() error {
	mu.Lock()
	defer mu.Unlock()
	if me.closed {
		return nil
	}
	C.utp_destroy(me.ctx)
	me.ctx = nil
	me.pc.Close()
	close(me.backlog)
	close(me.nonUtpReads)
	me.closed = true
	return nil
}

func (me *Socket) Addr() net.Addr {
	return me.pc.LocalAddr()
}

func (me *Socket) LocalAddr() net.Addr {
	return me.pc.LocalAddr()
}

func (s *Socket) Accept() (net.Conn, error) {
	nc, ok := <-s.backlog
	if !ok {
		return nil, errors.New("closed")
	}
	return nc, nil
}

func (s *Socket) Dial(addr string) (net.Conn, error) {
	return s.DialTimeout(addr, 0)
}

func (s *Socket) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
	ctx := context.Background()
	if timeout != 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}
	return s.DialContext(ctx, addr)
}

func (s *Socket) DialContext(ctx context.Context, addr string) (net.Conn, error) {
	ua, err := net.ResolveUDPAddr(s.Addr().Network(), addr)
	if err != nil {
		panic(err)
	}
	sa, sl := netAddrToLibSockaddr(ua)
	mu.Lock()
	c := s.newConn(C.utp_create_socket(s.ctx))
	C.utp_connect(c.s, sa, sl)
	defer mu.Unlock()
	err = c.waitForConnect(ctx)
	if err != nil {
		c.close()
		return nil, err
	}
	return c, nil
}

func (s *Socket) pushBacklog(c *Conn) {
	select {
	case s.backlog <- c:
	default:
		c.close()
	}
}

func (s *Socket) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
	p, ok := <-s.nonUtpReads
	if !ok {
		err = errors.New("closed")
		return
	}
	n = copy(b, p.b)
	addr = p.from
	return
}

func (s *Socket) onReadNonUtp(b []byte, from net.Addr) {
	socketNonUtpPacketsReceived.Add(1)
	select {
	case s.nonUtpReads <- packet{b, from}:
	default:
		// log.Printf("dropped non utp packet: no room in buffer")
		nonUtpPacketsDropped.Add(1)
	}
}

func (s *Socket) SetReadDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) SetWriteDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) SetDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) WriteTo(b []byte, addr net.Addr) (int, error) {
	return s.pc.WriteTo(b, addr)
}

func (s *Socket) ReadBufferLen() int {
	mu.Lock()
	defer mu.Unlock()
	return int(C.utp_context_get_option(s.ctx, C.UTP_RCVBUF))
}

func (s *Socket) WriteBufferLen() int {
	mu.Lock()
	defer mu.Unlock()
	return int(C.utp_context_get_option(s.ctx, C.UTP_SNDBUF))
}

func (s *Socket) SetWriteBufferLen(len int) {
	mu.Lock()
	defer mu.Unlock()
	i := C.utp_context_set_option(s.ctx, C.UTP_SNDBUF, C.int(len))
	if i != 0 {
		panic(i)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state_test

import (
	"bytes"
	"strings"
	"time"

	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	charmresource "gopkg.in/juju/charm.v6-unstable/resource"

	"github.com/juju/juju/component/all"
	"github.com/juju/juju/resource"
)

func init() {
	if err := all.RegisterForServer(); err != nil {
		panic(err)
	}
}

var _ = gc.Suite(&ResourcesSuite{})

type ResourcesSuite struct {
	ConnSuite
}

func (s *ResourcesSuite) TestFunctional(c *gc.C) {
	st, err := s.State.Resources()
	c.Assert(err, jc.ErrorIsNil)

	resources, err := st.ListResources("a-service")
	c.Assert(err, jc.ErrorIsNil)

	data := "spamspamspam"
	res := newResource(c, "spam", data)
	file := bytes.NewBufferString(data)

	_, err = st.SetResource("a-service", "a-user", res.Resource, file)
	c.Assert(err, jc.ErrorIsNil)

	resources, err = st.ListResources("a-service")
	c.Assert(err, jc.ErrorIsNil)

	res.Timestamp = resources.Resources[0].Timestamp
	c.Check(resources, jc.DeepEquals, resource.ServiceResources{Resources: []resource.Resource{
		res,
	}})

	// TODO(ericsnow) Add more as state.Resources grows more functionality.
}

func newResource(c *gc.C, name, data string) resource.Resource {
	fp, err := charmresource.GenerateFingerprint(strings.NewReader(data))
	c.Assert(err, jc.ErrorIsNil)

	res := resource.Resource{
		Resource: charmresource.Resource{
			Meta: charmresource.Meta{
				Name:    name,
				Type:    charmresource.TypeFile,
				Path:    name + ".tgz",
				Comment: "you need it",
			},
			Origin:      charmresource.OriginUpload,
			Revision:    1,
			Fingerprint: fp,
			Size:        int64(len(data)),
		},
		Username:  "a-user",
		Timestamp: time.Unix(time.Now().UTC().Unix(), 0),
	}
	err = res.Validate()
	c.Assert(err, jc.ErrorIsNil)
	return res
}
<commit_msg>Use resourcetesting.NewResource() in the state tests.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state_test

import (
	"bytes"
	"time"

	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/component/all"
	"github.com/juju/juju/resource"
	"github.com/juju/juju/resource/resourcetesting"
)

func init() {
	if err := all.RegisterForServer(); err != nil {
		panic(err)
	}
}

var _ = gc.Suite(&ResourcesSuite{})

type ResourcesSuite struct {
	ConnSuite
}

func (s *ResourcesSuite) TestFunctional(c *gc.C) {
	st, err := s.State.Resources()
	c.Assert(err, jc.ErrorIsNil)

	resources, err := st.ListResources("a-service")
	c.Assert(err, jc.ErrorIsNil)

	data := "spamspamspam"
	res := newResource(c, "spam", data)
	file := bytes.NewBufferString(data)

	_, err = st.SetResource("a-service", res.Username, res.Resource, file)
	c.Assert(err, jc.ErrorIsNil)

	resources, err = st.ListResources("a-service")
	c.Assert(err, jc.ErrorIsNil)

	res.Timestamp = resources.Resources[0].Timestamp
	c.Check(resources, jc.DeepEquals, resource.ServiceResources{Resources: []resource.Resource{
		res,
	}})

	// TODO(ericsnow) Add more as state.Resources grows more functionality.
}

func newResource(c *gc.C, name, data string) resource.Resource {
	opened := resourcetesting.NewResource(c, nil, name, "a-service", data)
	res := opened.Resource
	res.Timestamp = time.Unix(res.Timestamp.Unix(), 0)
	return res
}
<|endoftext|>


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

package main

import (
	"gopkg.in/goyy/goyy.v0/comm/i18n"
)

var i18N = i18n.New(locales, i18n.Locale_en_US)

var locales = map[string]map[string]string{
	i18n.Locale_zh_CN: zh_CN,
	i18n.Locale_en_US: en_US,
}

var zh_CN = map[string]string{
	"setting.empty":                     xsettings + ":configuration.settings:不能为空！",
	"setting.statement.empty":           xsettings + ":configuration.settings.statement:不能为空！",
	"setting.statement.seperator.empty": xsettings + ":configuration.settings.statement.@seperator:不能为空！",
	"setting.statement.case.empty":      xsettings + ":configuration.settings.statement.@case:不能为空！",
	"setting.statement.comment.empty":   xsettings + ":configuration.settings.statement.@comment:不能为空！",
	"project.empty":                     xprojects + ":configuration.projects:不能为空！",
	"project.project.empty":             xprojects + ":configuration.projects.project:不能为空！",
	"project.id.empty":                  xprojects + ":configuration.projects.project.@id:不能为空！",
	"project.database.empty":            xprojects + ":configuration.projects.project.@database:不能为空！",
	"module.empty":                      xmodules + ":configuration.modules:不能为空！",
	"module.module.empty":               xmodules + ":configuration.modules.module:不能为空！",
	"module.id.empty":                   xmodules + ":configuration.modules.module.@id:不能为空！",
	"module.project.empty":              xmodules + ":configuration.modules.module.@project:不能为空！",
	"module.project.errorf":             xmodules + ":configuration.modules.module.@project=%s:对应的项目不存在！",
	"domain.title":                      "实体结构",
	"domain.empty":                      xdomains + ":configuration.domains:不能为空！",
	"domain.domain.empty":               xdomains + ":configuration.domains.domain:不能为空！",
	"domain.id.empty":                   xdomains + ":configuration.domains.domain.@id:不能为空！",
	"domain.types.empty":                xdomains + ":configuration.domains.domain.@types:不能为空！",
	"domain.length.emptyf":              xdomains + ":configuration.domains.domain[@id=%s].@length:不能为空！",
	"domain.precision.emptyf":           xdomains + ":configuration.domains.domain[@id=%s].@precision:不能为空！",
	"column.empty":                      xcolumns + ":configuration.columns:不能为空！",
	"column.column.empty":               xcolumns + ":configuration.columns.column:不能为空！",
	"column.id.empty":                   xcolumns + ":configuration.columns.column.@id:不能为空！",
	"column.domain.empty":               xcolumns + ":configuration.columns.column.@domain:不能为空！",
	"column.domain.errorf":              xcolumns + ":configuration.columns.column.@domain=%s:对应的域不存在！",
	"table.emptyf":                      "%s:configuration.tables:不能为空！",
	"table.table.emptyf":                "%s:configuration.tables.table:不能为空！",
	"table.id.empty":                    xtables + ":configuration.tables.table.@id:不能为空！",
	"table.id.emptyf":                   "%s:configuration.tables.table.@id:不能为空！",
	"table.extends.errorf":              "%s:configuration.tables.table[@id=%s].@extends=%s:对应的表不存在！",
	"table.column.id.emptyf":            "%s:configuration.tables.table[@id=%s].column.@id:不能为空！",
	"table.column.extends.errorf":       "%s:configuration.tables.table[@id=%s].column.@extends=%s:对应的列不存在！",
	"table.column.domain.emptyf":        "%s:configuration.tables.table[@id=%s].column.@domain:不能为空！",
	"table.column.domain.errorf":        "%s:configuration.tables.table[@id=%s].column.@domain=%s:对应的域不存在！",
}

var en_US = map[string]string{
	"setting.empty":                     xsettings + ":configuration.settings:can not be empty!",
	"setting.statement.empty":           xsettings + ":configuration.settings.statement:can not be empty!",
	"setting.statement.seperator.empty": xsettings + ":configuration.settings.statement.@seperator:can not be empty!",
	"setting.statement.case.empty":      xsettings + ":configuration.settings.statement.@case:can not be empty!",
	"setting.statement.comment.empty":   xsettings + ":configuration.settings.statement.@comment:can not be empty!",
	"project.empty":                     xprojects + ":configuration.projects:can not be empty!",
	"project.project.empty":             xprojects + ":configuration.projects.project:can not be empty!",
	"project.id.empty":                  xprojects + ":configuration.projects.project.@id:can not be empty!",
	"project.database.empty":            xprojects + ":configuration.projects.project.@database:can not be empty!",
	"module.empty":                      xmodules + ":configuration.modules:can not be empty!",
	"module.module.empty":               xmodules + ":configuration.modules.module:can not be empty!",
	"module.id.empty":                   xmodules + ":configuration.modules.module.@id:can not be empty!",
	"module.project.empty":              xmodules + ":configuration.modules.module.@project:can not be empty!",
	"module.project.errorf":             xmodules + ":configuration.modules.module.@project=%s:match the project does not exist!",
	"domain.title":                      " Entity",
	"domain.empty":                      xdomains + ":configuration.domains:can not be empty!",
	"domain.domain.empty":               xdomains + ":configuration.domains.domain:can not be empty!",
	"domain.id.empty":                   xdomains + ":configuration.domains.domain.@id:can not be empty!",
	"domain.types.empty":                xdomains + ":configuration.domains.domain.@types:can not be empty!",
	"domain.length.emptyf":              xdomains + ":configuration.domains.domain[@id=%s].@length:can not be empty!",
	"domain.precision.emptyf":           xdomains + ":configuration.domains.domain[@id=%s].@precision:can not be empty!",
	"column.empty":                      xcolumns + ":configuration.columns:can not be empty!",
	"column.column.empty":               xcolumns + ":configuration.columns.column:can not be empty!",
	"column.id.empty":                   xcolumns + ":configuration.columns.column.@id:can not be empty!",
	"column.domain.empty":               xcolumns + ":configuration.columns.column.@domain:can not be empty!",
	"column.domain.errorf":              xcolumns + ":configuration.columns.column.@domain=%s:match the domain does not exist!",
	"table.emptyf":                      "%s:configuration.tables:can not be empty!",
	"table.table.emptyf":                "%s:configuration.tables.table:can not be empty!",
	"table.id.empty":                    xtables + ":configuration.tables.table.@id:can not be empty!",
	"table.id.emptyf":                   "%s:configuration.tables.table.@id:can not be empty!",
	"table.extends.errorf":              "%s:configuration.tables.table[@id=%s].@extends=%s:match the table does not exist!",
	"table.column.id.emptyf":            "%s:configuration.tables.table[@id=%s].column.@id:can not be empty!",
	"table.column.extends.errorf":       "%s:configuration.tables.table[@id=%s].column.@extends=%s:match the column does not exist!",
	"table.column.domain.emptyf":        "%s:configuration.tables.table[@id=%s].column.@domain:can not be empty!",
	"table.column.domain.errorf":        "%s:configuration.tables.table[@id=%s].column.@domain=%s:match the domain does not exist!",
}
<commit_msg>Add support for default locale<commit_after>// Copyright 2014 The goyy Authors.  All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package main

import (
	"gopkg.in/goyy/goyy.v0/comm/i18n"
)

var i18N = i18n.NewByEnv(locales)

var locales = map[string]map[string]string{
	i18n.Locale_zh_CN: map[string]string{
		"setting.empty":                     xsettings + ":configuration.settings:不能为空！",
		"setting.statement.empty":           xsettings + ":configuration.settings.statement:不能为空！",
		"setting.statement.seperator.empty": xsettings + ":configuration.settings.statement.@seperator:不能为空！",
		"setting.statement.case.empty":      xsettings + ":configuration.settings.statement.@case:不能为空！",
		"setting.statement.comment.empty":   xsettings + ":configuration.settings.statement.@comment:不能为空！",
		"project.empty":                     xprojects + ":configuration.projects:不能为空！",
		"project.project.empty":             xprojects + ":configuration.projects.project:不能为空！",
		"project.id.empty":                  xprojects + ":configuration.projects.project.@id:不能为空！",
		"project.database.empty":            xprojects + ":configuration.projects.project.@database:不能为空！",
		"module.empty":                      xmodules + ":configuration.modules:不能为空！",
		"module.module.empty":               xmodules + ":configuration.modules.module:不能为空！",
		"module.id.empty":                   xmodules + ":configuration.modules.module.@id:不能为空！",
		"module.project.empty":              xmodules + ":configuration.modules.module.@project:不能为空！",
		"module.project.errorf":             xmodules + ":configuration.modules.module.@project=%s:对应的项目不存在！",
		"domain.title":                      "实体结构",
		"domain.empty":                      xdomains + ":configuration.domains:不能为空！",
		"domain.domain.empty":               xdomains + ":configuration.domains.domain:不能为空！",
		"domain.id.empty":                   xdomains + ":configuration.domains.domain.@id:不能为空！",
		"domain.types.empty":                xdomains + ":configuration.domains.domain.@types:不能为空！",
		"domain.length.emptyf":              xdomains + ":configuration.domains.domain[@id=%s].@length:不能为空！",
		"domain.precision.emptyf":           xdomains + ":configuration.domains.domain[@id=%s].@precision:不能为空！",
		"column.empty":                      xcolumns + ":configuration.columns:不能为空！",
		"column.column.empty":               xcolumns + ":configuration.columns.column:不能为空！",
		"column.id.empty":                   xcolumns + ":configuration.columns.column.@id:不能为空！",
		"column.domain.empty":               xcolumns + ":configuration.columns.column.@domain:不能为空！",
		"column.domain.errorf":              xcolumns + ":configuration.columns.column.@domain=%s:对应的域不存在！",
		"table.emptyf":                      "%s:configuration.tables:不能为空！",
		"table.table.emptyf":                "%s:configuration.tables.table:不能为空！",
		"table.id.empty":                    xtables + ":configuration.tables.table.@id:不能为空！",
		"table.id.emptyf":                   "%s:configuration.tables.table.@id:不能为空！",
		"table.extends.errorf":              "%s:configuration.tables.table[@id=%s].@extends=%s:对应的表不存在！",
		"table.column.id.emptyf":            "%s:configuration.tables.table[@id=%s].column.@id:不能为空！",
		"table.column.extends.errorf":       "%s:configuration.tables.table[@id=%s].column.@extends=%s:对应的列不存在！",
		"table.column.domain.emptyf":        "%s:configuration.tables.table[@id=%s].column.@domain:不能为空！",
		"table.column.domain.errorf":        "%s:configuration.tables.table[@id=%s].column.@domain=%s:对应的域不存在！",
	},
	i18n.Locale_en_US: map[string]string{
		"setting.empty":                     xsettings + ":configuration.settings:can not be empty!",
		"setting.statement.empty":           xsettings + ":configuration.settings.statement:can not be empty!",
		"setting.statement.seperator.empty": xsettings + ":configuration.settings.statement.@seperator:can not be empty!",
		"setting.statement.case.empty":      xsettings + ":configuration.settings.statement.@case:can not be empty!",
		"setting.statement.comment.empty":   xsettings + ":configuration.settings.statement.@comment:can not be empty!",
		"project.empty":                     xprojects + ":configuration.projects:can not be empty!",
		"project.project.empty":             xprojects + ":configuration.projects.project:can not be empty!",
		"project.id.empty":                  xprojects + ":configuration.projects.project.@id:can not be empty!",
		"project.database.empty":            xprojects + ":configuration.projects.project.@database:can not be empty!",
		"module.empty":                      xmodules + ":configuration.modules:can not be empty!",
		"module.module.empty":               xmodules + ":configuration.modules.module:can not be empty!",
		"module.id.empty":                   xmodules + ":configuration.modules.module.@id:can not be empty!",
		"module.project.empty":              xmodules + ":configuration.modules.module.@project:can not be empty!",
		"module.project.errorf":             xmodules + ":configuration.modules.module.@project=%s:match the project does not exist!",
		"domain.title":                      " Entity",
		"domain.empty":                      xdomains + ":configuration.domains:can not be empty!",
		"domain.domain.empty":               xdomains + ":configuration.domains.domain:can not be empty!",
		"domain.id.empty":                   xdomains + ":configuration.domains.domain.@id:can not be empty!",
		"domain.types.empty":                xdomains + ":configuration.domains.domain.@types:can not be empty!",
		"domain.length.emptyf":              xdomains + ":configuration.domains.domain[@id=%s].@length:can not be empty!",
		"domain.precision.emptyf":           xdomains + ":configuration.domains.domain[@id=%s].@precision:can not be empty!",
		"column.empty":                      xcolumns + ":configuration.columns:can not be empty!",
		"column.column.empty":               xcolumns + ":configuration.columns.column:can not be empty!",
		"column.id.empty":                   xcolumns + ":configuration.columns.column.@id:can not be empty!",
		"column.domain.empty":               xcolumns + ":configuration.columns.column.@domain:can not be empty!",
		"column.domain.errorf":              xcolumns + ":configuration.columns.column.@domain=%s:match the domain does not exist!",
		"table.emptyf":                      "%s:configuration.tables:can not be empty!",
		"table.table.emptyf":                "%s:configuration.tables.table:can not be empty!",
		"table.id.empty":                    xtables + ":configuration.tables.table.@id:can not be empty!",
		"table.id.emptyf":                   "%s:configuration.tables.table.@id:can not be empty!",
		"table.extends.errorf":              "%s:configuration.tables.table[@id=%s].@extends=%s:match the table does not exist!",
		"table.column.id.emptyf":            "%s:configuration.tables.table[@id=%s].column.@id:can not be empty!",
		"table.column.extends.errorf":       "%s:configuration.tables.table[@id=%s].column.@extends=%s:match the column does not exist!",
		"table.column.domain.emptyf":        "%s:configuration.tables.table[@id=%s].column.@domain:can not be empty!",
		"table.column.domain.errorf":        "%s:configuration.tables.table[@id=%s].column.@domain=%s:match the domain does not exist!",
	},
}
<|endoftext|>


<commit_before>package httplog

import (
	"fmt"
	"github.com/dustin/go-humanize"
	"github.com/miolini/datacounter"
	"io"
	"net/http"
	"os"
	"time"
)

// FormatterFunc user defined formatter func
type FormatterFunc func(w io.Writer, r *http.Request, sentBytes uint64, elapsedTime time.Duration)

// DefaultFormatter default formatter func
var DefaultFormatter FormatterFunc = func(w io.Writer, r *http.Request, sentBytes uint64, elapsedTime time.Duration) {
	fmt.Fprintf(w, "%s [%.2fms %s] %s %s\n", time.Now().Format("2006-01-02 15:04:05"),
		elapsedTime.Seconds()*1000, humanize.Bytes(sentBytes), r.Method, r.URL.Path)
}

// Logger simple func for wrapping http.Handler and log to stderr
func Logger(h http.Handler) http.Handler {
	return LoggerWithWriterAndFormatter(h, os.Stderr, DefaultFormatter)
}

// LoggerWithFormatter wrapping func with user defined formatter
func LoggerWithFormatter(h http.Handler, formatter FormatterFunc) http.Handler {
	return LoggerWithWriterAndFormatter(h, os.Stderr, formatter)
}

// LoggerWithWriter func for wrapping http.Handler and user defined output io.Writer
func LoggerWithWriter(h http.Handler, w io.Writer) http.Handler {
	return LoggerWithWriterAndFormatter(h, w, DefaultFormatter)
}

// LoggerWithWriterAndFormatter func for wrapping http.Handler and user defined output io.Writer and FormatterFunc
func LoggerWithWriterAndFormatter(h http.Handler, lw io.Writer, formatter FormatterFunc) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		wcounter := datacounter.NewResponseWriterCounter(w)
		defer func(t time.Time) {
			formatter(lw, r, wcounter.Count(), time.Since(t))
		}(time.Now())
		h.ServeHTTP(wcounter, r)
	})
}
<commit_msg>change time layout<commit_after>package httplog

import (
	"fmt"
	"github.com/dustin/go-humanize"
	"github.com/miolini/datacounter"
	"io"
	"net/http"
	"os"
	"time"
)

// FormatterFunc user defined formatter func
type FormatterFunc func(w io.Writer, r *http.Request, sentBytes uint64, elapsedTime time.Duration)

// DefaultFormatter default formatter func
var DefaultFormatter FormatterFunc = func(w io.Writer, r *http.Request, sentBytes uint64, elapsedTime time.Duration) {
	fmt.Fprintf(w, "%s [%.2fms %s] %s %s\n", time.Now().Format("2006/01/02 15:04:05"),
		elapsedTime.Seconds()*1000, humanize.Bytes(sentBytes), r.Method, r.URL.Path)
}

// Logger simple func for wrapping http.Handler and log to stderr
func Logger(h http.Handler) http.Handler {
	return LoggerWithWriterAndFormatter(h, os.Stderr, DefaultFormatter)
}

// LoggerWithFormatter wrapping func with user defined formatter
func LoggerWithFormatter(h http.Handler, formatter FormatterFunc) http.Handler {
	return LoggerWithWriterAndFormatter(h, os.Stderr, formatter)
}

// LoggerWithWriter func for wrapping http.Handler and user defined output io.Writer
func LoggerWithWriter(h http.Handler, w io.Writer) http.Handler {
	return LoggerWithWriterAndFormatter(h, w, DefaultFormatter)
}

// LoggerWithWriterAndFormatter func for wrapping http.Handler and user defined output io.Writer and FormatterFunc
func LoggerWithWriterAndFormatter(h http.Handler, lw io.Writer, formatter FormatterFunc) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		wcounter := datacounter.NewResponseWriterCounter(w)
		defer func(t time.Time) {
			formatter(lw, r, wcounter.Count(), time.Since(t))
		}(time.Now())
		h.ServeHTTP(wcounter, r)
	})
}
<|endoftext|>


<commit_before>package gologger

import (
	"fmt"
	"os"
	"time"
)

const (
	TIME_FORMAT = "2006-01-02T15:04:05.000"
	DEBUG       = iota
	INFO
	WARN
	ERROR
)

var LogColors = map[int]int{
	DEBUG: 102,
	INFO:  28,
	WARN:  214,
	ERROR: 196,
}

var LogPrefixes = map[int]string{
	DEBUG: "DEBUG",
	INFO:  "INFO ",
	WARN:  "WARN ",
	ERROR: "ERROR",
}

func colorize(c int, s string) (r string) {
	return fmt.Sprintf("\033[38;5;%dm%s\033[0m", c, s)
}

type Logger struct {
	LogLevel int
	Started  time.Time
	Prefix   string
	Colored  bool
}

func New() *Logger {
	return &Logger{Colored: true, LogLevel: INFO}
}

// starts a timer, current runtime is then added to log output
func (self *Logger) Start() {
	self.Started = time.Now()
}

// stops the timer
func (self *Logger) Stop() {
	self.Started = time.Unix(0, 0)
}

func (self *Logger) Inspect(i interface{}) {
	self.Debugf("%+v", i)
	return
}

func (l *Logger) Debugf(format string, n ...interface{}) {
	l.logf(DEBUG, format, n...)
}

func (l *Logger) Infof(format string, n ...interface{}) {
	l.logf(INFO, format, n...)
}

func (l *Logger) Warnf(format string, n ...interface{}) {
	l.logf(WARN, format, n...)
}

func (l *Logger) Errorf(format string, n ...interface{}) {
	l.logf(ERROR, format, n...)
}

func (l *Logger) logf(level int, s string, n ...interface{}) {
	if level >= l.LogLevel {
		l.write(l.logPrefix(level), fmt.Sprintf(s, n...))
	}
}

func (l *Logger) Debug(n ...interface{}) {
	l.log(DEBUG, n...)
}

func (l *Logger) Info(n ...interface{}) {
	l.log(INFO, n...)
}

func (l *Logger) Warn(n ...interface{}) {
	l.log(WARN, n...)
}

func (l *Logger) Error(n ...interface{}) {
	l.log(ERROR, n...)
}

func (l *Logger) logPrefix(i int) (s string) {
	s = time.Now().Format(TIME_FORMAT)
	if l.Started.Unix() > 0 {
		time := fmt.Sprintf("%.3f", time.Now().Sub(l.Started).Seconds())
		s += fmt.Sprintf(" [%8s]", time)
	}
	if l.Prefix != "" {
		s = s + " [" + l.Prefix + "]"
	}
	s = s + " " + l.LogLevelPrefix(i)
	return
}

func (l *Logger) LogLevelPrefix(level int) (s string) {
	prefix := LogPrefixes[level]
	if l.Colored {
		color := LogColors[level]
		return colorize(color, prefix)
	}
	return prefix
}

func (l *Logger) log(level int, n ...interface{}) {
	if level >= l.LogLevel {
		all := append([]interface{}{l.logPrefix(level)}, n...)
		l.write(all...)
	}
}

func (self *Logger) write(n ...interface{}) {
	fmt.Fprintln(os.Stderr, n...)
}
<commit_msg>remove unused return<commit_after>package gologger

import (
	"fmt"
	"os"
	"time"
)

const (
	TIME_FORMAT = "2006-01-02T15:04:05.000"
	DEBUG       = iota
	INFO
	WARN
	ERROR
)

var LogColors = map[int]int{
	DEBUG: 102,
	INFO:  28,
	WARN:  214,
	ERROR: 196,
}

var LogPrefixes = map[int]string{
	DEBUG: "DEBUG",
	INFO:  "INFO ",
	WARN:  "WARN ",
	ERROR: "ERROR",
}

func colorize(c int, s string) (r string) {
	return fmt.Sprintf("\033[38;5;%dm%s\033[0m", c, s)
}

type Logger struct {
	LogLevel int
	Started  time.Time
	Prefix   string
	Colored  bool
}

func New() *Logger {
	return &Logger{Colored: true, LogLevel: INFO}
}

// starts a timer, current runtime is then added to log output
func (self *Logger) Start() {
	self.Started = time.Now()
}

// stops the timer
func (self *Logger) Stop() {
	self.Started = time.Unix(0, 0)
}

func (self *Logger) Inspect(i interface{}) {
	self.Debugf("%+v", i)
}

func (l *Logger) Debugf(format string, n ...interface{}) {
	l.logf(DEBUG, format, n...)
}

func (l *Logger) Infof(format string, n ...interface{}) {
	l.logf(INFO, format, n...)
}

func (l *Logger) Warnf(format string, n ...interface{}) {
	l.logf(WARN, format, n...)
}

func (l *Logger) Errorf(format string, n ...interface{}) {
	l.logf(ERROR, format, n...)
}

func (l *Logger) logf(level int, s string, n ...interface{}) {
	if level >= l.LogLevel {
		l.write(l.logPrefix(level), fmt.Sprintf(s, n...))
	}
}

func (l *Logger) Debug(n ...interface{}) {
	l.log(DEBUG, n...)
}

func (l *Logger) Info(n ...interface{}) {
	l.log(INFO, n...)
}

func (l *Logger) Warn(n ...interface{}) {
	l.log(WARN, n...)
}

func (l *Logger) Error(n ...interface{}) {
	l.log(ERROR, n...)
}

func (l *Logger) logPrefix(i int) (s string) {
	s = time.Now().Format(TIME_FORMAT)
	if l.Started.Unix() > 0 {
		time := fmt.Sprintf("%.3f", time.Now().Sub(l.Started).Seconds())
		s += fmt.Sprintf(" [%8s]", time)
	}
	if l.Prefix != "" {
		s = s + " [" + l.Prefix + "]"
	}
	s = s + " " + l.LogLevelPrefix(i)
	return
}

func (l *Logger) LogLevelPrefix(level int) (s string) {
	prefix := LogPrefixes[level]
	if l.Colored {
		color := LogColors[level]
		return colorize(color, prefix)
	}
	return prefix
}

func (l *Logger) log(level int, n ...interface{}) {
	if level >= l.LogLevel {
		all := append([]interface{}{l.logPrefix(level)}, n...)
		l.write(all...)
	}
}

func (self *Logger) write(n ...interface{}) {
	fmt.Fprintln(os.Stderr, n...)
}
<|endoftext|>