<commit_before>package common

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/going/toolkit/xmlpath"
)

type Parallels9Driver struct {
	// This is the path to the "prlctl" application.
	PrlctlPath string
	// The path to the parallels_dhcp_leases file
	dhcp_lease_file string
}

func (d *Parallels9Driver) Import(name, srcPath, dstDir string, reassignMac bool) error {

	err := d.Prlctl("register", srcPath, "--preserve-uuid")
	if err != nil {
		return err
	}

	srcId, err := getVmId(srcPath)
	if err != nil {
		return err
	}

	srcMac := "auto"
	if !reassignMac {
		srcMac, err = getFirtsMacAddress(srcPath)
		if err != nil {
			return err
		}
	}

	err = d.Prlctl("clone", srcId, "--name", name, "--dst", dstDir)
	if err != nil {
		return err
	}

	err = d.Prlctl("unregister", srcId)
	if err != nil {
		return err
	}

	err = d.Prlctl("set", name, "--device-set", "net0", "--mac", srcMac)
	return nil
}

func getVmId(path string) (string, error) {
	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Identification/VmUuid")
}

func getFirtsMacAddress(path string) (string, error) {
	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC")
}

func getConfigValueFromXpath(path, xpath string) (string, error) {
	file, err := os.Open(path + "/config.pvs")
	if err != nil {
		return "", err
	}
	xpathComp := xmlpath.MustCompile(xpath)
	root, err := xmlpath.Parse(file)
	if err != nil {
		return "", err
	}
	value, _ := xpathComp.String(root)
	return value, nil
}

// Finds an application bundle by identifier (for "darwin" platform only)
func getAppPath(bundleId string) (string, error) {
	var stdout bytes.Buffer

	cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId)
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return "", err
	}

	pathOutput := strings.TrimSpace(stdout.String())
	if pathOutput == "" {
		return "", fmt.Errorf(
			"Could not detect Parallels Desktop! Make sure it is properly installed.")
	}

	return pathOutput, nil
}

func (d *Parallels9Driver) DeviceAddCdRom(name string, image string) (string, error) {
	command := []string{
		"set", name,
		"--device-add", "cdrom",
		"--image", image,
	}

	out, err := exec.Command(d.PrlctlPath, command...).Output()
	if err != nil {
		return "", err
	}

	deviceRe := regexp.MustCompile(`\s+(cdrom\d+)\s+`)
	matches := deviceRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not determine cdrom device name in the output:\n%s", string(out))
	}

	device_name := matches[1]
	return device_name, nil
}

func (d *Parallels9Driver) IsRunning(name string) (bool, error) {
	var stdout bytes.Buffer

	cmd := exec.Command(d.PrlctlPath, "list", name, "--no-header", "--output", "status")
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return false, err
	}

	log.Printf("Checking VM state: %s\n", strings.TrimSpace(stdout.String()))

	for _, line := range strings.Split(stdout.String(), "\n") {
		if line == "running" {
			return true, nil
		}

		if line == "suspended" {
			return true, nil
		}
		if line == "paused" {
			return true, nil
		}
		if line == "stopping" {
			return true, nil
		}
	}

	return false, nil
}

func (d *Parallels9Driver) Stop(name string) error {
	if err := d.Prlctl("stop", name); err != nil {
		return err
	}

	// We sleep here for a little bit to let the session "unlock"
	time.Sleep(2 * time.Second)

	return nil
}

func (d *Parallels9Driver) Prlctl(args ...string) error {
	var stdout, stderr bytes.Buffer

	log.Printf("Executing prlctl: %#v", args)
	cmd := exec.Command(d.PrlctlPath, args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("prlctl error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	return err
}

func (d *Parallels9Driver) Verify() error {
	return nil
}

func (d *Parallels9Driver) Version() (string, error) {
	out, err := exec.Command(d.PrlctlPath, "--version").Output()
	if err != nil {
		return "", err
	}

	versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`)
	matches := versionRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not find Parallels Desktop version in output:\n%s", string(out))
	}

	version := matches[1]
	log.Printf("Parallels Desktop version: %s", version)
	return version, nil
}

func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error {
	var stdout, stderr bytes.Buffer

	if codes == nil || len(codes) == 0 {
		log.Printf("No scan codes to send")
		return nil
	}

	f, err := ioutil.TempFile("", "prltype")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())

	script := []byte(Prltype)
	_, err = f.Write(script)
	if err != nil {
		return err
	}

	args := prepend(vmName, codes)
	args = prepend(f.Name(), args)
	cmd := exec.Command("/usr/bin/python", args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err = cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("prltype error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	return err
}

func prepend(head string, tail []string) []string {
	tmp := make([]string, len(tail)+1)
	for i := 0; i < len(tail); i++ {
		tmp[i+1] = tail[i]
	}
	tmp[0] = head
	return tmp
}

func (d *Parallels9Driver) SetDefaultConfiguration(vmName string) error {
	commands := make([][]string, 7)
	commands[0] = []string{"set", vmName, "--cpus", "1"}
	commands[1] = []string{"set", vmName, "--memsize", "512"}
	commands[2] = []string{"set", vmName, "--startup-view", "same"}
	commands[3] = []string{"set", vmName, "--on-shutdown", "close"}
	commands[4] = []string{"set", vmName, "--on-window-close", "keep-running"}
	commands[5] = []string{"set", vmName, "--auto-share-camera", "off"}
	commands[6] = []string{"set", vmName, "--smart-guard", "off"}

	for _, command := range commands {
		err := d.Prlctl(command...)
		if err != nil {
			return err
		}
	}
	return nil
}

func (d *Parallels9Driver) Mac(vmName string) (string, error) {
	var stdout bytes.Buffer

	cmd := exec.Command(d.PrlctlPath, "list", "-i", vmName)
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		log.Printf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
		return "", err
	}

	stdoutString := strings.TrimSpace(stdout.String())
	re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*")
	macMatch := re.FindAllStringSubmatch(stdoutString, 1)

	if len(macMatch) != 1 {
		return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
	}

	mac := macMatch[0][1]
	log.Printf("Found MAC address for NIC: net0 - %s\n", mac)
	return mac, nil
}

// Finds the IP address of a VM connected that uses DHCP by its MAC address
//
// Parses the file /Library/Preferences/Parallels/parallels_dhcp_leases
// file contain a list of DHCP leases given by Parallels Desktop
// Example line:
// 10.211.55.181="1418921112,1800,001c42f593fb,ff42f593fb000100011c25b9ff001c42f593fb"
// IP Address   ="Lease expiry, Lease time, MAC, MAC or DUID"
func (d *Parallels9Driver) IpAddress(mac string) (string, error) {

	if len(mac) != 12 {
		return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac)
	}

	leases, err := ioutil.ReadFile(d.dhcp_lease_file)
	if err != nil {
		return "", err
	}

	re := regexp.MustCompile("(.*)=\"(.*),(.*)," + strings.ToLower(mac) + ",.*\"")
	mostRecentIp := ""
	mostRecentLease := uint64(0)
	for _, l := range re.FindAllStringSubmatch(string(leases), -1) {
		ip := l[1]
		expiry, _ := strconv.ParseUint(l[2], 10, 64)
		leaseTime, _ := strconv.ParseUint(l[3], 10, 32)
		log.Printf("Found lease: %s for MAC: %s, expiring at %d, leased for %d s.\n", ip, mac, expiry, leaseTime)
		if mostRecentLease <= expiry-leaseTime {
			mostRecentIp = ip
			mostRecentLease = expiry - leaseTime
		}
	}

	if len(mostRecentIp) == 0 {
		return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, d.dhcp_lease_file)
	}

	log.Printf("Found IP lease: %s for MAC address %s\n", mostRecentIp, mac)
	return mostRecentIp, nil
}

func (d *Parallels9Driver) ToolsIsoPath(k string) (string, error) {
	appPath, err := getAppPath("com.parallels.desktop.console")
	if err != nil {
		return "", err
	}

	toolsPath := filepath.Join(appPath, "Contents", "Resources", "Tools", "prl-tools-"+k+".iso")
	log.Printf("Parallels Tools path: '%s'", toolsPath)
	return toolsPath, nil
}
<commit_msg>Update dependency to an SSL-enabled path; this was migrated from launchpad to github<commit_after>package common

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"gopkg.in/xmlpath.v2"
)

type Parallels9Driver struct {
	// This is the path to the "prlctl" application.
	PrlctlPath string
	// The path to the parallels_dhcp_leases file
	dhcp_lease_file string
}

func (d *Parallels9Driver) Import(name, srcPath, dstDir string, reassignMac bool) error {

	err := d.Prlctl("register", srcPath, "--preserve-uuid")
	if err != nil {
		return err
	}

	srcId, err := getVmId(srcPath)
	if err != nil {
		return err
	}

	srcMac := "auto"
	if !reassignMac {
		srcMac, err = getFirtsMacAddress(srcPath)
		if err != nil {
			return err
		}
	}

	err = d.Prlctl("clone", srcId, "--name", name, "--dst", dstDir)
	if err != nil {
		return err
	}

	err = d.Prlctl("unregister", srcId)
	if err != nil {
		return err
	}

	err = d.Prlctl("set", name, "--device-set", "net0", "--mac", srcMac)
	return nil
}

func getVmId(path string) (string, error) {
	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Identification/VmUuid")
}

func getFirtsMacAddress(path string) (string, error) {
	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC")
}

func getConfigValueFromXpath(path, xpath string) (string, error) {
	file, err := os.Open(path + "/config.pvs")
	if err != nil {
		return "", err
	}
	xpathComp := xmlpath.MustCompile(xpath)
	root, err := xmlpath.Parse(file)
	if err != nil {
		return "", err
	}
	value, _ := xpathComp.String(root)
	return value, nil
}

// Finds an application bundle by identifier (for "darwin" platform only)
func getAppPath(bundleId string) (string, error) {
	var stdout bytes.Buffer

	cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId)
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return "", err
	}

	pathOutput := strings.TrimSpace(stdout.String())
	if pathOutput == "" {
		return "", fmt.Errorf(
			"Could not detect Parallels Desktop! Make sure it is properly installed.")
	}

	return pathOutput, nil
}

func (d *Parallels9Driver) DeviceAddCdRom(name string, image string) (string, error) {
	command := []string{
		"set", name,
		"--device-add", "cdrom",
		"--image", image,
	}

	out, err := exec.Command(d.PrlctlPath, command...).Output()
	if err != nil {
		return "", err
	}

	deviceRe := regexp.MustCompile(`\s+(cdrom\d+)\s+`)
	matches := deviceRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not determine cdrom device name in the output:\n%s", string(out))
	}

	device_name := matches[1]
	return device_name, nil
}

func (d *Parallels9Driver) IsRunning(name string) (bool, error) {
	var stdout bytes.Buffer

	cmd := exec.Command(d.PrlctlPath, "list", name, "--no-header", "--output", "status")
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return false, err
	}

	log.Printf("Checking VM state: %s\n", strings.TrimSpace(stdout.String()))

	for _, line := range strings.Split(stdout.String(), "\n") {
		if line == "running" {
			return true, nil
		}

		if line == "suspended" {
			return true, nil
		}
		if line == "paused" {
			return true, nil
		}
		if line == "stopping" {
			return true, nil
		}
	}

	return false, nil
}

func (d *Parallels9Driver) Stop(name string) error {
	if err := d.Prlctl("stop", name); err != nil {
		return err
	}

	// We sleep here for a little bit to let the session "unlock"
	time.Sleep(2 * time.Second)

	return nil
}

func (d *Parallels9Driver) Prlctl(args ...string) error {
	var stdout, stderr bytes.Buffer

	log.Printf("Executing prlctl: %#v", args)
	cmd := exec.Command(d.PrlctlPath, args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("prlctl error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	return err
}

func (d *Parallels9Driver) Verify() error {
	return nil
}

func (d *Parallels9Driver) Version() (string, error) {
	out, err := exec.Command(d.PrlctlPath, "--version").Output()
	if err != nil {
		return "", err
	}

	versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`)
	matches := versionRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not find Parallels Desktop version in output:\n%s", string(out))
	}

	version := matches[1]
	log.Printf("Parallels Desktop version: %s", version)
	return version, nil
}

