<commit_before>package manager

import (
	"fmt"
	"net"
	"os"
	"path"
	"sort"

	"github.com/Symantec/Dominator/lib/json"
	"github.com/Symantec/Dominator/lib/net/util"
	proto "github.com/Symantec/Dominator/proto/hypervisor"
)

func getHypervisorSubnet() (proto.Subnet, error) {
	defaultRoute, err := util.GetDefaultRoute()
	if err != nil {
		return proto.Subnet{}, err
	}
	resolverConfig, err := util.GetResolverConfiguration()
	if err != nil {
		return proto.Subnet{}, err
	}
	myIP, err := util.GetMyIP()
	if err != nil {
		return proto.Subnet{}, err
	}
	nameservers := make([]net.IP, 0, len(resolverConfig.Nameservers))
	for _, nameserver := range resolverConfig.Nameservers {
		if nameserver[0] == 127 {
			nameservers = append(nameservers, myIP)
		} else {
			nameservers = append(nameservers, nameserver)
		}
	}
	return proto.Subnet{
		Id:                "hypervisor",
		IpGateway:         defaultRoute.Address,
		IpMask:            net.IP(defaultRoute.Mask),
		DomainNameServers: nameservers,
	}, nil
}

func (m *Manager) addSubnets(subnets []proto.Subnet) error {
	for index := range subnets {
		subnets[index].Shrink()
	}
	if err := m.addSubnetsInternal(subnets); err != nil {
		return err
	}
	for _, subnet := range subnets {
		m.DhcpServer.AddSubnet(subnet)
		for _, ch := range m.subnetChannels {
			ch <- subnet
		}
	}
	return nil
}

func (m *Manager) addSubnetsInternal(subnets []proto.Subnet) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	for _, subnet := range subnets {
		if _, ok := m.subnets[subnet.Id]; ok {
			return fmt.Errorf("subnet: %s already exists", subnet.Id)
		}
	}
	for _, subnet := range subnets {
		m.subnets[subnet.Id] = subnet
	}
	subnetsToWrite := make([]proto.Subnet, 0, len(m.subnets)-1)
	for _, subnet := range m.subnets {
		if subnet.Id != "hypervisor" {
			subnetsToWrite = append(subnetsToWrite, subnet)
		}
	}
	err := json.WriteToFile(path.Join(m.StateDir, "subnets.json"),
		publicFilePerms, "    ", subnetsToWrite)
	if err != nil {
		return err
	}
	m.sendUpdateWithLock(proto.Update{HaveSubnets: true, Subnets: subnets})
	return nil
}

// This must be called with the lock held.
func (m *Manager) getMatchingSubnet(ipAddr net.IP) string {
	for id, subnet := range m.subnets {
		subnetMask := net.IPMask(subnet.IpMask)
		subnetAddr := subnet.IpGateway.Mask(subnetMask)
		if ipAddr.Mask(subnetMask).Equal(subnetAddr) {
			return id
		}
	}
	return ""
}

func (m *Manager) listSubnets(doSort bool) []proto.Subnet {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	subnets := make([]proto.Subnet, 0, len(m.subnets))
	if !doSort {
		for _, subnet := range m.subnets {
			subnets = append(subnets, subnet)
		}
		return subnets
	}
	subnetIDs := make([]string, 0, len(m.subnets))
	for subnetID := range m.subnets {
		subnetIDs = append(subnetIDs, subnetID)
	}
	sort.Strings(subnetIDs)
	for _, subnetID := range subnetIDs {
		subnets = append(subnets, m.subnets[subnetID])
	}
	return subnets
}

// This returns with the Manager locked, waiting for existing subnets to be
// drained from the channel by the caller before unlocking.
func (m *Manager) makeSubnetChannel() <-chan proto.Subnet {
	ch := make(chan proto.Subnet, 1)
	m.mutex.Lock()
	m.subnetChannels = append(m.subnetChannels, ch)
	go func() {
		defer m.mutex.Unlock()
		for _, subnet := range m.subnets {
			ch <- subnet
		}
	}()
	return ch
}

func (m *Manager) loadSubnets() error {
	var subnets []proto.Subnet
	err := json.ReadFromFile(path.Join(m.StateDir, "subnets.json"), &subnets)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	for index := range subnets {
		subnets[index].Shrink()
		m.DhcpServer.AddSubnet(subnets[index])
	}
	m.subnets = make(map[string]proto.Subnet, len(subnets))
	for _, subnet := range subnets {
		m.subnets[subnet.Id] = subnet
	}
	if subnet, err := getHypervisorSubnet(); err != nil {
		return err
	} else {
		m.subnets["hypervisor"] = subnet
	}
	return nil
}
<commit_msg>Fix bug in hypervisor: was only sending new, not all subnets in update.<commit_after>package manager

import (
	"fmt"
	"net"
	"os"
	"path"
	"sort"

	"github.com/Symantec/Dominator/lib/json"
	"github.com/Symantec/Dominator/lib/net/util"
	proto "github.com/Symantec/Dominator/proto/hypervisor"
)

func getHypervisorSubnet() (proto.Subnet, error) {
	defaultRoute, err := util.GetDefaultRoute()
	if err != nil {
		return proto.Subnet{}, err
	}
	resolverConfig, err := util.GetResolverConfiguration()
	if err != nil {
		return proto.Subnet{}, err
	}
	myIP, err := util.GetMyIP()
	if err != nil {
		return proto.Subnet{}, err
	}
	nameservers := make([]net.IP, 0, len(resolverConfig.Nameservers))
	for _, nameserver := range resolverConfig.Nameservers {
		if nameserver[0] == 127 {
			nameservers = append(nameservers, myIP)
		} else {
			nameservers = append(nameservers, nameserver)
		}
	}
	return proto.Subnet{
		Id:                "hypervisor",
		IpGateway:         defaultRoute.Address,
		IpMask:            net.IP(defaultRoute.Mask),
		DomainNameServers: nameservers,
	}, nil
}

func (m *Manager) addSubnets(subnets []proto.Subnet) error {
	for index := range subnets {
		subnets[index].Shrink()
	}
	if err := m.addSubnetsInternal(subnets); err != nil {
		return err
	}
	for _, subnet := range subnets {
		m.DhcpServer.AddSubnet(subnet)
		for _, ch := range m.subnetChannels {
			ch <- subnet
		}
	}
	return nil
}

func (m *Manager) addSubnetsInternal(subnets []proto.Subnet) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	for _, subnet := range subnets {
		if _, ok := m.subnets[subnet.Id]; ok {
			return fmt.Errorf("subnet: %s already exists", subnet.Id)
		}
	}
	for _, subnet := range subnets {
		m.subnets[subnet.Id] = subnet
	}
	subnetsToWrite := make([]proto.Subnet, 0, len(m.subnets)-1)
	for _, subnet := range m.subnets {
		if subnet.Id != "hypervisor" {
			subnetsToWrite = append(subnetsToWrite, subnet)
		}
	}
	err := json.WriteToFile(path.Join(m.StateDir, "subnets.json"),
		publicFilePerms, "    ", subnetsToWrite)
	if err != nil {
		return err
	}
	m.sendUpdateWithLock(
		proto.Update{HaveSubnets: true, Subnets: subnetsToWrite})
	return nil
}

// This must be called with the lock held.
func (m *Manager) getMatchingSubnet(ipAddr net.IP) string {
	for id, subnet := range m.subnets {
		subnetMask := net.IPMask(subnet.IpMask)
		subnetAddr := subnet.IpGateway.Mask(subnetMask)
		if ipAddr.Mask(subnetMask).Equal(subnetAddr) {
			return id
		}
	}
	return ""
}

func (m *Manager) listSubnets(doSort bool) []proto.Subnet {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	subnets := make([]proto.Subnet, 0, len(m.subnets))
	if !doSort {
		for _, subnet := range m.subnets {
			subnets = append(subnets, subnet)
		}
		return subnets
	}
	subnetIDs := make([]string, 0, len(m.subnets))
	for subnetID := range m.subnets {
		subnetIDs = append(subnetIDs, subnetID)
	}
	sort.Strings(subnetIDs)
	for _, subnetID := range subnetIDs {
		subnets = append(subnets, m.subnets[subnetID])
	}
	return subnets
}

// This returns with the Manager locked, waiting for existing subnets to be
// drained from the channel by the caller before unlocking.
func (m *Manager) makeSubnetChannel() <-chan proto.Subnet {
	ch := make(chan proto.Subnet, 1)
	m.mutex.Lock()
	m.subnetChannels = append(m.subnetChannels, ch)
	go func() {
		defer m.mutex.Unlock()
		for _, subnet := range m.subnets {
			ch <- subnet
		}
	}()
	return ch
}

func (m *Manager) loadSubnets() error {
	var subnets []proto.Subnet
	err := json.ReadFromFile(path.Join(m.StateDir, "subnets.json"), &subnets)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	for index := range subnets {
		subnets[index].Shrink()
		m.DhcpServer.AddSubnet(subnets[index])
	}
	m.subnets = make(map[string]proto.Subnet, len(subnets))
	for _, subnet := range subnets {
		m.subnets[subnet.Id] = subnet
	}
	if subnet, err := getHypervisorSubnet(); err != nil {
		return err
	} else {
		m.subnets["hypervisor"] = subnet
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"log"
	"os"
	"regexp"
	"strings"
)

const (
	functionDeclarationPattern = `func ([a-zA-Z]+)\(`
)

func failIfErr(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func findFunctionsInFile(f *os.File) []string {
	funcValidator := regexp.MustCompile(functionDeclarationPattern)

	functionNames := []string{}
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		if funcValidator.MatchString(line) {
			submatches := funcValidator.FindStringSubmatch(line)
			functionName := submatches[1]
			if functionName != `init` {
				functionNames = append(functionNames, functionName)
			}
		}
	}
	return functionNames
}

func findFunctionReferencesInTestFile(f *os.File, functionNames []string) {
	scanner := bufio.NewScanner(f)
	lineNumber := 0
	for scanner.Scan() {
		lineNumber++
		line := scanner.Text()

		for _, name := range functionNames {
			if strings.Contains(line, name) {
				log.Printf("function %s referenced on line %d of %s\n", name, lineNumber, f.Name())
				// delete that value from the slice here
			}
		}
	}
}

func main() {
	codeFilesToTestFilesMap := map[string]string{
		"api/helpers.go":                  "api/helpers_test.go",
		"api/product_attribute_values.go": "api/product_attribute_values_test.go",
		"api/product_attributes.go":       "api/product_attributes_test.go",
		"api/product_progenitors.go":      "api/product_progenitors_test.go",
		"api/products.go":                 "api/products_test.go",
		"api/queries.go":                  "api/queries_test.go",
	}
	functionsInEachFile := map[string][]string{}

	for codeFile, testFile := range codeFilesToTestFilesMap {
		cf, err := os.Open(codeFile)
		failIfErr(err)

		functionNames := findFunctionsInFile(cf)
		functionsInEachFile[codeFile] = functionNames

		tf, err := os.Open(testFile)
		failIfErr(err)
		findFunctionReferencesInTestFile(tf, functionNames)
	}

	log.Printf(`
	functionsInEachFile: %+v
	`, functionsInEachFile)
}
<commit_msg>"fix" tool for test coverage discovery.<commit_after>package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"regexp"
	"strings"
)

const (
	functionDeclarationPattern = `func ([a-zA-Z]+)\(`
)

func failIfErr(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func findFunctionsInFile(f *os.File) []string {
	funcValidator := regexp.MustCompile(functionDeclarationPattern)

	functionNames := []string{}
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		if funcValidator.MatchString(line) {
			submatches := funcValidator.FindStringSubmatch(line)
			functionName := submatches[1]
			if functionName != `init` {
				functionNames = append(functionNames, functionName)
			}
		}
	}
	return functionNames
}

func sliceIndex(item string, slice []string) int {
	for i, v := range slice {
		if v == item {
			return i
		}
	}
	// this should never happen at all
	return -1
}

func findFunctionReferencesInTestFile(f *os.File, functionNames []string) []string {
	scanner := bufio.NewScanner(f)
	lineNumber := 0
	for scanner.Scan() {
		lineNumber++
		line := scanner.Text()

		for _, name := range functionNames {
			if strings.Contains(line, name) {
				i := sliceIndex(name, functionNames)
				functionNames = append(functionNames[:i], functionNames[i+1:]...)
				break
			}
		}
	}
	return functionNames
}

