<commit_before>package main

import(
	"math"
	"fmt"
	"errors"
	"math/rand"
	"time"
	"sync"
	"code.google.com/p/plotinum/plot"
	"code.google.com/p/plotinum/plotter"
	"code.google.com/p/plotinum/plotutil"
)

type Result struct{
	Person Person
	Workload Workload
}

func (r Result) String()string{
	return fmt.Sprintf("<Result %v %s>", r.Person, r.Workload)
}

type Person struct{
	Vg float64 // Value/grade
	Gt float64 // Grade/time
	La float64 // Look ahead
	Vt float64 // Value/time
	P float64 // Rememberance
	B float64 // Beta
	D float64 // Delta

	Semester Semester // The schedule for the semester
	Assignments []Assignment // The assignment due
	WorkHours map[Assignment]int // The number of hours worked on assignments.
}

func (p Person) String() string{
	return fmt.Sprintf("<Person Vg:%f Gt:%f La:%f Vt:%f P:%f B:%f D:%f",
		p.Vg, p.Gt, p.La, p.Vt, p.P, p.B, p.D)
}

type Assignment struct{
	TotalGrade int // The total grade units this assignment is graded out of.
	DateDue int // The date due in numbers of day into the semester
	Name string
}

func (a Assignment) String() string{
	return fmt.Sprintf("",a.TotalGrade, a.DateDue, a.Name)
}

type Semester struct{
	Days int // The number of days
	Day int // The current day
	Weights map[int]float64 // A mapping of days to the attitude weight.
	Allowed map[int]int // A maping of days to hours available to work.
}

type Work struct{
	Day int
	Hours int
	Assignment Assignment
}

func (w Work) String() string{
	return fmt.Sprintf("<W Day=%d Hours=%d Assignment=%v",w.Day, w.Hours, w.Assignment)
}

func IntFactorial(i int)(int, error){
	if i > 20|| i < 1{return 0, errors.New("Factorial out of range")}
	if i == 1{
		return 1, nil
	}
	res, _ := IntFactorial(i-1)
	return i * res, nil
}

// Represents a possible ammount to work in the future.
type Workload struct{
	Days []Work
	Hours map[Assignment]int
}

func (w Workload) String() string {
	return fmt.Sprintf("<WL days=%v hours=%v>" ,w.Days, w.Hours)
}

// Generate workloads starting on the current day.
func (p *Person) Workloads() []Workload {

	workloads := make([]Workload,1)
	// Create base workload
	workloads[0] = *new(Workload)
	workloads[0].Days = make([]Work, 0)
	workloads[0].Hours = make(map[Assignment]int)
	for _,assignment := range(p.Assignments){
		workloads[0].Hours[assignment] = 0
	}
	// For each day until the look ahead day.
	for i := 0; float64(i)
= (p.Semester.Day + i){
					// For every time that does not put you over the limit.
					for t:=1; basewl.Hours[assignment]+
						t+p.WorkHours[assignment] <=
						assignment.TotalGrade &&
						t <= p.Semester.Allowed[p.Semester.Day + i]; t++{

						wl := *new(Workload)
						day := *new(Work)
						day.Assignment = assignment
						day.Hours = t
						day.Day = i + p.Semester.Day
						wl.Days = append(basewl.Days,day)
						wl.Hours = make(map[Assignment]int)
						for k, v := range(basewl.Hours){
							wl.Hours[k]=v
						}

						wl.Hours[assignment] = basewl.Hours[assignment] + t
						newloads = append(newloads,wl)
					}
				}
			}
			wl := *new(Workload)
			day := *new(Work)
			day.Assignment = Assignment{}
			day.Hours = 0
			day.Day = i + p.Semester.Day
			wl.Days = append(basewl.Days,day)
			wl.Hours = make(map[Assignment]int)
			for k,v := range(basewl.Hours){
				wl.Hours[k]=v
			}
			newloads = append(newloads, wl)
		}

		workloads = newloads
	}
	return workloads
}

// The discount function for this individual
// If d is 0 then the function is 1, otherwise it is B * (D ^ d)
func (p *Person) Discount(d int) float64{
	if d == 0 {
		return 1
	}
	return p.B * math.Pow(p.D, float64(d))
}

// Returns the expected utility of the given work.
func (p Person) WorkUtility(work Work) float64{
	utility := (float64(work.Hours) * p.Vg *p.Gt *p.B * p.Discount(work.Assignment.DateDue - p.Semester.Day))
	cost := (float64(work.Hours) * p.Semester.Weights[work.Day] * p.Discount(work.Day - p.Semester.Day))
	return utility -cost
}

// Returns the expected utility of the given workload evaluated on the current day.
func (p Person) Utility(workload Workload) float64{
	utility := float64(0)
	for _, work := range(workload.Days){
		if work.Assignment.TotalGrade != 0{
			utility += p.WorkUtility(work)
		}
	}
	return utility
}

func (p Person) Simulate(results chan Result){
	var globalWorkload Workload
	globalWorkload.Hours = make(map[Assignment]int)

	for p.Semester.Day
 maxUtility{
				maxUtility = utility
				choices = make([]Workload, 0)
				choices = append(choices, workload)
			}
		}

		var choice Workload
		if len(choices) > 1{
			choice = choices[rand.Intn(len(choices))]
		}else{
			choice = choices[0]
		}
		if len(choice.Days) > 0 {
			globalWorkload.Days = append(globalWorkload.Days, choice.Days[0])
			p.WorkHours[choice.Days[0].Assignment] += choice.Days[0].Hours
			globalWorkload.Hours[choice.Days[0].Assignment] += choice.Days[0].Hours
		}
	}

	results <- Result{Person:p, Workload:globalWorkload}
}

func main(){
	fmt.Println("Simulating people... This is most likely not going to work.")

	people := make([]Person, 0)
	rand.Seed(time.Now().Unix())

	assignments := make([]Assignment, 0)
	assignments = append(assignments, Assignment{DateDue:15,TotalGrade: 10, Name:"Assignment 1"})
	assignments = append(assignments, Assignment{DateDue:30,TotalGrade: 5, Name:"Assignment 2"})
	numPeople := 30
	numDays := 30
	for p:=0; p< numPeople; p++{
		// Create person
		Vg := rand.NormFloat64() * 1 + 5
		Gt := rand.NormFloat64() * 0.25 + 2
		B := rand.NormFloat64() * 0.1 + 0.8
		D := rand.NormFloat64() * 0.2 + 0.8
		person := Person{Vg:Vg, Gt:Gt, La: 7, Vt: 1, P:1, B:B, D:D, WorkHours:make(map[Assignment]int)}
		semester := Semester{Days: 30,Day:0,Weights:make(map[int]float64), Allowed:make(map[int]int)}
		person.Semester = semester
		for i := 0; i <= 30; i++{
			person.Semester.Weights[i] = float64(i)*0.025
			person.Semester.Allowed[i] = 2
			if i > 20 {
				person.Semester.Allowed[i] = 1
			}
		}

		person.Assignments = assignments

		people = append(people, person)
	}

	results := make(chan Result)
	go func(people []Person, results chan Result){
		wg := sync.WaitGroup{}
		for _,person := range people{
			wg.Add(1)
			go func(person Person){
				defer wg.Done()
				person.Simulate(results)
			}(person)
		}
		wg.Wait()
		close(results)
	}(people, results)

	frequency := make(map[Assignment]plotter.XYs, len(assignments))
	grades := make(map[Assignment]int)

	for _, Assignment := range assignments{
		frequency[Assignment] = make(plotter.XYs,numDays+1)
		for i := 0;i <= numDays; i++{
			frequency[Assignment][i].X = float64(i)
			frequency[Assignment][i].Y = 0
		}
	}
	for res := range results{
		res := res
		fmt.Printf("Results: %v \n\t%v\n", res.Person, res.Workload)
		for n,day := range res.Workload.Days{
			if day.Assignment.TotalGrade != 0{
				frequency[day.Assignment][n].Y += float64(day.Hours)
			}
		}
		for Assignment, Grade := range res.Workload.Hours{
			grades[Assignment] = grades[Assignment] + Grade
		}
	}

	p, err := plot.New()
	if err != nil {
			panic(err)
	}
	p.Title.Text = "Frequency"
	p.Y.Label.Text = "Heights"

	for assignment,hour := range grades{
		fmt.Printf("Average %s: %f\n", assignment, float64(hour)/float64(30))
	}

	for assignment, xys := range frequency{
	   plotutil.AddLinePoints(p, assignment.Name, xys)
	}
	p.Legend.Top = true
	p.X.Min=0
	p.X.Max=float64(numDays)
	if err := p.Save(5, 3, "barchart.png"); err != nil {
		panic(err)
	}

}
<commit_msg>Fixed ploting lines with same colors.<commit_after>package main

import(
	"math"
	"fmt"
	"errors"
	"math/rand"
	"time"
	"sync"
	"code.google.com/p/plotinum/plot"
	"code.google.com/p/plotinum/plotter"
	"code.google.com/p/plotinum/plotutil"
)

type Result struct{
	Person Person
	Workload Workload
}

func (r Result) String()string{
	return fmt.Sprintf("<Result %v %s>", r.Person, r.Workload)
}

type Person struct{
	Vg float64 // Value/grade
	Gt float64 // Grade/time
	La float64 // Look ahead
	Vt float64 // Value/time
	P float64 // Rememberance
	B float64 // Beta
	D float64 // Delta

	Semester Semester // The schedule for the semester
	Assignments []Assignment // The assignment due
	WorkHours map[Assignment]int // The number of hours worked on assignments.
}

func (p Person) String() string{
	return fmt.Sprintf("<Person Vg:%f Gt:%f La:%f Vt:%f P:%f B:%f D:%f",
		p.Vg, p.Gt, p.La, p.Vt, p.P, p.B, p.D)
}

type Assignment struct{
	TotalGrade int // The total grade units this assignment is graded out of.
	DateDue int // The date due in numbers of day into the semester
	Name string
}

func (a Assignment) String() string{
	return fmt.Sprintf("",a.TotalGrade, a.DateDue, a.Name)
}

type Semester struct{
	Days int // The number of days
	Day int // The current day
	Weights map[int]float64 // A mapping of days to the attitude weight.
	Allowed map[int]int // A maping of days to hours available to work.
}

type Work struct{
	Day int
	Hours int
	Assignment Assignment
}

func (w Work) String() string{
	return fmt.Sprintf("<W Day=%d Hours=%d Assignment=%v",w.Day, w.Hours, w.Assignment)
}

func IntFactorial(i int)(int, error){
	if i > 20|| i < 1{return 0, errors.New("Factorial out of range")}
	if i == 1{
		return 1, nil
	}
	res, _ := IntFactorial(i-1)
	return i * res, nil
}

// Represents a possible ammount to work in the future.
type Workload struct{
	Days []Work
	Hours map[Assignment]int
}

func (w Workload) String() string {
	return fmt.Sprintf("<WL days=%v hours=%v>" ,w.Days, w.Hours)
}

// Generate workloads starting on the current day.
func (p *Person) Workloads() []Workload {

	workloads := make([]Workload,1)
	// Create base workload
	workloads[0] = *new(Workload)
	workloads[0].Days = make([]Work, 0)
	workloads[0].Hours = make(map[Assignment]int)
	for _,assignment := range(p.Assignments){
		workloads[0].Hours[assignment] = 0
	}
	// For each day until the look ahead day.
	for i := 0; float64(i)
= (p.Semester.Day + i){
					// For every time that does not put you over the limit.
					for t:=1; basewl.Hours[assignment]+
						t+p.WorkHours[assignment] <=
						assignment.TotalGrade &&
						t <= p.Semester.Allowed[p.Semester.Day + i]; t++{

						wl := *new(Workload)
						day := *new(Work)
						day.Assignment = assignment
						day.Hours = t
						day.Day = i + p.Semester.Day
						wl.Days = append(basewl.Days,day)
						wl.Hours = make(map[Assignment]int)
						for k, v := range(basewl.Hours){
							wl.Hours[k]=v
						}

						wl.Hours[assignment] = basewl.Hours[assignment] + t
						newloads = append(newloads,wl)
					}
				}
			}
			wl := *new(Workload)
			day := *new(Work)
			day.Assignment = Assignment{}
			day.Hours = 0
			day.Day = i + p.Semester.Day
			wl.Days = append(basewl.Days,day)
			wl.Hours = make(map[Assignment]int)
			for k,v := range(basewl.Hours){
				wl.Hours[k]=v
			}
			newloads = append(newloads, wl)
		}

		workloads = newloads
	}
	return workloads
}

// The discount function for this individual
// If d is 0 then the function is 1, otherwise it is B * (D ^ d)
func (p *Person) Discount(d int) float64{
	if d == 0 {
		return 1
	}
	return p.B * math.Pow(p.D, float64(d))
}

// Returns the expected utility of the given work.
func (p Person) WorkUtility(work Work) float64{
	utility := (float64(work.Hours) * p.Vg *p.Gt *p.B * p.Discount(work.Assignment.DateDue - p.Semester.Day))
	cost := (float64(work.Hours) * p.Semester.Weights[work.Day] * p.Discount(work.Day - p.Semester.Day))
	return utility -cost
}