func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error {
	var stdout, stderr bytes.Buffer

	if codes == nil || len(codes) == 0 {
		log.Printf("No scan codes to send")
		return nil
	}

	f, err := ioutil.TempFile("", "prltype")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())

	script := []byte(Prltype)
	_, err = f.Write(script)
	if err != nil {
		return err
	}

	args := prepend(vmName, codes)
	args = prepend(f.Name(), args)
	cmd := exec.Command("/usr/bin/python", args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err = cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("prltype error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	return err
}

func prepend(head string, tail []string) []string {
	tmp := make([]string, len(tail)+1)
	for i := 0; i < len(tail); i++ {
		tmp[i+1] = tail[i]
	}
	tmp[0] = head
	return tmp
}

func (d *Parallels9Driver) SetDefaultConfiguration(vmName string) error {
	commands := make([][]string, 7)
	commands[0] = []string{"set", vmName, "--cpus", "1"}
	commands[1] = []string{"set", vmName, "--memsize", "512"}
	commands[2] = []string{"set", vmName, "--startup-view", "same"}
	commands[3] = []string{"set", vmName, "--on-shutdown", "close"}
	commands[4] = []string{"set", vmName, "--on-window-close", "keep-running"}
	commands[5] = []string{"set", vmName, "--auto-share-camera", "off"}
	commands[6] = []string{"set", vmName, "--smart-guard", "off"}

	for _, command := range commands {
		err := d.Prlctl(command...)
		if err != nil {
			return err
		}
	}
	return nil
}

func (d *Parallels9Driver) Mac(vmName string) (string, error) {
	var stdout bytes.Buffer

	cmd := exec.Command(d.PrlctlPath, "list", "-i", vmName)
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		log.Printf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
		return "", err
	}

	stdoutString := strings.TrimSpace(stdout.String())
	re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*")
	macMatch := re.FindAllStringSubmatch(stdoutString, 1)

	if len(macMatch) != 1 {
		return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
	}

	mac := macMatch[0][1]
	log.Printf("Found MAC address for NIC: net0 - %s\n", mac)
	return mac, nil
}

// Finds the IP address of a VM connected that uses DHCP by its MAC address
//
// Parses the file /Library/Preferences/Parallels/parallels_dhcp_leases
// file contain a list of DHCP leases given by Parallels Desktop
// Example line:
// 10.211.55.181="1418921112,1800,001c42f593fb,ff42f593fb000100011c25b9ff001c42f593fb"
// IP Address   ="Lease expiry, Lease time, MAC, MAC or DUID"
func (d *Parallels9Driver) IpAddress(mac string) (string, error) {

	if len(mac) != 12 {
		return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac)
	}

	leases, err := ioutil.ReadFile(d.dhcp_lease_file)
	if err != nil {
		return "", err
	}

	re := regexp.MustCompile("(.*)=\"(.*),(.*)," + strings.ToLower(mac) + ",.*\"")
	mostRecentIp := ""
	mostRecentLease := uint64(0)
	for _, l := range re.FindAllStringSubmatch(string(leases), -1) {
		ip := l[1]
		expiry, _ := strconv.ParseUint(l[2], 10, 64)
		leaseTime, _ := strconv.ParseUint(l[3], 10, 32)
		log.Printf("Found lease: %s for MAC: %s, expiring at %d, leased for %d s.\n", ip, mac, expiry, leaseTime)
		if mostRecentLease <= expiry-leaseTime {
			mostRecentIp = ip
			mostRecentLease = expiry - leaseTime
		}
	}

	if len(mostRecentIp) == 0 {
		return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, d.dhcp_lease_file)
	}

	log.Printf("Found IP lease: %s for MAC address %s\n", mostRecentIp, mac)
	return mostRecentIp, nil
}

func (d *Parallels9Driver) ToolsIsoPath(k string) (string, error) {
	appPath, err := getAppPath("com.parallels.desktop.console")
	if err != nil {
		return "", err
	}

	toolsPath := filepath.Join(appPath, "Contents", "Resources", "Tools", "prl-tools-"+k+".iso")
	log.Printf("Parallels Tools path: '%s'", toolsPath)
	return toolsPath, nil
}
<|endoftext|>


<commit_before>/*
Package router implements wrappers around
github.com/julienschmidt/httprouter.Router to enable the use of
golang.org/x/net/context.Context in endpoint handlers.

A simple example looks like this:

	package main

	import (
		"fmt"
		"log"
		"net/http"

		"github.com/teepark/contextual"
		"github.com/teepark/contextual/router"
		"golang.org/x/net/context"
	)

	func index(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Welcome!")
	}

	func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		params := router.Params(ctx)
		fmt.Fprintf(w, "Hello, %s!\n", params.ByName("name"))
	}

	func main() {
		router := router.New(nil, nil) // take defaults
		router.GET("/", contextual.HandlerFunc(index))
		router.GET("/hello/:name", contextual.HandlerFunc(hello))

		log.Fatal(http.ListenAndServe(":8080", router))
	}

The initialization function can be used like middleware, to preload data into
the context based on the request (a bit of faked code in this example):

	package main

	import (
		"fmt"
		"log"
		"net/http"

		"github.com/me/myAPI/authentication"
		"github.com/teepark/contextual"
		"github.com/teepark/contextual/router"
		"golang.org/x/net/context"
	)

	func index(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Welcome!")
	}

	func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		params := router.Params(ctx)
		fmt.Fprintf(w, "Hello, %s!\n", params.ByName("name"))
	}

	func initialContext(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		uid := authentication.Auth(r)
		if uid != 0 {
			// store the user_id for the endpoint handler
			ctx = context.WithValue(ctx, "user_id", uid)
		} else {
			// bail on the endpoint by canceling the context
			var cancel func()
			ctx, cancel = context.WithCancel(ctx)
			cancel()
			http.Error(w, "Please log in.", http.StatusForbidden)
		}
		return ctx
	}

	func main() {
		router := router.New(nil, initialContext)
		router.GET("/", contextual.HandlerFunc(index))
		router.GET("/hello/:name", contextual.HandlerFunc(hello))

		log.Fatal(http.ListenAndServe(":8080", router))
	}
*/
package router

import (
	"net/http"

	"github.com/julienschmidt/httprouter"
	"github.com/teepark/contextual"
	"golang.org/x/net/context"
)

// ParamKey is the key in a context.Context under which the httprouter.Params
// will be stored.
const ParamKey = "params"

// InitFunc is a function to transform the Context before an endpoint is called.
// To short-circuit and skip the handlers entirely, return a Context that is
// already done (probably by cancelling). Be sure you also set an appropriate
// response code to the ResponseWriter.
type InitFunc func(context.Context, http.ResponseWriter, *http.Request) context.Context

// Router wraps an httprouter.Router, then for each request adds the
// httprouter's Params as the Value("params") to the context, executes a custom
// initialization function, and passes the context on to handlers that accept
// it as an argument.
type Router struct {
	router *httprouter.Router
	init   InitFunc
}

// New creates a new Router around a given httprouter.Router.
// All arguments may be nil, in which case the Router would wrap a
// Router created with httprouter.New() and there would be no
// initialization of the context.
func New(router *httprouter.Router, init InitFunc) *Router {
	if router == nil {
		router = httprouter.New()
	}

	return &Router{
		router: router,
		init:   init,
	}
}

// ServeHTTP implements http.Handler
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	router.router.ServeHTTP(w, r)
}

// Lookup allows manual lookup of a method/path. If one is found, it
// returns the Handle and Params for the route. Otherwise the third
// return value indicates whether adding or removing a trailing slash
// would result in a found route.
func (router *Router) Lookup(method, path string) (httprouter.Handle, httprouter.Params, bool) {
	return router.router.Lookup(method, path)
}

// ServeFiles serves files from a given filesystem root. The path must
// end with "/*filepath". See httprouter's documentation for details.
func (router *Router) ServeFiles(path string, root http.FileSystem) {
	router.router.ServeFiles(path, root)
}

// Handle adds a method/path handler with a context.Context argument
func (router *Router) Handle(method, path string, handle contextual.Handler) {
	router.router.Handle(method, path, handlerShim(router, handle))
}

// Handle adds an http.Handler for a method/path
func (router *Router) Handler(method, path string, handler http.Handler) {
	router.router.Handler(method, path, handler)
}

// handlerFunc adds an http.HandlerFunc for a method/path
func (router *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
	router.router.HandlerFunc(method, path, handler)
}

// GET is a shortcut for Handle("GET", ...)
func (router *Router) GET(path string, handle contextual.Handler) {
	router.Handle("GET", path, handle)
}

// HEAD is a shortcut for Handle("HEAD", ...)
func (router *Router) HEAD(path string, handle contextual.Handler) {
	router.Handle("HEAD", path, handle)
}

// POST is a shortcut for Handle("POST", ...)
func (router *Router) POST(path string, handle contextual.Handler) {
	router.Handle("POST", path, handle)
}

// PUT is a shortcut for Handle("PUT", ...)
func (router *Router) PUT(path string, handle contextual.Handler) {
	router.Handle("PUT", path, handle)
}

// DELETE is a shortcut for Handle("DELETE", ...)
func (router *Router) DELETE(path string, handle contextual.Handler) {
	router.Handle("DELETE", path, handle)
}

// OPTIONS is a shortcut for Handle("OPTIONS", ...)
func (router *Router) OPTIONS(path string, handle contextual.Handler) {
	router.Handle("OPTIONS", path, handle)
}

// PATCH is a shortcut for Handle("PATCH", ...)
func (router *Router) PATCH(path string, handle contextual.Handler) {
	router.Handle("PATCH", path, handle)
}

func handlerShim(router *Router, ctxHandle contextual.Handler) httprouter.Handle {
	return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		ctx := context.WithValue(context.Background(), ParamKey, p)

		if router.init != nil {
			ctx = router.init(ctx, w, r)
			select {
			case <-ctx.Done():
				return
			default:
			}
		}

		ctxHandle.Serve(ctx, w, r)
	})
}

// Params retrieves the httprouter Params from a context
func Params(c context.Context) httprouter.Params {
	p := c.Value(ParamKey)
	if p == nil {
		return nil
	}
	return p.(httprouter.Params)
}
<commit_msg>cleans up the Router documentation<commit_after>/*
Package router implements wrappers around
github.com/julienschmidt/httprouter.Router to enable the use of
golang.org/x/net/context.Context in endpoint handlers.

A simple example looks like this:

	package main

	import (
		"fmt"
		"log"
		"net/http"

		"github.com/teepark/contextual"
		"github.com/teepark/contextual/router"
		"golang.org/x/net/context"
	)

	func index(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Welcome!")
	}

	func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		params := router.Params(ctx)
		fmt.Fprintf(w, "Hello, %s!\n", params.ByName("name"))
	}

	func main() {
		router := router.New(nil, nil) // take defaults
		router.GET("/", contextual.HandlerFunc(index))
		router.GET("/hello/:name", contextual.HandlerFunc(hello))

		log.Fatal(http.ListenAndServe(":8080", router))
	}

The initialization function can be used like middleware, to preload data into
the context based on the request (a bit of faked code in this example):

	package main

	import (
		"fmt"
		"log"
		"net/http"

		"github.com/me/myAPI/authentication"
		"github.com/teepark/contextual"
		"github.com/teepark/contextual/router"
		"golang.org/x/net/context"
	)

	func index(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Welcome!")
	}

	func hello(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		params := router.Params(ctx)
		fmt.Fprintf(w, "Hello, %s!\n", params.ByName("name"))
	}

	func initialContext(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		uid := authentication.Auth(r)
		if uid != 0 {
			// store the user_id for the endpoint handler
			ctx = context.WithValue(ctx, "user_id", uid)
		} else {
			// bail on the endpoint by canceling the context
			var cancel func()
			ctx, cancel = context.WithCancel(ctx)
			cancel()
			http.Error(w, "Please log in.", http.StatusForbidden)
		}
		return ctx
	}

	func main() {
		router := router.New(nil, initialContext)
		router.GET("/", contextual.HandlerFunc(index))
		router.GET("/hello/:name", contextual.HandlerFunc(hello))

		log.Fatal(http.ListenAndServe(":8080", router))
	}
*/
package router

import (
	"net/http"

	"github.com/julienschmidt/httprouter"
	"github.com/teepark/contextual"
	"golang.org/x/net/context"
)

// ParamKey is the key in a context.Context under which the httprouter.Params
// will be stored.
const ParamKey = "params"

// InitFunc is a function to transform the Context before an endpoint is called.
// To short-circuit and skip the handlers entirely, return a Context that is
// already done (probably by cancelling). Be sure you also set an appropriate
// response code to the ResponseWriter.
type InitFunc func(context.Context, http.ResponseWriter, *http.Request) context.Context

// Router wraps an httprouter.Router to accept contextual.Handlers. For
// each request, it adds the httprouter.Params to the context as
// Value(router.ParamKey), executes a context initialization function,
// and passes the resulting context to the handlers.
type Router struct {
	router *httprouter.Router
	init   InitFunc
}

// New creates a new Router around a given httprouter.Router.
// All arguments may be nil, in which case the Router would wrap a
// Router created with httprouter.New() and there would be no
// initialization of the context.
func New(router *httprouter.Router, init InitFunc) *Router {
	if router == nil {
		router = httprouter.New()
	}

	return &Router{
		router: router,
		init:   init,
	}
}

// ServeHTTP implements http.Handler
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	router.router.ServeHTTP(w, r)
}

