<commit_before>// Package matrix provides functions for simple linear algebra
package matrix

import (
	"bytes"
	"fmt"
	"math"
	"math/rand"
	"strconv"
	"strings"
)

var (
	ErrIncompatibleSizes = &MatrixError{"Incompatible sizes of matricies"}
	ErrOutOfBounds       = &MatrixError{"The element you are trying to access is out of bounds."}
)

// Describes error during calculations
type MatrixError struct {
	ErrorString string
}

// Return the Error's string
func (err *MatrixError) Error() string { return err.ErrorString }

// Matrix construct that holds all the information about a matrix
type Matrix struct {
	rows, cols int
	values     []float64
}

// A function that will apply an abitary transformation to an element in the matrix
type ApplyFunc func(index int, value float64) float64

// Creates a new Matrix and initializes all values to 0
func Zeros(rows, cols int) *Matrix {
	A := new(Matrix)

	A.rows = rows
	A.cols = cols
	A.values = make([]float64, rows*cols)

	return A
}

// Creates a new Matrix and initializes all values to 1
func Ones(rows, cols int) *Matrix {
	A := Zeros(rows, cols)
	A.AddNum(1)

	return A
}

// Creates a new identity matrix
func Eye(size int) *Matrix {
	A := Zeros(size, size)

	for i := 1; i <= size; i++ {
		A.Set(i, i, 1)
	}

	return A
}

// Constructs a new matrix with random values in range [0;1)
func Rand(rows, cols int) *Matrix {
	A := Zeros(rows, cols)

	for i := range A.values {
		A.values[i] = rand.Float64()
	}

	return A
}

// Constructs a new Matrix from a Matlab style representation
func FromMatlab(str string) *Matrix {
	rows := strings.Split(str, ";")

	for i, row := range rows {
		rows[i] = strings.Replace(row, ",", " ", -1)
	}

	nRows := len(rows)
	nColumns := len(strings.Fields(rows[0]))

	A := Zeros(nRows, nColumns)

	for i, row := range rows {
		row = strings.Trim(row, "[] ")
		strNums := strings.Fields(row)

		for j, num := range strNums {
			n, _ := strconv.ParseFloat(num, 64)
			A.Set(i+1, j+1, n)
		}
	}

	return A
}

// Return a Matlab representation of the matrix
func (A *Matrix) ToMatlab() string {
	buffer := new(bytes.Buffer)
	buffer.WriteString("[")

	for i, v := range A.values {
		buffer.WriteString(fmt.Sprintf("%f ", v))

		if (i+1)%A.cols == 0 {
			buffer.WriteString("; ")
		}
	}

	buffer.WriteString("]")

	return buffer.String()
}

// Gives the dimensions of the matrix
func (A *Matrix) Dim() (int, int) {
	return A.rows, A.cols
}

// Return the number of rows in the matrix
func (A *Matrix) Rows() int {
	return A.rows
}

// Return the number of columns in the matrix
func (A *Matrix) Columns() int {
	return A.cols
}

// Returns an array of all the values
func (A *Matrix) Values() []float64 {
	tmp := make([]float64, len(A.values))
	copy(tmp, A.values)
	return tmp
}

// Returns a string representation of the matrix
func (A *Matrix) String() string {
	buffer := new(bytes.Buffer)

	for i, elem := range A.values {
		buffer.WriteString(fmt.Sprintf("%.3f ", elem))

		if (i+1)%A.cols == 0 {
			buffer.WriteString("\n")
		}
	}

	return buffer.String()
}

// Returns an exact copy of the matrix
func (A *Matrix) Copy() *Matrix {
	B := Zeros(A.rows, A.cols)
	copy(B.values, A.values)

	return B
}

func (A *Matrix) Apply(f ApplyFunc) *Matrix {
	for i, v := range A.values {
		A.values[i] = f(i, v)
	}

	return A
}

func (A *Matrix) Get(row, col int) float64 {
	return A.values[(row-1)*A.cols+col-1]
}

// Set the element at [row, col] to val.
//
// Warning: This is an unsafe method to use, it does no boundary
// checking what so ever. If you'd like a safe version
// use: SafeSet
func (A *Matrix) Set(row, col int, val float64) {

	A.values[(row-1)*A.cols+col-1] = val

}

// Transpose the matrix
func (A *Matrix) Transpose() *Matrix {
	B := Zeros(A.cols, A.rows)

	for i := 1; i <= A.rows; i++ {
		for j := 1; j <= A.cols; j++ {
			B.values[(j-1)*B.cols+i-1] = A.values[(i-1)*A.cols+j-1]
		}
	}

	return B
}

// Add B to the matrix A, produces new matrix
func (A *Matrix) Add(B *Matrix) (*Matrix, error) {
	if !sameSize(A, B) {
		return nil, ErrIncompatibleSizes
	}

	C := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		C.values[i] = val + B.values[i]
	}

	return C, nil
}

// Subtract B from the matrix A, produces a new matrix
func (A *Matrix) Sub(B *Matrix) (*Matrix, error) {
	if !sameSize(A, B) {
		return nil, ErrIncompatibleSizes
	}

	C := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		C.values[i] = val - B.values[i]
	}

	return C, nil
}

// Multiplies 2 matricies with each other.
// Returns a new matrix.
//
// Warning: This is an unsafe method to use, it does no boundary
// checking what so ever. If you'd like a safe version
// use: SafeMul
func (A *Matrix) Mul(B *Matrix) *Matrix {

	C := Zeros(A.rows, B.cols)

	for i := 0; i < C.rows; i++ {
		for j := 0; j < C.cols; j++ {
			sum := float64(0)

			for k := 0; k < A.cols; k++ {
				sum += A.values[i*A.cols+k] * B.values[k*B.cols+j]
			}

			C.values[i*C.rows+j] = sum
		}
	}

	return C
}

// Standard scalar product of 2 matricies.
// Returns a new matrix.
//
// Warning: This is an unsafe method to use, it does no boundary
// checking what so ever. If you'd like a safe version
// use: SafeDot
func (A *Matrix) Dot(B *Matrix) *Matrix {
	C := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		C.values[i] = val * B.values[i]
	}

	return C
}

// Scale the matrix by the factor f
func (A *Matrix) Scale(f float64) *Matrix {
	B := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		B.values[i] = val * f
	}

	return B
}

// Take every element of the matrix to the power of n
func (A *Matrix) Power(n float64) *Matrix {
	B := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		B.values[i] = math.Pow(val, n)
	}

	return B
}

// Add n to all elements in the matrix (in-place)
func (A *Matrix) AddNum(n float64) *Matrix {
	for i := range A.values {
		A.values[i] += n
	}

	return A
}
<commit_msg>extended documentation; I hope all the safe/unsafe functions are actually going to work out as well as I'm thinking right now<commit_after>// Package matrix provides functions for simple linear algebra
package matrix

import (
	"bytes"
	"fmt"
	"math"
	"math/rand"
	"strconv"
	"strings"
)

var (
	ErrIncompatibleSizes = &MatrixError{"Incompatible sizes of matricies"}
	ErrOutOfBounds       = &MatrixError{"The element you are trying to access is out of bounds."}
)

// Describes error during calculations
type MatrixError struct {
	ErrorString string
}

// Return the Error's string
func (err *MatrixError) Error() string { return err.ErrorString }

// Matrix construct that holds all the information about a matrix
type Matrix struct {
	rows, cols int
	values     []float64
}

// A function that will apply an abitary transformation to an element in the matrix
type ApplyFunc func(index int, value float64) float64

// Creates a new Matrix and initializes all values to 0
func Zeros(rows, cols int) *Matrix {
	A := new(Matrix)

	A.rows = rows
	A.cols = cols
	A.values = make([]float64, rows*cols)

	return A
}

// Creates a new Matrix and initializes all values to 1
func Ones(rows, cols int) *Matrix {
	A := Zeros(rows, cols)
	A.AddNum(1)

	return A
}

// Creates a new identity matrix
func Eye(size int) *Matrix {
	A := Zeros(size, size)

	for i := 1; i <= size; i++ {
		A.Set(i, i, 1)
	}

	return A
}

// Constructs a new matrix with random values in range [0;1)
func Rand(rows, cols int) *Matrix {
	A := Zeros(rows, cols)

	for i := range A.values {
		A.values[i] = rand.Float64()
	}

	return A
}

// Constructs a new Matrix from a Matlab style representation
//
//	A := matrix.FromMatlab("[1 2 3; 4 5 6]")
func FromMatlab(str string) *Matrix {
	rows := strings.Split(str, ";")

	for i, row := range rows {
		rows[i] = strings.Replace(row, ",", " ", -1)
	}

	nRows := len(rows)
	nColumns := len(strings.Fields(rows[0]))

	A := Zeros(nRows, nColumns)

	for i, row := range rows {
		row = strings.Trim(row, "[] ")
		strNums := strings.Fields(row)

		for j, num := range strNums {
			n, _ := strconv.ParseFloat(num, 64)
			A.Set(i+1, j+1, n)
		}
	}

	return A
}

// Return a Matlab representation of the matrix
func (A *Matrix) ToMatlab() string {
	buffer := new(bytes.Buffer)
	buffer.WriteString("[")

	for i, v := range A.values {
		buffer.WriteString(fmt.Sprintf("%f ", v))

		if (i+1)%A.cols == 0 {
			buffer.WriteString("; ")
		}
	}

	buffer.WriteString("]")

	return buffer.String()
}

// Gives the dimensions of the matrix
func (A *Matrix) Dim() (int, int) {
	return A.rows, A.cols
}

// Return the number of rows in the matrix
func (A *Matrix) Rows() int {
	return A.rows
}

// Return the number of columns in the matrix
func (A *Matrix) Columns() int {
	return A.cols
}

// Returns an array of all the values
func (A *Matrix) Values() []float64 {
	tmp := make([]float64, len(A.values))
	copy(tmp, A.values)
	return tmp
}

// Returns a string representation of the matrix
func (A *Matrix) String() string {
	buffer := new(bytes.Buffer)

	for i, elem := range A.values {
		buffer.WriteString(fmt.Sprintf("%.3f ", elem))

		if (i+1)%A.cols == 0 {
			buffer.WriteString("\n")
		}
	}

	return buffer.String()
}

// Returns an exact copy of the matrix
func (A *Matrix) Copy() *Matrix {
	B := Zeros(A.rows, A.cols)
	copy(B.values, A.values)

	return B
}

// Give a function, apply its transformation to every element in the matrix.
//
//	A := matrix.Rand(4, 8)
//	A.Apply(func(index int, value float64) float64 {
//		return sigmoid(value)
//	})
func (A *Matrix) Apply(f ApplyFunc) *Matrix {
	for i, v := range A.values {
		A.values[i] = f(i, v)
	}

	return A
}

// Get the element at [row, col].
//
// Warning: This is an unsafe method to use, it does no boundary
// checking what so ever. If you'd like a safe version
// use: SafeGet
func (A *Matrix) Get(row, col int) float64 {
	return A.values[(row-1)*A.cols+col-1]
}

// Set the element at [row, col] to val.
//
// Warning: This is an unsafe method to use, it does no boundary
// checking what so ever. If you'd like a safe version
// use: SafeSet
func (A *Matrix) Set(row, col int, val float64) {

	A.values[(row-1)*A.cols+col-1] = val

}

// Transpose the matrix
func (A *Matrix) Transpose() *Matrix {
	B := Zeros(A.cols, A.rows)

	for i := 1; i <= A.rows; i++ {
		for j := 1; j <= A.cols; j++ {
			B.values[(j-1)*B.cols+i-1] = A.values[(i-1)*A.cols+j-1]
		}
	}

	return B
}

// Add B to the matrix A, produces new matrix
func (A *Matrix) Add(B *Matrix) (*Matrix, error) {
	if !sameSize(A, B) {
		return nil, ErrIncompatibleSizes
	}

	C := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		C.values[i] = val + B.values[i]
	}

	return C, nil
}

// Subtract B from the matrix A, produces a new matrix
func (A *Matrix) Sub(B *Matrix) (*Matrix, error) {
	if !sameSize(A, B) {
		return nil, ErrIncompatibleSizes
	}

	C := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		C.values[i] = val - B.values[i]
	}

	return C, nil
}

// Multiplies 2 matricies with each other.
// Returns a new matrix.
//
// Warning: This is an unsafe method to use, it does no boundary
// checking what so ever. If you'd like a safe version
// use: SafeMul
func (A *Matrix) Mul(B *Matrix) *Matrix {

	C := Zeros(A.rows, B.cols)

	for i := 0; i < C.rows; i++ {
		for j := 0; j < C.cols; j++ {
			sum := float64(0)

			for k := 0; k < A.cols; k++ {
				sum += A.values[i*A.cols+k] * B.values[k*B.cols+j]
			}

			C.values[i*C.rows+j] = sum
		}
	}

	return C
}

