<commit_before>package command

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/gofmt"
	"github.com/funkygao/termui"
	"github.com/nsf/termbox-go"
)

type Histogram struct {
	Ui  cli.Ui
	Cmd string

	offsetFile  string
	networkFile string // consul exec ifconfig bond0 | grep 'RX bytes' | awk '{print $1,$3,$7}' | sort
	drawMode    bool
}

func (this *Histogram) Run(args []string) (exitCode int) {
	cmdFlags := flag.NewFlagSet("histogram", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.BoolVar(&this.drawMode, "d", false, "")
	cmdFlags.StringVar(&this.offsetFile, "f", "/var/wd/topics_offsets/offsets", "")
	cmdFlags.StringVar(&this.networkFile, "n", "/var/wd/topics_offsets/network", "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	offsetTs, offsets := this.showOffsetGrowth()
	netTs, rx, tx := this.showNetworkGrowth()

	if this.drawMode {
		this.drawAll(offsetTs, offsets, netTs, rx, tx)
	}

	return
}

func (this *Histogram) showOffsetGrowth() ([]time.Time, []int64) {
	f, err := os.OpenFile(this.offsetFile, os.O_RDONLY, 0660)
	swallow(err)
	defer f.Close()

	ts := make([]time.Time, 0)
	vs := make([]int64, 0)

	r := bufio.NewReader(f)
	var (
		lastN = int64(0)
		tm    string
	)

	for {
		line, err := r.ReadString('\n')
		if err == io.EOF {
			break
		}

		line = strings.TrimSpace(line)

		if !strings.Contains(line, "CUM Messages") {
			// time info: Thu Jun 16 22:45:01 CST 2016
			tm = line
		} else {
			// offset:            -CUM Messages- 255,705,684,384
			n := strings.Split(line, "-CUM Messages-")[1]
			n = strings.Replace(n, ",", "", -1)
			n = strings.TrimSpace(n)
			offset, err := strconv.ParseInt(n, 10, 64)
			swallow(err)
			if lastN > 0 {
				t, e := time.Parse("Mon Jan 2 15:04:05 MST 2006", tm)
				swallow(e)
				ts = append(ts, t)
				vs = append(vs, offset-lastN)

				this.Ui.Output(fmt.Sprintf("%55s Message+ %15s/%s", tm,
					gofmt.Comma(offset-lastN), gofmt.Comma(lastN)))
			}

			lastN = offset
		}
	}

	return ts, vs
}

func (this *Histogram) showNetworkGrowth() ([]time.Time, []int64, []int64) {
	f, err := os.OpenFile(this.networkFile, os.O_RDONLY, 0660)
	swallow(err)
	defer f.Close()

	ts := make([]time.Time, 0)
	rx := make([]int64, 0)
	tx := make([]int64, 0)

	r := bufio.NewReader(f)
	var (
		lastRx           = int64(0)
		lastTx           = int64(0)
		rxTotal, txTotal int64
		tm               string
	)

	for {
		// CDM1C01-209018015: bytes:98975866482403 bytes:115679008715688
		line, err := r.ReadString('\n')
		if err == io.EOF {
			if lastRx > 0 {
				t, e := time.Parse("Mon Jan 2 15:04:05 MST 2006", tm)
				swallow(e)
				ts = append(ts, t)
				rx = append(rx, rxTotal-lastRx)
				tx = append(tx, txTotal-lastTx)

				this.Ui.Output(fmt.Sprintf("%55s    RX+:%10s/%-10s TX+:%10s/%-10s",
					tm, gofmt.ByteSize(rxTotal-lastRx), gofmt.ByteSize(lastRx),
					gofmt.ByteSize(txTotal-lastTx), gofmt.ByteSize(lastTx)))
			}

			break
		}

		line = strings.TrimSpace(line)
		if !strings.Contains(line, "bytes") {
			// time info: Thu Jun 16 22:45:01 CST 2016

			if lastRx > 0 {
				t, e := time.Parse("Mon Jan 2 15:04:05 MST 2006", tm)
				swallow(e)
				ts = append(ts, t)
				rx = append(rx, rxTotal-lastRx)
				tx = append(tx, txTotal-lastTx)

				this.Ui.Output(fmt.Sprintf("%55s    RX+:%10s/%-10s TX+:%10s/%-10s",
					tm, gofmt.ByteSize(rxTotal-lastRx), gofmt.ByteSize(lastRx),
					gofmt.ByteSize(txTotal-lastTx), gofmt.ByteSize(lastTx)))
			}

			tm = line

			lastRx = rxTotal
			lastTx = txTotal
			rxTotal = 0
			txTotal = 0
		} else {
			// CDM1C01-209018015: bytes:98975866482403 bytes:115679008715688
			parts := strings.Split(line, " ")
			//host := strings.TrimRight(parts[0], ":")
			rxBytes := strings.Split(parts[1], ":")[1]
			txBytes := strings.Split(parts[2], ":")[1]

			n, err := strconv.ParseInt(rxBytes, 10, 64)
			swallow(err)
			rxTotal += n

			n, err = strconv.ParseInt(txBytes, 10, 64)
			swallow(err)
			txTotal += n
		}
	}

	return ts, rx, tx
}

func (this *Histogram) drawAll(offsetTs []time.Time, offsets []int64,
	netTs []time.Time, rx []int64, tx []int64) {
	err := termui.Init()
	swallow(err)
	defer termui.Close()

	termui.UseTheme("helloworld")

	w, h := termbox.Size()

	bc1 := termui.NewBarChart()
	bc1.Border.Label = "Messages Produced/in million"
	data := make([]int, 0)
	for _, off := range offsets {
		data = append(data, int(off/1000000)) // in million
	}
	bclabels := make([]string, 0)
	for _, t := range offsetTs {
		bclabels = append(bclabels, fmt.Sprintf("%02d", t.Hour()))
	}
	bc1.Data = data
	bc1.Width = w
	bc1.SetY(0)
	bc1.Height = h / 3
	bc1.DataLabels = bclabels
	bc1.TextColor = termui.ColorWhite
	bc1.BarColor = termui.ColorRed
	bc1.NumColor = termui.ColorYellow

	bclabels = make([]string, 0) // shared between bc2 and bc3
	for _, t := range netTs {
		bclabels = append(bclabels, fmt.Sprintf("%02d", t.Hour()))
	}

	bc2 := termui.NewBarChart()
	bc2.Border.Label = "Network RX/in GB"
	data = make([]int, 0)
	for _, r := range rx {
		data = append(data, int(r>>30)) // in GB
	}
	bc2.Data = data
	bc2.Width = w
	bc2.SetY(h / 3)
	bc2.Height = h / 3
	bc2.DataLabels = bclabels
	bc2.TextColor = termui.ColorGreen
	bc2.BarColor = termui.ColorRed
	bc2.NumColor = termui.ColorYellow

	bc3 := termui.NewBarChart()
	bc3.Border.Label = "Network TX/in GB"
	data = make([]int, 0)
	for _, t := range tx {
		data = append(data, int(t>>30)) // in GB
	}
	bc3.Data = data
	bc3.Width = w
	bc3.SetY(h * 2 / 3)
	bc3.Height = h / 3
	bc3.DataLabels = bclabels
	bc3.TextColor = termui.ColorGreen
	bc3.BarColor = termui.ColorRed
	bc3.NumColor = termui.ColorYellow

	termui.Render(bc1, bc2, bc3)

	termbox.PollEvent()
}

func (*Histogram) Synopsis() string {
	return "Histogram of kafka produced messages and network volumn"
}

func (this *Histogram) Help() string {
	help := fmt.Sprintf(`
	Usage: %s histogram [options]

	    Histogram of kafka produced messages and network volumn

	Options:
	    -d
	      Draw mode.

	    -f offset file

	    -n network volumn file

	`, this.Cmd)
	return strings.TrimSpace(help)
}
<commit_msg>network volumn in 10GB<commit_after>package command

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/gofmt"
	"github.com/funkygao/termui"
	"github.com/nsf/termbox-go"
)

type Histogram struct {
	Ui  cli.Ui
	Cmd string

	offsetFile  string
	networkFile string // consul exec ifconfig bond0 | grep 'RX bytes' | awk '{print $1,$3,$7}' | sort
	drawMode    bool
}

func (this *Histogram) Run(args []string) (exitCode int) {
	cmdFlags := flag.NewFlagSet("histogram", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.BoolVar(&this.drawMode, "d", false, "")
	cmdFlags.StringVar(&this.offsetFile, "f", "/var/wd/topics_offsets/offsets", "")
	cmdFlags.StringVar(&this.networkFile, "n", "/var/wd/topics_offsets/network", "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	offsetTs, offsets := this.showOffsetGrowth()
	netTs, rx, tx := this.showNetworkGrowth()

	if this.drawMode {
		this.drawAll(offsetTs, offsets, netTs, rx, tx)
	}

	return
}

func (this *Histogram) showOffsetGrowth() ([]time.Time, []int64) {
	f, err := os.OpenFile(this.offsetFile, os.O_RDONLY, 0660)
	swallow(err)
	defer f.Close()

	ts := make([]time.Time, 0)
	vs := make([]int64, 0)

	r := bufio.NewReader(f)
	var (
		lastN = int64(0)
		tm    string
	)

	for {
		line, err := r.ReadString('\n')
		if err == io.EOF {
			break
		}

		line = strings.TrimSpace(line)

		if !strings.Contains(line, "CUM Messages") {
			// time info: Thu Jun 16 22:45:01 CST 2016
			tm = line
		} else {
			// offset:            -CUM Messages- 255,705,684,384
			n := strings.Split(line, "-CUM Messages-")[1]
			n = strings.Replace(n, ",", "", -1)
			n = strings.TrimSpace(n)
			offset, err := strconv.ParseInt(n, 10, 64)
			swallow(err)
			if lastN > 0 {
				t, e := time.Parse("Mon Jan 2 15:04:05 MST 2006", tm)
				swallow(e)
				ts = append(ts, t)
				vs = append(vs, offset-lastN)

				this.Ui.Output(fmt.Sprintf("%55s Message+ %15s/%s", tm,
					gofmt.Comma(offset-lastN), gofmt.Comma(lastN)))
			}

			lastN = offset
		}
	}

	return ts, vs
}

func (this *Histogram) showNetworkGrowth() ([]time.Time, []int64, []int64) {
	f, err := os.OpenFile(this.networkFile, os.O_RDONLY, 0660)
	swallow(err)
	defer f.Close()

	ts := make([]time.Time, 0)
	rx := make([]int64, 0)
	tx := make([]int64, 0)

	r := bufio.NewReader(f)
	var (
		lastRx           = int64(0)
		lastTx           = int64(0)
		rxTotal, txTotal int64
		tm               string
	)

	for {
		// CDM1C01-209018015: bytes:98975866482403 bytes:115679008715688
		line, err := r.ReadString('\n')
		if err == io.EOF {
			if lastRx > 0 {
				t, e := time.Parse("Mon Jan 2 15:04:05 MST 2006", tm)
				swallow(e)
				ts = append(ts, t)
				rx = append(rx, rxTotal-lastRx)
				tx = append(tx, txTotal-lastTx)

				this.Ui.Output(fmt.Sprintf("%55s    RX+:%10s/%-10s TX+:%10s/%-10s",
					tm, gofmt.ByteSize(rxTotal-lastRx), gofmt.ByteSize(lastRx),
					gofmt.ByteSize(txTotal-lastTx), gofmt.ByteSize(lastTx)))
			}

			break
		}

		line = strings.TrimSpace(line)
		if !strings.Contains(line, "bytes") {
			// time info: Thu Jun 16 22:45:01 CST 2016

			if lastRx > 0 {
				t, e := time.Parse("Mon Jan 2 15:04:05 MST 2006", tm)
				swallow(e)
				ts = append(ts, t)
				rx = append(rx, rxTotal-lastRx)
				tx = append(tx, txTotal-lastTx)

				this.Ui.Output(fmt.Sprintf("%55s    RX+:%10s/%-10s TX+:%10s/%-10s",
					tm, gofmt.ByteSize(rxTotal-lastRx), gofmt.ByteSize(lastRx),
					gofmt.ByteSize(txTotal-lastTx), gofmt.ByteSize(lastTx)))
			}

			tm = line

			lastRx = rxTotal
			lastTx = txTotal
			rxTotal = 0
			txTotal = 0
		} else {
			// CDM1C01-209018015: bytes:98975866482403 bytes:115679008715688
			parts := strings.Split(line, " ")
			//host := strings.TrimRight(parts[0], ":")
			rxBytes := strings.Split(parts[1], ":")[1]
			txBytes := strings.Split(parts[2], ":")[1]

			n, err := strconv.ParseInt(rxBytes, 10, 64)
			swallow(err)
			rxTotal += n

			n, err = strconv.ParseInt(txBytes, 10, 64)
			swallow(err)
			txTotal += n
		}
	}

	return ts, rx, tx
}

func (this *Histogram) drawAll(offsetTs []time.Time, offsets []int64,
	netTs []time.Time, rx []int64, tx []int64) {
	err := termui.Init()
	swallow(err)
	defer termui.Close()

	termui.UseTheme("helloworld")

	w, h := termbox.Size()

	bc1 := termui.NewBarChart()
	bc1.Border.Label = "Messages Produced/in million"
	data := make([]int, 0)
	for _, off := range offsets {
		data = append(data, int(off/1000000)) // in million
	}
	bclabels := make([]string, 0)
	for _, t := range offsetTs {
		bclabels = append(bclabels, fmt.Sprintf("%02d", t.Hour()))
	}
	bc1.Data = data
	bc1.Width = w
	bc1.SetY(0)
	bc1.Height = h / 3
	bc1.DataLabels = bclabels
	bc1.TextColor = termui.ColorWhite
	bc1.BarColor = termui.ColorRed
	bc1.NumColor = termui.ColorYellow

	bclabels = make([]string, 0) // shared between bc2 and bc3
	for _, t := range netTs {
		bclabels = append(bclabels, fmt.Sprintf("%02d", t.Hour()))
	}

	bc2 := termui.NewBarChart()
	bc2.Border.Label = "Network RX/in 10GB"
	data = make([]int, 0)
	for _, r := range rx {
		data = append(data, int(r>>30)/10)
	}
	bc2.Data = data
	bc2.Width = w
	bc2.SetY(h / 3)
	bc2.Height = h / 3
	bc2.DataLabels = bclabels
	bc2.TextColor = termui.ColorGreen
	bc2.BarColor = termui.ColorRed
	bc2.NumColor = termui.ColorYellow

	bc3 := termui.NewBarChart()
	bc3.Border.Label = "Network TX/in 10GB"
	data = make([]int, 0)
	for _, t := range tx {
		data = append(data, int(t>>30)/10)
	}
	bc3.Data = data
	bc3.Width = w
	bc3.SetY(h * 2 / 3)
	bc3.Height = h / 3
	bc3.DataLabels = bclabels
	bc3.TextColor = termui.ColorGreen
	bc3.BarColor = termui.ColorRed
	bc3.NumColor = termui.ColorYellow

	termui.Render(bc1, bc2, bc3)

	termbox.PollEvent()
}

func (*Histogram) Synopsis() string {
	return "Histogram of kafka produced messages and network volumn"
}

func (this *Histogram) Help() string {
	help := fmt.Sprintf(`
	Usage: %s histogram [options]

	    Histogram of kafka produced messages and network volumn

	Options:
	    -d
	      Draw mode.

	    -f offset file

	    -n network volumn file

	`, this.Cmd)
	return strings.TrimSpace(help)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

const NOT_SPACE = "[^\\s]"

func readLine(scanner *bufio.Scanner, replace_lines IntSet, replace_strings StrSlice) {

	scanner.Split(bufio.ScanLines)

	line := 1

	ra, _ := regexp.Compile(NOT_SPACE)

	var buffer []string

	for scanner.Scan() {
		_, s := replace_lines[line]
		if s {
			buffer = append(buffer, ra.ReplaceAllString(scanner.Text(), "-"))
		} else {
			if len(replace_strings) > 0 {
				str := scanner.Text()
				for _, rs := range replace_strings {
					str = strings.Replace(str, rs, strings.Repeat("-", len(rs)), -1)
				}
				buffer = append(buffer, str)
			} else {
				buffer = append(buffer, scanner.Text())
			}
		}
		line += 1
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	// print preview
	for k, v := range buffer {
		fmt.Println(k+1, v)
	}

}

func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// set of ints
type IntSet map[int]struct{}

// set of strings
type StrSlice []string

func (i *IntSet) String() string {
	return fmt.Sprint(*i)
}

func (i *IntSet) Set(value string) error {
	if len(*i) > 0 {
		return errors.New("line flag already set")
	}
	for _, n := range strings.Split(value, ",") {
		num, err := strconv.Atoi(n)
		if err != nil {
			continue
		}
		if _, found := (*i)[num]; found {
			continue
		}
		(*i)[num] = struct{}{}
	}
	return nil
}

func (s *StrSlice) String() string {
	return fmt.Sprint(*s)
}

func (s *StrSlice) Set(value string) error {
	*s = append(*s, value)
	return nil
}

func main() {

	var replace_lines = IntSet{}
	var replace_strings StrSlice

	flag.Var(&replace_lines, "l", ">>>>>>>>>>>>>>>>> l")
	flag.Var(&replace_strings, "r", ">>>>>>>>>>>>>>> r")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [-lr] file\n\n", os.Args[0])
		fmt.Printf("  example: %s -l 3,7 -r secret -r 'my passphrase' file.conf\n\n", os.Args[0])
		fmt.Println("  -l: Number of the line(s) to be replaced, comma separated.")
		fmt.Println("  -r: Word to be replaced, can be used multiple times.")
		fmt.Println("  -p: Push the gist.")
	}

	flag.Parse()

	if flag.NArg() != 1 {
		fmt.Fprintf(os.Stderr, "Usage: %s [-lrh] file, use -h for more info\n\n", os.Args[0])
		os.Exit(1)
	}

	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) == 0 {
		readLine(bufio.NewScanner(os.Stdin), replace_lines, replace_strings)
	} else {
		f := flag.Arg(0)
		if Exists(f) {
			file, err := os.Open(f)
			defer file.Close()
			if err != nil {
				log.Fatal(err)
			}
			readLine(bufio.NewScanner(file), replace_lines, replace_strings)
		} else {
			fmt.Printf("Cannot read file: %s\n", f)
		}
	}
}
<commit_msg>	modified:   gist.go<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

const NOT_SPACE = "[^\\s]"

func readLine(scanner *bufio.Scanner, replace_lines IntSet, replace_strings StrSlice) {

	scanner.Split(bufio.ScanLines)

	line := 1

	ra, _ := regexp.Compile(NOT_SPACE)

	var buffer []string

	for scanner.Scan() {
		_, s := replace_lines[line]
		if s {
			buffer = append(buffer, ra.ReplaceAllString(scanner.Text(), "-"))
		} else {
			if len(replace_strings) > 0 {
				str := scanner.Text()
				for _, rs := range replace_strings {
					str = strings.Replace(str, rs, strings.Repeat("-", len(rs)), -1)
				}
				buffer = append(buffer, str)
			} else {
				buffer = append(buffer, scanner.Text())
			}
		}
		line += 1
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

	// print preview
	pad := len(fmt.Sprint(len(buffer)))
	for k, v := range buffer {
		fmt.Printf("%*d  %s\n", pad, k, v)
	}

}

func Exists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// set of ints
type IntSet map[int]struct{}

// set of strings
type StrSlice []string

func (i *IntSet) String() string {
	return fmt.Sprint(*i)
}

func (i *IntSet) Set(value string) error {
	if len(*i) > 0 {
		return errors.New("line flag already set")
	}
	for _, n := range strings.Split(value, ",") {
		num, err := strconv.Atoi(n)
		if err != nil {
			continue
		}
		if _, found := (*i)[num]; found {
			continue
		}
		(*i)[num] = struct{}{}
	}
	return nil
}

func (s *StrSlice) String() string {
	return fmt.Sprint(*s)
}

func (s *StrSlice) Set(value string) error {
	*s = append(*s, value)
	return nil
}

func main() {

	var replace_lines = IntSet{}
	var replace_strings StrSlice

	flag.Var(&replace_lines, "l", ">>>>>>>>>>>>>>>>> l")
	flag.Var(&replace_strings, "r", ">>>>>>>>>>>>>>> r")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [-lr] file\n\n", os.Args[0])
		fmt.Printf("  example: %s -l 3,7 -r secret -r 'my passphrase' file.conf\n\n", os.Args[0])
		fmt.Println("  -l: Number of the line(s) to be replaced, comma separated.")
		fmt.Println("  -r: Word to be replaced, can be used multiple times.")
		fmt.Println("  -p: Push the gist.")
	}

	flag.Parse()

	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) == 0 {
		readLine(bufio.NewScanner(os.Stdin), replace_lines, replace_strings)
	} else {
		if flag.NArg() != 1 {
			fmt.Fprintf(os.Stderr, "Usage: %s [-lrh] file, use -h for more info\n\n", os.Args[0])
			os.Exit(1)
		}
		f := flag.Arg(0)
		if Exists(f) {
			file, err := os.Open(f)
			defer file.Close()
			if err != nil {
				log.Fatal(err)
			}
			readLine(bufio.NewScanner(file), replace_lines, replace_strings)
		} else {
			fmt.Printf("Cannot read file: %s\n", f)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	iniconf "code.google.com/p/goconf/conf"
	"crypto/sha512"
	"encoding/json"
	"errors"
	"fmt"
	// "github.com/gamelost/bot3server/server"
	nsq "github.com/gamelost/go-nsq"
	// irc "github.com/gamelost/goirc/client"
	ms "github.com/mitchellh/mapstructure"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

const (
	GLCD_CONFIG = "glcd.config"
)

var gamestateTopic = ""

type Message struct {
	ClientId string
	Type     string // better way to persist type info?
	Data     interface{}
}

type ZoneInfo struct {
	x int
	y int
}

type Zone struct {
	Id    int
	Name  string
	State *ZoneInfo
}

type PlayerInfo struct {
	Name     string
	ClientId string
}

type Players []PlayerInfo

type PlayerState struct {
	ClientId string
	X        float64
	Y        float64
	AvatarId string `json:",omitempty"`
}

type PlayerAuthInfo struct {
	Name     string `bson:"user"`
	Password string `bson:"password"`
}

type Heartbeat struct {
	ClientId  string
	Timestamp time.Time
}

/* Players coming in and out */
type PlayerPassport struct {
	Action string
	Avatar string
}

type ErrorMessage string

type ChatMessage struct {
	Sender  string
	Message string
}

func main() {
	// the quit channel
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	// read in necessary configuration
	configFile, err := iniconf.ReadConfigFile(GLCD_CONFIG)
	if err != nil {
		log.Fatal("Unable to read configuration file. Exiting now.")
	}

	glcd := &GLCD{QuitChan: sigChan}
	glcd.init(configFile)

	// receiving quit shuts down
	<-glcd.QuitChan
}

type GLCClient struct {
	Name          string
	ClientId      string
	Authenticated bool
	State         *PlayerState
	Heartbeat     time.Time
}

// struct type for Bot3
type GLCD struct {
	Online     bool
	ConfigFile *iniconf.ConfigFile

	// NSQ input/output
	NSQWriter             *nsq.Writer
	GLCDaemonTopic        *nsq.Reader
	GLCGameStateTopicName string
	GLCDaemonTopicChannel string
	Clients               map[string]*GLCClient

	// game state channels
	HeartbeatChan   chan *Heartbeat
	KnockChan       chan *GLCClient
	AuthChan        chan *PlayerAuthInfo
	PlayerStateChan chan *PlayerState

	QuitChan chan os.Signal

	MongoSession *mgo.Session
	MongoDB      *mgo.Database
}

func (glcd *GLCD) init(conf *iniconf.ConfigFile) error {

	glcd.ConfigFile = conf
	glcd.Online = false

	glcd.Clients = map[string]*GLCClient{}

	// Connect to Mongo.
	servers, err := glcd.ConfigFile.GetString("mongo", "servers")

	if err != nil {
		return fmt.Errorf("Mongo: No server configured.")
	}

	glcd.MongoSession, err = mgo.Dial(servers)

	if err != nil {
	}

	db, err := glcd.ConfigFile.GetString("mongo", "db")

	if err != nil {
		return fmt.Errorf("Mongo: No database configured.")
	} else {
		fmt.Println("Successfully obtained config from mongo")
	}

	glcd.MongoDB = glcd.MongoSession.DB(db)

	// set up channels
	glcd.HeartbeatChan = make(chan *Heartbeat)
	glcd.KnockChan = make(chan *GLCClient)
	glcd.AuthChan = make(chan *PlayerAuthInfo)
	glcd.PlayerStateChan = make(chan *PlayerState)

	nsqdAddress, _ := conf.GetString("nsq", "nsqd-address")
	lookupdAddress, _ := conf.GetString("nsq", "lookupd-address")
	glcd.GLCGameStateTopicName, _ = conf.GetString("nsq", "server-topic")

	glcdTopic, _ := conf.GetString("nsq", "glcd-topic")

	// Create the channel, by connecting to lookupd. (TODO; if it doesn't
	// exist. Also do it the right way with a Register command?)
	glcd.NSQWriter = nsq.NewWriter(nsqdAddress)
	glcd.NSQWriter.Publish(glcd.GLCGameStateTopicName, []byte("{\"client\":\"server\"}"))

	// set up reader for glcdTopic
	reader, err := nsq.NewReader(glcdTopic, "main")
	if err != nil {
		glcd.QuitChan <- syscall.SIGINT
	}
	glcd.GLCDaemonTopic = reader
	glcd.GLCDaemonTopic.AddHandler(glcd)
	glcd.GLCDaemonTopic.ConnectToLookupd(lookupdAddress)

	// goroutines to handle concurrent events
	go glcd.CleanupClients()
	go glcd.HandlePlayerAuthChannel()
	go glcd.HandleHeartbeatChannel()
	go glcd.HandleKnockChannel()
	go glcd.HandlePlayerStateChannel()

	return nil
}

func (glcd *GLCD) Publish(msg *Message) {
	encodedRequest, _ := json.Marshal(*msg)
	glcd.NSQWriter.Publish(glcd.GLCGameStateTopicName, encodedRequest)
}

func (glcd *GLCD) HandlePlayerStateChannel() {
	for {
		ps := <-glcd.PlayerStateChan
		glcd.Publish(&Message{Type: "playerState", Data: ps})
	}
}

func (glcd *GLCD) HandleHeartbeatChannel() {
	for {
		hb := <-glcd.HeartbeatChan
		//fmt.Printf("HandleHeartbeatChannel: Received heartbeat: %+v\n", hb)

		// see if key and client exists in the map
		c, exists := glcd.Clients[hb.ClientId]

		if exists {
			//fmt.Printf("Client %s exists.  Updating heartbeat.\n", hb.ClientId)
			c.Heartbeat = time.Now()
		} else {
			//fmt.Printf("Adding client %s to client list\n", hb.ClientId)
			client := &GLCClient{ClientId: hb.ClientId, Heartbeat: time.Now(), Authenticated: false}
			glcd.Clients[hb.ClientId] = client
		}
	}
}

func (glcd *GLCD) HandleKnockChannel() error {
	for {
		client := <-glcd.KnockChan
		fmt.Printf("Received knock from %s\n", client.ClientId)
		players := make(Players, len(glcd.Clients))

		i := 0
		for _, c := range glcd.Clients {
			players[i] = PlayerInfo{ClientId: c.ClientId}
			i++
		}

		glcd.Publish(&Message{ClientId: client.ClientId, Type: "knock", Data: players})
	}
}

func (glcd *GLCD) CleanupClients() error {
	for {
		exp := time.Now().Unix()
		<-time.After(time.Second * 10)
		//fmt.Println("Doing client clean up")
		// Expire any clients who haven't sent a heartbeat in the last 10 seconds.
		for k, v := range glcd.Clients {
			if v.Heartbeat.Unix() < exp {
				fmt.Printf("Deleting client %s due to inactivity.\n", v.ClientId)
				delete(glcd.Clients, k)
				//glcd.Publish(&Message{Type: "playerPassport", Data: PlayerPassport{Action: "playerGone"}}) // somehow add k to this
			} else {
				//fmt.Printf("Client has not expired.")
			}
		}
	}
}

// Send a zone file update.
func (glcd *GLCD) SendZones() {
	fmt.Println("SendZones --")
	c := glcd.MongoDB.C("zones")
	q := c.Find(nil)

	if q == nil {
		glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("No zones found")})
	} else {
		fmt.Println("Publishing zones to clients")
		var results []interface{}
		err := q.All(&results)
		if err == nil {
			for _, res := range results {
				fmt.Printf("Res: is %+v", res)
				glcd.Publish(&Message{Type: "updateZone", Data: res.(bson.M)}) // dump res as a JSON string
			}
		} else {
			glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("Unable to fetch zones: %v", err)})
		}
	}
}

func (glcd *GLCD) HandleChatMessage(msg *Message, data interface{}) {
	glcd.Publish(msg)
}

func (glcd *GLCD) SendZone(zone *Zone) {
	c := glcd.MongoDB.C("zones")
	query := bson.M{"zone": zone.Name}
	results := c.Find(query)

	if results == nil {
		glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("No such zone '%s'", zone.Name)})
	} else {
		var res interface{}
		err := results.One(&res)
		if err == nil {
			glcd.Publish(&Message{Type: "zone", Data: res.(string)})
		} else {
			glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("Unable to fetch zone: %v", err)})
		}
	}
}