// Lookup allows manual lookup of a method/path. If one is found, it
// returns the Handle and Params for the route. Otherwise the third
// return value indicates whether adding or removing a trailing slash
// would result in a found route.
func (router *Router) Lookup(method, path string) (httprouter.Handle, httprouter.Params, bool) {
	return router.router.Lookup(method, path)
}

// ServeFiles serves files from a given filesystem root. The path must
// end with "/*filepath". See httprouter's documentation for details.
func (router *Router) ServeFiles(path string, root http.FileSystem) {
	router.router.ServeFiles(path, root)
}

// Handle adds a method/path handler with a context.Context argument
func (router *Router) Handle(method, path string, handle contextual.Handler) {
	router.router.Handle(method, path, handlerShim(router, handle))
}

// Handle adds an http.Handler for a method/path
func (router *Router) Handler(method, path string, handler http.Handler) {
	router.router.Handler(method, path, handler)
}

// handlerFunc adds an http.HandlerFunc for a method/path
func (router *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
	router.router.HandlerFunc(method, path, handler)
}

// GET is a shortcut for Handle("GET", ...)
func (router *Router) GET(path string, handle contextual.Handler) {
	router.Handle("GET", path, handle)
}

// HEAD is a shortcut for Handle("HEAD", ...)
func (router *Router) HEAD(path string, handle contextual.Handler) {
	router.Handle("HEAD", path, handle)
}

// POST is a shortcut for Handle("POST", ...)
func (router *Router) POST(path string, handle contextual.Handler) {
	router.Handle("POST", path, handle)
}

// PUT is a shortcut for Handle("PUT", ...)
func (router *Router) PUT(path string, handle contextual.Handler) {
	router.Handle("PUT", path, handle)
}

// DELETE is a shortcut for Handle("DELETE", ...)
func (router *Router) DELETE(path string, handle contextual.Handler) {
	router.Handle("DELETE", path, handle)
}

// OPTIONS is a shortcut for Handle("OPTIONS", ...)
func (router *Router) OPTIONS(path string, handle contextual.Handler) {
	router.Handle("OPTIONS", path, handle)
}

// PATCH is a shortcut for Handle("PATCH", ...)
func (router *Router) PATCH(path string, handle contextual.Handler) {
	router.Handle("PATCH", path, handle)
}

func handlerShim(router *Router, ctxHandle contextual.Handler) httprouter.Handle {
	return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		ctx := context.WithValue(context.Background(), ParamKey, p)

		if router.init != nil {
			ctx = router.init(ctx, w, r)
			select {
			case <-ctx.Done():
				return
			default:
			}
		}

		ctxHandle.Serve(ctx, w, r)
	})
}

// Params retrieves the httprouter Params from a context
func Params(c context.Context) httprouter.Params {
	p := c.Value(ParamKey)
	if p == nil {
		return nil
	}
	return p.(httprouter.Params)
}
<|endoftext|>


<commit_before>package rproxy

import (
	"errors"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"

	"gitlab.codility.net/marcink/redis-proxy/resp"
)

type ProxyConfigHolder interface {
	ReloadConfig()
	GetConfig() *ProxyConfig
}

type Proxy struct {
	configLoader ConfigLoader
	config       *ProxyConfig
	controller   *ProxyController
	listenAddr   *net.Addr
}

func NewProxy(cl ConfigLoader) (*Proxy, error) {
	config, err := cl.Load()
	if err != nil {
		return nil, err
	}
	proxy := &Proxy{
		configLoader: cl,
		config:       config,
		controller:   NewProxyController()}
	return proxy, nil
}

func (proxy *Proxy) Run() error {
	genListener, err := net.Listen("tcp", proxy.config.Listen.Addr)
	if err != nil {
		return err
	}
	defer genListener.Close()
	listener := genListener.(*net.TCPListener)

	addr := listener.Addr()
	proxy.listenAddr = &addr

	log.Println("Listening on", proxy.ListenAddr())

	proxy.controller.Start(proxy) // TODO: clean this up when getting rid of circular dep
	go proxy.watchSignals()
	go proxy.publishAdminInterface()

	for proxy.controller.Alive() {
		listener.SetDeadline(time.Now().Add(time.Second))
		conn, err := listener.Accept()
		if err != nil {
			if resp.IsNetTimeout(err) {
				continue
			}
			return err
		} else {
			go proxy.handleClient(resp.NewConn(conn, 0, proxy.config.LogMessages))
		}
	}
	return nil
}

func (proxy *Proxy) Alive() bool {
	return proxy.controller.Alive()
}

func (proxy *Proxy) ListenAddr() net.Addr {
	return *proxy.listenAddr
}

func (proxy *Proxy) RequiresClientAuth() bool {
	return proxy.config.Listen.Pass != ""
}

func (proxy *Proxy) ReloadConfig() {
	newConfig, err := proxy.configLoader.Load()
	if err != nil {
		log.Printf("Got an error while loading %s: %s.  Keeping old config.", proxy, err)
		return
	}

	if err := proxy.verifyNewConfig(newConfig); err != nil {
		log.Printf("Can not reload into new config: %s.  Keeping old config.", err)
		return
	}
	proxy.config = newConfig
}

func (proxy *Proxy) GetConfig() *ProxyConfig {
	return proxy.config
}

func (proxy *Proxy) watchSignals() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGHUP)

	for {
		s := <-c
		log.Printf("Got signal: %v, reloading config\n", s)
		proxy.controller.Reload()
	}
}

func (proxy *Proxy) verifyNewConfig(newConfig *ProxyConfig) error {
	config := proxy.config
	if !config.Listen.Equal(&newConfig.Listen) {
		return errors.New("New config must have the same `listen` block as the old one.")
	}
	if !config.Admin.Equal(&newConfig.Admin) {
		return errors.New("New config must have the same `admin` block as the old one.")
	}
	return nil
}

func (proxy *Proxy) handleClient(cliConn *resp.Conn) {
	log.Printf("Handling new client: connection from %s", cliConn.RemoteAddr())

	uplinkConf := &AddrSpec{}
	var uplinkConn *resp.Conn
	cliAuthenticated := false
	db := 0

	defer func() {
		cliConn.Close()
		if uplinkConn != nil {
			uplinkConn.Close()
		}
	}()

	for {
		req, err := cliConn.ReadMsg()
		if err != nil {
			log.Printf("Read error: %v\n", err)
			return
		}

		if req.Op() == resp.MsgOpBroken {
			cliConn.Write(resp.MsgParseError)
			return
		}

		if req.Op() == resp.MsgOpAuth {
			if proxy.RequiresClientAuth() {
				cliAuthenticated = (req.FirstArg() == proxy.config.Listen.Pass)
				if cliAuthenticated {
					cliConn.Write([]byte(resp.MsgOk))
				} else {
					cliConn.Write([]byte(resp.MsgInvalidPass))
				}
			} else {
				cliConn.Write([]byte(resp.MsgNoPasswordSet))
			}
			continue
		}

		if proxy.RequiresClientAuth() && !cliAuthenticated {
			cliConn.Write([]byte(resp.MsgNoAuth))
			continue
		}

		res, err := proxy.controller.CallUplink(func() (*resp.Msg, error) {
			config := proxy.config
			currUplinkConf := &config.Uplink
			if !uplinkConf.Equal(currUplinkConf) {
				uplinkConf = currUplinkConf
				if uplinkConn != nil {
					uplinkConn.Close()
				}
				uplinkConn, err = resp.Dial("tcp", uplinkConf.Addr,
					config.ReadTimeLimitMs,
					config.LogMessages,
				)
				if err != nil {
					return nil, err
				}

				if uplinkConf.Pass != "" {
					if err := uplinkConn.Authenticate(uplinkConf.Pass); err != nil {
						return nil, err
					}
				}

				if db != 0 {
					if err := uplinkConn.Select(db); err != nil {
						return nil, err
					}
				}
			}

			_, err := uplinkConn.WriteMsg(req)
			if err != nil {
				return nil, err
			}
			return uplinkConn.ReadMsg()
		})
		if err != nil {
			log.Printf("Error: %v\n", err)
			return
		}

		if (req.Op() == resp.MsgOpSelect) && res.IsOk() {
			db = req.FirstArgInt()
		}

		cliConn.WriteMsg(res)
	}
}
<commit_msg>Do not complain about EOF in client connections.<commit_after>package rproxy

import (
	"errors"
	"io"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"

	"gitlab.codility.net/marcink/redis-proxy/resp"
)

type ProxyConfigHolder interface {
	ReloadConfig()
	GetConfig() *ProxyConfig
}

type Proxy struct {
	configLoader ConfigLoader
	config       *ProxyConfig
	controller   *ProxyController
	listenAddr   *net.Addr
}

func NewProxy(cl ConfigLoader) (*Proxy, error) {
	config, err := cl.Load()
	if err != nil {
		return nil, err
	}
	proxy := &Proxy{
		configLoader: cl,
		config:       config,
		controller:   NewProxyController()}
	return proxy, nil
}

func (proxy *Proxy) Run() error {
	genListener, err := net.Listen("tcp", proxy.config.Listen.Addr)
	if err != nil {
		return err
	}
	defer genListener.Close()
	listener := genListener.(*net.TCPListener)

	addr := listener.Addr()
	proxy.listenAddr = &addr

	log.Println("Listening on", proxy.ListenAddr())

	proxy.controller.Start(proxy) // TODO: clean this up when getting rid of circular dep
	go proxy.watchSignals()
	go proxy.publishAdminInterface()

	for proxy.controller.Alive() {
		listener.SetDeadline(time.Now().Add(time.Second))
		conn, err := listener.Accept()
		if err != nil {
			if resp.IsNetTimeout(err) {
				continue
			}
			return err
		} else {
			go proxy.handleClient(resp.NewConn(conn, 0, proxy.config.LogMessages))
		}
	}
	return nil
}

func (proxy *Proxy) Alive() bool {
	return proxy.controller.Alive()
}

func (proxy *Proxy) ListenAddr() net.Addr {
	return *proxy.listenAddr
}

func (proxy *Proxy) RequiresClientAuth() bool {
	return proxy.config.Listen.Pass != ""
}

func (proxy *Proxy) ReloadConfig() {
	newConfig, err := proxy.configLoader.Load()
	if err != nil {
		log.Printf("Got an error while loading %s: %s.  Keeping old config.", proxy, err)
		return
	}

	if err := proxy.verifyNewConfig(newConfig); err != nil {
		log.Printf("Can not reload into new config: %s.  Keeping old config.", err)
		return
	}
	proxy.config = newConfig
}

func (proxy *Proxy) GetConfig() *ProxyConfig {
	return proxy.config
}

func (proxy *Proxy) watchSignals() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGHUP)

	for {
		s := <-c
		log.Printf("Got signal: %v, reloading config\n", s)
		proxy.controller.Reload()
	}
}

func (proxy *Proxy) verifyNewConfig(newConfig *ProxyConfig) error {
	config := proxy.config
	if !config.Listen.Equal(&newConfig.Listen) {
		return errors.New("New config must have the same `listen` block as the old one.")
	}
	if !config.Admin.Equal(&newConfig.Admin) {
		return errors.New("New config must have the same `admin` block as the old one.")
	}
	return nil
}

func (proxy *Proxy) handleClient(cliConn *resp.Conn) {
	log.Printf("Handling new client: connection from %s", cliConn.RemoteAddr())

	uplinkConf := &AddrSpec{}
	var uplinkConn *resp.Conn
	cliAuthenticated := false
	db := 0

	defer func() {
		cliConn.Close()
		if uplinkConn != nil {
			uplinkConn.Close()
		}
	}()

	for {
		req, err := cliConn.ReadMsg()
		if err != nil {
			if err != io.EOF {
				log.Printf("Read error: %v\n", err)
			}
			return
		}

		if req.Op() == resp.MsgOpBroken {
			cliConn.Write(resp.MsgParseError)
			return
		}

		if req.Op() == resp.MsgOpAuth {
			if proxy.RequiresClientAuth() {
				cliAuthenticated = (req.FirstArg() == proxy.config.Listen.Pass)
				if cliAuthenticated {
					cliConn.Write([]byte(resp.MsgOk))
				} else {
					cliConn.Write([]byte(resp.MsgInvalidPass))
				}
			} else {
				cliConn.Write([]byte(resp.MsgNoPasswordSet))
			}
			continue
		}

		if proxy.RequiresClientAuth() && !cliAuthenticated {
			cliConn.Write([]byte(resp.MsgNoAuth))
			continue
		}

		res, err := proxy.controller.CallUplink(func() (*resp.Msg, error) {
			config := proxy.config
			currUplinkConf := &config.Uplink
			if !uplinkConf.Equal(currUplinkConf) {
				uplinkConf = currUplinkConf
				if uplinkConn != nil {
					uplinkConn.Close()
				}
				uplinkConn, err = resp.Dial("tcp", uplinkConf.Addr,
					config.ReadTimeLimitMs,
					config.LogMessages,
				)
				if err != nil {
					return nil, err
				}

				if uplinkConf.Pass != "" {
					if err := uplinkConn.Authenticate(uplinkConf.Pass); err != nil {
						return nil, err
					}
				}

				if db != 0 {
					if err := uplinkConn.Select(db); err != nil {
						return nil, err
					}
				}
			}

			_, err := uplinkConn.WriteMsg(req)
			if err != nil {
				return nil, err
			}
			return uplinkConn.ReadMsg()
		})
		if err != nil {
			log.Printf("Error: %v\n", err)
			return
		}

		if (req.Op() == resp.MsgOpSelect) && res.IsOk() {
			db = req.FirstArgInt()
		}

		cliConn.WriteMsg(res)
	}
}
<|endoftext|>