// Standard scalar product of 2 matricies.
// Returns a new matrix.
//
// Warning: This is an unsafe method to use, it does no boundary
// checking what so ever. If you'd like a safe version
// use: SafeDot
func (A *Matrix) Dot(B *Matrix) *Matrix {
	C := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		C.values[i] = val * B.values[i]
	}

	return C
}

// Scale the matrix by the factor f
func (A *Matrix) Scale(f float64) *Matrix {
	B := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		B.values[i] = val * f
	}

	return B
}

// Take every element of the matrix to the power of n
func (A *Matrix) Power(n float64) *Matrix {
	B := Zeros(A.rows, A.cols)

	for i, val := range A.values {
		B.values[i] = math.Pow(val, n)
	}

	return B
}

// Add n to all elements in the matrix (in-place)
func (A *Matrix) AddNum(n float64) *Matrix {
	for i := range A.values {
		A.values[i] += n
	}

	return A
}
<|endoftext|>


<commit_before>package extra

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strconv"

	"github.com/pkg/errors"

	"github.com/elpinal/coco3/extra/ast"
	"github.com/elpinal/coco3/extra/parser" // Only for ParseError.
	"github.com/elpinal/coco3/extra/typed"
	"github.com/elpinal/coco3/extra/types"
)

type Env struct {
	cmds map[string]typed.Command
}

func New() Env {
	return Env{cmds: map[string]typed.Command{
		"exec": execCommand,
		"cd":   cdCommand,
		"exit": exitCommand,
		"free": freeCommand,

		"git":   gitCommand,
		"cargo": cargoCommand,
		"go":    goCommand,
		"stack": stackCommand,
	}}
}

func WithoutDefault() Env {
	return Env{cmds: make(map[string]typed.Command)}
}

func (e *Env) Bind(name string, c typed.Command) {
	e.cmds[name] = c
}

func (e *Env) Eval(command *ast.Command) error {
	if command == nil {
		return nil
	}
	tc, found := e.cmds[command.Name.Lit]
	if !found {
		return &parser.ParseError{
			Msg:    fmt.Sprintf("no such typed command: %q", command.Name.Lit),
			Line:   command.Name.Line,
			Column: command.Name.Column,
		}
	}
	if len(command.Args) != len(tc.Params) {
		return &parser.ParseError{
			Msg:    fmt.Sprintf("the length of args (%d) != the one of params (%d)", len(command.Args), len(tc.Params)),
			Line:   command.Name.Line,
			Column: command.Name.Column,
		}
	}
	for i, arg := range command.Args {
		if arg.Type() != tc.Params[i] {
			return &parser.ParseError{
				Msg:    fmt.Sprintf("type mismatch: (%v) (type of %v) does not match with (%v) (expected type)", arg.Type(), arg, tc.Params[i]),
				Line:   command.Name.Line,
				Column: command.Name.Column,
			}
		}
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	defer close(c)
	defer signal.Stop(c)

	return tc.Fn(command.Args)
}

func toSlice(list ast.List) ([]string, error) {
	ret := make([]string, 0, list.Length())
	for {
		switch x := list.(type) {
		case *ast.Cons:
			ret = append(ret, x.Head)
			list = x.Tail
		case *ast.Empty:
			return ret, nil
		default:
			return nil, fmt.Errorf("unexpected list type: %T", x)
		}
	}
}

var execCommand = typed.Command{
	Params: []types.Type{types.String, types.StringList},
	Fn: func(args []ast.Expr) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, "exec")
		}
		cmd := exec.Command(args[0].(*ast.String).Lit, cmdArgs...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	},
}

var cdCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(args []ast.Expr) error {
		return os.Chdir(args[0].(*ast.String).Lit)
	},
}

var exitCommand = typed.Command{
	Params: []types.Type{types.Int},
	Fn: func(args []ast.Expr) error {
		n, err := strconv.Atoi(args[0].(*ast.Int).Lit)
		if err != nil {
			return err
		}
		os.Exit(n)
		return nil
	},
}

var freeCommand = typed.Command{
	Params: []types.Type{types.String, types.StringList},
	Fn: func(args []ast.Expr) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, "free")
		}
		name := args[0].(*ast.String).Lit
		cmd := exec.Cmd{Path: name, Args: append([]string{name}, cmdArgs...)}
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	},
}

func commandsInCommand(name string) func([]ast.Expr) error {
	return func(args []ast.Expr) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, name)
		}
		var cmd *exec.Cmd
		switch lit := args[0].(*ast.Ident).Lit; lit {
		case "command":
			cmd = exec.Command(name, cmdArgs...)
		default:
			cmd = exec.Command(name, append([]string{lit}, cmdArgs...)...)
		}
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	}
}

var gitCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("git"),
}

var cargoCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("cargo"),
}

var goCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("go"),
}

var stackCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("stack"),
}
<commit_msg>Add some typed commands<commit_after>package extra

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strconv"

	"github.com/pkg/errors"

	"github.com/elpinal/coco3/extra/ast"
	"github.com/elpinal/coco3/extra/parser" // Only for ParseError.
	"github.com/elpinal/coco3/extra/typed"
	"github.com/elpinal/coco3/extra/types"
)

type Env struct {
	cmds map[string]typed.Command
}

func New() Env {
	return Env{cmds: map[string]typed.Command{
		"exec": execCommand,
		"cd":   cdCommand,
		"exit": exitCommand,
		"free": freeCommand,

		"git":   gitCommand,
		"cargo": cargoCommand,
		"go":    goCommand,
		"stack": stackCommand,

		"vim":    vimCommand,
		"emacs":  emacsCommand,
		"screen": screenCommand,
	}}
}

func WithoutDefault() Env {
	return Env{cmds: make(map[string]typed.Command)}
}

func (e *Env) Bind(name string, c typed.Command) {
	e.cmds[name] = c
}

func (e *Env) Eval(command *ast.Command) error {
	if command == nil {
		return nil
	}
	tc, found := e.cmds[command.Name.Lit]
	if !found {
		return &parser.ParseError{
			Msg:    fmt.Sprintf("no such typed command: %q", command.Name.Lit),
			Line:   command.Name.Line,
			Column: command.Name.Column,
		}
	}
	if len(command.Args) != len(tc.Params) {
		return &parser.ParseError{
			Msg:    fmt.Sprintf("the length of args (%d) != the one of params (%d)", len(command.Args), len(tc.Params)),
			Line:   command.Name.Line,
			Column: command.Name.Column,
		}
	}
	for i, arg := range command.Args {
		if arg.Type() != tc.Params[i] {
			return &parser.ParseError{
				Msg:    fmt.Sprintf("type mismatch: (%v) (type of %v) does not match with (%v) (expected type)", arg.Type(), arg, tc.Params[i]),
				Line:   command.Name.Line,
				Column: command.Name.Column,
			}
		}
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	defer close(c)
	defer signal.Stop(c)

	return tc.Fn(command.Args)
}

func toSlice(list ast.List) ([]string, error) {
	ret := make([]string, 0, list.Length())
	for {
		switch x := list.(type) {
		case *ast.Cons:
			ret = append(ret, x.Head)
			list = x.Tail
		case *ast.Empty:
			return ret, nil
		default:
			return nil, fmt.Errorf("unexpected list type: %T", x)
		}
	}
}

var execCommand = typed.Command{
	Params: []types.Type{types.String, types.StringList},
	Fn: func(args []ast.Expr) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, "exec")
		}
		cmd := exec.Command(args[0].(*ast.String).Lit, cmdArgs...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	},
}

var cdCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(args []ast.Expr) error {
		return os.Chdir(args[0].(*ast.String).Lit)
	},
}

var exitCommand = typed.Command{
	Params: []types.Type{types.Int},
	Fn: func(args []ast.Expr) error {
		n, err := strconv.Atoi(args[0].(*ast.Int).Lit)
		if err != nil {
			return err
		}
		os.Exit(n)
		return nil
	},
}

var freeCommand = typed.Command{
	Params: []types.Type{types.String, types.StringList},
	Fn: func(args []ast.Expr) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, "free")
		}
		name := args[0].(*ast.String).Lit
		cmd := exec.Cmd{Path: name, Args: append([]string{name}, cmdArgs...)}
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	},
}

func commandsInCommand(name string) func([]ast.Expr) error {
	return func(args []ast.Expr) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, name)
		}
		var cmd *exec.Cmd
		switch lit := args[0].(*ast.Ident).Lit; lit {
		case "command":
			cmd = exec.Command(name, cmdArgs...)
		default:
			cmd = exec.Command(name, append([]string{lit}, cmdArgs...)...)
		}
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	}
}

var gitCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("git"),
}

var cargoCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("cargo"),
}

var goCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("go"),
}

var stackCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("stack"),
}

func stdExec(name string) func([]ast.Expr) error {
	return func(_ []ast.Expr) error {
		cmd := exec.Command(name)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	}
}

var vimCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("vim"),
}

var emacsCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("emacs"),
}

var screenCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("screen"),
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/arn"
	"github.com/aws/aws-sdk-go/service/redshift"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsRedshiftSnapshotCopyGrant() *schema.Resource {
	return &schema.Resource{
		// There is no API for updating/modifying grants, hence no Update
		// Instead changes to most fields will force a new resource
		Create: resourceAwsRedshiftSnapshotCopyGrantCreate,
		Read:   resourceAwsRedshiftSnapshotCopyGrantRead,
		Update: resourceAwsRedshiftSnapshotCopyGrantUpdate,
		Delete: resourceAwsRedshiftSnapshotCopyGrantDelete,
		Exists: resourceAwsRedshiftSnapshotCopyGrantExists,

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"snapshot_copy_grant_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"kms_key_id": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},
			"tags": tagsSchema(),
		},
	}
}

func resourceAwsRedshiftSnapshotCopyGrantCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	grantName := d.Get("snapshot_copy_grant_name").(string)

	input := redshift.CreateSnapshotCopyGrantInput{
		SnapshotCopyGrantName: aws.String(grantName),
	}

	if v, ok := d.GetOk("kms_key_id"); ok {
		input.KmsKeyId = aws.String(v.(string))
	}

	input.Tags = tagsFromMapRedshift(d.Get("tags").(map[string]interface{}))

	log.Printf("[DEBUG]: Adding new Redshift SnapshotCopyGrant: %s", input)

	var out *redshift.CreateSnapshotCopyGrantOutput
	var err error

	out, err = conn.CreateSnapshotCopyGrant(&input)

	if err != nil {
		return fmt.Errorf("error creating Redshift Snapshot Copy Grant (%s): %s", grantName, err)
	}

	log.Printf("[DEBUG] Created new Redshift SnapshotCopyGrant: %s", *out.SnapshotCopyGrant.SnapshotCopyGrantName)
	d.SetId(grantName)

	return resourceAwsRedshiftSnapshotCopyGrantRead(d, meta)
}

func resourceAwsRedshiftSnapshotCopyGrantRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	grantName := d.Id()
	log.Printf("[DEBUG] Looking for grant: %s", grantName)
	grant, err := findAwsRedshiftSnapshotCopyGrantWithRetry(conn, grantName)

	if err != nil {
		return err
	}

	if grant == nil {
		log.Printf("[WARN] %s Redshift snapshot copy grant not found, removing from state file", grantName)
		d.SetId("")
		return nil
	}

	arn := arn.ARN{
		Partition: meta.(*AWSClient).partition,
		Service:   "redshift",
		Region:    meta.(*AWSClient).region,
		AccountID: meta.(*AWSClient).accountid,
		Resource:  fmt.Sprintf("snapshotcopygrant:%s", grantName),
	}.String()

	d.Set("arn", arn)

	d.Set("kms_key_id", grant.KmsKeyId)
	d.Set("snapshot_copy_grant_name", grant.SnapshotCopyGrantName)
	if err := d.Set("tags", tagsToMapRedshift(grant.Tags)); err != nil {
		return fmt.Errorf("Error setting Redshift Snapshot Copy Grant Tags: %#v", err)
	}

	return nil
}

func resourceAwsRedshiftSnapshotCopyGrantUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	d.Partial(true)

	if tagErr := setTagsRedshift(conn, d); tagErr != nil {
		return tagErr
	} else {
		d.SetPartial("tags")
	}

	d.Partial(false)

	return resourceAwsRedshiftSnapshotCopyGrantRead(d, meta)
}