// Send a zone file update.
func (glcd *GLCD) UpdateZone(zone *Zone) {
	query := bson.M{"zone": zone.Name}
	zdata := ZoneInfo{}
	c := glcd.MongoDB.C("zones")
	val := bson.M{"type": "zone", "zdata": zdata, "timestamp": time.Now()}
	change := bson.M{"$set": val}

	err := c.Update(query, change)

	if err == mgo.ErrNotFound {
		val["id"], _ = c.Count()
		change = bson.M{"$set": val}
		err = c.Update(query, change)
	}

	if err != nil {
		glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("Unable to update zone: %v", err)})
	} else {
		glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("Updated zone '%s'", zone.Name)})
	}
}

func (glcd *GLCD) HandleMessage(nsqMessage *nsq.Message) error {

	// fmt.Println("-------")
	// fmt.Printf("Received message %s\n\n", nsqMessage.Body)
	// fmt.Println("-------")
	msg := &Message{}

	err := json.Unmarshal(nsqMessage.Body, &msg)

	if err != nil {
		fmt.Printf(err.Error())
	}

	var dataMap map[string]interface{}
	var ok bool

	if msg.Data != nil {
		dataMap, ok = msg.Data.(map[string]interface{})
	} else {
		dataMap = make(map[string]interface{})
		ok = true
	}

	if !ok {
		return nil
	}

	if msg.Type == "playerPassport" {
		//		HandlePassport(msg.Data)
	} else if msg.Type == "playerState" {
		var ps PlayerState
		err := ms.Decode(dataMap, &ps)
		if err != nil {
			fmt.Println(err.Error())
		} else {
			ps.ClientId = msg.ClientId
			log.Printf("Player state: %+v\n", ps)
			glcd.PlayerStateChan <- &ps
		}
	} else if msg.Type == "connected" {
		fmt.Println("Received connected from client")
		glcd.SendZones()
	} else if msg.Type == "sendZones" {
		fmt.Println("Received sendZones from client")
		//		HandleZoneUpdate(msg.Data)
	} else if msg.Type == "chat" {
		glcd.HandleChatMessage(msg, msg.Data)
	} else if msg.Type == "heartbeat" {
		hb := &Heartbeat{}
		hb.ClientId = msg.ClientId
		glcd.HeartbeatChan <- hb
	} else if msg.Type == "knock" {
		glcd.KnockChan <- glcd.Clients[msg.ClientId]
	} else if msg.Type == "playerAuth" {
		var pai PlayerAuthInfo
		err := ms.Decode(dataMap, &pai)
		if err != nil {
			fmt.Println(err.Error())
		} else {
			glcd.AuthChan <- &pai
		}
	} else if msg.Type == "error" {
		//		HandleError(msg.Data)
	} else {
		// log.Printf("Unknown Message Type: %s", msg.Type)
	}

	return nil
}