<commit_before>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package superast

import (
	"encoding/json"
	"flag"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
)

var (
	write = flag.Bool("write", false, "Write json results")
	name  = flag.String("name", "", "Test name")
)

func init() {
	flag.Parse()
}

func toJSON(t *testing.T, a *AST) []byte {
	b, err := json.MarshalIndent(a.RootBlock, "", "  ")
	if err != nil {
		t.Errorf("Could not generate JSON from AST: %s", err)
	}
	b = append(b, '\n')
	return b
}

const (
	testsDir    = "tests"
	inFilename  = "in.go"
	outFilename = "out.json"
)

func doTest(t *testing.T, name string) {
	fset := token.NewFileSet()
	in, err := os.Open(filepath.Join(testsDir, name, inFilename))
	if err != nil {
		t.Errorf("Failed opening file: %s", err)
	}
	f, err := parser.ParseFile(fset, name+".go", in, 0)
	if err != nil {
		t.Errorf("Failed parsing source file: %s", err)
	}
	a := NewAST(fset)
	ast.Walk(a, f)
	got := toJSON(t, a)
	outPath := filepath.Join(testsDir, name, outFilename)
	if *write {
		out, err := os.Create(outPath)
		if err != nil {
			t.Errorf("Failed opening file: %s", err)
		}
		_, err = out.Write(got)
		if err != nil {
			t.Errorf("Failed writing json file: %s", err)
		}
	} else {
		out, err := os.Open(outPath)
		if err != nil {
			t.Errorf("Failed opening file: %s", err)
		}
		want, err := ioutil.ReadAll(out)
		if err != nil {
			t.Errorf("Failed reading json file: %s", err)
		}
		if string(want) != string(got) {
			t.Errorf("Mismatching JSON outputs in the test '%s'", name)
		}
	}
}

func TestCases(t *testing.T) {
	entries, err := ioutil.ReadDir(testsDir)
	if err != nil {
		return
	}
	if *name != "" {
		doTest(t, *name)
	} else {
		for _, e := range entries {
			if !e.IsDir() {
				continue
			}
			doTest(t, e.Name())
		}
	}
}
<commit_msg>Properly close files when done<commit_after>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package superast

import (
	"encoding/json"
	"flag"
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
)

var (
	write = flag.Bool("write", false, "Write json results")
	name  = flag.String("name", "", "Test name")
)

func init() {
	flag.Parse()
}

func toJSON(t *testing.T, a *AST) []byte {
	b, err := json.MarshalIndent(a.RootBlock, "", "  ")
	if err != nil {
		t.Errorf("Could not generate JSON from AST: %s", err)
	}
	b = append(b, '\n')
	return b
}

const (
	testsDir    = "tests"
	inFilename  = "in.go"
	outFilename = "out.json"
)

func doTest(t *testing.T, name string) {
	fset := token.NewFileSet()
	in, err := os.Open(filepath.Join(testsDir, name, inFilename))
	if err != nil {
		t.Errorf("Failed opening file: %s", err)
	}
	defer in.Close()
	f, err := parser.ParseFile(fset, name+".go", in, 0)
	if err != nil {
		t.Errorf("Failed parsing source file: %s", err)
	}
	a := NewAST(fset)
	ast.Walk(a, f)
	got := toJSON(t, a)
	outPath := filepath.Join(testsDir, name, outFilename)
	if *write {
		out, err := os.Create(outPath)
		if err != nil {
			t.Errorf("Failed opening file: %s", err)
		}
		defer out.Close()
		_, err = out.Write(got)
		if err != nil {
			t.Errorf("Failed writing json file: %s", err)
		}
	} else {
		out, err := os.Open(outPath)
		if err != nil {
			t.Errorf("Failed opening file: %s", err)
		}
		defer out.Close()
		want, err := ioutil.ReadAll(out)
		if err != nil {
			t.Errorf("Failed reading json file: %s", err)
		}
		if string(want) != string(got) {
			t.Errorf("Mismatching JSON outputs in the test '%s'", name)
		}
	}
}

func TestCases(t *testing.T) {
	entries, err := ioutil.ReadDir(testsDir)
	if err != nil {
		return
	}
	if *name != "" {
		doTest(t, *name)
	} else {
		for _, e := range entries {
			if !e.IsDir() {
				continue
			}
			doTest(t, e.Name())
		}
	}
}
<|endoftext|>


<commit_before>package cec

/*
#cgo pkg-config: libcec
#include <stdio.h>
#include <libcec/cecc.h>

ICECCallbacks g_callbacks;
// callbacks.go exports
int logMessageCallback(void *, const cec_log_message);

void setupCallbacks(libcec_configuration *conf)
{
	g_callbacks.CBCecLogMessage = &logMessageCallback;
	(*conf).callbacks = &g_callbacks;
}

void setName(libcec_configuration *conf, char *name)
{
	snprintf((*conf).strDeviceName, 13, "%s", name);
}

static void clearLogicalAddresses(cec_logical_addresses* addresses)
{
	int i;

	addresses->primary = CECDEVICE_UNREGISTERED;
	for (i = 0; i < 16; i++)
		addresses->addresses[i] = 0;
}

void setLogicalAddress(cec_logical_addresses* addresses, cec_logical_address address)
{
	if (addresses->primary == CECDEVICE_UNREGISTERED)
		addresses->primary = address;

	addresses->addresses[(int) address] = 1;
}

*/
import "C"

import (
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"strings"
)

// Connection class
type Connection struct {
	connection C.libcec_connection_t
}

type cecAdapter struct {
	Path string
	Comm string
}

func cecInit(deviceName string) (C.libcec_connection_t, error) {
	var connection C.libcec_connection_t
	var conf C.libcec_configuration

	// XXX: remove ?
	// conf.clientVersion = C.uint32_t(C.CEC_CLIENT_VERSION_CURRENT)
	// conf.serverVersion = C.uint32_t(C.CEC_SERVER_VERSION_CURRENT)

	for i := 0; i < 5; i++ {
		conf.deviceTypes.types[i] = C.CEC_DEVICE_TYPE_RESERVED
	}
	conf.deviceTypes.types[0] = C.CEC_DEVICE_TYPE_RECORDING_DEVICE

	C.setName(&conf, C.CString(deviceName))
	C.setupCallbacks(&conf)

	connection = C.libcec_initialise(&conf)
	if connection == C.libcec_connection_t(nil) {
		return connection, errors.New("Failed to init CEC")
	}
	return connection, nil
}

func getAdapter(connection C.libcec_connection_t, name string) (cecAdapter, error) {
	var adapter cecAdapter

	var deviceList [10]C.cec_adapter
	devicesFound := int(C.libcec_find_adapters(connection, &deviceList[0], 10, nil))

	for i := 0; i < devicesFound; i++ {
		device := deviceList[i]
		adapter.Path = C.GoStringN(&device.path[0], 1024)
		adapter.Comm = C.GoStringN(&device.comm[0], 1024)

		if strings.Contains(adapter.Path, name) || strings.Contains(adapter.Comm, name) {
			return adapter, nil
		}
	}

	return adapter, errors.New("No Device Found")
}

func openAdapter(connection C.libcec_connection_t, adapter cecAdapter) error {
	C.libcec_init_video_standalone(connection)

	result := C.libcec_open(connection, C.CString(adapter.Comm), C.CEC_DEFAULT_CONNECT_TIMEOUT)
	if result < 1 {
		return errors.New("Failed to open adapter")
	}

	return nil
}

// Transmit CEC command - command is encoded as a hex string with
// colons (e.g. "40:04")
func (c *Connection) Transmit(command string) {
	var cecCommand C.cec_command

	cmd, err := hex.DecodeString(removeSeparators(command))
	if err != nil {
		log.Fatal(err)
	}
	cmdLen := len(cmd)

	if cmdLen > 0 {
		cecCommand.initiator = C.cec_logical_address((cmd[0] >> 4) & 0xF)
		cecCommand.destination = C.cec_logical_address(cmd[0] & 0xF)
		if cmdLen > 1 {
			cecCommand.opcode_set = 1
			cecCommand.opcode = C.cec_opcode(cmd[1])
		} else {
			cecCommand.opcode_set = 0
		}
		if cmdLen > 2 {
			cecCommand.parameters.size = C.uint8_t(cmdLen - 2)
			for i := 0; i < cmdLen-2; i++ {
				cecCommand.parameters.data[i] = C.uint8_t(cmd[i+2])
			}
		} else {
			cecCommand.parameters.size = 0
		}
	}

	C.libcec_transmit(c.connection, (*C.cec_command)(&cecCommand))
}

// Destroy - destroy the cec connection
func (c *Connection) Destroy() {
	C.libcec_destroy(c.connection)
}

// PowerOn - power on the device with the given logical address
func (c *Connection) PowerOn(address int) error {
	if C.libcec_power_on_devices(c.connection, C.cec_logical_address(address)) != 0 {
		return errors.New("Error in cec_power_on_devices")
	}
	return nil
}

// Standby - put the device with the given address in standby mode
func (c *Connection) Standby(address int) error {
	if C.libcec_standby_devices(c.connection, C.cec_logical_address(address)) != 0 {
		return errors.New("Error in cec_standby_devices")
	}
	return nil
}

// VolumeUp - send a volume up command to the amp if present
func (c *Connection) VolumeUp() error {
	if C.libcec_volume_up(c.connection, 1) != 0 {
		return errors.New("Error in cec_volume_up")
	}
	return nil
}

// VolumeDown - send a volume down command to the amp if present
func (c *Connection) VolumeDown() error {
	if C.libcec_volume_down(c.connection, 1) != 0 {
		return errors.New("Error in cec_volume_down")
	}
	return nil
}

// Mute - send a mute/unmute command to the amp if present
func (c *Connection) Mute() error {
	if C.libcec_mute_audio(c.connection, 1) != 0 {
		return errors.New("Error in cec_mute_audio")
	}
	return nil
}

// KeyPress - send a key press (down) command code to the given address
func (c *Connection) KeyPress(address int, key int) error {
	if C.libcec_send_keypress(c.connection, C.cec_logical_address(address), C.cec_user_control_code(key), 1) != 1 {
		return errors.New("Error in cec_send_keypress")
	}
	return nil
}

// KeyRelease - send a key releas command to the given address
func (c *Connection) KeyRelease(address int) error {
	if C.libcec_send_key_release(c.connection, C.cec_logical_address(address), 1) != 1 {
		return errors.New("Error in cec_send_key_release")
	}
	return nil
}

// GetActiveDevices - returns an array of active devices
func (c *Connection) GetActiveDevices() [16]bool {
	var devices [16]bool
	result := C.libcec_get_active_devices(c.connection)

	for i := 0; i < 16; i++ {
		if int(result.addresses[i]) > 0 {
			devices[i] = true
		}
	}

	return devices
}

// GetDeviceOSDName - get the OSD name of the specified device
func (c *Connection) GetDeviceOSDName(address int) string {
	result := C.libcec_get_device_osd_name(c.connection, C.cec_logical_address(address))

	return C.GoString(&result.name[0])
}

// IsActiveSource - check if the device at the given address is the active source
func (c *Connection) IsActiveSource(address int) bool {
	result := C.libcec_is_active_source(c.connection, C.cec_logical_address(address))

	if int(result) != 0 {
		return true
	}

	return false
}

// GetDeviceVendorID - Get the Vendor-ID of the device at the given address
func (c *Connection) GetDeviceVendorID(address int) uint64 {
	result := C.libcec_get_device_vendor_id(c.connection, C.cec_logical_address(address))

	return uint64(result)
}

// GetDevicePhysicalAddress - Get the physical address of the device at
// the given logical address
func (c *Connection) GetDevicePhysicalAddress(address int) string {
	result := C.libcec_get_device_physical_address(c.connection, C.cec_logical_address(address))

	return fmt.Sprintf("%x.%x.%x.%x", (uint(result)>>12)&0xf, (uint(result)>>8)&0xf, (uint(result)>>4)&0xf, uint(result)&0xf)
}