func resourceAwsRedshiftSnapshotCopyGrantDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	grantName := d.Id()

	deleteInput := redshift.DeleteSnapshotCopyGrantInput{
		SnapshotCopyGrantName: aws.String(grantName),
	}

	log.Printf("[DEBUG] Deleting snapshot copy grant: %s", grantName)
	_, err := conn.DeleteSnapshotCopyGrant(&deleteInput)

	if err != nil {
		if isAWSErr(err, redshift.ErrCodeSnapshotCopyGrantNotFoundFault, "") {
			return nil
		}
		return err
	}

	log.Printf("[DEBUG] Checking if grant is deleted: %s", grantName)
	err = waitForAwsRedshiftSnapshotCopyGrantToBeDeleted(conn, grantName)

	return err
}

func resourceAwsRedshiftSnapshotCopyGrantExists(d *schema.ResourceData, meta interface{}) (bool, error) {
	conn := meta.(*AWSClient).redshiftconn

	grantName := d.Id()

	log.Printf("[DEBUG] Looking for Grant: %s", grantName)
	grant, err := findAwsRedshiftSnapshotCopyGrantWithRetry(conn, grantName)

	if err != nil {
		return false, err
	}
	if grant != nil {
		return true, err
	}

	return false, nil
}

func getAwsRedshiftSnapshotCopyGrant(grants []*redshift.SnapshotCopyGrant, grantName string) *redshift.SnapshotCopyGrant {
	for _, grant := range grants {
		if *grant.SnapshotCopyGrantName == grantName {
			return grant
		}
	}

	return nil
}

/*
In the functions below it is not possible to use retryOnAwsCodes function, as there
is no get grant call, so an error has to be created if the grant is or isn't returned
by the describe grants call when expected.
*/

// NB: This function only retries the grant not being returned and some edge cases, while AWS Errors
// are handled by the findAwsRedshiftSnapshotCopyGrant function
func findAwsRedshiftSnapshotCopyGrantWithRetry(conn *redshift.Redshift, grantName string) (*redshift.SnapshotCopyGrant, error) {
	var grant *redshift.SnapshotCopyGrant
	err := resource.Retry(3*time.Minute, func() *resource.RetryError {
		var err error
		grant, err = findAwsRedshiftSnapshotCopyGrant(conn, grantName, nil)

		if err != nil {
			if serr, ok := err.(*resource.NotFoundError); ok {
				// Force a retry if the grant should exist
				return resource.RetryableError(serr)
			}

			return resource.NonRetryableError(err)
		}

		return nil
	})
	if isResourceTimeoutError(err) {
		grant, err = findAwsRedshiftSnapshotCopyGrant(conn, grantName, nil)
	}
	if err != nil {
		return nil, fmt.Errorf("Error finding snapshot copy grant: %s", err)
	}
	return grant, nil
}

// Used by the tests as well
func waitForAwsRedshiftSnapshotCopyGrantToBeDeleted(conn *redshift.Redshift, grantName string) error {
	var grant *redshift.SnapshotCopyGrant
	err := resource.Retry(3*time.Minute, func() *resource.RetryError {
		var err error
		grant, err = findAwsRedshiftSnapshotCopyGrant(conn, grantName, nil)
		if err != nil {
			if isAWSErr(err, redshift.ErrCodeSnapshotCopyGrantNotFoundFault, "") {
				return nil
			}
		}

		if grant != nil {
			// Force a retry if the grant still exists
			return resource.RetryableError(
				fmt.Errorf("[DEBUG] Grant still exists while expected to be deleted: %s", *grant.SnapshotCopyGrantName))
		}

		return resource.NonRetryableError(err)
	})
	if isResourceTimeoutError(err) {
		grant, err = findAwsRedshiftSnapshotCopyGrant(conn, grantName, nil)
		if isAWSErr(err, redshift.ErrCodeSnapshotCopyGrantNotFoundFault, "") {
			return nil
		}
	}
	if err != nil {
		return fmt.Errorf("Error waiting for snapshot copy grant to be deleted: %s", err)
	}
	return nil
}

// The DescribeSnapshotCopyGrants API defaults to listing only 100 grants
// Use a marker to iterate over all grants in "pages"
// NB: This function only retries on AWS Errors
func findAwsRedshiftSnapshotCopyGrant(conn *redshift.Redshift, grantName string, marker *string) (*redshift.SnapshotCopyGrant, error) {

	input := redshift.DescribeSnapshotCopyGrantsInput{
		MaxRecords: aws.Int64(int64(100)),
	}

	// marker and grant name are mutually exclusive
	if marker != nil {
		input.Marker = marker
	} else {
		input.SnapshotCopyGrantName = aws.String(grantName)
	}

	var out *redshift.DescribeSnapshotCopyGrantsOutput
	var err error
	var grant *redshift.SnapshotCopyGrant

	err = resource.Retry(3*time.Minute, func() *resource.RetryError {
		out, err = conn.DescribeSnapshotCopyGrants(&input)

		if err != nil {
			return resource.NonRetryableError(err)
		}

		return nil
	})
	if isResourceTimeoutError(err) {
		out, err = conn.DescribeSnapshotCopyGrants(&input)
	}
	if err != nil {
		return nil, err
	}

	grant = getAwsRedshiftSnapshotCopyGrant(out.SnapshotCopyGrants, grantName)
	if grant != nil {
		return grant, nil
	} else if out.Marker != nil {
		log.Printf("[DEBUG] Snapshot copy grant not found but marker returned, getting next page via marker: %s", aws.StringValue(out.Marker))
		return findAwsRedshiftSnapshotCopyGrant(conn, grantName, out.Marker)
	}

	return nil, &resource.NotFoundError{
		Message:     fmt.Sprintf("[DEBUG] Grant %s not found", grantName),
		LastRequest: input,
	}
}
<commit_msg>allow snapshot copy grants to be imported<commit_after>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/arn"
	"github.com/aws/aws-sdk-go/service/redshift"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsRedshiftSnapshotCopyGrant() *schema.Resource {
	return &schema.Resource{
		// There is no API for updating/modifying grants, hence no Update
		// Instead changes to most fields will force a new resource
		Create: resourceAwsRedshiftSnapshotCopyGrantCreate,
		Read:   resourceAwsRedshiftSnapshotCopyGrantRead,
		Update: resourceAwsRedshiftSnapshotCopyGrantUpdate,
		Delete: resourceAwsRedshiftSnapshotCopyGrantDelete,
		Exists: resourceAwsRedshiftSnapshotCopyGrantExists,

		Importer: &schema.ResourceImporter{
			State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
				d.Set("snapshot_copy_grant_name", d.Id())
				return []*schema.ResourceData{d}, nil
			},
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"snapshot_copy_grant_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"kms_key_id": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Computed: true,
			},
			"tags": tagsSchema(),
		},
	}
}

func resourceAwsRedshiftSnapshotCopyGrantCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	grantName := d.Get("snapshot_copy_grant_name").(string)

	input := redshift.CreateSnapshotCopyGrantInput{
		SnapshotCopyGrantName: aws.String(grantName),
	}

	if v, ok := d.GetOk("kms_key_id"); ok {
		input.KmsKeyId = aws.String(v.(string))
	}

	input.Tags = tagsFromMapRedshift(d.Get("tags").(map[string]interface{}))

	log.Printf("[DEBUG]: Adding new Redshift SnapshotCopyGrant: %s", input)

	var out *redshift.CreateSnapshotCopyGrantOutput
	var err error

	out, err = conn.CreateSnapshotCopyGrant(&input)

	if err != nil {
		return fmt.Errorf("error creating Redshift Snapshot Copy Grant (%s): %s", grantName, err)
	}

	log.Printf("[DEBUG] Created new Redshift SnapshotCopyGrant: %s", *out.SnapshotCopyGrant.SnapshotCopyGrantName)
	d.SetId(grantName)

	return resourceAwsRedshiftSnapshotCopyGrantRead(d, meta)
}

func resourceAwsRedshiftSnapshotCopyGrantRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	grantName := d.Id()
	log.Printf("[DEBUG] Looking for grant: %s", grantName)
	grant, err := findAwsRedshiftSnapshotCopyGrantWithRetry(conn, grantName)

	if err != nil {
		return err
	}

	if grant == nil {
		log.Printf("[WARN] %s Redshift snapshot copy grant not found, removing from state file", grantName)
		d.SetId("")
		return nil
	}

	arn := arn.ARN{
		Partition: meta.(*AWSClient).partition,
		Service:   "redshift",
		Region:    meta.(*AWSClient).region,
		AccountID: meta.(*AWSClient).accountid,
		Resource:  fmt.Sprintf("snapshotcopygrant:%s", grantName),
	}.String()

	d.Set("arn", arn)

	d.Set("kms_key_id", grant.KmsKeyId)
	d.Set("snapshot_copy_grant_name", grant.SnapshotCopyGrantName)
	if err := d.Set("tags", tagsToMapRedshift(grant.Tags)); err != nil {
		return fmt.Errorf("Error setting Redshift Snapshot Copy Grant Tags: %#v", err)
	}

	return nil
}

func resourceAwsRedshiftSnapshotCopyGrantUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	d.Partial(true)

	if tagErr := setTagsRedshift(conn, d); tagErr != nil {
		return tagErr
	} else {
		d.SetPartial("tags")
	}

	d.Partial(false)

	return resourceAwsRedshiftSnapshotCopyGrantRead(d, meta)
}

func resourceAwsRedshiftSnapshotCopyGrantDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	grantName := d.Id()

	deleteInput := redshift.DeleteSnapshotCopyGrantInput{
		SnapshotCopyGrantName: aws.String(grantName),
	}

	log.Printf("[DEBUG] Deleting snapshot copy grant: %s", grantName)
	_, err := conn.DeleteSnapshotCopyGrant(&deleteInput)

	if err != nil {
		if isAWSErr(err, redshift.ErrCodeSnapshotCopyGrantNotFoundFault, "") {
			return nil
		}
		return err
	}

	log.Printf("[DEBUG] Checking if grant is deleted: %s", grantName)
	err = waitForAwsRedshiftSnapshotCopyGrantToBeDeleted(conn, grantName)

	return err
}

func resourceAwsRedshiftSnapshotCopyGrantExists(d *schema.ResourceData, meta interface{}) (bool, error) {
	conn := meta.(*AWSClient).redshiftconn

	grantName := d.Id()

	log.Printf("[DEBUG] Looking for Grant: %s", grantName)
	grant, err := findAwsRedshiftSnapshotCopyGrantWithRetry(conn, grantName)

	if err != nil {
		return false, err
	}
	if grant != nil {
		return true, err
	}

	return false, nil
}

func getAwsRedshiftSnapshotCopyGrant(grants []*redshift.SnapshotCopyGrant, grantName string) *redshift.SnapshotCopyGrant {
	for _, grant := range grants {
		if *grant.SnapshotCopyGrantName == grantName {
			return grant
		}
	}

	return nil
}

/*
In the functions below it is not possible to use retryOnAwsCodes function, as there
is no get grant call, so an error has to be created if the grant is or isn't returned
by the describe grants call when expected.
*/

// NB: This function only retries the grant not being returned and some edge cases, while AWS Errors
// are handled by the findAwsRedshiftSnapshotCopyGrant function
func findAwsRedshiftSnapshotCopyGrantWithRetry(conn *redshift.Redshift, grantName string) (*redshift.SnapshotCopyGrant, error) {
	var grant *redshift.SnapshotCopyGrant
	err := resource.Retry(3*time.Minute, func() *resource.RetryError {
		var err error
		grant, err = findAwsRedshiftSnapshotCopyGrant(conn, grantName, nil)

		if err != nil {
			if serr, ok := err.(*resource.NotFoundError); ok {
				// Force a retry if the grant should exist
				return resource.RetryableError(serr)
			}

			return resource.NonRetryableError(err)
		}

		return nil
	})
	if isResourceTimeoutError(err) {
		grant, err = findAwsRedshiftSnapshotCopyGrant(conn, grantName, nil)
	}
	if err != nil {
		return nil, fmt.Errorf("Error finding snapshot copy grant: %s", err)
	}
	return grant, nil
}