func main() {
	codeFilesToTestFilesMap := map[string]string{
		"api/helpers.go":                  "api/helpers_test.go",
		"api/product_attribute_values.go": "api/product_attribute_values_test.go",
		"api/product_attributes.go":       "api/product_attributes_test.go",
		"api/product_progenitors.go":      "api/product_progenitors_test.go",
		"api/products.go":                 "api/products_test.go",
		"api/queries.go":                  "api/queries_test.go",
	}
	functionsInEachFile := map[string][]string{}

	for codeFile, testFile := range codeFilesToTestFilesMap {
		cf, err := os.Open(codeFile)
		failIfErr(err)

		functionNames := findFunctionsInFile(cf)
		functionsInEachFile[codeFile] = functionNames

		tf, err := os.Open(testFile)
		failIfErr(err)
		remainingFunctionNames := findFunctionReferencesInTestFile(tf, functionNames)

		if len(remainingFunctionNames) > 0 {
			fmt.Printf("untested functions in %s:\n\t%s\n\n", testFile, strings.Join(remainingFunctionNames, "\n\t"))
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Jason Ish. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above
//    copyright notice, this list of conditions and the following
//    disclaimer in the documentation and/or other materials provided
//    with the distribution.
//
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package dumper

import (
	"net/http"
	"strconv"
	"log"
	"os"
	"os/exec"
	"bufio"
	"io"
	"fmt"
)

// DumperProxy - proxies the output from
type DumperProxy struct {
	Options  DumperOptions
	Writer   http.ResponseWriter
	Filename string
}

func (dp *DumperProxy) BuildArgs() []string {
	args := []string{"dump"}

	if dp.Options.Directory != "" {
		args = append(args, "-directory")
		args = append(args, dp.Options.Directory)
	}

	if dp.Options.Prefix != "" {
		args = append(args, "-prefix")
		args = append(args, dp.Options.Prefix)
	}

	args = append(args, []string{"-start-time",
		strconv.FormatInt(dp.Options.StartTime, 10)}...)

	if dp.Options.Duration > 0 {
		args = append(args, "-duration")
		args = append(args, strconv.FormatInt(dp.Options.Duration, 10))
	}

	if dp.Options.Filter != "" {
		args = append(args, "-filter")
		args = append(args, dp.Options.Filter)
	}

	if dp.Options.Recursive {
		args = append(args, "-recursive")
	}

	args = append(args, "-verbose")

	return args
}

func (dp *DumperProxy) Run() {

	args := dp.BuildArgs()

	dumper := exec.Command(os.Args[0], args...)

	stderr, err := dumper.StderrPipe()
	if err != nil {
		log.Println(err)
		return
	}

	stdout, err := dumper.StdoutPipe()
	if err != nil {
		log.Println(err)
		return
	}

	// Handle stderr output in a go routine.
	go func() {
		reader := bufio.NewReader(stderr)
		for {
			line, err := reader.ReadString('\n')
			if err != nil {
				log.Println(err)
				break;
			}
			log.Printf("dumpy dump (stderr): %s", line)
		}
	}()

	err = dumper.Start()
	log.Println(err)

	bytesWritten := 0
	buf := make([]byte, 8192)

	for {
		n, err := stdout.Read(buf)
		if err != nil {
			if err != io.EOF {
				log.Printf("error reading from stdin: %s", err.Error())
				if bytesWritten == 0 {
					http.Error(dp.Writer, err.Error(), http.StatusInternalServerError)
				}
			}
			break
		}

		if bytesWritten == 0 {
			dp.Writer.Header().Add("content-type", "application/vnd.tcpdump.pcap")
			dp.Writer.Header().Add("content-disposition",
				fmt.Sprintf("attachment; filename=%s", dp.Filename))
		}

		m, err := dp.Writer.Write(buf[0:n])
		if err != nil {
			log.Printf("Failed to write to http client: %v", err.Error())
		}
		if m != n {
			log.Printf("Didn't write all the bytes??")
		}

		bytesWritten += n

	}

	if bytesWritten == 0 {
		http.Error(dp.Writer, "No packets found.", http.StatusNotFound)
	}

	log.Printf("Wrote %d bytes of pcap data.", bytesWritten)
}<commit_msg>wait for dumper process, prevents zombies<commit_after>// Copyright (c) 2016 Jason Ish. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above
//    copyright notice, this list of conditions and the following
//    disclaimer in the documentation and/or other materials provided
//    with the distribution.
//
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package dumper

import (
	"net/http"
	"strconv"
	"log"
	"os"
	"os/exec"
	"bufio"
	"io"
	"fmt"
)

// DumperProxy - proxies the output from
type DumperProxy struct {
	Options  DumperOptions
	Writer   http.ResponseWriter
	Filename string
}

func (dp *DumperProxy) BuildArgs() []string {
	args := []string{"dump"}

	if dp.Options.Directory != "" {
		args = append(args, "-directory")
		args = append(args, dp.Options.Directory)
	}

	if dp.Options.Prefix != "" {
		args = append(args, "-prefix")
		args = append(args, dp.Options.Prefix)
	}

	args = append(args, []string{"-start-time",
		strconv.FormatInt(dp.Options.StartTime, 10)}...)

	if dp.Options.Duration > 0 {
		args = append(args, "-duration")
		args = append(args, strconv.FormatInt(dp.Options.Duration, 10))
	}

	if dp.Options.Filter != "" {
		args = append(args, "-filter")
		args = append(args, dp.Options.Filter)
	}

	if dp.Options.Recursive {
		args = append(args, "-recursive")
	}

	args = append(args, "-verbose")

	return args
}

func (dp *DumperProxy) Run() {

	args := dp.BuildArgs()

	dumper := exec.Command(os.Args[0], args...)

	stderr, err := dumper.StderrPipe()
	if err != nil {
		log.Println(err)
		return
	}

	stdout, err := dumper.StdoutPipe()
	if err != nil {
		log.Println(err)
		return
	}

	// Handle stderr output in a go routine.
	go func() {
		reader := bufio.NewReader(stderr)
		for {
			line, err := reader.ReadString('\n')
			if err != nil {
				log.Println(err)
				break;
			}
			log.Printf("dumpy dump (stderr): %s", line)
		}
	}()

	err = dumper.Start()
	log.Println(err)

	bytesWritten := 0
	buf := make([]byte, 8192)

	for {
		n, err := stdout.Read(buf)
		if err != nil {
			if err != io.EOF {
				log.Printf("error reading from stdin: %s", err.Error())
				if bytesWritten == 0 {
					http.Error(dp.Writer, err.Error(), http.StatusInternalServerError)
				}
			}
			break
		}

		if bytesWritten == 0 {
			dp.Writer.Header().Add("content-type", "application/vnd.tcpdump.pcap")
			dp.Writer.Header().Add("content-disposition",
				fmt.Sprintf("attachment; filename=%s", dp.Filename))
		}

		m, err := dp.Writer.Write(buf[0:n])
		if err != nil {
			log.Printf("Failed to write to http client: %v", err.Error())
		}
		if m != n {
			log.Printf("Didn't write all the bytes??")
		}

		bytesWritten += n

	}

	if bytesWritten == 0 {
		http.Error(dp.Writer, "No packets found.", http.StatusNotFound)
	}

	dumper.Wait();

	log.Printf("Wrote %d bytes of pcap data.", bytesWritten)
}<|endoftext|>


<commit_before>package server

import (
    "github.com/itsankoff/gotcha/common"
    "log"
)

type Control struct {
    input chan *common.Message
}

func NewControl(input chan *common.Message) *Control {
    c := &Control{
        input: input,
    }

    go c.listen()
    return c
}

func (c Control) listen() {
    select {
    case msg := <-c.input:
        log.Println("Control received", msg)
        valid := c.validate(msg)
        if valid {
            cmd := msg.Cmd()
            switch(cmd) {
            case "register":
            case "auth":
            case "list_contacts":
            case "add_contact":
            case "remove_contact":
            case "create_group":
            case "add_to_group":
            case "remove_from_group":
            case "delete_group":
            case "list_groups":
            case "join_group":
            case "leave_group":
            default:
                log.Println("Unknown control command", cmd)
            }
        } else {
            log.Println("Invalid control message", msg)
        }
    }
}

func (c Control) validate(msg *common.Message) bool {
    return true
}

func (c Control) RegisterUser(user string, password string) *common.User {
    return nil
}

func (c Control) AuthUser(user string, password string) bool {
    return false
}

func (c Control) ListContacts(user string) []*common.User {
    return nil
}

func (c Control) AddContact(user *common.User, contact *common.User) bool {
    return false
}

func (c Control) RemoveContact(user *common.User, contact *common.User) bool {
    return false
}

func (c Control) CreateGroup(user *common.User) (groupId string) {
    return
}

func (c Control) AddToGroup(user *common.User, groupId string) bool {
    return false
}

func (c Control) RemoveFromGroup(user *common.User, groupId string) bool {
    return false
}

func (c Control) DeleteGroup(groupId string) bool {
    return false
}

func (c Control) ListGroups(user *common.User) *[]string {
    return &[]string{}
}

func (c Control) JoinGroup(user *common.User, groupId string) bool {
    return false
}

func (c Control) LeaveGroup(user *common.User, groupId string) bool {
    return false
}
<commit_msg>Implement groups managment<commit_after>package server

import (
    "github.com/itsankoff/gotcha/common"
    "log"
    "encoding/json"
)

type Control struct {
    input           chan *common.Message
    groups          []*Group
    outputStore     *OutputStore
}

func NewControl(input chan *common.Message,
                outputStore *OutputStore) *Control {
    c := &Control{
        input: input,
        outputStore: outputStore,
    }

    go c.listen()
    return c
}

func (c Control) listen() {
    select {
    case msg := <-c.input:
        log.Println("Control received", msg)

        valid := c.validate(msg)
        if valid {
            var payload map[string]interface{}
            err := json.Unmarshal(msg.Raw(), &payload)
            if err == nil {
                cmd := msg.Cmd()
                switch(cmd) {
                case "register":
                case "auth":
                case "list_contacts":
                case "add_contact":
                case "remove_contact":
                case "create_group":
                    groupId := c.CreateGroup()
                    c.AddToGroup(groupId, msg.From())
                case "add_to_group":
                    groupId := payload["group_id"]
                    userId := payload["user_id"]
                    c.AddToGroup(groupId.(string), userId.(string))
                case "remove_from_group":
                    groupId := payload["group_id"].(string)
                    userId := payload["user_id"].(string)
                    c.RemoveFromGroup(groupId, userId)
                case "delete_group":
                    groupId := payload["group_id"].(string)
                    c.DeleteGroup(groupId)
                case "list_groups":
                case "join_group":
                case "leave_group":
                default:
                    log.Println("Unknown control command", cmd)
                }
            } else {
                log.Println("Failed to decode control message payload", msg)
            }
        } else {
            log.Println("Invalid control message", msg)
        }
    }
}

func (c Control) validate(msg *common.Message) bool {
    return true
}

func (c Control) findGroup(groupId string) *Group {
    for _, g := range c.groups {
        if g.Id == groupId {
            return g
        }
    }

    return nil
}

func (c Control) RegisterUser(user string, password string) *common.User {
    return nil
}

func (c Control) AuthUser(user string, password string) bool {
    return false
}

func (c Control) ListContacts(user string) []*common.User {
    return nil
}

func (c Control) AddContact(user *common.User, contact *common.User) bool {
    return false
}

func (c Control) RemoveContact(user *common.User, contact *common.User) bool {
    return false
}

func (c *Control) CreateGroup() string {
    group := NewGroup()
    c.groups = append(c.groups, group)
    c.outputStore.AddOutput(group.Id, group.Out)
    return group.Id
}

func (c Control) AddToGroup(groupId string, userId string) bool {
    group := c.findGroup(groupId)
    if group == nil {
        log.Println("Failed to add user. No group with id", groupId, userId)
        return false
    }

    userOutput := c.outputStore.GetOutput(userId)
    if userOutput == nil {
        log.Println("Failed to add user to group. No user output",
                    groupId, userId)
        return false
    }

    return group.AddOutput(userId, userOutput)
}

func (c Control) RemoveFromGroup(user string, groupId string) bool {
    group := c.findGroup(groupId)
    if group == nil {
        log.Println("Failed to remove user. No group with id", groupId, user)
        return false
    }

    return group.RemoveOutput(user)
}

func (c *Control) DeleteGroup(groupId string) bool {
    for i, group:= range c.groups {
        if group.Id == groupId {
            c.groups = append(c.groups[:i], c.groups[i+1:]...)
            c.outputStore.RemoveOutput(group.Id)
            close(group.Out)
            return true
        }
    }

    return false
}

func (c Control) ListGroups(user *common.User) *[]string {
    groupIds := []string{}
    for _, g := range c.groups {
        groupIds = append(groupIds, g.Id)
    }

    return &groupIds
}
<|endoftext|>


<commit_before>package main

import (
	"math"
	"sort"
	"strconv"
	"time"

	m "github.com/CasualSuperman/Diorite/multiverse"
)

const setReleaseFormat = "2006-01-02"

// Convert to a Multiverse.
func (om onlineMultiverse) Convert() (mv m.Multiverse) {
	mv.Sets = make([]m.Set, len(om.Sets))
	mv.Modified = om.Modified

	i := 0

	for _, jSet := range om.Sets {
		mv.Sets[i] = jSet.Set()
		i++
	}

	s := setSorter{
		mv.Sets,
		releaseDateSort,
	}

	sort.Sort(s)

	cardIndexCache := make(map[string]int)

	for _, set := range om.Sets {
		var setIndex int

		for i, s := range mv.Sets {
			if s.Name == set.Name {
				setIndex = i
				break
			}
		}

		for _, jCard := range set.Cards {
			var rarity m.Rarity
			switch jCard.Rarity {
			case "Common":
				rarity = m.Rarities.Common
			case "Uncommon":
				rarity = m.Rarities.Uncommon
			case "Rare":
				rarity = m.Rarities.Rare
			case "Mythic Rare":
				rarity = m.Rarities.Mythic
			case "Special":
				rarity = m.Rarities.Special
			case "Basic Land":
				rarity = m.Rarities.Basic
			}

			var printing = m.Printing{
				m.MultiverseID(jCard.MultiverseID),
				&mv.Sets[setIndex],
				rarity,
			}

			index, ok := cardIndexCache[jCard.Name]

			if ok {
				jCard := &mv.Cards[index]
				jCard.Printings = append(jCard.Printings, printing)
			} else {
				c := jCard.Card()
				c.Printings = []m.Printing{printing}
				i := len(mv.Cards)
				cardIndexCache[jCard.Name] = i

				if i < cap(mv.Cards) {
					mv.Cards = mv.Cards[:i+1]
				} else {
					newCards := make([]m.Card, i+1, (i+1)*2)
					copy(newCards, mv.Cards)
					mv.Cards = newCards
				}
				mv.Cards[i] = *c
			}
		}
	}

	newCards := make([]m.Card, len(mv.Cards))
	copy(newCards, mv.Cards)
	mv.Cards = newCards

	return
}

func (jc *jsonCard) Card() *m.Card {
	c := new(m.Card)
	c.Name = jc.Name
	c.Cmc = jc.Cmc
	c.Cost = jc.ManaCost

	for _, color := range jc.Colors {
		switch color {
		case "White":
			c.Colors |= m.ManaColors.White
		case "Blue":
			c.Colors |= m.ManaColors.Blue
		case "Black":
			c.Colors |= m.ManaColors.Black
		case "Red":
			c.Colors |= m.ManaColors.Red
		case "Green":
			c.Colors |= m.ManaColors.Green
		}
	}

	for _, SuperType := range jc.Supertypes {
		switch SuperType {
		case "Basic":
			c.Supertypes |= m.SuperTypes.Basic
		case "Elite":
			c.Supertypes |= m.SuperTypes.Elite
		case "Legendary":
			c.Supertypes |= m.SuperTypes.Legendary
		case "Ongoing":
			c.Supertypes |= m.SuperTypes.Ongoing
		case "Snow":
			c.Supertypes |= m.SuperTypes.Snow
		case "World":
			c.Supertypes |= m.SuperTypes.World
		}
	}

	for _, Type := range jc.Types {
		switch Type {
		case "Artifact":
			c.Types |= m.Types.Artifact
		case "Creature":
			c.Types |= m.Types.Creature
		case "Enchantment":
			c.Types |= m.Types.Enchantment
		case "Instant":
			c.Types |= m.Types.Instant
		case "Land":
			c.Types |= m.Types.Land
		case "Planeswalker":
			c.Types |= m.Types.Planeswalker
		case "Sorcery":
			c.Types |= m.Types.Sorcery
		case "Tribal":
			c.Types |= m.Types.Tribal
		}
	}

	c.Subtypes = jc.Subtypes

	c.Text = jc.Text
	c.Flavor = jc.Flavor
	c.Artist = jc.Artist
	c.Number = jc.Number

	c.Rulings = make([]m.Ruling, len(jc.Rulings))

	for i, ruling := range jc.Rulings {
		c.Rulings[i].Text = ruling.Text
		c.Rulings[i].Date, _ = time.Parse("2006-01-02", ruling.Date)
	}

	power, err := strconv.ParseFloat(jc.Power, 32)

	if err == nil {
		c.Power.Val = float32(power)
	} else {
		c.Power.Val = float32(math.NaN())
		if jc.Power != "" {
			c.Power.Original = jc.Power
		}
	}

	toughness, err := strconv.ParseFloat(jc.Toughness, 32)

	if err == nil {
		c.Toughness.Val = float32(toughness)
	} else {
		c.Toughness.Val = float32(math.NaN())
		if jc.Toughness != "" {
			c.Toughness.Original = jc.Toughness
		}
	}

	c.Loyalty = jc.Loyalty

	return c
}

func (js *jsonSet) Set() m.Set {
	s := m.Set{
		Name:  js.Name,
		Code:  js.Code,
		Block: js.Block,
	}
	s.Released, _ = time.Parse(setReleaseFormat, js.ReleaseDate)

	switch js.Border {
	case "black":
		s.Border = m.BorderColors.Black
	case "white":
		s.Border = m.BorderColors.White
	case "silver":
		s.Border = m.BorderColors.Silver
	}

	switch js.Type {
	case "core":
		s.Type = m.SetTypes.Core
	case "expansion":
		s.Type = m.SetTypes.Expansion
	case "reprint":
		s.Type = m.SetTypes.Reprint
	case "box":
		s.Type = m.SetTypes.Box
	case "un":
		s.Type = m.SetTypes.Un
	case "from the vault":
		s.Type = m.SetTypes.FromTheVault
	case "premium deck":
		s.Type = m.SetTypes.PremiumDeck
	case "duel deck":
		s.Type = m.SetTypes.DuelDeck
	case "starter":
		s.Type = m.SetTypes.Starter
	case "commander":
		s.Type = m.SetTypes.Commander
	case "planechase":
		s.Type = m.SetTypes.Planechase
	case "archenemy":
		s.Type = m.SetTypes.Archenemy
	}

	s.Cards = make([]m.MultiverseID, len(js.Cards))

	i := 0
	for _, card := range js.Cards {
		s.Cards[i] = m.MultiverseID(card.MultiverseID)
		i++
	}

	return s
}
<commit_msg>Sanitize JSON; I was getting odd null characters.<commit_after>package main

import (
	"math"
	"sort"
	"strconv"
	"strings"
	"time"

	m "github.com/CasualSuperman/Diorite/multiverse"
)

const setReleaseFormat = "2006-01-02"

func sanitize(s string) string {
	s = strings.Map(func(r rune) rune {
		if r == 0 {
			return -1
		}
		return r
	}, s)
	return s
}

// Convert to a Multiverse.
func (om onlineMultiverse) Convert() (mv m.Multiverse) {
	mv.Sets = make([]m.Set, len(om.Sets))
	mv.Modified = om.Modified

	i := 0

	for _, jSet := range om.Sets {
		mv.Sets[i] = jSet.Set()
		i++
	}

	s := setSorter{
		mv.Sets,
		releaseDateSort,
	}

	sort.Sort(s)

	cardIndexCache := make(map[string]int)

	for _, set := range om.Sets {
		var setIndex int

		for i, s := range mv.Sets {
			if s.Name == set.Name {
				setIndex = i
				break
			}
		}

		for _, jCard := range set.Cards {
			var rarity m.Rarity
			switch jCard.Rarity {
			case "Common":
				rarity = m.Rarities.Common
			case "Uncommon":
				rarity = m.Rarities.Uncommon
			case "Rare":
				rarity = m.Rarities.Rare
			case "Mythic Rare":
				rarity = m.Rarities.Mythic
			case "Special":
				rarity = m.Rarities.Special
			case "Basic Land":
				rarity = m.Rarities.Basic
			default:
				panic("unknown rarity")
			}

			var printing = m.Printing{
				m.MultiverseID(jCard.MultiverseID),
				&mv.Sets[setIndex],
				rarity,
			}

			index, ok := cardIndexCache[jCard.Name]

			if ok {
				jCard := &mv.Cards[index]
				jCard.Printings = append(jCard.Printings, printing)
			} else {
				c := jCard.Card()
				c.Printings = []m.Printing{printing}
				i := len(mv.Cards)
				cardIndexCache[jCard.Name] = i

				if i < cap(mv.Cards) {
					mv.Cards = mv.Cards[:i+1]
				} else {
					newCards := make([]m.Card, i+1, (i+1)*2)
					copy(newCards, mv.Cards)
					mv.Cards = newCards
				}
				mv.Cards[i] = *c
			}
		}
	}

	newCards := make([]m.Card, len(mv.Cards))
	copy(newCards, mv.Cards)
	mv.Cards = newCards

	return
}

func (jc *jsonCard) Card() *m.Card {
	c := new(m.Card)
	c.Name = sanitize(jc.Name)
	c.Cmc = jc.Cmc
	c.Cost = jc.ManaCost

	for _, color := range jc.Colors {
		switch color {
		case "White":
			c.Colors |= m.ManaColors.White
		case "Blue":
			c.Colors |= m.ManaColors.Blue
		case "Black":
			c.Colors |= m.ManaColors.Black
		case "Red":
			c.Colors |= m.ManaColors.Red
		case "Green":
			c.Colors |= m.ManaColors.Green
		}
	}

	for _, SuperType := range jc.Supertypes {
		switch SuperType {
		case "Basic":
			c.Supertypes |= m.SuperTypes.Basic
		case "Elite":
			c.Supertypes |= m.SuperTypes.Elite
		case "Legendary":
			c.Supertypes |= m.SuperTypes.Legendary
		case "Ongoing":
			c.Supertypes |= m.SuperTypes.Ongoing
		case "Snow":
			c.Supertypes |= m.SuperTypes.Snow
		case "World":
			c.Supertypes |= m.SuperTypes.World
		}
	}

	for _, Type := range jc.Types {
		switch Type {
		case "Artifact":
			c.Types |= m.Types.Artifact
		case "Creature":
			c.Types |= m.Types.Creature
		case "Enchantment":
			c.Types |= m.Types.Enchantment
		case "Instant":
			c.Types |= m.Types.Instant
		case "Land":
			c.Types |= m.Types.Land
		case "Planeswalker":
			c.Types |= m.Types.Planeswalker
		case "Sorcery":
			c.Types |= m.Types.Sorcery
		case "Tribal":
			c.Types |= m.Types.Tribal
		}
	}

	c.Subtypes = jc.Subtypes

	c.Text = sanitize(jc.Text)
	c.Flavor = sanitize(jc.Flavor)
	c.Artist = sanitize(jc.Artist)
	c.Number = jc.Number

	c.Rulings = make([]m.Ruling, len(jc.Rulings))

	for i, ruling := range jc.Rulings {
		c.Rulings[i].Text = sanitize(ruling.Text)
		c.Rulings[i].Date, _ = time.Parse("2006-01-02", ruling.Date)
	}

	power, err := strconv.ParseFloat(jc.Power, 32)

	if err == nil {
		c.Power.Val = float32(power)
	} else {
		c.Power.Val = float32(math.NaN())
		if jc.Power != "" {
			c.Power.Original = jc.Power
		}
	}

	toughness, err := strconv.ParseFloat(jc.Toughness, 32)

	if err == nil {
		c.Toughness.Val = float32(toughness)
	} else {
		c.Toughness.Val = float32(math.NaN())
		if jc.Toughness != "" {
			c.Toughness.Original = jc.Toughness
		}
	}

	c.Loyalty = jc.Loyalty

	return c
}

func (js *jsonSet) Set() m.Set {
	s := m.Set{
		Name:  js.Name,
		Code:  js.Code,
		Block: js.Block,
	}
	s.Released, _ = time.Parse(setReleaseFormat, js.ReleaseDate)

	switch js.Border {
	case "black":
		s.Border = m.BorderColors.Black
	case "white":
		s.Border = m.BorderColors.White
	case "silver":
		s.Border = m.BorderColors.Silver
	}

	switch js.Type {
	case "core":
		s.Type = m.SetTypes.Core
	case "expansion":
		s.Type = m.SetTypes.Expansion
	case "reprint":
		s.Type = m.SetTypes.Reprint
	case "box":
		s.Type = m.SetTypes.Box
	case "un":
		s.Type = m.SetTypes.Un
	case "from the vault":
		s.Type = m.SetTypes.FromTheVault
	case "premium deck":
		s.Type = m.SetTypes.PremiumDeck
	case "duel deck":
		s.Type = m.SetTypes.DuelDeck
	case "starter":
		s.Type = m.SetTypes.Starter
	case "commander":
		s.Type = m.SetTypes.Commander
	case "planechase":
		s.Type = m.SetTypes.Planechase
	case "archenemy":
		s.Type = m.SetTypes.Archenemy
	}

	s.Cards = make([]m.MultiverseID, len(js.Cards))

	i := 0
	for _, card := range js.Cards {
		s.Cards[i] = m.MultiverseID(card.MultiverseID)
		i++
	}

	return s
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"net/http"
	"strings"
)

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	//s.handlermutex.Lock()
	//defer s.handlermutex.Unlock()

	var i int

	if !s.closed {
		s.clmutex.Lock()
		clients := s.clientlist
		s.clmutex.Unlock()

		for i = range clients {
			nick, ok := clients[i]["client_nickname"]
			if ok {
				if strings.Contains(clients[i]["client_type"], "0") {
					fmt.Fprintln(w, "
", nick, "
")
				}
			} else {
				fmt.Fprintln(w, "error: ", clients[i], ", empty map")
				if s.logger != nil {
					s.logger.Print("error: " + fmt.Sprint(clients[i]))
				}
			}
		}
		if i == 0 {
			fmt.Fprintln(w, "
No one is online right now.
")
		}
	} else {
		http.Error(w, "internal error", 500)
	}

	return
}
<commit_msg>changing for loop in ServerHTTP<commit_after>package server

import (
	"fmt"
	"net/http"
	"strings"
)

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	//s.handlermutex.Lock()
	//defer s.handlermutex.Unlock()

	var i int

	if !s.closed {
		s.clmutex.Lock()
		clients := s.clientlist
		s.clmutex.Unlock()

		for _, client := range clients {
			nick, ok := client["client_nickname"]
			if ok {
				if strings.Contains(client["client_type"], "0") {
					fmt.Fprintln(w, "
", nick, "
")
				}
			} else {
				fmt.Fprintln(w, "error: ", client, ", empty map")
				if s.logger != nil {
					s.logger.Print("error: " + fmt.Sprint(client))
				}
			}
		}
		if i == 0 {
			fmt.Fprintln(w, "
No one is online right now.
")
		}
	} else {
		http.Error(w, "internal error", 500)
	}

	return
}
<|endoftext|>


<commit_before>//go:generate esc -o static.go -prefix ../web/build -pkg server ../web/build
package server

import (
	"encoding/hex"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/cardigann/cardigann/config"
	"github.com/cardigann/cardigann/indexer"
	"github.com/cardigann/cardigann/logger"
	"github.com/cardigann/cardigann/torznab"
	"github.com/gorilla/mux"
)

const (
	buildDir = "/web/build"
)

var (
	log              = logger.Logger
	apiRoutePrefixes = []string{
		"/torznab/",
		"/download/",
		"/xhr/",
	}
)

type Params struct {
	BaseURL    string
	APIKey     []byte
	Passphrase string
	Config     config.Config
}

type handler struct {
	http.Handler
	Params      Params
	FileHandler http.Handler
	indexers    map[string]torznab.Indexer
}

func NewHandler(p Params) (http.Handler, error) {
	h := &handler{
		Params:      p,
		FileHandler: http.FileServer(FS(false)),
		indexers:    map[string]torznab.Indexer{},
	}

	router := mux.NewRouter()

	// torznab routes
	router.HandleFunc("/torznab/{indexer}", h.torznabHandler).Methods("GET")
	router.HandleFunc("/torznab/{indexer}/api", h.torznabHandler).Methods("GET")
	router.HandleFunc("/download/{token}/{filename}", h.downloadHandler).Methods("GET")

	// xhr routes for the webapp
	router.HandleFunc("/xhr/indexers/{indexer}/test", h.getIndexerTestHandler).Methods("GET")
	router.HandleFunc("/xhr/indexers/{indexer}/config", h.getIndexersConfigHandler).Methods("GET")
	router.HandleFunc("/xhr/indexers/{indexer}/config", h.patchIndexersConfigHandler).Methods("PATCH")
	router.HandleFunc("/xhr/indexers", h.getIndexersHandler).Methods("GET")
	router.HandleFunc("/xhr/indexers", h.patchIndexersHandler).Methods("PATCH")
	router.HandleFunc("/xhr/auth", h.getAuthHandler).Methods("GET")
	router.HandleFunc("/xhr/auth", h.postAuthHandler).Methods("POST")

	h.Handler = router
	return h, h.initialize()
}

func (h *handler) initialize() error {
	if h.Params.Passphrase == "" {
		apiKey, hasApiKey, err := h.Params.Config.Get("global", "apikey")
		if err != nil {
			return err
		}
		if !hasApiKey {
			k, err := h.sharedKey()
			if err != nil {
				return err
			}
			h.Params.APIKey = k
			return h.Params.Config.Set("global", "apikey", fmt.Sprintf("%x", k))
		}
		k, err := hex.DecodeString(apiKey)
		if err != nil {
			return err
		}
		h.Params.APIKey = k
	}
	return nil
}

func (h *handler) baseURL(r *http.Request, path string) (*url.URL, error) {
	if h.Params.BaseURL != "" {
		return url.Parse(h.Params.BaseURL)
	}
	proto := "http"
	if r.TLS != nil {
		proto = "https"
	}
	return url.Parse(fmt.Sprintf("%s://%s%s", proto, r.Host, path))
}

func (h *handler) createIndexer(key string) (torznab.Indexer, error) {
	def, err := indexer.LoadDefinition(key)
	if err != nil {
		log.WithError(err).Warnf("Failed to load definition for %q", key)
		return nil, err
	}

	log.WithFields(logrus.Fields{"indexer": key}).Debugf("Loaded indexer")
	indexer, err := indexer.NewRunner(def, h.Params.Config), nil
	if err != nil {
		return nil, err
	}

	return indexer, nil
}

func (h *handler) lookupIndexer(key string) (torznab.Indexer, error) {
	if _, ok := h.indexers[key]; !ok {
		indexer, err := h.createIndexer(key)
		if err != nil {
			return nil, err
		}
		h.indexers[key] = indexer
	}

	return h.indexers[key], nil
}

func (h *handler) lookupAggregate() (torznab.Indexer, error) {
	keys, err := indexer.ListDefinitions()
	if err != nil {
		return nil, err
	}

	agg := indexer.Aggregate{}
	for _, key := range keys {
		def, err := indexer.LoadDefinition(key)
		if err != nil {
			return nil, err
		}

		agg = append(agg, indexer.NewRunner(def, h.Params.Config))
	}

	return agg, nil
}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if origin := r.Header.Get("Origin"); origin != "" {
		w.Header().Set("Access-Control-Allow-Origin", origin)
		w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PATCH")
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		w.Header().Set("Access-Control-Allow-Headers",
			"Accept, Cache-Control, Content-Type, Content-Length, Accept-Encoding, Authorization, Last-Event-ID")
	}
	if r.Method == "OPTIONS" {
		return
	}

	log.WithFields(logrus.Fields{
		"method": r.Method,
		"path":   r.URL.RequestURI(),
		"remote": r.RemoteAddr,
	}).Debugf("%s %s", r.Method, r.URL.RequestURI())

	for _, prefix := range apiRoutePrefixes {
		if strings.HasPrefix(r.URL.Path, prefix) {
			h.Handler.ServeHTTP(w, r)
			return
		}
	}

	h.FileHandler.ServeHTTP(w, r)
}

func (h *handler) torznabHandler(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	indexerID := params["indexer"]

	apiKey := r.URL.Query().Get("apikey")
	if !h.checkAPIKey(apiKey) {
		torznab.Error(w, "Invalid apikey parameter", torznab.ErrInsufficientPrivs)
		return
	}

	indexer, err := h.lookupIndexer(indexerID)
	if err != nil {
		torznab.Error(w, err.Error(), torznab.ErrIncorrectParameter)
		return
	}

	t := r.URL.Query().Get("t")

	if t == "" {
		http.Redirect(w, r, r.URL.Path+"?t=caps", http.StatusTemporaryRedirect)
		return
	}

	switch t {
	case "caps":
		indexer.Capabilities().ServeHTTP(w, r)

	case "search", "tvsearch", "tv-search":
		feed, err := h.search(r, indexer, indexerID)
		if err != nil {
			torznab.Error(w, err.Error(), torznab.ErrUnknownError)
			return
		}
		x, err := xml.MarshalIndent(feed, "", "  ")
		if err != nil {
			torznab.Error(w, err.Error(), torznab.ErrUnknownError)
			return
		}
		w.Header().Set("Content-Type", "application/rss+xml")
		w.Write(x)

	default:
		torznab.Error(w, "Unknown type parameter", torznab.ErrIncorrectParameter)
	}
}

func (h *handler) downloadHandler(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	token := params["token"]
	filename := params["filename"]

	log.WithFields(logrus.Fields{"filename": filename}).Debugf("Processing download via handler")

	k, err := h.sharedKey()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	t, err := decodeToken(token, k)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}

	indexer, err := h.lookupIndexer(t.Site)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}

	rc, _, err := indexer.Download(t.Link)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}

	w.Header().Set("Content-Type", "application/x-bittorrent")
	w.Header().Set("Content-Disposition", "attachment; filename="+filename)
	w.Header().Set("Content-Transfer-Encoding", "binary")

	defer rc.Close()
	io.Copy(w, rc)
}

func (h *handler) search(r *http.Request, indexer torznab.Indexer, siteKey string) (*torznab.ResultFeed, error) {
	baseURL, err := h.baseURL(r, "/download")
	if err != nil {
		return nil, err
	}

	query, err := torznab.ParseQuery(r.URL.Query())
	if err != nil {
		return nil, err
	}

	items, err := indexer.Search(query)
	if err != nil {
		return nil, err
	}

	feed := &torznab.ResultFeed{
		Info:  indexer.Info(),
		Items: items,
	}

	k, err := h.sharedKey()
	if err != nil {
		return nil, err
	}

	// rewrite links to use the server
	for idx, item := range feed.Items {
		t := &token{
			Site: item.Site,
			Link: item.Link,
		}

		te, err := t.Encode(k)
		if err != nil {
			return nil, err
		}
		baseURL.Path += fmt.Sprintf("/%s/%s.torrent", te, item.Title)
		feed.Items[idx].Link = baseURL.String()
	}

	return feed, err
}
<commit_msg>Add HEAD handler for download<commit_after>//go:generate esc -o static.go -prefix ../web/build -pkg server ../web/build
package server

import (
	"encoding/hex"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/cardigann/cardigann/config"
	"github.com/cardigann/cardigann/indexer"
	"github.com/cardigann/cardigann/logger"
	"github.com/cardigann/cardigann/torznab"
	"github.com/gorilla/mux"
)

const (
	buildDir = "/web/build"
)

var (
	log              = logger.Logger
	apiRoutePrefixes = []string{
		"/torznab/",
		"/download/",
		"/xhr/",
	}
)

type Params struct {
	BaseURL    string
	APIKey     []byte
	Passphrase string
	Config     config.Config
}

type handler struct {
	http.Handler
	Params      Params
	FileHandler http.Handler
	indexers    map[string]torznab.Indexer
}

func NewHandler(p Params) (http.Handler, error) {
	h := &handler{
		Params:      p,
		FileHandler: http.FileServer(FS(false)),
		indexers:    map[string]torznab.Indexer{},
	}

	router := mux.NewRouter()

	// torznab routes
	router.HandleFunc("/torznab/{indexer}", h.torznabHandler).Methods("GET")
	router.HandleFunc("/torznab/{indexer}/api", h.torznabHandler).Methods("GET")
	router.HandleFunc("/download/{token}/{filename}", h.downloadHandler).Methods("HEAD")
	router.HandleFunc("/download/{token}/{filename}", h.downloadHandler).Methods("GET")

	// xhr routes for the webapp
	router.HandleFunc("/xhr/indexers/{indexer}/test", h.getIndexerTestHandler).Methods("GET")
	router.HandleFunc("/xhr/indexers/{indexer}/config", h.getIndexersConfigHandler).Methods("GET")
	router.HandleFunc("/xhr/indexers/{indexer}/config", h.patchIndexersConfigHandler).Methods("PATCH")
	router.HandleFunc("/xhr/indexers", h.getIndexersHandler).Methods("GET")
	router.HandleFunc("/xhr/indexers", h.patchIndexersHandler).Methods("PATCH")
	router.HandleFunc("/xhr/auth", h.getAuthHandler).Methods("GET")
	router.HandleFunc("/xhr/auth", h.postAuthHandler).Methods("POST")

	h.Handler = router
	return h, h.initialize()
}

func (h *handler) initialize() error {
	if h.Params.Passphrase == "" {
		apiKey, hasApiKey, err := h.Params.Config.Get("global", "apikey")
		if err != nil {
			return err
		}
		if !hasApiKey {
			k, err := h.sharedKey()
			if err != nil {
				return err
			}
			h.Params.APIKey = k
			return h.Params.Config.Set("global", "apikey", fmt.Sprintf("%x", k))
		}
		k, err := hex.DecodeString(apiKey)
		if err != nil {
			return err
		}
		h.Params.APIKey = k
	}
	return nil
}

func (h *handler) baseURL(r *http.Request, path string) (*url.URL, error) {
	if h.Params.BaseURL != "" {
		return url.Parse(h.Params.BaseURL)
	}
	proto := "http"
	if r.TLS != nil {
		proto = "https"
	}
	return url.Parse(fmt.Sprintf("%s://%s%s", proto, r.Host, path))
}

func (h *handler) createIndexer(key string) (torznab.Indexer, error) {
	def, err := indexer.LoadDefinition(key)
	if err != nil {
		log.WithError(err).Warnf("Failed to load definition for %q", key)
		return nil, err
	}

	log.WithFields(logrus.Fields{"indexer": key}).Debugf("Loaded indexer")
	indexer, err := indexer.NewRunner(def, h.Params.Config), nil
	if err != nil {
		return nil, err
	}

	return indexer, nil
}

func (h *handler) lookupIndexer(key string) (torznab.Indexer, error) {
	if _, ok := h.indexers[key]; !ok {
		indexer, err := h.createIndexer(key)
		if err != nil {
			return nil, err
		}
		h.indexers[key] = indexer
	}

	return h.indexers[key], nil
}

func (h *handler) lookupAggregate() (torznab.Indexer, error) {
	keys, err := indexer.ListDefinitions()
	if err != nil {
		return nil, err
	}

	agg := indexer.Aggregate{}
	for _, key := range keys {
		def, err := indexer.LoadDefinition(key)
		if err != nil {
			return nil, err
		}

		agg = append(agg, indexer.NewRunner(def, h.Params.Config))
	}

	return agg, nil
}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if origin := r.Header.Get("Origin"); origin != "" {
		w.Header().Set("Access-Control-Allow-Origin", origin)
		w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PATCH")
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		w.Header().Set("Access-Control-Allow-Headers",
			"Accept, Cache-Control, Content-Type, Content-Length, Accept-Encoding, Authorization, Last-Event-ID")
	}
	if r.Method == "OPTIONS" {
		return
	}

	log.WithFields(logrus.Fields{
		"method": r.Method,
		"path":   r.URL.RequestURI(),
		"remote": r.RemoteAddr,
	}).Debugf("%s %s", r.Method, r.URL.RequestURI())

	for _, prefix := range apiRoutePrefixes {
		if strings.HasPrefix(r.URL.Path, prefix) {
			h.Handler.ServeHTTP(w, r)
			return
		}
	}

	h.FileHandler.ServeHTTP(w, r)
}