// Returns the expected utility of the given workload evaluated on the current day.
func (p Person) Utility(workload Workload) float64{
	utility := float64(0)
	for _, work := range(workload.Days){
		if work.Assignment.TotalGrade != 0{
			utility += p.WorkUtility(work)
		}
	}
	return utility
}

func (p Person) Simulate(results chan Result){
	var globalWorkload Workload
	globalWorkload.Hours = make(map[Assignment]int)

	for p.Semester.Day
 maxUtility{
				maxUtility = utility
				choices = make([]Workload, 0)
				choices = append(choices, workload)
			}
		}

		var choice Workload
		if len(choices) > 1{
			choice = choices[rand.Intn(len(choices))]
		}else{
			choice = choices[0]
		}
		if len(choice.Days) > 0 {
			globalWorkload.Days = append(globalWorkload.Days, choice.Days[0])
			p.WorkHours[choice.Days[0].Assignment] += choice.Days[0].Hours
			globalWorkload.Hours[choice.Days[0].Assignment] += choice.Days[0].Hours
		}
	}

	results <- Result{Person:p, Workload:globalWorkload}
}

func main(){
	fmt.Println("Simulating people... This is most likely not going to work.")

	people := make([]Person, 0)
	rand.Seed(time.Now().Unix())

	assignments := make([]Assignment, 0)
	assignments = append(assignments, Assignment{DateDue:15,TotalGrade: 10, Name:"Assignment 1"})
	assignments = append(assignments, Assignment{DateDue:30,TotalGrade: 5, Name:"Assignment 2"})
	numPeople := 30
	numDays := 30
	for p:=0; p< numPeople; p++{
		// Create person
		Vg := rand.NormFloat64() * 1 + 5
		Gt := rand.NormFloat64() * 0.25 + 2
		B := rand.NormFloat64() * 0.1 + 0.8
		D := rand.NormFloat64() * 0.2 + 0.8
		person := Person{Vg:Vg, Gt:Gt, La: 7, Vt: 1, P:1, B:B, D:D, WorkHours:make(map[Assignment]int)}
		semester := Semester{Days: 30,Day:0,Weights:make(map[int]float64), Allowed:make(map[int]int)}
		person.Semester = semester
		for i := 0; i <= 30; i++{
			person.Semester.Weights[i] = float64(i)*0.025
			person.Semester.Allowed[i] = 2
			if i > 20 {
				person.Semester.Allowed[i] = 1
			}
		}

		person.Assignments = assignments

		people = append(people, person)
	}

	results := make(chan Result)
	go func(people []Person, results chan Result){
		wg := sync.WaitGroup{}
		for _,person := range people{
			wg.Add(1)
			go func(person Person){
				defer wg.Done()
				person.Simulate(results)
			}(person)
		}
		wg.Wait()
		close(results)
	}(people, results)

	frequency := make(map[Assignment]plotter.XYs, len(assignments))
	grades := make(map[Assignment]int)

	for _, Assignment := range assignments{
		frequency[Assignment] = make(plotter.XYs,numDays+1)
		for i := 0;i <= numDays; i++{
			frequency[Assignment][i].X = float64(i)
			frequency[Assignment][i].Y = 0
		}
	}
	for res := range results{
		res := res
		fmt.Printf("Results: %v \n\t%v\n", res.Person, res.Workload)
		for n,day := range res.Workload.Days{
			if day.Assignment.TotalGrade != 0{
				frequency[day.Assignment][n].Y += float64(day.Hours)
			}
		}
		for Assignment, Grade := range res.Workload.Hours{
			grades[Assignment] = grades[Assignment] + Grade
		}
	}

	p, err := plot.New()
	if err != nil {
			panic(err)
	}
	p.Title.Text = "Frequency"
	p.Y.Label.Text = "Heights"
	p.Add(plotter.NewGrid())

	for assignment,hour := range grades{
		fmt.Printf("Average %s: %f\n", assignment, float64(hour)/float64(30))
	}

	i := 0
	for assignment, xys := range frequency{
		lpline,lppoints, err := plotter.NewLinePoints(xys)
		if err != nil{
			panic(err)
		}
		lpline.Color = plotutil.Color(i)
		lpline.Dashes = plotutil.Dashes(i)
		lppoints.Color = plotutil.Color(i)
		lppoints.Shape = plotutil.Shape(i)
		i++
		p.Add(lpline)
		p.Add(lppoints)
		p.Legend.Add(assignment.Name, lpline, lppoints)
	}
	p.Legend.Top = true
	p.X.Min=0
	p.X.Max=float64(numDays)
	if err := p.Save(5, 3, "barchart.png"); err != nil {
		panic(err)
	}

}
<|endoftext|>


<commit_before>package main

import (
  "glop/gin"
  "glop/gui"
  "glop/util/algorithm"
  "glop/sprite"
  "gl"
  "math"
  "github.com/arbaal/mathgl"
  "json"
  "path/filepath"
  "io/ioutil"
  "os"
)

type CellData struct {
  move_cost int

  highlight Highlight
}

type Highlight int
const (
  None Highlight = iota
  Reachable
  MouseOver
//  Impassable
//  OutOfRange
)

func (t *CellData) Render(x,y,z,scale float32) {
  gl.Disable(gl.TEXTURE_2D)
  gl.Enable(gl.BLEND)
  gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
  gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL)
  var r,g,b,a float32
  a = 1.0
  switch t.move_cost {
    case 1:
      r,g,b = 0.1, 0.9, 0.4
    case 5:
      r,g,b = 0.0, 0.7, 0.2
    case 10:
      r,g,b = 0.0, 0.0, 1.0
    case 2:
      r,g,b = 0.6, 0.5, 0.3
    default:
      r,g,b = 1,0,0
  }
  x *= scale
  y *= scale
  gl.Color4f(r, g, b, a)
  gl.Begin(gl.QUADS)
    gl.Vertex3f(x, y, z)
    gl.Vertex3f(x, y+scale, z)
    gl.Vertex3f(x+scale, y+scale, z)
    gl.Vertex3f(x+scale, y, z)
  gl.End()

  if t.highlight != None {
    switch t.highlight {
      case Reachable:
        r,g,b,a = 0,0.2,0.9,0.3
      case MouseOver:
        r,g,b,a = 0.1,0.9,0.2,0.4
      default:
        panic("Unknown highlight")
    }
    gl.Color4f(r, g, b, a)
    gl.Begin(gl.QUADS)
      gl.Vertex3f(x, y, z)
      gl.Vertex3f(x, y+scale, z)
      gl.Vertex3f(x+scale, y+scale, z)
      gl.Vertex3f(x+scale, y, z)
    gl.End()
  }
}

// Contains everything about a level that is stored on disk
type StaticLevelData struct {
  grid [][]CellData
}

func (s *StaticLevelData) NumVertex() int {
  return len(s.grid) * len(s.grid[0])
}
func (s *StaticLevelData) fromVertex(v int) (int,int) {
  return v % len(s.grid), v / len(s.grid)
}
func (s *StaticLevelData) toVertex(x,y int) int {
  return x + y * len(s.grid)
}
func (s *StaticLevelData) Adjacent(v int) ([]int, []float64) {
  x,y := s.fromVertex(v)
  var adj []int
  var weight []float64

  // separate arrays for the adjacent diagonal cells, this way we make sure they are listed
  // at the end so that searches will prefer orthogonal adjacent cells
  var adj_diag []int
  var weight_diag []float64

  for dx := -1; dx <= 1; dx++ {
    if x + dx < 0 || x + dx >= len(s.grid) { continue }
    for dy := -1; dy <= 1; dy++ {
      if dx == 0 && dy == 0 { continue }
      if y + dy < 0 || y + dy >= len(s.grid[0]) { continue }
      if s.grid[x+dx][y+dy].move_cost <= 0 { continue }
      // Prevent moving along a diagonal if we couldn't get to that space normally via
      // either of the non-diagonal paths
      if dx != 0 && dy != 0 {
        if s.grid[x+dx][y].move_cost > 0 && s.grid[x][y+dy].move_cost > 0 {
          cost_a := float64(s.grid[x+dx][y].move_cost + s.grid[x][y+dy].move_cost) / 2
          cost_b := float64(s.grid[x+dx][y+dy].move_cost)
          adj_diag = append(adj, s.toVertex(x+dx, y+dy))
          weight_diag = append(weight, math.Fmax(cost_a, cost_b))
        }
      } else {
        if s.grid[x+dx][y+dy].move_cost > 0 {
          adj = append(adj, s.toVertex(x+dx, y+dy))
          weight = append(weight, float64(s.grid[x+dx][y+dy].move_cost))
        }
      }
    }
  }
  for i := range adj_diag {
    adj = append(adj, adj_diag[i])
    weight = append(weight, weight_diag[i])
  }
  return adj,weight
}

// Contains everything for the playing of the game
type Level struct {
  StaticLevelData

  // List of all sprites currently on the map
  dudes []*sprite.Sprite
  d_pos []mathgl.Vec2

  // The gui element rendering the terrain and all of the other drawables
  terrain *gui.Terrain

  entities []*entity

  selected *entity

  // If a unit is selected this will hold the list of cells that are reachable
  // from that unit's position within its allotted AP
  reachable []int

  // window coords of the mouse
  winx,winy int
}

func (l *Level) Think(dt int64) {

  // Draw all sprites
  for i := range l.entities {
    e := l.entities[i]
    e.Think(dt)
    l.terrain.AddUprightDrawable(e.bx + 0.25, e.by + 0.25, e.s)
  }

  for i := range l.grid {
    for j := range l.grid[i] {
      l.grid[i][j].highlight = None
    }
  }

  if l.selected != nil {
    if len(l.selected.path) == 0 {
      if len(l.reachable) == 0 {
        bx := int(l.selected.bx)
        by := int(l.selected.by)
        l.reachable = algorithm.ReachableWithinLimit(l, []int{ l.toVertex(bx, by) }, float64(l.selected.ap))
      }
      for _,v := range l.reachable {
        x,y := l.fromVertex(v)
        l.grid[x][y].highlight = Reachable
      }
    } else {
      for _,v := range l.selected.path {
        l.grid[v[0]][v[1]].highlight = Reachable
      }
    }
  }

  bx,by := l.terrain.WindowToBoard(l.winx, l.winy)
  mx := int(bx)
  my := int(by)
  if mx >= 0 && my >= 0 && mx < len(l.grid) && my < len(l.grid[0]) {
    l.grid[mx][my].highlight = MouseOver
  }

  // Draw tile movement speeds
  for i := range l.grid {
    for j := range l.grid[i] {
      l.terrain.AddFlattenedDrawable(float32(i), float32(j), &l.grid[i][j])
    }
  }
}

func (l *Level) HandleEventGroup(event_group gin.EventGroup) {
  x,y := gin.In().GetKey(304).Cursor().Point()
  l.winx = x
  l.winy = y
  bx,by := l.terrain.WindowToBoard(x, y)

  // Left mouse click, do the first option from this list that is possible
  // Select/Deselect the entity under the mouse
  // Tell the selected entity to mouse to the current mouse position
  if found,event := event_group.FindEvent(304); found && event.Type == gin.Press {
    click := mathgl.Vec2{ bx, by }

    var ent *entity
    var dist float32 = float32(math.Inf(1))
    for i := range l.entities {
      var cc mathgl.Vec2
      cc.Assign(&click)
      cc.Subtract(&mathgl.Vec2{ l.entities[i].bx + 0.5, l.entities[i].by + 0.5 })
      dx := cc.X
      if dx < 0 { dx = -dx }
      dy := cc.Y
      if dy < 0 { dy = -dy }
      d := float32(math.Fmax(float64(dx), float64(dy)))
      if d < dist {
        dist = d
        ent = l.entities[i]
      }
    }

    if l.selected == nil && dist < 3 {
      l.selected = ent
      l.reachable = nil
      return
    }

    if l.selected != nil && dist < 0.5 {
      if l.selected == ent {
        l.selected = nil
      } else {
        l.reachable = nil
        l.selected = ent
      }
      return
    }

    ent = nil

    if l.selected != nil && ent == nil {
      start := l.toVertex(int(l.selected.bx), int(l.selected.by))
      end := l.toVertex(int(click.X), int(click.Y))
      ap,path := algorithm.Dijkstra(l, []int{ start }, []int{ end })
      if len(path) == 0 || int(ap) > l.selected.ap { return }
      path = path[1:]
      l.selected.path = l.selected.path[0:0]
      l.reachable = nil
      for i := range path {
        x,y := l.fromVertex(path[i])
        l.selected.path = append(l.selected.path, [2]int{x,y})
      }
    }
  }
}

type levelDataCell struct {
  Terrain string
}
type levelData struct {
  Image string
  Cells [][]levelDataCell
}

type levelDataContainer struct {
  Level levelData
}