// Used by the tests as well
func waitForAwsRedshiftSnapshotCopyGrantToBeDeleted(conn *redshift.Redshift, grantName string) error {
	var grant *redshift.SnapshotCopyGrant
	err := resource.Retry(3*time.Minute, func() *resource.RetryError {
		var err error
		grant, err = findAwsRedshiftSnapshotCopyGrant(conn, grantName, nil)
		if err != nil {
			if isAWSErr(err, redshift.ErrCodeSnapshotCopyGrantNotFoundFault, "") {
				return nil
			}
		}

		if grant != nil {
			// Force a retry if the grant still exists
			return resource.RetryableError(
				fmt.Errorf("[DEBUG] Grant still exists while expected to be deleted: %s", *grant.SnapshotCopyGrantName))
		}

		return resource.NonRetryableError(err)
	})
	if isResourceTimeoutError(err) {
		grant, err = findAwsRedshiftSnapshotCopyGrant(conn, grantName, nil)
		if isAWSErr(err, redshift.ErrCodeSnapshotCopyGrantNotFoundFault, "") {
			return nil
		}
	}
	if err != nil {
		return fmt.Errorf("Error waiting for snapshot copy grant to be deleted: %s", err)
	}
	return nil
}

// The DescribeSnapshotCopyGrants API defaults to listing only 100 grants
// Use a marker to iterate over all grants in "pages"
// NB: This function only retries on AWS Errors
func findAwsRedshiftSnapshotCopyGrant(conn *redshift.Redshift, grantName string, marker *string) (*redshift.SnapshotCopyGrant, error) {

	input := redshift.DescribeSnapshotCopyGrantsInput{
		MaxRecords: aws.Int64(int64(100)),
	}

	// marker and grant name are mutually exclusive
	if marker != nil {
		input.Marker = marker
	} else {
		input.SnapshotCopyGrantName = aws.String(grantName)
	}

	var out *redshift.DescribeSnapshotCopyGrantsOutput
	var err error
	var grant *redshift.SnapshotCopyGrant

	err = resource.Retry(3*time.Minute, func() *resource.RetryError {
		out, err = conn.DescribeSnapshotCopyGrants(&input)

		if err != nil {
			return resource.NonRetryableError(err)
		}

		return nil
	})
	if isResourceTimeoutError(err) {
		out, err = conn.DescribeSnapshotCopyGrants(&input)
	}
	if err != nil {
		return nil, err
	}

	grant = getAwsRedshiftSnapshotCopyGrant(out.SnapshotCopyGrants, grantName)
	if grant != nil {
		return grant, nil
	} else if out.Marker != nil {
		log.Printf("[DEBUG] Snapshot copy grant not found but marker returned, getting next page via marker: %s", aws.StringValue(out.Marker))
		return findAwsRedshiftSnapshotCopyGrant(conn, grantName, out.Marker)
	}

	return nil, &resource.NotFoundError{
		Message:     fmt.Sprintf("[DEBUG] Grant %s not found", grantName),
		LastRequest: input,
	}
}
<|endoftext|>


<commit_before>package h2quic

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"runtime"
	"sync"
	"sync/atomic"
	"time"

	"github.com/lucas-clemente/quic-go"
	"github.com/lucas-clemente/quic-go/protocol"
	"github.com/lucas-clemente/quic-go/qerr"
	"github.com/lucas-clemente/quic-go/utils"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/hpack"
)

type streamCreator interface {
	GetOrOpenStream(protocol.StreamID) (utils.Stream, error)
	Close(error) error
	RemoteAddr() *net.UDPAddr
}

// Server is a HTTP2 server listening for QUIC connections.
type Server struct {
	*http.Server

	// Private flag for demo, do not use
	CloseAfterFirstRequest bool

	port uint32 // used atomically

	server      *quic.Server
	serverMutex sync.Mutex
}

// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/2 requests on incoming connections.
func (s *Server) ListenAndServe() error {
	if s.Server == nil {
		return errors.New("use of h2quic.Server without http.Server")
	}
	return s.serveImpl(s.TLSConfig, nil)
}

// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/2 requests on incoming connections.
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
	var err error
	certs := make([]tls.Certificate, 1)
	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}
	// We currently only use the cert-related stuff from tls.Config,
	// so we don't need to make a full copy.
	config := &tls.Config{
		Certificates: certs,
	}
	return s.serveImpl(config, nil)
}

// Serve an existing UDP connection.
func (s *Server) Serve(conn *net.UDPConn) error {
	return s.serveImpl(s.TLSConfig, conn)
}

func (s *Server) serveImpl(tlsConfig *tls.Config, conn *net.UDPConn) error {
	if s.Server == nil {
		return errors.New("use of h2quic.Server without http.Server")
	}
	s.serverMutex.Lock()
	if s.server != nil {
		s.serverMutex.Unlock()
		return errors.New("ListenAndServe may only be called once")
	}
	var err error
	server, err := quic.NewServer(s.Addr, tlsConfig, s.handleStreamCb)
	if err != nil {
		s.serverMutex.Unlock()
		return err
	}
	s.server = server
	s.serverMutex.Unlock()
	if conn == nil {
		return server.ListenAndServe()
	}
	return server.Serve(conn)
}

func (s *Server) handleStreamCb(session *quic.Session, stream utils.Stream) {
	s.handleStream(session, stream)
}

func (s *Server) handleStream(session streamCreator, stream utils.Stream) {
	if stream.StreamID() != 3 {
		return
	}

	hpackDecoder := hpack.NewDecoder(4096, nil)
	h2framer := http2.NewFramer(nil, stream)

	go func() {
		var headerStreamMutex sync.Mutex // Protects concurrent calls to Write()
		for {
			if err := s.handleRequest(session, stream, &headerStreamMutex, hpackDecoder, h2framer); err != nil {
				// QuicErrors must originate from stream.Read() returning an error.
				// In this case, the session has already logged the error, so we don't
				// need to log it again.
				if _, ok := err.(*qerr.QuicError); !ok {
					utils.Errorf("error handling h2 request: %s", err.Error())
				}
				return
			}
		}
	}()
}

func (s *Server) handleRequest(session streamCreator, headerStream utils.Stream, headerStreamMutex *sync.Mutex, hpackDecoder *hpack.Decoder, h2framer *http2.Framer) error {
	h2frame, err := h2framer.ReadFrame()
	if err != nil {
		return err
	}
	h2headersFrame := h2frame.(*http2.HeadersFrame)
	if !h2headersFrame.HeadersEnded() {
		return errors.New("http2 header continuation not implemented")
	}
	headers, err := hpackDecoder.DecodeFull(h2headersFrame.HeaderBlockFragment())
	if err != nil {
		utils.Errorf("invalid http2 headers encoding: %s", err.Error())
		return err
	}

	req, err := requestFromHeaders(headers)
	if err != nil {
		return err
	}

	req.RemoteAddr = session.RemoteAddr().String()

	if utils.Debug() {
		utils.Infof("%s %s%s, on data stream %d", req.Method, req.Host, req.RequestURI, h2headersFrame.StreamID)
	} else {
		utils.Infof("%s %s%s", req.Method, req.Host, req.RequestURI)
	}

	dataStream, err := session.GetOrOpenStream(protocol.StreamID(h2headersFrame.StreamID))
	if err != nil {
		return err
	}

	if h2headersFrame.StreamEnded() {
		dataStream.CloseRemote(0)
		_, _ = dataStream.Read([]byte{0}) // read the eof
	}

	// stream's Close() closes the write side, not the read side
	req.Body = ioutil.NopCloser(dataStream)

	responseWriter := newResponseWriter(headerStream, headerStreamMutex, dataStream, protocol.StreamID(h2headersFrame.StreamID))

	go func() {
		handler := s.Handler
		if handler == nil {
			handler = http.DefaultServeMux
		}
		panicked := false
		func() {
			defer func() {
				if p := recover(); p != nil {
					// Copied from net/http/server.go
					const size = 64 << 10
					buf := make([]byte, size)
					buf = buf[:runtime.Stack(buf, false)]
					utils.Errorf("http: panic serving: %v\n%s", p, buf)
					panicked = true
				}
			}()
			handler.ServeHTTP(responseWriter, req)
		}()
		if panicked {
			responseWriter.WriteHeader(500)
		} else {
			responseWriter.WriteHeader(200)
		}
		if responseWriter.dataStream != nil {
			responseWriter.dataStream.Close()
		}
		if s.CloseAfterFirstRequest {
			time.Sleep(100 * time.Millisecond)
			session.Close(nil)
		}
	}()

	return nil
}

// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients.
// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
func (s *Server) Close() error {
	s.serverMutex.Lock()
	defer s.serverMutex.Unlock()
	if s.server != nil {
		err := s.server.Close()
		s.server = nil
		return err
	}
	return nil
}

// CloseGracefully shuts down the server gracefully. The server sends a GOAWAY frame first, then waits for either timeout to trigger, or for all running requests to complete.
// CloseGracefully in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
func (s *Server) CloseGracefully(timeout time.Duration) error {
	// TODO: implement
	return nil
}

// SetQuicHeaders can be used to set the proper headers that announce that this server supports QUIC.
// The values that are set depend on the port information from s.Server.Addr, and currently look like this (if Addr has port 443):
//  Alternate-Protocol: 443:quic
//  Alt-Svc: quic=":443"; ma=2592000; v="33,32,31,30"
func (s *Server) SetQuicHeaders(hdr http.Header) error {
	port := atomic.LoadUint32(&s.port)

	if port == 0 {
		// Extract port from s.Server.Addr
		_, portStr, err := net.SplitHostPort(s.Server.Addr)
		if err != nil {
			return err
		}
		portInt, err := net.LookupPort("tcp", portStr)
		if err != nil {
			return err
		}
		port = uint32(portInt)
		atomic.StoreUint32(&s.port, port)
	}

	hdr.Add("Alternate-Protocol", fmt.Sprintf("%d:quic", port))
	hdr.Add("Alt-Svc", fmt.Sprintf(`quic=":%d"; ma=2592000; v="%s"`, port, protocol.SupportedVersionsAsString))

	return nil
}

// ListenAndServeQUIC listens on the UDP network address addr and calls the
// handler for HTTP/2 requests on incoming connections. http.DefaultServeMux is
// used when handler is nil.
func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error {
	server := &Server{
		Server: &http.Server{
			Addr:    addr,
			Handler: handler,
		},
	}
	return server.ListenAndServeTLS(certFile, keyFile)
}
<commit_msg>add a h2quic.ListenAndServe method that listens on TCP in parallel<commit_after>package h2quic

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"runtime"
	"sync"
	"sync/atomic"
	"time"

	"github.com/lucas-clemente/quic-go"
	"github.com/lucas-clemente/quic-go/protocol"
	"github.com/lucas-clemente/quic-go/qerr"
	"github.com/lucas-clemente/quic-go/utils"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/hpack"
)

type streamCreator interface {
	GetOrOpenStream(protocol.StreamID) (utils.Stream, error)
	Close(error) error
	RemoteAddr() *net.UDPAddr
}

// Server is a HTTP2 server listening for QUIC connections.
type Server struct {
	*http.Server

	// Private flag for demo, do not use
	CloseAfterFirstRequest bool

	port uint32 // used atomically

	server      *quic.Server
	serverMutex sync.Mutex
}

// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/2 requests on incoming connections.
func (s *Server) ListenAndServe() error {
	if s.Server == nil {
		return errors.New("use of h2quic.Server without http.Server")
	}
	return s.serveImpl(s.TLSConfig, nil)
}

// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/2 requests on incoming connections.
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
	var err error
	certs := make([]tls.Certificate, 1)
	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}
	// We currently only use the cert-related stuff from tls.Config,
	// so we don't need to make a full copy.
	config := &tls.Config{
		Certificates: certs,
	}
	return s.serveImpl(config, nil)
}

// Serve an existing UDP connection.
func (s *Server) Serve(conn *net.UDPConn) error {
	return s.serveImpl(s.TLSConfig, conn)
}

func (s *Server) serveImpl(tlsConfig *tls.Config, conn *net.UDPConn) error {
	if s.Server == nil {
		return errors.New("use of h2quic.Server without http.Server")
	}
	s.serverMutex.Lock()
	if s.server != nil {
		s.serverMutex.Unlock()
		return errors.New("ListenAndServe may only be called once")
	}
	var err error
	server, err := quic.NewServer(s.Addr, tlsConfig, s.handleStreamCb)
	if err != nil {
		s.serverMutex.Unlock()
		return err
	}
	s.server = server
	s.serverMutex.Unlock()
	if conn == nil {
		return server.ListenAndServe()
	}
	return server.Serve(conn)
}

func (s *Server) handleStreamCb(session *quic.Session, stream utils.Stream) {
	s.handleStream(session, stream)
}