func (glcd *GLCD) isPasswordCorrect(name string, password string) (bool, error) {
	c := glcd.MongoDB.C("users")
	authInfo := PlayerAuthInfo{}
	query := bson.M{"user": name}
	err := c.Find(query).One(&authInfo)

	if err != nil {
		return false, err
	}

	return password == authInfo.Password, nil
}

func generateSaltedPasswordHash(password string, salt []byte) ([]byte, error) {
	hash := sha512.New()
	//hash.Write(server_salt)
	hash.Write(salt)
	hash.Write([]byte(password))
	return hash.Sum(salt), nil
}

func (glcd *GLCD) getUserPasswordHash(name string) ([]byte, error) {
	return nil, nil
}

func (glcd *GLCD) isPasswordCorrectWithHash(name string, password string, salt []byte) (bool, error) {
	expectedHash, err := glcd.getUserPasswordHash(name)

	if err != nil {
		return false, err
	}

	if len(expectedHash) != 32+sha512.Size {
		return false, errors.New("Wrong size")
	}

	actualHash := sha512.New()
	actualHash.Write(salt)
	actualHash.Write([]byte(password))

	return bytes.Equal(actualHash.Sum(nil), expectedHash[32:]), nil
}

func (glcd *GLCD) HandlePlayerAuthChannel() error {
	for {
		authInfo := <-glcd.AuthChan
		fmt.Printf("Received auth for user %s\n", authInfo.Name)

		_, ok := glcd.Clients[authInfo.Name]

		if ok {
			authed, err := glcd.isPasswordCorrect(authInfo.Name, authInfo.Password)

			if err != nil {
				fmt.Printf("User %s %s\n", authInfo.Name, err)
			}

			if authed {
				fmt.Printf("Auth successful for user %s\n", authInfo.Name)
				// ALLOW PLAYERS DO ANYTHING
				// UPDATE glcd.Clients.AUthenticated = true
				glcd.Clients[authInfo.Name].Authenticated = true
			} else {
				fmt.Printf("Auth failed for user %s\n", authInfo.Name)
			}
		} else {
			fmt.Printf("User %s does not exist!\n", authInfo.Name)
		}
	}
}
<commit_msg>Add AvatarState to PlayerState<commit_after>package main

import (
	"bytes"
	iniconf "code.google.com/p/goconf/conf"
	"crypto/sha512"
	"encoding/json"
	"errors"
	"fmt"
	// "github.com/gamelost/bot3server/server"
	nsq "github.com/gamelost/go-nsq"
	// irc "github.com/gamelost/goirc/client"
	ms "github.com/mitchellh/mapstructure"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

const (
	GLCD_CONFIG = "glcd.config"
)

var gamestateTopic = ""

type Message struct {
	ClientId string
	Type     string // better way to persist type info?
	Data     interface{}
}

type ZoneInfo struct {
	x int
	y int
}

type Zone struct {
	Id    int
	Name  string
	State *ZoneInfo
}

type PlayerInfo struct {
	Name     string
	ClientId string
}

type Players []PlayerInfo

type PlayerState struct {
	ClientId string
	X        float64
	Y        float64
	AvatarId string `json:",omitempty"`
	AvatarState int64 `json:",omitempty"`
}

type PlayerAuthInfo struct {
	Name     string `bson:"user"`
	Password string `bson:"password"`
}

type Heartbeat struct {
	ClientId  string
	Timestamp time.Time
}

/* Players coming in and out */
type PlayerPassport struct {
	Action string
	Avatar string
}

type ErrorMessage string

type ChatMessage struct {
	Sender  string
	Message string
}

func main() {
	// the quit channel
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	// read in necessary configuration
	configFile, err := iniconf.ReadConfigFile(GLCD_CONFIG)
	if err != nil {
		log.Fatal("Unable to read configuration file. Exiting now.")
	}

	glcd := &GLCD{QuitChan: sigChan}
	glcd.init(configFile)

	// receiving quit shuts down
	<-glcd.QuitChan
}

type GLCClient struct {
	Name          string
	ClientId      string
	Authenticated bool
	State         *PlayerState
	Heartbeat     time.Time
}

// struct type for Bot3
type GLCD struct {
	Online     bool
	ConfigFile *iniconf.ConfigFile

	// NSQ input/output
	NSQWriter             *nsq.Writer
	GLCDaemonTopic        *nsq.Reader
	GLCGameStateTopicName string
	GLCDaemonTopicChannel string
	Clients               map[string]*GLCClient

	// game state channels
	HeartbeatChan   chan *Heartbeat
	KnockChan       chan *GLCClient
	AuthChan        chan *PlayerAuthInfo
	PlayerStateChan chan *PlayerState

	QuitChan chan os.Signal

	MongoSession *mgo.Session
	MongoDB      *mgo.Database
}

func (glcd *GLCD) init(conf *iniconf.ConfigFile) error {

	glcd.ConfigFile = conf
	glcd.Online = false

	glcd.Clients = map[string]*GLCClient{}

	// Connect to Mongo.
	servers, err := glcd.ConfigFile.GetString("mongo", "servers")

	if err != nil {
		return fmt.Errorf("Mongo: No server configured.")
	}

	glcd.MongoSession, err = mgo.Dial(servers)

	if err != nil {
	}

	db, err := glcd.ConfigFile.GetString("mongo", "db")

	if err != nil {
		return fmt.Errorf("Mongo: No database configured.")
	} else {
		fmt.Println("Successfully obtained config from mongo")
	}

	glcd.MongoDB = glcd.MongoSession.DB(db)

	// set up channels
	glcd.HeartbeatChan = make(chan *Heartbeat)
	glcd.KnockChan = make(chan *GLCClient)
	glcd.AuthChan = make(chan *PlayerAuthInfo)
	glcd.PlayerStateChan = make(chan *PlayerState)

	nsqdAddress, _ := conf.GetString("nsq", "nsqd-address")
	lookupdAddress, _ := conf.GetString("nsq", "lookupd-address")
	glcd.GLCGameStateTopicName, _ = conf.GetString("nsq", "server-topic")

	glcdTopic, _ := conf.GetString("nsq", "glcd-topic")

	// Create the channel, by connecting to lookupd. (TODO; if it doesn't
	// exist. Also do it the right way with a Register command?)
	glcd.NSQWriter = nsq.NewWriter(nsqdAddress)
	glcd.NSQWriter.Publish(glcd.GLCGameStateTopicName, []byte("{\"client\":\"server\"}"))

	// set up reader for glcdTopic
	reader, err := nsq.NewReader(glcdTopic, "main")
	if err != nil {
		glcd.QuitChan <- syscall.SIGINT
	}
	glcd.GLCDaemonTopic = reader
	glcd.GLCDaemonTopic.AddHandler(glcd)
	glcd.GLCDaemonTopic.ConnectToLookupd(lookupdAddress)

	// goroutines to handle concurrent events
	go glcd.CleanupClients()
	go glcd.HandlePlayerAuthChannel()
	go glcd.HandleHeartbeatChannel()
	go glcd.HandleKnockChannel()
	go glcd.HandlePlayerStateChannel()

	return nil
}

func (glcd *GLCD) Publish(msg *Message) {
	encodedRequest, _ := json.Marshal(*msg)
	glcd.NSQWriter.Publish(glcd.GLCGameStateTopicName, encodedRequest)
}

func (glcd *GLCD) HandlePlayerStateChannel() {
	for {
		ps := <-glcd.PlayerStateChan
		glcd.Publish(&Message{Type: "playerState", Data: ps})
	}
}

func (glcd *GLCD) HandleHeartbeatChannel() {
	for {
		hb := <-glcd.HeartbeatChan
		//fmt.Printf("HandleHeartbeatChannel: Received heartbeat: %+v\n", hb)

		// see if key and client exists in the map
		c, exists := glcd.Clients[hb.ClientId]

		if exists {
			//fmt.Printf("Client %s exists.  Updating heartbeat.\n", hb.ClientId)
			c.Heartbeat = time.Now()
		} else {
			//fmt.Printf("Adding client %s to client list\n", hb.ClientId)
			client := &GLCClient{ClientId: hb.ClientId, Heartbeat: time.Now(), Authenticated: false}
			glcd.Clients[hb.ClientId] = client
		}
	}
}

func (glcd *GLCD) HandleKnockChannel() error {
	for {
		client := <-glcd.KnockChan
		fmt.Printf("Received knock from %s\n", client.ClientId)
		players := make(Players, len(glcd.Clients))

		i := 0
		for _, c := range glcd.Clients {
			players[i] = PlayerInfo{ClientId: c.ClientId}
			i++
		}

		glcd.Publish(&Message{ClientId: client.ClientId, Type: "knock", Data: players})
	}
}

func (glcd *GLCD) CleanupClients() error {
	for {
		exp := time.Now().Unix()
		<-time.After(time.Second * 10)
		//fmt.Println("Doing client clean up")
		// Expire any clients who haven't sent a heartbeat in the last 10 seconds.
		for k, v := range glcd.Clients {
			if v.Heartbeat.Unix() < exp {
				fmt.Printf("Deleting client %s due to inactivity.\n", v.ClientId)
				delete(glcd.Clients, k)
				//glcd.Publish(&Message{Type: "playerPassport", Data: PlayerPassport{Action: "playerGone"}}) // somehow add k to this
			} else {
				//fmt.Printf("Client has not expired.")
			}
		}
	}
}

// Send a zone file update.
func (glcd *GLCD) SendZones() {
	fmt.Println("SendZones --")
	c := glcd.MongoDB.C("zones")
	q := c.Find(nil)

	if q == nil {
		glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("No zones found")})
	} else {
		fmt.Println("Publishing zones to clients")
		var results []interface{}
		err := q.All(&results)
		if err == nil {
			for _, res := range results {
				fmt.Printf("Res: is %+v", res)
				glcd.Publish(&Message{Type: "updateZone", Data: res.(bson.M)}) // dump res as a JSON string
			}
		} else {
			glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("Unable to fetch zones: %v", err)})
		}
	}
}