// GetDevicePowerStatus - Get the power status of the device at the
// given address
func (c *Connection) GetDevicePowerStatus(address int) string {
	result := C.libcec_get_device_power_status(c.connection, C.cec_logical_address(address))

	// C.CEC_POWER_STATUS_UNKNOWN == error

	if int(result) == C.CEC_POWER_STATUS_ON {
		return "on"
	} else if int(result) == C.CEC_POWER_STATUS_STANDBY {
		return "standby"
	} else if int(result) == C.CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON {
		return "starting"
	} else if int(result) == C.CEC_POWER_STATUS_IN_TRANSITION_ON_TO_STANDBY {
		return "shutting down"
	} else {
		return ""
	}
}
<commit_msg>add client-version to config<commit_after>package cec

/*
#cgo pkg-config: libcec
#include <stdio.h>
#include <libcec/cecc.h>

ICECCallbacks g_callbacks;
// callbacks.go exports
int logMessageCallback(void *, const cec_log_message);

void setupCallbacks(libcec_configuration *conf)
{
	g_callbacks.CBCecLogMessage = &logMessageCallback;
	(*conf).callbacks = &g_callbacks;
}

void setName(libcec_configuration *conf, char *name)
{
	snprintf((*conf).strDeviceName, 13, "%s", name);
}

static void clearLogicalAddresses(cec_logical_addresses* addresses)
{
	int i;

	addresses->primary = CECDEVICE_UNREGISTERED;
	for (i = 0; i < 16; i++)
		addresses->addresses[i] = 0;
}

void setLogicalAddress(cec_logical_addresses* addresses, cec_logical_address address)
{
	if (addresses->primary == CECDEVICE_UNREGISTERED)
		addresses->primary = address;

	addresses->addresses[(int) address] = 1;
}

*/
import "C"

import (
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"strings"
)

// Connection class
type Connection struct {
	connection C.libcec_connection_t
}

type cecAdapter struct {
	Path string
	Comm string
}

func cecInit(deviceName string) (C.libcec_connection_t, error) {
	var connection C.libcec_connection_t
	var conf C.libcec_configuration

	conf.clientVersion = C.uint32_t(C.LIBCEC_VERSION_CURRENT)

	for i := 0; i < 5; i++ {
		conf.deviceTypes.types[i] = C.CEC_DEVICE_TYPE_RESERVED
	}
	conf.deviceTypes.types[0] = C.CEC_DEVICE_TYPE_RECORDING_DEVICE

	C.setName(&conf, C.CString(deviceName))
	C.setupCallbacks(&conf)

	connection = C.libcec_initialise(&conf)
	if connection == C.libcec_connection_t(nil) {
		return connection, errors.New("Failed to init CEC")
	}
	return connection, nil
}

func getAdapter(connection C.libcec_connection_t, name string) (cecAdapter, error) {
	var adapter cecAdapter

	var deviceList [10]C.cec_adapter
	devicesFound := int(C.libcec_find_adapters(connection, &deviceList[0], 10, nil))

	for i := 0; i < devicesFound; i++ {
		device := deviceList[i]
		adapter.Path = C.GoStringN(&device.path[0], 1024)
		adapter.Comm = C.GoStringN(&device.comm[0], 1024)

		if strings.Contains(adapter.Path, name) || strings.Contains(adapter.Comm, name) {
			return adapter, nil
		}
	}

	return adapter, errors.New("No Device Found")
}

func openAdapter(connection C.libcec_connection_t, adapter cecAdapter) error {
	C.libcec_init_video_standalone(connection)

	result := C.libcec_open(connection, C.CString(adapter.Comm), C.CEC_DEFAULT_CONNECT_TIMEOUT)
	if result < 1 {
		return errors.New("Failed to open adapter")
	}

	return nil
}

// Transmit CEC command - command is encoded as a hex string with
// colons (e.g. "40:04")
func (c *Connection) Transmit(command string) {
	var cecCommand C.cec_command

	cmd, err := hex.DecodeString(removeSeparators(command))
	if err != nil {
		log.Fatal(err)
	}
	cmdLen := len(cmd)

	if cmdLen > 0 {
		cecCommand.initiator = C.cec_logical_address((cmd[0] >> 4) & 0xF)
		cecCommand.destination = C.cec_logical_address(cmd[0] & 0xF)
		if cmdLen > 1 {
			cecCommand.opcode_set = 1
			cecCommand.opcode = C.cec_opcode(cmd[1])
		} else {
			cecCommand.opcode_set = 0
		}
		if cmdLen > 2 {
			cecCommand.parameters.size = C.uint8_t(cmdLen - 2)
			for i := 0; i < cmdLen-2; i++ {
				cecCommand.parameters.data[i] = C.uint8_t(cmd[i+2])
			}
		} else {
			cecCommand.parameters.size = 0
		}
	}

	C.libcec_transmit(c.connection, (*C.cec_command)(&cecCommand))
}

// Destroy - destroy the cec connection
func (c *Connection) Destroy() {
	C.libcec_destroy(c.connection)
}

// PowerOn - power on the device with the given logical address
func (c *Connection) PowerOn(address int) error {
	if C.libcec_power_on_devices(c.connection, C.cec_logical_address(address)) != 0 {
		return errors.New("Error in cec_power_on_devices")
	}
	return nil
}

// Standby - put the device with the given address in standby mode
func (c *Connection) Standby(address int) error {
	if C.libcec_standby_devices(c.connection, C.cec_logical_address(address)) != 0 {
		return errors.New("Error in cec_standby_devices")
	}
	return nil
}

// VolumeUp - send a volume up command to the amp if present
func (c *Connection) VolumeUp() error {
	if C.libcec_volume_up(c.connection, 1) != 0 {
		return errors.New("Error in cec_volume_up")
	}
	return nil
}

// VolumeDown - send a volume down command to the amp if present
func (c *Connection) VolumeDown() error {
	if C.libcec_volume_down(c.connection, 1) != 0 {
		return errors.New("Error in cec_volume_down")
	}
	return nil
}

// Mute - send a mute/unmute command to the amp if present
func (c *Connection) Mute() error {
	if C.libcec_mute_audio(c.connection, 1) != 0 {
		return errors.New("Error in cec_mute_audio")
	}
	return nil
}

// KeyPress - send a key press (down) command code to the given address
func (c *Connection) KeyPress(address int, key int) error {
	if C.libcec_send_keypress(c.connection, C.cec_logical_address(address), C.cec_user_control_code(key), 1) != 1 {
		return errors.New("Error in cec_send_keypress")
	}
	return nil
}

// KeyRelease - send a key releas command to the given address
func (c *Connection) KeyRelease(address int) error {
	if C.libcec_send_key_release(c.connection, C.cec_logical_address(address), 1) != 1 {
		return errors.New("Error in cec_send_key_release")
	}
	return nil
}

// GetActiveDevices - returns an array of active devices
func (c *Connection) GetActiveDevices() [16]bool {
	var devices [16]bool
	result := C.libcec_get_active_devices(c.connection)

	for i := 0; i < 16; i++ {
		if int(result.addresses[i]) > 0 {
			devices[i] = true
		}
	}

	return devices
}

// GetDeviceOSDName - get the OSD name of the specified device
func (c *Connection) GetDeviceOSDName(address int) string {
	result := C.libcec_get_device_osd_name(c.connection, C.cec_logical_address(address))

	return C.GoString(&result.name[0])
}

// IsActiveSource - check if the device at the given address is the active source
func (c *Connection) IsActiveSource(address int) bool {
	result := C.libcec_is_active_source(c.connection, C.cec_logical_address(address))

	if int(result) != 0 {
		return true
	}

	return false
}

// GetDeviceVendorID - Get the Vendor-ID of the device at the given address
func (c *Connection) GetDeviceVendorID(address int) uint64 {
	result := C.libcec_get_device_vendor_id(c.connection, C.cec_logical_address(address))

	return uint64(result)
}

// GetDevicePhysicalAddress - Get the physical address of the device at
// the given logical address
func (c *Connection) GetDevicePhysicalAddress(address int) string {
	result := C.libcec_get_device_physical_address(c.connection, C.cec_logical_address(address))

	return fmt.Sprintf("%x.%x.%x.%x", (uint(result)>>12)&0xf, (uint(result)>>8)&0xf, (uint(result)>>4)&0xf, uint(result)&0xf)
}

// GetDevicePowerStatus - Get the power status of the device at the
// given address
func (c *Connection) GetDevicePowerStatus(address int) string {
	result := C.libcec_get_device_power_status(c.connection, C.cec_logical_address(address))

	// C.CEC_POWER_STATUS_UNKNOWN == error

	if int(result) == C.CEC_POWER_STATUS_ON {
		return "on"
	} else if int(result) == C.CEC_POWER_STATUS_STANDBY {
		return "standby"
	} else if int(result) == C.CEC_POWER_STATUS_IN_TRANSITION_STANDBY_TO_ON {
		return "starting"
	} else if int(result) == C.CEC_POWER_STATUS_IN_TRANSITION_ON_TO_STANDBY {
		return "shutting down"
	} else {
		return ""
	}
}
<|endoftext|>


<commit_before>package schema

const UnknownColumn = "§"

type Diff int

const (
	NoDiff Diff = iota
	Added
	Removed
	Changed
)

func (d Diff) Sigil() string {
	switch d {
	case Added:
		return "A"
	case Removed:
		return "D"
	case Changed:
		return "M"
	}
	return ""
}

type Schema struct {
	Tables []*Table
}

func (s *Schema) Table(name string) *Table {
	for _, table := range s.Tables {
		if table.Name == name {
			return table
		}
	}
	return nil
}

type Table struct {
	Name        string
	Columns     []*Column
	Indexes     []*Index
	Constraints []Constraint
	knownDeps   map[string]bool
	*DiffStruct
}

func (t *Table) Column(name string) *Column {
	for _, c := range t.Columns {
		if c.Name == name {
			return c
		}
	}
	return nil
}

func (t *Table) Index(name string) *Index {
	for _, i := range t.Indexes {
		if i.Name == name {
			return i
		}
	}
	return nil
}

type Column struct {
	Name    string
	SqlType string
	Default string
	Alias   string
	DiffStruct
}

type Index struct {
	Name      string
	TableName string
	Columns   []string
	Unique    bool
	Force     bool // Always created, even if other indexes are omitted
	DiffStruct
}

type Constraint interface {
	Name() string
	Sql() string
	DependsOnTable() string
	Differ
}

type Differ interface {
	DiffMode() Diff
	SetDiffMode(diff Diff)
}

type DiffStruct struct {
	Diff Diff
}

func (d *DiffStruct) DiffMode() Diff        { return d.Diff }
func (d *DiffStruct) SetDiffMode(diff Diff) { d.Diff = diff }

func constraintNamed(name string) string {
	if name == "" {
		return ""
	}
	return "constraint " + name + " "
}

type ForeignKeyConstraint struct {
	ConstraintName   string
	SourceTableField string
	TargetTable      string
	TargetTableField string
	*DiffStruct
}

func (f ForeignKeyConstraint) Sql() string {
	return constraintNamed(f.ConstraintName) +
		"foreign key (" + f.SourceTableField + ") references " +
		f.TargetTable + " (" + f.TargetTableField + ")"
}

func (f ForeignKeyConstraint) Name() string {
	return f.ConstraintName
}

func (f ForeignKeyConstraint) DependsOnTable() string {
	return f.TargetTable
}

type PrimaryKeyConstraint struct {
	ConstraintName string
	Column         string
	*DiffStruct
}

func (c PrimaryKeyConstraint) Name() string {
	return c.ConstraintName
}

func (c PrimaryKeyConstraint) Sql() string {
	return constraintNamed(c.ConstraintName) + "primary key (" + c.Column + ")"
}

func (c PrimaryKeyConstraint) DependsOnTable() string {
	return ""
}
<commit_msg>Make foreign keys cascade delete by default.<commit_after>package schema

const UnknownColumn = "§"

type Diff int

const (
	NoDiff Diff = iota
	Added
	Removed
	Changed
)

func (d Diff) Sigil() string {
	switch d {
	case Added:
		return "A"
	case Removed:
		return "D"
	case Changed:
		return "M"
	}
	return ""
}

type Schema struct {
	Tables []*Table
}

func (s *Schema) Table(name string) *Table {
	for _, table := range s.Tables {
		if table.Name == name {
			return table
		}
	}
	return nil
}

type Table struct {
	Name        string
	Columns     []*Column
	Indexes     []*Index
	Constraints []Constraint
	knownDeps   map[string]bool
	*DiffStruct
}

func (t *Table) Column(name string) *Column {
	for _, c := range t.Columns {
		if c.Name == name {
			return c
		}
	}
	return nil
}

func (t *Table) Index(name string) *Index {
	for _, i := range t.Indexes {
		if i.Name == name {
			return i
		}
	}
	return nil
}

type Column struct {
	Name    string
	SqlType string
	Default string
	Alias   string
	DiffStruct
}