func LoadLevel(pathname string) (*Level, os.Error) {
  datapath := filepath.Join(filepath.Clean(pathname), "data.json")
  datafile,err := os.Open(datapath)
  if err != nil {
    return nil, err
  }
  data,err := ioutil.ReadAll(datafile)
  if err != nil {
    return nil, err
  }
  var ldc levelDataContainer
  json.Unmarshal(data, &ldc)

  var level Level
  dx := len(ldc.Level.Cells)
  dy := len(ldc.Level.Cells[0])
  all_cells := make([]CellData, dx*dy)
  level.grid = make([][]CellData, dx)
  for i := range level.grid {
    level.grid[i] = all_cells[i*dy : (i+1)*dy]
  }
  for i := range level.grid {
    for j := range level.grid[i] {
      switch ldc.Level.Cells[i][j].Terrain {
        case "grass":
          level.grid[i][j].move_cost = 1
        case "brush":
          level.grid[i][j].move_cost = 5
        case "water":
          level.grid[i][j].move_cost = 10
        case "dirt":
          level.grid[i][j].move_cost = 2
        default:
          panic("WTF")
      }
    }
  }
  bg_path := filepath.Join(filepath.Clean(pathname), ldc.Level.Image)
  terrain,err := gui.MakeTerrain(bg_path, 100, dx, dy, 65)
  if err != nil {
    return nil, err
  }
  level.terrain = terrain
  terrain.SetEventHandler(&level)
  return &level, nil
}

<commit_msg>Cache things when possible<commit_after>package main

import (
  "glop/gin"
  "glop/gui"
  "glop/util/algorithm"
  "glop/sprite"
  "gl"
  "math"
  "github.com/arbaal/mathgl"
  "json"
  "path/filepath"
  "io/ioutil"
  "os"
)

type staticCellData struct {
  move_cost int
}
type cachedCellData struct {
  highlight Highlight
}

type CellData struct {
  staticCellData
  cachedCellData
}

type Highlight int
const (
  None Highlight = iota
  Reachable
  MouseOver
//  Impassable
//  OutOfRange
)

func (t *CellData) Clear() {
  t.cachedCellData = cachedCellData{}
}

func (t *CellData) Render(x,y,z,scale float32) {
  gl.Disable(gl.TEXTURE_2D)
  gl.Enable(gl.BLEND)
  gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
  gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL)
  var r,g,b,a float32
  a = 0.2
  switch t.move_cost {
    case 1:
      r,g,b = 0.1, 0.9, 0.4
    case 5:
      r,g,b = 0.0, 0.7, 0.2
    case 10:
      r,g,b = 0.0, 0.0, 1.0
    case 2:
      r,g,b = 0.6, 0.5, 0.3
    default:
      r,g,b = 1,0,0
  }
  x *= scale
  y *= scale
  gl.Color4f(r, g, b, a)
  gl.Begin(gl.QUADS)
    gl.Vertex3f(x, y, z)
    gl.Vertex3f(x, y+scale, z)
    gl.Vertex3f(x+scale, y+scale, z)
    gl.Vertex3f(x+scale, y, z)
  gl.End()

  if t.highlight != None {
    switch t.highlight {
      case Reachable:
        r,g,b,a = 0,0.2,0.9,0.3
      case MouseOver:
        r,g,b,a = 0.1,0.9,0.2,0.4
      default:
        panic("Unknown highlight")
    }
    gl.Color4f(r, g, b, a)
    gl.Begin(gl.QUADS)
      gl.Vertex3f(x, y, z)
      gl.Vertex3f(x, y+scale, z)
      gl.Vertex3f(x+scale, y+scale, z)
      gl.Vertex3f(x+scale, y, z)
    gl.End()
  }
}

// Contains everything about a level that is stored on disk
type StaticLevelData struct {
  grid [][]CellData
}

func (s *StaticLevelData) NumVertex() int {
  return len(s.grid) * len(s.grid[0])
}
func (s *StaticLevelData) fromVertex(v int) (int,int) {
  return v % len(s.grid), v / len(s.grid)
}
func (s *StaticLevelData) toVertex(x,y int) int {
  return x + y * len(s.grid)
}
func (s *StaticLevelData) Adjacent(v int) ([]int, []float64) {
  x,y := s.fromVertex(v)
  var adj []int
  var weight []float64

  // separate arrays for the adjacent diagonal cells, this way we make sure they are listed
  // at the end so that searches will prefer orthogonal adjacent cells
  var adj_diag []int
  var weight_diag []float64

  for dx := -1; dx <= 1; dx++ {
    if x + dx < 0 || x + dx >= len(s.grid) { continue }
    for dy := -1; dy <= 1; dy++ {
      if dx == 0 && dy == 0 { continue }
      if y + dy < 0 || y + dy >= len(s.grid[0]) { continue }
      if s.grid[x+dx][y+dy].move_cost <= 0 { continue }
      // Prevent moving along a diagonal if we couldn't get to that space normally via
      // either of the non-diagonal paths
      if dx != 0 && dy != 0 {
        if s.grid[x+dx][y].move_cost > 0 && s.grid[x][y+dy].move_cost > 0 {
          cost_a := float64(s.grid[x+dx][y].move_cost + s.grid[x][y+dy].move_cost) / 2
          cost_b := float64(s.grid[x+dx][y+dy].move_cost)
          adj_diag = append(adj, s.toVertex(x+dx, y+dy))
          weight_diag = append(weight, math.Fmax(cost_a, cost_b))
        }
      } else {
        if s.grid[x+dx][y+dy].move_cost > 0 {
          adj = append(adj, s.toVertex(x+dx, y+dy))
          weight = append(weight, float64(s.grid[x+dx][y+dy].move_cost))
        }
      }
    }
  }
  for i := range adj_diag {
    adj = append(adj, adj_diag[i])
    weight = append(weight, weight_diag[i])
  }
  return adj,weight
}

// Contains everything for the playing of the game
type Level struct {
  StaticLevelData

  // List of all sprites currently on the map
  dudes []*sprite.Sprite
  d_pos []mathgl.Vec2

  // The gui element rendering the terrain and all of the other drawables
  terrain *gui.Terrain

  entities []*entity

  selected *entity

  // If a unit is selected this will hold the list of cells that are reachable
  // from that unit's position within its allotted AP
  reachable []int

  // window coords of the mouse
  winx,winy int

  // unset when the cache is cleared, lets Think() know it has to refil the cache
  cached bool
}

func (l *Level) clearCache() {
  if !l.cached { return }
  print("Clearing\n")
  for i := range l.grid {
    for j := range l.grid[i] {
      l.grid[i][j].Clear()
    }
  }
  l.cached = false
}

func (l *Level) Think(dt int64) {
  // Draw all sprites
  for i := range l.entities {
    e := l.entities[i]
    pbx := int(e.bx)
    pby := int(e.by)
    e.Think(dt)
    if pbx != int(e.bx) || pby != int(e.by) {
      l.clearCache()
    }
    l.terrain.AddUprightDrawable(e.bx + 0.25, e.by + 0.25, e.s)
  }

  if !l.cached && l.selected != nil {
    if len(l.selected.path) == 0 {
      if len(l.reachable) == 0 {
        bx := int(l.selected.bx)
        by := int(l.selected.by)
        l.reachable = algorithm.ReachableWithinLimit(l, []int{ l.toVertex(bx, by) }, float64(l.selected.ap))
      }
      for _,v := range l.reachable {
        x,y := l.fromVertex(v)
        l.grid[x][y].highlight = Reachable
      }
    } else {
      for _,v := range l.selected.path {
        l.grid[v[0]][v[1]].highlight = Reachable
      }
    }
  }

  // Draw tile movement speeds
  for i := range l.grid {
    for j := range l.grid[i] {
      if i == 0 && j == 0 { print("added drawables\n")}
      l.terrain.AddFlattenedDrawable(float32(i), float32(j), &l.grid[i][j])
    }
  }

  l.cached = true

  // Highlight the square under the cursor
  bx,by := l.terrain.WindowToBoard(l.winx, l.winy)
  mx := int(bx)
  my := int(by)
  if mx >= 0 && my >= 0 && mx < len(l.grid) && my < len(l.grid[0]) {
    cell := l.grid[mx][my]
    cell.highlight = MouseOver
    l.terrain.AddFlattenedDrawable(float32(mx), float32(my), &cell)
  }

}

func (l *Level) HandleEventGroup(event_group gin.EventGroup) {
  x,y := gin.In().GetKey(304).Cursor().Point()
  l.winx = x
  l.winy = y
  bx,by := l.terrain.WindowToBoard(x, y)

  // Left mouse click, do the first option from this list that is possible
  // Select/Deselect the entity under the mouse
  // Tell the selected entity to mouse to the current mouse position
  if found,event := event_group.FindEvent(304); found && event.Type == gin.Press {
    click := mathgl.Vec2{ bx, by }

    var ent *entity
    var dist float32 = float32(math.Inf(1))
    for i := range l.entities {
      var cc mathgl.Vec2
      cc.Assign(&click)
      cc.Subtract(&mathgl.Vec2{ l.entities[i].bx + 0.5, l.entities[i].by + 0.5 })
      dx := cc.X
      if dx < 0 { dx = -dx }
      dy := cc.Y
      if dy < 0 { dy = -dy }
      d := float32(math.Fmax(float64(dx), float64(dy)))
      if d < dist {
        dist = d
        ent = l.entities[i]
      }
    }

    if l.selected == nil && dist < 3 {
      l.selected = ent
      l.reachable = nil
      l.clearCache()
      return
    }

    if l.selected != nil && dist < 0.5 {
      if l.selected == ent {
        l.selected = nil
      } else {
        l.reachable = nil
        l.selected = ent
      }
      l.clearCache()
      return
    }

    ent = nil

    if l.selected != nil && ent == nil {
      start := l.toVertex(int(l.selected.bx), int(l.selected.by))
      end := l.toVertex(int(click.X), int(click.Y))
      ap,path := algorithm.Dijkstra(l, []int{ start }, []int{ end })
      if len(path) == 0 || int(ap) > l.selected.ap { return }
      path = path[1:]
      l.selected.path = l.selected.path[0:0]
      l.reachable = nil
      for i := range path {
        x,y := l.fromVertex(path[i])
        l.selected.path = append(l.selected.path, [2]int{x,y})
      }
      l.clearCache()
    }
  }
}

type levelDataCell struct {
  Terrain string
}
type levelData struct {
  Image string
  Cells [][]levelDataCell
}

type levelDataContainer struct {
  Level levelData
}

func LoadLevel(pathname string) (*Level, os.Error) {
  datapath := filepath.Join(filepath.Clean(pathname), "data.json")
  datafile,err := os.Open(datapath)
  if err != nil {
    return nil, err
  }
  data,err := ioutil.ReadAll(datafile)
  if err != nil {
    return nil, err
  }
  var ldc levelDataContainer
  json.Unmarshal(data, &ldc)

  var level Level
  dx := len(ldc.Level.Cells)
  dy := len(ldc.Level.Cells[0])
  all_cells := make([]CellData, dx*dy)
  level.grid = make([][]CellData, dx)
  for i := range level.grid {
    level.grid[i] = all_cells[i*dy : (i+1)*dy]
  }
  for i := range level.grid {
    for j := range level.grid[i] {
      switch ldc.Level.Cells[i][j].Terrain {
        case "grass":
          level.grid[i][j].move_cost = 1
        case "brush":
          level.grid[i][j].move_cost = 5
        case "water":
          level.grid[i][j].move_cost = 10
        case "dirt":
          level.grid[i][j].move_cost = 2
        default:
          panic("WTF")
      }
    }
  }
  bg_path := filepath.Join(filepath.Clean(pathname), ldc.Level.Image)
  terrain,err := gui.MakeTerrain(bg_path, 100, dx, dy, 65)
  if err != nil {
    return nil, err
  }
  level.terrain = terrain
  terrain.SetEventHandler(&level)
  return &level, nil
}

<|endoftext|>


<commit_before>package skydb

import (
	"errors"
	"time"
)

// ErrUserDuplicated is returned by Conn.CreateUser when
// the UserInfo to be created has the same ID/username in the current container
var ErrUserDuplicated = errors.New("skydb: duplicated UserID or Username")

// ErrUserNotFound is returned by Conn.GetUser, Conn.UpdateUser and
// Conn.DeleteUser when the UserInfo's ID is not found
// in the current container
var ErrUserNotFound = errors.New("skydb: UserInfo ID not found")

var ErrRoleUpdatesFailed = errors.New("skydb: Update of user roles failed")

// ErrDeviceNotFound is returned by Conn.GetDevice, Conn.DeleteDevice,
// Conn.DeleteDeviceByToken and Conn.DeleteEmptyDevicesByTime, if the desired Device
// cannot be found in the current container
var ErrDeviceNotFound = errors.New("skydb: Specific device not found")

// ZeroTime represent a zero time.Time. It is used in DeleteDeviceByToken and
// DeleteEmptyDevicesByTime to signify a Delete without time constraint.
var ZeroTime = time.Time{}

// DBHookFunc specifies the interface of a database hook function
type DBHookFunc func(Database, *Record, RecordHookEvent)

// QueryConfig provides optional parameters for queries.
// result is unlimited if Limit=0
type QueryConfig struct {
	Limit  uint64
	Offset uint64
}