func (glcd *GLCD) HandleChatMessage(msg *Message, data interface{}) {
	glcd.Publish(msg)
}

func (glcd *GLCD) SendZone(zone *Zone) {
	c := glcd.MongoDB.C("zones")
	query := bson.M{"zone": zone.Name}
	results := c.Find(query)

	if results == nil {
		glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("No such zone '%s'", zone.Name)})
	} else {
		var res interface{}
		err := results.One(&res)
		if err == nil {
			glcd.Publish(&Message{Type: "zone", Data: res.(string)})
		} else {
			glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("Unable to fetch zone: %v", err)})
		}
	}
}

// Send a zone file update.
func (glcd *GLCD) UpdateZone(zone *Zone) {
	query := bson.M{"zone": zone.Name}
	zdata := ZoneInfo{}
	c := glcd.MongoDB.C("zones")
	val := bson.M{"type": "zone", "zdata": zdata, "timestamp": time.Now()}
	change := bson.M{"$set": val}

	err := c.Update(query, change)

	if err == mgo.ErrNotFound {
		val["id"], _ = c.Count()
		change = bson.M{"$set": val}
		err = c.Update(query, change)
	}

	if err != nil {
		glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("Unable to update zone: %v", err)})
	} else {
		glcd.Publish(&Message{Type: "error", Data: fmt.Sprintf("Updated zone '%s'", zone.Name)})
	}
}

func (glcd *GLCD) HandleMessage(nsqMessage *nsq.Message) error {

	// fmt.Println("-------")
	// fmt.Printf("Received message %s\n\n", nsqMessage.Body)
	// fmt.Println("-------")
	msg := &Message{}

	err := json.Unmarshal(nsqMessage.Body, &msg)

	if err != nil {
		fmt.Printf(err.Error())
	}

	var dataMap map[string]interface{}
	var ok bool

	if msg.Data != nil {
		dataMap, ok = msg.Data.(map[string]interface{})
	} else {
		dataMap = make(map[string]interface{})
		ok = true
	}

	if !ok {
		return nil
	}

	if msg.Type == "playerPassport" {
		//		HandlePassport(msg.Data)
	} else if msg.Type == "playerState" {
		var ps PlayerState
		err := ms.Decode(dataMap, &ps)
		if err != nil {
			fmt.Println(err.Error())
		} else {
			ps.ClientId = msg.ClientId
			log.Printf("Player state: %+v\n", ps)
			glcd.PlayerStateChan <- &ps
		}
	} else if msg.Type == "connected" {
		fmt.Println("Received connected from client")
		glcd.SendZones()
	} else if msg.Type == "sendZones" {
		fmt.Println("Received sendZones from client")
		//		HandleZoneUpdate(msg.Data)
	} else if msg.Type == "chat" {
		glcd.HandleChatMessage(msg, msg.Data)
	} else if msg.Type == "heartbeat" {
		hb := &Heartbeat{}
		hb.ClientId = msg.ClientId
		glcd.HeartbeatChan <- hb
	} else if msg.Type == "knock" {
		glcd.KnockChan <- glcd.Clients[msg.ClientId]
	} else if msg.Type == "playerAuth" {
		var pai PlayerAuthInfo
		err := ms.Decode(dataMap, &pai)
		if err != nil {
			fmt.Println(err.Error())
		} else {
			glcd.AuthChan <- &pai
		}
	} else if msg.Type == "error" {
		//		HandleError(msg.Data)
	} else {
		// log.Printf("Unknown Message Type: %s", msg.Type)
	}

	return nil
}

func (glcd *GLCD) isPasswordCorrect(name string, password string) (bool, error) {
	c := glcd.MongoDB.C("users")
	authInfo := PlayerAuthInfo{}
	query := bson.M{"user": name}
	err := c.Find(query).One(&authInfo)

	if err != nil {
		return false, err
	}

	return password == authInfo.Password, nil
}

func generateSaltedPasswordHash(password string, salt []byte) ([]byte, error) {
	hash := sha512.New()
	//hash.Write(server_salt)
	hash.Write(salt)
	hash.Write([]byte(password))
	return hash.Sum(salt), nil
}

func (glcd *GLCD) getUserPasswordHash(name string) ([]byte, error) {
	return nil, nil
}

func (glcd *GLCD) isPasswordCorrectWithHash(name string, password string, salt []byte) (bool, error) {
	expectedHash, err := glcd.getUserPasswordHash(name)

	if err != nil {
		return false, err
	}

	if len(expectedHash) != 32+sha512.Size {
		return false, errors.New("Wrong size")
	}

	actualHash := sha512.New()
	actualHash.Write(salt)
	actualHash.Write([]byte(password))

	return bytes.Equal(actualHash.Sum(nil), expectedHash[32:]), nil
}