func (s *Server) handleStream(session streamCreator, stream utils.Stream) {
	if stream.StreamID() != 3 {
		return
	}

	hpackDecoder := hpack.NewDecoder(4096, nil)
	h2framer := http2.NewFramer(nil, stream)

	go func() {
		var headerStreamMutex sync.Mutex // Protects concurrent calls to Write()
		for {
			if err := s.handleRequest(session, stream, &headerStreamMutex, hpackDecoder, h2framer); err != nil {
				// QuicErrors must originate from stream.Read() returning an error.
				// In this case, the session has already logged the error, so we don't
				// need to log it again.
				if _, ok := err.(*qerr.QuicError); !ok {
					utils.Errorf("error handling h2 request: %s", err.Error())
				}
				return
			}
		}
	}()
}

func (s *Server) handleRequest(session streamCreator, headerStream utils.Stream, headerStreamMutex *sync.Mutex, hpackDecoder *hpack.Decoder, h2framer *http2.Framer) error {
	h2frame, err := h2framer.ReadFrame()
	if err != nil {
		return err
	}
	h2headersFrame := h2frame.(*http2.HeadersFrame)
	if !h2headersFrame.HeadersEnded() {
		return errors.New("http2 header continuation not implemented")
	}
	headers, err := hpackDecoder.DecodeFull(h2headersFrame.HeaderBlockFragment())
	if err != nil {
		utils.Errorf("invalid http2 headers encoding: %s", err.Error())
		return err
	}

	req, err := requestFromHeaders(headers)
	if err != nil {
		return err
	}

	req.RemoteAddr = session.RemoteAddr().String()

	if utils.Debug() {
		utils.Infof("%s %s%s, on data stream %d", req.Method, req.Host, req.RequestURI, h2headersFrame.StreamID)
	} else {
		utils.Infof("%s %s%s", req.Method, req.Host, req.RequestURI)
	}

	dataStream, err := session.GetOrOpenStream(protocol.StreamID(h2headersFrame.StreamID))
	if err != nil {
		return err
	}

	if h2headersFrame.StreamEnded() {
		dataStream.CloseRemote(0)
		_, _ = dataStream.Read([]byte{0}) // read the eof
	}

	// stream's Close() closes the write side, not the read side
	req.Body = ioutil.NopCloser(dataStream)

	responseWriter := newResponseWriter(headerStream, headerStreamMutex, dataStream, protocol.StreamID(h2headersFrame.StreamID))

	go func() {
		handler := s.Handler
		if handler == nil {
			handler = http.DefaultServeMux
		}
		panicked := false
		func() {
			defer func() {
				if p := recover(); p != nil {
					// Copied from net/http/server.go
					const size = 64 << 10
					buf := make([]byte, size)
					buf = buf[:runtime.Stack(buf, false)]
					utils.Errorf("http: panic serving: %v\n%s", p, buf)
					panicked = true
				}
			}()
			handler.ServeHTTP(responseWriter, req)
		}()
		if panicked {
			responseWriter.WriteHeader(500)
		} else {
			responseWriter.WriteHeader(200)
		}
		if responseWriter.dataStream != nil {
			responseWriter.dataStream.Close()
		}
		if s.CloseAfterFirstRequest {
			time.Sleep(100 * time.Millisecond)
			session.Close(nil)
		}
	}()

	return nil
}

// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients.
// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
func (s *Server) Close() error {
	s.serverMutex.Lock()
	defer s.serverMutex.Unlock()
	if s.server != nil {
		err := s.server.Close()
		s.server = nil
		return err
	}
	return nil
}

// CloseGracefully shuts down the server gracefully. The server sends a GOAWAY frame first, then waits for either timeout to trigger, or for all running requests to complete.
// CloseGracefully in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
func (s *Server) CloseGracefully(timeout time.Duration) error {
	// TODO: implement
	return nil
}

// SetQuicHeaders can be used to set the proper headers that announce that this server supports QUIC.
// The values that are set depend on the port information from s.Server.Addr, and currently look like this (if Addr has port 443):
//  Alternate-Protocol: 443:quic
//  Alt-Svc: quic=":443"; ma=2592000; v="33,32,31,30"
func (s *Server) SetQuicHeaders(hdr http.Header) error {
	port := atomic.LoadUint32(&s.port)

	if port == 0 {
		// Extract port from s.Server.Addr
		_, portStr, err := net.SplitHostPort(s.Server.Addr)
		if err != nil {
			return err
		}
		portInt, err := net.LookupPort("tcp", portStr)
		if err != nil {
			return err
		}
		port = uint32(portInt)
		atomic.StoreUint32(&s.port, port)
	}

	hdr.Add("Alternate-Protocol", fmt.Sprintf("%d:quic", port))
	hdr.Add("Alt-Svc", fmt.Sprintf(`quic=":%d"; ma=2592000; v="%s"`, port, protocol.SupportedVersionsAsString))

	return nil
}

// ListenAndServeQUIC listens on the UDP network address addr and calls the
// handler for HTTP/2 requests on incoming connections. http.DefaultServeMux is
// used when handler is nil.
func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error {
	server := &Server{
		Server: &http.Server{
			Addr:    addr,
			Handler: handler,
		},
	}
	return server.ListenAndServeTLS(certFile, keyFile)
}

// ListenAndServe listens on the given network address for both, TLS and QUIC
// connetions in parallel. It returns if one of the two returns an error.
// http.DefaultServeMux is used when handler is nil.
// The correct Alt-Svc headers for QUIC are set.
func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error {
	// Load certs
	var err error
	certs := make([]tls.Certificate, 1)
	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}
	// We currently only use the cert-related stuff from tls.Config,
	// so we don't need to make a full copy.
	config := &tls.Config{
		Certificates: certs,
	}

	// Open the listeners
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return err
	}
	udpConn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		return err
	}
	defer udpConn.Close()

	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		return err
	}
	tcpConn, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		return err
	}
	defer tcpConn.Close()

	// Start the servers
	httpServer := &http.Server{
		Addr:      addr,
		TLSConfig: config,
	}

	quicServer := &Server{
		Server: httpServer,
	}

	if handler == nil {
		handler = http.DefaultServeMux
	}
	httpServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		quicServer.SetQuicHeaders(w.Header())
		handler.ServeHTTP(w, r)
	})

	hErr := make(chan error)
	qErr := make(chan error)
	go func() {
		hErr <- httpServer.Serve(tcpConn)
	}()
	go func() {
		qErr <- quicServer.Serve(udpConn)
	}()

	select {
	case err := <-hErr:
		quicServer.Close()
		return err
	case err := <-qErr:
		// Cannot close the HTTP server or wait for requests to complete properly :/
		return err
	}
}
<|endoftext|>


<commit_before>package image

import (
	"encoding/json"
	"fmt"
	img "image"
	"image/draw"
	"io"
	"log"
	"math"
	"os"
	"os/exec"
	"time"

	"pixur.org/pixur/schema"

	"github.com/nfnt/resize"

	"image/gif"
	"image/jpeg"
	"image/png"
)

// TODO: maybe make this into it's own package
const (
	DefaultThumbnailWidth  = 160
	DefaultThumbnailHeight = 160

	maxWebmDuration = 60*2 + 1 // Two minutes, with 1 second of leeway
)

type BadWebmFormatErr struct {
	error
}

func FillImageConfig(f *os.File, p *schema.Pic) (img.Image, error) {
	if _, err := f.Seek(0, os.SEEK_SET); err != nil {
		return nil, err
	}
	defer f.Seek(0, os.SEEK_SET)

	im, imgType, err := img.Decode(f)
	if err == img.ErrFormat {
		// Try Webm
		im, err = fillImageConfigFromWebm(f, p)
		if err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, err
	} else {
		// TODO: handle this error
		p.Mime, _ = schema.FromImageFormat(imgType)
		p.Width = int64(im.Bounds().Dx())
		p.Height = int64(im.Bounds().Dy())
	}

	if p.Mime == schema.Pic_GIF {
		if _, err := f.Seek(0, os.SEEK_SET); err != nil {
			return nil, err
		}

		GIF, err := gif.DecodeAll(f)
		if err != nil {
			return nil, err
		}
		// Ignore gifs that have only one frame
		if len(GIF.Delay) > 1 {
			p.AnimationInfo = &schema.AnimationInfo{
				Duration: GetGifDuration(GIF),
			}
			// TODO: maybe skip the first second of frames like webm
		}
	}

	return im, nil
}

// TODO: add tests for this
func GetGifDuration(g *gif.GIF) *schema.Duration {
	var duration time.Duration
	// TODO: check for overflow
	// each delay unit is 1/100 of a second
	for _, frameHundredths := range g.Delay {
		duration += time.Millisecond * time.Duration(10*frameHundredths)
	}

	return schema.FromDuration(duration)
}

// TODO: interpret image rotation metadata
func MakeThumbnail(im img.Image) img.Image {
	bounds := findMaxSquare(im.Bounds())
	largeSquareImage := img.NewNRGBA(bounds)
	draw.Draw(largeSquareImage, bounds, im, bounds.Min, draw.Src)
	return resize.Resize(DefaultThumbnailWidth, DefaultThumbnailHeight, largeSquareImage,
		resize.NearestNeighbor)
}

func SaveThumbnail(im img.Image, p *schema.Pic, pixPath string) error {
	f, err := os.Create(p.ThumbnailPath(pixPath))
	if err != nil {
		return err
	}
	defer f.Close()
	return jpeg.Encode(f, im, nil)
}

func findMaxSquare(bounds img.Rectangle) img.Rectangle {
	width := bounds.Dx()
	height := bounds.Dy()
	if height < width {
		missingSpace := width - height
		return img.Rectangle{
			Min: img.Point{
				X: bounds.Min.X + missingSpace/2,
				Y: bounds.Min.Y,
			},
			Max: img.Point{
				X: bounds.Min.X + missingSpace/2 + height,
				Y: bounds.Max.Y,
			},
		}
	} else {
		missingSpace := height - width
		return img.Rectangle{
			Min: img.Point{
				X: bounds.Min.X,
				Y: bounds.Min.Y + missingSpace/2,
			},
			Max: img.Point{
				X: bounds.Max.X,
				Y: bounds.Min.Y + missingSpace/2 + width,
			},
		}
	}
}

type FFprobeConfig struct {
	Streams []FFprobeStream `json:"streams"`
	Format  FFprobeFormat   `json:"format"`
}

type FFprobeFormat struct {
	StreamCount int     `json:"nb_streams"`
	FormatName  string  `json:"format_name"`
	Duration    float64 `json:"duration,string"`
}

type FFprobeStream struct {
	CodecName string `json:"codec_name"`
	CodecType string `json:"codec_type"`
	Width     int64  `json:"width"`
	Height    int64  `json:"height"`
}

func fillImageConfigFromWebm(tempFile *os.File, p *schema.Pic) (img.Image, error) {
	config, err := GetWebmConfig(tempFile.Name())
	if err != nil {
		return nil, err
	}
	p.Mime = schema.Pic_WEBM
	// Handle the 0 and 1 case
	for _, stream := range config.Streams {
		p.Width = stream.Width
		p.Height = stream.Height
		break
	}

	if dur, success := ConvertFloatDuration(config.Format.Duration); success {
		if dur >= time.Nanosecond {
			p.AnimationInfo = &schema.AnimationInfo{
				Duration: schema.FromDuration(dur),
			}
		}
	} else {
		log.Println("Invalid duration from ffmpeg: ", config.Format.Duration)
	}

	return getFirstWebmFrame(tempFile.Name())
}

func ConvertFloatDuration(seconds float64) (time.Duration, bool) {
	if math.IsNaN(seconds) || math.IsInf(seconds, 0) || seconds > math.MaxInt64 {
		return time.Duration(0), false
	}
	if seconds < 0 {
		return time.Duration(0), false
	}
	return time.Duration(seconds * 1e9), true
}

func GetWebmConfig(filepath string) (*FFprobeConfig, error) {
	cmd := exec.Command("ffprobe",
		"-print_format", "json",
		"-v", "quiet", // disable version info
		"-show_format",
		"-show_streams",
		filepath)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	config := new(FFprobeConfig)
	if err := json.NewDecoder(stdout).Decode(config); err != nil {
		return nil, err
	}
	if err := cmd.Wait(); err != nil {
		return nil, err
	}

	if config.Format.FormatName != "matroska,webm" {
		return nil, fmt.Errorf("Only webm supported: %v", config)
	}
	if config.Format.StreamCount == 0 {
		return nil, fmt.Errorf("No Streams found: %v", config)
	}
	if config.Format.Duration < 0 || config.Format.Duration > maxWebmDuration {
		return nil, fmt.Errorf("Invalid Duration %v", config)
	}

	var videoFound bool
	// Only check for a video stream, since we will just mute it on output.
	for _, stream := range config.Streams {
		if stream.CodecType == "video" {
			if stream.CodecName == "vp8" || stream.CodecName == "vp9" {
				videoFound = true
				break
			}
		}
	}

	if !videoFound {
		return nil, &BadWebmFormatErr{fmt.Errorf("Bad Video %v", config)}
	}

	return config, nil
}