type Index struct {
	Name      string
	TableName string
	Columns   []string
	Unique    bool
	Force     bool // Always created, even if other indexes are omitted
	DiffStruct
}

type Constraint interface {
	Name() string
	Sql() string
	DependsOnTable() string
	Differ
}

type Differ interface {
	DiffMode() Diff
	SetDiffMode(diff Diff)
}

type DiffStruct struct {
	Diff Diff
}

func (d *DiffStruct) DiffMode() Diff        { return d.Diff }
func (d *DiffStruct) SetDiffMode(diff Diff) { d.Diff = diff }

func constraintNamed(name string) string {
	if name == "" {
		return ""
	}
	return "constraint " + name + " "
}

type ForeignKeyConstraint struct {
	ConstraintName   string
	SourceTableField string
	TargetTable      string
	TargetTableField string
	*DiffStruct
}

func (f ForeignKeyConstraint) Sql() string {
	return constraintNamed(f.ConstraintName) +
		"foreign key (" + f.SourceTableField + ") references " +
		f.TargetTable + " (" + f.TargetTableField + ") on delete cascade"
}

func (f ForeignKeyConstraint) Name() string {
	return f.ConstraintName
}

func (f ForeignKeyConstraint) DependsOnTable() string {
	return f.TargetTable
}

type PrimaryKeyConstraint struct {
	ConstraintName string
	Column         string
	*DiffStruct
}

func (c PrimaryKeyConstraint) Name() string {
	return c.ConstraintName
}

func (c PrimaryKeyConstraint) Sql() string {
	return constraintNamed(c.ConstraintName) + "primary key (" + c.Column + ")"
}

func (c PrimaryKeyConstraint) DependsOnTable() string {
	return ""
}
<|endoftext|>


<commit_before>package fauxgl

import (
	"image"
	"math"
	"runtime"
	"sync"
)

type Face int

const (
	_ Face = iota
	FaceCW
	FaceCCW
)

type Cull int

const (
	_ Cull = iota
	CullNone
	CullFront
	CullBack
)

type Context struct {
	Width        int
	Height       int
	ColorBuffer  *image.NRGBA
	DepthBuffer  []float64
	ClearColor   Color
	Shader       Shader
	ReadDepth    bool
	WriteDepth   bool
	WriteColor   bool
	AlphaBlend   bool
	Wireframe    bool
	FrontFace    Face
	Cull         Cull
	LineWidth    float64
	DepthBias    float64
	screenMatrix Matrix
	locks        []sync.Mutex
}

func NewContext(width, height int) *Context {
	dc := &Context{}
	dc.Width = width
	dc.Height = height
	dc.ColorBuffer = image.NewNRGBA(image.Rect(0, 0, width, height))
	dc.DepthBuffer = make([]float64, width*height)
	dc.ClearColor = Transparent
	dc.Shader = NewSolidColorShader(Identity(), Color{1, 0, 1, 1})
	dc.ReadDepth = true
	dc.WriteDepth = true
	dc.WriteColor = true
	dc.AlphaBlend = true
	dc.Wireframe = false
	dc.FrontFace = FaceCCW
	dc.Cull = CullBack
	dc.LineWidth = 2
	dc.DepthBias = 0
	dc.screenMatrix = Screen(width, height)
	dc.locks = make([]sync.Mutex, 256)
	dc.ClearDepthBuffer()
	return dc
}

func (dc *Context) Image() image.Image {
	return dc.ColorBuffer
}

func (dc *Context) ClearColorBufferWith(color Color) {
	c := color.NRGBA()
	for y := 0; y < dc.Height; y++ {
		i := dc.ColorBuffer.PixOffset(0, y)
		for x := 0; x < dc.Width; x++ {
			dc.ColorBuffer.Pix[i+0] = c.R
			dc.ColorBuffer.Pix[i+1] = c.G
			dc.ColorBuffer.Pix[i+2] = c.B
			dc.ColorBuffer.Pix[i+3] = c.A
			i += 4
		}
	}
}

func (dc *Context) ClearColorBuffer() {
	dc.ClearColorBufferWith(dc.ClearColor)
}

func (dc *Context) ClearDepthBufferWith(value float64) {
	for i := range dc.DepthBuffer {
		dc.DepthBuffer[i] = value
	}
}

func (dc *Context) ClearDepthBuffer() {
	dc.ClearDepthBufferWith(math.MaxFloat64)
}

func edge(a, b, c Vector) float64 {
	return (b.X-c.X)*(a.Y-c.Y) - (b.Y-c.Y)*(a.X-c.X)
}

func (dc *Context) rasterize(v0, v1, v2 Vertex, s0, s1, s2 Vector) {
	// integer bounding box
	min := s0.Min(s1.Min(s2)).Floor()
	max := s0.Max(s1.Max(s2)).Ceil()
	x0 := int(min.X)
	x1 := int(max.X)
	y0 := int(min.Y)
	y1 := int(max.Y)

	// forward differencing variables
	p := Vector{float64(x0) + 0.5, float64(y0) + 0.5, 0}
	w00 := edge(s1, s2, p)
	w01 := edge(s2, s0, p)
	w02 := edge(s0, s1, p)
	a01 := s1.Y - s0.Y
	b01 := s0.X - s1.X
	a12 := s2.Y - s1.Y
	b12 := s1.X - s2.X
	a20 := s0.Y - s2.Y
	b20 := s2.X - s0.X

	// reciprocals
	ra := 1 / edge(s0, s1, s2)
	r0 := 1 / v0.Output.W
	r1 := 1 / v1.Output.W
	r2 := 1 / v2.Output.W
	ra12 := 1 / a12
	ra20 := 1 / a20
	ra01 := 1 / a01

	// iterate over all pixels in bounding box
	for y := y0; y <= y1; y++ {
		var d float64
		d0 := -w00 * ra12
		d1 := -w01 * ra20
		d2 := -w02 * ra01
		if w00 < 0 && d0 > d {
			d = d0
		}
		if w01 < 0 && d1 > d {
			d = d1
		}
		if w02 < 0 && d2 > d {
			d = d2
		}
		d = float64(int(d))
		if d < 0 {
			// occurs in pathological cases
			d = 0
		}
		w0 := w00 + a12*d
		w1 := w01 + a20*d
		w2 := w02 + a01*d
		wasInside := false
		for x := x0 + int(d); x <= x1; x++ {
			b0 := w0 * ra
			b1 := w1 * ra
			b2 := w2 * ra
			w0 += a12
			w1 += a20
			w2 += a01
			// check if inside triangle
			if b0 < 0 || b1 < 0 || b2 < 0 {
				if wasInside {
					break
				}
				continue
			}
			wasInside = true
			// check depth buffer for early abort
			i := y*dc.Width + x
			if i < 0 || i >= len(dc.DepthBuffer) {
				// TODO: clipping roundoff error; fix
				// TODO: could also be from fat lines going off screen
				continue
			}
			z := b0*s0.Z + b1*s1.Z + b2*s2.Z
			bz := z + dc.DepthBias
			if dc.ReadDepth && bz > dc.DepthBuffer[i] { // safe w/out lock?
				continue
			}
			// perspective-correct interpolation of vertex data
			b := VectorW{b0 * r0, b1 * r1, b2 * r2, 0}
			b.W = 1 / (b.X + b.Y + b.Z)
			v := InterpolateVertexes(v0, v1, v2, b)
			// invoke fragment shader
			color := dc.Shader.Fragment(v)
			if color == Discard {
				continue
			}
			// update buffers atomically
			lock := &dc.locks[(x+y)&255]
			lock.Lock()
			// check depth buffer again
			if bz <= dc.DepthBuffer[i] || !dc.ReadDepth {
				if dc.WriteDepth {
					// update depth buffer
					dc.DepthBuffer[i] = z
				}
				if dc.WriteColor {
					// update color buffer
					if dc.AlphaBlend && color.A < 1 {
						sr, sg, sb, sa := color.NRGBA().RGBA()
						a := (0xffff - sa) * 0x101
						j := dc.ColorBuffer.PixOffset(x, y)
						dr := &dc.ColorBuffer.Pix[j+0]
						dg := &dc.ColorBuffer.Pix[j+1]
						db := &dc.ColorBuffer.Pix[j+2]
						da := &dc.ColorBuffer.Pix[j+3]
						*dr = uint8((uint32(*dr)*a/0xffff + sr) >> 8)
						*dg = uint8((uint32(*dg)*a/0xffff + sg) >> 8)
						*db = uint8((uint32(*db)*a/0xffff + sb) >> 8)
						*da = uint8((uint32(*da)*a/0xffff + sa) >> 8)
					} else {
						dc.ColorBuffer.SetNRGBA(x, y, color.NRGBA())
					}
				}
			}
			lock.Unlock()
		}
		w00 += b12
		w01 += b20
		w02 += b01
	}
}

func (dc *Context) line(v0, v1 Vertex, s0, s1 Vector) {
	n := s1.Sub(s0).Perpendicular().MulScalar(dc.LineWidth / 2)
	s0 = s0.Add(s0.Sub(s1).Normalize().MulScalar(dc.LineWidth / 2))
	s1 = s1.Add(s1.Sub(s0).Normalize().MulScalar(dc.LineWidth / 2))
	s00 := s0.Add(n)
	s01 := s0.Sub(n)
	s10 := s1.Add(n)
	s11 := s1.Sub(n)
	dc.rasterize(v1, v0, v0, s11, s01, s00)
	dc.rasterize(v1, v1, v0, s10, s11, s00)
}

func (dc *Context) wireframe(v0, v1, v2 Vertex, s0, s1, s2 Vector) {
	dc.line(v0, v1, s0, s1)
	dc.line(v1, v2, s1, s2)
	dc.line(v2, v0, s2, s0)
}

func (dc *Context) drawClippedLine(v0, v1 Vertex) {
	// normalized device coordinates
	ndc0 := v0.Output.DivScalar(v0.Output.W).Vector()
	ndc1 := v1.Output.DivScalar(v1.Output.W).Vector()

	// screen coordinates
	s0 := dc.screenMatrix.MulPosition(ndc0)
	s1 := dc.screenMatrix.MulPosition(ndc1)

	// rasterize
	dc.line(v0, v1, s0, s1)
}

func (dc *Context) drawClippedTriangle(v0, v1, v2 Vertex) {
	// normalized device coordinates
	ndc0 := v0.Output.DivScalar(v0.Output.W).Vector()
	ndc1 := v1.Output.DivScalar(v1.Output.W).Vector()
	ndc2 := v2.Output.DivScalar(v2.Output.W).Vector()

	// back face culling
	if dc.Cull != CullNone {
		a := (ndc1.X-ndc0.X)*(ndc2.Y-ndc0.Y) - (ndc2.X-ndc0.X)*(ndc1.Y-ndc0.Y)
		if dc.Cull == CullFront {
			a = -a
		}
		if dc.FrontFace == FaceCW {
			a = -a
		}
		if a <= 0 {
			return
		}
	}

	// screen coordinates
	s0 := dc.screenMatrix.MulPosition(ndc0)
	s1 := dc.screenMatrix.MulPosition(ndc1)
	s2 := dc.screenMatrix.MulPosition(ndc2)

	// rasterize
	if dc.Wireframe {
		dc.wireframe(v0, v1, v2, s0, s1, s2)
	} else {
		dc.rasterize(v0, v1, v2, s0, s1, s2)
	}
}

func (dc *Context) DrawLine(t *Line) {
	// invoke vertex shader
	v1 := dc.Shader.Vertex(t.V1)
	v2 := dc.Shader.Vertex(t.V2)

	if v1.Outside() || v2.Outside() {
		// clip to viewing volume
		line := ClipLine(NewLine(v1, v2))
		if line != nil {
			dc.drawClippedLine(line.V1, line.V2)
		}
	} else {
		// no need to clip
		dc.drawClippedLine(v1, v2)
	}
}

func (dc *Context) DrawTriangle(t *Triangle) {
	// invoke vertex shader
	v1 := dc.Shader.Vertex(t.V1)
	v2 := dc.Shader.Vertex(t.V2)
	v3 := dc.Shader.Vertex(t.V3)

	if v1.Outside() || v2.Outside() || v3.Outside() {
		// clip to viewing volume
		triangles := ClipTriangle(NewTriangle(v1, v2, v3))
		for _, t := range triangles {
			dc.drawClippedTriangle(t.V1, t.V2, t.V3)
		}
	} else {
		// no need to clip
		dc.drawClippedTriangle(v1, v2, v3)
	}
}

func (dc *Context) DrawLines(lines []*Line) {
	wn := runtime.NumCPU()
	done := make(chan bool, wn)
	for wi := 0; wi < wn; wi++ {
		go func(wi int) {
			for i, l := range lines {
				if i%wn == wi {
					dc.DrawLine(l)
				}
			}
			done <- true
		}(wi)
	}
	for wi := 0; wi < wn; wi++ {
		<-done
	}
}