func (glcd *GLCD) HandlePlayerAuthChannel() error {
	for {
		authInfo := <-glcd.AuthChan
		fmt.Printf("Received auth for user %s\n", authInfo.Name)

		_, ok := glcd.Clients[authInfo.Name]

		if ok {
			authed, err := glcd.isPasswordCorrect(authInfo.Name, authInfo.Password)

			if err != nil {
				fmt.Printf("User %s %s\n", authInfo.Name, err)
			}

			if authed {
				fmt.Printf("Auth successful for user %s\n", authInfo.Name)
				// ALLOW PLAYERS DO ANYTHING
				// UPDATE glcd.Clients.AUthenticated = true
				glcd.Clients[authInfo.Name].Authenticated = true
			} else {
				fmt.Printf("Auth failed for user %s\n", authInfo.Name)
			}
		} else {
			fmt.Printf("User %s does not exist!\n", authInfo.Name)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/chadweimer/gomp/models"
	"github.com/chadweimer/gomp/modules/conf"
	"github.com/chadweimer/gomp/routers"
	"github.com/gorilla/context"
	"github.com/gorilla/sessions"
	"github.com/julienschmidt/httprouter"
	"github.com/urfave/negroni"
	//"gopkg.in/tylerb/graceful.v1"
	"gopkg.in/unrolled/render.v1"
)

func main() {
	cfg := conf.Load("conf/app.json")
	model := models.New(cfg)
	sessionStore := sessions.NewCookieStore([]byte(cfg.SecretKey))
	renderer := render.New(render.Options{
		Layout: "shared/layout",
		Funcs: []template.FuncMap{map[string]interface{}{
			"ToLower":     strings.ToLower,
			"QueryEscape": url.QueryEscape,
			"Add":         func(a, b int64) int64 { return a + b },
			"RootUrlPath": func() string { return cfg.RootURLPath },
			"TimeEqual":   func(a, b time.Time) bool { return a == b },
			"Paginate": func(pageNum, numPages, num int64) []int64 {
				if numPages == 0 {
					return []int64{1}
				}

				if numPages < num {
					num = numPages
				}

				startPage := pageNum - num/2
				endPage := pageNum + num/2
				if startPage < 1 {
					startPage = 1
					endPage = startPage + num - 1
				} else if endPage > numPages {
					endPage = numPages
					startPage = endPage - num + 1
				}

				pageNums := make([]int64, num, num)
				for i := int64(0); i < num; i++ {
					pageNums[i] = i + startPage
				}
				return pageNums
			},
		}}})
	rc := routers.NewController(renderer, cfg, model, sessionStore)

	// Since httprouter explicitly doesn't allow /path/to and /path/:match,
	// we get a little fancy and use 2 mux'es to emulate/force the behavior
	mainMux := httprouter.New()
	mainMux.GET("/", rc.ListRecipes)
	mainMux.GET("/recipes", rc.ListRecipes)
	mainMux.GET("/recipes/create", rc.CreateRecipe)
	mainMux.POST("/recipes/create", rc.CreateRecipePost)

	// Use the recipeMux to configure the routes related to a single recipe,
	// since /recipes/:id conflicts with /recipes/create above
	recipeMux := httprouter.New()
	recipeMux.GET("/recipes/:id", rc.GetRecipe)
	recipeMux.GET("/recipes/:id/edit", rc.EditRecipe)
	recipeMux.POST("/recipes/:id/edit", rc.EditRecipePost)
	recipeMux.GET("/recipes/:id/delete", rc.DeleteRecipe)
	recipeMux.POST("/recipes/:id/attach", rc.CreateAttachmentPost)
	recipeMux.GET("/recipes/:id/attach/:name/delete", rc.DeleteAttachment)
	recipeMux.POST("/recipes/:id/note", rc.CreateNotePost)
	recipeMux.POST("/recipes/:id/note/:note_id", rc.EditNotePost)
	recipeMux.GET("/recipes/:id/note/:note_id/delete", rc.DeleteNote)
	recipeMux.POST("/recipes/:id/rate", rc.RateRecipePost)
	recipeMux.NotFound = http.HandlerFunc(rc.NotFound)

	// Fall into the recipeMux only when the route isn't found in mainMux
	mainMux.NotFound = recipeMux

	n := negroni.New()
	n.Use(negroni.NewRecovery())
	if cfg.IsDevelopment {
		n.Use(negroni.NewLogger())
	}

	n.Use(&negroni.Static{Dir: http.Dir("public")})
	n.Use(&negroni.Static{Dir: http.Dir(fmt.Sprintf("%s/files", cfg.DataPath)), Prefix: "/files"})
	n.UseHandler(mainMux)

	log.Printf("Starting server on port :%d", cfg.Port)
	//timeout := 10 * time.Second
	//if cfg.IsDevelopment {
	//	timeout = 1 * time.Second
	//}
	//graceful.Run(fmt.Sprintf(":%d", cfg.Port), timeout, context.ClearHandler(n))
	http.ListenAndServe(fmt.Sprintf(":%d", cfg.Port), context.ClearHandler(n))
}
<commit_msg>Brought back Graceful.<commit_after>package main

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/chadweimer/gomp/models"
	"github.com/chadweimer/gomp/modules/conf"
	"github.com/chadweimer/gomp/routers"
	"github.com/gorilla/context"
	"github.com/gorilla/sessions"
	"github.com/julienschmidt/httprouter"
	"github.com/urfave/negroni"
	"gopkg.in/tylerb/graceful.v1"
	"gopkg.in/unrolled/render.v1"
)

func main() {
	cfg := conf.Load("conf/app.json")
	model := models.New(cfg)
	sessionStore := sessions.NewCookieStore([]byte(cfg.SecretKey))
	renderer := render.New(render.Options{
		Layout: "shared/layout",
		Funcs: []template.FuncMap{map[string]interface{}{
			"ToLower":     strings.ToLower,
			"QueryEscape": url.QueryEscape,
			"Add":         func(a, b int64) int64 { return a + b },
			"RootUrlPath": func() string { return cfg.RootURLPath },
			"TimeEqual":   func(a, b time.Time) bool { return a == b },
			"Paginate": func(pageNum, numPages, num int64) []int64 {
				if numPages == 0 {
					return []int64{1}
				}

				if numPages < num {
					num = numPages
				}

				startPage := pageNum - num/2
				endPage := pageNum + num/2
				if startPage < 1 {
					startPage = 1
					endPage = startPage + num - 1
				} else if endPage > numPages {
					endPage = numPages
					startPage = endPage - num + 1
				}

				pageNums := make([]int64, num, num)
				for i := int64(0); i < num; i++ {
					pageNums[i] = i + startPage
				}
				return pageNums
			},
		}}})
	rc := routers.NewController(renderer, cfg, model, sessionStore)

	// Since httprouter explicitly doesn't allow /path/to and /path/:match,
	// we get a little fancy and use 2 mux'es to emulate/force the behavior
	mainMux := httprouter.New()
	mainMux.GET("/", rc.ListRecipes)
	mainMux.GET("/recipes", rc.ListRecipes)
	mainMux.GET("/recipes/create", rc.CreateRecipe)
	mainMux.POST("/recipes/create", rc.CreateRecipePost)

	// Use the recipeMux to configure the routes related to a single recipe,
	// since /recipes/:id conflicts with /recipes/create above
	recipeMux := httprouter.New()
	recipeMux.GET("/recipes/:id", rc.GetRecipe)
	recipeMux.GET("/recipes/:id/edit", rc.EditRecipe)
	recipeMux.POST("/recipes/:id/edit", rc.EditRecipePost)
	recipeMux.GET("/recipes/:id/delete", rc.DeleteRecipe)
	recipeMux.POST("/recipes/:id/attach", rc.CreateAttachmentPost)
	recipeMux.GET("/recipes/:id/attach/:name/delete", rc.DeleteAttachment)
	recipeMux.POST("/recipes/:id/note", rc.CreateNotePost)
	recipeMux.POST("/recipes/:id/note/:note_id", rc.EditNotePost)
	recipeMux.GET("/recipes/:id/note/:note_id/delete", rc.DeleteNote)
	recipeMux.POST("/recipes/:id/rate", rc.RateRecipePost)
	recipeMux.NotFound = http.HandlerFunc(rc.NotFound)

	// Fall into the recipeMux only when the route isn't found in mainMux
	mainMux.NotFound = recipeMux

	n := negroni.New()
	n.Use(negroni.NewRecovery())
	if cfg.IsDevelopment {
		n.Use(negroni.NewLogger())
	}

	n.Use(&negroni.Static{Dir: http.Dir("public")})
	n.Use(&negroni.Static{Dir: http.Dir(fmt.Sprintf("%s/files", cfg.DataPath)), Prefix: "/files"})
	n.UseHandler(mainMux)

	log.Printf("Starting server on port :%d", cfg.Port)
	timeout := 10 * time.Second
	if cfg.IsDevelopment {
		timeout = 1 * time.Second
	}
	graceful.Run(fmt.Sprintf(":%d", cfg.Port), timeout, context.ClearHandler(n))
}
<|endoftext|>


<commit_before>package mcp2210

import (
	"errors"
)

// ----------------------------------------------------------------------------------
//  Constants
// ----------------------------------------------------------------------------------

const (
	DirectionIn			= 0x00
	DirectionOut		= 0x01

	ValueInactive		= 0x00
	ValueActive			= 0x01

	FunctionGPIO		= 0x00
	FunctionChipSelect	= 0x01
	FunctionAlternative	= 0x02
)

// ----------------------------------------------------------------------------------
//  Setters
// ----------------------------------------------------------------------------------

func (this *MCP2210) setCurrentPinValues(low uint16, high uint16) {
	this.currentPinValues = low | (high << 8)
}

// ----------------------------------------------------------------------------------
//  Informational Functions
// ----------------------------------------------------------------------------------

func (this *MCP2210) updateGPIOValues() error {
	if this.hidDevice == nil {
		return errors.New("device not opened")
	}

	// assemble and send command
	response, err := this.sendCommand(cmdGetPinValue)
	if err != nil {
		return err
	}

	// everything is fine, update the GPIO values
	this.setCurrentPinValues(uint16(response[4]), uint16(response[5]))
	return nil
}

func (this *MCP2210) GetGPIOValue(pin uint16) (uint8, error) {
	if this.hidDevice == nil {
		return 0xFF, errors.New("device not opened")
	}

	// update the GPIO values to get the most recent state
	err := this.updateGPIOValues()
	if err != nil {
		return 0xFF, err
	}

	return uint8((this.currentPinValues & (1 << pin)) >> pin), nil
}

// ----------------------------------------------------------------------------------
//  Changing Functions
// ----------------------------------------------------------------------------------

func (this *MCP2210) SetGPIOValue(pin uint16, state uint16) error {
	if this.hidDevice == nil {
		return errors.New("device not opened")
	}

	// set the pin state
	if state == ValueActive {
		this.currentPinValues |= 1 << pin
	} else {
		this.currentPinValues &= ^(1 << pin)
	}

	// send the command
	response, err := this.sendCommand(
		cmdSetPinValue,	// opcode
		0x00,			// reserved
		0x00,			// reserved
		0x00,			// reserved
		byte(this.currentPinValues),		// GP 0-7
		byte(this.currentPinValues >> 8),	// GP 8
	)
	if err != nil {
		return err
	}

	// set the actual GPIO values
	this.setCurrentPinValues(uint16(response[4]), uint16(response[5]))

	return nil
}
<commit_msg>code cleanup<commit_after>package mcp2210

import (
	"errors"
)

// ----------------------------------------------------------------------------------
//  Constants
// ----------------------------------------------------------------------------------

const (
	DirectionIn			= 0x00
	DirectionOut		= 0x01

	ValueInactive		= 0x00
	ValueActive			= 0x01

	FunctionGPIO		= 0x00
	FunctionChipSelect	= 0x01
	FunctionAlternative	= 0x02
)

// ----------------------------------------------------------------------------------
//  Setters
// ----------------------------------------------------------------------------------

func (this *MCP2210) setCurrentPinValues(low uint16, high uint16) {
	this.currentPinValues = low | (high << 8)
}

// ----------------------------------------------------------------------------------
//  Informational Functions
// ----------------------------------------------------------------------------------

func (this *MCP2210) updateGPIOValues() error {
	if this.hidDevice == nil {
		return errors.New("device not opened")
	}

	// assemble and send command
	response, err := this.sendCommand(cmdGetPinValue)
	if err != nil {
		return err
	}

	// everything is fine, update the GPIO values
	this.setCurrentPinValues(uint16(response[4]), uint16(response[5]))
	return nil
}

func (this *MCP2210) GetGPIOValue(pin uint16) (uint8, error) {
	if this.hidDevice == nil {
		return 0xFF, errors.New("device not opened")
	}

	// update the GPIO values to get the most recent state
	err := this.updateGPIOValues()
	if err != nil {
		return 0xFF, err
	}

	// 1 = active/high, 0 = inactive/low
	return uint8((this.currentPinValues & (1 << pin)) >> pin), nil
}

// ----------------------------------------------------------------------------------
//  Changing Functions
// ----------------------------------------------------------------------------------

func (this *MCP2210) SetGPIOValue(pin uint16, state uint16) error {
	if this.hidDevice == nil {
		return errors.New("device not opened")
	}

	// set the pin state
	if state == ValueActive {
		this.currentPinValues |= 1 << pin
	} else {
		this.currentPinValues &= ^(1 << pin)
	}

	// send the command
	response, err := this.sendCommand(
		cmdSetPinValue,	// opcode
		0x00,			// reserved
		0x00,			// reserved
		0x00,			// reserved
		byte(this.currentPinValues),		// GP 0-7
		byte(this.currentPinValues >> 8),	// GP 8
	)
	if err != nil {
		return err
	}

	// set the actual GPIO values
	this.setCurrentPinValues(uint16(response[4]), uint16(response[5]))

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2012 Jesse van den Kieboom. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package flags

import (
	"bufio"
	"fmt"
	"io"
	"strings"
	"unicode/utf8"
)

func (p *Parser) maxLongLen() (int, bool) {
	maxlonglen := 0
	hasshort := false

	for _, grp := range p.Groups {
		for _, info := range grp.Options {
			if info.ShortName != 0 {
				hasshort = true
			}

			l := utf8.RuneCountInString(info.LongName)

			if l > maxlonglen {
				maxlonglen = l
			}
		}
	}

	return maxlonglen, hasshort
}

func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, maxlen int, hasshort bool, termcol int) {
	if option.ShortName != 0 {
		writer.WriteString("  -")
		writer.WriteRune(option.ShortName)
	} else if hasshort {
		writer.WriteString("    ")
	}

	written := 0
	prelen := 4

	if option.LongName != "" {
		if option.ShortName != 0 {
			writer.WriteString(", ")
		} else {
			writer.WriteString("  ")
		}

		fmt.Fprintf(writer, "--%s", option.LongName)
		written = utf8.RuneCountInString(option.LongName)

		prelen += written + 4
	}

	if option.Description != "" {
		if written < maxlen {
			dw := maxlen - written

			writer.WriteString(strings.Repeat(" ", dw))
			prelen += dw
		}

		def := convertToString(option.value, option.options)
		var desc string

		if def != "" {
			desc = fmt.Sprintf("%s (%v)", option.Description, def)
		} else {
			desc = option.Description
		}

		writer.WriteString(wrapText(desc,
			termcol-prelen,
			strings.Repeat(" ", prelen)))
	}

	writer.WriteString("\n")
}

// WriteHelp writes a help message containing all the possible options and
// their descriptions to the provided writer. Note that the HelpFlag parser
// option provides a convenient way to add a -h/--help option group to the
// command line parser which will automatically show the help messages using
// this method.
func (p *Parser) WriteHelp(writer io.Writer) {
	if writer == nil {
		return
	}

	wr := bufio.NewWriter(writer)

	if p.ApplicationName != "" {
		wr.WriteString("Usage:\n")
		fmt.Fprintf(wr, "  %s", p.ApplicationName)

		if p.Usage != "" {
			fmt.Fprintf(wr, " %s", p.Usage)
		}

		wr.WriteString("\n")
	}

	maxlonglen, hasshort := p.maxLongLen()
	maxlen := maxlonglen + 4

	termcol := getTerminalColumns()

	for _, grp := range p.Groups {
		wr.WriteString("\n")

		fmt.Fprintf(wr, "%s:\n", grp.Name)

		for _, info := range grp.Options {
			p.writeHelpOption(wr, info, maxlen, hasshort, termcol)
		}
	}

	wr.Flush()
}
<commit_msg>terminal columns are not always available, assume 80<commit_after>// Copyright 2012 Jesse van den Kieboom. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package flags

import (
	"bufio"
	"fmt"
	"io"
	"strings"
	"unicode/utf8"
)

func (p *Parser) maxLongLen() (int, bool) {
	maxlonglen := 0
	hasshort := false

	for _, grp := range p.Groups {
		for _, info := range grp.Options {
			if info.ShortName != 0 {
				hasshort = true
			}

			l := utf8.RuneCountInString(info.LongName)

			if l > maxlonglen {
				maxlonglen = l
			}
		}
	}

	return maxlonglen, hasshort
}

func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, maxlen int, hasshort bool, termcol int) {
	if option.ShortName != 0 {
		writer.WriteString("  -")
		writer.WriteRune(option.ShortName)
	} else if hasshort {
		writer.WriteString("    ")
	}

	written := 0
	prelen := 4

	if option.LongName != "" {
		if option.ShortName != 0 {
			writer.WriteString(", ")
		} else {
			writer.WriteString("  ")
		}

		fmt.Fprintf(writer, "--%s", option.LongName)
		written = utf8.RuneCountInString(option.LongName)

		prelen += written + 4
	}

	if option.Description != "" {
		if written < maxlen {
			dw := maxlen - written

			writer.WriteString(strings.Repeat(" ", dw))
			prelen += dw
		}

		def := convertToString(option.value, option.options)
		var desc string

		if def != "" {
			desc = fmt.Sprintf("%s (%v)", option.Description, def)
		} else {
			desc = option.Description
		}

		writer.WriteString(wrapText(desc,
			termcol-prelen,
			strings.Repeat(" ", prelen)))
	}

	writer.WriteString("\n")
}

// WriteHelp writes a help message containing all the possible options and
// their descriptions to the provided writer. Note that the HelpFlag parser
// option provides a convenient way to add a -h/--help option group to the
// command line parser which will automatically show the help messages using
// this method.
func (p *Parser) WriteHelp(writer io.Writer) {
	if writer == nil {
		return
	}

	wr := bufio.NewWriter(writer)

	if p.ApplicationName != "" {
		wr.WriteString("Usage:\n")
		fmt.Fprintf(wr, "  %s", p.ApplicationName)

		if p.Usage != "" {
			fmt.Fprintf(wr, " %s", p.Usage)
		}

		wr.WriteString("\n")
	}

	maxlonglen, hasshort := p.maxLongLen()
	maxlen := maxlonglen + 4

	termcol := getTerminalColumns()
	if termcol <= 0 {
		termcol = 80
	}

	for _, grp := range p.Groups {
		wr.WriteString("\n")

		fmt.Fprintf(wr, "%s:\n", grp.Name)

		for _, info := range grp.Options {
			p.writeHelpOption(wr, info, maxlen, hasshort, termcol)
		}
	}

	wr.Flush()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"sort"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/newrelic/sidecar/catalog"
	"github.com/newrelic/sidecar/output"
	"github.com/newrelic/sidecar/service"
	"github.com/nitro/memberlist"
)