// Conn encapsulates the interface of an Skygear connection to a container.
type Conn interface {
	// CRUD of UserInfo, smell like a bad design to attach these onto
	// a Conn, but looks very convenient to user.

	// CreateUser creates a new UserInfo in the container
	// this Conn associated to.
	CreateUser(userinfo *UserInfo) error

	// GetUser fetches the UserInfo with supplied ID in the container and
	// fills in the supplied UserInfo with the result.
	//
	// GetUser returns ErrUserNotFound if no UserInfo exists
	// for the supplied ID.
	GetUser(id string, userinfo *UserInfo) error

	// GetUserByUsernameEmail fetch the UserInfo with supplied username or email,
	//
	GetUserByUsernameEmail(username string, email string, userinfo *UserInfo) error

	// GetUserByPrincipalID fetches the UserInfo with supplied principal ID in the
	// container and fills in the supplied UserInfo with the result.
	//
	// Principal ID is an ID of an authenticated principal with such
	// authentication provided by AuthProvider.
	//
	// GetUserByPrincipalID returns ErrUserNotFound if no UserInfo exists
	// for the supplied principal ID.
	GetUserByPrincipalID(principalID string, userinfo *UserInfo) error

	// UpdateUser updates an existing UserInfo matched by the ID field.
	//
	// UpdateUser returns ErrUserNotFound if such UserInfo does not
	// exist in the container.
	UpdateUser(userinfo *UserInfo) error

	// QueryUser queries for UserInfo matching one of the specified emails.
	QueryUser(emails []string) ([]UserInfo, error)

	// DeleteUser removes UserInfo with the supplied ID in the container.
	//
	// DeleteUser returns ErrUserNotFound if such UserInfo does not
	// exist in the container.
	DeleteUser(id string) error

	// GetAdminRoles return the current admine roles
	GetAdminRoles() ([]string, error)

	// SetAdminRoles accepts array of role, the order will be
	SetAdminRoles(roles []string) error

	// SetDefaultRoles accepts array of roles, the supplied roles will assigned
	// to newly created user CreateUser
	SetDefaultRoles(roles []string) error

	// SetRecordAccess sets record access
	SetRecordAccess(recordType string, acl RecordACL) error

	// GetRecordAccess returns record creation access of a specific type
	GetRecordAccess(recordType string) (RecordACL, error)

	// GetAsset retrieves Asset information by its name
	GetAsset(name string, asset *Asset) error

	// SaveAsset saves an Asset information into a container to
	// be referenced by records.
	SaveAsset(asset *Asset) error

	QueryRelation(user string, name string, direction string, config QueryConfig) []UserInfo
	QueryRelationCount(user string, name string, direction string) (uint64, error)
	AddRelation(user string, name string, targetUser string) error
	RemoveRelation(user string, name string, targetUser string) error

	GetDevice(id string, device *Device) error

	// QueryDevicesByUser queries the Device database which are registered
	// by the specified user.
	QueryDevicesByUser(user string) ([]Device, error)
	SaveDevice(device *Device) error
	DeleteDevice(id string) error

	// DeleteDeviceByToken deletes device where its Token == token and
	// LastRegisteredAt < t. If t == ZeroTime, LastRegisteredAt is not considered.
	//
	// If such device does not exist, ErrDeviceNotFound is returned.
	DeleteDeviceByToken(token string, t time.Time) error

	// DeleteEmptyDevicesByTime deletes device where Token is empty and
	// LastRegisteredAt < t. If t == ZeroTime, LastRegisteredAt is not considered.
	//
	// If such device does not exist, ErrDeviceNotFound is returned.
	DeleteEmptyDevicesByTime(t time.Time) error

	PublicDB() Database
	PrivateDB(userKey string) Database

	// Subscribe registers the specified recordEventChan to receive
	// RecordEvent from the Conn implementation
	Subscribe(recordEventChan chan RecordEvent) error

	Close() error
}

// AccessModel indicates the type of access control model while db query.
//go:generate stringer -type=AccessModel
type AccessModel int

// RoleBasedAccess is tranditional Role based Access Control
// RelationBasedAccess is Access Control determine by the user-user relation
// between creator and accessor
const (
	RoleBasedAccess AccessModel = iota + 1
	RelationBasedAccess
)

// RecordHookEvent indicates the type of record event that triggered
// the hook
type RecordHookEvent int

// See the definition of RecordHookEvent
const (
	RecordCreated RecordHookEvent = iota + 1
	RecordUpdated
	RecordDeleted
)

// RecordEvent describes a change event on Record which is either
// Created, Updated or Deleted.
//
// For RecordCreated or RecordUpdated event, Record is the newly
// created / updated Record. For RecordDeleted, Record is the Record
// being deleted.
type RecordEvent struct {
	Record *Record
	Event  RecordHookEvent
}
<commit_msg>Update method description for get/create record access #594<commit_after>package skydb

import (
	"errors"
	"time"
)

// ErrUserDuplicated is returned by Conn.CreateUser when
// the UserInfo to be created has the same ID/username in the current container
var ErrUserDuplicated = errors.New("skydb: duplicated UserID or Username")

// ErrUserNotFound is returned by Conn.GetUser, Conn.UpdateUser and
// Conn.DeleteUser when the UserInfo's ID is not found
// in the current container
var ErrUserNotFound = errors.New("skydb: UserInfo ID not found")

var ErrRoleUpdatesFailed = errors.New("skydb: Update of user roles failed")

// ErrDeviceNotFound is returned by Conn.GetDevice, Conn.DeleteDevice,
// Conn.DeleteDeviceByToken and Conn.DeleteEmptyDevicesByTime, if the desired Device
// cannot be found in the current container
var ErrDeviceNotFound = errors.New("skydb: Specific device not found")

// ZeroTime represent a zero time.Time. It is used in DeleteDeviceByToken and
// DeleteEmptyDevicesByTime to signify a Delete without time constraint.
var ZeroTime = time.Time{}

// DBHookFunc specifies the interface of a database hook function
type DBHookFunc func(Database, *Record, RecordHookEvent)

// QueryConfig provides optional parameters for queries.
// result is unlimited if Limit=0
type QueryConfig struct {
	Limit  uint64
	Offset uint64
}

// Conn encapsulates the interface of an Skygear connection to a container.
type Conn interface {
	// CRUD of UserInfo, smell like a bad design to attach these onto
	// a Conn, but looks very convenient to user.

	// CreateUser creates a new UserInfo in the container
	// this Conn associated to.
	CreateUser(userinfo *UserInfo) error

	// GetUser fetches the UserInfo with supplied ID in the container and
	// fills in the supplied UserInfo with the result.
	//
	// GetUser returns ErrUserNotFound if no UserInfo exists
	// for the supplied ID.
	GetUser(id string, userinfo *UserInfo) error

	// GetUserByUsernameEmail fetch the UserInfo with supplied username or email,
	//
	GetUserByUsernameEmail(username string, email string, userinfo *UserInfo) error

	// GetUserByPrincipalID fetches the UserInfo with supplied principal ID in the
	// container and fills in the supplied UserInfo with the result.
	//
	// Principal ID is an ID of an authenticated principal with such
	// authentication provided by AuthProvider.
	//
	// GetUserByPrincipalID returns ErrUserNotFound if no UserInfo exists
	// for the supplied principal ID.
	GetUserByPrincipalID(principalID string, userinfo *UserInfo) error

	// UpdateUser updates an existing UserInfo matched by the ID field.
	//
	// UpdateUser returns ErrUserNotFound if such UserInfo does not
	// exist in the container.
	UpdateUser(userinfo *UserInfo) error

	// QueryUser queries for UserInfo matching one of the specified emails.
	QueryUser(emails []string) ([]UserInfo, error)

	// DeleteUser removes UserInfo with the supplied ID in the container.
	//
	// DeleteUser returns ErrUserNotFound if such UserInfo does not
	// exist in the container.
	DeleteUser(id string) error

	// GetAdminRoles return the current admine roles
	GetAdminRoles() ([]string, error)

	// SetAdminRoles accepts array of role, the order will be
	SetAdminRoles(roles []string) error

	// SetDefaultRoles accepts array of roles, the supplied roles will assigned
	// to newly created user CreateUser
	SetDefaultRoles(roles []string) error

	// SetRecordAccess sets default record access of a specific type
	SetRecordAccess(recordType string, acl RecordACL) error

	// GetRecordAccess returns default record access of a specific type
	GetRecordAccess(recordType string) (RecordACL, error)

	// GetAsset retrieves Asset information by its name
	GetAsset(name string, asset *Asset) error

	// SaveAsset saves an Asset information into a container to
	// be referenced by records.
	SaveAsset(asset *Asset) error

	QueryRelation(user string, name string, direction string, config QueryConfig) []UserInfo
	QueryRelationCount(user string, name string, direction string) (uint64, error)
	AddRelation(user string, name string, targetUser string) error
	RemoveRelation(user string, name string, targetUser string) error

	GetDevice(id string, device *Device) error

	// QueryDevicesByUser queries the Device database which are registered
	// by the specified user.
	QueryDevicesByUser(user string) ([]Device, error)
	SaveDevice(device *Device) error
	DeleteDevice(id string) error

	// DeleteDeviceByToken deletes device where its Token == token and
	// LastRegisteredAt < t. If t == ZeroTime, LastRegisteredAt is not considered.
	//
	// If such device does not exist, ErrDeviceNotFound is returned.
	DeleteDeviceByToken(token string, t time.Time) error

	// DeleteEmptyDevicesByTime deletes device where Token is empty and
	// LastRegisteredAt < t. If t == ZeroTime, LastRegisteredAt is not considered.
	//
	// If such device does not exist, ErrDeviceNotFound is returned.
	DeleteEmptyDevicesByTime(t time.Time) error

	PublicDB() Database
	PrivateDB(userKey string) Database

	// Subscribe registers the specified recordEventChan to receive
	// RecordEvent from the Conn implementation
	Subscribe(recordEventChan chan RecordEvent) error

	Close() error
}

// AccessModel indicates the type of access control model while db query.
//go:generate stringer -type=AccessModel
type AccessModel int

// RoleBasedAccess is tranditional Role based Access Control
// RelationBasedAccess is Access Control determine by the user-user relation
// between creator and accessor
const (
	RoleBasedAccess AccessModel = iota + 1
	RelationBasedAccess
)

// RecordHookEvent indicates the type of record event that triggered
// the hook
type RecordHookEvent int

// See the definition of RecordHookEvent
const (
	RecordCreated RecordHookEvent = iota + 1
	RecordUpdated
	RecordDeleted
)

// RecordEvent describes a change event on Record which is either
// Created, Updated or Deleted.
//
// For RecordCreated or RecordUpdated event, Record is the newly
// created / updated Record. For RecordDeleted, Record is the Record
// being deleted.
type RecordEvent struct {
	Record *Record
	Event  RecordHookEvent
}
<|endoftext|>


<commit_before>package ghalloc

import "unsafe"

type slabClass struct {
	Capacity  uint64 // Number of chunks of the given class.
	ChunkSize int    // Chunk size of the given class.
	SlabSize  int
	slabs     []*slab // Array of slabs of the given class.
}

func newSlabClass(chunkSize, slabSize int) *slabClass {
	sc := &slabClass{
		Capacity:  uint64(slabSize / chunkSize),
		ChunkSize: chunkSize,
		SlabSize:  slabSize,
	}

	sc.slabs = []*slab{newSlab(sc)}

	return sc
}

// Allocate new slab.
func (s *slabClass) grow() {
	s.slabs = append(s.slabs, newSlab(s))
}

// Get pointer to a free chunk in the given slab class.
func (s *slabClass) getChunk() (unsafe.Pointer, error) {
	return nil, nil
}
<commit_msg>Refactor slabClass.grow<commit_after>package ghalloc

import "unsafe"

type slabClass struct {
	Capacity  uint64 // Number of chunks of the given class.
	ChunkSize int    // Chunk size of the given class.
	SlabSize  int
	slabs     []*slab // Array of slabs of the given class.
}

func newSlabClass(chunkSize, slabSize int) *slabClass {
	sc := &slabClass{
		Capacity:  uint64(slabSize / chunkSize),
		ChunkSize: chunkSize,
		SlabSize:  slabSize,
	}

	sc.slabs = []*slab{newSlab(sc)}

	return sc
}

// Allocate new slab.
func (s *slabClass) grow() *slab {
	ns := newSlab(s)
	s.slabs = append(s.slabs, ns)
	return ns
}

// Get pointer to a free chunk in the given slab class.
func (s *slabClass) getChunk() (unsafe.Pointer, error) {
	return nil, nil
}
<|endoftext|>


<commit_before>package socket

import (
	"encoding/json"
	"log"
	"strings"

	"../engine"
	"../login"
)

// Hub is the default socker server struct
type Hub struct {
	clients    map[*client]bool
	broadcast  chan *packet
	request    chan *packet
	register   chan *client
	unregister chan *client
	mode       string
	users      *login.UserList
}

// NewHub provides a pointer to a new default socket server
func NewHub(modeStr string, userList *login.UserList) *Hub {
	return &Hub{
		broadcast:  make(chan *packet),
		request:    make(chan *packet),
		register:   make(chan *client),
		unregister: make(chan *client),
		clients:    make(map[*client]bool),
		mode:       modeStr,
		users:      userList,
	}
}