func (dc *Context) DrawTriangles(triangles []*Triangle) {
	wn := runtime.NumCPU()
	done := make(chan bool, wn)
	for wi := 0; wi < wn; wi++ {
		go func(wi int) {
			for i, t := range triangles {
				if i%wn == wi {
					dc.DrawTriangle(t)
				}
			}
			done <- true
		}(wi)
	}
	for wi := 0; wi < wn; wi++ {
		<-done
	}
}

func (dc *Context) DrawMesh(mesh *Mesh) {
	dc.DrawTriangles(mesh.Triangles)
	dc.DrawLines(mesh.Lines)
}
<commit_msg>fix culling<commit_after>package fauxgl

import (
	"image"
	"math"
	"runtime"
	"sync"
)

type Face int

const (
	_ Face = iota
	FaceCW
	FaceCCW
)

type Cull int

const (
	_ Cull = iota
	CullNone
	CullFront
	CullBack
)

type Context struct {
	Width        int
	Height       int
	ColorBuffer  *image.NRGBA
	DepthBuffer  []float64
	ClearColor   Color
	Shader       Shader
	ReadDepth    bool
	WriteDepth   bool
	WriteColor   bool
	AlphaBlend   bool
	Wireframe    bool
	FrontFace    Face
	Cull         Cull
	LineWidth    float64
	DepthBias    float64
	screenMatrix Matrix
	locks        []sync.Mutex
}

func NewContext(width, height int) *Context {
	dc := &Context{}
	dc.Width = width
	dc.Height = height
	dc.ColorBuffer = image.NewNRGBA(image.Rect(0, 0, width, height))
	dc.DepthBuffer = make([]float64, width*height)
	dc.ClearColor = Transparent
	dc.Shader = NewSolidColorShader(Identity(), Color{1, 0, 1, 1})
	dc.ReadDepth = true
	dc.WriteDepth = true
	dc.WriteColor = true
	dc.AlphaBlend = true
	dc.Wireframe = false
	dc.FrontFace = FaceCCW
	dc.Cull = CullBack
	dc.LineWidth = 2
	dc.DepthBias = 0
	dc.screenMatrix = Screen(width, height)
	dc.locks = make([]sync.Mutex, 256)
	dc.ClearDepthBuffer()
	return dc
}

func (dc *Context) Image() image.Image {
	return dc.ColorBuffer
}

func (dc *Context) ClearColorBufferWith(color Color) {
	c := color.NRGBA()
	for y := 0; y < dc.Height; y++ {
		i := dc.ColorBuffer.PixOffset(0, y)
		for x := 0; x < dc.Width; x++ {
			dc.ColorBuffer.Pix[i+0] = c.R
			dc.ColorBuffer.Pix[i+1] = c.G
			dc.ColorBuffer.Pix[i+2] = c.B
			dc.ColorBuffer.Pix[i+3] = c.A
			i += 4
		}
	}
}

func (dc *Context) ClearColorBuffer() {
	dc.ClearColorBufferWith(dc.ClearColor)
}

func (dc *Context) ClearDepthBufferWith(value float64) {
	for i := range dc.DepthBuffer {
		dc.DepthBuffer[i] = value
	}
}

func (dc *Context) ClearDepthBuffer() {
	dc.ClearDepthBufferWith(math.MaxFloat64)
}

func edge(a, b, c Vector) float64 {
	return (b.X-c.X)*(a.Y-c.Y) - (b.Y-c.Y)*(a.X-c.X)
}

func (dc *Context) rasterize(v0, v1, v2 Vertex, s0, s1, s2 Vector) {
	// integer bounding box
	min := s0.Min(s1.Min(s2)).Floor()
	max := s0.Max(s1.Max(s2)).Ceil()
	x0 := int(min.X)
	x1 := int(max.X)
	y0 := int(min.Y)
	y1 := int(max.Y)

	// forward differencing variables
	p := Vector{float64(x0) + 0.5, float64(y0) + 0.5, 0}
	w00 := edge(s1, s2, p)
	w01 := edge(s2, s0, p)
	w02 := edge(s0, s1, p)
	a01 := s1.Y - s0.Y
	b01 := s0.X - s1.X
	a12 := s2.Y - s1.Y
	b12 := s1.X - s2.X
	a20 := s0.Y - s2.Y
	b20 := s2.X - s0.X

	// reciprocals
	ra := 1 / edge(s0, s1, s2)
	r0 := 1 / v0.Output.W
	r1 := 1 / v1.Output.W
	r2 := 1 / v2.Output.W
	ra12 := 1 / a12
	ra20 := 1 / a20
	ra01 := 1 / a01

	// iterate over all pixels in bounding box
	for y := y0; y <= y1; y++ {
		var d float64
		d0 := -w00 * ra12
		d1 := -w01 * ra20
		d2 := -w02 * ra01
		if w00 < 0 && d0 > d {
			d = d0
		}
		if w01 < 0 && d1 > d {
			d = d1
		}
		if w02 < 0 && d2 > d {
			d = d2
		}
		d = float64(int(d))
		if d < 0 {
			// occurs in pathological cases
			d = 0
		}
		w0 := w00 + a12*d
		w1 := w01 + a20*d
		w2 := w02 + a01*d
		wasInside := false
		for x := x0 + int(d); x <= x1; x++ {
			b0 := w0 * ra
			b1 := w1 * ra
			b2 := w2 * ra
			w0 += a12
			w1 += a20
			w2 += a01
			// check if inside triangle
			if b0 < 0 || b1 < 0 || b2 < 0 {
				if wasInside {
					break
				}
				continue
			}
			wasInside = true
			// check depth buffer for early abort
			i := y*dc.Width + x
			if i < 0 || i >= len(dc.DepthBuffer) {
				// TODO: clipping roundoff error; fix
				// TODO: could also be from fat lines going off screen
				continue
			}
			z := b0*s0.Z + b1*s1.Z + b2*s2.Z
			bz := z + dc.DepthBias
			if dc.ReadDepth && bz > dc.DepthBuffer[i] { // safe w/out lock?
				continue
			}
			// perspective-correct interpolation of vertex data
			b := VectorW{b0 * r0, b1 * r1, b2 * r2, 0}
			b.W = 1 / (b.X + b.Y + b.Z)
			v := InterpolateVertexes(v0, v1, v2, b)
			// invoke fragment shader
			color := dc.Shader.Fragment(v)
			if color == Discard {
				continue
			}
			// update buffers atomically
			lock := &dc.locks[(x+y)&255]
			lock.Lock()
			// check depth buffer again
			if bz <= dc.DepthBuffer[i] || !dc.ReadDepth {
				if dc.WriteDepth {
					// update depth buffer
					dc.DepthBuffer[i] = z
				}
				if dc.WriteColor {
					// update color buffer
					if dc.AlphaBlend && color.A < 1 {
						sr, sg, sb, sa := color.NRGBA().RGBA()
						a := (0xffff - sa) * 0x101
						j := dc.ColorBuffer.PixOffset(x, y)
						dr := &dc.ColorBuffer.Pix[j+0]
						dg := &dc.ColorBuffer.Pix[j+1]
						db := &dc.ColorBuffer.Pix[j+2]
						da := &dc.ColorBuffer.Pix[j+3]
						*dr = uint8((uint32(*dr)*a/0xffff + sr) >> 8)
						*dg = uint8((uint32(*dg)*a/0xffff + sg) >> 8)
						*db = uint8((uint32(*db)*a/0xffff + sb) >> 8)
						*da = uint8((uint32(*da)*a/0xffff + sa) >> 8)
					} else {
						dc.ColorBuffer.SetNRGBA(x, y, color.NRGBA())
					}
				}
			}
			lock.Unlock()
		}
		w00 += b12
		w01 += b20
		w02 += b01
	}
}

func (dc *Context) line(v0, v1 Vertex, s0, s1 Vector) {
	n := s1.Sub(s0).Perpendicular().MulScalar(dc.LineWidth / 2)
	s0 = s0.Add(s0.Sub(s1).Normalize().MulScalar(dc.LineWidth / 2))
	s1 = s1.Add(s1.Sub(s0).Normalize().MulScalar(dc.LineWidth / 2))
	s00 := s0.Add(n)
	s01 := s0.Sub(n)
	s10 := s1.Add(n)
	s11 := s1.Sub(n)
	dc.rasterize(v1, v0, v0, s11, s01, s00)
	dc.rasterize(v1, v1, v0, s10, s11, s00)
}

func (dc *Context) wireframe(v0, v1, v2 Vertex, s0, s1, s2 Vector) {
	dc.line(v0, v1, s0, s1)
	dc.line(v1, v2, s1, s2)
	dc.line(v2, v0, s2, s0)
}

func (dc *Context) drawClippedLine(v0, v1 Vertex) {
	// normalized device coordinates
	ndc0 := v0.Output.DivScalar(v0.Output.W).Vector()
	ndc1 := v1.Output.DivScalar(v1.Output.W).Vector()

	// screen coordinates
	s0 := dc.screenMatrix.MulPosition(ndc0)
	s1 := dc.screenMatrix.MulPosition(ndc1)

	// rasterize
	dc.line(v0, v1, s0, s1)
}

func (dc *Context) drawClippedTriangle(v0, v1, v2 Vertex) {
	// normalized device coordinates
	ndc0 := v0.Output.DivScalar(v0.Output.W).Vector()
	ndc1 := v1.Output.DivScalar(v1.Output.W).Vector()
	ndc2 := v2.Output.DivScalar(v2.Output.W).Vector()

	// back face culling
	a := (ndc1.X-ndc0.X)*(ndc2.Y-ndc0.Y) - (ndc2.X-ndc0.X)*(ndc1.Y-ndc0.Y)
	if a < 0 {
		v0, v1, v2 = v2, v1, v0
		ndc0, ndc1, ndc2 = ndc2, ndc1, ndc0
	}
	if dc.Cull == CullFront {
		a = -a
	}
	if dc.FrontFace == FaceCW {
		a = -a
	}
	if dc.Cull != CullNone && a <= 0 {
		return
	}

	// screen coordinates
	s0 := dc.screenMatrix.MulPosition(ndc0)
	s1 := dc.screenMatrix.MulPosition(ndc1)
	s2 := dc.screenMatrix.MulPosition(ndc2)

	// rasterize
	if dc.Wireframe {
		dc.wireframe(v0, v1, v2, s0, s1, s2)
	} else {
		dc.rasterize(v0, v1, v2, s0, s1, s2)
	}
}

func (dc *Context) DrawLine(t *Line) {
	// invoke vertex shader
	v1 := dc.Shader.Vertex(t.V1)
	v2 := dc.Shader.Vertex(t.V2)

	if v1.Outside() || v2.Outside() {
		// clip to viewing volume
		line := ClipLine(NewLine(v1, v2))
		if line != nil {
			dc.drawClippedLine(line.V1, line.V2)
		}
	} else {
		// no need to clip
		dc.drawClippedLine(v1, v2)
	}
}

func (dc *Context) DrawTriangle(t *Triangle) {
	// invoke vertex shader
	v1 := dc.Shader.Vertex(t.V1)
	v2 := dc.Shader.Vertex(t.V2)
	v3 := dc.Shader.Vertex(t.V3)

	if v1.Outside() || v2.Outside() || v3.Outside() {
		// clip to viewing volume
		triangles := ClipTriangle(NewTriangle(v1, v2, v3))
		for _, t := range triangles {
			dc.drawClippedTriangle(t.V1, t.V2, t.V3)
		}
	} else {
		// no need to clip
		dc.drawClippedTriangle(v1, v2, v3)
	}
}

func (dc *Context) DrawLines(lines []*Line) {
	wn := runtime.NumCPU()
	done := make(chan bool, wn)
	for wi := 0; wi < wn; wi++ {
		go func(wi int) {
			for i, l := range lines {
				if i%wn == wi {
					dc.DrawLine(l)
				}
			}
			done <- true
		}(wi)
	}
	for wi := 0; wi < wn; wi++ {
		<-done
	}
}

func (dc *Context) DrawTriangles(triangles []*Triangle) {
	wn := runtime.NumCPU()
	done := make(chan bool, wn)
	for wi := 0; wi < wn; wi++ {
		go func(wi int) {
			for i, t := range triangles {
				if i%wn == wi {
					dc.DrawTriangle(t)
				}
			}
			done <- true
		}(wi)
	}
	for wi := 0; wi < wn; wi++ {
		<-done
	}
}

func (dc *Context) DrawMesh(mesh *Mesh) {
	dc.DrawTriangles(mesh.Triangles)
	dc.DrawLines(mesh.Lines)
}
<|endoftext|>


<commit_before>package agent

import (
	"encoding/json"
	"github.com/mistifyio/kvite"
	"github.com/mistifyio/mistify-agent/client"
	"github.com/mistifyio/mistify-agent/config"
	"github.com/mistifyio/mistify-agent/log"
	"github.com/mistifyio/mistify-agent/rpc"
	"sync"
	"time"
)