func makeHandler(fn func(http.ResponseWriter, *http.Request,
	*memberlist.Memberlist, *catalog.ServicesState),
	list *memberlist.Memberlist, state *catalog.ServicesState) http.HandlerFunc {

	return func(response http.ResponseWriter, req *http.Request) {
		fn(response, req, list, state)
	}
}

func watchHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "application/json")

	lastChange := time.Unix(0, 0)

	for {
		if state.LastChanged.After(lastChange) {
			lastChange = state.LastChanged
			jsonStr, _ := json.Marshal(state.ByService())
			response.Write(jsonStr)
			// In order to flush immediately, we have to cast to a Flusher.
			// The normal HTTP library supports this but not all do, so we
			// check just in case.
			if f, ok := response.(http.Flusher); ok {
				f.Flush()
			}
		}
		time.Sleep(250 * time.Millisecond)
	}
}

func servicesHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	params := mux.Vars(req)

	defer req.Body.Close()

	if params["extension"] == ".json" {
		response.Header().Set("Content-Type", "application/json")
		jsonStr, _ := json.MarshalIndent(state.ByService(), "", "  ")
		response.Write(jsonStr)
		return
	}
}

func serversHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "text/html")
	response.Write(
		[]byte(`

 			<meta http-equiv="refresh" content="4">

` + state.Format(list) + "
"))
}

func stateHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "application/json")
	response.Write(state.Encode())
	return
}

func statusStr(status int) string {
	switch status {
	case 0:
		return "Alive"
	case 1:
		return "Tombstone"
	case 2:
		return "Unhealthy"
	default:
		return "Unknown"
	}
}

func portsStr(svcPorts []service.Port) string {
	var ports []string

	for _, port := range svcPorts {
		if port.ServicePort != 0 {
			ports = append(ports, fmt.Sprintf("%v->%v", port.ServicePort, port.Port))
		} else {
			ports = append(ports, fmt.Sprintf("%v", port.Port))
		}
	}

	return strings.Join(ports, ", ")
}

type listByName []*memberlist.Node

func (a listByName) Len() int           { return len(a) }
func (a listByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a listByName) Less(i, j int) bool { return a[i].Name < a[j].Name }

type Member struct {
	Node    *memberlist.Node
	Updated time.Time
}

func lineWrapMembers(cols int, fields []*Member) [][]*Member {
	if len(fields) < cols {
		return [][]*Member{fields}
	}

	retval := make([][]*Member, len(fields)/cols+1)
	for i := 0; i < len(fields); i++ {
		row := i / cols
		retval[row] = append(retval[row], fields[i])
	}

	return retval
}

func viewHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	timeAgo := func(when time.Time) string { return output.TimeAgo(when, time.Now().UTC()) }

	funcMap := template.FuncMap{
		"statusStr":   statusStr,
		"timeAgo":     timeAgo,
		"portsStr":    portsStr,
		"clusterName": func() string { return list.ClusterName() },
	}

	t, err := template.New("services").Funcs(funcMap).ParseFiles("views/services.html")
	if err != nil {
		log.Errorf("Error parsing template: %s", err.Error())
	}

	members := list.Members()
	sort.Sort(listByName(members))

	compiledMembers := make([]*Member, len(members))
	for i, member := range members {
		if _, ok := state.Servers[member.Name]; ok {
			compiledMembers[i] = &Member{member, state.Servers[member.Name].LastUpdated}
		} else {
			compiledMembers[i] = &Member{Node: member}
			log.Debug("No updated time for " + member.Name)
		}
	}

	wrappedMembers := lineWrapMembers(5, compiledMembers)

	viewData := struct {
		Services map[string][]*service.Service
		Members  [][]*Member
	}{
		Services: state.ByService(),
		Members:  wrappedMembers,
	}

	t.ExecuteTemplate(response, "services.html", viewData)
}

func serveHttp(list *memberlist.Memberlist, state *catalog.ServicesState) {
	router := mux.NewRouter()

	router.HandleFunc(
		"/services{extension}", makeHandler(servicesHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/servers", makeHandler(serversHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/services", makeHandler(viewHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/state", makeHandler(stateHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/watch", makeHandler(watchHandler, list, state),
	).Methods("GET")

	fs := http.FileServer(http.Dir("views/static/"))

	router.Handle("/static/{file}", http.StripPrefix("/static/", fs))

	http.Handle("/", router)

	err := http.ListenAndServe("0.0.0.0:7777", nil)
	exitWithError(err, "Can't start HTTP server")
}
<commit_msg>Use the right content type for state endpoint.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"sort"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/newrelic/sidecar/catalog"
	"github.com/newrelic/sidecar/output"
	"github.com/newrelic/sidecar/service"
	"github.com/nitro/memberlist"
)

func makeHandler(fn func(http.ResponseWriter, *http.Request,
	*memberlist.Memberlist, *catalog.ServicesState),
	list *memberlist.Memberlist, state *catalog.ServicesState) http.HandlerFunc {

	return func(response http.ResponseWriter, req *http.Request) {
		fn(response, req, list, state)
	}
}

func watchHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "application/json")

	lastChange := time.Unix(0, 0)

	for {
		if state.LastChanged.After(lastChange) {
			lastChange = state.LastChanged
			jsonStr, _ := json.Marshal(state.ByService())
			response.Write(jsonStr)
			// In order to flush immediately, we have to cast to a Flusher.
			// The normal HTTP library supports this but not all do, so we
			// check just in case.
			if f, ok := response.(http.Flusher); ok {
				f.Flush()
			}
		}
		time.Sleep(250 * time.Millisecond)
	}
}

func servicesHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	params := mux.Vars(req)
	defer req.Body.Close()

	if params["extension"] == ".json" {
		response.Header().Set("Content-Type", "application/json")
		jsonStr, _ := json.MarshalIndent(state.ByService(), "", "  ")
		response.Write(jsonStr)
		return
	}
}

func serversHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "text/html")
	response.Write(
		[]byte(`

 			<meta http-equiv="refresh" content="4">

` + state.Format(list) + "
"))
}

func stateHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	params := mux.Vars(req)
	defer req.Body.Close()

	if params["extension"] == ".json" {
		response.Header().Set("Content-Type", "application/json")
		response.Write(state.Encode())
		return
	}
}

func statusStr(status int) string {
	switch status {
	case 0:
		return "Alive"
	case 1:
		return "Tombstone"
	case 2:
		return "Unhealthy"
	default:
		return "Unknown"
	}
}

func portsStr(svcPorts []service.Port) string {
	var ports []string

	for _, port := range svcPorts {
		if port.ServicePort != 0 {
			ports = append(ports, fmt.Sprintf("%v->%v", port.ServicePort, port.Port))
		} else {
			ports = append(ports, fmt.Sprintf("%v", port.Port))
		}
	}

	return strings.Join(ports, ", ")
}

type listByName []*memberlist.Node

func (a listByName) Len() int           { return len(a) }
func (a listByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a listByName) Less(i, j int) bool { return a[i].Name < a[j].Name }

type Member struct {
	Node    *memberlist.Node
	Updated time.Time
}

func lineWrapMembers(cols int, fields []*Member) [][]*Member {
	if len(fields) < cols {
		return [][]*Member{fields}
	}

	retval := make([][]*Member, len(fields)/cols+1)
	for i := 0; i < len(fields); i++ {
		row := i / cols
		retval[row] = append(retval[row], fields[i])
	}

	return retval
}

func viewHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	timeAgo := func(when time.Time) string { return output.TimeAgo(when, time.Now().UTC()) }

	funcMap := template.FuncMap{
		"statusStr":   statusStr,
		"timeAgo":     timeAgo,
		"portsStr":    portsStr,
		"clusterName": func() string { return list.ClusterName() },
	}

	t, err := template.New("services").Funcs(funcMap).ParseFiles("views/services.html")
	if err != nil {
		log.Errorf("Error parsing template: %s", err.Error())
	}

	members := list.Members()
	sort.Sort(listByName(members))

	compiledMembers := make([]*Member, len(members))
	for i, member := range members {
		if _, ok := state.Servers[member.Name]; ok {
			compiledMembers[i] = &Member{member, state.Servers[member.Name].LastUpdated}
		} else {
			compiledMembers[i] = &Member{Node: member}
			log.Debug("No updated time for " + member.Name)
		}
	}

	wrappedMembers := lineWrapMembers(5, compiledMembers)

	viewData := struct {
		Services map[string][]*service.Service
		Members  [][]*Member
	}{
		Services: state.ByService(),
		Members:  wrappedMembers,
	}

	t.ExecuteTemplate(response, "services.html", viewData)
}

func serveHttp(list *memberlist.Memberlist, state *catalog.ServicesState) {
	router := mux.NewRouter()

	router.HandleFunc(
		"/services{extension}", makeHandler(servicesHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/servers", makeHandler(serversHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/services", makeHandler(viewHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/state{extension}", makeHandler(stateHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/watch", makeHandler(watchHandler, list, state),
	).Methods("GET")

	fs := http.FileServer(http.Dir("views/static/"))

	router.Handle("/static/{file}", http.StripPrefix("/static/", fs))

	http.Handle("/", router)

	err := http.ListenAndServe("0.0.0.0:7777", nil)
	exitWithError(err, "Can't start HTTP server")
}
<|endoftext|>


<commit_before>package htcat

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"sync"
)

const (
	_        = iota
	kB int64 = 1 << (10 * iota)
	mB
	gB
	tB
	pB
	eB
)

type HtCat struct {
	defrag

	u     *url.URL
	cl    *http.Client
	tasks chan *httpFrag

	// Protect httpFragGen with a Mutex.
	httpFragGenMu sync.Mutex
	httpFragGen
}

type HttpStatusError struct {
	error
	Status string
}

func (cat *HtCat) startup(parallelism int) {
	req := http.Request{
		Method:     "GET",
		URL:        cat.u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Body:       nil,
		Host:       cat.u.Host,
	}

	resp, err := cat.cl.Do(&req)
	if err != nil {
		go cat.defrag.cancel(err)
		return
	}

	// Check for non-200 OK response codes from the startup-GET.
	if resp.Status != "200 OK" {
		err = HttpStatusError{
			error: fmt.Errorf(
				"Expected HTTP Status 200, received: %q",
				resp.Status),
			Status: resp.Status}
		go cat.defrag.cancel(err)
		return
	}

	l := resp.Header.Get("Content-Length")

	// Some kinds of small or indeterminate-length files will
	// receive no parallelism.  This procedure helps prepare the
	// HtCat value for a one-HTTP-Request GET.
	noParallel := func(wtc writerToCloser) {
		f := cat.defrag.nextFragment()
		cat.defrag.setLast(cat.defrag.lastAllocated())
		f.contents = wtc
		cat.register(f)
	}

	if l == "" {
		// No Content-Length, stream without parallelism nor
		// assumptions about the length of the stream.
		go noParallel(struct {
			io.WriterTo
			io.Closer
		}{
			WriterTo: bufio.NewReader(resp.Body),
			Closer:   resp.Body,
		})
		return
	}

	length, err := strconv.ParseInt(l, 10, 64)
	if err != nil {
		// Invalid integer for Content-Length, defer reporting
		// the error until a WriteTo call is made.
		go cat.defrag.cancel(err)
		return
	}

	// Set up httpFrag generator state.
	cat.totalSize = length
	cat.targetFragSize = length / int64(parallelism)
	if cat.targetFragSize > 20*mB {
		cat.targetFragSize = 20 * mB
	}

	// Very small fragments are probably not worthwhile to start
	// up new requests for, but it in this case it was possible to
	// ascertain the size, so take advantage of that to start
	// reading in the background as eagerly as possible.
	if cat.targetFragSize < 1*mB {
		er := newEagerReader(resp.Body, cat.totalSize)
		go noParallel(er)
		go func() { er.WaitClosed() }()
		return
	}

	// None of the other special short-circuit cases have been
	// triggered, so begin preparation for full-blown parallel
	// GET.  One GET worker is started here to take advantage of
	// the already pending response (which has no determinate
	// length, so it must be limited).
	hf := cat.nextFragment()
	go func() {
		er := newEagerReader(
			struct {
				io.Reader
				io.Closer
			}{
				Reader: io.LimitReader(resp.Body, hf.size),
				Closer: resp.Body,
			},
			hf.size)

		hf.fragment.contents = er
		cat.register(hf.fragment)
		er.WaitClosed()

		// Chain into being a regular worker, having finished
		// the special start-up segment.
		cat.get()
	}()

}

func New(client *http.Client, u *url.URL, parallelism int) *HtCat {
	cat := HtCat{
		u:  u,
		cl: client,
	}

	cat.initDefrag()
	cat.startup(parallelism)

	if cat.totalSize <= 0 {
		return &cat
	}

	// Start background workers.
	//
	// "startup" starts one worker that is specially constructed
	// to deal with the first request, so back off by one to
	// prevent performing with too much parallelism.
	for i := 1; i < parallelism; i += 1 {
		go cat.get()
	}

	return &cat
}

func (cat *HtCat) nextFragment() *httpFrag {
	cat.httpFragGenMu.Lock()
	defer cat.httpFragGenMu.Unlock()

	var hf *httpFrag

	if cat.httpFragGen.hasNext() {
		f := cat.defrag.nextFragment()
		hf = cat.httpFragGen.nextFragment(f)
	} else {
		cat.defrag.setLast(cat.defrag.lastAllocated())
	}

	return hf
}

func (cat *HtCat) get() {
	for {
		hf := cat.nextFragment()
		if hf == nil {
			return
		}

		req := http.Request{
			Method:     "GET",
			URL:        cat.u,
			Proto:      "HTTP/1.1",
			ProtoMajor: 1,
			ProtoMinor: 1,
			Header:     hf.header,
			Body:       nil,
			Host:       cat.u.Host,
		}

		resp, err := cat.cl.Do(&req)
		if err != nil {
			cat.defrag.cancel(err)
			return
		}

		// Check for non-206 Partial Content response codes from the
		// range-GET.
		if resp.Status != "206 Partial Content" {
			err = HttpStatusError{
				error: fmt.Errorf("Expected HTTP Status 206, "+
					"received: %q",
					resp.Status),
				Status: resp.Status}
			go cat.defrag.cancel(err)
			return
		}

		er := newEagerReader(resp.Body, hf.size)
		hf.fragment.contents = er
		cat.register(hf.fragment)
		er.WaitClosed()
	}
}
<commit_msg>Simplify 'go' notation<commit_after>package htcat

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"sync"
)