func (h *handler) torznabHandler(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	indexerID := params["indexer"]

	apiKey := r.URL.Query().Get("apikey")
	if !h.checkAPIKey(apiKey) {
		torznab.Error(w, "Invalid apikey parameter", torznab.ErrInsufficientPrivs)
		return
	}

	indexer, err := h.lookupIndexer(indexerID)
	if err != nil {
		torznab.Error(w, err.Error(), torznab.ErrIncorrectParameter)
		return
	}

	t := r.URL.Query().Get("t")

	if t == "" {
		http.Redirect(w, r, r.URL.Path+"?t=caps", http.StatusTemporaryRedirect)
		return
	}

	switch t {
	case "caps":
		indexer.Capabilities().ServeHTTP(w, r)

	case "search", "tvsearch", "tv-search":
		feed, err := h.search(r, indexer, indexerID)
		if err != nil {
			torznab.Error(w, err.Error(), torznab.ErrUnknownError)
			return
		}
		x, err := xml.MarshalIndent(feed, "", "  ")
		if err != nil {
			torznab.Error(w, err.Error(), torznab.ErrUnknownError)
			return
		}
		w.Header().Set("Content-Type", "application/rss+xml")
		w.Write(x)

	default:
		torznab.Error(w, "Unknown type parameter", torznab.ErrIncorrectParameter)
	}
}