// The Idea here is to read the first 1 second of video data, and stop after
// that.  The last frame received will be the thumbnail.  Normally the first
// frame of video will be an intro or darker frame.  This is undesirable, so
// we give the video 1 second of warm up time.
// This doesn't use the -ss argument, because if the video is less than one
// second, no output will be produced.  In general, if there is not a frame
// after the first duration, no output will be produced.  If there was a video
// with a single frame that had a duration of 2 seconds, no output would be
// produced.  This also means that we can't just seek to some percentage of
// the way through the video and get a frame.
// This takes a different approach: output every frame, stopping after the
// first second of video time.  Then, keep the last frame produced.  This
// takes advantage of PNGs being self contained, allowing this to read the
// concatenated image output produced by ffmpeg.  Thus, videos shorter than
// 1 second will produce *something*, and videos longer than 1 don't need to
// be completely parsed.
// TODO: This is pretty slow, find a faster way.
func getFirstWebmFrame(filepath string) (img.Image, error) {
	cmd := exec.Command("ffmpeg",
		"-i", filepath,
		"-v", "quiet", // disable version info
		"-t", "1.0", // Grab the last frame before the first second
		"-frames:v", "120", // Handle up to 120fps video, then give up.
		"-codec:v", "png",
		"-compression_level", "0", // Don't bother compressing
		"-f", "image2pipe",
		"-")
	// PNG data comes across stdout
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer stdout.Close()
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	defer cmd.Process.Kill()
	// Avoid checking more than 120 frames
	maxFrames := 120
	var im img.Image

	for i := 0; i < maxFrames; i++ {
		// Can't use image.Decode because it reads too far ahead.
		lastIm, err := png.Decode(stdout)
		if err == io.ErrUnexpectedEOF {
			break
		} else if err != nil {
			return nil, err
		}
		im = lastIm
	}
	if err := cmd.Wait(); err != nil {
		return nil, err
	}

	if im == nil {
		return nil, &BadWebmFormatErr{fmt.Errorf("No frames in webm")}
	}

	return im, nil
}
<commit_msg>Add comments to gif duration<commit_after>package image

import (
	"encoding/json"
	"fmt"
	img "image"
	"image/draw"
	"io"
	"log"
	"math"
	"os"
	"os/exec"
	"time"

	"pixur.org/pixur/schema"

	"github.com/nfnt/resize"

	"image/gif"
	"image/jpeg"
	"image/png"
)

// TODO: maybe make this into it's own package
const (
	DefaultThumbnailWidth  = 160
	DefaultThumbnailHeight = 160

	maxWebmDuration = 60*2 + 1 // Two minutes, with 1 second of leeway
)

type BadWebmFormatErr struct {
	error
}

func FillImageConfig(f *os.File, p *schema.Pic) (img.Image, error) {
	if _, err := f.Seek(0, os.SEEK_SET); err != nil {
		return nil, err
	}
	defer f.Seek(0, os.SEEK_SET)

	im, imgType, err := img.Decode(f)
	if err == img.ErrFormat {
		// Try Webm
		im, err = fillImageConfigFromWebm(f, p)
		if err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, err
	} else {
		// TODO: handle this error
		p.Mime, _ = schema.FromImageFormat(imgType)
		p.Width = int64(im.Bounds().Dx())
		p.Height = int64(im.Bounds().Dy())
	}

	if p.Mime == schema.Pic_GIF {
		if _, err := f.Seek(0, os.SEEK_SET); err != nil {
			return nil, err
		}

		GIF, err := gif.DecodeAll(f)
		if err != nil {
			return nil, err
		}
		// Ignore gifs that have only one frame
		if len(GIF.Delay) > 1 {
			p.AnimationInfo = &schema.AnimationInfo{
				Duration: GetGifDuration(GIF),
			}
			// TODO: maybe skip the first second of frames like webm
		}
	}

	return im, nil
}

// Returns the duration of this gif.  It will be different per browser,
// with inaccuracies usually in the 0/100 and 1/100 delays (which are
// rounded up to 10/100).  http://nullsleep.tumblr.com/post/16524517190/
// animated-gif-minimum-frame-delay-browser describes the delays for
// common browsers.  An idea to solve this is to keep a histogram of the
// short delay frames (from 0-5 hundredths) and allow the browser js to
// reinterpret the duration.
// TODO: add tests for this
func GetGifDuration(g *gif.GIF) *schema.Duration {
	var duration time.Duration
	// TODO: check for overflow
	// each delay unit is 1/100 of a second
	for _, frameHundredths := range g.Delay {
		duration += time.Millisecond * time.Duration(10*frameHundredths)
	}

	return schema.FromDuration(duration)
}

// TODO: interpret image rotation metadata
func MakeThumbnail(im img.Image) img.Image {
	bounds := findMaxSquare(im.Bounds())
	largeSquareImage := img.NewNRGBA(bounds)
	draw.Draw(largeSquareImage, bounds, im, bounds.Min, draw.Src)
	return resize.Resize(DefaultThumbnailWidth, DefaultThumbnailHeight, largeSquareImage,
		resize.NearestNeighbor)
}

func SaveThumbnail(im img.Image, p *schema.Pic, pixPath string) error {
	f, err := os.Create(p.ThumbnailPath(pixPath))
	if err != nil {
		return err
	}
	defer f.Close()
	return jpeg.Encode(f, im, nil)
}

func findMaxSquare(bounds img.Rectangle) img.Rectangle {
	width := bounds.Dx()
	height := bounds.Dy()
	if height < width {
		missingSpace := width - height
		return img.Rectangle{
			Min: img.Point{
				X: bounds.Min.X + missingSpace/2,
				Y: bounds.Min.Y,
			},
			Max: img.Point{
				X: bounds.Min.X + missingSpace/2 + height,
				Y: bounds.Max.Y,
			},
		}
	} else {
		missingSpace := height - width
		return img.Rectangle{
			Min: img.Point{
				X: bounds.Min.X,
				Y: bounds.Min.Y + missingSpace/2,
			},
			Max: img.Point{
				X: bounds.Max.X,
				Y: bounds.Min.Y + missingSpace/2 + width,
			},
		}
	}
}

type FFprobeConfig struct {
	Streams []FFprobeStream `json:"streams"`
	Format  FFprobeFormat   `json:"format"`
}

type FFprobeFormat struct {
	StreamCount int     `json:"nb_streams"`
	FormatName  string  `json:"format_name"`
	Duration    float64 `json:"duration,string"`
}

type FFprobeStream struct {
	CodecName string `json:"codec_name"`
	CodecType string `json:"codec_type"`
	Width     int64  `json:"width"`
	Height    int64  `json:"height"`
}

func fillImageConfigFromWebm(tempFile *os.File, p *schema.Pic) (img.Image, error) {
	config, err := GetWebmConfig(tempFile.Name())
	if err != nil {
		return nil, err
	}
	p.Mime = schema.Pic_WEBM
	// Handle the 0 and 1 case
	for _, stream := range config.Streams {
		p.Width = stream.Width
		p.Height = stream.Height
		break
	}

	if dur, success := ConvertFloatDuration(config.Format.Duration); success {
		if dur >= time.Nanosecond {
			p.AnimationInfo = &schema.AnimationInfo{
				Duration: schema.FromDuration(dur),
			}
		}
	} else {
		log.Println("Invalid duration from ffmpeg: ", config.Format.Duration)
	}

	return getFirstWebmFrame(tempFile.Name())
}

func ConvertFloatDuration(seconds float64) (time.Duration, bool) {
	if math.IsNaN(seconds) || math.IsInf(seconds, 0) || seconds > math.MaxInt64 {
		return time.Duration(0), false
	}
	if seconds < 0 {
		return time.Duration(0), false
	}
	return time.Duration(seconds * 1e9), true
}

func GetWebmConfig(filepath string) (*FFprobeConfig, error) {
	cmd := exec.Command("ffprobe",
		"-print_format", "json",
		"-v", "quiet", // disable version info
		"-show_format",
		"-show_streams",
		filepath)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	config := new(FFprobeConfig)
	if err := json.NewDecoder(stdout).Decode(config); err != nil {
		return nil, err
	}
	if err := cmd.Wait(); err != nil {
		return nil, err
	}

	if config.Format.FormatName != "matroska,webm" {
		return nil, fmt.Errorf("Only webm supported: %v", config)
	}
	if config.Format.StreamCount == 0 {
		return nil, fmt.Errorf("No Streams found: %v", config)
	}
	if config.Format.Duration < 0 || config.Format.Duration > maxWebmDuration {
		return nil, fmt.Errorf("Invalid Duration %v", config)
	}

	var videoFound bool
	// Only check for a video stream, since we will just mute it on output.
	for _, stream := range config.Streams {
		if stream.CodecType == "video" {
			if stream.CodecName == "vp8" || stream.CodecName == "vp9" {
				videoFound = true
				break
			}
		}
	}

	if !videoFound {
		return nil, &BadWebmFormatErr{fmt.Errorf("Bad Video %v", config)}
	}

	return config, nil
}

// The Idea here is to read the first 1 second of video data, and stop after
// that.  The last frame received will be the thumbnail.  Normally the first
// frame of video will be an intro or darker frame.  This is undesirable, so
// we give the video 1 second of warm up time.
// This doesn't use the -ss argument, because if the video is less than one
// second, no output will be produced.  In general, if there is not a frame
// after the first duration, no output will be produced.  If there was a video
// with a single frame that had a duration of 2 seconds, no output would be
// produced.  This also means that we can't just seek to some percentage of
// the way through the video and get a frame.
// This takes a different approach: output every frame, stopping after the
// first second of video time.  Then, keep the last frame produced.  This
// takes advantage of PNGs being self contained, allowing this to read the
// concatenated image output produced by ffmpeg.  Thus, videos shorter than
// 1 second will produce *something*, and videos longer than 1 don't need to
// be completely parsed.
// TODO: This is pretty slow, find a faster way.
func getFirstWebmFrame(filepath string) (img.Image, error) {
	cmd := exec.Command("ffmpeg",
		"-i", filepath,
		"-v", "quiet", // disable version info
		"-t", "1.0", // Grab the last frame before the first second
		"-frames:v", "120", // Handle up to 120fps video, then give up.
		"-codec:v", "png",
		"-compression_level", "0", // Don't bother compressing
		"-f", "image2pipe",
		"-")
	// PNG data comes across stdout
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer stdout.Close()
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	defer cmd.Process.Kill()
	// Avoid checking more than 120 frames
	maxFrames := 120
	var im img.Image

	for i := 0; i < maxFrames; i++ {
		// Can't use image.Decode because it reads too far ahead.
		lastIm, err := png.Decode(stdout)
		if err == io.ErrUnexpectedEOF {
			break
		} else if err != nil {
			return nil, err
		}
		im = lastIm
	}
	if err := cmd.Wait(); err != nil {
		return nil, err
	}

	if im == nil {
		return nil, &BadWebmFormatErr{fmt.Errorf("No frames in webm")}
	}

	return im, nil
}
<|endoftext|>


<commit_before>package testdata

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"testing"

	"v.io/core/veyron/lib/modules"
	"v.io/core/veyron/lib/testutil/v23tests"
	_ "v.io/core/veyron/profiles"
)

func TestHelperProcess(t *testing.T) {
	modules.DispatchInTest()
}

// redirect redirects the stdout of the given invocation to the file at the
// given path.
func redirect(t *testing.T, inv v23tests.Invocation, path string) {
	if err := ioutil.WriteFile(path, []byte(inv.Output()), 0600); err != nil {
		t.Fatalf("WriteFile(%q) failed: %v\n", path, err)
	}
}

func TestBlessSelf(t *testing.T) {
	env := v23tests.New(t)
	defer env.Cleanup()

	var (
		outputDir         = env.TempDir()
		aliceDir          = filepath.Join(outputDir, "alice")
		aliceBlessingFile = filepath.Join(outputDir, "aliceself")
	)

	bin := env.BuildGoPkg("v.io/core/veyron/tools/principal")
	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)

	bin = bin.WithEnv("VEYRON_CREDENTIALS=" + aliceDir)
	redirect(t, bin.Start("blessself", "alicereborn"), aliceBlessingFile)
	got := bin.Start("dumpblessings", aliceBlessingFile).Output()
	want := `Blessings          : alicereborn
PublicKey          : ([0-9a-f]{2}:){15}[0-9a-f]{2}
Certificate chains : 1
Chain #0 \(1 certificates\). Root certificate public key: ([0-9a-f]{2}:){15}[0-9a-f]{2}
  Certificate #0: alicereborn with 0 caveats`
	if regexp.MustCompile(want).FindString(got) == "" {
		t.Fatalf("wanted something matching \n%s, got \n%s\n", want, got)
	}
}
<commit_msg>veyron/tools/principal/testdata: port principal test to Go<commit_after>package testdata

