<commit_before>package gautomator

import (
	"fmt"
	"github.com/gonum/matrix/mat64" // Matrix
	"io"
	"time"
)

// A task is an action executed by a module
type Task struct {
	Id     int      `json:"id"`
	Origin string   `json:"origin"`
	Name   string   `json:"name"` //the task name
	Node   string   `json:"node"` // The node name
	Module string   `json:"module"`
	Args   []string `json:"args"`
	Status int      `json:"status"` //-2: queued
	// -1: running
	// >=0 : return code
	StartTime      time.Time `json:"startTime"`
	EndTime        time.Time `json:"endTime"`
	TaskCanRunChan chan bool // true: run, false: wait
}

type jsonStructure struct {
	Nodes []*Task `json:"task"`
}

// This is the structure corresponding to the "dot-graph" of a task list
// We store the nodes in a map
// The index is the source node
type TaskGraphStructure struct {
	Tasks           map[int]*Task
	DegreeMatrix    *mat64.Dense
	AdjacencyMatrix *mat64.Dense // Row id is the map id of the source task
	// Col id is the map id of the destination task
}

func (this *TaskGraphStructure) PrintAdjacencyMatrix() {
	rowSize, colSize := this.AdjacencyMatrix.Dims()
	fmt.Printf("  ")
	for c := 0; c < colSize; c++ {
		fmt.Printf("%v ", this.Tasks[c].Name)
	}
	fmt.Printf("\n")
	for r := 0; r < rowSize; r++ {
		fmt.Printf("%v ", this.Tasks[r].Name)
		for c := 0; c < colSize; c++ {
			fmt.Printf("%v ", this.AdjacencyMatrix.At(r, c))
		}
		fmt.Printf("\n")
	}
}

func (this *TaskGraphStructure) PrintDegreeMatrix() {
	rowSize, colSize := this.DegreeMatrix.Dims()
	for r := 0; r < rowSize; r++ {
		for c := 0; c < colSize; c++ {
			fmt.Printf("%v ", this.DegreeMatrix.At(r, c))
		}
		fmt.Printf("\n")
	}
}

func NewTask() *Task {
	return &Task{
		-1,
		"null",
		"null",
		"null",
		"dummy",
		make([]string, 1),
		-2,
		time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
		time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
		make(chan bool),
	}

}
func NewTaskGraphStructure() *TaskGraphStructure {
	return &TaskGraphStructure{
		make(map[int]*Task, 0),
		mat64.NewDense(0, 0, nil),
		mat64.NewDense(0, 0, nil),
	}
}

// Returns a combination of the current structure
// and the one passed as argument
func (this *TaskGraphStructure) AugmentTaskStructure(taskStructure *TaskGraphStructure) *TaskGraphStructure {
	// merging adjacency matrix
	initialRowLen, initialColLen := this.AdjacencyMatrix.Dims()
	addedRowLen, addedColLen := taskStructure.AdjacencyMatrix.Dims()
	this.AdjacencyMatrix = mat64.DenseCopyOf(this.AdjacencyMatrix.Grow(addedRowLen, addedColLen))
	//a, b := this.AdjacencyMatrix.Dims()
	for r := 0; r < initialRowLen+addedRowLen; r++ {
		for c := 0; c < initialColLen+addedColLen; c++ {
			switch {
			case r < initialRowLen && c < initialColLen:
				// If we are in the original matrix: do nothing
			case r < initialRowLen && c > initialColLen:
				// If outside, put some zero
				this.AdjacencyMatrix.Set(r, c, float64(0))
			case r > initialRowLen && c < initialColLen:
				// If outside, put some zero
				this.AdjacencyMatrix.Set(r, c, float64(0))
			case r >= initialRowLen && c >= initialColLen:
				// Add the new matrix
				this.AdjacencyMatrix.Set(r, c, taskStructure.AdjacencyMatrix.At(r-initialRowLen, c-initialColLen))
			}
		}
	}
	// merging degree matrix
	initialRowLen, initialColLen = this.DegreeMatrix.Dims()
	addedRowLen, addedColLen = taskStructure.DegreeMatrix.Dims()
	this.DegreeMatrix = mat64.DenseCopyOf(this.DegreeMatrix.Grow(addedRowLen, addedColLen))
	for r := 0; r < initialRowLen+addedRowLen; r++ {
		for c := 0; c < initialColLen+addedColLen; c++ {
			switch {
			case r < initialRowLen && c < initialColLen:
				// If we are in the original matrix: do nothing
			case r < initialRowLen && c > initialColLen:
				// If outside, set zero
				this.DegreeMatrix.Set(r, c, float64(0))
			case r > initialRowLen && c < initialColLen:
				// If outside, set zero
				this.DegreeMatrix.Set(r, c, float64(0))
			case r >= initialRowLen && c >= initialColLen:
				// Add the new matrix
				this.DegreeMatrix.Set(r, c, taskStructure.DegreeMatrix.At(r-initialRowLen, c-initialColLen))
			}
		}
	}
	actualSize := len(this.Tasks)
	for i, task := range taskStructure.Tasks {
		task.Id = actualSize + i
		this.Tasks[actualSize+i] = task
	}
	return this
}

func (this *TaskGraphStructure) getTaskFromName(name string) (int, *Task) {
	for index, task := range this.Tasks {
		if task.Name == name {
			return index, task
		}
	}
	return -1, nil
}

func colSum(matrix *mat64.Dense, colId int) float64 {
	row, _ := matrix.Dims()
	sum := float64(0)
	for r := 0; r < row; r++ {
		sum += matrix.At(r, colId)
	}
	return sum
}

func rowSum(matrix *mat64.Dense, rowId int) float64 {
	_, col := matrix.Dims()
	sum := float64(0)
	for c := 0; c < col; c++ {
		sum += matrix.At(rowId, c)
	}
	return sum
}

// the aim of this function is to find if a task has a subdefinition (aka an origin) and change it
// Example:
// imagine the graphs
// digraph bla {
//    a -> b;
//    b -> c;
// }
// digraph b {
//    alpha -> gamma;
// }
// then alpha and beta will have "b" as Origin.
// therefore we should add a link in the AdjacencyMatix and in the DegreeMatrix
func (this *TaskGraphStructure) Relink() *TaskGraphStructure {
	// IN this array we store the row,col on which we set 1
	backup := make([]int, 0)
	_, col := this.AdjacencyMatrix.Dims()
	for _, task := range this.Tasks {
		if colSum(this.AdjacencyMatrix, task.Id) == 0 {
			id, _ := this.getTaskFromName(task.Origin)
			if id != -1 {
				// Task is a meta task
				this.Tasks[id].Module = "meta"
				this.AdjacencyMatrix.Set(id, task.Id, float64(1))
				backup = append(backup, id, task.Id)
			}
		}
		if rowSum(this.AdjacencyMatrix, task.Id) == 0 {
			id, _ := this.getTaskFromName(task.Origin)
			if id != -1 {
				for c := 0; c < col; c++ {
					add := true
					for counter := 0; counter < len(backup)-1; counter += 2 {
						if backup[counter] == id && backup[counter+1] == c {
							add = false
						}
					}
					if add == true {
						this.AdjacencyMatrix.Set(task.Id, c, this.AdjacencyMatrix.At(task.Id, c)+this.AdjacencyMatrix.At(id, c))
					}
				}
			}
		}
	}
	//TODO: complete the degreematrix
	return this
}

// Duplicate the task "id"
// Returns the id of the new task and the whole structure
func (this *TaskGraphStructure) DuplicateTask(id int) (int, *TaskGraphStructure) {
	row, _ := this.AdjacencyMatrix.Dims()
	// Add the task to the list
	origin := this.Tasks[id]
	newId := row + 1
	newTask := origin
	newTask.Id = newId
	this.Tasks[newId] = newTask
	// Adjust the AdjacencyMatrix
	this.AdjacencyMatrix = mat64.DenseCopyOf(this.AdjacencyMatrix.Grow(1, 1))
	// Copy the row 'id' to row 'newId'
	for r := 0; r < newId; r++ {
		this.AdjacencyMatrix.Set(r, newId, this.AdjacencyMatrix.At(r, id))
	}
	// Copy the col 'id' to col 'newId'
	for c := 0; c < newId; c++ {
		this.AdjacencyMatrix.Set(newId, c, this.AdjacencyMatrix.At(id, c))
	}
	return newId, this
}

// This function print the dot file associated with the graph
func (this *TaskGraphStructure) PrintDot(w io.Writer) {
	fmt.Fprintln(w, "digraph G {")
	// Writing node definition
	for _, task := range this.Tasks {
		fmt.Fprintf(w, "\t\"%v\" [\n", task.Id)
		fmt.Fprintf(w, "\t\tid = \"%v\"\n", task.Id)
		if task.Module == "meta" {
			fmt.Fprintln(w, "\t\tshape=diamond")
			fmt.Fprintf(w, "\t\tlabel=\"%v\"", task.Name)
		} else {
			fmt.Fprintf(w, "\t\tlabel = \"<name>%v|<node>%v|<module>%v\"\n", task.Name, task.Node, task.Module)
			fmt.Fprintf(w, "\t\tshape = \"record\"\n")
		}
		fmt.Fprintf(w, "\t];\n")
	}
	row, col := this.AdjacencyMatrix.Dims()
	for r := 0; r < row; r++ {
		for c := 0; c < col; c++ {
			if this.AdjacencyMatrix.At(r, c) == 1 {
				fmt.Fprintf(w, "\t%v -> %v\n", this.Tasks[r].Id, this.Tasks[c].Id)
			}
		}
	}
	fmt.Fprintln(w, "}")
}

// Return a structure of all the task with the given origin
func (this *TaskGraphStructure) GetSubstructure(origin string) *TaskGraphStructure {
	subTaskStructure := NewTaskGraphStructure()
	index := 0
	tasksToExtract := make(map[int]*Task, 0)
	for _, task := range this.Tasks {
		if task.Origin == origin {
			//fmt.Printf("Adding %v(%v) at index:%v\n", task.Name, task.Id, index)
			tasksToExtract[index] = task
			index += 1
		}
	}
	// Create the matrix of the correct size
	size := len(tasksToExtract)
	if size > 0 {
		subTaskStructure.AdjacencyMatrix = mat64.NewDense(size, size, nil)
		subTaskStructure.DegreeMatrix = mat64.NewDense(size, size, nil)
		for i := 0; i < size; i++ {
			task := tasksToExtract[i]
			//fmt.Printf("Task with ID:%v and name:%v will have id:%v\n", task.Id, task.Name, i)
			// BUG here probably
			// Construct the AdjacencyMatrix line by line
			for col := 0; col < size; col++ {
				task2 := tasksToExtract[col]
				//fmt.Printf("Setting %v,%v with value from %v,%v\n", i, col, task.Id, task2.Id)
				subTaskStructure.AdjacencyMatrix.Set(i, col, this.AdjacencyMatrix.At(task.Id, task2.Id))
			}
			subTaskStructure.DegreeMatrix.Set(i, i, this.DegreeMatrix.At(task.Id, task.Id))
			subTaskStructure.Tasks[i] = NewTask()
			subTaskStructure.Tasks[i].Name = task.Name
			subTaskStructure.Tasks[i].Module = task.Module
			subTaskStructure.Tasks[i].Args = task.Args
			subTaskStructure.Tasks[i].Origin = task.Origin
			subTaskStructure.Tasks[i].Id = i
		}
		//subTaskStructure.PrintAdjacencyMatrix()
		return subTaskStructure
	} else {
		return nil
	}
}
<commit_msg>Cleanup<commit_after>package gautomator

import (
	"fmt"
	"github.com/gonum/matrix/mat64" // Matrix
	"io"
	"time"
)

// A task is an action executed by a module
type Task struct {
	Id     int      `json:"id"`
	Origin string   `json:"origin"`
	Name   string   `json:"name"` //the task name
	Node   string   `json:"node"` // The node name
	Module string   `json:"module"`
	Args   []string `json:"args"`
	Status int      `json:"status"` //-2: queued
	// -1: running
	// >=0 : return code
	StartTime      time.Time `json:"startTime"`
	EndTime        time.Time `json:"endTime"`
	TaskCanRunChan chan bool // true: run, false: wait
}

// This is the structure corresponding to the "dot-graph" of a task list
// We store the nodes in a map
// The index is the source node
type TaskGraphStructure struct {
	Tasks           map[int]*Task
	DegreeMatrix    *mat64.Dense
	AdjacencyMatrix *mat64.Dense // Row id is the map id of the source task
	// Col id is the map id of the destination task
}