func (h *handler) downloadHandler(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	token := params["token"]
	filename := params["filename"]

	log.WithFields(logrus.Fields{"filename": filename}).Debugf("Processing download via handler")

	k, err := h.sharedKey()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	t, err := decodeToken(token, k)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}

	indexer, err := h.lookupIndexer(t.Site)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}

	rc, _, err := indexer.Download(t.Link)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}

	w.Header().Set("Content-Type", "application/x-bittorrent")
	w.Header().Set("Content-Disposition", "attachment; filename="+filename)
	w.Header().Set("Content-Transfer-Encoding", "binary")

	defer rc.Close()
	io.Copy(w, rc)
}

func (h *handler) search(r *http.Request, indexer torznab.Indexer, siteKey string) (*torznab.ResultFeed, error) {
	baseURL, err := h.baseURL(r, "/download")
	if err != nil {
		return nil, err
	}

	query, err := torznab.ParseQuery(r.URL.Query())
	if err != nil {
		return nil, err
	}

	items, err := indexer.Search(query)
	if err != nil {
		return nil, err
	}

	feed := &torznab.ResultFeed{
		Info:  indexer.Info(),
		Items: items,
	}

	k, err := h.sharedKey()
	if err != nil {
		return nil, err
	}

	// rewrite links to use the server
	for idx, item := range feed.Items {
		t := &token{
			Site: item.Site,
			Link: item.Link,
		}

		te, err := t.Encode(k)
		if err != nil {
			return nil, err
		}
		baseURL.Path += fmt.Sprintf("/%s/%s.torrent", te, item.Title)
		feed.Items[idx].Link = baseURL.String()
	}

	return feed, err
}
<|endoftext|>