// Run initiates channels for actions
func (h *Hub) Run() {
	for {
		select {
		case client := <-h.register:
			h.connect(client)
		case client := <-h.unregister:
			h.disconnect(client)
		case request := <-h.request:
			h.intakeRequest(request)
		case message := <-h.broadcast:
			h.sendBroadcast(message)
		}
	}
}

func (h *Hub) connect(client *client) {
	if h.mode == "Debug" {
		log.Println("Connecting socket @", client.conn.RemoteAddr(), client.Tag)
	}
	h.clients[client] = true
	for i, v := range h.users.SafeList {
		if v.Name == client.Tag {
			// MARKER Server -> Client connected.
			// TODO implement smart client connections to establish first player
			h.users.SafeList[i].Status = "Online"
			h.users.SafeList[i].Connections++
			var pack packet
			pack.ID = client.Tag
			data, _ := json.Marshal(h.users.SafeList)
			pack.Data = "{\"user_list\": " + string(data) + "}"
			h.sendBroadcast(&pack)
			return
		}
	}
}

func (h *Hub) disconnect(client *client) {
	if h.mode == "Debug" {
		log.Println("Disconnecting socket @", client.conn.RemoteAddr(), client.Tag)
	}
	if _, ok := h.clients[client]; ok {
		delete(h.clients, client)
		close(client.send)
	}
	for i, v := range h.users.SafeList {
		if v.Name == client.Tag {
			// MARKER Server -> Client disconnected.
			// TODO implement smart client connections to establish first player on disconnect as well
			h.users.SafeList[i].Connections--
			if h.users.SafeList[i].Connections <= 0 {
				h.users.SafeList[i].Connections = 0
				h.users.SafeList[i].Status = "Offline"
			}
			var pack packet
			pack.ID = client.Tag
			data, _ := json.Marshal(h.users.SafeList)
			pack.Data = "{\"user_list\": " + string(data) + "}"
			h.sendBroadcast(&pack)
			return
		}
	}
}

func (h *Hub) intakeRequest(packet *packet) {
	if h.mode == "Debug" {
		log.Println("Got packet: " + packet.Data + " From: " + packet.ID)
	}
	var req request
	if err := json.Unmarshal([]byte(packet.Data), &req); err != nil {
		log.Println("REQUEST ERROR!!! : ", err)
		return
	}
	// MARKER Server -> Socket server received data from client.
	if req.Type == "Click" {
		if req.X < 0 || req.Y < 0 || req.X >= 60 || req.Y >= 40 {
			packet.Data = "{\"error\":\"Click is out of bounds\"}"
			h.sendMessage(packet)
			return
		}
		validMove, err := engine.GameInstance.ProcessClick(packet.ID, req.X, req.Y)
		if validMove {
			packet.Data = string(engine.GameInstance.GetData(packet.ID, "MapData"))
			h.sendBroadcast(packet)
			return
		}
		packet.Data = "{\"error\":\"" + err.Error() + "\"}"
		h.sendMessage(packet)
		return
	}
	if req.Type == "StartGame" {
		for _, v := range h.users.List {
			if v.Name == packet.ID && v.Admin == true {
				names := strings.Split(req.Message, "")
				count := len(names)
				if count < 2 || count > 4 {
					packet.Data = "{\"error\":\"This server is setup to only support 2-4 players\"}"
					h.sendMessage(packet)
					return
				}
				for _, name := range names {
					found := false
					for _, v2 := range h.users.List {
						if v2.Name == name {
							found = true
						}
					}
					if !found {
						packet.Data = "{\"error\":\"Submitted name: " + name + " not found on server\"}"
						h.sendMessage(packet)
						return
					}
				}
				engine.GameInstance.StartGame(names)
				packet.Data = "{\"success\":\"Game Started\"}"
				h.sendBroadcast(packet)
				return
			}
		}
	}
	if req.Type == "MapData" {
		packet.Data = string(engine.GameInstance.GetData(packet.ID, "MapData"))
		h.sendBroadcast(packet)
		return
	}
	if req.Type == "ChatMessage" {
		packet.Data = "{\"message\":\"" + req.Message + "\", \"author\": \"" + packet.ID + "\"}"
		h.sendBroadcast(packet)
		return
	}
}

func (h *Hub) sendMessage(packet *packet) {
	if h.mode == "Debug" {
		log.Println("Sending message to : " + packet.ID)
	}
	for client := range h.clients {
		if client.Tag == packet.ID {
			select {
			case client.send <- []byte(packet.Data):
				if h.mode == "Debug" {
					log.Println(client.conn.RemoteAddr(), client.Tag, "received data")
				}
			default:
				if h.mode == "Debug" {
					log.Println("Default Condition -- No message into client.send")
				}
				close(client.send)
				delete(h.clients, client)
			}
		}
	}
}

func (h *Hub) sendBroadcast(packet *packet) {
	if h.mode == "Debug" {
		log.Println("Broadcasting from: " + packet.ID)
	}
	for client := range h.clients {
		select {
		case client.send <- []byte(packet.Data):
			if h.mode == "Debug" {
				log.Println(client.conn.RemoteAddr(), client.Tag, "received data")
			}
		default:
			if h.mode == "Debug" {
				log.Println("Default Condition -- No message into client.send")
			}
			close(client.send)
			delete(h.clients, client)
		}
	}
}
<commit_msg>Remove todo comments that are no longer necessary due to design change<commit_after>package socket

import (
	"encoding/json"
	"log"
	"strings"

	"../engine"
	"../login"
)

// Hub is the default socker server struct
type Hub struct {
	clients    map[*client]bool
	broadcast  chan *packet
	request    chan *packet
	register   chan *client
	unregister chan *client
	mode       string
	users      *login.UserList
}

// NewHub provides a pointer to a new default socket server
func NewHub(modeStr string, userList *login.UserList) *Hub {
	return &Hub{
		broadcast:  make(chan *packet),
		request:    make(chan *packet),
		register:   make(chan *client),
		unregister: make(chan *client),
		clients:    make(map[*client]bool),
		mode:       modeStr,
		users:      userList,
	}
}

// Run initiates channels for actions
func (h *Hub) Run() {
	for {
		select {
		case client := <-h.register:
			h.connect(client)
		case client := <-h.unregister:
			h.disconnect(client)
		case request := <-h.request:
			h.intakeRequest(request)
		case message := <-h.broadcast:
			h.sendBroadcast(message)
		}
	}
}

func (h *Hub) connect(client *client) {
	if h.mode == "Debug" {
		log.Println("Connecting socket @", client.conn.RemoteAddr(), client.Tag)
	}
	h.clients[client] = true
	for i, v := range h.users.SafeList {
		if v.Name == client.Tag {
			// MARKER Server -> Client connected.
			h.users.SafeList[i].Status = "Online"
			h.users.SafeList[i].Connections++
			var pack packet
			pack.ID = client.Tag
			data, _ := json.Marshal(h.users.SafeList)
			pack.Data = "{\"user_list\": " + string(data) + "}"
			h.sendBroadcast(&pack)
			return
		}
	}
}

func (h *Hub) disconnect(client *client) {
	if h.mode == "Debug" {
		log.Println("Disconnecting socket @", client.conn.RemoteAddr(), client.Tag)
	}
	if _, ok := h.clients[client]; ok {
		delete(h.clients, client)
		close(client.send)
	}
	for i, v := range h.users.SafeList {
		if v.Name == client.Tag {
			// MARKER Server -> Client disconnected.
			h.users.SafeList[i].Connections--
			if h.users.SafeList[i].Connections <= 0 {
				h.users.SafeList[i].Connections = 0
				h.users.SafeList[i].Status = "Offline"
			}
			var pack packet
			pack.ID = client.Tag
			data, _ := json.Marshal(h.users.SafeList)
			pack.Data = "{\"user_list\": " + string(data) + "}"
			h.sendBroadcast(&pack)
			return
		}
	}
}

func (h *Hub) intakeRequest(packet *packet) {
	if h.mode == "Debug" {
		log.Println("Got packet: " + packet.Data + " From: " + packet.ID)
	}
	var req request
	if err := json.Unmarshal([]byte(packet.Data), &req); err != nil {
		log.Println("REQUEST ERROR!!! : ", err)
		return
	}
	// MARKER Server -> Socket server received data from client.
	if req.Type == "Click" {
		if req.X < 0 || req.Y < 0 || req.X >= 60 || req.Y >= 40 {
			packet.Data = "{\"error\":\"Click is out of bounds\"}"
			h.sendMessage(packet)
			return
		}
		validMove, err := engine.GameInstance.ProcessClick(packet.ID, req.X, req.Y)
		if validMove {
			packet.Data = string(engine.GameInstance.GetData(packet.ID, "MapData"))
			h.sendBroadcast(packet)
			return
		}
		packet.Data = "{\"error\":\"" + err.Error() + "\"}"
		h.sendMessage(packet)
		return
	}
	if req.Type == "StartGame" {
		for _, v := range h.users.List {
			if v.Name == packet.ID && v.Admin == true {
				names := strings.Split(req.Message, "")
				count := len(names)
				if count < 2 || count > 4 {
					packet.Data = "{\"error\":\"This server is setup to only support 2-4 players\"}"
					h.sendMessage(packet)
					return
				}
				for _, name := range names {
					found := false
					for _, v2 := range h.users.List {
						if v2.Name == name {
							found = true
						}
					}
					if !found {
						packet.Data = "{\"error\":\"Submitted name: " + name + " not found on server\"}"
						h.sendMessage(packet)
						return
					}
				}
				engine.GameInstance.StartGame(names)
				packet.Data = "{\"success\":\"Game Started\"}"
				h.sendBroadcast(packet)
				return
			}
		}
	}
	if req.Type == "MapData" {
		packet.Data = string(engine.GameInstance.GetData(packet.ID, "MapData"))
		h.sendBroadcast(packet)
		return
	}
	if req.Type == "ChatMessage" {
		packet.Data = "{\"message\":\"" + req.Message + "\", \"author\": \"" + packet.ID + "\"}"
		h.sendBroadcast(packet)
		return
	}
}

func (h *Hub) sendMessage(packet *packet) {
	if h.mode == "Debug" {
		log.Println("Sending message to : " + packet.ID)
	}
	for client := range h.clients {
		if client.Tag == packet.ID {
			select {
			case client.send <- []byte(packet.Data):
				if h.mode == "Debug" {
					log.Println(client.conn.RemoteAddr(), client.Tag, "received data")
				}
			default:
				if h.mode == "Debug" {
					log.Println("Default Condition -- No message into client.send")
				}
				close(client.send)
				delete(h.clients, client)
			}
		}
	}
}

func (h *Hub) sendBroadcast(packet *packet) {
	if h.mode == "Debug" {
		log.Println("Broadcasting from: " + packet.ID)
	}
	for client := range h.clients {
		select {
		case client.send <- []byte(packet.Data):
			if h.mode == "Debug" {
				log.Println(client.conn.RemoteAddr(), client.Tag, "received data")
			}
		default:
			if h.mode == "Debug" {
				log.Println("Default Condition -- No message into client.send")
			}
			close(client.send)
			delete(h.clients, client)
		}
	}
}
<|endoftext|>


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

package state

import (
	"time"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/names"
	"github.com/juju/utils"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"gopkg.in/mgo.v2/txn"
)

var actionLogger = loggo.GetLogger("juju.state.action")

// ActionStatus represents the possible end states for an action.
type ActionStatus string

const (
	// ActionFailed signifies that the action did not complete successfully.
	ActionFailed ActionStatus = "failed"

	// ActionCompleted indicates that the action ran to completion as intended.
	ActionCompleted ActionStatus = "completed"

	// ActionCancelled means that the Action was cancelled before being run.
	ActionCancelled ActionStatus = "cancelled"

	// ActionPending is the default status when an Action is first queued.
	ActionPending ActionStatus = "pending"
)
const actionMarker string = "_a_"

type actionNotificationDoc struct {
	// DocId is the composite _id that can be matched by an
	// idPrefixWatcher that is configured to watch for the
	// ActionReceiver Name() which makes up the first part of this
	// composite _id.
	DocId string `bson:"_id"`

	// EnvUUID is the environment identifier.
	EnvUUID string `bson:"env-uuid"`

	// Receiver is the Name of the Unit or any other ActionReceiver for
	// which this notification is queued.
	Receiver string `bson:"receiver"`

	// ActionID is the unique identifier for the Action this notification
	// represents.
	ActionID string `bson:"actionid"`
}