const (
	_        = iota
	kB int64 = 1 << (10 * iota)
	mB
	gB
	tB
	pB
	eB
)

type HtCat struct {
	defrag

	u     *url.URL
	cl    *http.Client
	tasks chan *httpFrag

	// Protect httpFragGen with a Mutex.
	httpFragGenMu sync.Mutex
	httpFragGen
}

type HttpStatusError struct {
	error
	Status string
}

func (cat *HtCat) startup(parallelism int) {
	req := http.Request{
		Method:     "GET",
		URL:        cat.u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Body:       nil,
		Host:       cat.u.Host,
	}

	resp, err := cat.cl.Do(&req)
	if err != nil {
		go cat.defrag.cancel(err)
		return
	}

	// Check for non-200 OK response codes from the startup-GET.
	if resp.Status != "200 OK" {
		err = HttpStatusError{
			error: fmt.Errorf(
				"Expected HTTP Status 200, received: %q",
				resp.Status),
			Status: resp.Status}
		go cat.defrag.cancel(err)
		return
	}

	l := resp.Header.Get("Content-Length")

	// Some kinds of small or indeterminate-length files will
	// receive no parallelism.  This procedure helps prepare the
	// HtCat value for a one-HTTP-Request GET.
	noParallel := func(wtc writerToCloser) {
		f := cat.defrag.nextFragment()
		cat.defrag.setLast(cat.defrag.lastAllocated())
		f.contents = wtc
		cat.register(f)
	}

	if l == "" {
		// No Content-Length, stream without parallelism nor
		// assumptions about the length of the stream.
		go noParallel(struct {
			io.WriterTo
			io.Closer
		}{
			WriterTo: bufio.NewReader(resp.Body),
			Closer:   resp.Body,
		})
		return
	}

	length, err := strconv.ParseInt(l, 10, 64)
	if err != nil {
		// Invalid integer for Content-Length, defer reporting
		// the error until a WriteTo call is made.
		go cat.defrag.cancel(err)
		return
	}

	// Set up httpFrag generator state.
	cat.totalSize = length
	cat.targetFragSize = length / int64(parallelism)
	if cat.targetFragSize > 20*mB {
		cat.targetFragSize = 20 * mB
	}

	// Very small fragments are probably not worthwhile to start
	// up new requests for, but it in this case it was possible to
	// ascertain the size, so take advantage of that to start
	// reading in the background as eagerly as possible.
	if cat.targetFragSize < 1*mB {
		er := newEagerReader(resp.Body, cat.totalSize)
		go noParallel(er)
		go er.WaitClosed()
		return
	}

	// None of the other special short-circuit cases have been
	// triggered, so begin preparation for full-blown parallel
	// GET.  One GET worker is started here to take advantage of
	// the already pending response (which has no determinate
	// length, so it must be limited).
	hf := cat.nextFragment()
	go func() {
		er := newEagerReader(
			struct {
				io.Reader
				io.Closer
			}{
				Reader: io.LimitReader(resp.Body, hf.size),
				Closer: resp.Body,
			},
			hf.size)

		hf.fragment.contents = er
		cat.register(hf.fragment)
		er.WaitClosed()

		// Chain into being a regular worker, having finished
		// the special start-up segment.
		cat.get()
	}()

}

func New(client *http.Client, u *url.URL, parallelism int) *HtCat {
	cat := HtCat{
		u:  u,
		cl: client,
	}

	cat.initDefrag()
	cat.startup(parallelism)

	if cat.totalSize <= 0 {
		return &cat
	}

	// Start background workers.
	//
	// "startup" starts one worker that is specially constructed
	// to deal with the first request, so back off by one to
	// prevent performing with too much parallelism.
	for i := 1; i < parallelism; i += 1 {
		go cat.get()
	}

	return &cat
}

func (cat *HtCat) nextFragment() *httpFrag {
	cat.httpFragGenMu.Lock()
	defer cat.httpFragGenMu.Unlock()

	var hf *httpFrag

	if cat.httpFragGen.hasNext() {
		f := cat.defrag.nextFragment()
		hf = cat.httpFragGen.nextFragment(f)
	} else {
		cat.defrag.setLast(cat.defrag.lastAllocated())
	}

	return hf
}

func (cat *HtCat) get() {
	for {
		hf := cat.nextFragment()
		if hf == nil {
			return
		}

		req := http.Request{
			Method:     "GET",
			URL:        cat.u,
			Proto:      "HTTP/1.1",
			ProtoMajor: 1,
			ProtoMinor: 1,
			Header:     hf.header,
			Body:       nil,
			Host:       cat.u.Host,
		}

		resp, err := cat.cl.Do(&req)
		if err != nil {
			cat.defrag.cancel(err)
			return
		}

		// Check for non-206 Partial Content response codes from the
		// range-GET.
		if resp.Status != "206 Partial Content" {
			err = HttpStatusError{
				error: fmt.Errorf("Expected HTTP Status 206, "+
					"received: %q",
					resp.Status),
				Status: resp.Status}
			go cat.defrag.cancel(err)
			return
		}

		er := newEagerReader(resp.Body, hf.size)
		hf.fragment.contents = er
		cat.register(hf.fragment)
		er.WaitClosed()
	}
}
<|endoftext|>


<commit_before>package glutton

import (
	"bufio"
	"log"
	"net"
	"net/http"
)

func handleHTTP(conn net.Conn) error {
	defer conn.Close()
	req, err := http.ReadRequest(bufio.NewReader(conn))
	if err != nil {
		log.Println(err)
		return err
	}
	log.Printf("%+v", req)
	if req.ContentLength > 0 {
		buf := new([]byte)
		req.Body.Read(*buf)
		log.Println(string(*buf))
	}
	conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
	return nil
}
<commit_msg>closing the request body<commit_after>package glutton

import (
	"bufio"
	"log"
	"net"
	"net/http"
)

func handleHTTP(conn net.Conn) error {
	defer conn.Close()
	req, err := http.ReadRequest(bufio.NewReader(conn))
	if err != nil {
		log.Println(err)
		return err
	}
	log.Printf("%+v", req)
	if req.ContentLength > 0 {
		defer req.Body.Close()
		buf := new([]byte)
		req.Body.Read(*buf)
		log.Println(string(*buf))
	}
	conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	_ "github.com/mattn/go-sqlite3"
	"log"
	"strings"
	"time"
)

func Uptime(curup string) {
	split := strings.Split(curup, ":")
	curboot := split[0] + "h" + split[1] + "m" + split[2] + "s"
	curtime, err := time.ParseDuration(curboot)
	if err != nil {
		log.Fatal(err)
	}

	db, err := sql.Open("sqlite3", "toril.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	stmt, err := db.Prepare("SELECT boot_id, uptime FROM boots WHERE boot_time = (SELECT MAX(boot_time) FROM boots)")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	var oldid int
	var oldup string
	err = stmt.QueryRow().Scan(&oldid, &oldup)
	if err != nil {
		log.Fatal(err)
	}
	split = strings.Split(oldup, ":")
	oldboot := split[0] + "h" + split[1] + "m" + split[2] + "s"
	oldtime, err := time.ParseDuration(oldboot)
	if err != nil {
		log.Fatal(err)
	}

	if curtime < oldtime {
		// it's a new boot, so create a new boot ID and send email
		tx, err := db.Begin()
		if err != nil {
			log.Fatal(err)
		}

		boottime := time.Now().Add(-curtime)
		stmt, err = db.Prepare("INSERT INTO boots (boot_time, uptime) VALUES(?, ?)")
		if err != nil {
			log.Fatal(err)
		}
		defer stmt.Close()

		_, err = stmt.Exec(boottime, curup)
		if err != nil {
			log.Fatal(err)
		}
		tx.Commit()
		// SendBootEmail needs to be created in tokens.go
		// It should define and send the three variables
		// required by/to email.Notify(from, to, pwd)
		SendBootEmail()
	} else {
		// it's still the current boot, so update current uptime
		tx, err := db.Begin()
                if err != nil {
			log.Fatal(err)
                }
                stmt, err := tx.Prepare("UPDATE boots SET uptime = ? WHERE boot_id = ?")
                if err != nil {
                        log.Fatal(err)
                }
                defer stmt.Close()

                _, err = stmt.Exec(curup, oldid)
                if err != nil {
                        log.Fatal(err)
                }
                tx.Commit()
	}
}
<commit_msg>gofmt<commit_after>package main

import (
	"database/sql"
	_ "github.com/mattn/go-sqlite3"
	"log"
	"strings"
	"time"
)

func Uptime(curup string) {
	split := strings.Split(curup, ":")
	curboot := split[0] + "h" + split[1] + "m" + split[2] + "s"
	curtime, err := time.ParseDuration(curboot)
	if err != nil {
		log.Fatal(err)
	}

	db, err := sql.Open("sqlite3", "toril.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	stmt, err := db.Prepare("SELECT boot_id, uptime FROM boots WHERE boot_time = (SELECT MAX(boot_time) FROM boots)")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	var oldid int
	var oldup string
	err = stmt.QueryRow().Scan(&oldid, &oldup)
	if err != nil {
		log.Fatal(err)
	}
	split = strings.Split(oldup, ":")
	oldboot := split[0] + "h" + split[1] + "m" + split[2] + "s"
	oldtime, err := time.ParseDuration(oldboot)
	if err != nil {
		log.Fatal(err)
	}

	if curtime < oldtime {
		// it's a new boot, so create a new boot ID and send email
		tx, err := db.Begin()
		if err != nil {
			log.Fatal(err)
		}

		boottime := time.Now().Add(-curtime)
		stmt, err = db.Prepare("INSERT INTO boots (boot_time, uptime) VALUES(?, ?)")
		if err != nil {
			log.Fatal(err)
		}
		defer stmt.Close()

		_, err = stmt.Exec(boottime, curup)
		if err != nil {
			log.Fatal(err)
		}
		tx.Commit()
		// SendBootEmail needs to be created in tokens.go
		// It should define and send the three variables
		// required by/to email.Notify(from, to, pwd)
		SendBootEmail()
	} else {
		// it's still the current boot, so update current uptime
		tx, err := db.Begin()
		if err != nil {
			log.Fatal(err)
		}
		stmt, err := tx.Prepare("UPDATE boots SET uptime = ? WHERE boot_id = ?")
		if err != nil {
			log.Fatal(err)
		}
		defer stmt.Close()

		_, err = stmt.Exec(curup, oldid)
		if err != nil {
			log.Fatal(err)
		}
		tx.Commit()
	}
}
<|endoftext|>


<commit_before>package hush

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
)

type Tree struct {
	items yaml.MapSlice
}

func Main() {
	tree, err := LoadTree()
	if err != nil {
		die("%s\n", err.Error())
	}
	//warn("initial tree = %#v\n", tree)

	if len(os.Args) == 1 {
		err = tree.Print()
		if err != nil {
			die("%s\n", err.Error())
		}
		os.Exit(0)
	}

	switch os.Args[1] {
	case "set": // hush set paypal.com/personal/user john@example.com
		mainSetValue(tree)
	default:
		die("Usage: hum ...\n")
	}
}

func mainSetValue(tree *Tree) {
	pattern, value := os.Args[2], os.Args[3]
	value, err := captureValue(value)
	if err != nil {
		die("%s\n", err.Error())
	}
	paths, err := tree.Match(pattern)
	if err != nil {
		die("%s\n", err.Error())
	}

	var path []string
	switch len(paths) {
	case 0:
		path = strings.Split(pattern, "/")
	case 1:
		path = paths[0]
	default:
		die("pattern %q matches multiple paths: %s", paths)
	}

	tree.SetPath(path, value)
	tree.Print()
	err = tree.Save()
	if err != nil {
		die("%s\n", err.Error())
	}
}

func isTerminal(file *os.File) bool {
	return terminal.IsTerminal(int(os.Stdin.Fd()))
}

func captureValue(value string) (string, error) {
	if value == "-" {
		if isTerminal(os.Stdout) {
			editor := editor()
			warn("would launch %s to capture value\n", editor)
			return "", nil
		}

		all, err := ioutil.ReadAll(os.Stdin)
		return string(all), err
	}
	return value, nil
}

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	os.Exit(1)
}