import (
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
	"testing"
	"time"

	"v.io/core/veyron/lib/expect"
	"v.io/core/veyron/lib/modules"
	"v.io/core/veyron/lib/testutil/v23tests"
	_ "v.io/core/veyron/profiles"
)

func TestHelperProcess(t *testing.T) {
	modules.DispatchInTest()
}

// redirect redirects the stdout of the given invocation to the file at the
// given path.
func redirect(t *testing.T, inv v23tests.Invocation, path string) {
	if err := ioutil.WriteFile(path, []byte(inv.Output()), 0600); err != nil {
		t.Fatalf("WriteFile(%q) failed: %v\n", path, err)
	}
}

// removePublicKeys replaces public keys (16 hex bytes, :-separated) with
// XX:....  This substitution enables comparison with golden output even when
// keys are freshly minted by the "principal create" command.
func removePublicKeys(input string) string {
	return regexp.MustCompile("([0-9a-f]{2}:){15}[0-9a-f]{2}").ReplaceAllString(input, "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX")
}

func removeCaveats(input string) string {
	return regexp.MustCompile(`0x54a676398137187ecdb26d2d69ba0004\(int64=.*\)`).ReplaceAllString(input, "ExpiryCaveat")
}

func TestBlessSelf(t *testing.T) {
	env := v23tests.New(t)
	defer env.Cleanup()

	var (
		outputDir         = env.TempDir()
		aliceDir          = filepath.Join(outputDir, "alice")
		aliceBlessingFile = filepath.Join(outputDir, "aliceself")
	)

	bin := env.BuildGoPkg("v.io/core/veyron/tools/principal")
	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)

	bin = bin.WithEnv("VEYRON_CREDENTIALS=" + aliceDir)
	redirect(t, bin.Start("blessself", "alicereborn"), aliceBlessingFile)
	got := removePublicKeys(bin.Start("dumpblessings", aliceBlessingFile).Output())
	want := `Blessings          : alicereborn
PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
Certificate chains : 1
Chain #0 (1 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
  Certificate #0: alicereborn with 0 caveats
`
	if want != got {
		t.Fatalf("unexpected output, wanted \n%s, got\n%s", want, got)
	}
}

func TestStore(t *testing.T) {
	env := v23tests.New(t)
	defer env.Cleanup()

	var (
		outputDir   = env.TempDir()
		bin         = env.BuildGoPkg("v.io/core/veyron/tools/principal")
		aliceDir    = filepath.Join(outputDir, "alice")
		aliceFriend = filepath.Join(outputDir, "alice.bless")
		bobDir      = filepath.Join(outputDir, "bob")
		bobForPeer  = filepath.Join(outputDir, "bob.store.forpeer")
	)

	// Create two principals: alice and bob.
	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)

	// Bless Alice with Bob's principal.
	blessEnv := []string{"VEYRON_CREDENTIALS=" + aliceDir}
	redirect(t, bin.WithEnv(blessEnv...).Start("bless", bobDir, "friend"), aliceFriend)

	// Run store forpeer on bob.
	bin.Start("--veyron.credentials="+bobDir, "store", "set", aliceFriend, "alice").WaitOrDie(os.Stdout, os.Stderr)
	redirect(t, bin.WithEnv(blessEnv...).Start("--veyron.credentials="+bobDir, "store", "forpeer", "alice/server"), bobForPeer)

	got := removeCaveats(removePublicKeys(bin.Start("dumpblessings", bobForPeer).Output()))
	want := `Blessings          : bob#alice/friend
PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
Certificate chains : 2
Chain #0 (1 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
  Certificate #0: bob with 0 caveats
Chain #1 (2 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
  Certificate #0: alice with 0 caveats
  Certificate #1: friend with 1 caveat
    (0) ExpiryCaveat
`
	if want != got {
		t.Fatalf("unexpected output, wanted\n%s, got\n%s", want, got)
	}
}

func TestDump(t *testing.T) {
	env := v23tests.New(t)
	defer env.Cleanup()

	var (
		outputDir = env.TempDir()
		bin       = env.BuildGoPkg("v.io/core/veyron/tools/principal")
		aliceDir  = filepath.Join(outputDir, "alice")
	)

	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)

	blessEnv := []string{"VEYRON_CREDENTIALS=" + aliceDir}
	got := removePublicKeys(bin.WithEnv(blessEnv...).Start("dump").Output())
	want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
---------------- BlessingStore ----------------
Default blessings: alice
Peer pattern                   : Blessings
...                            : alice
---------------- BlessingRoots ----------------
Public key                                      : Pattern
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [alice]
`
	if want != got {
		t.Fatalf("unexpected output, wanted\n%s, got\n%s", want, got)
	}
}

func TestRecvBlessings(t *testing.T) {
	env := v23tests.New(t)
	defer env.Cleanup()

	outputDir := env.TempDir()
	bin := env.BuildGoPkg("v.io/core/veyron/tools/principal")

	var (
		aliceDir = filepath.Join(outputDir, "alice")
		carolDir = filepath.Join(outputDir, "carol")
	)

	// Generate principals for alice and carol.
	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
	bin.Start("create", carolDir, "carol").WaitOrDie(os.Stdout, os.Stderr)

	// Run recvblessings on carol, and have alice send blessings over
	// (blessings received must be set as default and shareable with all peers).
	var args []string
	{

		inv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings")
		defer inv.Kill(syscall.SIGTERM)
		e := expect.NewSession(t, inv.Stdout(), 3*time.Second)
		// recvblessings suggests a random extension, find the extension and replace it with friend/carol.
		cmd := e.ExpectSetEventuallyRE("(^principal bless .*$)")[0][0]
		args = strings.Split(regexp.MustCompile("extension[0-9]*").ReplaceAllString(cmd, "friend/carol"), " ")
	}

	bin.WithEnv("VEYRON_CREDENTIALS="+aliceDir).Start(args[1:]...).WaitOrDie(os.Stdout, os.Stderr)

	// Run recvblessings on carol, and have alice send blessings over
	// (blessings received must be set as shareable with peers matching 'alice/...'.)
	{
		inv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings", "--for_peer=alice", "--set_default=false")
		defer inv.Kill(syscall.SIGTERM)
		e := expect.NewSession(t, inv.Stdout(), 3*time.Second)
		// recvblessings suggests a random extension, find the extension and replace it with friend/carol/foralice.
		cmd := e.ExpectSetEventuallyRE("(^principal bless .*$)")[0][0]
		args = strings.Split(regexp.MustCompile("extension[0-9]*").ReplaceAllString(cmd, "friend/carol/foralice"), " ")
	}
	bin.WithEnv("VEYRON_CREDENTIALS="+aliceDir).Start(args[1:]...).WaitOrDie(os.Stdout, os.Stderr)

	listenerInv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings", "--for_peer=alice/...", "--set_default=false", "--vmodule=*=2", "--logtostderr")
	defer listenerInv.Kill(syscall.SIGTERM)

	e := expect.NewSession(t, listenerInv.Stdout(), 3*time.Second)
	sendCmd := e.ExpectSetEventuallyRE("(^principal bless .*$)")[0][0]
	{
		// Mucking around with the key should fail.
		args = strings.Split(regexp.MustCompile("remote_key=").ReplaceAllString(sendCmd, "remote_key=BAD"), " ")
	}

	{
		var buf bytes.Buffer
		if bin.WithEnv("VEYRON_CREDENTIALS="+aliceDir).Start(args[1:]...).Wait(os.Stdout, &buf) == nil {
			t.Fatalf("sender should have failed but did not")
		}

		if want, got := "key mismatch", buf.String(); !strings.Contains(got, want) {
			t.Fatalf("expected %q to be contained within\n%s\n, but was not", want, got)
		}
	}

	{
		var buf bytes.Buffer
		// Mucking around with the token should fail.
		args = strings.Split(regexp.MustCompile("remote_token=").ReplaceAllString(sendCmd, "remote_token=BAD"), " ")
		if bin.WithEnv("VEYRON_CREDENTIALS="+aliceDir).Start(args[1:]...).Wait(os.Stdout, &buf) == nil {
			t.Fatalf("sender should have failed but did not")
		}

		if want, got := "blessings received from unexpected sender", buf.String(); !strings.Contains(got, want) {
			t.Fatalf("expected %q to be contained within\n%s\n, but was not", want, got)
		}
	}

	// Dump carol out, the only blessing that survives should be from the
	// first "bless" command. (alice/friend/carol).
	{
		got := removePublicKeys(bin.Start("--veyron.credentials="+carolDir, "dump").Output())
		want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
---------------- BlessingStore ----------------
Default blessings: alice/friend/carol
Peer pattern                   : Blessings
...                            : alice/friend/carol
alice                          : alice/friend/carol/foralice
---------------- BlessingRoots ----------------
Public key                                      : Pattern
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [alice]
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [carol]
`
		if want != got {
			t.Fatalf("unexpected output, wanted\n%s, got\n%s", want, got)
		}
	}
}

func TestFork(t *testing.T) {
	env := v23tests.New(t)
	defer env.Cleanup()

	var (
		outputDir             = env.TempDir()
		bin                   = env.BuildGoPkg("v.io/core/veyron/tools/principal")
		aliceDir              = filepath.Join(outputDir, "alice")
		alicePhoneDir         = filepath.Join(outputDir, "alice-phone")
		alicePhoneCalendarDir = filepath.Join(outputDir, "alice-phone-calendar")
	)

	// Generate principals for alice.
	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)

	// Run fork to setup up credentials for alice/phone that are
	// blessed by alice under the extension "phone".
	bin.Start("--veyron.credentials="+aliceDir, "fork", alicePhoneDir, "phone").WaitOrDie(os.Stdout, os.Stderr)

	// Dump alice-phone out, the only blessings it has must be from alice (alice/phone).
	{
		got := removePublicKeys(bin.Start("--veyron.credentials="+alicePhoneDir, "dump").Output())
		want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
---------------- BlessingStore ----------------
Default blessings: alice/phone
Peer pattern                   : Blessings
...                            : alice/phone
---------------- BlessingRoots ----------------
Public key                                      : Pattern
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [alice]
`
		if want != got {
			t.Fatalf("unexpected output, wanted\n%s, got\n%s", want, got)
		}
	}

	// Run fork to setup up credentials for alice/phone/calendar that are
	// blessed by alice-phone under the extension "calendar".
	bin.Start("--veyron.credentials="+alicePhoneDir, "fork", alicePhoneCalendarDir, "calendar").WaitOrDie(os.Stdout, os.Stderr)
	{
		got := removePublicKeys(bin.Start("--veyron.credentials="+alicePhoneCalendarDir, "dump").Output())
		want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
---------------- BlessingStore ----------------
Default blessings: alice/phone/calendar
Peer pattern                   : Blessings
...                            : alice/phone/calendar
---------------- BlessingRoots ----------------
Public key                                      : Pattern
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [alice]
`
		if want != got {
			t.Fatalf("unexpected output, wanted\n%s, got\n%s", want, got)
		}
	}
}

func TestCreate(t *testing.T) {
	env := v23tests.New(t)
	defer env.Cleanup()

	var (
		outputDir = env.TempDir()
		bin       = env.BuildGoPkg("v.io/core/veyron/tools/principal")
		aliceDir  = filepath.Join(outputDir, "alice")
	)

	// Creating a principal should succeed the first time.
	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)

	// The second time should fail (the create command won't override an existing principal).
	if bin.Start("create", aliceDir, "alice").Wait(os.Stdout, os.Stderr) == nil {
		t.Fatalf("principal creation should have failed, but did not")
	}

	// If we specify -overwrite, it will.
	bin.Start("create", "--overwrite", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
}
<|endoftext|>


<commit_before>package plugins

import (
	"ircclient"
	"time"
	"container/list"
	"json"
	"sync"
	"fmt"
)

type LecturePlugin struct {
	ic            *ircclient.IRCClient
	confplugin    *ConfigPlugin
	authplugin    *AuthPlugin
	notifications *list.List
	done          chan bool
	update        chan bool
	lock          sync.Mutex
}

type notification struct {
	when  int64
	entry configEntry
}

type configEntry struct {
	Name     string // AuD
	Time     string // Mon 13:15
	Channel  string // #faui2k11
	LongName string // Algorithmen und Datenstrukturen
	Academic string // Brinda
	Venue    string // H11
}