type actionDoc struct {
	// DocId is the key for this document; it is a UUID.
	DocId string `bson:"_id"`

	// EnvUUID is the environment identifier.
	EnvUUID string `bson:"env-uuid"`

	// Receiver is the Name of the Unit or any other ActionReceiver for
	// which this Action is queued.
	Receiver string `bson:"receiver"`

	// Name identifies the action that should be run; it should
	// match an action defined by the unit's charm.
	Name string `bson:"name"`

	// Parameters holds the action's parameters, if any; it should validate
	// against the schema defined by the named action in the unit's charm.
	Parameters map[string]interface{} `bson:"parameters"`

	// Enqueued is the time the action was added.
	Enqueued time.Time `bson:"enqueued"`

	// Status represents the end state of the Action; ActionFailed for an
	// action that was removed prematurely, or that failed, and
	// ActionCompleted for an action that successfully completed.
	Status ActionStatus `bson:"status"`

	// Message captures any error returned by the action.
	Message string `bson:"message"`

	// Results are the structured results from the action.
	Results map[string]interface{} `bson:"results"`

	// Completed reflects the time that the action was Finished.
	Completed time.Time `bson:"completed"`
}

// Action represents an instruction to do some "action" and is expected
// to match an action definition in a charm.
type Action struct {
	st  *State
	doc actionDoc
}

// Id returns the local name of the Action.
func (a *Action) Id() string {
	return a.st.localID(a.doc.DocId)
}

// NotificationId returns the Id used in the notification watcher.
func (a *Action) NotificationId() string {
	return ensureActionMarker(a.Receiver()) + a.Id()
}

// Receiver returns the Name of the ActionReceiver for which this action
// is enqueued.  Usually this is a Unit Name().
func (a *Action) Receiver() string {
	return a.doc.Receiver
}

// Name returns the name of the action, as defined in the charm.
func (a *Action) Name() string {
	return a.doc.Name
}

// Parameters will contain a structure representing arguments or parameters to
// an action, and is expected to be validated by the Unit using the Charm
// definition of the Action.
func (a *Action) Parameters() map[string]interface{} {
	return a.doc.Parameters
}

// Enqueued returns the time the action was added to state as a pending
// Action.
func (a *Action) Enqueued() time.Time {
	return a.doc.Enqueued
}

// Status returns the final state of the action.
func (a *Action) Status() ActionStatus {
	return a.doc.Status
}

// Results returns the structured output of the action and any error.
func (a *Action) Results() (map[string]interface{}, string) {
	return a.doc.Results, a.doc.Message
}

// Completed returns the completion time of the Action.
func (a *Action) Completed() time.Time {
	return a.doc.Completed
}

// ValidateTag should be called before calls to Tag() or ActionTag(). It verifies
// that the Action can produce a valid Tag.
func (a *Action) ValidateTag() bool {
	_, ok := names.ParseActionTagFromParts(a.Receiver(), a.Id())
	return ok
}

// Tag implements the Entity interface and returns a names.Tag that
// is a names.ActionTag.
func (a *Action) Tag() names.Tag {
	return a.ActionTag()
}

// ActionTag returns an ActionTag constructed from this action's
// Prefix and Sequence.
func (a *Action) ActionTag() names.ActionTag {
	return names.JoinActionTag(a.Receiver(), a.Id())
}

// ActionResults is a data transfer object that holds the key Action
// output and results information.
type ActionResults struct {
	Status  ActionStatus           `json:"status"`
	Results map[string]interface{} `json:"results"`
	Message string                 `json:"message"`
}

// Finish removes action from the pending queue and captures the output
// and end state of the action.
func (a *Action) Finish(results ActionResults) (*Action, error) {
	return a.removeAndLog(results.Status, results.Results, results.Message)
}

// removeAndLog takes the action off of the pending queue, and creates
// an actionresult to capture the outcome of the action.
func (a *Action) removeAndLog(finalStatus ActionStatus, results map[string]interface{}, message string) (*Action, error) {
	err := a.st.runTransaction([]txn.Op{
		{
			C:  actionsC,
			Id: a.doc.DocId,
			Update: bson.D{{"$set", bson.D{
				{"status", finalStatus},
				{"message", message},
				{"results", results},
				{"completed", nowToTheSecond()},
			}}},
		}, {
			C:      actionNotificationsC,
			Id:     a.st.docID(ensureActionMarker(a.Receiver()) + a.Id()),
			Remove: true,
		}})
	if err != nil {
		return nil, err
	}
	return a.st.Action(a.Id())
}

// newActionTagFromNotification converts an actionNotificationDoc into
// an names.ActionTag
func newActionTagFromNotification(doc actionNotificationDoc) names.ActionTag {
	actionLogger.Debugf("newActionTagFromNotification doc: '%#v'", doc)
	return names.JoinActionTag(doc.Receiver, doc.ActionID)
}

// newAction builds an Action for the given State and actionDoc.
func newAction(st *State, adoc actionDoc) *Action {
	return &Action{
		st:  st,
		doc: adoc,
	}
}

// newActionDoc builds the actionDoc with the given name and parameters.
func newActionDoc(st *State, receiverTag names.Tag, actionName string, parameters map[string]interface{}) (actionDoc, actionNotificationDoc, error) {
	prefix := ensureActionMarker(receiverTag.Id())
	actionId, err := utils.NewUUID()
	if err != nil {
		return actionDoc{}, actionNotificationDoc{}, err
	}
	actionLogger.Debugf("newActionDoc name: '%s', receiver: '%s', actionId: '%s'", actionName, receiverTag, actionId)
	envuuid := st.EnvironUUID()
	return actionDoc{
			DocId:      st.docID(actionId.String()),
			EnvUUID:    envuuid,
			Receiver:   receiverTag.Id(),
			Name:       actionName,
			Parameters: parameters,
			Enqueued:   nowToTheSecond(),
			Status:     ActionPending,
		}, actionNotificationDoc{
			DocId:    st.docID(prefix + actionId.String()),
			EnvUUID:  envuuid,
			Receiver: receiverTag.Id(),
			ActionID: actionId.String(),
		}, nil
}

var ensureActionMarker = ensureSuffixFn(actionMarker)

// Action returns an Action by Id.
func (st *State) Action(id string) (*Action, error) {
	actions, closer := st.getCollection(actionsC)
	defer closer()

	doc := actionDoc{}
	err := actions.FindId(st.docID(id)).One(&doc)
	if err == mgo.ErrNotFound {
		return nil, errors.NotFoundf("action %q", id)
	}
	if err != nil {
		return nil, errors.Annotatef(err, "cannot get action %q", id)
	}

	return newAction(st, doc), nil
}

// ActionByTag returns an Action given an ActionTag.
func (st *State) ActionByTag(tag names.ActionTag) (*Action, error) {
	return st.Action(tag.Suffix())
}

func (st *State) addAction(ar ActionReceiver, actionName string, payload map[string]interface{}) (*Action, error) {
	receiverCollectionName, receiverId, err := st.parseTag(ar.Tag())
	if err != nil {
		return nil, errors.Trace(err)
	}

	doc, ndoc, err := newActionDoc(st, ar.Tag(), actionName, payload)
	if err != nil {
		return nil, errors.Trace(err)
	}

	ops := []txn.Op{{
		C:      receiverCollectionName,
		Id:     receiverId,
		Assert: notDeadDoc,
	}, {
		C:      actionsC,
		Id:     doc.DocId,
		Assert: txn.DocMissing,
		Insert: doc,
	}, {
		C:      actionNotificationsC,
		Id:     ndoc.DocId,
		Assert: txn.DocMissing,
		Insert: ndoc,
	}}

	buildTxn := func(attempt int) ([]txn.Op, error) {
		if notDead, err := isNotDead(st.db, receiverCollectionName, receiverId); err != nil {
			return nil, err
		} else if !notDead {
			return nil, ErrDead
		}
		return ops, nil
	}
	if err = st.run(buildTxn); err == nil {
		return newAction(st, doc), nil
	}
	return nil, err
}

// matchingActions finds actions that match ActionReceiver.
func (st *State) matchingActions(ar ActionReceiver) ([]*Action, error) {
	return st.matchingActionsByReceiverId(ar.Tag().Id())
}

// matchingActionsByReceiverId finds actions that match ActionReceiver name.
func (st *State) matchingActionsByReceiverId(id string) ([]*Action, error) {
	var doc actionDoc
	var actions []*Action

	actionsCollection, closer := st.getCollection(actionsC)
	defer closer()

	envuuid := st.EnvironUUID()
	sel := bson.D{{"env-uuid", envuuid}, {"receiver", id}}
	iter := actionsCollection.Find(sel).Iter()

	for iter.Next(&doc) {
		actions = append(actions, newAction(st, doc))
	}
	return actions, errors.Trace(iter.Close())
}

// matchingActionNotifications finds actionNotifications that match ActionReceiver.
func (st *State) matchingActionNotifications(ar ActionReceiver) ([]names.ActionTag, error) {
	return st.matchingActionNotificationsByReceiverId(ar.Tag().Id())
}

// matchingActionNotificationsByReceiverId finds actionNotifications that match ActionReceiver.
func (st *State) matchingActionNotificationsByReceiverId(id string) ([]names.ActionTag, error) {
	var doc actionNotificationDoc
	var tags []names.ActionTag

	notificationCollection, closer := st.getCollection(actionNotificationsC)
	defer closer()

	envuuid := st.EnvironUUID()
	sel := bson.D{{"env-uuid", envuuid}, {"receiver", id}}
	iter := notificationCollection.Find(sel).Iter()

	for iter.Next(&doc) {
		tags = append(tags, newActionTagFromNotification(doc))
	}
	return tags, errors.Trace(iter.Close())
}

// matchingActionsPending finds actions that match ActionReceiver and
// that are pending.
func (st *State) matchingActionsPending(ar ActionReceiver) ([]*Action, error) {
	completed := bson.D{{"status", ActionPending}}
	return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed)
}

// matchingActionsCompleted finds actions that match ActionReceiver and
// that are complete.
func (st *State) matchingActionsCompleted(ar ActionReceiver) ([]*Action, error) {
	completed := bson.D{{"$or", []bson.D{
		{{"status", ActionCompleted}},
		{{"status", ActionCancelled}},
		{{"status", ActionFailed}},
	}}}
	return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed)
}

// matchingActionsByReceiverAndStatus finds actionNotifications that
// match ActionReceiver.
func (st *State) matchingActionsByReceiverAndStatus(tag names.Tag, statusCondition bson.D) ([]*Action, error) {
	var doc actionDoc
	var actions []*Action

	actionsCollection, closer := st.getCollection(actionsC)
	defer closer()

	envuuid := st.EnvironUUID()

	condition := []bson.D{{{"env-uuid", envuuid}}, {{"receiver", tag.Id()}}, statusCondition}
	sel := bson.D{{"$and", condition}}
	iter := actionsCollection.Find(sel).Iter()

	for iter.Next(&doc) {
		actions = append(actions, newAction(st, doc))
	}
	return actions, errors.Trace(iter.Close())
}
<commit_msg>Fix for feedback from review<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"time"

	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/names"
	"github.com/juju/utils"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"gopkg.in/mgo.v2/txn"
)

var actionLogger = loggo.GetLogger("juju.state.action")

// ActionStatus represents the possible end states for an action.
type ActionStatus string

const (
	// ActionFailed signifies that the action did not complete successfully.
	ActionFailed ActionStatus = "failed"

	// ActionCompleted indicates that the action ran to completion as intended.
	ActionCompleted ActionStatus = "completed"

	// ActionCancelled means that the Action was cancelled before being run.
	ActionCancelled ActionStatus = "cancelled"

	// ActionPending is the default status when an Action is first queued.
	ActionPending ActionStatus = "pending"
)
const actionMarker string = "_a_"

type actionNotificationDoc struct {
	// DocId is the composite _id that can be matched by an
	// idPrefixWatcher that is configured to watch for the
	// ActionReceiver Name() which makes up the first part of this
	// composite _id.
	DocId string `bson:"_id"`

	// EnvUUID is the environment identifier.
	EnvUUID string `bson:"env-uuid"`

	// Receiver is the Name of the Unit or any other ActionReceiver for
	// which this notification is queued.
	Receiver string `bson:"receiver"`

	// ActionID is the unique identifier for the Action this notification
	// represents.
	ActionID string `bson:"actionid"`
}

type actionDoc struct {
	// DocId is the key for this document; it is a UUID.
	DocId string `bson:"_id"`

	// EnvUUID is the environment identifier.
	EnvUUID string `bson:"env-uuid"`

	// Receiver is the Name of the Unit or any other ActionReceiver for
	// which this Action is queued.
	Receiver string `bson:"receiver"`

	// Name identifies the action that should be run; it should
	// match an action defined by the unit's charm.
	Name string `bson:"name"`

	// Parameters holds the action's parameters, if any; it should validate
	// against the schema defined by the named action in the unit's charm.
	Parameters map[string]interface{} `bson:"parameters"`

	// Enqueued is the time the action was added.
	Enqueued time.Time `bson:"enqueued"`

	// Status represents the end state of the Action; ActionFailed for an
	// action that was removed prematurely, or that failed, and
	// ActionCompleted for an action that successfully completed.
	Status ActionStatus `bson:"status"`

	// Message captures any error returned by the action.
	Message string `bson:"message"`

	// Results are the structured results from the action.
	Results map[string]interface{} `bson:"results"`

	// Completed reflects the time that the action was Finished.
	Completed time.Time `bson:"completed"`
}

// Action represents an instruction to do some "action" and is expected
// to match an action definition in a charm.
type Action struct {
	st  *State
	doc actionDoc
}