<commit_before>// This is free and unencumbered software released into the public
// domain.  For more information, see <http://unlicense.org> or the
// accompanying UNLICENSE file.

package editor

import (
	"fmt"
	"log"

	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/nelsam/gxui"
	"github.com/nelsam/gxui/math"
	"github.com/nelsam/gxui/mixins"
	"github.com/nelsam/gxui/mixins/outer"
	"github.com/nelsam/gxui/themes/basic"
	"github.com/nelsam/vidar/theme"
)

var (
	splitterBG = gxui.Color{
		R: .05,
		G: .05,
		B: .05,
		A: 1,
	}
	splitterFG = gxui.Color{
		R: .2,
		G: .2,
		B: .2,
		A: 1,
	}
)

type Orienter interface {
	SetOrientation(gxui.Orientation)
}

type Splitter interface {
	Split(orientation gxui.Orientation)
}

type MultiEditor interface {
	gxui.Control
	outer.LayoutChildren
	Focus()
	Has(hiddenPrefix, path string) bool
	Open(hiddenPrefix, path, headerText string, environ []string) (editor *CodeEditor, existed bool)
	Editors() uint
	CurrentEditor() *CodeEditor
	CurrentFile() string
	CloseCurrentEditor() (name string, editor *CodeEditor)
	Add(name string, editor *CodeEditor)
	SaveAll()
}

type Direction int

const (
	Up Direction = 1 + iota
	Right
	Down
	Left
)

type SplitEditor struct {
	mixins.SplitterLayout

	driver      gxui.Driver
	theme       *basic.Theme
	syntaxTheme theme.Theme
	font        gxui.Font
	window      gxui.Window

	current MultiEditor
}

func NewSplitEditor(driver gxui.Driver, window gxui.Window, theme *basic.Theme, syntaxTheme theme.Theme, font gxui.Font) *SplitEditor {
	editor := &SplitEditor{
		driver:      driver,
		theme:       theme,
		syntaxTheme: syntaxTheme,
		font:        font,
		window:      window,
	}
	editor.SplitterLayout.Init(editor, theme)
	return editor
}

func (e *SplitEditor) Elements() []interface{} {
	return []interface{}{e.current}
}

func (e *SplitEditor) Split(orientation gxui.Orientation) {
	if e.current.Editors() <= 1 {
		return
	}
	if splitter, ok := e.current.(Splitter); ok {
		splitter.Split(orientation)
		return
	}
	name, editor := e.current.CloseCurrentEditor()
	newSplit := NewTabbedEditor(e.driver, e.theme, e.syntaxTheme, e.font)
	defer func() {
		newSplit.Add(name, editor)
		newSplit.Focus()
	}()
	if e.Orientation() == orientation {
		e.AddChild(newSplit)
		return
	}
	newSplitter := NewSplitEditor(e.driver, e.window, e.theme, e.syntaxTheme, e.font)
	newSplitter.SetOrientation(orientation)
	var (
		index       int
		searchChild *gxui.Child
	)
	for index, searchChild = range e.Children() {
		if e.current == searchChild.Control {
			break
		}
	}
	e.RemoveChildAt(index)
	newSplitter.AddChild(e.current)
	e.current = newSplitter
	e.AddChildAt(index, e.current)
	newSplitter.AddChild(newSplit)
}

func (e *SplitEditor) Editors() (count uint) {
	for _, child := range e.Children() {
		editor, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		count += editor.Editors()
	}
	return count
}

func (e *SplitEditor) CloseCurrentEditor() (name string, editor *CodeEditor) {
	name, editor = e.current.CloseCurrentEditor()
	if e.current.Editors() == 0 && len(e.Children()) > 1 {
		e.RemoveChild(e.current)
		e.current = e.Children()[0].Control.(MultiEditor)
		e.current.Focus()
	}
	return name, editor
}

func (e *SplitEditor) Add(name string, editor *CodeEditor) {
	e.current.Add(name, editor)
}

func (e *SplitEditor) AddChild(child gxui.Control) *gxui.Child {
	editor, ok := child.(MultiEditor)
	if !ok {
		panic(fmt.Errorf("SplitEditor: Non-MultiEditor type %T sent to AddChild", child))
	}
	if e.current == nil {
		e.current = editor
	}
	return e.SplitterLayout.AddChild(child)
}

func (e *SplitEditor) Focus() {
	e.current.Focus()
}

func (l *SplitEditor) CreateSplitterBar() gxui.Control {
	b := NewSplitterBar(l.window.Viewport(), l.theme)
	b.SetOrientation(l.Orientation())
	b.OnSplitterDragged(func(wndPnt math.Point) { l.SplitterDragged(b, wndPnt) })
	return b
}

func (e *SplitEditor) SetOrientation(o gxui.Orientation) {
	e.SplitterLayout.SetOrientation(o)
	for _, child := range e.Children() {
		if orienter, ok := child.Control.(Orienter); ok {
			orienter.SetOrientation(o)
		}
	}
}

func (e *SplitEditor) MouseUp(event gxui.MouseEvent) {
	for _, child := range e.Children() {
		offsetPoint := event.Point.AddX(-child.Offset.X).AddY(-child.Offset.Y)
		if !child.Control.ContainsPoint(offsetPoint) {
			continue
		}
		newFocus, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		e.current = newFocus
		e.current.Focus()
		break
	}
	e.SplitterLayout.MouseUp(event)
}

func (e *SplitEditor) Has(hiddenPrefix, path string) bool {
	for _, child := range e.Children() {
		if child.Control.(MultiEditor).Has(hiddenPrefix, path) {
			return true
		}
	}
	return false
}

func (e *SplitEditor) Open(hiddenPrefix, path, headerText string, environ []string) (editor *CodeEditor, existed bool) {
	for _, child := range e.Children() {
		if me, ok := child.Control.(MultiEditor); ok && me.Has(hiddenPrefix, path) {
			e.current = me
			return me.Open(hiddenPrefix, path, headerText, environ)
		}
	}
	return e.current.Open(hiddenPrefix, path, headerText, environ)
}

func (e *SplitEditor) CurrentEditor() *CodeEditor {
	return e.current.CurrentEditor()
}

func (e *SplitEditor) CurrentFile() string {
	return e.current.CurrentFile()
}

func (e *SplitEditor) ChildIndex(c gxui.Control) int {
	if c == nil {
		return -1
	}
	for i, child := range e.Children() {
		if child.Control == c {
			return i
		}
	}
	return -1
}

func (e *SplitEditor) NextEditor(direction Direction) *CodeEditor {
	editor, _ := e.nextEditor(direction)
	return editor
}

func (e *SplitEditor) nextEditor(direction Direction) (editor *CodeEditor, wrapped bool) {
	switch direction {
	case Up, Down:
		if e.Orientation().Horizontal() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.nextEditor(direction)
			}
			return nil, false
		}
	case Left, Right:
		if e.Orientation().Vertical() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.nextEditor(direction)
			}
			return nil, false
		}
	}

	if splitter, ok := e.current.(*SplitEditor); ok {
		// Special case - there could be another split editor with our orientation
		// as a child, and *that* is the editor that needs the split moved.
		ed, wrapped := splitter.nextEditor(direction)
		if e != nil && !wrapped {
			return ed, false
		}
	}

	children := e.Children()
	i := children.IndexOf(e.current)
	if i < 0 {
		log.Printf("Error: Current editor is not part of the splitter's layout")
		return nil, false
	}
	switch direction {
	case Up, Left:
		i--
		if i < 0 {
			wrapped = true
			i = len(children) - 1
		}
	case Down, Right:
		i++
		if i == len(children) {
			wrapped = true
			i = 0
		}
	}
	me := children[i].Control.(MultiEditor)
	if splitter, ok := me.(*SplitEditor); ok {
		return splitter.first(direction), wrapped
	}

	return me.CurrentEditor(), wrapped
}