func (this *TaskGraphStructure) PrintAdjacencyMatrix() {
	rowSize, colSize := this.AdjacencyMatrix.Dims()
	fmt.Printf("  ")
	for c := 0; c < colSize; c++ {
		fmt.Printf("%v ", this.Tasks[c].Name)
	}
	fmt.Printf("\n")
	for r := 0; r < rowSize; r++ {
		fmt.Printf("%v ", this.Tasks[r].Name)
		for c := 0; c < colSize; c++ {
			fmt.Printf("%v ", this.AdjacencyMatrix.At(r, c))
		}
		fmt.Printf("\n")
	}
}

func (this *TaskGraphStructure) PrintDegreeMatrix() {
	rowSize, colSize := this.DegreeMatrix.Dims()
	for r := 0; r < rowSize; r++ {
		for c := 0; c < colSize; c++ {
			fmt.Printf("%v ", this.DegreeMatrix.At(r, c))
		}
		fmt.Printf("\n")
	}
}

func NewTask() *Task {
	return &Task{
		-1,
		"null",
		"null",
		"null",
		"dummy",
		make([]string, 1),
		-2,
		time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
		time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
		make(chan bool),
	}

}
func NewTaskGraphStructure() *TaskGraphStructure {
	return &TaskGraphStructure{
		make(map[int]*Task, 0),
		mat64.NewDense(0, 0, nil),
		mat64.NewDense(0, 0, nil),
	}
}

// Returns a combination of the current structure
// and the one passed as argument
func (this *TaskGraphStructure) AugmentTaskStructure(taskStructure *TaskGraphStructure) *TaskGraphStructure {
	// merging adjacency matrix
	initialRowLen, initialColLen := this.AdjacencyMatrix.Dims()
	addedRowLen, addedColLen := taskStructure.AdjacencyMatrix.Dims()
	this.AdjacencyMatrix = mat64.DenseCopyOf(this.AdjacencyMatrix.Grow(addedRowLen, addedColLen))
	//a, b := this.AdjacencyMatrix.Dims()
	for r := 0; r < initialRowLen+addedRowLen; r++ {
		for c := 0; c < initialColLen+addedColLen; c++ {
			switch {
			case r < initialRowLen && c < initialColLen:
				// If we are in the original matrix: do nothing
			case r < initialRowLen && c > initialColLen:
				// If outside, put some zero
				this.AdjacencyMatrix.Set(r, c, float64(0))
			case r > initialRowLen && c < initialColLen:
				// If outside, put some zero
				this.AdjacencyMatrix.Set(r, c, float64(0))
			case r >= initialRowLen && c >= initialColLen:
				// Add the new matrix
				this.AdjacencyMatrix.Set(r, c, taskStructure.AdjacencyMatrix.At(r-initialRowLen, c-initialColLen))
			}
		}
	}
	// merging degree matrix
	initialRowLen, initialColLen = this.DegreeMatrix.Dims()
	addedRowLen, addedColLen = taskStructure.DegreeMatrix.Dims()
	this.DegreeMatrix = mat64.DenseCopyOf(this.DegreeMatrix.Grow(addedRowLen, addedColLen))
	for r := 0; r < initialRowLen+addedRowLen; r++ {
		for c := 0; c < initialColLen+addedColLen; c++ {
			switch {
			case r < initialRowLen && c < initialColLen:
				// If we are in the original matrix: do nothing
			case r < initialRowLen && c > initialColLen:
				// If outside, set zero
				this.DegreeMatrix.Set(r, c, float64(0))
			case r > initialRowLen && c < initialColLen:
				// If outside, set zero
				this.DegreeMatrix.Set(r, c, float64(0))
			case r >= initialRowLen && c >= initialColLen:
				// Add the new matrix
				this.DegreeMatrix.Set(r, c, taskStructure.DegreeMatrix.At(r-initialRowLen, c-initialColLen))
			}
		}
	}
	actualSize := len(this.Tasks)
	for i, task := range taskStructure.Tasks {
		task.Id = actualSize + i
		this.Tasks[actualSize+i] = task
	}
	return this
}

func (this *TaskGraphStructure) getTaskFromName(name string) (int, *Task) {
	for index, task := range this.Tasks {
		if task.Name == name {
			return index, task
		}
	}
	return -1, nil
}

func colSum(matrix *mat64.Dense, colId int) float64 {
	row, _ := matrix.Dims()
	sum := float64(0)
	for r := 0; r < row; r++ {
		sum += matrix.At(r, colId)
	}
	return sum
}

func rowSum(matrix *mat64.Dense, rowId int) float64 {
	_, col := matrix.Dims()
	sum := float64(0)
	for c := 0; c < col; c++ {
		sum += matrix.At(rowId, c)
	}
	return sum
}

// the aim of this function is to find if a task has a subdefinition (aka an origin) and change it
// Example:
// imagine the graphs
// digraph bla {
//    a -> b;
//    b -> c;
// }
// digraph b {
//    alpha -> gamma;
// }
// then alpha and beta will have "b" as Origin.
// therefore we should add a link in the AdjacencyMatix and in the DegreeMatrix
func (this *TaskGraphStructure) Relink() *TaskGraphStructure {
	// IN this array we store the row,col on which we set 1
	backup := make([]int, 0)
	_, col := this.AdjacencyMatrix.Dims()
	for _, task := range this.Tasks {
		if colSum(this.AdjacencyMatrix, task.Id) == 0 {
			id, _ := this.getTaskFromName(task.Origin)
			if id != -1 {
				// Task is a meta task
				this.Tasks[id].Module = "meta"
				this.AdjacencyMatrix.Set(id, task.Id, float64(1))
				backup = append(backup, id, task.Id)
			}
		}
		if rowSum(this.AdjacencyMatrix, task.Id) == 0 {
			id, _ := this.getTaskFromName(task.Origin)
			if id != -1 {
				for c := 0; c < col; c++ {
					add := true
					for counter := 0; counter < len(backup)-1; counter += 2 {
						if backup[counter] == id && backup[counter+1] == c {
							add = false
						}
					}
					if add == true {
						this.AdjacencyMatrix.Set(task.Id, c, this.AdjacencyMatrix.At(task.Id, c)+this.AdjacencyMatrix.At(id, c))
					}
				}
			}
		}
	}
	//TODO: complete the degreematrix
	return this
}

// Duplicate the task "id"
// Returns the id of the new task and the whole structure
func (this *TaskGraphStructure) DuplicateTask(id int) (int, *TaskGraphStructure) {
	row, _ := this.AdjacencyMatrix.Dims()
	// Add the task to the list
	origin := this.Tasks[id]
	newId := row + 1
	newTask := origin
	newTask.Id = newId
	this.Tasks[newId] = newTask
	// Adjust the AdjacencyMatrix
	this.AdjacencyMatrix = mat64.DenseCopyOf(this.AdjacencyMatrix.Grow(1, 1))
	// Copy the row 'id' to row 'newId'
	for r := 0; r < newId; r++ {
		this.AdjacencyMatrix.Set(r, newId, this.AdjacencyMatrix.At(r, id))
	}
	// Copy the col 'id' to col 'newId'
	for c := 0; c < newId; c++ {
		this.AdjacencyMatrix.Set(newId, c, this.AdjacencyMatrix.At(id, c))
	}
	return newId, this
}

// This function print the dot file associated with the graph
func (this *TaskGraphStructure) PrintDot(w io.Writer) {
	fmt.Fprintln(w, "digraph G {")
	// Writing node definition
	for _, task := range this.Tasks {
		fmt.Fprintf(w, "\t\"%v\" [\n", task.Id)
		fmt.Fprintf(w, "\t\tid = \"%v\"\n", task.Id)
		if task.Module == "meta" {
			fmt.Fprintln(w, "\t\tshape=diamond")
			fmt.Fprintf(w, "\t\tlabel=\"%v\"", task.Name)
		} else {
			fmt.Fprintf(w, "\t\tlabel = \"<name>%v|<node>%v|<module>%v\"\n", task.Name, task.Node, task.Module)
			fmt.Fprintf(w, "\t\tshape = \"record\"\n")
		}
		fmt.Fprintf(w, "\t];\n")
	}
	row, col := this.AdjacencyMatrix.Dims()
	for r := 0; r < row; r++ {
		for c := 0; c < col; c++ {
			if this.AdjacencyMatrix.At(r, c) == 1 {
				fmt.Fprintf(w, "\t%v -> %v\n", this.Tasks[r].Id, this.Tasks[c].Id)
			}
		}
	}
	fmt.Fprintln(w, "}")
}

// Return a structure of all the task with the given origin
func (this *TaskGraphStructure) GetSubstructure(origin string) *TaskGraphStructure {
	subTaskStructure := NewTaskGraphStructure()
	index := 0
	tasksToExtract := make(map[int]*Task, 0)
	for _, task := range this.Tasks {
		if task.Origin == origin {
			//fmt.Printf("Adding %v(%v) at index:%v\n", task.Name, task.Id, index)
			tasksToExtract[index] = task
			index += 1
		}
	}
	// Create the matrix of the correct size
	size := len(tasksToExtract)
	if size > 0 {
		subTaskStructure.AdjacencyMatrix = mat64.NewDense(size, size, nil)
		subTaskStructure.DegreeMatrix = mat64.NewDense(size, size, nil)
		for i := 0; i < size; i++ {
			task := tasksToExtract[i]
			//fmt.Printf("Task with ID:%v and name:%v will have id:%v\n", task.Id, task.Name, i)
			// BUG here probably
			// Construct the AdjacencyMatrix line by line
			for col := 0; col < size; col++ {
				task2 := tasksToExtract[col]
				//fmt.Printf("Setting %v,%v with value from %v,%v\n", i, col, task.Id, task2.Id)
				subTaskStructure.AdjacencyMatrix.Set(i, col, this.AdjacencyMatrix.At(task.Id, task2.Id))
			}
			subTaskStructure.DegreeMatrix.Set(i, i, this.DegreeMatrix.At(task.Id, task.Id))
			subTaskStructure.Tasks[i] = NewTask()
			subTaskStructure.Tasks[i].Name = task.Name
			subTaskStructure.Tasks[i].Module = task.Module
			subTaskStructure.Tasks[i].Args = task.Args
			subTaskStructure.Tasks[i].Origin = task.Origin
			subTaskStructure.Tasks[i].Id = i
		}
		//subTaskStructure.PrintAdjacencyMatrix()
		return subTaskStructure
	} else {
		return nil
	}
}
<|endoftext|>


<commit_before>package flue

import (
	"fmt"
	"github.com/gonum/matrix/mat64" // Matrix
	"time"
)

// A task is an action executed by a module
type Task struct {
	Id     int
	Name   string //the task name
	Node   string // The node name
	Module string
	Args   []string
	Status int // -2: queued
	// -1: running
	// >=0 : return code
	StartTime      time.Time
	EndTime        time.Time
	TaskCanRunChan chan bool // true: run, false: wait
}

// This is the structure corresponding to the "dot-graph" of a task list
// We store the nodes in a map
// The index is the source node
// The value is an array of strings containing the destination
type TaskGraphStructure struct {
	Tasks           map[int]*Task
	DegreeMatrix    *mat64.Dense
	AdjacencyMatrix *mat64.Dense // Row id is the map id of the source task
	// Col id is the map id of the destination task
}

func PrintAdjacencyMatrix(taskStructure *TaskGraphStructure) {
	rowSize, colSize := taskStructure.AdjacencyMatrix.Dims()
	fmt.Printf("  ")
	for c := 0; c < colSize; c++ {
		fmt.Printf("%v ", taskStructure.Tasks[c].Name)
	}
	fmt.Printf("\n")
	for r := 0; r < rowSize; r++ {
		fmt.Printf("%v ", taskStructure.Tasks[r].Name)
		for c := 0; c < colSize; c++ {
			fmt.Printf("%v ", taskStructure.AdjacencyMatrix.At(r, c))
		}
		fmt.Printf("\n")
	}
}

func PrintDegreeMatrix(taskStructure *TaskGraphStructure) {
	rowSize, colSize := taskStructure.DegreeMatrix.Dims()
	for r := 0; r < rowSize; r++ {
		for c := 0; c < colSize; c++ {
			fmt.Printf("%v ", taskStructure.DegreeMatrix.At(r, c))
		}
		fmt.Printf("\n")
	}
}

func NewTask() *Task {
	return &Task{
		-1,
		"null",
		"localhost",
		"dummy",
		make([]string, 1),
		-2,
		time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
		time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
		make(chan bool),
	}

}
func NewTaskGraphStructure() *TaskGraphStructure {
	return &TaskGraphStructure{
		make(map[int]*Task, 0),
		mat64.NewDense(0, 0, nil),
		mat64.NewDense(0, 0, nil),
	}
}
<commit_msg>Comments<commit_after>package flue