// Id returns the local name of the Action.
func (a *Action) Id() string {
	return a.st.localID(a.doc.DocId)
}

// NotificationId returns the Id used in the notification watcher.
func (a *Action) NotificationId() string {
	return ensureActionMarker(a.Receiver()) + a.Id()
}

// Receiver returns the Name of the ActionReceiver for which this action
// is enqueued.  Usually this is a Unit Name().
func (a *Action) Receiver() string {
	return a.doc.Receiver
}

// Name returns the name of the action, as defined in the charm.
func (a *Action) Name() string {
	return a.doc.Name
}

// Parameters will contain a structure representing arguments or parameters to
// an action, and is expected to be validated by the Unit using the Charm
// definition of the Action.
func (a *Action) Parameters() map[string]interface{} {
	return a.doc.Parameters
}

// Enqueued returns the time the action was added to state as a pending
// Action.
func (a *Action) Enqueued() time.Time {
	return a.doc.Enqueued
}

// Status returns the final state of the action.
func (a *Action) Status() ActionStatus {
	return a.doc.Status
}

// Results returns the structured output of the action and any error.
func (a *Action) Results() (map[string]interface{}, string) {
	return a.doc.Results, a.doc.Message
}

// Completed returns the completion time of the Action.
func (a *Action) Completed() time.Time {
	return a.doc.Completed
}

// ValidateTag should be called before calls to Tag() or ActionTag(). It verifies
// that the Action can produce a valid Tag.
func (a *Action) ValidateTag() bool {
	_, ok := names.ParseActionTagFromParts(a.Receiver(), a.Id())
	return ok
}

// Tag implements the Entity interface and returns a names.Tag that
// is a names.ActionTag.
func (a *Action) Tag() names.Tag {
	return a.ActionTag()
}

// ActionTag returns an ActionTag constructed from this action's
// Prefix and Sequence.
func (a *Action) ActionTag() names.ActionTag {
	return names.JoinActionTag(a.Receiver(), a.Id())
}

// ActionResults is a data transfer object that holds the key Action
// output and results information.
type ActionResults struct {
	Status  ActionStatus           `json:"status"`
	Results map[string]interface{} `json:"results"`
	Message string                 `json:"message"`
}

// Finish removes action from the pending queue and captures the output
// and end state of the action.
func (a *Action) Finish(results ActionResults) (*Action, error) {
	return a.removeAndLog(results.Status, results.Results, results.Message)
}

// removeAndLog takes the action off of the pending queue, and creates
// an actionresult to capture the outcome of the action.
func (a *Action) removeAndLog(finalStatus ActionStatus, results map[string]interface{}, message string) (*Action, error) {
	err := a.st.runTransaction([]txn.Op{
		{
			C:  actionsC,
			Id: a.doc.DocId,
			Update: bson.D{{"$set", bson.D{
				{"status", finalStatus},
				{"message", message},
				{"results", results},
				{"completed", nowToTheSecond()},
			}}},
		}, {
			C:      actionNotificationsC,
			Id:     a.st.docID(ensureActionMarker(a.Receiver()) + a.Id()),
			Remove: true,
		}})
	if err != nil {
		return nil, err
	}
	return a.st.Action(a.Id())
}

// newActionTagFromNotification converts an actionNotificationDoc into
// an names.ActionTag
func newActionTagFromNotification(doc actionNotificationDoc) names.ActionTag {
	actionLogger.Debugf("newActionTagFromNotification doc: '%#v'", doc)
	return names.JoinActionTag(doc.Receiver, doc.ActionID)
}

// newAction builds an Action for the given State and actionDoc.
func newAction(st *State, adoc actionDoc) *Action {
	return &Action{
		st:  st,
		doc: adoc,
	}
}

// newActionDoc builds the actionDoc with the given name and parameters.
func newActionDoc(st *State, receiverTag names.Tag, actionName string, parameters map[string]interface{}) (actionDoc, actionNotificationDoc, error) {
	prefix := ensureActionMarker(receiverTag.Id())
	actionId, err := utils.NewUUID()
	if err != nil {
		return actionDoc{}, actionNotificationDoc{}, err
	}
	actionLogger.Debugf("newActionDoc name: '%s', receiver: '%s', actionId: '%s'", actionName, receiverTag, actionId)
	envuuid := st.EnvironUUID()
	return actionDoc{
			DocId:      st.docID(actionId.String()),
			EnvUUID:    envuuid,
			Receiver:   receiverTag.Id(),
			Name:       actionName,
			Parameters: parameters,
			Enqueued:   nowToTheSecond(),
			Status:     ActionPending,
		}, actionNotificationDoc{
			DocId:    st.docID(prefix + actionId.String()),
			EnvUUID:  envuuid,
			Receiver: receiverTag.Id(),
			ActionID: actionId.String(),
		}, nil
}

var ensureActionMarker = ensureSuffixFn(actionMarker)

// Action returns an Action by Id.
func (st *State) Action(id string) (*Action, error) {
	actions, closer := st.getCollection(actionsC)
	defer closer()

	doc := actionDoc{}
	err := actions.FindId(st.docID(id)).One(&doc)
	if err == mgo.ErrNotFound {
		return nil, errors.NotFoundf("action %q", id)
	}
	if err != nil {
		return nil, errors.Annotatef(err, "cannot get action %q", id)
	}

	return newAction(st, doc), nil
}

// ActionByTag returns an Action given an ActionTag.
func (st *State) ActionByTag(tag names.ActionTag) (*Action, error) {
	return st.Action(tag.Suffix())
}

func (st *State) addAction(ar ActionReceiver, actionName string, payload map[string]interface{}) (*Action, error) {
	receiverCollectionName, receiverId, err := st.parseTag(ar.Tag())
	if err != nil {
		return nil, errors.Trace(err)
	}

	doc, ndoc, err := newActionDoc(st, ar.Tag(), actionName, payload)
	if err != nil {
		return nil, errors.Trace(err)
	}

	ops := []txn.Op{{
		C:      receiverCollectionName,
		Id:     receiverId,
		Assert: notDeadDoc,
	}, {
		C:      actionsC,
		Id:     doc.DocId,
		Assert: txn.DocMissing,
		Insert: doc,
	}, {
		C:      actionNotificationsC,
		Id:     ndoc.DocId,
		Assert: txn.DocMissing,
		Insert: ndoc,
	}}

	buildTxn := func(attempt int) ([]txn.Op, error) {
		if notDead, err := isNotDead(st.db, receiverCollectionName, receiverId); err != nil {
			return nil, err
		} else if !notDead {
			return nil, ErrDead
		}
		return ops, nil
	}
	if err = st.run(buildTxn); err == nil {
		return newAction(st, doc), nil
	}
	return nil, err
}

// matchingActions finds actions that match ActionReceiver.
func (st *State) matchingActions(ar ActionReceiver) ([]*Action, error) {
	return st.matchingActionsByReceiverId(ar.Tag().Id())
}

// matchingActionsByReceiverId finds actions that match ActionReceiver name.
func (st *State) matchingActionsByReceiverId(id string) ([]*Action, error) {
	var doc actionDoc
	var actions []*Action

	actionsCollection, closer := st.getCollection(actionsC)
	defer closer()

	envuuid := st.EnvironUUID()
	sel := bson.D{{"env-uuid", envuuid}, {"receiver", id}}
	iter := actionsCollection.Find(sel).Iter()

	for iter.Next(&doc) {
		actions = append(actions, newAction(st, doc))
	}
	return actions, errors.Trace(iter.Close())
}

// matchingActionNotifications finds actionNotifications that match ActionReceiver.
func (st *State) matchingActionNotifications(ar ActionReceiver) ([]names.ActionTag, error) {
	return st.matchingActionNotificationsByReceiverId(ar.Tag().Id())
}

// matchingActionNotificationsByReceiverId finds actionNotifications that match ActionReceiver.
func (st *State) matchingActionNotificationsByReceiverId(id string) ([]names.ActionTag, error) {
	var doc actionNotificationDoc
	var tags []names.ActionTag

	notificationCollection, closer := st.getCollection(actionNotificationsC)
	defer closer()

	envuuid := st.EnvironUUID()
	sel := bson.D{{"$and", []bson.D{{{"env-uuid", envuuid}, {"receiver", id}}}}}
	iter := notificationCollection.Find(sel).Iter()

	for iter.Next(&doc) {
		tags = append(tags, newActionTagFromNotification(doc))
	}
	return tags, errors.Trace(iter.Close())
}

// matchingActionsPending finds actions that match ActionReceiver and
// that are pending.
func (st *State) matchingActionsPending(ar ActionReceiver) ([]*Action, error) {
	completed := bson.D{{"status", ActionPending}}
	return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed)
}

// matchingActionsCompleted finds actions that match ActionReceiver and
// that are complete.
func (st *State) matchingActionsCompleted(ar ActionReceiver) ([]*Action, error) {
	completed := bson.D{{"$or", []bson.D{
		{{"status", ActionCompleted}},
		{{"status", ActionCancelled}},
		{{"status", ActionFailed}},
	}}}
	return st.matchingActionsByReceiverAndStatus(ar.Tag(), completed)
}

// matchingActionsByReceiverAndStatus finds actionNotifications that
// match ActionReceiver.
func (st *State) matchingActionsByReceiverAndStatus(tag names.Tag, statusCondition bson.D) ([]*Action, error) {
	var doc actionDoc
	var actions []*Action

	actionsCollection, closer := st.getCollection(actionsC)
	defer closer()

	envuuid := st.EnvironUUID()

	condition := []bson.D{{{"env-uuid", envuuid}}, {{"receiver", tag.Id()}}, statusCondition}
	sel := bson.D{{"$and", condition}}
	iter := actionsCollection.Find(sel).Iter()

	for iter.Next(&doc) {
		actions = append(actions, newAction(st, doc))
	}
	return actions, errors.Trace(iter.Close())
}
<|endoftext|>


<commit_before>package anaconda

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/dustin/go-jsonpointer"
)

const (
	BaseUrlUserStream = "https://userstream.twitter.com/1.1"
	BaseUrlSiteStream = "https://sitestream.twitter.com/1.1"
	BaseUrlStream     = "https://stream.twitter.com/1.1"
)

// messages

type StatusDeletionNotice struct {
	Id        int64  `json:"id"`
	IdStr     string `json:"id_str"`
	UserId    int64  `json:"user_id"`
	UserIdStr string `json:"user_id_str"`
}
type statusDeletionNotice struct {
	Delete *struct {
		Status *StatusDeletionNotice `json:"status"`
	} `json:"delete"`
}

type LocationDeletionNotice struct {
	UserId          int64  `json:"user_id"`
	UserIdStr       string `json:"user_id_str"`
	UpToStatusId    int64  `json:"up_to_status_id"`
	UpToStatusIdStr string `json:"up_to_status_id_str"`
}
type locationDeletionNotice struct {
	ScrubGeo *LocationDeletionNotice `json:"scrub_geo"`
}

type LimitNotice struct {
	Track int64 `json:"track"`
}
type limitNotice struct {
	Limit *LimitNotice `json:"limit"`
}

type StatusWithheldNotice struct {
	Id                  int64    `json:"id"`
	UserId              int64    `json:"user_id"`
	WithheldInCountries []string `json:"withheld_in_countries"`
}
type statusWithheldNotice struct {
	StatusWithheld *StatusWithheldNotice `json:"status_withheld"`
}

type UserWithheldNotice struct {
	Id                  int64    `json:"id"`
	WithheldInCountries []string `json:"withheld_in_countries"`
}
type userWithheldNotice struct {
	UserWithheld *UserWithheldNotice `json:"user_withheld"`
}

type DisconnectMessage struct {
	Code       int64  `json:"code"`
	StreamName string `json:"stream_name"`
	Reason     string `json:"reason"`
}
type disconnectMessage struct {
	Disconnect *DisconnectMessage `json:"disconnect"`
}

type StallWarning struct {
	Code        string `json:"code"`
	Message     string `json:"message"`
	PercentFull int64  `json:"percent_full"`
}
type stallWarning struct {
	Warning *StallWarning `json:"warning"`
}

type FriendsList []int64
type friendsList struct {
	Friends *FriendsList `json:"friends"`
}

type streamDirectMessage struct {
	DirectMessage *DirectMessage `json:"direct_message"`
}

type Event struct {
	Target    *User  `json:"target"`
	Source    *User  `json:"source"`
	Event     string `json:"event"`
	CreatedAt string `json:"created_at"`
}

type EventList struct {
	Event
	TargetObject *List `json:"target_object"`
}

type EventTweet struct {
	Event
	TargetObject *Tweet `json:"target_object"`
}

type TooManyFollow struct {
	Warning *struct {
		Code    string `json:"code"`
		Message string `json:"message"`
		UserId  int64  `json:"user_id"`
	} `json:"warning"`
}

// TODO: Site Stream messages. I cant test.