type (
	// Context is the core of the Agent.
	Context struct {
		db          *kvite.DB
		ImageClient *rpc.Client
		Config      *config.Config
		Actions     map[string]*Action
		Services    map[string]*Service

		Runners     map[string]*GuestRunner
		RunnerMutex sync.Mutex

		Metrics map[string]*Stage
		// TODO: have a metrics method/channel
	}

	// GuestRunner represents a task runner for a single guest
	GuestRunner struct {
		Context *Context
		ID      string
		nudge   chan struct{} //this "nudges" the runner to run if it's in select
		exit    chan struct{} //tells the runner to exit
		wait    time.Duration //time to wait between runs
	}
)

// NewContext creates a new context. In general, there should only be one.
func NewContext(cfg *config.Config) (*Context, error) {
	ctx := &Context{
		Config:   cfg,
		Actions:  make(map[string]*Action),
		Services: make(map[string]*Service),
		Metrics:  make(map[string]*Stage),
	}

	db, err := kvite.Open(cfg.DBPath, "mistify_agent")
	if err != nil {
		return nil, err
	}
	ctx.db = db
	ctx.ImageClient, err = rpc.NewClient(16000)
	if err != nil {
		return nil, err
	}

	for name, service := range cfg.Services {
		ctx.Services[name], err = ctx.NewService(name, service.Port, service.MaxPending)
		if err != nil {
			return nil, err
		}
	}

	for name, cfgAction := range cfg.Actions {

		action := &Action{
			Name: name,
			ctx:  ctx,
		}

		stages := make([]*Stage, 0, len(cfgAction.Sync))
		for _, stage := range cfgAction.Sync {
			//log.Info(stage.Service)
			//log.Info("%+v\n", ctx.Services[stage.Service])
			stages = append(stages, &Stage{
				Action:  name,
				Service: ctx.Services[stage.Service],
				Method:  stage.Method,
				Args:    stage.Args,
			})
		}

		action.Sync, err = ctx.NewPipeline(stages, name)
		if err != nil {
			return nil, err
		}

		stages = make([]*Stage, 0, len(cfgAction.Async))
		for _, stage := range cfgAction.Async {
			stages = append(stages, &Stage{
				Action:  name,
				Service: ctx.Services[stage.Service],
				Method:  stage.Method,
				Args:    stage.Args,
			})
		}
		action.Async, err = ctx.NewPipeline(stages, name)
		log.Info("async stages: %s %+v", name, stages)
		log.Info("async pipeline: %s %+v", name, action.Async)

		if err != nil {
			return nil, err
		}

		ctx.Actions[name] = action
	}

	for name, cfgMetric := range cfg.Metrics {
		stage := &Stage{
			Action:  name,
			Service: ctx.Services[cfgMetric.Service],
			Method:  cfgMetric.Method,
			Args:    cfgMetric.Args,
		}

		ctx.Metrics[name] = stage
	}

	ctx.Runners = make(map[string]*GuestRunner)

	data, err := json.MarshalIndent(ctx, "   ", " ")
	if err != nil {
		log.Fatal(err)
	}

	log.Info("%s\n", data)

	return ctx, nil
}

// GetGuest fetches a single guest
func (ctx *Context) GetGuest(id string) (*client.Guest, error) {
	var g client.Guest
	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		data, err := b.Get(id)
		if err != nil {
			return err
		}
		if data == nil {
			return NotFound
		}

		return json.Unmarshal(data, &g)
	})
	if err != nil {
		return nil, err
	}
	return &g, nil
}

// RunAction executes the pipeline associated with the current guest action
func (runner *GuestRunner) RunAction() {
	guest, err := runner.Context.GetGuest(runner.ID)
	if err != nil {
		log.Error("GuestRunner: %s => %s", runner.ID, err)
		return
	}

	action := guest.Action
	log.Info("%s: running %s", runner.ID, action)

	guest, err = runner.Context.runAsyncAction(guest)
	if err != nil {
		// this error should get persisted to the guest somehow?
		log.Error("GuestRunner: %s, %s", runner.ID, err)
		return
	}
	//create and delete are special
	// should we allow the stages to change action???
	switch action {
	case "create":
		guest.Action = "run"
		if err := runner.Context.PersistGuest(guest); err != nil {
			// this error should get persisted to the guest somehow?
			log.Error("GuestRunner: %s", runner.ID, err)
		}
	case "delete":
		if err := runner.Context.DeleteGuest(guest); err != nil {
			log.Error("GuestRunner: %s", runner.ID, err)
		}
	}
}

// Run executes the guest loop
func (runner *GuestRunner) Run() {
	for {
		// TODO: be configurable
		select {

		case <-runner.exit:
			break

			// TODO: have a way for stages/pipelines to set this and us set it back??
		case <-time.After(runner.wait):
			runner.RunAction()

		case <-runner.nudge:
			runner.RunAction()
		}
	}
}

// CreateGuestRunner creates a new runner for a guest
func (ctx *Context) CreateGuestRunner(guest *client.Guest) (*GuestRunner, error) {
	ctx.RunnerMutex.Lock()
	defer ctx.RunnerMutex.Unlock()

	runner := &GuestRunner{
		Context: ctx,
		ID:      guest.Id,
		// what should the buffering be here?
		nudge: make(chan struct{}, 4),
		exit:  make(chan struct{}, 1),
		// configurable?
		wait: 5 * time.Second,
	}

	ctx.Runners[guest.Id] = runner
	log.Info("starting %s", guest.Id)
	go runner.Run()

	return runner, nil
}

// RunGuests creates and runs helpers for each defined guest. In general, this should only be called early in a process
// There is no locking provided.
func (ctx *Context) RunGuests() error {
	return ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.ForEach(func(k string, v []byte) error {
			var guest client.Guest
			if err := json.Unmarshal(v, &guest); err != nil {
				// should this be fatal if it just fails on one guest??
				return err
			}
			// TODO: we need to keep track of these...
			_, err := ctx.CreateGuestRunner(&guest)
			if err != nil {
				return err
			}
			return nil
		})
	})
}
<commit_msg>correctly exit runner<commit_after>package agent

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

	"github.com/mistifyio/kvite"
	"github.com/mistifyio/mistify-agent/client"
	"github.com/mistifyio/mistify-agent/config"
	"github.com/mistifyio/mistify-agent/log"
	"github.com/mistifyio/mistify-agent/rpc"
)

type (
	// Context is the core of the Agent.
	Context struct {
		db          *kvite.DB
		ImageClient *rpc.Client
		Config      *config.Config
		Actions     map[string]*Action
		Services    map[string]*Service

		Runners     map[string]*GuestRunner
		RunnerMutex sync.Mutex

		Metrics map[string]*Stage
		// TODO: have a metrics method/channel
	}

	// GuestRunner represents a task runner for a single guest
	GuestRunner struct {
		Context *Context
		ID      string
		nudge   chan struct{} //this "nudges" the runner to run if it's in select
		exit    chan struct{} //tells the runner to exit
		wait    time.Duration //time to wait between runs
	}
)

// NewContext creates a new context. In general, there should only be one.
func NewContext(cfg *config.Config) (*Context, error) {
	ctx := &Context{
		Config:   cfg,
		Actions:  make(map[string]*Action),
		Services: make(map[string]*Service),
		Metrics:  make(map[string]*Stage),
	}

	db, err := kvite.Open(cfg.DBPath, "mistify_agent")
	if err != nil {
		return nil, err
	}
	ctx.db = db
	ctx.ImageClient, err = rpc.NewClient(16000)
	if err != nil {
		return nil, err
	}

	for name, service := range cfg.Services {
		ctx.Services[name], err = ctx.NewService(name, service.Port, service.MaxPending)
		if err != nil {
			return nil, err
		}
	}

	for name, cfgAction := range cfg.Actions {

		action := &Action{
			Name: name,
			ctx:  ctx,
		}

		stages := make([]*Stage, 0, len(cfgAction.Sync))
		for _, stage := range cfgAction.Sync {
			//log.Info(stage.Service)
			//log.Info("%+v\n", ctx.Services[stage.Service])
			stages = append(stages, &Stage{
				Action:  name,
				Service: ctx.Services[stage.Service],
				Method:  stage.Method,
				Args:    stage.Args,
			})
		}

		action.Sync, err = ctx.NewPipeline(stages, name)
		if err != nil {
			return nil, err
		}

		stages = make([]*Stage, 0, len(cfgAction.Async))
		for _, stage := range cfgAction.Async {
			stages = append(stages, &Stage{
				Action:  name,
				Service: ctx.Services[stage.Service],
				Method:  stage.Method,
				Args:    stage.Args,
			})
		}
		action.Async, err = ctx.NewPipeline(stages, name)
		log.Info("async stages: %s %+v", name, stages)
		log.Info("async pipeline: %s %+v", name, action.Async)

		if err != nil {
			return nil, err
		}

		ctx.Actions[name] = action
	}

	for name, cfgMetric := range cfg.Metrics {
		stage := &Stage{
			Action:  name,
			Service: ctx.Services[cfgMetric.Service],
			Method:  cfgMetric.Method,
			Args:    cfgMetric.Args,
		}

		ctx.Metrics[name] = stage
	}

	ctx.Runners = make(map[string]*GuestRunner)

	data, err := json.MarshalIndent(ctx, "   ", " ")
	if err != nil {
		log.Fatal(err)
	}

	log.Info("%s\n", data)

	return ctx, nil
}

// GetGuest fetches a single guest
func (ctx *Context) GetGuest(id string) (*client.Guest, error) {
	var g client.Guest
	err := ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		data, err := b.Get(id)
		if err != nil {
			return err
		}
		if data == nil {
			return NotFound
		}

		return json.Unmarshal(data, &g)
	})
	if err != nil {
		return nil, err
	}
	return &g, nil
}

// RunAction executes the pipeline associated with the current guest action
func (runner *GuestRunner) RunAction() {
	log.Error("GuestRunner: %s start", runner.ID)

	guest, err := runner.Context.GetGuest(runner.ID)
	if err != nil {
		log.Error("GuestRunner: %s => %s", runner.ID, err)
		return
	}

	action := guest.Action
	log.Info("%s: running %s", runner.ID, action)

	guest, err = runner.Context.runAsyncAction(guest)
	if err != nil {
		// this error should get persisted to the guest somehow?
		log.Error("GuestRunner: %s, %s", runner.ID, err)
		return
	}
	//create and delete are special
	// should we allow the stages to change action???
	switch action {
	case "create":
		guest.Action = "run"
		if err := runner.Context.PersistGuest(guest); err != nil {
			// this error should get persisted to the guest somehow?
			log.Error("GuestRunner: %s", runner.ID, err)
		}
	case "delete":
		if err := runner.Context.DeleteGuest(guest); err != nil {
			log.Error("GuestRunner: %s", runner.ID, err)
		}
		runner.Exit()
	}
}

// Run executes the guest loop
func (runner *GuestRunner) Run() {
LOOP:
	for {
		log.Info("run loop %s", runner.ID)

		// TODO: be configurable
		select {

		case <-runner.exit:
			log.Info("run loop time to die %s", runner.ID)
			break LOOP

			// TODO: have a way for stages/pipelines to set this and us set it back??
		case <-time.After(runner.wait):
			runner.RunAction()

		case <-runner.nudge:
			runner.RunAction()
		}
	}
}

// CreateGuestRunner creates a new runner for a guest
func (ctx *Context) CreateGuestRunner(guest *client.Guest) (*GuestRunner, error) {
	ctx.RunnerMutex.Lock()
	defer ctx.RunnerMutex.Unlock()

	runner := &GuestRunner{
		Context: ctx,
		ID:      guest.Id,
		// what should the buffering be here?
		nudge: make(chan struct{}, 4),
		exit:  make(chan struct{}, 1),
		// configurable?
		wait: 5 * time.Second,
	}

	ctx.Runners[guest.Id] = runner
	log.Info("starting %s, %s", guest.Id, guest.Action)
	go runner.Run()

	return runner, nil
}

// RunGuests creates and runs helpers for each defined guest. In general, this should only be called early in a process
// There is no locking provided.
func (ctx *Context) RunGuests() error {
	return ctx.db.Transaction(func(tx *kvite.Tx) error {
		b, err := tx.Bucket("guests")
		if err != nil {
			return err
		}
		return b.ForEach(func(k string, v []byte) error {
			var guest client.Guest
			if err := json.Unmarshal(v, &guest); err != nil {
				// should this be fatal if it just fails on one guest??
				return err
			}
			// TODO: we need to keep track of these...
			_, err := ctx.CreateGuestRunner(&guest)
			if err != nil {
				return err
			}
			return nil
		})
	})
}

func (runner *GuestRunner) Exit() {
	runner.exit <- struct{}{}
}
<|endoftext|>