import (
	"fmt"
	"github.com/gonum/matrix/mat64" // Matrix
	"time"
)

// A task is an action executed by a module
type Task struct {
	Id     int
	Name   string //the task name
	Node   string // The node name
	Module string
	Args   []string
	Status int // -2: queued
	// -1: running
	// >=0 : return code
	StartTime      time.Time
	EndTime        time.Time
	TaskCanRunChan chan bool // true: run, false: wait
}

// This is the structure corresponding to the "dot-graph" of a task list
// We store the nodes in a map
// The index is the source node
type TaskGraphStructure struct {
	Tasks           map[int]*Task
	DegreeMatrix    *mat64.Dense
	AdjacencyMatrix *mat64.Dense // Row id is the map id of the source task
	// Col id is the map id of the destination task
}

func PrintAdjacencyMatrix(taskStructure *TaskGraphStructure) {
	rowSize, colSize := taskStructure.AdjacencyMatrix.Dims()
	fmt.Printf("  ")
	for c := 0; c < colSize; c++ {
		fmt.Printf("%v ", taskStructure.Tasks[c].Name)
	}
	fmt.Printf("\n")
	for r := 0; r < rowSize; r++ {
		fmt.Printf("%v ", taskStructure.Tasks[r].Name)
		for c := 0; c < colSize; c++ {
			fmt.Printf("%v ", taskStructure.AdjacencyMatrix.At(r, c))
		}
		fmt.Printf("\n")
	}
}

func PrintDegreeMatrix(taskStructure *TaskGraphStructure) {
	rowSize, colSize := taskStructure.DegreeMatrix.Dims()
	for r := 0; r < rowSize; r++ {
		for c := 0; c < colSize; c++ {
			fmt.Printf("%v ", taskStructure.DegreeMatrix.At(r, c))
		}
		fmt.Printf("\n")
	}
}