// Stream allows you to stream using one of the
// PublicStream* or UserStream api methods
//
// A go loop is started an gives you an interface{}
// Which you can cast into a tweet like this :
//    t, ok := o.(twitter.Tweet) // try casting into a tweet
//    if !ok {
//      log.Debug("Recieved non tweet message")
//    }
//
// If we can't stream the chan will be closed.
// Otherwise the loop will connect and send streams in the chan.
// It will also try to reconnect itself after 2s if the connection is lost
// If twitter response is one of 420, 429 or 503 (meaning "wait a sec")
// the loop retries to open the socket with a simple autogrowing backoff.
//
// May be we could pass it a Logger interface to allow the
// stream to log in the right place ?
type Stream struct {
	api TwitterApi
	C   chan interface{}
}

func (s Stream) Close() {
	if s.C != nil {
		close(s.C)
	}
}

func (s Stream) listen(response http.Response) {
	scanner := bufio.NewScanner(response.Body)
	for {
		if ok := scanner.Scan(); !ok {
			break
		}
		// TODO: DRY
		j := scanner.Bytes()
		if scanner.Text() == "" {
			continue
		} else if o := new(Tweet); jsonAsStruct(j, "/source", o) {
			s.C <- *o
		} else if o := new(statusDeletionNotice); jsonAsStruct(j, "/delete", o) {
			s.C <- *o.Delete.Status
		} else if o := new(locationDeletionNotice); jsonAsStruct(j, "/scrub_geo", o) {
			s.C <- *o.ScrubGeo
		} else if o := new(limitNotice); jsonAsStruct(j, "/limit", o) {
			s.C <- *o.Limit
		} else if o := new(statusWithheldNotice); jsonAsStruct(j, "/status_withheld", o) {
			s.C <- *o.StatusWithheld
		} else if o := new(userWithheldNotice); jsonAsStruct(j, "/user_withheld", o) {
			s.C <- *o.UserWithheld
		} else if o := new(disconnectMessage); jsonAsStruct(j, "/disconnect", o) {
			s.C <- *o.Disconnect
		} else if o := new(stallWarning); jsonAsStruct(j, "/warning", o) {
			s.C <- *o.Warning
		} else if o := new(friendsList); jsonAsStruct(j, "/friends", o) {
			s.C <- *o.Friends
		} else if o := new(streamDirectMessage); jsonAsStruct(j, "/direct_message", o) {
			s.C <- *o.DirectMessage
		} else if o := new(EventTweet); jsonAsStruct(j, "/target_object/source", o) {
			s.C <- *o
		} else if o := new(EventList); jsonAsStruct(j, "/target_object/slug", o) {
			s.C <- *o
		} else if o := new(Event); jsonAsStruct(j, "/target_object", o) {
			s.C <- *o
		}
	}
}

func (s Stream) requestStream(urlStr string, v url.Values, method int) (resp *http.Response, err error) {
	switch method {
	case _GET:
		return oauthClient.Get(s.api.HttpClient, s.api.Credentials, urlStr, v)
	case _POST:
		return oauthClient.Post(s.api.HttpClient, s.api.Credentials, urlStr, v)
	default:
	}
	return nil, fmt.Errorf("HTTP method not yet supported")
}

func (s Stream) loop(urlStr string, v url.Values, method int) {
	defer s.Close()

	backoff := time.Duration(2 * time.Second)
	for resp, err := s.requestStream(urlStr, v, method); err == nil; {
		switch resp.StatusCode {
		case 200, 304:
			s.listen(*resp)
			backoff = time.Duration(2 * time.Second)
			break
		case 420, 429, 503:
			fmt.Println("Twitter streaming: backing off:", resp.Status)
			backoff += time.Duration(2 * time.Second)
			break
		case 400, 401, 403, 404, 406, 410, 422, 500, 502, 504:
			fmt.Println("Twitter streaming: leaving after an irremediable error:", resp.Status)
			// Close chan in case of error
			return
		}
		time.Sleep(backoff)
	}
}

func (a TwitterApi) newStream(urlStr string, v url.Values, method int) (stream Stream, err error) {

	stream = Stream{
		api: a,
		C:   make(chan interface{}),
	}
	go stream.loop(urlStr, v, method)
	return
}

func (a TwitterApi) UserStream(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlUserStream+"/user.json", v, _GET)
}

func (a TwitterApi) PublicStreamSample(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlStream+"/statuses/sample.json", v, _GET)
}

// XXX: To use this API authority is requied. but I dont have this. I cant test.
func (a TwitterApi) PublicStreamFirehose(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlStream+"/statuses/firehose.json", v, _GET)
}

// XXX: PublicStream(Track|Follow|Locations) func is needed?
func (a TwitterApi) PublicStreamFilter(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlStream+"/statuses/filter.json", v, _POST)
}

// XXX: To use this API authority is requied. but I dont have this. I cant test.
func (a TwitterApi) SiteStream(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlSiteStream+"/site.json", v, _GET)
}

func jsonAsStruct(j []byte, path string, obj interface{}) (res bool) {
	if v, _ := jsonpointer.Find(j, path); v == nil {
		return false
	}
	err := json.Unmarshal(j, obj)
	return err == nil
}
<commit_msg>feat(stream.Close): try to avoid panic on double close...<commit_after>package anaconda

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/dustin/go-jsonpointer"
)

const (
	BaseUrlUserStream = "https://userstream.twitter.com/1.1"
	BaseUrlSiteStream = "https://sitestream.twitter.com/1.1"
	BaseUrlStream     = "https://stream.twitter.com/1.1"
)

// messages

type StatusDeletionNotice struct {
	Id        int64  `json:"id"`
	IdStr     string `json:"id_str"`
	UserId    int64  `json:"user_id"`
	UserIdStr string `json:"user_id_str"`
}
type statusDeletionNotice struct {
	Delete *struct {
		Status *StatusDeletionNotice `json:"status"`
	} `json:"delete"`
}

type LocationDeletionNotice struct {
	UserId          int64  `json:"user_id"`
	UserIdStr       string `json:"user_id_str"`
	UpToStatusId    int64  `json:"up_to_status_id"`
	UpToStatusIdStr string `json:"up_to_status_id_str"`
}
type locationDeletionNotice struct {
	ScrubGeo *LocationDeletionNotice `json:"scrub_geo"`
}

type LimitNotice struct {
	Track int64 `json:"track"`
}
type limitNotice struct {
	Limit *LimitNotice `json:"limit"`
}

type StatusWithheldNotice struct {
	Id                  int64    `json:"id"`
	UserId              int64    `json:"user_id"`
	WithheldInCountries []string `json:"withheld_in_countries"`
}
type statusWithheldNotice struct {
	StatusWithheld *StatusWithheldNotice `json:"status_withheld"`
}

type UserWithheldNotice struct {
	Id                  int64    `json:"id"`
	WithheldInCountries []string `json:"withheld_in_countries"`
}
type userWithheldNotice struct {
	UserWithheld *UserWithheldNotice `json:"user_withheld"`
}

type DisconnectMessage struct {
	Code       int64  `json:"code"`
	StreamName string `json:"stream_name"`
	Reason     string `json:"reason"`
}
type disconnectMessage struct {
	Disconnect *DisconnectMessage `json:"disconnect"`
}

type StallWarning struct {
	Code        string `json:"code"`
	Message     string `json:"message"`
	PercentFull int64  `json:"percent_full"`
}
type stallWarning struct {
	Warning *StallWarning `json:"warning"`
}

type FriendsList []int64
type friendsList struct {
	Friends *FriendsList `json:"friends"`
}

type streamDirectMessage struct {
	DirectMessage *DirectMessage `json:"direct_message"`
}

type Event struct {
	Target    *User  `json:"target"`
	Source    *User  `json:"source"`
	Event     string `json:"event"`
	CreatedAt string `json:"created_at"`
}

type EventList struct {
	Event
	TargetObject *List `json:"target_object"`
}

type EventTweet struct {
	Event
	TargetObject *Tweet `json:"target_object"`
}

type TooManyFollow struct {
	Warning *struct {
		Code    string `json:"code"`
		Message string `json:"message"`
		UserId  int64  `json:"user_id"`
	} `json:"warning"`
}

// TODO: Site Stream messages. I cant test.

// Stream allows you to stream using one of the
// PublicStream* or UserStream api methods
//
// A go loop is started an gives you an interface{}
// Which you can cast into a tweet like this :
//    t, ok := o.(twitter.Tweet) // try casting into a tweet
//    if !ok {
//      log.Debug("Recieved non tweet message")
//    }
//
// If we can't stream the chan will be closed.
// Otherwise the loop will connect and send streams in the chan.
// It will also try to reconnect itself after 2s if the connection is lost
// If twitter response is one of 420, 429 or 503 (meaning "wait a sec")
// the loop retries to open the socket with a simple autogrowing backoff.
//
// May be we could pass it a Logger interface to allow the
// stream to log in the right place ?
type Stream struct {
	api TwitterApi
	C   chan interface{}
}

func (s Stream) Close() {
	select {
	case _, open := <-s.C:
		if true == open {
			close(s.C)
		}
	default:
		close(s.C)
	}
}

func (s Stream) listen(response http.Response) {
	scanner := bufio.NewScanner(response.Body)
	for {
		if ok := scanner.Scan(); !ok {
			break
		}
		// TODO: DRY
		j := scanner.Bytes()
		if scanner.Text() == "" {
			continue
		} else if o := new(Tweet); jsonAsStruct(j, "/source", o) {
			s.C <- *o
		} else if o := new(statusDeletionNotice); jsonAsStruct(j, "/delete", o) {
			s.C <- *o.Delete.Status
		} else if o := new(locationDeletionNotice); jsonAsStruct(j, "/scrub_geo", o) {
			s.C <- *o.ScrubGeo
		} else if o := new(limitNotice); jsonAsStruct(j, "/limit", o) {
			s.C <- *o.Limit
		} else if o := new(statusWithheldNotice); jsonAsStruct(j, "/status_withheld", o) {
			s.C <- *o.StatusWithheld
		} else if o := new(userWithheldNotice); jsonAsStruct(j, "/user_withheld", o) {
			s.C <- *o.UserWithheld
		} else if o := new(disconnectMessage); jsonAsStruct(j, "/disconnect", o) {
			s.C <- *o.Disconnect
		} else if o := new(stallWarning); jsonAsStruct(j, "/warning", o) {
			s.C <- *o.Warning
		} else if o := new(friendsList); jsonAsStruct(j, "/friends", o) {
			s.C <- *o.Friends
		} else if o := new(streamDirectMessage); jsonAsStruct(j, "/direct_message", o) {
			s.C <- *o.DirectMessage
		} else if o := new(EventTweet); jsonAsStruct(j, "/target_object/source", o) {
			s.C <- *o
		} else if o := new(EventList); jsonAsStruct(j, "/target_object/slug", o) {
			s.C <- *o
		} else if o := new(Event); jsonAsStruct(j, "/target_object", o) {
			s.C <- *o
		}
	}
}

func (s Stream) requestStream(urlStr string, v url.Values, method int) (resp *http.Response, err error) {
	switch method {
	case _GET:
		return oauthClient.Get(s.api.HttpClient, s.api.Credentials, urlStr, v)
	case _POST:
		return oauthClient.Post(s.api.HttpClient, s.api.Credentials, urlStr, v)
	default:
	}
	return nil, fmt.Errorf("HTTP method not yet supported")
}

func (s Stream) loop(urlStr string, v url.Values, method int) {
	defer s.Close()

	backoff := time.Duration(2 * time.Second)
	for resp, err := s.requestStream(urlStr, v, method); err == nil; {
		switch resp.StatusCode {
		case 200, 304:
			s.listen(*resp)
			backoff = time.Duration(2 * time.Second)
			break
		case 420, 429, 503:
			fmt.Println("Twitter streaming: backing off:", resp.Status)
			backoff += time.Duration(2 * time.Second)
			break
		case 400, 401, 403, 404, 406, 410, 422, 500, 502, 504:
			fmt.Println("Twitter streaming: leaving after an irremediable error:", resp.Status)
			// Close chan in case of error
			return
		}
		time.Sleep(backoff)
	}
}

func (a TwitterApi) newStream(urlStr string, v url.Values, method int) (stream Stream, err error) {

	stream = Stream{
		api: a,
		C:   make(chan interface{}),
	}
	go stream.loop(urlStr, v, method)
	return
}

func (a TwitterApi) UserStream(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlUserStream+"/user.json", v, _GET)
}

func (a TwitterApi) PublicStreamSample(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlStream+"/statuses/sample.json", v, _GET)
}

// XXX: To use this API authority is requied. but I dont have this. I cant test.
func (a TwitterApi) PublicStreamFirehose(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlStream+"/statuses/firehose.json", v, _GET)
}

// XXX: PublicStream(Track|Follow|Locations) func is needed?
func (a TwitterApi) PublicStreamFilter(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlStream+"/statuses/filter.json", v, _POST)
}

// XXX: To use this API authority is requied. but I dont have this. I cant test.
func (a TwitterApi) SiteStream(v url.Values) (stream Stream, err error) {
	return a.newStream(BaseUrlSiteStream+"/site.json", v, _GET)
}

func jsonAsStruct(j []byte, path string, obj interface{}) (res bool) {
	if v, _ := jsonpointer.Find(j, path); v == nil {
		return false
	}
	err := json.Unmarshal(j, obj)
	return err == nil
}
<|endoftext|>