func (e *SplitEditor) first(d Direction) *CodeEditor {
	switch d {
	case Up, Down:
		if e.Orientation().Horizontal() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.first(d)
			}
			return e.current.CurrentEditor()
		}
	case Left, Right:
		if e.Orientation().Vertical() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.first(d)
			}
			return e.current.CurrentEditor()
		}
	}

	var first *gxui.Child
	switch d {
	case Up, Left:
		first = e.Children()[0]
	case Down, Right:
		first = e.Children()[len(e.Children())-1]
	}

	switch src := first.Control.(type) {
	case *SplitEditor:
		return src.first(d)
	case MultiEditor:
		return src.CurrentEditor()
	default:
		log.Printf("Error: first editor is not an editor")
		return nil
	}
}

func (e *SplitEditor) SaveAll() {
	for _, child := range e.Children() {
		editor, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		editor.SaveAll()
	}
}

type SplitterBar struct {
	mixins.SplitterBar
	viewport    gxui.Viewport
	orientation gxui.Orientation

	arrow, horizResize, vertResize *glfw.Cursor
}

func NewSplitterBar(viewport gxui.Viewport, theme gxui.Theme) *SplitterBar {
	s := &SplitterBar{
		viewport:    viewport,
		arrow:       glfw.CreateStandardCursor(glfw.ArrowCursor),
		horizResize: glfw.CreateStandardCursor(glfw.HResizeCursor),
		vertResize:  glfw.CreateStandardCursor(glfw.VResizeCursor),
	}
	s.SplitterBar.Init(s, theme)
	s.SetBackgroundColor(splitterBG)
	s.SetForegroundColor(splitterFG)
	return s
}

func (s *SplitterBar) SetOrientation(o gxui.Orientation) {
	s.orientation = o
}

func (s *SplitterBar) MouseEnter(gxui.MouseEvent) {
	switch s.orientation {
	case gxui.Vertical:
		s.viewport.SetCursor(s.vertResize)
	case gxui.Horizontal:
		s.viewport.SetCursor(s.horizResize)
	}
}

func (s *SplitterBar) MouseExit(gxui.MouseEvent) {
	s.viewport.SetCursor(s.arrow)
}
<commit_msg>Update split editor type assertions to handle splitter bars et al.<commit_after>// This is free and unencumbered software released into the public
// domain.  For more information, see <http://unlicense.org> or the
// accompanying UNLICENSE file.

package editor

import (
	"fmt"
	"log"

	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/nelsam/gxui"
	"github.com/nelsam/gxui/math"
	"github.com/nelsam/gxui/mixins"
	"github.com/nelsam/gxui/mixins/outer"
	"github.com/nelsam/gxui/themes/basic"
	"github.com/nelsam/vidar/theme"
)

var (
	splitterBG = gxui.Color{
		R: .05,
		G: .05,
		B: .05,
		A: 1,
	}
	splitterFG = gxui.Color{
		R: .2,
		G: .2,
		B: .2,
		A: 1,
	}
)

type Orienter interface {
	SetOrientation(gxui.Orientation)
}

type Splitter interface {
	Split(orientation gxui.Orientation)
}

type MultiEditor interface {
	gxui.Control
	outer.LayoutChildren
	Focus()
	Has(hiddenPrefix, path string) bool
	Open(hiddenPrefix, path, headerText string, environ []string) (editor *CodeEditor, existed bool)
	Editors() uint
	CurrentEditor() *CodeEditor
	CurrentFile() string
	CloseCurrentEditor() (name string, editor *CodeEditor)
	Add(name string, editor *CodeEditor)
	SaveAll()
}

type Direction int

const (
	Up Direction = 1 + iota
	Right
	Down
	Left
)

type SplitEditor struct {
	mixins.SplitterLayout

	driver      gxui.Driver
	theme       *basic.Theme
	syntaxTheme theme.Theme
	font        gxui.Font
	window      gxui.Window

	current MultiEditor
}

func NewSplitEditor(driver gxui.Driver, window gxui.Window, theme *basic.Theme, syntaxTheme theme.Theme, font gxui.Font) *SplitEditor {
	editor := &SplitEditor{
		driver:      driver,
		theme:       theme,
		syntaxTheme: syntaxTheme,
		font:        font,
		window:      window,
	}
	editor.SplitterLayout.Init(editor, theme)
	return editor
}

func (e *SplitEditor) Elements() []interface{} {
	return []interface{}{e.current}
}

func (e *SplitEditor) Split(orientation gxui.Orientation) {
	if e.current.Editors() <= 1 {
		return
	}
	if splitter, ok := e.current.(Splitter); ok {
		splitter.Split(orientation)
		return
	}
	name, editor := e.current.CloseCurrentEditor()
	newSplit := NewTabbedEditor(e.driver, e.theme, e.syntaxTheme, e.font)
	defer func() {
		newSplit.Add(name, editor)
		newSplit.Focus()
	}()
	if e.Orientation() == orientation {
		e.AddChild(newSplit)
		return
	}
	newSplitter := NewSplitEditor(e.driver, e.window, e.theme, e.syntaxTheme, e.font)
	newSplitter.SetOrientation(orientation)
	var (
		index       int
		searchChild *gxui.Child
	)
	for index, searchChild = range e.Children() {
		if e.current == searchChild.Control {
			break
		}
	}
	e.RemoveChildAt(index)
	newSplitter.AddChild(e.current)
	e.current = newSplitter
	e.AddChildAt(index, e.current)
	newSplitter.AddChild(newSplit)
}

func (e *SplitEditor) Editors() (count uint) {
	for _, child := range e.Children() {
		editor, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		count += editor.Editors()
	}
	return count
}

func (e *SplitEditor) CloseCurrentEditor() (name string, editor *CodeEditor) {
	name, editor = e.current.CloseCurrentEditor()
	if e.current.Editors() == 0 && len(e.Children()) > 1 {
		e.RemoveChild(e.current)
		e.current = e.Children()[0].Control.(MultiEditor)
		e.current.Focus()
	}
	return name, editor
}

func (e *SplitEditor) Add(name string, editor *CodeEditor) {
	e.current.Add(name, editor)
}

func (e *SplitEditor) AddChild(child gxui.Control) *gxui.Child {
	editor, ok := child.(MultiEditor)
	if !ok {
		panic(fmt.Errorf("SplitEditor: Non-MultiEditor type %T sent to AddChild", child))
	}
	if e.current == nil {
		e.current = editor
	}
	return e.SplitterLayout.AddChild(child)
}

func (e *SplitEditor) Focus() {
	e.current.Focus()
}

func (l *SplitEditor) CreateSplitterBar() gxui.Control {
	b := NewSplitterBar(l.window.Viewport(), l.theme)
	b.SetOrientation(l.Orientation())
	b.OnSplitterDragged(func(wndPnt math.Point) { l.SplitterDragged(b, wndPnt) })
	return b
}

func (e *SplitEditor) SetOrientation(o gxui.Orientation) {
	e.SplitterLayout.SetOrientation(o)
	for _, child := range e.Children() {
		if orienter, ok := child.Control.(Orienter); ok {
			orienter.SetOrientation(o)
		}
	}
}

func (e *SplitEditor) MouseUp(event gxui.MouseEvent) {
	for _, child := range e.Children() {
		offsetPoint := event.Point.AddX(-child.Offset.X).AddY(-child.Offset.Y)
		if !child.Control.ContainsPoint(offsetPoint) {
			continue
		}
		newFocus, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		e.current = newFocus
		e.current.Focus()
		break
	}
	e.SplitterLayout.MouseUp(event)
}

func (e *SplitEditor) Has(hiddenPrefix, path string) bool {
	for _, child := range e.Children() {
		if me, ok := child.Control.(MultiEditor); ok && me.Has(hiddenPrefix, path) {
			return true
		}
	}
	return false
}

func (e *SplitEditor) Open(hiddenPrefix, path, headerText string, environ []string) (editor *CodeEditor, existed bool) {
	for _, child := range e.Children() {
		if me, ok := child.Control.(MultiEditor); ok && me.Has(hiddenPrefix, path) {
			e.current = me
			return me.Open(hiddenPrefix, path, headerText, environ)
		}
	}
	return e.current.Open(hiddenPrefix, path, headerText, environ)
}

func (e *SplitEditor) CurrentEditor() *CodeEditor {
	return e.current.CurrentEditor()
}

func (e *SplitEditor) CurrentFile() string {
	return e.current.CurrentFile()
}

func (e *SplitEditor) ChildIndex(c gxui.Control) int {
	if c == nil {
		return -1
	}
	for i, child := range e.Children() {
		if child.Control == c {
			return i
		}
	}
	return -1
}

func (e *SplitEditor) NextEditor(direction Direction) *CodeEditor {
	editor, _ := e.nextEditor(direction)
	return editor
}

func (e *SplitEditor) nextEditor(direction Direction) (editor *CodeEditor, wrapped bool) {
	switch direction {
	case Up, Down:
		if e.Orientation().Horizontal() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.nextEditor(direction)
			}
			return nil, false
		}
	case Left, Right:
		if e.Orientation().Vertical() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.nextEditor(direction)
			}
			return nil, false
		}
	}

	if splitter, ok := e.current.(*SplitEditor); ok {
		// Special case - there could be another split editor with our orientation
		// as a child, and *that* is the editor that needs the split moved.
		ed, wrapped := splitter.nextEditor(direction)
		if e != nil && !wrapped {
			return ed, false
		}
	}

	children := e.Children()
	i := children.IndexOf(e.current)
	if i < 0 {
		log.Printf("Error: Current editor is not part of the splitter's layout")
		return nil, false
	}
	var next func(i int) int
	switch direction {
	case Up, Left:
		next = func(i int) int {
			i--
			if i < 0 {
				wrapped = true
				i = len(children) - 1
			}
			return i
		}
	case Down, Right:
		next = func(i int) int {
			i++
			if i == len(children) {
				wrapped = true
				i = 0
			}
			return i
		}
	}
	var (
		me MultiEditor
		ok bool
	)

	for {
		i = next(i)
		me, ok = children[i].Control.(MultiEditor)
		if ok {
			break
		}
	}
	if splitter, ok := me.(*SplitEditor); ok {
		return splitter.first(direction), wrapped
	}

	return me.CurrentEditor(), wrapped
}

func (e *SplitEditor) first(d Direction) *CodeEditor {
	switch d {
	case Up, Down:
		if e.Orientation().Horizontal() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.first(d)
			}
			return e.current.CurrentEditor()
		}
	case Left, Right:
		if e.Orientation().Vertical() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.first(d)
			}
			return e.current.CurrentEditor()
		}
	}

	var first *gxui.Child
	switch d {
	case Up, Left:
		first = e.Children()[0]
	case Down, Right:
		first = e.Children()[len(e.Children())-1]
	}

	switch src := first.Control.(type) {
	case *SplitEditor:
		return src.first(d)
	case MultiEditor:
		return src.CurrentEditor()
	default:
		log.Printf("Error: first editor is not an editor")
		return nil
	}
}

func (e *SplitEditor) SaveAll() {
	for _, child := range e.Children() {
		editor, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		editor.SaveAll()
	}
}

type SplitterBar struct {
	mixins.SplitterBar
	viewport    gxui.Viewport
	orientation gxui.Orientation

	arrow, horizResize, vertResize *glfw.Cursor
}

func NewSplitterBar(viewport gxui.Viewport, theme gxui.Theme) *SplitterBar {
	s := &SplitterBar{
		viewport:    viewport,
		arrow:       glfw.CreateStandardCursor(glfw.ArrowCursor),
		horizResize: glfw.CreateStandardCursor(glfw.HResizeCursor),
		vertResize:  glfw.CreateStandardCursor(glfw.VResizeCursor),
	}
	s.SplitterBar.Init(s, theme)
	s.SetBackgroundColor(splitterBG)
	s.SetForegroundColor(splitterFG)
	return s
}

func (s *SplitterBar) SetOrientation(o gxui.Orientation) {
	s.orientation = o
}

func (s *SplitterBar) MouseEnter(gxui.MouseEvent) {
	switch s.orientation {
	case gxui.Vertical:
		s.viewport.SetCursor(s.vertResize)
	case gxui.Horizontal:
		s.viewport.SetCursor(s.horizResize)
	}
}