func NewTask() *Task {
	return &Task{
		-1,
		"null",
		"localhost",
		"dummy",
		make([]string, 1),
		-2,
		time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
		time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
		make(chan bool),
	}

}
func NewTaskGraphStructure() *TaskGraphStructure {
	return &TaskGraphStructure{
		make(map[int]*Task, 0),
		mat64.NewDense(0, 0, nil),
		mat64.NewDense(0, 0, nil),
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package deployments

import (
	"github.com/decred/dcrd/chaincfg"
	"github.com/decred/dcrd/wire"
)

// HardcodedDeployment specifies hardcoded block heights that a deployment
// activates at.  If the value is negative, the deployment is either inactive or
// can't be determined due to the uniqueness properties of the network.
//
// Since these are hardcoded deployments, and cannot support every possible
// network, conditional logic should only be applied when a deployment is
// active, not when it is inactive.
type HardcodedDeployment struct {
	MainNetActivationHeight  int32
	TestNet2ActivationHeight int32
	TestNet3ActivationHeight int32
	SimNetActivationHeight   int32
}

// DCP0001 specifies hard forking changes to the stake difficulty algorithm as
// defined by https://github.com/decred/dcps/blob/master/dcp-0001/dcp-0001.mediawiki.
var DCP0001 = HardcodedDeployment{
	MainNetActivationHeight:  149248,
	TestNet2ActivationHeight: 46128,
	TestNet3ActivationHeight: 0,
	SimNetActivationHeight:   -1,
}

// DCP0002 specifies the activation of the OP_SHA256 hard fork as defined by
// https://github.com/decred/dcps/blob/master/dcp-0002/dcp-0002.mediawiki.
var DCP0002 = HardcodedDeployment{
	MainNetActivationHeight:  189568,
	TestNet2ActivationHeight: 151968,
	TestNet3ActivationHeight: 0,
	SimNetActivationHeight:   -1,
}

// DCP0003 specifies the activation of a CSV soft fork as defined by
// https://github.com/decred/dcps/blob/master/dcp-0003/dcp-0003.mediawiki.
var DCP0003 = HardcodedDeployment{
	MainNetActivationHeight:  189568,
	TestNet2ActivationHeight: 151968,
	TestNet3ActivationHeight: 0,
	SimNetActivationHeight:   -1,
}

// Active returns whether the hardcoded deployment is active at height on the
// network specified by params.  Active always returns false for unrecognized
// networks.
func (d *HardcodedDeployment) Active(height int32, params *chaincfg.Params) bool {
	var activationHeight int32 = -1
	switch params.Net {
	case wire.MainNet:
		activationHeight = d.MainNetActivationHeight
	case 0x48e7a065: // testnet2
		activationHeight = d.TestNet2ActivationHeight
	case wire.TestNet3:
		activationHeight = d.TestNet3ActivationHeight
	case wire.SimNet:
		activationHeight = d.SimNetActivationHeight
	}
	return activationHeight >= 0 && height >= activationHeight
}
<commit_msg>Activate simnet deployments from genesis block.<commit_after>// Copyright (c) 2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package deployments

import (
	"github.com/decred/dcrd/chaincfg"
	"github.com/decred/dcrd/wire"
)

// HardcodedDeployment specifies hardcoded block heights that a deployment
// activates at.  If the value is negative, the deployment is either inactive or
// can't be determined due to the uniqueness properties of the network.
//
// Since these are hardcoded deployments, and cannot support every possible
// network, conditional logic should only be applied when a deployment is
// active, not when it is inactive.
type HardcodedDeployment struct {
	MainNetActivationHeight  int32
	TestNet2ActivationHeight int32
	TestNet3ActivationHeight int32
	SimNetActivationHeight   int32
}

// DCP0001 specifies hard forking changes to the stake difficulty algorithm as
// defined by https://github.com/decred/dcps/blob/master/dcp-0001/dcp-0001.mediawiki.
var DCP0001 = HardcodedDeployment{
	MainNetActivationHeight:  149248,
	TestNet2ActivationHeight: 46128,
	TestNet3ActivationHeight: 0,
	SimNetActivationHeight:   0,
}

// DCP0002 specifies the activation of the OP_SHA256 hard fork as defined by
// https://github.com/decred/dcps/blob/master/dcp-0002/dcp-0002.mediawiki.
var DCP0002 = HardcodedDeployment{
	MainNetActivationHeight:  189568,
	TestNet2ActivationHeight: 151968,
	TestNet3ActivationHeight: 0,
	SimNetActivationHeight:   0,
}

// DCP0003 specifies the activation of a CSV soft fork as defined by
// https://github.com/decred/dcps/blob/master/dcp-0003/dcp-0003.mediawiki.
var DCP0003 = HardcodedDeployment{
	MainNetActivationHeight:  189568,
	TestNet2ActivationHeight: 151968,
	TestNet3ActivationHeight: 0,
	SimNetActivationHeight:   0,
}

// Active returns whether the hardcoded deployment is active at height on the
// network specified by params.  Active always returns false for unrecognized
// networks.
func (d *HardcodedDeployment) Active(height int32, params *chaincfg.Params) bool {
	var activationHeight int32 = -1
	switch params.Net {
	case wire.MainNet:
		activationHeight = d.MainNetActivationHeight
	case 0x48e7a065: // testnet2
		activationHeight = d.TestNet2ActivationHeight
	case wire.TestNet3:
		activationHeight = d.TestNet3ActivationHeight
	case wire.SimNet:
		activationHeight = d.SimNetActivationHeight
	}
	return activationHeight >= 0 && height >= activationHeight
}
<|endoftext|>


<commit_before>package storage

import (
	"testing"
)

var (
	testProjectName = "andygrunwald/TrendingGithub"
)

func TestMarkRepositoryAsTweeted(t *testing.T) {
	storage := MemoryStorage{}
	pool := storage.NewPool("", "")
	conn := pool.Get()

	res, err := conn.MarkRepositoryAsTweeted(testProjectName, "1440946305")
	if err != nil {
		t.Fatalf("Error of marking repository: \"%s\"", err)
	}

	if res == false {
		t.Fatal("Marking repositoriy failed, got false, expected true")
	}
}

func TestIsRepositoryAlreadyTweeted(t *testing.T) {
	storage := MemoryStorage{}
	pool := storage.NewPool("", "")
	conn := pool.Get()

	res, err := conn.IsRepositoryAlreadyTweeted(testProjectName)
	if err != nil {
		t.Fatalf("First already tweeted check throws an error: \"%s\"", err)
	}
	if res == true {
		t.Fatal("Repository was already tweeted, got true, expected false")
	}

	res, err = conn.MarkRepositoryAsTweeted(testProjectName, "1440946884")
	if err != nil {
		t.Fatalf("Error of marking repository: \"%s\"", err)
	}

	if res == false {
		t.Fatal("Marking repositoriy failed, got false, expected true")
	}

	res, err = conn.IsRepositoryAlreadyTweeted(testProjectName)
	if err != nil {
		t.Fatalf("Second already tweeted check throws an error: \"%s\"", err)
	}
	if res == false {
		t.Fatal("Repository was not already tweeted, got false, expected true")
	}
}
<commit_msg>Renamed memory storage tests<commit_after>package storage

import (
	"testing"
)

var (
	testProjectName = "andygrunwald/TrendingGithub"
)

func TestMemory_MarkRepositoryAsTweeted(t *testing.T) {
	storage := MemoryStorage{}
	pool := storage.NewPool("", "")
	conn := pool.Get()

	res, err := conn.MarkRepositoryAsTweeted(testProjectName, "1440946305")
	if err != nil {
		t.Fatalf("Error of marking repository: \"%s\"", err)
	}

	if res == false {
		t.Fatal("Marking repositoriy failed, got false, expected true")
	}
}

func TestMemory_IsRepositoryAlreadyTweeted(t *testing.T) {
	storage := MemoryStorage{}
	pool := storage.NewPool("", "")
	conn := pool.Get()

	res, err := conn.IsRepositoryAlreadyTweeted(testProjectName)
	if err != nil {
		t.Fatalf("First already tweeted check throws an error: \"%s\"", err)
	}
	if res == true {
		t.Fatal("Repository was already tweeted, got true, expected false")
	}

	res, err = conn.MarkRepositoryAsTweeted(testProjectName, "1440946884")
	if err != nil {
		t.Fatalf("Error of marking repository: \"%s\"", err)
	}

	if res == false {
		t.Fatal("Marking repositoriy failed, got false, expected true")
	}

	res, err = conn.IsRepositoryAlreadyTweeted(testProjectName)
	if err != nil {
		t.Fatalf("Second already tweeted check throws an error: \"%s\"", err)
	}
	if res == false {
		t.Fatal("Repository was not already tweeted, got false, expected true")
	}
}
<|endoftext|>


<commit_before>package redis

import (
    "sync"
    "time"
    "strconv"
    "github.com/BluePecker/JwtAuth/storage"
    "github.com/go-redis/redis"
    "fmt"
)

type Redis struct {
    create time.Time
    mu     sync.RWMutex
    mem    storage.MemStore
    client *redis.Client
}

func (er *Redis) Initializer(opt storage.Options) error {
    er.client = redis.NewClient(&redis.Options{
        Network: "tcp",
        Addr: opt.Host + ":" + strconv.Itoa(opt.Port),
        PoolSize: opt.PoolSize,
    })
    err := er.client.Ping().Err()
    if err != nil {
        defer er.client.Close()
    }
    return err
}

func (er *Redis) TTL(key string) int {
    er.mu.RLock()
    defer er.mu.RUnlock()
    if !er.mem.Exist(key) {
        return int(er.client.TTL(key).Val().Seconds())
    }
    return er.mem.TTL(key)
}

func (er *Redis) Read(key string) (interface{}, error) {
    er.mu.RLock()
    defer er.mu.RUnlock()
    if v, err := er.mem.Get(key); err != nil {
        status := er.client.Get(key)
        return status.Val(), status.Err()
    } else {
        return v, nil
    }
}

func (er *Redis) ReadInt(key string) (int, error) {
    er.mu.RLock()
    defer er.mu.RUnlock()
    v, err := er.mem.GetInt(key)
    if err != nil {
        status := er.client.Get(key)
        if status.Err() != nil {
            return 0, status.Err()
        }
        return strconv.Atoi(status.Val())
    }
    return v, nil
}

func (er *Redis) ReadString(key string) (string, error) {
    er.mu.RLock()
    defer er.mu.RUnlock()
    v, err := er.mem.GetString(key)
    if err != nil {
        status := er.client.Get(key)
        if status.Err() != nil {
            return "", status.Err()
        }
        return status.Val(), nil
    }
    return v, nil
}

func (er *Redis) Upgrade(key string, expire int) {
    er.mu.Lock()
    defer er.mu.Unlock()
    if v, err := er.Read(key); err != nil {
        er.Write(key, v, expire)
    }

}

func (er *Redis) Write(key string, value interface{}, expire int) {
    er.mu.Lock()
    defer er.mu.Unlock()
    if er.mem.Set(key, value, expire) == nil {
        fmt.Printf(key)
        er.flushToDB(key, value, expire)
    }
}

func (er *Redis) WriteImmutable(key string, value interface{}, expire int) {
    er.mu.Lock()
    defer er.mu.Unlock()
    if er.mem.SetImmutable(key, value, expire) == nil {
        er.flushToDB(key, value, expire)
    }
}

func (er *Redis) flushToDB(key string, value interface{}, expire int) error {
    cmdStatus := er.client.Set(key, value, time.Duration(expire) * time.Second)
    return cmdStatus.Err()
}

func init() {
    storage.Register("redis", &Redis{})
}<commit_msg>redis driver<commit_after>package redis

import (
    "sync"
    "time"
    "strconv"
    "github.com/BluePecker/JwtAuth/storage"
    "github.com/go-redis/redis"
    "fmt"
)

type Redis struct {
    create time.Time
    mu     sync.RWMutex
    mem    storage.MemStore
    client *redis.Client
}

func (er *Redis) Initializer(opt storage.Options) error {
    er.client = redis.NewClient(&redis.Options{
        Network: "tcp",
        Addr: opt.Host + ":" + strconv.Itoa(opt.Port),
        PoolSize: opt.PoolSize,
    })
    err := er.client.Ping().Err()
    if err != nil {
        defer er.client.Close()
    }
    return err
}

func (er *Redis) TTL(key string) int {
    er.mu.RLock()
    defer er.mu.RUnlock()
    if !er.mem.Exist(key) {
        return int(er.client.TTL(key).Val().Seconds())
    }
    return er.mem.TTL(key)
}

func (er *Redis) Read(key string) (interface{}, error) {
    er.mu.RLock()
    defer er.mu.RUnlock()
    if v, err := er.mem.Get(key); err != nil {
        status := er.client.Get(key)
        return status.Val(), status.Err()
    } else {
        return v, nil
    }
}

func (er *Redis) ReadInt(key string) (int, error) {
    er.mu.RLock()
    defer er.mu.RUnlock()
    v, err := er.mem.GetInt(key)
    if err != nil {
        status := er.client.Get(key)
        if status.Err() != nil {
            return 0, status.Err()
        }
        return strconv.Atoi(status.Val())
    }
    return v, nil
}

func (er *Redis) ReadString(key string) (string, error) {
    er.mu.RLock()
    defer er.mu.RUnlock()
    v, err := er.mem.GetString(key)
    if err != nil {
        status := er.client.Get(key)
        if status.Err() != nil {
            return "", status.Err()
        }
        return status.Val(), nil
    }
    return v, nil
}

func (er *Redis) Upgrade(key string, expire int) {
    er.mu.Lock()
    defer er.mu.Unlock()
    if v, err := er.Read(key); err != nil {
        er.Write(key, v, expire)
    }

}

func (er *Redis) Write(key string, value interface{}, expire int) {
    er.mu.Lock()
    defer er.mu.Unlock()
    if er.mem.Set(key, value, expire) == nil {
        err := er.flushToDB(key, value, expire)
        fmt.Println(key, err)
    }
}

func (er *Redis) WriteImmutable(key string, value interface{}, expire int) {
    er.mu.Lock()
    defer er.mu.Unlock()
    if er.mem.SetImmutable(key, value, expire) == nil {
        er.flushToDB(key, value, expire)
    }
}

func (er *Redis) flushToDB(key string, value interface{}, expire int) error {
    cmdStatus := er.client.Set(key, value, time.Duration(expire) * time.Second)
    return cmdStatus.Err()
}

func init() {
    storage.Register("redis", &Redis{})
}<|endoftext|>


<commit_before>// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// A HTTP wrapper which handles some errors and provides a way to replace the HTTP client by a mock.
// Author: bratseth

package utils

import (
    "net/http"
    "net/url"
    "strings"
    "time"
)

// Set this to a mock HttpClient instead to unit test HTTP requests
var ActiveHttpClient = CreateClient(time.Second * 10)

type HttpClient interface {
    Do(request *http.Request, timeout time.Duration) (response *http.Response, error error)
}

type defaultHttpClient struct {
    client http.Client
}

func (c defaultHttpClient) Do(request *http.Request, timeout time.Duration) (response *http.Response, error error) {
    if c.client.Timeout != timeout { // Create a new client with the right timeout
        c.client = http.Client{Timeout: timeout,}
    }
    return c.client.Do(request)
}

func CreateClient(timeout time.Duration) HttpClient {
    return &defaultHttpClient{
        client: http.Client{Timeout: timeout,},
    }
}

// Convenience function for doing a HTTP GET
func HttpGet(host string, path string, description string) *http.Response {
    url, urlError := url.Parse(host + path)
    if urlError != nil {
        Error("Invalid target url '" + host + path + "'")
        return nil
    }
    return HttpDo(&http.Request{URL: url,}, time.Second * 10, description)
}

func HttpDo(request *http.Request, timeout time.Duration, description string) *http.Response {
    response, error := ActiveHttpClient.Do(request, timeout)
    if error != nil {
        Error("Could not connect to", strings.ToLower(description), "at", request.URL.Host)
        Detail(error.Error())
    }
    return response
}

<commit_msg>Store reference<commit_after>// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// A HTTP wrapper which handles some errors and provides a way to replace the HTTP client by a mock.
// Author: bratseth

package utils

import (
    "net/http"
    "net/url"
    "strings"
    "time"
)

// Set this to a mock HttpClient instead to unit test HTTP requests
var ActiveHttpClient = CreateClient(time.Second * 10)

type HttpClient interface {
    Do(request *http.Request, timeout time.Duration) (response *http.Response, error error)
}

type defaultHttpClient struct {
    client *http.Client
}

func (c defaultHttpClient) Do(request *http.Request, timeout time.Duration) (response *http.Response, error error) {
    if c.client.Timeout != timeout { // Create a new client with the right timeout
        c.client = &http.Client{Timeout: timeout,}
    }
    return c.client.Do(request)
}

func CreateClient(timeout time.Duration) HttpClient {
    return &defaultHttpClient{
        client: &http.Client{Timeout: timeout,},
    }
}

// Convenience function for doing a HTTP GET
func HttpGet(host string, path string, description string) *http.Response {
    url, urlError := url.Parse(host + path)
    if urlError != nil {
        Error("Invalid target url '" + host + path + "'")
        return nil
    }
    return HttpDo(&http.Request{URL: url,}, time.Second * 10, description)
}

func HttpDo(request *http.Request, timeout time.Duration, description string) *http.Response {
    response, error := ActiveHttpClient.Do(request, timeout)
    if error != nil {
        Error("Could not connect to", strings.ToLower(description), "at", request.URL.Host)
        Detail(error.Error())
    }
    return response
}

<|endoftext|>


<commit_before>// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// vespa document API client
// Author: bratseth

package vespa

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/vespa-engine/vespa/client/go/curl"
	"github.com/vespa-engine/vespa/client/go/util"
)

// Sends the operation given in the file
func Send(jsonFile string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation("", jsonFile, service, anyOperation, options)
}

func Put(documentId string, jsonFile string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation(documentId, jsonFile, service, putOperation, options)
}

func Update(documentId string, jsonFile string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation(documentId, jsonFile, service, updateOperation, options)
}

func RemoveId(documentId string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation(documentId, "", service, removeOperation, options)
}

func RemoveOperation(jsonFile string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation("", jsonFile, service, removeOperation, options)
}

const (
	anyOperation    string = "any"
	putOperation    string = "put"
	updateOperation string = "update"
	removeOperation string = "remove"
)

type OperationOptions struct {
	CurlOutput io.Writer
	Timeout    time.Duration
}

func sendOperation(documentId string, jsonFile string, service *Service, operation string, options OperationOptions) util.OperationResult {
	header := http.Header{}
	header.Add("Content-Type", "application/json")

	var documentData []byte
	if operation == "remove" && jsonFile == "" {
		documentData = []byte("{\n    \"remove\": \"" + documentId + "\"\n}\n")
	} else {
		fileReader, err := os.Open(jsonFile)
		if err != nil {
			return util.FailureWithDetail("Could not open file '"+jsonFile+"'", err.Error())
		}
		defer fileReader.Close()
		documentData, err = ioutil.ReadAll(fileReader)
		if err != nil {
			return util.FailureWithDetail("Failed to read '"+jsonFile+"'", err.Error())
		}
	}

	var doc map[string]interface{}
	json.Unmarshal(documentData, &doc)

	operationInFile := operationIn(doc)
	if operation == anyOperation { // Operation is decided by file content
		operation = operationInFile
	} else if operationInFile != "" && operationInFile != operation { // Otherwise operation must match
		return util.Failure("Wanted document operation is " + operation + " but the JSON file specifies " + operationInFile)
	}

	if documentId == "" { // Document id is decided by file content
		if doc[operation] == nil {
			return util.Failure("No document id given neither as argument or as a '" + operation + "' key in the json file")
		}
		documentId = doc[operation].(string) // document feeder format
	}

	documentPath, documentPathError := IdToURLPath(documentId)
	if documentPathError != nil {
		return util.Failure("Invalid document id '" + documentId + "': " + documentPathError.Error())
	}

	url, urlParseError := url.Parse(service.BaseURL + "/document/v1/" + documentPath)
	if urlParseError != nil {
		return util.Failure("Invalid request path: '" + service.BaseURL + "/document/v1/" + documentPath + "': " + urlParseError.Error())
	}

	request := &http.Request{
		URL:    url,
		Method: operationToHTTPMethod(operation),
		Header: header,
		Body:   ioutil.NopCloser(bytes.NewReader(documentData)),
	}
	response, err := serviceDo(service, request, jsonFile, options)
	if response == nil {
		return util.Failure("Request failed: " + err.Error())
	}

	defer response.Body.Close()
	if response.StatusCode == 200 {
		return util.Success(operation + " " + documentId)
	} else if response.StatusCode/100 == 4 {
		return util.FailureWithPayload("Invalid document operation: "+response.Status, util.ReaderToJSON(response.Body))
	} else {
		return util.FailureWithPayload(service.Description()+" at "+request.URL.Host+": "+response.Status, util.ReaderToJSON(response.Body))
	}
}

func operationIn(doc map[string]interface{}) string {
	if doc["put"] != nil {
		return "put"
	} else if doc["update"] != nil {
		return "update"
	} else if doc["remove"] != nil {
		return "remove"
	} else {
		return ""
	}
}

func operationToHTTPMethod(operation string) string {
	switch operation {
	case "put":
		return "POST"
	case "update":
		return "PUT"
	case "remove":
		return "DELETE"
	}
	panic("Unexpected document operation ''" + operation + "'")
}

func serviceDo(service *Service, request *http.Request, filename string, options OperationOptions) (*http.Response, error) {
	cmd, err := curl.RawArgs(request.URL.String())
	if err != nil {
		return nil, err
	}
	cmd.Method = request.Method
	for k, vs := range request.Header {
		for _, v := range vs {
			cmd.Header(k, v)
		}
	}
	cmd.BodyFile = filename
	cmd.Certificate = service.TLSOptions.CertificateFile
	cmd.PrivateKey = service.TLSOptions.PrivateKeyFile
	out := cmd.String() + "\n"
	if _, err := io.WriteString(options.CurlOutput, out); err != nil {
		return nil, err
	}
	return service.Do(request, options.Timeout)
}

func Get(documentId string, service *Service, options OperationOptions) util.OperationResult {
	documentPath, documentPathError := IdToURLPath(documentId)
	if documentPathError != nil {
		return util.Failure("Invalid document id '" + documentId + "': " + documentPathError.Error())
	}

	url, urlParseError := url.Parse(service.BaseURL + "/document/v1/" + documentPath)
	if urlParseError != nil {
		return util.Failure("Invalid request path: '" + service.BaseURL + "/document/v1/" + documentPath + "': " + urlParseError.Error())
	}

	request := &http.Request{
		URL:    url,
		Method: "GET",
	}
	response, err := serviceDo(service, request, "", options)
	if response == nil {
		return util.Failure("Request failed: " + err.Error())
	}

	defer response.Body.Close()
	if response.StatusCode == 200 {
		return util.SuccessWithPayload("Read "+documentId, util.ReaderToJSON(response.Body))
	} else if response.StatusCode/100 == 4 {
		return util.FailureWithPayload("Invalid document operation: "+response.Status, util.ReaderToJSON(response.Body))
	} else {
		return util.FailureWithPayload(service.Description()+" at "+request.URL.Host+": "+response.Status, util.ReaderToJSON(response.Body))
	}
}
<commit_msg>Check error for not nil, not response for nil<commit_after>// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// vespa document API client
// Author: bratseth

package vespa

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/vespa-engine/vespa/client/go/curl"
	"github.com/vespa-engine/vespa/client/go/util"
)