// Fills the list of notifications with all lectures for
// the current day.
func (l *LecturePlugin) fillNotificationList() {
	l.notifications = list.New()

	l.confplugin.Lock()
	l.lock.Lock()
	defer l.lock.Unlock()
	defer l.confplugin.Unlock()
	options, _ := l.confplugin.Conf.Options("Lectures")
	curtime := time.LocalTime()
	for _, key := range options {
		value, _ := l.confplugin.Conf.String("Lectures", key)
		var lecture configEntry
		if err := json.Unmarshal([]byte(value), &lecture); err != nil {
			// panics should only happen during initialization, during runtime,
			// all config entries are checked before insertion.
			panic("LecturePlugin: Invalid JSON for key " + key + " : " + err.String())
		}

		timertime, err := time.Parse("Mon 15:04", lecture.Time)
		if err != nil {
			panic("LecturePlugin: Unable to parse time \"" + lecture.Time + "\" in config: " + err.String())
		}

		// Only consider notifications for the current day
		// TODO: Notifications for next day should also be considered
		// if necessary (because we send notifications ~10minutes
		// before the lecture starts)
		if timertime.Weekday != curtime.Weekday {
			continue
		}

		save1, save2, save3 := timertime.Hour, timertime.Minute, timertime.Second
		*timertime = *curtime
		timertime.Hour, timertime.Minute, timertime.Second = save1, save2, save3
		// TODO: Make this configurable
		timertime = time.SecondsToLocalTime(timertime.Seconds() - (60))
		if timertime.Seconds() >= curtime.Seconds() {
			fmt.Println("Registered lecture")
			l.notifications.PushFront(notification{timertime.Seconds(), lecture})
		}
	}
}

// Gets seconds until beginning of next day
func (l *LecturePlugin) untilNextDay() int64 {
	t := time.LocalTime()
	t.Hour, t.Minute, t.Second = 0, 0, 1
	// First second of new day
	sec := t.Seconds() + (24 * 60 * 60)
	return sec - time.Seconds()
}

func (l *LecturePlugin) sendNotifications() {
	for {
		var nextNotification int64 = time.Seconds() + (24 * 3600)
		// List contains the element we sent notifications for,
		// so we can remove them in the second loop.
		li := list.New()
		l.lock.Lock()
		for e := l.notifications.Front(); e != nil; e = e.Next() {
			notify := e.Value.(notification)
			entry := notify.entry
			if notify.when <= time.Seconds() {
				l.ic.SendLine("PRIVMSG " + entry.Channel + " :inb4 (" + entry.Time + "): \"" + entry.LongName + "\" (" + entry.Name + ") bei " + entry.Academic + ", Ort: " + entry.Venue)
				li.PushFront(e)
			} else if nextNotification > notify.when {
				nextNotification = notify.when
			}
		}
		for e := li.Front(); e != nil; e = e.Next() {
			li.Remove(e.Value.(*list.Element))
		}
		l.lock.Unlock()
		select {
		case <-l.done:
			return
		case <-time.After(l.untilNextDay() * 1e9):
			l.fillNotificationList()
		case <-time.After((nextNotification - time.Seconds()) * 1e9):
		case <-l.update:
			// Send notifications and refresh timer
		}
	}
}

func (l *LecturePlugin) Register(cl *ircclient.IRCClient) {
	l.ic = cl
	plugin, ok := l.ic.GetPlugin("config")
	if !ok {
		panic("LecturePlugin: Register: Unable to get configuration manager plugin")
	}
	l.confplugin, _ = plugin.(*ConfigPlugin)
	authplugin, ok := l.ic.GetPlugin("auth")
	if !ok {
		panic("LecturePlugin: Register: Unable to get authorization plugin")
	}
	l.authplugin, _ = authplugin.(*AuthPlugin)
	if !l.confplugin.Conf.HasSection("Lectures") {
		l.confplugin.Conf.AddSection("Lectures")
	   // test := &configEntry{"AuD", "Mon 13:15", "#go-faui2k11", "Algorithmen und Datenstrukturen", "Brinda", "H11"}
	   // js, _ := json.Marshal(test)
	   // l.confplugin.Conf.AddOption("Lectures", test.Name, string(js))
	}
	l.done = make(chan bool)
	l.update = make(chan bool)
	l.fillNotificationList()
	go l.sendNotifications()
}

func (l *LecturePlugin) String() string {
	return "lecture"
}

func (l *LecturePlugin) Info() string {
	return "lecture notifications"
}

func (l *LecturePlugin) ProcessLine(msg *ircclient.IRCMessage) {
}

func (l *LecturePlugin) ProcessCommand(cmd *ircclient.IRCCommand) {
	if cmd.Command != "reglecture" && cmd.Command != "dellecture" {
		return
	}
	if l.authplugin.GetAccessLevel(cmd.Source) < 300 {
		l.ic.Reply(cmd, "You are not authorized to do that")
		return
	}
	switch cmd.Command {
	case "reglecture":
		if len(cmd.Args) != 6 {
			l.ic.Reply(cmd, "reglesson takes exactly 6 arguments:")
			l.ic.Reply(cmd, "Syntax: reglesson NAME TIME CHANNEL LONGNAME ACADEMIC VENUE")
			l.ic.Reply(cmd, "Example: reglesson AuD \"Mon 13:15\" #faui2k11 \"Algorithmen und Datenstrukturen\" Brinda H11")
			return
		}
		lecture := configEntry{cmd.Args[0], cmd.Args[1], cmd.Args[2], cmd.Args[3], cmd.Args[4], cmd.Args[5]}
		jlecture, _ := json.Marshal(lecture)
		l.confplugin.Lock()
		l.confplugin.Conf.AddOption("Lectures", fmt.Sprintf("%d", time.Seconds()), string(jlecture))
		l.confplugin.Unlock()
		l.ic.Reply(cmd, "Lecture added.")
		l.fillNotificationList()
		l.update <- true

	case "dellecture":
		// TODO
	}
}

func (l *LecturePlugin) Unregister() {
	l.done <- true
}
<commit_msg>Commit some crap for lecture.go<commit_after>package plugins

import (
	"os"
	"ircclient"
	"time"
	"container/list"
	"json"
	"sync"
	"fmt"
	"log"
)

type LecturePlugin struct {
	ic            *ircclient.IRCClient
	confplugin    *ConfigPlugin
	authplugin    *AuthPlugin
	notifications *list.List
	done          chan bool
	update        chan bool
	lock          sync.Mutex
}

type notification struct {
	when  int64
	entry configEntry
}

type configEntry struct {
	Name     string // AuD
	Time     string // Mon 13:15
	Channel  string // #faui2k11
	LongName string // Algorithmen und Datenstrukturen
	Academic string // Brinda
	Venue    string // H11
}

func nextAt(date string) (int64, os.Error) {
	timertime, err := time.Parse("Mon 15:04", date)
	if err != nil {
		return 0, err
	}
	curtime := time.LocalTime()

	weekday := timertime.Weekday
	save1, save2, save3 := timertime.Hour, timertime.Minute, timertime.Second
	*timertime = *curtime
	timertime.Hour, timertime.Minute, timertime.Second = save1, save2, save3

	for timertime.Weekday != weekday {
		timertime = time.SecondsToLocalTime(timertime.Seconds() + (24 * 60 * 60))
	}
	log.Println("Lecture next at: " + string(timertime.Seconds()) + ", current: " + string(curtime.Seconds()))
	return timertime.Seconds(), nil
}

// Fills the list of notifications with all lectures for
// the current day.
func (l *LecturePlugin) fillNotificationList() {
	l.notifications = list.New()

	l.confplugin.Lock()
	l.lock.Lock()
	defer l.lock.Unlock()
	defer l.confplugin.Unlock()
	options, _ := l.confplugin.Conf.Options("Lectures")
	for _, key := range options {
		value, _ := l.confplugin.Conf.String("Lectures", key)
		var lecture configEntry
		if err := json.Unmarshal([]byte(value), &lecture); err != nil {
			// panics should only happen during initialization, during runtime,
			// all config entries are checked before insertion.
			panic("LecturePlugin: Invalid JSON for key " + key + " : " + err.String())
		}

		// TODO: Configurable
		time, err := nextAt(lecture.Time)
		if err != nil {
			log.Printf("Unable to parse time value for lecture %s: %s\n", lecture.Name, err.String())
		}
		l.notifications.PushFront(notification{time - 60, lecture})
	}
}

// Gets seconds until beginning of next day
/*
func (l *LecturePlugin) untilNextDay() int64 {
	t := time.LocalTime()
	t.Hour, t.Minute, t.Second = 0, 0, 1
	// First second of new day
	sec := t.Seconds() + (24 * 60 * 60)
	return sec - time.Seconds()
}
*/

func (l *LecturePlugin) sendNotifications() {
	for {
		var nextNotification int64 = time.Seconds() + (24 * 3600)
		// List contains the element we sent notifications for,
		// so we can remove them in the second loop.
		li := list.New()
		l.lock.Lock()
		for e := l.notifications.Front(); e != nil; e = e.Next() {
			notify := e.Value.(notification)
			entry := notify.entry
			if notify.when <= time.Seconds() {
				l.ic.SendLine("PRIVMSG " + entry.Channel + " :inb4 (" + entry.Time + "): \"" + entry.LongName + "\" (" + entry.Name + ") bei " + entry.Academic + ", Ort: " + entry.Venue)
				li.PushFront(e)
			} else if nextNotification > notify.when {
				nextNotification = notify.when
			}
		}
		for e := li.Front(); e != nil; e = e.Next() {
			li.Remove(e.Value.(*list.Element))
		}
		l.lock.Unlock()
		select {
		case <-l.done:
			return
		case <-time.After((nextNotification - time.Seconds()) * 1e9):
		case <-l.update:
			// Send notifications and refresh timer
		}
	}
}

func (l *LecturePlugin) Register(cl *ircclient.IRCClient) {
	l.ic = cl
	plugin, ok := l.ic.GetPlugin("config")
	if !ok {
		panic("LecturePlugin: Register: Unable to get configuration manager plugin")
	}
	l.confplugin, _ = plugin.(*ConfigPlugin)
	authplugin, ok := l.ic.GetPlugin("auth")
	if !ok {
		panic("LecturePlugin: Register: Unable to get authorization plugin")
	}
	l.authplugin, _ = authplugin.(*AuthPlugin)
	if !l.confplugin.Conf.HasSection("Lectures") {
		l.confplugin.Conf.AddSection("Lectures")
	   // test := &configEntry{"AuD", "Mon 13:15", "#go-faui2k11", "Algorithmen und Datenstrukturen", "Brinda", "H11"}
	   // js, _ := json.Marshal(test)
	   // l.confplugin.Conf.AddOption("Lectures", test.Name, string(js))
	}
	l.done = make(chan bool)
	l.update = make(chan bool)
	l.fillNotificationList()
	go l.sendNotifications()
}

func (l *LecturePlugin) String() string {
	return "lecture"
}

func (l *LecturePlugin) Info() string {
	return "lecture notifications"
}

func (l *LecturePlugin) ProcessLine(msg *ircclient.IRCMessage) {
}

func (l *LecturePlugin) ProcessCommand(cmd *ircclient.IRCCommand) {
	if cmd.Command != "reglecture" && cmd.Command != "dellecture" {
		return
	}
	if l.authplugin.GetAccessLevel(cmd.Source) < 300 {
		l.ic.Reply(cmd, "You are not authorized to do that")
		return
	}
	switch cmd.Command {
	case "reglecture":
		if len(cmd.Args) != 6 {
			l.ic.Reply(cmd, "reglesson takes exactly 6 arguments:")
			l.ic.Reply(cmd, "Syntax: reglesson NAME TIME CHANNEL LONGNAME ACADEMIC VENUE")
			l.ic.Reply(cmd, "Example: reglesson AuD \"Mon 13:15\" #faui2k11 \"Algorithmen und Datenstrukturen\" Brinda H11")
			return
		}
		lecture := configEntry{cmd.Args[0], cmd.Args[1], cmd.Args[2], cmd.Args[3], cmd.Args[4], cmd.Args[5]}
		jlecture, _ := json.Marshal(lecture)
		l.confplugin.Lock()
		l.confplugin.Conf.AddOption("Lectures", fmt.Sprintf("%d", time.Seconds()), string(jlecture))
		l.confplugin.Unlock()
		l.ic.Reply(cmd, "Lecture added.")
		l.fillNotificationList()
		l.update <- true

	case "dellecture":
		// TODO
	}
}

func (l *LecturePlugin) Unregister() {
	l.done <- true
}
<|endoftext|>