func (s *SplitterBar) MouseExit(gxui.MouseEvent) {
	s.viewport.SetCursor(s.arrow)
}
<|endoftext|>


<commit_before>package archetype

import (
	"archive/zip"
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"text/template"
	"time"

	"github.com/Masterminds/sprig"
	awsEvents "github.com/aws/aws-lambda-go/events"
	awsLambdaEvents "github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-sdk-go/aws/session"
	sparta "github.com/mweagle/Sparta"
	"github.com/mweagle/Sparta/archetype/xformer"
	gocf "github.com/mweagle/go-cloudformation"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

func dropError() error {
	return errors.New("KinesisFirehoseDrop")
}

var (
	xformError error
)

// TemplateFileName is the name of the file in the ZIP archive
const TemplateFileName = "xform.template"
const xformResourcePrefix = "firehosexform_"
const envVarKinesisFirehoseTransformName = "SPARTA_KINESIS_FIREHOSE_TRANSFORM"

// KinesisFirehoseReactor represents a lambda function that responds to Dynamo  messages
type KinesisFirehoseReactor interface {
	// OnKinesisFirehoseRecord when an Kinesis reocrd occurs.
	OnKinesisFirehoseRecord(ctx context.Context,
		record *awsEvents.KinesisFirehoseEventRecord) (*awsEvents.KinesisFirehoseResponseRecord, error)
}

// KinesisFirehoseReactorFunc is a free function that adapts a KinesisFirehoseReactor
// compliant signature into a function that exposes an OnEvent
// function
type KinesisFirehoseReactorFunc func(ctx context.Context,
	kinesisRecord *awsEvents.KinesisFirehoseEventRecord) (*awsEvents.KinesisFirehoseResponseRecord, error)

// OnKinesisFirehoseRecord satisfies the KinesisFirehoseReactor interface
func (reactorFunc KinesisFirehoseReactorFunc) OnKinesisFirehoseRecord(ctx context.Context,
	kinesisRecord *awsEvents.KinesisFirehoseEventRecord) (*awsEvents.KinesisFirehoseResponseRecord, error) {
	return reactorFunc(ctx, kinesisRecord)
}

// ReactorName provides the name of the reactor func
func (reactorFunc KinesisFirehoseReactorFunc) ReactorName() string {
	return runtime.FuncForPC(reflect.ValueOf(reactorFunc).Pointer()).Name()
}

// NewKinesisFirehoseLambdaTransformer returns a new firehose proocessor that supports
// transforming records.
func NewKinesisFirehoseLambdaTransformer(reactor KinesisFirehoseReactor,
	timeout time.Duration) (*sparta.LambdaAWSInfo, error) {

	reactorLambda := func(ctx context.Context,
		kinesisFirehoseEvent awsLambdaEvents.KinesisFirehoseEvent) (interface{}, error) {
		// Apply the transform to each record and see
		// what it says

		response := &awsEvents.KinesisFirehoseResponse{
			Records: make([]awsEvents.KinesisFirehoseResponseRecord,
				len(kinesisFirehoseEvent.Records)),
		}

		for eachIndex, eachRecord := range kinesisFirehoseEvent.Records {
			responseRecord, responseRecordErr := reactor.OnKinesisFirehoseRecord(ctx, &eachRecord)
			if responseRecordErr != nil {
				return nil, errors.Wrapf(responseRecordErr, "Failed to transform record")
			}
			if responseRecord == nil {
				responseRecord = &awsEvents.KinesisFirehoseResponseRecord{
					RecordID: eachRecord.RecordID,
					Result:   awsEvents.KinesisFirehoseTransformedStateDropped,
					Data:     eachRecord.Data,
				}
			}
			response.Records[eachIndex] = *responseRecord
		}
		return response, nil
	}

	lambdaFn, lambdaFnErr := sparta.NewAWSLambda(reactorName(reactor),
		reactorLambda,
		sparta.IAMRoleDefinition{})
	return lambdaFn, lambdaFnErr
}

// NewKinesisFirehoseTransformer returns a new firehose proocessor that supports
// transforming records.
func NewKinesisFirehoseTransformer(xformFilePath string,
	timeout time.Duration,
	hooks *sparta.WorkflowHooks) (*sparta.LambdaAWSInfo, error) {

	baseName := filepath.Base(xformFilePath)
	archiveEntryName := sparta.CloudFormationResourceName(xformResourcePrefix, xformFilePath)
	lambdaName := fmt.Sprintf("Firehose%s", baseName)

	// Return a lambda function that applies the XForm transformation
	reactorLambda := func(ctx context.Context,
		kinesisEvent awsEvents.KinesisFirehoseEvent) (*awsEvents.KinesisFirehoseResponse, error) {
		return lambdaXForm(ctx, kinesisEvent)
	}
	lambdaFn, lambdaFnErr := sparta.NewAWSLambda(lambdaName,
		reactorLambda,
		sparta.IAMRoleDefinition{})
	if lambdaFnErr != nil {
		return nil, errors.Wrapf(lambdaFnErr, "attempting to create Kinesis Firehose reactor")
	}

	// Borrow the resource name creator to get a name for the archive
	lambdaFn.Options.Environment[envVarKinesisFirehoseTransformName] = gocf.String(archiveEntryName)
	lambdaFn.Options.Timeout = (timeout.Milliseconds() / 1000)

	// Create the decorator that adds the file to the ZIP archive using
	// the transform name...
	archiveDecorator := func(context map[string]interface{},
		serviceName string,
		zipWriter *zip.Writer,
		awsSession *session.Session,
		noop bool,
		logger *logrus.Logger) error {
		fileInfo, fileInfoErr := os.Stat(xformFilePath)
		if fileInfoErr != nil {
			return errors.Wrapf(fileInfoErr, "Failed to get fileInfo for Kinesis Firehose transform")
		}
		fileReader, fileReaderErr := os.Open(xformFilePath)
		if fileReaderErr != nil {
			return errors.Wrapf(fileReaderErr, "Failed to open Kinesis Firehose transform file")
		}
		defer fileReader.Close()

		fileHeader, fileHeaderErr := zip.FileInfoHeader(fileInfo)
		if fileHeaderErr != nil {
			return errors.Wrapf(fileHeaderErr, "Failed to detect ZIP header for Kinesis Firehose transform")
		}

		fileHeader.Name = archiveEntryName
		fileHeader.Method = zip.Deflate

		// Copy it...
		writer, writerErr := zipWriter.CreateHeader(fileHeader)
		if writerErr != nil {
			return errors.Wrapf(fileHeaderErr, "Failed to create ZIP header for Kinesis Firehose transform")
		}
		_, copyErr := io.Copy(writer, fileReader)
		return copyErr
	}
	// Done...
	hooks.Archives = append(hooks.Archives, sparta.ArchiveHookFunc(archiveDecorator))
	return lambdaFn, nil
}

// ApplyTransformToKinesisFirehoseEvent is the generic transformation function that applies
// a template.Template transformation to each
func ApplyTransformToKinesisFirehoseEvent(ctx context.Context,
	templateBytes []byte,
	kinesisEvent awsEvents.KinesisFirehoseEvent) (*awsEvents.KinesisFirehoseResponse, error) {

	logger, loggerOk := ctx.Value(sparta.ContextKeyLogger).(*logrus.Logger)
	if loggerOk {
		logger.Info("Hello world structured log message")
	}

	funcMap := sprig.TxtFuncMap()
	funcMap["KinesisFirehoseDrop"] = interface{}(func() (string, error) {
		return "", dropError()
	})

	// Setup the function map that knows how to do the JMESPath
	// given the map...
	transform, transformErr := template.
		New("xformer").
		Funcs(funcMap).
		Parse(string(templateBytes))
	if transformErr != nil {
		return nil, errors.Wrapf(transformErr, "Attempting to create template")
	}

	response := &awsEvents.KinesisFirehoseResponse{
		Records: make([]awsEvents.KinesisFirehoseResponseRecord, len(kinesisEvent.Records)),
	}
	headerInfo := &xformer.KinesisEventHeaderInfo{
		InvocationID: kinesisEvent.InvocationID,

		DeliveryStreamArn:      kinesisEvent.DeliveryStreamArn,
		SourceKinesisStreamArn: kinesisEvent.SourceKinesisStreamArn,
		Region:                 kinesisEvent.Region,
	}

	for eachIndex, eachRecord := range kinesisEvent.Records {
		xformedRecord := awsEvents.KinesisFirehoseResponseRecord{
			RecordID: eachRecord.RecordID,
			Result:   awsEvents.KinesisFirehoseTransformedStateDropped,
			Data:     eachRecord.Data,
		}
		xform, xformErr := xformer.NewKinesisFirehoseEventXFormer(headerInfo, &eachRecord)
		if xformErr == nil {
			dataMap := map[string]interface{}{
				"Record": xform,
			}
			var outputBuffer bytes.Buffer
			templateErr := transform.Execute(&outputBuffer, dataMap)
			if templateErr != nil {
				// Is the fail value "KinesisFirehoseDrop" ?
				if !strings.Contains(templateErr.Error(), dropError().Error()) {
					xformedRecord.Result = awsEvents.KinesisFirehoseTransformedStateProcessingFailed
				}
			} else if xform.Error() != nil {
				xformedRecord.Result = awsEvents.KinesisFirehoseTransformedStateProcessingFailed
			} else {
				if loggerOk && logger.IsLevelEnabled(logrus.DebugLevel) {
					logger.WithFields(logrus.Fields{
						"input":  eachRecord.Data,
						"output": outputBuffer.Bytes(),
					}).Debug("Transformation result")
				}

				xformedRecord.Data = outputBuffer.Bytes()
				xformedRecord.Result = awsEvents.KinesisFirehoseTransformedStateOk
			}
		}
		// Save it...
		response.Records[eachIndex] = xformedRecord
	}
	return response, nil
}
<commit_msg>Cleanup gosec<commit_after>package archetype

import (
	"archive/zip"
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"text/template"
	"time"

	"github.com/Masterminds/sprig"
	awsEvents "github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-sdk-go/aws/session"
	sparta "github.com/mweagle/Sparta"
	"github.com/mweagle/Sparta/archetype/xformer"
	gocf "github.com/mweagle/go-cloudformation"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

func dropError() error {
	return errors.New("KinesisFirehoseDrop")
}

// TemplateFileName is the name of the file in the ZIP archive
const TemplateFileName = "xform.template"
const xformResourcePrefix = "firehosexform_"
const envVarKinesisFirehoseTransformName = "SPARTA_KINESIS_FIREHOSE_TRANSFORM"

// KinesisFirehoseReactor represents a lambda function that responds to Dynamo  messages
type KinesisFirehoseReactor interface {
	// OnKinesisFirehoseRecord when an Kinesis reocrd occurs.
	OnKinesisFirehoseRecord(ctx context.Context,
		record *awsEvents.KinesisFirehoseEventRecord) (*awsEvents.KinesisFirehoseResponseRecord, error)
}

// KinesisFirehoseReactorFunc is a free function that adapts a KinesisFirehoseReactor
// compliant signature into a function that exposes an OnEvent
// function
type KinesisFirehoseReactorFunc func(ctx context.Context,
	kinesisRecord *awsEvents.KinesisFirehoseEventRecord) (*awsEvents.KinesisFirehoseResponseRecord, error)

// OnKinesisFirehoseRecord satisfies the KinesisFirehoseReactor interface
func (reactorFunc KinesisFirehoseReactorFunc) OnKinesisFirehoseRecord(ctx context.Context,
	kinesisRecord *awsEvents.KinesisFirehoseEventRecord) (*awsEvents.KinesisFirehoseResponseRecord, error) {
	return reactorFunc(ctx, kinesisRecord)
}

// ReactorName provides the name of the reactor func
func (reactorFunc KinesisFirehoseReactorFunc) ReactorName() string {
	return runtime.FuncForPC(reflect.ValueOf(reactorFunc).Pointer()).Name()
}

// NewKinesisFirehoseLambdaTransformer returns a new firehose proocessor that supports
// transforming records.
func NewKinesisFirehoseLambdaTransformer(reactor KinesisFirehoseReactor,
	timeout time.Duration) (*sparta.LambdaAWSInfo, error) {

	reactorLambda := func(ctx context.Context,
		kinesisFirehoseEvent awsEvents.KinesisFirehoseEvent) (interface{}, error) {
		// Apply the transform to each record and see
		// what it says

		response := &awsEvents.KinesisFirehoseResponse{
			Records: make([]awsEvents.KinesisFirehoseResponseRecord,
				len(kinesisFirehoseEvent.Records)),
		}

		for eachIndex, eachRecord := range kinesisFirehoseEvent.Records {
			responseRecord, responseRecordErr := reactor.OnKinesisFirehoseRecord(ctx, &eachRecord)
			if responseRecordErr != nil {
				return nil, errors.Wrapf(responseRecordErr, "Failed to transform record")
			}
			if responseRecord == nil {
				responseRecord = &awsEvents.KinesisFirehoseResponseRecord{
					RecordID: eachRecord.RecordID,
					Result:   awsEvents.KinesisFirehoseTransformedStateDropped,
					Data:     eachRecord.Data,
				}
			}
			response.Records[eachIndex] = *responseRecord
		}
		return response, nil
	}

	lambdaFn, lambdaFnErr := sparta.NewAWSLambda(reactorName(reactor),
		reactorLambda,
		sparta.IAMRoleDefinition{})
	return lambdaFn, lambdaFnErr
}

// NewKinesisFirehoseTransformer returns a new firehose proocessor that supports
// transforming records.
func NewKinesisFirehoseTransformer(xformFilePath string,
	timeout time.Duration,
	hooks *sparta.WorkflowHooks) (*sparta.LambdaAWSInfo, error) {

	baseName := filepath.Base(xformFilePath)
	archiveEntryName := sparta.CloudFormationResourceName(xformResourcePrefix, xformFilePath)
	lambdaName := fmt.Sprintf("Firehose%s", baseName)

	// Return a lambda function that applies the XForm transformation
	reactorLambda := func(ctx context.Context,
		kinesisEvent awsEvents.KinesisFirehoseEvent) (*awsEvents.KinesisFirehoseResponse, error) {
		return lambdaXForm(ctx, kinesisEvent)
	}
	lambdaFn, lambdaFnErr := sparta.NewAWSLambda(lambdaName,
		reactorLambda,
		sparta.IAMRoleDefinition{})
	if lambdaFnErr != nil {
		return nil, errors.Wrapf(lambdaFnErr, "attempting to create Kinesis Firehose reactor")
	}

	// Borrow the resource name creator to get a name for the archive
	lambdaFn.Options.Environment[envVarKinesisFirehoseTransformName] = gocf.String(archiveEntryName)
	lambdaFn.Options.Timeout = (timeout.Milliseconds() / 1000)

	// Create the decorator that adds the file to the ZIP archive using
	// the transform name...
	archiveDecorator := func(context map[string]interface{},
		serviceName string,
		zipWriter *zip.Writer,
		awsSession *session.Session,
		noop bool,
		logger *logrus.Logger) error {
		fileInfo, fileInfoErr := os.Stat(xformFilePath)
		if fileInfoErr != nil {
			return errors.Wrapf(fileInfoErr, "Failed to get fileInfo for Kinesis Firehose transform")
		}
		// G304: Potential file inclusion via variable
		/* #nosec */
		fileReader, fileReaderErr := os.Open(xformFilePath)
		if fileReaderErr != nil {
			return errors.Wrapf(fileReaderErr, "Failed to open Kinesis Firehose transform file")
		}
		defer fileReader.Close()

		fileHeader, fileHeaderErr := zip.FileInfoHeader(fileInfo)
		if fileHeaderErr != nil {
			return errors.Wrapf(fileHeaderErr, "Failed to detect ZIP header for Kinesis Firehose transform")
		}

		fileHeader.Name = archiveEntryName
		fileHeader.Method = zip.Deflate

		// Copy it...
		writer, writerErr := zipWriter.CreateHeader(fileHeader)
		if writerErr != nil {
			return errors.Wrapf(fileHeaderErr, "Failed to create ZIP header for Kinesis Firehose transform")
		}
		_, copyErr := io.Copy(writer, fileReader)
		return copyErr
	}
	// Done...
	hooks.Archives = append(hooks.Archives, sparta.ArchiveHookFunc(archiveDecorator))
	return lambdaFn, nil
}

// ApplyTransformToKinesisFirehoseEvent is the generic transformation function that applies
// a template.Template transformation to each
func ApplyTransformToKinesisFirehoseEvent(ctx context.Context,
	templateBytes []byte,
	kinesisEvent awsEvents.KinesisFirehoseEvent) (*awsEvents.KinesisFirehoseResponse, error) {

	logger, loggerOk := ctx.Value(sparta.ContextKeyLogger).(*logrus.Logger)
	if loggerOk {
		logger.Info("Hello world structured log message")
	}

	funcMap := sprig.TxtFuncMap()
	funcMap["KinesisFirehoseDrop"] = interface{}(func() (string, error) {
		return "", dropError()
	})

	// Setup the function map that knows how to do the JMESPath
	// given the map...
	transform, transformErr := template.
		New("xformer").
		Funcs(funcMap).
		Parse(string(templateBytes))
	if transformErr != nil {
		return nil, errors.Wrapf(transformErr, "Attempting to create template")
	}

	response := &awsEvents.KinesisFirehoseResponse{
		Records: make([]awsEvents.KinesisFirehoseResponseRecord, len(kinesisEvent.Records)),
	}
	headerInfo := &xformer.KinesisEventHeaderInfo{
		InvocationID: kinesisEvent.InvocationID,

		DeliveryStreamArn:      kinesisEvent.DeliveryStreamArn,
		SourceKinesisStreamArn: kinesisEvent.SourceKinesisStreamArn,
		Region:                 kinesisEvent.Region,
	}

	for eachIndex, eachRecord := range kinesisEvent.Records {
		xformedRecord := awsEvents.KinesisFirehoseResponseRecord{
			RecordID: eachRecord.RecordID,
			Result:   awsEvents.KinesisFirehoseTransformedStateDropped,
			Data:     eachRecord.Data,
		}
		xform, xformErr := xformer.NewKinesisFirehoseEventXFormer(headerInfo, &eachRecord)
		if xformErr == nil {
			dataMap := map[string]interface{}{
				"Record": xform,
			}
			var outputBuffer bytes.Buffer
			templateErr := transform.Execute(&outputBuffer, dataMap)
			if templateErr != nil {
				// Is the fail value "KinesisFirehoseDrop" ?
				if !strings.Contains(templateErr.Error(), dropError().Error()) {
					xformedRecord.Result = awsEvents.KinesisFirehoseTransformedStateProcessingFailed
				}
			} else if xform.Error() != nil {
				xformedRecord.Result = awsEvents.KinesisFirehoseTransformedStateProcessingFailed
			} else {
				if loggerOk && logger.IsLevelEnabled(logrus.DebugLevel) {
					logger.WithFields(logrus.Fields{
						"input":  eachRecord.Data,
						"output": outputBuffer.Bytes(),
					}).Debug("Transformation result")
				}

				xformedRecord.Data = outputBuffer.Bytes()
				xformedRecord.Result = awsEvents.KinesisFirehoseTransformedStateOk
			}
		}
		// Save it...
		response.Records[eachIndex] = xformedRecord
	}
	return response, nil
}
<|endoftext|>


<commit_before>// Basic data structures for Go.
package main

import (
	"fmt"
)

// Letters for quick lookup.
// These are the letters marked on top of the board.  "I" is
// always left out, likely to avoid confusion with "1".
const BoardLetters = "ABCDEFGHJKLMNOPQRST"
const ActionChars = "cbw"

// Here it would be nice if the type system allows us to say
// "integer with one of these value: 0, 1, 2.
type Stone int

const (
	// Stone colors. Used for board state.
	Empty = 0 // No stone here.
	Black = 1
	White = 2

	// Moves. These correspond to board state making it easy to
	// translate between the two. (A capture will clear that position on
	// the board.)
	Capture    = 0
	PlaceBlack = 1
	PlaceWhite = 2
)

// Position where a stone can be placed.
type Pos struct {
	X int // Left to right, 0-18 (marked as 1-19 on board.)
	Y int // Bottom to top, 0-18 (ABCDEFGHJKLMNOPQRST.)
}

type BoardSize struct {
	Width  int
	Height int
}

// A move. (Place or capture stone.)
type Move struct {
	Action Stone // (Hmm, this looks a bit weird here.)
	Pos
}

func (move Move) String() string {
	// Todo: check if values are out of range?
	color := ActionChars[move.Action]
	letter := BoardLetters[move.X]
	number := move.Y + 1

	return fmt.Sprintf("%c%c%d", color, letter, number)
}

// The board is just a cache of moves. We can recompute the board from
// the moves.
type Game struct {
	BoardSize
	Board map[Pos]Stone
	Moves []Move
	// Here we should have a stack (or slice) of boards so that we have
	// a history of previous board states.

	// + We need to keep score somehow. (Number of captured stones for
	// each player.)
}

func (game *Game) AddMove(move Move) {
	game.Moves = append(game.Moves, move)
	game.Board[move.Pos] = move.Action
}

func (game *Game) AddMoves(moves []Move) {
	for _, move := range moves {
		fmt.Println(move)
		game.AddMove(move)
	}
}

func (game *Game) PrintBoard() {
	stoneChars := ".bw"

	fmt.Println("   " + BoardLetters)

	for y := game.Height - 1; y >= 0; y-- {
		line := ""
		for x := 0; x < game.Width; x++ {
			line += fmt.Sprintf("%c", stoneChars[game.Board[Pos{x, y}]])
		}
		fmt.Printf("%2d %s\n", y+1, line)
	}
}

func NewGame(boardSize BoardSize) Game {
	return Game{
		BoardSize: boardSize,
		Board:     make(map[Pos]Stone),
	}
}

func main() {
	game := NewGame(BoardSize{19, 19})
	moves := []Move{{PlaceBlack, Pos{0, 0}}, {PlaceWhite, Pos{1, 6}}}
	game.AddMoves(moves)

	game.PrintBoard()

	// fmt.Println(game.Board[Pos{}])
	// fmt.Println(game.Board[Pos{1, 1}])

	// fmt.Println(moves)
}
<commit_msg>Now printing out moves on one line.<commit_after>// Basic data structures for Go.
package main

import (
	"fmt"
)

// Letters for quick lookup.
// These are the letters marked on top of the board.  "I" is
// always left out, likely to avoid confusion with "1".
const BoardLetters = "ABCDEFGHJKLMNOPQRST"
const ActionChars = "cbw"

// Here it would be nice if the type system allows us to say
// "integer with one of these value: 0, 1, 2.
type Stone int

const (
	// Stone colors. Used for board state.
	Empty = 0 // No stone here.
	Black = 1
	White = 2

	// Moves. These correspond to board state making it easy to
	// translate between the two. (A capture will clear that position on
	// the board.)
	Capture    = 0
	PlaceBlack = 1
	PlaceWhite = 2
)

// Position where a stone can be placed.
type Pos struct {
	X int // Left to right, 0-18 (marked as 1-19 on board.)
	Y int // Bottom to top, 0-18 (ABCDEFGHJKLMNOPQRST.)
}

type BoardSize struct {
	Width  int
	Height int
}

// A move. (Place or capture stone.)
type Move struct {
	Action Stone // (Hmm, this looks a bit weird here.)
	Pos
}

func (move Move) String() string {
	// Todo: check if values are out of range?
	color := ActionChars[move.Action]
	letter := BoardLetters[move.X]
	number := move.Y + 1

	return fmt.Sprintf("%c%c%d", color, letter, number)
}

// The board is just a cache of moves. We can recompute the board from
// the moves.
type Game struct {
	BoardSize
	Board map[Pos]Stone
	Moves []Move
	// Here we should have a stack (or slice) of boards so that we have
	// a history of previous board states.

	// + We need to keep score somehow. (Number of captured stones for
	// each player.)
}

func (game *Game) AddMove(move Move) {
	game.Moves = append(game.Moves, move)
	game.Board[move.Pos] = move.Action
}

func (game *Game) AddMoves(moves []Move) {
	for _, move := range moves {
		game.AddMove(move)
	}
}

func (game *Game) PrintBoard() {
	stoneChars := ".bw"

	fmt.Println("   " + BoardLetters)

	for y := game.Height - 1; y >= 0; y-- {
		line := ""
		for x := 0; x < game.Width; x++ {
			line += fmt.Sprintf("%c", stoneChars[game.Board[Pos{x, y}]])
		}
		fmt.Printf("%2d %s\n", y+1, line)
	}
}

func NewGame(boardSize BoardSize) Game {
	return Game{
		BoardSize: boardSize,
		Board:     make(map[Pos]Stone),
	}
}

func main() {
	game := NewGame(BoardSize{19, 19})
	moves := []Move{{PlaceBlack, Pos{0, 0}}, {PlaceWhite, Pos{1, 6}}}
	fmt.Println()
	fmt.Println("Moves:", moves)
	fmt.Println()
	game.AddMoves(moves)
	game.PrintBoard()

	// fmt.Println(game.Board[Pos{}])
	// fmt.Println(game.Board[Pos{1, 1}])

	// fmt.Println(moves)
}
<|endoftext|>