// Sends the operation given in the file
func Send(jsonFile string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation("", jsonFile, service, anyOperation, options)
}

func Put(documentId string, jsonFile string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation(documentId, jsonFile, service, putOperation, options)
}

func Update(documentId string, jsonFile string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation(documentId, jsonFile, service, updateOperation, options)
}

func RemoveId(documentId string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation(documentId, "", service, removeOperation, options)
}

func RemoveOperation(jsonFile string, service *Service, options OperationOptions) util.OperationResult {
	return sendOperation("", jsonFile, service, removeOperation, options)
}

const (
	anyOperation    string = "any"
	putOperation    string = "put"
	updateOperation string = "update"
	removeOperation string = "remove"
)

type OperationOptions struct {
	CurlOutput io.Writer
	Timeout    time.Duration
}

func sendOperation(documentId string, jsonFile string, service *Service, operation string, options OperationOptions) util.OperationResult {
	header := http.Header{}
	header.Add("Content-Type", "application/json")

	var documentData []byte
	if operation == "remove" && jsonFile == "" {
		documentData = []byte("{\n    \"remove\": \"" + documentId + "\"\n}\n")
	} else {
		fileReader, err := os.Open(jsonFile)
		if err != nil {
			return util.FailureWithDetail("Could not open file '"+jsonFile+"'", err.Error())
		}
		defer fileReader.Close()
		documentData, err = ioutil.ReadAll(fileReader)
		if err != nil {
			return util.FailureWithDetail("Failed to read '"+jsonFile+"'", err.Error())
		}
	}

	var doc map[string]interface{}
	json.Unmarshal(documentData, &doc)

	operationInFile := operationIn(doc)
	if operation == anyOperation { // Operation is decided by file content
		operation = operationInFile
	} else if operationInFile != "" && operationInFile != operation { // Otherwise operation must match
		return util.Failure("Wanted document operation is " + operation + " but the JSON file specifies " + operationInFile)
	}

	if documentId == "" { // Document id is decided by file content
		if doc[operation] == nil {
			return util.Failure("No document id given neither as argument or as a '" + operation + "' key in the json file")
		}
		documentId = doc[operation].(string) // document feeder format
	}

	documentPath, documentPathError := IdToURLPath(documentId)
	if documentPathError != nil {
		return util.Failure("Invalid document id '" + documentId + "': " + documentPathError.Error())
	}

	url, urlParseError := url.Parse(service.BaseURL + "/document/v1/" + documentPath)
	if urlParseError != nil {
		return util.Failure("Invalid request path: '" + service.BaseURL + "/document/v1/" + documentPath + "': " + urlParseError.Error())
	}

	request := &http.Request{
		URL:    url,
		Method: operationToHTTPMethod(operation),
		Header: header,
		Body:   ioutil.NopCloser(bytes.NewReader(documentData)),
	}
	response, err := serviceDo(service, request, jsonFile, options)
	if err != nil {
		return util.Failure("Request failed: " + err.Error())
	}

	defer response.Body.Close()
	if response.StatusCode == 200 {
		return util.Success(operation + " " + documentId)
	} else if response.StatusCode/100 == 4 {
		return util.FailureWithPayload("Invalid document operation: "+response.Status, util.ReaderToJSON(response.Body))
	} else {
		return util.FailureWithPayload(service.Description()+" at "+request.URL.Host+": "+response.Status, util.ReaderToJSON(response.Body))
	}
}

func operationIn(doc map[string]interface{}) string {
	if doc["put"] != nil {
		return "put"
	} else if doc["update"] != nil {
		return "update"
	} else if doc["remove"] != nil {
		return "remove"
	} else {
		return ""
	}
}

func operationToHTTPMethod(operation string) string {
	switch operation {
	case "put":
		return "POST"
	case "update":
		return "PUT"
	case "remove":
		return "DELETE"
	}
	panic("Unexpected document operation ''" + operation + "'")
}

func serviceDo(service *Service, request *http.Request, filename string, options OperationOptions) (*http.Response, error) {
	cmd, err := curl.RawArgs(request.URL.String())
	if err != nil {
		return nil, err
	}
	cmd.Method = request.Method
	for k, vs := range request.Header {
		for _, v := range vs {
			cmd.Header(k, v)
		}
	}
	cmd.BodyFile = filename
	cmd.Certificate = service.TLSOptions.CertificateFile
	cmd.PrivateKey = service.TLSOptions.PrivateKeyFile
	out := cmd.String() + "\n"
	if _, err := io.WriteString(options.CurlOutput, out); err != nil {
		return nil, err
	}
	return service.Do(request, options.Timeout)
}

func Get(documentId string, service *Service, options OperationOptions) util.OperationResult {
	documentPath, documentPathError := IdToURLPath(documentId)
	if documentPathError != nil {
		return util.Failure("Invalid document id '" + documentId + "': " + documentPathError.Error())
	}

	url, urlParseError := url.Parse(service.BaseURL + "/document/v1/" + documentPath)
	if urlParseError != nil {
		return util.Failure("Invalid request path: '" + service.BaseURL + "/document/v1/" + documentPath + "': " + urlParseError.Error())
	}

	request := &http.Request{
		URL:    url,
		Method: "GET",
	}
	response, err := serviceDo(service, request, "", options)
	if err != nil {
		return util.Failure("Request failed: " + err.Error())
	}

	defer response.Body.Close()
	if response.StatusCode == 200 {
		return util.SuccessWithPayload("Read "+documentId, util.ReaderToJSON(response.Body))
	} else if response.StatusCode/100 == 4 {
		return util.FailureWithPayload("Invalid document operation: "+response.Status, util.ReaderToJSON(response.Body))
	} else {
		return util.FailureWithPayload(service.Description()+" at "+request.URL.Host+": "+response.Status, util.ReaderToJSON(response.Body))
	}
}
<|endoftext|>


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

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the built-in primitive functions.

package golisp

import (
	"fmt"
)

func RegisterSpecialFormPrimitives() {
	MakePrimitiveFunction("cond", -1, CondImpl)
	MakePrimitiveFunction("case", -1, CaseImpl)
	MakePrimitiveFunction("if", -1, IfImpl)
	MakePrimitiveFunction("lambda", -1, LambdaImpl)
	MakePrimitiveFunction("define", -1, DefineImpl)
	MakePrimitiveFunction("defmacro", -1, DefmacroImpl)
	MakePrimitiveFunction("let", -1, LetImpl)
	MakePrimitiveFunction("begin", -1, BeginImpl)
	MakePrimitiveFunction("do", -1, DoImpl)
	MakePrimitiveFunction("apply", -1, ApplyImpl)
	MakePrimitiveFunction("eval", 1, EvalImpl)
	MakePrimitiveFunction("->", -1, ChainImpl)
	MakePrimitiveFunction("=>", -1, TapImpl)
	MakePrimitiveFunction("definition-of", 1, DefinitionOfImpl)
}

func CondImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var condition *Data
	for c := args; NotNilP(c); c = Cdr(c) {
		clause := Car(c)
		if !PairP(clause) {
			err = ProcessError("Cond expect a sequence of clauses that are lists", env)
			return
		}
		condition, err = Eval(Car(clause), env)
		if err != nil {
			return
		}
		if BooleanValue(condition) || StringValue(Car(clause)) == "else" {
			for e := Cdr(clause); NotNilP(e); e = Cdr(e) {
				result, err = Eval(Car(e), env)
				if err != nil {
					return
				}
			}
			return
		}
	}
	return
}

func evalList(l *Data, env *SymbolTableFrame) (result *Data, err error) {
	for sexpr := l; NotNilP(sexpr); sexpr = Cdr(sexpr) {
		result, err = Eval(Car(sexpr), env)
		if err != nil {
			return
		}
	}
	return
}

func CaseImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var keyValue *Data

	keyValue, err = Eval(Car(args), env)
	if err != nil {
		return
	}

	for clauseCell := Cdr(args); NotNilP(clauseCell); clauseCell = Cdr(clauseCell) {
		clause := Car(clauseCell)
		if !PairP(clause) {
			err = ProcessError("Case requires non-atomic clauses", env)
			return
		}
		if ListP(Car(clause)) {
			for v := Car(clause); NotNilP(v); v = Cdr(v) {
				if IsEqual(Car(v), keyValue) {
					return evalList(Cdr(clause), env)
				}
			}
		} else if IsEqual(Car(clause), SymbolWithName("else")) {
			return evalList(Cdr(clause), env)
		}
	}

	return
}

func IfImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) < 2 || Length(args) > 3 {
		err = ProcessError(fmt.Sprintf("IF requires 2 or 3 arguments. Received %d.", Length(args)), env)
		return
	}

	c, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	condition := BooleanValue(c)
	thenClause := Second(args)
	elseClause := Third(args)

	if condition {
		return Eval(thenClause, env)
	} else {
		return Eval(elseClause, env)
	}
}

func LambdaImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	params := Car(args)
	body := Cdr(args)
	return FunctionWithNameParamsBodyAndParent("anonymous", params, body, env), nil
}

func DefineImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var value *Data
	thing := Car(args)
	if SymbolP(thing) {
		value, err = Eval(Cadr(args), env)
		if err != nil {
			return
		}
	} else if PairP(thing) {
		name := Car(thing)
		params := Cdr(thing)
		thing = name
		if !SymbolP(name) {
			err = ProcessError("Function name has to be a symbol", env)
			return
		}
		body := Cdr(args)
		value = FunctionWithNameParamsBodyAndParent(StringValue(name), params, body, env)
	} else {
		err = ProcessError("Invalid definition", env)
		return
	}
	env.BindLocallyTo(thing, value)
	return value, nil
}

func DefmacroImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var value *Data
	thing := Car(args)
	if PairP(thing) {
		name := Car(thing)
		params := Cdr(thing)
		thing = name
		if !SymbolP(name) {
			err = ProcessError("Macro name has to be a symbol", env)
			return
		}
		body := Cadr(args)
		value = MacroWithNameParamsBodyAndParent(StringValue(name), params, body, env)
	} else {
		err = ProcessError("Invalid macro definition", env)
		return
	}
	env.BindLocallyTo(thing, value)
	return value, nil
}

func bindLetLocals(bindingForms *Data, env *SymbolTableFrame) (err error) {
	var name *Data
	var value *Data

	for cell := bindingForms; NotNilP(cell); cell = Cdr(cell) {
		bindingPair := Car(cell)
		if !PairP(bindingPair) {
			err = ProcessError("Let requires a list of bindings (with are pairs) as it's first argument", env)
			return
		}
		name = Car(bindingPair)
		if !SymbolP(name) {
			err = ProcessError("First part of a let binding pair must be a symbol", env)
		}
		value, err = Eval(Cadr(bindingPair), env)
		if err != nil {
			return
		}
		env.BindLocallyTo(name, value)
	}
	return
}

func LetImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) < 1 {
		err = ProcessError("Let requires at least a list of bindings", env)
		return
	}

	if !PairP(Car(args)) {
		err = ProcessError("Let requires a list of bindings as it's first argument", env)
		return
	}

	localEnv := NewSymbolTableFrameBelow(env)
	localEnv.Previous = env
	bindLetLocals(Car(args), localEnv)

	for cell := Cdr(args); NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		result, err = Eval(sexpr, localEnv)
		if err != nil {
			return
		}
	}

	return
}

func BeginImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	for cell := args; NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		result, err = Eval(sexpr, env)
		if err != nil {
			return
		}
	}
	return
}

func rebindDoLocals(bindingForms *Data, env *SymbolTableFrame) (err error) {
	var name *Data
	var value *Data

	for cell := bindingForms; NotNilP(cell); cell = Cdr(cell) {
		bindingTuple := Car(cell)
		name = First(bindingTuple)
		if NotNilP(Third(bindingTuple)) {
			value, err = Eval(Third(bindingTuple), env)
			if err != nil {
				return
			}
			env.BindLocallyTo(name, value)
		}
	}
	return
}

func DoImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) < 2 {
		err = ProcessError("Do requires at least a list of bindings and a test clause", env)
		return
	}

	bindings := Car(args)
	if !PairP(bindings) {
		err = ProcessError("Do requires a list of bindings as it's first argument", env)
		return
	}

	testClause := Cadr(args)
	if !PairP(testClause) {
		err = ProcessError("Do requires a list as it's second argument", env)
		return
	}

	localEnv := NewSymbolTableFrameBelow(env)
	localEnv.Previous = env
	bindLetLocals(bindings, localEnv)

	body := Cddr(args)

	var shouldExit *Data

	for true {
		shouldExit, err = Eval(Car(testClause), localEnv)
		if err != nil {
			return
		}

		if BooleanValue(shouldExit) {
			for cell := Cdr(testClause); NotNilP(cell); cell = Cdr(cell) {
				sexpr := Car(cell)
				result, err = Eval(sexpr, localEnv)
				if err != nil {
					return
				}
			}
			return
		}

		for cell := body; NotNilP(cell); cell = Cdr(cell) {
			sexpr := Car(cell)
			result, err = Eval(sexpr, localEnv)
			if err != nil {
				return
			}
		}

		rebindDoLocals(bindings, localEnv)
	}
	return
}

func ApplyImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) < 1 {
		err = ProcessError("apply requires at least one argument", env)
		return
	}

	f, err := Eval(Car(args), env)
	if err != nil {
		return
	}

	if !FunctionP(f) {
		err = ProcessError(fmt.Sprintf("apply requires a function as it's first argument, but got %s.", String(f)), env)
	}

	ary := make([]*Data, 0, Length(args)-1)

	var v *Data
	for c := Cdr(args); NotNilP(c); c = Cdr(c) {
		v, err = Eval(Car(c), env)
		if err != nil {
			return
		}
		ary = append(ary, v)
	}

	var argList *Data
	if ListP(ary[len(ary)-1]) {
		if len(ary) > 1 {
			argList = ArrayToListWithTail(ary[0:len(ary)-1], ary[len(ary)-1])
		} else {
			argList = ary[0]
		}
	} else {
		err = ProcessError("The last argument to apply must be a list", env)
		return
	}

	return Apply(f, argList, env)
}

func EvalImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	sexpr, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !ListP(sexpr) {
		err = ProcessError(fmt.Sprintf("eval expect a list argument, received a %s.", TypeName(TypeOf(sexpr))), env)
	}
	return Eval(sexpr, env)
}

func ChainImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) == 0 {
		err = ProcessError("-> requires at least an initial value.", env)
		return
	}

	var value *Data

	value, err = Eval(Car(args), env)
	if err != nil {
		return
	}

	for cell := Cdr(args); NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		var newExpr *Data
		if ListP(sexpr) {
			newExpr = Cons(Car(sexpr), Cons(value, Cdr(sexpr)))
		} else {
			newExpr = Cons(sexpr, Cons(value, nil))
		}
		value, err = Eval(newExpr, env)
		if err != nil {
			return
		}
	}
	result = value
	return
}

func TapImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) == 0 {
		err = ProcessError("tap requires at least an initial value.", env)
		return
	}

	var value *Data

	value, err = Eval(Car(args), env)
	if err != nil {
		return
	}
	result = value

	for cell := Cdr(args); NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		var newExpr *Data
		if ListP(sexpr) {
			newExpr = Cons(Car(sexpr), Cons(value, Cdr(sexpr)))
		} else {
			newExpr = Cons(sexpr, Cons(value, nil))
		}
		_, err = Eval(newExpr, env)
		if err != nil {
			return
		}
	}
	return
}

func DefinitionOfImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var name *Data = nil
	if SymbolP(Car(args)) {
		name = Car(args)
	} else {
		name = SymbolWithName("anonymous")
	}

	f, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !FunctionP(f) {
		err = ProcessError(fmt.Sprintf("code requires a function argument, but received a %s.", TypeName(TypeOf(f))), env)
		return
	}

	function := FunctionValue(f)
	if function.Name == "anonymous" {
		return Cons(SymbolWithName("define"), Cons(name, Cons(Cons(SymbolWithName("lambda"), Cons(function.Params, function.Body)), nil))), nil
	} else {
		return Cons(SymbolWithName("define"), Cons(Cons(SymbolWithName(function.Name), function.Params), function.Body)), nil
	}
}
<commit_msg>Added a version of eval that runs in the global environment.<commit_after>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the built-in primitive functions.

package golisp

import (
	"fmt"
)

func RegisterSpecialFormPrimitives() {
	MakePrimitiveFunction("cond", -1, CondImpl)
	MakePrimitiveFunction("case", -1, CaseImpl)
	MakePrimitiveFunction("if", -1, IfImpl)
	MakePrimitiveFunction("lambda", -1, LambdaImpl)
	MakePrimitiveFunction("define", -1, DefineImpl)
	MakePrimitiveFunction("defmacro", -1, DefmacroImpl)
	MakePrimitiveFunction("let", -1, LetImpl)
	MakePrimitiveFunction("begin", -1, BeginImpl)
	MakePrimitiveFunction("do", -1, DoImpl)
	MakePrimitiveFunction("apply", -1, ApplyImpl)
	MakePrimitiveFunction("eval", 1, EvalImpl)
	MakePrimitiveFunction("global-eval", 1, GlobalEvalImpl)
	MakePrimitiveFunction("->", -1, ChainImpl)
	MakePrimitiveFunction("=>", -1, TapImpl)
	MakePrimitiveFunction("definition-of", 1, DefinitionOfImpl)
}

func CondImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var condition *Data
	for c := args; NotNilP(c); c = Cdr(c) {
		clause := Car(c)
		if !PairP(clause) {
			err = ProcessError("Cond expect a sequence of clauses that are lists", env)
			return
		}
		condition, err = Eval(Car(clause), env)
		if err != nil {
			return
		}
		if BooleanValue(condition) || StringValue(Car(clause)) == "else" {
			for e := Cdr(clause); NotNilP(e); e = Cdr(e) {
				result, err = Eval(Car(e), env)
				if err != nil {
					return
				}
			}
			return
		}
	}
	return
}

func evalList(l *Data, env *SymbolTableFrame) (result *Data, err error) {
	for sexpr := l; NotNilP(sexpr); sexpr = Cdr(sexpr) {
		result, err = Eval(Car(sexpr), env)
		if err != nil {
			return
		}
	}
	return
}

func CaseImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var keyValue *Data

	keyValue, err = Eval(Car(args), env)
	if err != nil {
		return
	}

	for clauseCell := Cdr(args); NotNilP(clauseCell); clauseCell = Cdr(clauseCell) {
		clause := Car(clauseCell)
		if !PairP(clause) {
			err = ProcessError("Case requires non-atomic clauses", env)
			return
		}
		if ListP(Car(clause)) {
			for v := Car(clause); NotNilP(v); v = Cdr(v) {
				if IsEqual(Car(v), keyValue) {
					return evalList(Cdr(clause), env)
				}
			}
		} else if IsEqual(Car(clause), SymbolWithName("else")) {
			return evalList(Cdr(clause), env)
		}
	}

	return
}

func IfImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) < 2 || Length(args) > 3 {
		err = ProcessError(fmt.Sprintf("IF requires 2 or 3 arguments. Received %d.", Length(args)), env)
		return
	}

	c, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	condition := BooleanValue(c)
	thenClause := Second(args)
	elseClause := Third(args)

	if condition {
		return Eval(thenClause, env)
	} else {
		return Eval(elseClause, env)
	}
}

func LambdaImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	params := Car(args)
	body := Cdr(args)
	return FunctionWithNameParamsBodyAndParent("anonymous", params, body, env), nil
}

func DefineImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var value *Data
	thing := Car(args)
	if SymbolP(thing) {
		value, err = Eval(Cadr(args), env)
		if err != nil {
			return
		}
	} else if PairP(thing) {
		name := Car(thing)
		params := Cdr(thing)
		thing = name
		if !SymbolP(name) {
			err = ProcessError("Function name has to be a symbol", env)
			return
		}
		body := Cdr(args)
		value = FunctionWithNameParamsBodyAndParent(StringValue(name), params, body, env)
	} else {
		err = ProcessError("Invalid definition", env)
		return
	}
	env.BindLocallyTo(thing, value)
	return value, nil
}

func DefmacroImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var value *Data
	thing := Car(args)
	if PairP(thing) {
		name := Car(thing)
		params := Cdr(thing)
		thing = name
		if !SymbolP(name) {
			err = ProcessError("Macro name has to be a symbol", env)
			return
		}
		body := Cadr(args)
		value = MacroWithNameParamsBodyAndParent(StringValue(name), params, body, env)
	} else {
		err = ProcessError("Invalid macro definition", env)
		return
	}
	env.BindLocallyTo(thing, value)
	return value, nil
}

func bindLetLocals(bindingForms *Data, env *SymbolTableFrame) (err error) {
	var name *Data
	var value *Data

	for cell := bindingForms; NotNilP(cell); cell = Cdr(cell) {
		bindingPair := Car(cell)
		if !PairP(bindingPair) {
			err = ProcessError("Let requires a list of bindings (with are pairs) as it's first argument", env)
			return
		}
		name = Car(bindingPair)
		if !SymbolP(name) {
			err = ProcessError("First part of a let binding pair must be a symbol", env)
		}
		value, err = Eval(Cadr(bindingPair), env)
		if err != nil {
			return
		}
		env.BindLocallyTo(name, value)
	}
	return
}

func LetImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) < 1 {
		err = ProcessError("Let requires at least a list of bindings", env)
		return
	}

	if !PairP(Car(args)) {
		err = ProcessError("Let requires a list of bindings as it's first argument", env)
		return
	}

	localEnv := NewSymbolTableFrameBelow(env)
	localEnv.Previous = env
	bindLetLocals(Car(args), localEnv)

	for cell := Cdr(args); NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		result, err = Eval(sexpr, localEnv)
		if err != nil {
			return
		}
	}

	return
}

func BeginImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	for cell := args; NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		result, err = Eval(sexpr, env)
		if err != nil {
			return
		}
	}
	return
}

func rebindDoLocals(bindingForms *Data, env *SymbolTableFrame) (err error) {
	var name *Data
	var value *Data

	for cell := bindingForms; NotNilP(cell); cell = Cdr(cell) {
		bindingTuple := Car(cell)
		name = First(bindingTuple)
		if NotNilP(Third(bindingTuple)) {
			value, err = Eval(Third(bindingTuple), env)
			if err != nil {
				return
			}
			env.BindLocallyTo(name, value)
		}
	}
	return
}

func DoImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) < 2 {
		err = ProcessError("Do requires at least a list of bindings and a test clause", env)
		return
	}

	bindings := Car(args)
	if !PairP(bindings) {
		err = ProcessError("Do requires a list of bindings as it's first argument", env)
		return
	}

	testClause := Cadr(args)
	if !PairP(testClause) {
		err = ProcessError("Do requires a list as it's second argument", env)
		return
	}

	localEnv := NewSymbolTableFrameBelow(env)
	localEnv.Previous = env
	bindLetLocals(bindings, localEnv)

	body := Cddr(args)

	var shouldExit *Data

	for true {
		shouldExit, err = Eval(Car(testClause), localEnv)
		if err != nil {
			return
		}

		if BooleanValue(shouldExit) {
			for cell := Cdr(testClause); NotNilP(cell); cell = Cdr(cell) {
				sexpr := Car(cell)
				result, err = Eval(sexpr, localEnv)
				if err != nil {
					return
				}
			}
			return
		}

		for cell := body; NotNilP(cell); cell = Cdr(cell) {
			sexpr := Car(cell)
			result, err = Eval(sexpr, localEnv)
			if err != nil {
				return
			}
		}

		rebindDoLocals(bindings, localEnv)
	}
	return
}

func ApplyImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) < 1 {
		err = ProcessError("apply requires at least one argument", env)
		return
	}

	f, err := Eval(Car(args), env)
	if err != nil {
		return
	}

	if !FunctionP(f) {
		err = ProcessError(fmt.Sprintf("apply requires a function as it's first argument, but got %s.", String(f)), env)
	}

	ary := make([]*Data, 0, Length(args)-1)

	var v *Data
	for c := Cdr(args); NotNilP(c); c = Cdr(c) {
		v, err = Eval(Car(c), env)
		if err != nil {
			return
		}
		ary = append(ary, v)
	}

	var argList *Data
	if ListP(ary[len(ary)-1]) {
		if len(ary) > 1 {
			argList = ArrayToListWithTail(ary[0:len(ary)-1], ary[len(ary)-1])
		} else {
			argList = ary[0]
		}
	} else {
		err = ProcessError("The last argument to apply must be a list", env)
		return
	}

	return Apply(f, argList, env)
}

func EvalImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	sexpr, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !ListP(sexpr) {
		err = ProcessError(fmt.Sprintf("eval expect a list argument, received a %s.", TypeName(TypeOf(sexpr))), env)
	}
	return Eval(sexpr, env)
}

func GlobalEvalImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	sexpr, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !ListP(sexpr) {
		err = ProcessError(fmt.Sprintf("eval expect a list argument, received a %s.", TypeName(TypeOf(sexpr))), env)
	}
	return Eval(sexpr, Global)
}

func ChainImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) == 0 {
		err = ProcessError("-> requires at least an initial value.", env)
		return
	}

	var value *Data

	value, err = Eval(Car(args), env)
	if err != nil {
		return
	}

	for cell := Cdr(args); NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		var newExpr *Data
		if ListP(sexpr) {
			newExpr = Cons(Car(sexpr), Cons(value, Cdr(sexpr)))
		} else {
			newExpr = Cons(sexpr, Cons(value, nil))
		}
		value, err = Eval(newExpr, env)
		if err != nil {
			return
		}
	}
	result = value
	return
}

func TapImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args) == 0 {
		err = ProcessError("tap requires at least an initial value.", env)
		return
	}

	var value *Data

	value, err = Eval(Car(args), env)
	if err != nil {
		return
	}
	result = value

	for cell := Cdr(args); NotNilP(cell); cell = Cdr(cell) {
		sexpr := Car(cell)
		var newExpr *Data
		if ListP(sexpr) {
			newExpr = Cons(Car(sexpr), Cons(value, Cdr(sexpr)))
		} else {
			newExpr = Cons(sexpr, Cons(value, nil))
		}
		_, err = Eval(newExpr, env)
		if err != nil {
			return
		}
	}
	return
}

func DefinitionOfImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	var name *Data = nil
	if SymbolP(Car(args)) {
		name = Car(args)
	} else {
		name = SymbolWithName("anonymous")
	}

	f, err := Eval(Car(args), env)
	if err != nil {
		return
	}
	if !FunctionP(f) {
		err = ProcessError(fmt.Sprintf("code requires a function argument, but received a %s.", TypeName(TypeOf(f))), env)
		return
	}

	function := FunctionValue(f)
	if function.Name == "anonymous" {
		return Cons(SymbolWithName("define"), Cons(name, Cons(Cons(SymbolWithName("lambda"), Cons(function.Params, function.Body)), nil))), nil
	} else {
		return Cons(SymbolWithName("define"), Cons(Cons(SymbolWithName(function.Name), function.Params), function.Body)), nil
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Joseph deBlaquiere <jadeblaquiere@yahoo.com>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of ciphrtxt nor the names of its
//   contributors may be used to endorse or promote products derived from
//   this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package ciphrtxt

import (
	// "bytes"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	cwebsocket "github.com/jadeblaquiere/websocket-client"
)

const (
	DefaultWatchdogTimeout = 150 * time.Second
	DefaultTimeTickle      = 30 * time.Second
	DefaultStatusTickle    = 300 * time.Second
	DefaultPeersTickle     = 300 * time.Second
)

type WSDisconnectFunc func()

type WSProtocolHandler interface {
	TxHeader(rmh MessageHeader)
	OnDisconnect(f WSDisconnectFunc)
	Disconnect()
	Status() *StatusResponse
}

func NewWSProtocolHandler(con cwebsocket.ClientConnection, local *LocalHeaderCache, remote *HeaderCache) WSProtocolHandler {
	wsh := wsHandler{
		con:    con,
		local:  local,
		remote: remote,
	}
	if remote == nil {
		wsh.inbound = true
	}
	wsh.setup()
	wsHandlerListMutex.Lock()
	defer wsHandlerListMutex.Unlock()
	wsHandlerList = append(wsHandlerList, &wsh)
	return &wsh
}

type wsHandler struct {
	con          cwebsocket.ClientConnection
	local        *LocalHeaderCache
	remote       *HeaderCache
	tmpStatus    StatusResponse
	disconnect   WSDisconnectFunc
	watchdog     *time.Timer
	timeTickle   *time.Timer
	statusTickle *time.Timer
	peersTickle  *time.Timer
	abort        chan bool
	inbound      bool
}

var wsHandlerList []*wsHandler
var wsHandlerListMutex sync.Mutex

func (wsh *wsHandler) resetTimeTickle() {
	if !wsh.timeTickle.Stop() {
		<-wsh.timeTickle.C
	}
	wsh.timeTickle.Reset(DefaultTimeTickle)
	wsh.resetWatchdog()
}

func (wsh *wsHandler) resetStatusTickle() {
	if !wsh.statusTickle.Stop() {
		<-wsh.statusTickle.C
	}
	wsh.statusTickle.Reset(DefaultStatusTickle)
	wsh.resetWatchdog()
}

func (wsh *wsHandler) resetWatchdog() {
	if !wsh.watchdog.Stop() {
		<-wsh.watchdog.C
	}
	wsh.watchdog.Reset(DefaultWatchdogTimeout)
}

func (wsh *wsHandler) txTime(t int) {
	wsh.resetTimeTickle()
	wsh.log("tx->TIME to")
	// if wsh.remote != nil {
	// fmt.Printf("tx->TIME to %s:%d\n", wsh.remote.host, wsh.remote.port)
	// } else {
	// fmt.Printf("tx->TIME to Pending Peer\n")
	// }
	wsh.con.Emit("response-time", int(time.Now().Unix()))
}

func (wsh *wsHandler) rxTime(t int) {
	wsh.resetWatchdog()
	wsh.log("rx<-TIME from")
	if wsh.remote != nil {
		// fmt.Printf("rx<-TIME from %s:%d\n", wsh.remote.host, wsh.remote.port)
		wsh.remote.serverTime = uint32(t)
	}
}

func (wsh *wsHandler) txStatus(t int) {
	wsh.resetWatchdog()
	j, err := json.Marshal(wsh.local.Status())
	if err == nil {
		wsh.log("tx->STATUS to")
		// if wsh.remote != nil {
		// fmt.Printf("tx->STATUS to %s:%d\n", wsh.remote.host, wsh.remote.port)
		// } else {
		// fmt.Printf("tx->STATUS to Pending Peer\n")
		// }
		wsh.con.Emit("response-status", j)
	}
}

func (wsh *wsHandler) rxStatus(m []byte) {
	var status StatusResponse
	err := json.Unmarshal(m, &status)
	if err == nil {
		wsh.resetStatusTickle()
		wsh.log("rx<-STATUS from")
		if wsh.remote != nil {
			// fmt.Printf("rx<-STATUS from %s:%d\n", wsh.remote.host, wsh.remote.port)
			wsh.remote.status = status
		} else {
			// fmt.Printf("rx<-STATUS from Pending Peer %s:%d\n", status.Network.Host, status.Network.MSGPort)
			wsh.tmpStatus = status
		}
	}
}

func (wsh *wsHandler) txPeers(t int) {
	wsh.resetWatchdog()
	peers := wsh.local.ListPeers()
	for _, peer := range peers {
		j, err := json.Marshal(peer)
		if err == nil {
			wsh.log(fmt.Sprintf("tx->PEER (%s:%d) to ", peer.Host, peer.Port))
			// if wsh.remote != nil {
			// fmt.Printf("tx->PEER %s:%d to %s:%d\n", peer.Host, peer.Port, wsh.remote.host, wsh.remote.port)
			// } else {
			// fmt.Printf("tx->PEER %s:%d to Pending Peer\n", peer.Host, peer.Port)
			// }
			wsh.con.Emit("response-peer", j)
		}
	}
}

func (wsh *wsHandler) rxPeer(m []byte) {
	wsh.resetWatchdog()
	var peer PeerItemResponse
	err := json.Unmarshal(m, &peer)
	if err == nil {
		wsh.log(fmt.Sprintf("rx<-PEER (%s:%d) from", peer.Host, peer.Port))
		// if wsh.remote != nil {
		// fmt.Printf("rx<-PEER %s:%d from %s:%d\n", peer.Host, peer.Port, wsh.remote.host, wsh.remote.port)
		// } else {
		// fmt.Printf("rx<-PEER %s:%d from Pending Peer\n", peer.Host, peer.Port)
		//}
		wsh.local.AddPeer(peer.Host, peer.Port)
	}
}

func (wsh *wsHandler) TxHeader(rmh MessageHeader) {
	//fmt.Printf("tx->HEADER to %s:%d\n", wsh.remote.host, wsh.remote.port)
	wsh.log("tx->HEADER to")
	wsh.con.Emit("response-header", rmh.Serialize())
}

func (wsh *wsHandler) rxHeader(s string) {
	rmh := &RawMessageHeader{}
	err := rmh.Deserialize(s)
	if err == nil {
		wsh.resetWatchdog()
		wsh.log("rx<-HEADER from")
		if wsh.remote != nil {
			// fmt.Printf("rx<-HEADER from %s:%d\n", wsh.remote.host, wsh.remote.port)
			insert, err := wsh.remote.Insert(rmh)
			if err != nil {
				return
			}
			if insert {
				_, _ = wsh.local.Insert(rmh)
			}
			// } else {
			// fmt.Printf("rx<-HEADER from Pending Peer\n")
		}
	} else {
		fmt.Printf("rx<-HEADER, error deserializing %s (len %d)\n", s, len(s))
	}
}

func (wsh *wsHandler) log(logmsg string) {
	if wsh.remote != nil {
		fmt.Printf("%s %s:%d\n", logmsg, wsh.remote.host, wsh.remote.port)
	} else {
		fmt.Printf("%s Pending (%s:%d)\n", logmsg, wsh.tmpStatus.Network.Host, wsh.tmpStatus.Network.MSGPort)
	}
}

func (wsh *wsHandler) OnDisconnect(f WSDisconnectFunc) {
	wsh.disconnect = f
}

func (wsh *wsHandler) Status() *StatusResponse {
	if wsh.remote != nil {
		return &wsh.remote.status
	} else {
		return &wsh.tmpStatus
	}
}

func (wsh *wsHandler) setup() {
	wsh.watchdog = time.NewTimer(DefaultWatchdogTimeout)
	wsh.timeTickle = time.NewTimer(DefaultTimeTickle)
	wsh.statusTickle = time.NewTimer(DefaultStatusTickle)
	wsh.peersTickle = time.NewTimer(DefaultPeersTickle)
	wsh.abort = make(chan bool)
	wsh.con.On("request-time", wsh.txTime)
	wsh.con.On("response-time", wsh.rxTime)
	wsh.con.On("request-status", wsh.txStatus)
	wsh.con.On("response-status", wsh.rxStatus)
	wsh.con.On("response-header", wsh.rxHeader)
	wsh.con.On("request-peers", wsh.txPeers)
	wsh.con.On("response-peer", wsh.rxPeer)
	wsh.con.OnDisconnect(func() {
		wsh.Disconnect()
	})

	go wsh.eventLoop()
	go wsh.txPeers(0)
}

func (wsh *wsHandler) Disconnect() {
	if wsh.disconnect != nil {
		wsh.disconnect()

	}
	if !wsh.timeTickle.Stop() {
		<-wsh.timeTickle.C
	}
	if !wsh.statusTickle.Stop() {
		<-wsh.statusTickle.C
	}
	if !wsh.watchdog.Stop() {
		<-wsh.watchdog.C
	}
	wsh.abort <- true
	wsh.con.Disconnect()
	wsHandlerListMutex.Lock()
	defer wsHandlerListMutex.Unlock()
	for i, w := range wsHandlerList {
		if w == wsh {
			wsHandlerList[i] = wsHandlerList[len(wsHandlerList)-1]
			wsHandlerList[len(wsHandlerList)-1] = nil
			wsHandlerList = wsHandlerList[:len(wsHandlerList)-1]
			return
		}
	}
	panic("wsHandler.Disconnect: trying to remove element not in list")
}

func (wsh *wsHandler) eventLoop() {
	for {
		select {
		case <-wsh.watchdog.C:
			fmt.Println("Watchdog expired, closing connection")
			wsh.Disconnect()
			return
		case <-wsh.timeTickle.C:
			if wsh.remote != nil {
				fmt.Printf("tx->TIME REQUEST to %s:%d\n", wsh.remote.host, wsh.remote.port)
			} else {
				fmt.Printf("tx->TIME REQUEST to Pending Peer\n")
			}
			wsh.con.Emit("request-time", int(0))
			wsh.timeTickle.Reset(DefaultTimeTickle)
			continue
		case <-wsh.statusTickle.C:
			if wsh.remote != nil {
				fmt.Printf("tx->STATUS REQUEST to %s:%d\n", wsh.remote.host, wsh.remote.port)
			} else {
				fmt.Printf("tx->STATUS REQUEST to Pending Peer\n")
			}
			wsh.con.Emit("request-status", int(0))
			wsh.statusTickle.Reset(DefaultStatusTickle)
			continue
		case <-wsh.peersTickle.C:
			if wsh.remote != nil {
				fmt.Printf("tx->PEERS REQUEST to %s:%d\n", wsh.remote.host, wsh.remote.port)
			} else {
				fmt.Printf("tx->PEERS REQUEST to Pending Peer\n")
			}
			wsh.con.Emit("request-peers", int(0))
			wsh.statusTickle.Reset(DefaultStatusTickle)
			continue
		case done := <-wsh.abort:
			if done {
				return
			}
		}
	}
}
<commit_msg>detect no-connect state<commit_after>// Copyright (c) 2017, Joseph deBlaquiere <jadeblaquiere@yahoo.com>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of ciphrtxt nor the names of its
//   contributors may be used to endorse or promote products derived from
//   this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package ciphrtxt

import (
	// "bytes"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	cwebsocket "github.com/jadeblaquiere/websocket-client"
)

const (
	DefaultWatchdogTimeout = 150 * time.Second
	DefaultTimeTickle      = 30 * time.Second
	DefaultStatusTickle    = 300 * time.Second
	DefaultPeersTickle     = 300 * time.Second
)

type WSDisconnectFunc func()

type WSProtocolHandler interface {
	TxHeader(rmh MessageHeader)
	OnDisconnect(f WSDisconnectFunc)
	Disconnect()
	Status() *StatusResponse
}

func NewWSProtocolHandler(con cwebsocket.ClientConnection, local *LocalHeaderCache, remote *HeaderCache) WSProtocolHandler {
	wsh := wsHandler{
		con:    con,
		local:  local,
		remote: remote,
	}
	if remote == nil {
		wsh.inbound = true
	}
	wsh.setup()
	wsHandlerListMutex.Lock()
	defer wsHandlerListMutex.Unlock()
	wsHandlerList = append(wsHandlerList, &wsh)
	return &wsh
}

type wsHandler struct {
	con          cwebsocket.ClientConnection
	local        *LocalHeaderCache
	remote       *HeaderCache
	tmpStatus    *StatusResponse
	disconnect   WSDisconnectFunc
	watchdog     *time.Timer
	timeTickle   *time.Timer
	statusTickle *time.Timer
	peersTickle  *time.Timer
	abort        chan bool
	inbound      bool
}

var wsHandlerList []*wsHandler
var wsHandlerListMutex sync.Mutex

func (wsh *wsHandler) resetTimeTickle() {
	if !wsh.timeTickle.Stop() {
		<-wsh.timeTickle.C
	}
	wsh.timeTickle.Reset(DefaultTimeTickle)
	wsh.resetWatchdog()
}

func (wsh *wsHandler) resetStatusTickle() {
	if !wsh.statusTickle.Stop() {
		<-wsh.statusTickle.C
	}
	wsh.statusTickle.Reset(DefaultStatusTickle)
	wsh.resetWatchdog()
}

func (wsh *wsHandler) resetWatchdog() {
	if !wsh.watchdog.Stop() {
		<-wsh.watchdog.C
	}
	wsh.watchdog.Reset(DefaultWatchdogTimeout)
}

func (wsh *wsHandler) txTime(t int) {
	wsh.resetTimeTickle()
	wsh.log("tx->TIME to")
	// if wsh.remote != nil {
	// fmt.Printf("tx->TIME to %s:%d\n", wsh.remote.host, wsh.remote.port)
	// } else {
	// fmt.Printf("tx->TIME to Pending Peer\n")
	// }
	wsh.con.Emit("response-time", int(time.Now().Unix()))
}

func (wsh *wsHandler) rxTime(t int) {
	wsh.resetWatchdog()
	wsh.log("rx<-TIME from")
	if wsh.remote != nil {
		// fmt.Printf("rx<-TIME from %s:%d\n", wsh.remote.host, wsh.remote.port)
		wsh.remote.serverTime = uint32(t)
	}
}

func (wsh *wsHandler) txStatus(t int) {
	wsh.resetWatchdog()
	j, err := json.Marshal(wsh.local.Status())
	if err == nil {
		wsh.log("tx->STATUS to")
		// if wsh.remote != nil {
		// fmt.Printf("tx->STATUS to %s:%d\n", wsh.remote.host, wsh.remote.port)
		// } else {
		// fmt.Printf("tx->STATUS to Pending Peer\n")
		// }
		wsh.con.Emit("response-status", j)
	} else {
		fmt.Printf("CLIENT: failed to marshal status response")
	}
}

func (wsh *wsHandler) rxStatus(m []byte) {
	var status StatusResponse
	err := json.Unmarshal(m, &status)
	if err == nil {
		wsh.resetStatusTickle()
		wsh.log("rx<-STATUS from")
		if wsh.remote != nil {
			// fmt.Printf("rx<-STATUS from %s:%d\n", wsh.remote.host, wsh.remote.port)
			wsh.remote.status = status
		} else {
			// fmt.Printf("rx<-STATUS from Pending Peer %s:%d\n", status.Network.Host, status.Network.MSGPort)
			wsh.tmpStatus = &status
		}
	}
}

func (wsh *wsHandler) txPeers(t int) {
	wsh.resetWatchdog()
	peers := wsh.local.ListPeers()
	for _, peer := range peers {
		j, err := json.Marshal(peer)
		if err == nil {
			wsh.log(fmt.Sprintf("tx->PEER (%s:%d) to", peer.Host, peer.Port))
			// if wsh.remote != nil {
			// fmt.Printf("tx->PEER %s:%d to %s:%d\n", peer.Host, peer.Port, wsh.remote.host, wsh.remote.port)
			// } else {
			// fmt.Printf("tx->PEER %s:%d to Pending Peer\n", peer.Host, peer.Port)
			// }
			wsh.con.Emit("response-peer", j)
		}
	}
}

func (wsh *wsHandler) rxPeer(m []byte) {
	wsh.resetWatchdog()
	var peer PeerItemResponse
	err := json.Unmarshal(m, &peer)
	if err == nil {
		wsh.log(fmt.Sprintf("rx<-PEER (%s:%d) from", peer.Host, peer.Port))
		// if wsh.remote != nil {
		// fmt.Printf("rx<-PEER %s:%d from %s:%d\n", peer.Host, peer.Port, wsh.remote.host, wsh.remote.port)
		// } else {
		// fmt.Printf("rx<-PEER %s:%d from Pending Peer\n", peer.Host, peer.Port)
		//}
		wsh.local.AddPeer(peer.Host, peer.Port)
	}
}

func (wsh *wsHandler) TxHeader(rmh MessageHeader) {
	//fmt.Printf("tx->HEADER to %s:%d\n", wsh.remote.host, wsh.remote.port)
	wsh.log("tx->HEADER to")
	wsh.con.Emit("response-header", rmh.Serialize())
}

func (wsh *wsHandler) rxHeader(s string) {
	rmh := &RawMessageHeader{}
	err := rmh.Deserialize(s)
	if err == nil {
		wsh.resetWatchdog()
		wsh.log("rx<-HEADER from")
		if wsh.remote != nil {
			// fmt.Printf("rx<-HEADER from %s:%d\n", wsh.remote.host, wsh.remote.port)
			insert, err := wsh.remote.Insert(rmh)
			if err != nil {
				return
			}
			if insert {
				_, _ = wsh.local.Insert(rmh)
			}
			// } else {
			// fmt.Printf("rx<-HEADER from Pending Peer\n")
		}
	} else {
		fmt.Printf("rx<-HEADER, error deserializing %s (len %d)\n", s, len(s))
	}
}

func (wsh *wsHandler) log(logmsg string) {
	if wsh.remote != nil {
		fmt.Printf("%s %s:%d\n", logmsg, wsh.remote.host, wsh.remote.port)
	} else {
		if wsh.tmpStatus != nil {
			fmt.Printf("%s Pending (%s:%d)\n", logmsg, wsh.tmpStatus.Network.Host, wsh.tmpStatus.Network.MSGPort)
		} else {
			fmt.Printf("%s Pending (unknown)\n", logmsg)
		}
	}
}

func (wsh *wsHandler) OnDisconnect(f WSDisconnectFunc) {
	wsh.disconnect = f
}

func (wsh *wsHandler) Status() *StatusResponse {
	if wsh.remote != nil {
		return &wsh.remote.status
	} else {
		return wsh.tmpStatus
	}
}

func (wsh *wsHandler) setup() {
	wsh.watchdog = time.NewTimer(DefaultWatchdogTimeout)
	wsh.timeTickle = time.NewTimer(DefaultTimeTickle)
	wsh.statusTickle = time.NewTimer(DefaultStatusTickle)
	wsh.peersTickle = time.NewTimer(DefaultPeersTickle)
	wsh.abort = make(chan bool)
	wsh.con.On("request-time", wsh.txTime)
	wsh.con.On("response-time", wsh.rxTime)
	wsh.con.On("request-status", wsh.txStatus)
	wsh.con.On("response-status", wsh.rxStatus)
	wsh.con.On("response-header", wsh.rxHeader)
	wsh.con.On("request-peers", wsh.txPeers)
	wsh.con.On("response-peer", wsh.rxPeer)
	wsh.con.OnDisconnect(func() {
		wsh.Disconnect()
	})

	go wsh.eventLoop()
	go wsh.txPeers(0)
}

func (wsh *wsHandler) Disconnect() {
	if wsh.disconnect != nil {
		wsh.disconnect()

	}
	if !wsh.timeTickle.Stop() {
		<-wsh.timeTickle.C
	}
	if !wsh.statusTickle.Stop() {
		<-wsh.statusTickle.C
	}
	if !wsh.watchdog.Stop() {
		<-wsh.watchdog.C
	}
	wsh.abort <- true
	//wsh.con.Disconnect()
	wsHandlerListMutex.Lock()
	defer wsHandlerListMutex.Unlock()
	for i, w := range wsHandlerList {
		if w == wsh {
			wsHandlerList[i] = wsHandlerList[len(wsHandlerList)-1]
			wsHandlerList[len(wsHandlerList)-1] = nil
			wsHandlerList = wsHandlerList[:len(wsHandlerList)-1]
			return
		}
	}
	panic("wsHandler.Disconnect: trying to remove element not in list")
}

func (wsh *wsHandler) eventLoop() {
	for {
		select {
		case <-wsh.watchdog.C:
			fmt.Println("Watchdog expired, closing connection")
			wsh.Disconnect()
			return
		case <-wsh.timeTickle.C:
			wsh.log("tx->TIME REQUEST to")
			// if wsh.remote != nil {
			// fmt.Printf("tx->TIME REQUEST to %s:%d\n", wsh.remote.host, wsh.remote.port)
			// } else {
			// fmt.Printf("tx->TIME REQUEST to Pending Peer\n")
			// }
			wsh.con.Emit("request-time", int(0))
			wsh.timeTickle.Reset(DefaultTimeTickle)
			continue
		case <-wsh.statusTickle.C:
			wsh.log("tx->STATUS REQUEST to")
			// if wsh.remote != nil {
			// fmt.Printf("tx->STATUS REQUEST to %s:%d\n", wsh.remote.host, wsh.remote.port)
			// } else {
			// fmt.Printf("tx->STATUS REQUEST to Pending Peer\n")
			// }
			wsh.con.Emit("request-status", int(0))
			wsh.statusTickle.Reset(DefaultStatusTickle)
			continue
		case <-wsh.peersTickle.C:
			wsh.log("tx->PEERS REQUEST to")
			// if wsh.remote != nil {
			// 	fmt.Printf("tx->PEERS REQUEST to %s:%d\n", wsh.remote.host, wsh.remote.port)
			// } else {
			// 	fmt.Printf("tx->PEERS REQUEST to Pending Peer\n")
			// }
			wsh.con.Emit("request-peers", int(0))
			wsh.statusTickle.Reset(DefaultStatusTickle)
			continue
		case done := <-wsh.abort:
			if done {
				return
			}
		}
	}
}
<|endoftext|>