func warn(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
}

func LoadTree() (*Tree, error) {
	hushPath, err := hushPath()
	if err != nil {
		return nil, err
	}

	_, err = os.Stat(hushPath)
	if os.IsNotExist(err) {
		warn("hush file does not exist. assuming an empty one\n")
		return &Tree{}, nil
	}
	if err != nil {
		return nil, errors.Wrap(err, "can't stat hush file")
	}
	// TODO reduce file permissions if they're too loose

	file, err := os.Open(hushPath)
	if err != nil {
		return nil, errors.Wrap(err, "opening hush file")
	}
	hushData, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, errors.Wrap(err, "can't read hush file")
	}

	keys := make(yaml.MapSlice, 0)
	err = yaml.Unmarshal(hushData, &keys)
	if err != nil {
		return nil, errors.Wrap(err, "can't parse hush file")
	}
	tree := &Tree{keys}
	tree.decrypt()
	return tree, nil
}

func (tree *Tree) Match(pattern string) ([][]string, error) {
	var matches [][]string
	// TODO perform pattern matching
	return matches, nil
}

func (tree *Tree) Get(needle string) (interface{}, bool) {
	for _, item := range tree.items {
		if key, ok := item.Key.(string); ok {
			if key == needle {
				return item.Value, true
			}
		} else {
			die("all keys should be strings not %#v\n", item.Key)
		}
	}
	return nil, false
}

func (tree *Tree) Set(needle string, val interface{}) {
	//warn("Set: %s %s\n", needle, val)
	for i, item := range tree.items {
		if key, ok := item.Key.(string); ok {
			if key == needle {
				tree.items[i].Value = val
				return
			}
		} else {
			die("all keys should be strings not %#v\n", item.Key)
		}
	}

	tree.items = append(tree.items, yaml.MapItem{
		Key:   needle,
		Value: val,
	})
}

func (tree *Tree) SetPath(path []string, val interface{}) {
	//warn("SetPath: %s %s\n", path, val)
	//defer warn("after Set(): %#v\n", tree)
	switch len(path) {
	case 0:
		die("path should not have 0 length")
	case 1:
		tree.Set(path[0], val)
		return
	}

	t := &Tree{}
	key := path[0]
	x, found := tree.Get(key)
	if items, ok := x.(yaml.MapSlice); found && ok {
		//warn("descending into: %s\n", key)
		t.items = items
	} else {
		//warn("creating subtree: %s\n", key)
	}
	t.SetPath(path[1:], val)
	tree.Set(key, t.items)
}

// Print displays a tree for human consumption.
func (tree *Tree) Print() error {
	data, err := yaml.Marshal(tree.items)
	if err != nil {
		return errors.Wrap(err, "printing tree")
	}

	_, err = os.Stdout.Write(data)
	return err
}

// Save stores a tree to disk for permanent, private archival.
func (tree *Tree) Save() error {
	tree.encrypt()

	data, err := yaml.Marshal(tree.items)
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}

	// save to temporary file
	file, err := ioutil.TempFile("", "hush-")
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	_, err = file.Write(data)
	file.Close()
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}

	// move temporary file over top of permanent file
	hushPath, err := hushPath()
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	err = os.Rename(file.Name(), hushPath)
	return errors.Wrap(err, "saving tree")
}

func (tree *Tree) encrypt() {
	/*
		block, err := aes.NewCipher(encryptionKey)
		if err != nil {
			panic(err)
		}
		gcm, err := cipher.NewGCM(block)
		if err != nil {
			panic(err)
		}
		nonce := make([]byte, 12)
		_, err = rand.Read(nonce)
		if err != nil {
			panic(err)
		}

		mapLeaves(tree.items, func(leaf string) string {
			plaintext := make([]byte, 1+len(leaf))
			plaintext[0] = 1 // version number
			copy(plaintext[1:], []byte(leaf))
			ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
			return base64.StdEncoding.EncodeToString(ciphertext)
		})
	*/
}

var encryptionKey = []byte(`0123456789abcdef`)

func (tree *Tree) decrypt() {
	/*
		mapLeaves(tree.items, func(leaf string) string {
			data, err := base64.StdEncoding.DecodeString(leaf)
			if err != nil {
				// it must be decrypted already
				return leaf
			}
			if len(data) < 1 {
				panic("too little encrypted data")
			}
			if data[0] != 1 {
				panic("I only understand version 1")
			}
			return string(data[1:])
		})
	*/
}

/*
func mapLeaves(items yaml.MapSlice, f func(string) string) {
	for i := range items {
		item := &items[i]
		val := item.Value
		if items, ok := val.(yaml.MapSlice); ok {
			mapLeaves(items, f)
		} else if str, ok := val.(string); ok {
			item.Value = f(str)
		} else {
			panic("unexpected leaf type")
		}
	}
}
*/

func home() (string, error) {
	home := os.Getenv("HOME")
	if home == "" {
		return "", errors.New("Point $HOME at your home directory")
	}
	return home, nil
}

func hushPath() (string, error) {
	home, err := home()
	if err != nil {
		return "", err
	}
	return path.Join(home, ".hush"), nil
}

var editorVarNames = []string{
	"HUSH_EDITOR",
	"VISUAL",
	"EDITOR",
}

func editor() string {
	for _, varName := range editorVarNames {
		ed := os.Getenv(varName)
		if ed != "" {
			return ed
		}
	}

	ed := "vi"
	warn("environment configures no editor. defaulting to %s", ed)
	return ed
}
<commit_msg>Add "hush import" command<commit_after>package hush

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
)

type Tree struct {
	items yaml.MapSlice
}

func Main() {
	tree, err := LoadTree()
	if err != nil {
		die("%s\n", err.Error())
	}
	//warn("initial tree = %#v\n", tree)

	if len(os.Args) == 1 {
		err = tree.Print()
		if err != nil {
			die("%s\n", err.Error())
		}
		os.Exit(0)
	}

	switch os.Args[1] {
	case "set": // hush set paypal.com/personal/user john@example.com
		mainSetValue(tree)
	case "import": // hush import
		mainImport(tree)
	default:
		die("Usage: hum ...\n")
	}
}

func mainSetValue(tree *Tree) {
	pattern, value := os.Args[2], os.Args[3]
	value, err := captureValue(value)
	if err != nil {
		die("%s\n", err.Error())
	}
	paths, err := tree.Match(pattern)
	if err != nil {
		die("%s\n", err.Error())
	}

	var path []string
	switch len(paths) {
	case 0:
		path = strings.Split(pattern, "/")
	case 1:
		path = paths[0]
	default:
		die("pattern %q matches multiple paths: %s", paths)
	}

	tree.SetPath(path, value)
	tree.Print()
	err = tree.Save()
	if err != nil {
		die("%s\n", err.Error())
	}
}

func mainImport(tree *Tree) {
	scanner := bufio.NewScanner(os.Stdin)
	for n := 1; scanner.Scan(); n++ {
		parts := strings.SplitN(scanner.Text(), "\t", 2)
		if len(parts) < 2 {
			warn("line %d missing tab delimiter\n", n)
			continue
		}
		path := strings.Split(parts[0], "/")
		val := parts[1]
		tree.SetPath(path, val)
	}
	tree.Print()
	err := tree.Save()
	if err != nil {
		die("%s\n", err.Error())
	}
}

func isTerminal(file *os.File) bool {
	return terminal.IsTerminal(int(os.Stdin.Fd()))
}

func captureValue(value string) (string, error) {
	if value == "-" {
		if isTerminal(os.Stdout) {
			editor := editor()
			warn("would launch %s to capture value\n", editor)
			return "", nil
		}

		all, err := ioutil.ReadAll(os.Stdin)
		return string(all), err
	}
	return value, nil
}

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	os.Exit(1)
}

func warn(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
}

func LoadTree() (*Tree, error) {
	hushPath, err := hushPath()
	if err != nil {
		return nil, err
	}

	_, err = os.Stat(hushPath)
	if os.IsNotExist(err) {
		warn("hush file does not exist. assuming an empty one\n")
		return &Tree{}, nil
	}
	if err != nil {
		return nil, errors.Wrap(err, "can't stat hush file")
	}
	// TODO reduce file permissions if they're too loose

	file, err := os.Open(hushPath)
	if err != nil {
		return nil, errors.Wrap(err, "opening hush file")
	}
	hushData, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, errors.Wrap(err, "can't read hush file")
	}

	keys := make(yaml.MapSlice, 0)
	err = yaml.Unmarshal(hushData, &keys)
	if err != nil {
		return nil, errors.Wrap(err, "can't parse hush file")
	}
	tree := &Tree{keys}
	tree.decrypt()
	return tree, nil
}

func (tree *Tree) Match(pattern string) ([][]string, error) {
	var matches [][]string
	// TODO perform pattern matching
	return matches, nil
}

func (tree *Tree) Get(needle string) (interface{}, bool) {
	for _, item := range tree.items {
		if key, ok := item.Key.(string); ok {
			if key == needle {
				return item.Value, true
			}
		} else {
			die("all keys should be strings not %#v\n", item.Key)
		}
	}
	return nil, false
}

func (tree *Tree) Set(needle string, val interface{}) {
	//warn("Set: %s %s\n", needle, val)
	for i, item := range tree.items {
		if key, ok := item.Key.(string); ok {
			if key == needle {
				tree.items[i].Value = val
				return
			}
		} else {
			die("all keys should be strings not %#v\n", item.Key)
		}
	}

	tree.items = append(tree.items, yaml.MapItem{
		Key:   needle,
		Value: val,
	})
}

func (tree *Tree) SetPath(path []string, val interface{}) {
	//warn("SetPath: %s %s\n", path, val)
	//defer warn("after Set(): %#v\n", tree)
	switch len(path) {
	case 0:
		die("path should not have 0 length")
	case 1:
		tree.Set(path[0], val)
		return
	}

	t := &Tree{}
	key := path[0]
	x, found := tree.Get(key)
	if items, ok := x.(yaml.MapSlice); found && ok {
		//warn("descending into: %s\n", key)
		t.items = items
	} else {
		//warn("creating subtree: %s\n", key)
	}
	t.SetPath(path[1:], val)
	tree.Set(key, t.items)
}

// Print displays a tree for human consumption.
func (tree *Tree) Print() error {
	data, err := yaml.Marshal(tree.items)
	if err != nil {
		return errors.Wrap(err, "printing tree")
	}

	_, err = os.Stdout.Write(data)
	return err
}

// Save stores a tree to disk for permanent, private archival.
func (tree *Tree) Save() error {
	tree.encrypt()

	data, err := yaml.Marshal(tree.items)
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}

	// save to temporary file
	file, err := ioutil.TempFile("", "hush-")
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	_, err = file.Write(data)
	file.Close()
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}

	// move temporary file over top of permanent file
	hushPath, err := hushPath()
	if err != nil {
		return errors.Wrap(err, "saving tree")
	}
	err = os.Rename(file.Name(), hushPath)
	return errors.Wrap(err, "saving tree")
}

func (tree *Tree) encrypt() {
	/*
		block, err := aes.NewCipher(encryptionKey)
		if err != nil {
			panic(err)
		}
		gcm, err := cipher.NewGCM(block)
		if err != nil {
			panic(err)
		}
		nonce := make([]byte, 12)
		_, err = rand.Read(nonce)
		if err != nil {
			panic(err)
		}

		mapLeaves(tree.items, func(leaf string) string {
			plaintext := make([]byte, 1+len(leaf))
			plaintext[0] = 1 // version number
			copy(plaintext[1:], []byte(leaf))
			ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
			return base64.StdEncoding.EncodeToString(ciphertext)
		})
	*/
}

var encryptionKey = []byte(`0123456789abcdef`)

func (tree *Tree) decrypt() {
	/*
		mapLeaves(tree.items, func(leaf string) string {
			data, err := base64.StdEncoding.DecodeString(leaf)
			if err != nil {
				// it must be decrypted already
				return leaf
			}
			if len(data) < 1 {
				panic("too little encrypted data")
			}
			if data[0] != 1 {
				panic("I only understand version 1")
			}
			return string(data[1:])
		})
	*/
}

/*
func mapLeaves(items yaml.MapSlice, f func(string) string) {
	for i := range items {
		item := &items[i]
		val := item.Value
		if items, ok := val.(yaml.MapSlice); ok {
			mapLeaves(items, f)
		} else if str, ok := val.(string); ok {
			item.Value = f(str)
		} else {
			panic("unexpected leaf type")
		}
	}
}
*/

func home() (string, error) {
	home := os.Getenv("HOME")
	if home == "" {
		return "", errors.New("Point $HOME at your home directory")
	}
	return home, nil
}

func hushPath() (string, error) {
	home, err := home()
	if err != nil {
		return "", err
	}
	return path.Join(home, ".hush"), nil
}

var editorVarNames = []string{
	"HUSH_EDITOR",
	"VISUAL",
	"EDITOR",
}

func editor() string {
	for _, varName := range editorVarNames {
		ed := os.Getenv(varName)
		if ed != "" {
			return ed
		}
	}

	ed := "vi"
	warn("environment configures no editor. defaulting to %s", ed)
	return ed
}
<|endoftext|>