<commit_before>package commands

import (
	"time"

	"github.com/github/git-lfs/git"
	"github.com/github/git-lfs/lfs"
	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
	"github.com/github/git-lfs/vendor/_nuts/github.com/spf13/cobra"
)

var (
	fetchCmd = &cobra.Command{
		Use:   "fetch",
		Short: "Downloads LFS files",
		Run:   fetchCommand,
	}
	fetchIncludeArg string
	fetchExcludeArg string
	fetchRecentArg  bool
)

func fetchCommand(cmd *cobra.Command, args []string) {
	var refs []*git.Ref

	if len(args) > 0 {
		// Remote is first arg
		lfs.Config.CurrentRemote = args[0]
	} else {
		trackedRemote, err := git.CurrentRemote()
		if err == nil {
			lfs.Config.CurrentRemote = trackedRemote
		} // otherwise leave as default (origin)
	}
	if len(args) > 1 {
		for _, r := range args[1:] {
			ref, err := git.ResolveRef(r)
			if err != nil {
				Panic(err, "Invalid ref argument")
			}
			refs = append(refs, ref)
		}
	} else {
		ref, err := git.CurrentRef()
		if err != nil {
			Panic(err, "Could not fetch")
		}
		refs = []*git.Ref{ref}
	}

	includePaths, excludePaths := determineIncludeExcludePaths(fetchIncludeArg, fetchExcludeArg)

	// Fetch refs sequentially per arg order; duplicates in later refs will be ignored
	for _, ref := range refs {
		Print("Fetching %v", ref.Name)
		fetchRef(ref.Sha, includePaths, excludePaths)
	}

	if fetchRecentArg || lfs.Config.FetchPruneConfig().FetchRecentAlways {
		Print("Fetching recent objects")
		fetchRecent(refs, includePaths, excludePaths)
	}

}

func init() {
	fetchCmd.Flags().StringVarP(&fetchIncludeArg, "include", "I", "", "Include a list of paths")
	fetchCmd.Flags().StringVarP(&fetchExcludeArg, "exclude", "X", "", "Exclude a list of paths")
	fetchCmd.Flags().BoolVarP(&fetchRecentArg, "recent", "r", false, "Fetch recent refs & commits")
	RootCmd.AddCommand(fetchCmd)
}

func pointersToFetchForRef(ref string) ([]*lfs.WrappedPointer, error) {
	// Use SkipDeletedBlobs to avoid fetching ALL previous versions of modified files
	opts := &lfs.ScanRefsOptions{ScanMode: lfs.ScanRefsMode, SkipDeletedBlobs: true}
	return lfs.ScanRefs(ref, "", opts)
}

func fetchRefToChan(ref string, include, exclude []string) chan *lfs.WrappedPointer {
	c := make(chan *lfs.WrappedPointer)
	pointers, err := pointersToFetchForRef(ref)
	if err != nil {
		Panic(err, "Could not scan for Git LFS files")
	}

	go fetchAndReportToChan(pointers, include, exclude, c)

	return c
}

// Fetch all binaries for a given ref (that we don't have already)
func fetchRef(ref string, include, exclude []string) {
	pointers, err := pointersToFetchForRef(ref)
	if err != nil {
		Panic(err, "Could not scan for Git LFS files")
	}
	fetchPointers(pointers, include, exclude)
}

// Fetch all previous versions of objects from since to ref (not including final state at ref)
// So this will fetch all the '-' sides of the diff from since to ref
func fetchPreviousVersions(ref string, since time.Time, include, exclude []string) {
	pointers, err := lfs.ScanPreviousVersions(ref, since)
	if err != nil {
		Panic(err, "Could not scan for Git LFS previous versions")
	}
	fetchPointers(pointers, include, exclude)
}

// Fetch recent objects based on config
func fetchRecent(alreadyFetchedRefs []*git.Ref, include, exclude []string) {
	fetchconf := lfs.Config.FetchPruneConfig()

	if fetchconf.FetchRecentRefsDays == 0 && fetchconf.FetchRecentCommitsDays == 0 {
		return
	}

	// Make a list of what unique commits we've already fetched for to avoid duplicating work
	uniqueRefShas := make(map[string]string, len(alreadyFetchedRefs))
	for _, ref := range alreadyFetchedRefs {
		uniqueRefShas[ref.Sha] = ref.Name
	}
	// First find any other recent refs
	if fetchconf.FetchRecentRefsDays > 0 {
		refsSince := time.Now().AddDate(0, 0, -fetchconf.FetchRecentRefsDays)
		refs, err := git.RecentBranches(refsSince, fetchconf.FetchRecentRefsIncludeRemotes, "")
		if err != nil {
			Panic(err, "Could not scan for recent refs")
		}
		for _, ref := range refs {
			// Don't fetch for the same SHA twice
			if prevRefName, ok := uniqueRefShas[ref.Sha]; ok {
				Print("Skipping fetch for %v, already fetched via %v", ref.Name, prevRefName)
			} else {
				uniqueRefShas[ref.Sha] = ref.Name
				Print("Fetching recent ref %v", ref.Name)
				fetchRef(ref.Sha, include, exclude)
			}
		}
	}
	// For every unique commit we've fetched, check recent commits too
	if fetchconf.FetchRecentCommitsDays > 0 {
		for commit, refName := range uniqueRefShas {
			// We measure from the last commit at the ref
			summ, err := git.GetCommitSummary(commit)
			if err != nil {
				Error("Couldn't scan commits at %v: %v", refName, err)
				continue
			}
			Print("Fetching recent changes for %v", refName)
			commitsSince := summ.CommitDate.AddDate(0, 0, -fetchconf.FetchRecentCommitsDays)
			fetchPreviousVersions(commit, commitsSince, include, exclude)
		}

	}
}

func fetchPointers(pointers []*lfs.WrappedPointer, include, exclude []string) {
	fetchAndReportToChan(pointers, include, exclude, nil)
}

// Fetch and report completion of each OID to a channel (optional, pass nil to skip)
func fetchAndReportToChan(pointers []*lfs.WrappedPointer, include, exclude []string, out chan<- *lfs.WrappedPointer) {

	totalSize := int64(0)
	for _, p := range pointers {
		totalSize += p.Size
	}
	q := lfs.NewDownloadQueue(len(pointers), totalSize, false)

	for _, p := range pointers {
		// Only add to download queue if local file is not the right size already
		// This avoids previous case of over-reporting a requirement for files we already have
		// which would only be skipped by PointerSmudgeObject later
		passFilter := lfs.FilenamePassesIncludeExcludeFilter(p.Name, include, exclude)
		if !lfs.ObjectExistsOfSize(p.Oid, p.Size) && passFilter {
			q.Add(lfs.NewDownloadable(p))
		} else {
			// If we already have it, or it won't be fetched
			// report it to chan immediately to support pull/checkout
			if out != nil {
				out <- p
			}

		}
	}

	if out != nil {
		dlwatch := q.Watch()

		go func() {
			// fetch only reports single OID, but OID *might* be referenced by multiple
			// WrappedPointers if same content is at multiple paths, so map oid->slice
			oidToPointers := make(map[string][]*lfs.WrappedPointer, len(pointers))
			for _, pointer := range pointers {
				plist := oidToPointers[pointer.Oid]
				oidToPointers[pointer.Oid] = append(plist, pointer)
			}

			for oid := range dlwatch {
				plist, ok := oidToPointers[oid]
				if !ok {
					continue
				}
				for _, p := range plist {
					out <- p
				}
			}
			close(out)
		}()

	}
	processQueue := time.Now()
	q.Wait()
	tracerx.PerformanceSince("process queue", processQueue)

	for _, err := range q.Errors() {
		if Debugging || err.Panic {
			LoggedError(err.Err, err.Error())
		} else {
			Error(err.Error())
		}
	}
}
<commit_msg>ラララララ ラー ララララー<commit_after>package commands

import (
	"time"

	"github.com/github/git-lfs/git"
	"github.com/github/git-lfs/lfs"
	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
	"github.com/github/git-lfs/vendor/_nuts/github.com/spf13/cobra"
)

var (
	fetchCmd = &cobra.Command{
		Use:   "fetch",
		Short: "Downloads LFS files",
		Run:   fetchCommand,
	}
	fetchIncludeArg string
	fetchExcludeArg string
	fetchRecentArg  bool
)

func fetchCommand(cmd *cobra.Command, args []string) {
	var refs []*git.Ref

	if len(args) > 0 {
		// Remote is first arg
		lfs.Config.CurrentRemote = args[0]
	} else {
		trackedRemote, err := git.CurrentRemote()
		if err == nil {
			lfs.Config.CurrentRemote = trackedRemote
		} // otherwise leave as default (origin)
	}
	if len(args) > 1 {
		for _, r := range args[1:] {
			ref, err := git.ResolveRef(r)
			if err != nil {
				Panic(err, "Invalid ref argument")
			}
			refs = append(refs, ref)
		}
	} else {
		ref, err := git.CurrentRef()
		if err != nil {
			Panic(err, "Could not fetch")
		}
		refs = []*git.Ref{ref}
	}

	includePaths, excludePaths := determineIncludeExcludePaths(fetchIncludeArg, fetchExcludeArg)

	// Fetch refs sequentially per arg order; duplicates in later refs will be ignored
	for _, ref := range refs {
		Print("Fetching %v", ref.Name)
		fetchRef(ref.Sha, includePaths, excludePaths)
	}

	if fetchRecentArg || lfs.Config.FetchPruneConfig().FetchRecentAlways {
		Print("Fetching recent objects")
		fetchRecent(refs, includePaths, excludePaths)
	}

}

func init() {
	fetchCmd.Flags().StringVarP(&fetchIncludeArg, "include", "I", "", "Include a list of paths")
	fetchCmd.Flags().StringVarP(&fetchExcludeArg, "exclude", "X", "", "Exclude a list of paths")
	fetchCmd.Flags().BoolVarP(&fetchRecentArg, "recent", "r", false, "Fetch recent refs & commits")
	RootCmd.AddCommand(fetchCmd)
}

func pointersToFetchForRef(ref string) ([]*lfs.WrappedPointer, error) {
	// Use SkipDeletedBlobs to avoid fetching ALL previous versions of modified files
	opts := &lfs.ScanRefsOptions{ScanMode: lfs.ScanRefsMode, SkipDeletedBlobs: true}
	return lfs.ScanRefs(ref, "", opts)
}

func fetchRefToChan(ref string, include, exclude []string) chan *lfs.WrappedPointer {
	c := make(chan *lfs.WrappedPointer)
	pointers, err := pointersToFetchForRef(ref)
	if err != nil {
		Panic(err, "Could not scan for Git LFS files")
	}

	go fetchAndReportToChan(pointers, include, exclude, c)

	return c
}

// Fetch all binaries for a given ref (that we don't have already)
func fetchRef(ref string, include, exclude []string) {
	pointers, err := pointersToFetchForRef(ref)
	if err != nil {
		Panic(err, "Could not scan for Git LFS files")
	}
	fetchPointers(pointers, include, exclude)
}

// Fetch all previous versions of objects from since to ref (not including final state at ref)
// So this will fetch all the '-' sides of the diff from since to ref
func fetchPreviousVersions(ref string, since time.Time, include, exclude []string) {
	pointers, err := lfs.ScanPreviousVersions(ref, since)
	if err != nil {
		Panic(err, "Could not scan for Git LFS previous versions")
	}
	fetchPointers(pointers, include, exclude)
}

// Fetch recent objects based on config
func fetchRecent(alreadyFetchedRefs []*git.Ref, include, exclude []string) {
	fetchconf := lfs.Config.FetchPruneConfig()

	if fetchconf.FetchRecentRefsDays == 0 && fetchconf.FetchRecentCommitsDays == 0 {
		return
	}

	// Make a list of what unique commits we've already fetched for to avoid duplicating work
	uniqueRefShas := make(map[string]string, len(alreadyFetchedRefs))
	for _, ref := range alreadyFetchedRefs {
		uniqueRefShas[ref.Sha] = ref.Name
	}
	// First find any other recent refs
	if fetchconf.FetchRecentRefsDays > 0 {
		refsSince := time.Now().AddDate(0, 0, -fetchconf.FetchRecentRefsDays)
		refs, err := git.RecentBranches(refsSince, fetchconf.FetchRecentRefsIncludeRemotes, "")
		if err != nil {
			Panic(err, "Could not scan for recent refs")
		}
		for _, ref := range refs {
			// Don't fetch for the same SHA twice
			if prevRefName, ok := uniqueRefShas[ref.Sha]; ok {
				Print("Skipping fetch for %v, already fetched via %v", ref.Name, prevRefName)
			} else {
				uniqueRefShas[ref.Sha] = ref.Name
				Print("Fetching recent ref %v", ref.Name)
				fetchRef(ref.Sha, include, exclude)
			}
		}
	}
	// For every unique commit we've fetched, check recent commits too
	if fetchconf.FetchRecentCommitsDays > 0 {
		for commit, refName := range uniqueRefShas {
			// We measure from the last commit at the ref
			summ, err := git.GetCommitSummary(commit)
			if err != nil {
				Error("Couldn't scan commits at %v: %v", refName, err)
				continue
			}
			Print("Fetching recent changes for %v", refName)
			commitsSince := summ.CommitDate.AddDate(0, 0, -fetchconf.FetchRecentCommitsDays)
			fetchPreviousVersions(commit, commitsSince, include, exclude)
		}

	}
}

func fetchPointers(pointers []*lfs.WrappedPointer, include, exclude []string) {
	fetchAndReportToChan(pointers, include, exclude, nil)
}

// Fetch and report completion of each OID to a channel (optional, pass nil to skip)
func fetchAndReportToChan(pointers []*lfs.WrappedPointer, include, exclude []string, out chan<- *lfs.WrappedPointer) {

	totalSize := int64(0)
	for _, p := range pointers {
		totalSize += p.Size
	}
	q := lfs.NewDownloadQueue(len(pointers), totalSize, false)

	for _, p := range pointers {
		// Only add to download queue if local file is not the right size already
		// This avoids previous case of over-reporting a requirement for files we already have
		// which would only be skipped by PointerSmudgeObject later
		passFilter := lfs.FilenamePassesIncludeExcludeFilter(p.Name, include, exclude)
		if !lfs.ObjectExistsOfSize(p.Oid, p.Size) && passFilter {
			tracerx.Printf("fetch %v [%v]", p.Name, p.Oid)
			q.Add(lfs.NewDownloadable(p))
		} else {
			if !passFilter {
				tracerx.Printf("Skipping %v [%v], include/exclude filters applied", p.Name, p.Oid)
			} else {
				tracerx.Printf("Skipping %v [%v], already exists", p.Name, p.Oid)
			}

			// If we already have it, or it won't be fetched
			// report it to chan immediately to support pull/checkout
			if out != nil {
				out <- p
			}

		}
	}

	if out != nil {
		dlwatch := q.Watch()

		go func() {
			// fetch only reports single OID, but OID *might* be referenced by multiple
			// WrappedPointers if same content is at multiple paths, so map oid->slice
			oidToPointers := make(map[string][]*lfs.WrappedPointer, len(pointers))
			for _, pointer := range pointers {
				plist := oidToPointers[pointer.Oid]
				oidToPointers[pointer.Oid] = append(plist, pointer)
			}

			for oid := range dlwatch {
				plist, ok := oidToPointers[oid]
				if !ok {
					continue
				}
				for _, p := range plist {
					out <- p
				}
			}
			close(out)
		}()

	}
	processQueue := time.Now()
	q.Wait()
	tracerx.PerformanceSince("process queue", processQueue)

	for _, err := range q.Errors() {
		if Debugging || err.Panic {
			LoggedError(err.Err, err.Error())
		} else {
			Error(err.Error())
		}
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/mkboudreau/asrt/output"
)

var (
	ErrInvalidTargets       error = errors.New("Must specify url targets in file or as arguments to command.")
	ErrInvalidTimeoutFormat       = errors.New("Timeout must conform to time.Duration format.")
	ErrInvalidRateFormat          = errors.New("Rate must conform to time.Duration format.")
	ErrInvalidMethod              = errors.New(fmt.Sprintf("Method unknown. Valid methods are %d", validMethods))
	ErrInvalidFormat              = errors.New(fmt.Sprintf("Output format unknown. Valid formats are %d", validFormats))
)

var DefaultHttpStatuses = map[string]int{
	string(methodGet):    200,
	string(methodPost):   201,
	string(methodPut):    200,
	string(methodDelete): 200,
	string(methodHead):   200,
	string(methodPatch):  200,
}

const (
	methodGet    commandMethod = "GET"
	methodPost                 = "POST"
	methodPut                  = "PUT"
	methodDelete               = "DELETE"
	methodHead                 = "HEAD"
	methodPatch                = "PATCH"
)

const (
	formatCSV  outputFormat = "CSV"
	formatTAB               = "TAB"
	formatJSON              = "JSON"
)

type configuration struct {
	context         *cli.Context
	CommandName     string
	Rate            time.Duration
	Output          outputFormat
	Pretty          bool
	AggregateOutput bool
	Quiet           bool
	Markdown        bool
	Workers         int
	Targets         []*target
}

type target struct {
	Method         commandMethod
	Timeout        time.Duration
	ExpectedStatus int
	URL            string
	Headers        map[string]string
}

type commandMethod string
type outputFormat string

func getConfiguration(c *cli.Context) (*configuration, error) {
	config := &configuration{context: c, CommandName: c.Command.Name, Targets: make([]*target, 0)}

	config.Pretty = c.Bool("pretty")
	config.AggregateOutput = c.Bool("aggregate")
	config.Quiet = c.Bool("quiet")
	config.Markdown = c.Bool("markdown")
	config.Workers = c.Int("workers")
	file := c.String("file")

	config.Output = outputFormat(getUpperOrDefault(c.String("format"), formatTAB))
	if config.Output == "" {
		return nil, ErrInvalidFormat
	}

	config.Rate = getTimeDurationConfig(c, "rate")

	if file == "" && len(c.Args()) == 0 {
		//cli.ShowCommandHelp(c, "status")
		return nil, ErrInvalidTargets
	}

	if newTargets, err := buildTargetsFromArgs(c); err != nil {
		return nil, err
	} else {
		config.Targets = append(config.Targets, newTargets...)
	}
	if file != "" {
		if newTargets, err := buildTargetsFromFile(c); err != nil {
			return nil, err
		} else {
			config.Targets = append(config.Targets, newTargets...)
		}
	}

	return config, nil
}

func getUpperOrDefault(val string, def interface{}) string {
	v := strings.ToUpper(val)
	if v == "" {
		if tmp, ok := def.(string); !ok {
			return ""
		} else {
			v = tmp
		}
	}
	return v
}
func getTimeDurationConfig(c *cli.Context, key string) time.Duration {
	v := c.String(key)
	d, err := time.ParseDuration(v)
	if err != nil {
		d = 0 * time.Second
		log.Printf("Could not parse duration %v, defaulting to %v", v, d)
	}
	return d
}

func (config *configuration) ResultFormatter() output.ResultFormatter {
	modifiers := &output.ResultFormatModifiers{
		Pretty:    config.Pretty,
		Aggregate: config.AggregateOutput,
		Quiet:     config.Quiet,
		Markdown:  config.Markdown,
	}

	switch {
	case config.Output == formatJSON:
		return output.NewJsonResultFormatter(modifiers)
	case config.Output == formatCSV:
		return output.NewCsvResultFormatter(modifiers)
	case config.Output == formatTAB:
		return output.NewTabResultFormatter(modifiers)
	}

	return nil
}

func (config *configuration) Writer() io.Writer {
	writers := make([]io.Writer, 0)

	main := config.getMainWriter()
	if main != nil {
		writers = append(writers, main)
	}

	slack := config.getSlackWriter()
	if slack != nil {
		writers = append(writers, slack)
	}

	return newProxyWriteCloser(writers...)
}

type proxyWriteCloser struct {
	writers []io.Writer
	writer  io.Writer
}

func newProxyWriteCloser(writers ...io.Writer) *proxyWriteCloser {
	return &proxyWriteCloser{
		writers: writers,
		writer:  io.MultiWriter(writers...),
	}
}

func (pwc *proxyWriteCloser) Write(p []byte) (n int, err error) {
	return pwc.writer.Write(p)
}

func (pwc *proxyWriteCloser) Close() error {
	for _, w := range pwc.writers {
		if c, ok := w.(io.Closer); ok {
			if err := c.Close(); err != nil {
				return err
			}
		}
	}
	return nil
}

func (config *configuration) getMainWriter() io.Writer {
	switch config.context.Command.Name {
	case "status":
		return os.Stdout
	case "dashboard":
		return os.Stdout
	case "server":
	}
	return nil
}

func (config *configuration) getSlackWriter() io.Writer {
	if config.context.String("slack-url") == "" {
		return nil
	}

	w := output.NewSlackWriter(config.context.String("slack-url"))

	if config.context.String("slack-channel") != "" {
		w.SlackChannel(config.context.String("slack-channel"))
	}
	if config.context.String("slack-user") != "" {
		w.SlackUser(config.context.String("slack-user"))
	}
	if config.context.String("slack-icon") != "" {
		icon := config.context.String("slack-icon")
		w.SlackIconUrl(output.SlackIconUrl(icon))
	}

	fmt.Println(w.Payload)

	return w
}

func buildTargetsFromFile(c *cli.Context) ([]*target, error) {
	filename := c.String("file")
	targets := make([]*target, 0)

	timeout := getTimeDurationConfig(c, "timeout")

	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}

	r := bufio.NewReader(file)
	for {
		line, err := r.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
		line = strings.Replace(line, "\t", " ", 10)
		line = strings.Trim(line, "\n")
		line = strings.Trim(line, " ")
		parts := strings.Split(line, " ")
		if len(parts) < 2 {
			return nil, fmt.Errorf("Found invalid line in file [%v]. Must have method and url", line)
		}

		method := commandMethod(getUpperOrDefault(parts[0], methodGet))
		if method == "" {
			return nil, ErrInvalidMethod
		}

		urlString := parts[1]
		var t *target
		var tErr error

		if len(parts) > 2 {
			expectedStatus, sError := strconv.Atoi(parts[2])
			if sError != nil && sError != io.EOF {
				return nil, fmt.Errorf("Found invalid line in file [%v]. Could not parse expected status %v: error %v", line, parts[2], sError)
			}
			t, tErr = newTargetWithExpectation(urlString, expectedStatus)
		} else {

			expectedStatus := DefaultHttpStatuses[string(method)]
			t, tErr = newTargetWithExpectation(urlString, expectedStatus)
		}
		if tErr != nil {
			return nil, fmt.Errorf("could not create target with url %v: %v", urlString, tErr)
		}

		t.Timeout = timeout
		t.Method = method
		targets = append(targets, t)
	}

	return targets, nil
}

func buildTargetsFromArgs(c *cli.Context) ([]*target, error) {

	timeout := getTimeDurationConfig(c, "timeout")
	method := commandMethod(getUpperOrDefault(c.String("method"), methodGet))
	if method == "" {
		return nil, ErrInvalidMethod
	}

	targets := make([]*target, 0)
	for _, u := range c.Args() {
		expectedStatus := DefaultHttpStatuses[string(method)]
		t, err := newTargetWithExpectation(u, expectedStatus)
		if err != nil {
			return nil, fmt.Errorf("could not create target with url %v: %v", u, err)
		}
		t.Timeout = timeout
		t.Method = method
		targets = append(targets, t)
	}
	return targets, nil
}

func newTargetWithExpectation(urlString string, expectedStatus int) (*target, error) {
	u, err := url.Parse(urlString)
	if err != nil {
		return nil, err
	}

	if u.Scheme == "" {
		u.Scheme = "http"
	}

	t := &target{
		URL:            u.String(),
		ExpectedStatus: expectedStatus,
	}

	return t, nil
}
<commit_msg>Remove extranneous logging and comments<commit_after>package commands

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/mkboudreau/asrt/output"
)

var (
	ErrInvalidTargets       error = errors.New("Must specify url targets in file or as arguments to command.")
	ErrInvalidTimeoutFormat       = errors.New("Timeout must conform to time.Duration format.")
	ErrInvalidRateFormat          = errors.New("Rate must conform to time.Duration format.")
	ErrInvalidMethod              = errors.New(fmt.Sprintf("Method unknown. Valid methods are %d", validMethods))
	ErrInvalidFormat              = errors.New(fmt.Sprintf("Output format unknown. Valid formats are %d", validFormats))
)

var DefaultHttpStatuses = map[string]int{
	string(methodGet):    200,
	string(methodPost):   201,
	string(methodPut):    200,
	string(methodDelete): 200,
	string(methodHead):   200,
	string(methodPatch):  200,
}

const (
	methodGet    commandMethod = "GET"
	methodPost                 = "POST"
	methodPut                  = "PUT"
	methodDelete               = "DELETE"
	methodHead                 = "HEAD"
	methodPatch                = "PATCH"
)

const (
	formatCSV  outputFormat = "CSV"
	formatTAB               = "TAB"
	formatJSON              = "JSON"
)

type configuration struct {
	context         *cli.Context
	CommandName     string
	Rate            time.Duration
	Output          outputFormat
	Pretty          bool
	AggregateOutput bool
	Quiet           bool
	Markdown        bool
	Workers         int
	Targets         []*target
}

type target struct {
	Method         commandMethod
	Timeout        time.Duration
	ExpectedStatus int
	URL            string
	Headers        map[string]string
}

type commandMethod string
type outputFormat string

func getConfiguration(c *cli.Context) (*configuration, error) {
	config := &configuration{context: c, CommandName: c.Command.Name, Targets: make([]*target, 0)}

	config.Pretty = c.Bool("pretty")
	config.AggregateOutput = c.Bool("aggregate")
	config.Quiet = c.Bool("quiet")
	config.Markdown = c.Bool("markdown")
	config.Workers = c.Int("workers")
	file := c.String("file")

	config.Output = outputFormat(getUpperOrDefault(c.String("format"), formatTAB))
	if config.Output == "" {
		return nil, ErrInvalidFormat
	}

	config.Rate = getTimeDurationConfig(c, "rate")

	if file == "" && len(c.Args()) == 0 {
		return nil, ErrInvalidTargets
	}

	if newTargets, err := buildTargetsFromArgs(c); err != nil {
		return nil, err
	} else {
		config.Targets = append(config.Targets, newTargets...)
	}
	if file != "" {
		if newTargets, err := buildTargetsFromFile(c); err != nil {
			return nil, err
		} else {
			config.Targets = append(config.Targets, newTargets...)
		}
	}

	return config, nil
}

func getUpperOrDefault(val string, def interface{}) string {
	v := strings.ToUpper(val)
	if v == "" {
		if tmp, ok := def.(string); !ok {
			return ""
		} else {
			v = tmp
		}
	}
	return v
}
func getTimeDurationConfig(c *cli.Context, key string) time.Duration {
	v := c.String(key)
	d, err := time.ParseDuration(v)
	if err != nil {
		d = 0 * time.Second
		log.Printf("Could not parse duration %v, defaulting to %v", v, d)
	}
	return d
}

func (config *configuration) ResultFormatter() output.ResultFormatter {
	modifiers := &output.ResultFormatModifiers{
		Pretty:    config.Pretty,
		Aggregate: config.AggregateOutput,
		Quiet:     config.Quiet,
		Markdown:  config.Markdown,
	}

	switch {
	case config.Output == formatJSON:
		return output.NewJsonResultFormatter(modifiers)
	case config.Output == formatCSV:
		return output.NewCsvResultFormatter(modifiers)
	case config.Output == formatTAB:
		return output.NewTabResultFormatter(modifiers)
	}

	return nil
}

func (config *configuration) Writer() io.Writer {
	writers := make([]io.Writer, 0)

	main := config.getMainWriter()
	if main != nil {
		writers = append(writers, main)
	}

	slack := config.getSlackWriter()
	if slack != nil {
		writers = append(writers, slack)
	}

	return newProxyWriteCloser(writers...)
}

type proxyWriteCloser struct {
	writers []io.Writer
	writer  io.Writer
}

func newProxyWriteCloser(writers ...io.Writer) *proxyWriteCloser {
	return &proxyWriteCloser{
		writers: writers,
		writer:  io.MultiWriter(writers...),
	}
}

func (pwc *proxyWriteCloser) Write(p []byte) (n int, err error) {
	return pwc.writer.Write(p)
}

func (pwc *proxyWriteCloser) Close() error {
	for _, w := range pwc.writers {
		if c, ok := w.(io.Closer); ok {
			if err := c.Close(); err != nil {
				return err
			}
		}
	}
	return nil
}

func (config *configuration) getMainWriter() io.Writer {
	switch config.context.Command.Name {
	case "status":
		return os.Stdout
	case "dashboard":
		return os.Stdout
	case "server":
	}
	return nil
}

func (config *configuration) getSlackWriter() io.Writer {
	if config.context.String("slack-url") == "" {
		return nil
	}

	w := output.NewSlackWriter(config.context.String("slack-url"))

	if config.context.String("slack-channel") != "" {
		w.SlackChannel(config.context.String("slack-channel"))
	}
	if config.context.String("slack-user") != "" {
		w.SlackUser(config.context.String("slack-user"))
	}
	if config.context.String("slack-icon") != "" {
		icon := config.context.String("slack-icon")
		w.SlackIconUrl(output.SlackIconUrl(icon))
	}

	return w
}

func buildTargetsFromFile(c *cli.Context) ([]*target, error) {
	filename := c.String("file")
	targets := make([]*target, 0)

	timeout := getTimeDurationConfig(c, "timeout")

	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}

	r := bufio.NewReader(file)
	for {
		line, err := r.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
		line = strings.Replace(line, "\t", " ", 10)
		line = strings.Trim(line, "\n")
		line = strings.Trim(line, " ")
		parts := strings.Split(line, " ")
		if len(parts) < 2 {
			return nil, fmt.Errorf("Found invalid line in file [%v]. Must have method and url", line)
		}

		method := commandMethod(getUpperOrDefault(parts[0], methodGet))
		if method == "" {
			return nil, ErrInvalidMethod
		}

		urlString := parts[1]
		var t *target
		var tErr error

		if len(parts) > 2 {
			expectedStatus, sError := strconv.Atoi(parts[2])
			if sError != nil && sError != io.EOF {
				return nil, fmt.Errorf("Found invalid line in file [%v]. Could not parse expected status %v: error %v", line, parts[2], sError)
			}
			t, tErr = newTargetWithExpectation(urlString, expectedStatus)
		} else {

			expectedStatus := DefaultHttpStatuses[string(method)]
			t, tErr = newTargetWithExpectation(urlString, expectedStatus)
		}
		if tErr != nil {
			return nil, fmt.Errorf("could not create target with url %v: %v", urlString, tErr)
		}

		t.Timeout = timeout
		t.Method = method
		targets = append(targets, t)
	}

	return targets, nil
}

func buildTargetsFromArgs(c *cli.Context) ([]*target, error) {

	timeout := getTimeDurationConfig(c, "timeout")
	method := commandMethod(getUpperOrDefault(c.String("method"), methodGet))
	if method == "" {
		return nil, ErrInvalidMethod
	}

	targets := make([]*target, 0)
	for _, u := range c.Args() {
		expectedStatus := DefaultHttpStatuses[string(method)]
		t, err := newTargetWithExpectation(u, expectedStatus)
		if err != nil {
			return nil, fmt.Errorf("could not create target with url %v: %v", u, err)
		}
		t.Timeout = timeout
		t.Method = method
		targets = append(targets, t)
	}
	return targets, nil
}

func newTargetWithExpectation(urlString string, expectedStatus int) (*target, error) {
	u, err := url.Parse(urlString)
	if err != nil {
		return nil, err
	}

	if u.Scheme == "" {
		u.Scheme = "http"
	}

	t := &target{
		URL:            u.String(),
		ExpectedStatus: expectedStatus,
	}

	return t, nil
}
<|endoftext|>


<commit_before>package commands

import (
	"errors"
	"github.com/millenc/golatch"
	"github.com/spf13/cobra"
)

//Flag variables
var ParentID string
var Name string
var TwoFactor string
var LockOnRequest string

//Flag initialization
func init() {
	OperationAddCmd.PersistentFlags().StringVarP(&ParentID, "parent", "p", "", "Parent ID (must be an existing operation or application)")
	OperationAddCmd.PersistentFlags().StringVarP(&Name, "name", "n", "", "Name of the operation")
	OperationAddCmd.PersistentFlags().StringVarP(&TwoFactor, "two-factor", "t", golatch.DISABLED, "Two Factor Authentication (possible values are MANDATORY,OPT_IN and DISABLED)")
	OperationAddCmd.PersistentFlags().StringVarP(&LockOnRequest, "lock-on-request", "l", golatch.DISABLED, "Lock On Request (possible values are MANDATORY,OPT_IN and DISABLED)")
}

//Status command
var OperationAddCmd = &cobra.Command{
	Use:   "add",
	Short: "Adds a new operation",
	Run: func(cmd *cobra.Command, args []string) {
		if ParentID == "" {
			Session.Halt(errors.New("You must provide the ID of the parent operation or application (--parent)."))
		}
		if Name == "" {
			Session.Halt(errors.New("You must provide the new operation's name (--name)."))
		}

		if resp, err := Latch.AddOperation(ParentID, Name, TwoFactor, LockOnRequest); err == nil {
			Session.AddSuccess("Operation created with ID: " + resp.OperationId())
		} else {
			Session.Halt(err)
		}
	},
}
<commit_msg>Fixed Add operation command comment.<commit_after>package commands

import (
	"errors"
	"github.com/millenc/golatch"
	"github.com/spf13/cobra"
)

//Flag variables
var ParentID string
var Name string
var TwoFactor string
var LockOnRequest string

//Flag initialization
func init() {
	OperationAddCmd.PersistentFlags().StringVarP(&ParentID, "parent", "p", "", "Parent ID (must be an existing operation or application)")
	OperationAddCmd.PersistentFlags().StringVarP(&Name, "name", "n", "", "Name of the operation")
	OperationAddCmd.PersistentFlags().StringVarP(&TwoFactor, "two-factor", "t", golatch.DISABLED, "Two Factor Authentication (possible values are MANDATORY,OPT_IN and DISABLED)")
	OperationAddCmd.PersistentFlags().StringVarP(&LockOnRequest, "lock-on-request", "l", golatch.DISABLED, "Lock On Request (possible values are MANDATORY,OPT_IN and DISABLED)")
}

//Add Operation command
var OperationAddCmd = &cobra.Command{
	Use:   "add",
	Short: "Adds a new operation",
	Run: func(cmd *cobra.Command, args []string) {
		if ParentID == "" {
			Session.Halt(errors.New("You must provide the ID of the parent operation or application (--parent)."))
		}
		if Name == "" {
			Session.Halt(errors.New("You must provide the new operation's name (--name)."))
		}

		if resp, err := Latch.AddOperation(ParentID, Name, TwoFactor, LockOnRequest); err == nil {
			Session.AddSuccess("Operation created with ID: " + resp.OperationId())
		} else {
			Session.Halt(err)
		}
	},
}
<|endoftext|>


<commit_before>package comments

import (
	"fmt"
	"log"

	"github.com/google/go-github/github"
)

var (
	teamsCache             = map[string][]github.Team{}
	teamHasPushAccessCache = map[string]*github.Repository{}
	teamMembershipCache    = map[string]bool{}
)

type Authenticator struct {
	client *github.Client
}

func isAuthorizedCommenter(client *github.Client, event github.IssueCommentEvent) bool {
	auth := Authenticator{client: client}
	orgTeams := auth.teamsForOrg(*event.Repo.Owner.Login)
	for _, team := range orgTeams {
		if auth.isTeamMember(*team.ID, *event.Comment.User.Login) &&
			auth.teamHasPushAccess(*team.ID, *event.Repo.Owner.Name, *event.Repo.Name) {
			return true
		}
	}
	return false
}

func (auth Authenticator) isTeamMember(teamId int, login string) bool {
	cacheKey := auth.cacheKeyIsTeamMember(teamId, login)
	if _, ok := teamMembershipCache[cacheKey]; !ok {
		newOk, _, err := auth.client.Organizations.IsTeamMember(teamId, login)
		if err != nil {
			log.Printf("ERROR performing IsTeamMember(%d, \"%s\"): %v", teamId, login, err)
			return false
		}
		teamMembershipCache[cacheKey] = newOk
	}
	return teamMembershipCache[cacheKey]
}

func (auth Authenticator) teamHasPushAccess(teamId int, owner, repo string) bool {
	cacheKey := auth.cacheKeyTeamHashPushAccess(teamId, owner, repo)
	if _, ok := teamHasPushAccessCache[cacheKey]; !ok {
		repository, _, err := auth.client.Organizations.IsTeamRepo(teamId, owner, repo)
		if err != nil {
			log.Printf("ERROR performing IsTeamRepo(%d, \"%s\", \"%s\"): %v", teamId, repo, err)
			return false
		}
		if repository == nil {
			return false
		}
		teamHasPushAccessCache[cacheKey] = repository
	}
	permissions := *teamHasPushAccessCache[cacheKey].Permissions
	return permissions["push"] || permissions["admin"]
}

func (auth Authenticator) teamsForOrg(org string) []github.Team {
	if _, ok := teamsCache[org]; !ok {
		teamz, _, err := auth.client.Organizations.ListTeams(org, &github.ListOptions{
			PerPage: 100,
		})
		if err != nil {
			log.Printf("ERROR performing ListTeams(\"%s\"): %v", org, err)
			return nil
		}
		teamsCache[org] = teamz
	}
	return teamsCache[org]
}

func (auth Authenticator) cacheKeyIsTeamMember(teamId int, login string) string {
	return fmt.Sprintf("%d_%s", teamId, login)
}

func (auth Authenticator) cacheKeyTeamHashPushAccess(teamId int, owner, repo string) string {
	return fmt.Sprintf("%d_%s_%s", teamId, owner, repo)
}
<commit_msg>Use .Login instead of .Name<commit_after>package comments

import (
	"fmt"
	"log"

	"github.com/google/go-github/github"
)

var (
	teamsCache             = map[string][]github.Team{}
	teamHasPushAccessCache = map[string]*github.Repository{}
	teamMembershipCache    = map[string]bool{}
)

type Authenticator struct {
	client *github.Client
}

func isAuthorizedCommenter(client *github.Client, event github.IssueCommentEvent) bool {
	auth := Authenticator{client: client}
	orgTeams := auth.teamsForOrg(*event.Repo.Owner.Login)
	for _, team := range orgTeams {
		if auth.isTeamMember(*team.ID, *event.Comment.User.Login) &&
			auth.teamHasPushAccess(*team.ID, *event.Repo.Owner.Login, *event.Repo.Name) {
			return true
		}
	}
	return false
}

func (auth Authenticator) isTeamMember(teamId int, login string) bool {
	cacheKey := auth.cacheKeyIsTeamMember(teamId, login)
	if _, ok := teamMembershipCache[cacheKey]; !ok {
		newOk, _, err := auth.client.Organizations.IsTeamMember(teamId, login)
		if err != nil {
			log.Printf("ERROR performing IsTeamMember(%d, \"%s\"): %v", teamId, login, err)
			return false
		}
		teamMembershipCache[cacheKey] = newOk
	}
	return teamMembershipCache[cacheKey]
}

func (auth Authenticator) teamHasPushAccess(teamId int, owner, repo string) bool {
	cacheKey := auth.cacheKeyTeamHashPushAccess(teamId, owner, repo)
	if _, ok := teamHasPushAccessCache[cacheKey]; !ok {
		repository, _, err := auth.client.Organizations.IsTeamRepo(teamId, owner, repo)
		if err != nil {
			log.Printf("ERROR performing IsTeamRepo(%d, \"%s\", \"%s\"): %v", teamId, repo, err)
			return false
		}
		if repository == nil {
			return false
		}
		teamHasPushAccessCache[cacheKey] = repository
	}
	permissions := *teamHasPushAccessCache[cacheKey].Permissions
	return permissions["push"] || permissions["admin"]
}

func (auth Authenticator) teamsForOrg(org string) []github.Team {
	if _, ok := teamsCache[org]; !ok {
		teamz, _, err := auth.client.Organizations.ListTeams(org, &github.ListOptions{
			PerPage: 100,
		})
		if err != nil {
			log.Printf("ERROR performing ListTeams(\"%s\"): %v", org, err)
			return nil
		}
		teamsCache[org] = teamz
	}
	return teamsCache[org]
}

func (auth Authenticator) cacheKeyIsTeamMember(teamId int, login string) string {
	return fmt.Sprintf("%d_%s", teamId, login)
}

func (auth Authenticator) cacheKeyTeamHashPushAccess(teamId int, owner, repo string) string {
	return fmt.Sprintf("%d_%s_%s", teamId, owner, repo)
}
<|endoftext|>


<commit_before>package menu

import (
	gltext "github.com/4ydx/gltext"
	glfw "github.com/go-gl/glfw3"
	"github.com/go-gl/glow/gl-core/3.3/gl"
	"github.com/go-gl/mathgl/mgl32"
	"os"
)

type Point struct {
	X, Y float32
}

var vertexShaderSource string = `
#version 330

uniform mat4 matrix;

in vec4 position;

void main() {
  gl_Position = matrix * position;
}
` + "\x00"

var fragmentShaderSource string = `
#version 330

out vec4 fragment_color;

void main() {
  fragment_color = vec4(1,1,1,1);
}
` + "\x00"

type Menu struct {
	// options
	Visible      bool
	ShowOn       glfw.Key
	Height       float32
	Width        float32
	IsAutoCenter bool
	lowerLeft    Point

	// interactive objects
	Font          *gltext.Font
	Labels        []*Label
	TextScaleRate float32 // increment during a scale operation

	// opengl oriented
	WindowWidth   float32
	WindowHeight  float32
	program       uint32 // shader program
	glMatrix      int32  // ortho matrix
	position      uint32 // index location
	vao           uint32
	vbo           uint32
	ebo           uint32
	ortho         mgl32.Mat4
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int
}

func (menu *Menu) AddLabel(label *Label, str string) {
	label.Load(menu, menu.Font)
	label.Text.SetString(str)
	label.Text.SetScale(1)
	label.Text.SetPosition(0, 0)
	label.Text.SetColor(0, 0, 0, 1)
	menu.Labels = append(menu.Labels, label)
}

func (menu *Menu) Toggle() {
	menu.Visible = !menu.Visible
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
}

func (menu *Menu) Load(width float32, height float32, scale int32) (err error) {
	glfloat_size := 4
	glint_size := 4

	menu.Visible = false
	menu.ShowOn = glfw.KeyM
	menu.Width = width
	menu.Height = height

	// load font
	fd, err := os.Open("font/luximr.ttf")
	if err != nil {
		return
	}
	defer fd.Close()

	menu.Font, err = gltext.LoadTruetype(fd, scale, 32, 127)
	if err != nil {
		return
	}

	// 2DO: make this time dependent rather than fps dependent
	menu.TextScaleRate = 0.01

	// create shader program and define attributes and uniforms
	menu.program, err = gltext.NewProgram(vertexShaderSource, fragmentShaderSource)
	if err != nil {
		return
	}
	menu.glMatrix = gl.GetUniformLocation(menu.program, gl.Str("matrix\x00"))
	menu.position = uint32(gl.GetAttribLocation(menu.program, gl.Str("position\x00")))

	gl.GenVertexArrays(1, &menu.vao)
	gl.GenBuffers(1, &menu.vbo)
	gl.GenBuffers(1, &menu.ebo)

	// vao
	gl.BindVertexArray(menu.vao)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)

	gl.EnableVertexAttribArray(menu.position)
	gl.VertexAttribPointer(
		menu.position,
		2,
		gl.FLOAT,
		false,
		0, // no stride... yet
		gl.PtrOffset(0),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	// ebo, vbo data
	menu.vboIndexCount = 4 * 2 // four indices (2 points per index)
	menu.eboIndexCount = 6     // 6 triangle indices for a quad
	menu.vboData = make([]float32, menu.vboIndexCount, menu.vboIndexCount)
	menu.eboData = make([]int32, menu.eboIndexCount, menu.eboIndexCount)
	menu.lowerLeft = menu.findCenter()
	menu.makeBufferData()

	// setup context
	gl.BindVertexArray(menu.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, glfloat_size*menu.vboIndexCount, gl.Ptr(menu.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, glint_size*menu.eboIndexCount, gl.Ptr(menu.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return nil
}

func (menu *Menu) ResizeWindow(width float32, height float32) {
	menu.WindowWidth = width
	menu.WindowHeight = height
	menu.Font.ResizeWindow(width, height)
	menu.ortho = mgl32.Ortho2D(-menu.WindowWidth/2, menu.WindowWidth/2, -menu.WindowHeight/2, menu.WindowHeight/2)
}

func (menu *Menu) makeBufferData() {
	// index (0,0)
	menu.vboData[0] = menu.lowerLeft.X // position
	menu.vboData[1] = menu.lowerLeft.Y

	// index (1,0)
	menu.vboData[2] = menu.lowerLeft.X + menu.Width
	menu.vboData[3] = menu.lowerLeft.Y

	// index (1,1)
	menu.vboData[4] = menu.lowerLeft.X + menu.Width
	menu.vboData[5] = menu.lowerLeft.Y + menu.Height

	// index (0,1)
	menu.vboData[6] = menu.lowerLeft.X
	menu.vboData[7] = menu.lowerLeft.Y + menu.Height

	menu.eboData[0] = 0
	menu.eboData[1] = 1
	menu.eboData[2] = 2
	menu.eboData[3] = 0
	menu.eboData[4] = 2
	menu.eboData[5] = 3
}

func (menu *Menu) Release() {
	gl.DeleteBuffers(1, &menu.vbo)
	gl.DeleteBuffers(1, &menu.ebo)
	gl.DeleteBuffers(1, &menu.vao)
	for i := range menu.Labels {
		menu.Labels[i].Text.Release()
		if menu.Labels[i].Shadow.Text != nil {
			menu.Labels[i].Shadow.Text.Release()
		}
	}
}

func (menu *Menu) Draw() bool {
	if !menu.Visible {
		return menu.Visible
	}
	gl.UseProgram(menu.program)

	gl.UniformMatrix4fv(menu.glMatrix, 1, false, &menu.ortho[0])

	gl.BindVertexArray(menu.vao)
	gl.DrawElements(gl.TRIANGLES, int32(menu.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	for i := range menu.Labels {
		if !menu.Labels[i].IsHover {
			if menu.Labels[i].OnNotHover != nil {
				menu.Labels[i].OnNotHover(menu.Labels[i])
				if menu.Labels[i].Shadow != nil {
					menu.Labels[i].OnNotHover(&menu.Labels[i].Shadow.Label)
				}
			}
		}
		menu.Labels[i].Draw()
	}
	return menu.Visible
}

func (menu *Menu) OrthoToScreenCoord() (x, y float32) {
	x = menu.lowerLeft.X + menu.WindowWidth/2
	y = menu.lowerLeft.Y + menu.WindowHeight/2
	return
}

func (menu *Menu) ScreenClick(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].IsClicked != nil {
			menu.Labels[i].IsClicked(xPos, yPos)
		}
	}
}

func (menu *Menu) ScreenHover(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].IsHovered != nil {
			menu.Labels[i].IsHovered(xPos, yPos)
		}
	}
}

func (menu *Menu) findCenter() (lowerLeft Point) {
	menuWidthHalf := menu.Width / 2
	menuHeightHalf := menu.Height / 2

	lowerLeft.X = -menuWidthHalf
	lowerLeft.Y = -menuHeightHalf
	return
}
<commit_msg>Added control over background color.<commit_after>package menu

import (
	gltext "github.com/4ydx/gltext"
	glfw "github.com/go-gl/glfw3"
	"github.com/go-gl/glow/gl-core/3.3/gl"
	"github.com/go-gl/mathgl/mgl32"
	"os"
)

type Point struct {
	X, Y float32
}

var vertexShaderSource string = `
#version 330

uniform mat4 matrix;

in vec4 position;

void main() {
  gl_Position = matrix * position;
}
` + "\x00"

var fragmentShaderSource string = `
#version 330

uniform vec4 background;
out vec4 fragment_color;

void main() {
  fragment_color = background;
}
` + "\x00"

type Menu struct {
	// options
	Visible      bool
	ShowOn       glfw.Key
	Height       float32
	Width        float32
	IsAutoCenter bool
	lowerLeft    Point

	backgroundUniform int32
	Background        mgl32.Vec4

	// interactive objects
	Font          *gltext.Font
	Labels        []*Label
	TextScaleRate float32 // increment during a scale operation

	// opengl oriented
	WindowWidth   float32
	WindowHeight  float32
	program       uint32 // shader program
	glMatrix      int32  // ortho matrix
	position      uint32 // index location
	vao           uint32
	vbo           uint32
	ebo           uint32
	ortho         mgl32.Mat4
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int
}

func (menu *Menu) AddLabel(label *Label, str string) {
	label.Load(menu, menu.Font)
	label.Text.SetString(str)
	label.Text.SetScale(1)
	label.Text.SetPosition(0, 0)
	label.Text.SetColor(0, 0, 0, 1)
	menu.Labels = append(menu.Labels, label)
}

func (menu *Menu) Toggle() {
	menu.Visible = !menu.Visible
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
}

func (menu *Menu) Load(width float32, height float32, scale int32) (err error) {
	glfloat_size := 4
	glint_size := 4

	menu.Visible = false
	menu.ShowOn = glfw.KeyM
	menu.Width = width
	menu.Height = height

	// load font
	fd, err := os.Open("font/luximr.ttf")
	if err != nil {
		return
	}
	defer fd.Close()

	menu.Font, err = gltext.LoadTruetype(fd, scale, 32, 127)
	if err != nil {
		return
	}

	// 2DO: make this time dependent rather than fps dependent
	menu.TextScaleRate = 0.01

	// create shader program and define attributes and uniforms
	menu.program, err = gltext.NewProgram(vertexShaderSource, fragmentShaderSource)
	if err != nil {
		return
	}
	menu.glMatrix = gl.GetUniformLocation(menu.program, gl.Str("matrix\x00"))
	menu.backgroundUniform = gl.GetUniformLocation(menu.program, gl.Str("background\x00"))
	menu.position = uint32(gl.GetAttribLocation(menu.program, gl.Str("position\x00")))

	gl.GenVertexArrays(1, &menu.vao)
	gl.GenBuffers(1, &menu.vbo)
	gl.GenBuffers(1, &menu.ebo)

	// vao
	gl.BindVertexArray(menu.vao)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)

	gl.EnableVertexAttribArray(menu.position)
	gl.VertexAttribPointer(
		menu.position,
		2,
		gl.FLOAT,
		false,
		0, // no stride... yet
		gl.PtrOffset(0),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	// ebo, vbo data
	menu.vboIndexCount = 4 * 2 // four indices (2 points per index)
	menu.eboIndexCount = 6     // 6 triangle indices for a quad
	menu.vboData = make([]float32, menu.vboIndexCount, menu.vboIndexCount)
	menu.eboData = make([]int32, menu.eboIndexCount, menu.eboIndexCount)
	menu.lowerLeft = menu.findCenter()
	menu.makeBufferData()

	// setup context
	gl.BindVertexArray(menu.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, glfloat_size*menu.vboIndexCount, gl.Ptr(menu.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, glint_size*menu.eboIndexCount, gl.Ptr(menu.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return nil
}

func (menu *Menu) ResizeWindow(width float32, height float32) {
	menu.WindowWidth = width
	menu.WindowHeight = height
	menu.Font.ResizeWindow(width, height)
	menu.ortho = mgl32.Ortho2D(-menu.WindowWidth/2, menu.WindowWidth/2, -menu.WindowHeight/2, menu.WindowHeight/2)
}

func (menu *Menu) makeBufferData() {
	// index (0,0)
	menu.vboData[0] = menu.lowerLeft.X // position
	menu.vboData[1] = menu.lowerLeft.Y

	// index (1,0)
	menu.vboData[2] = menu.lowerLeft.X + menu.Width
	menu.vboData[3] = menu.lowerLeft.Y

	// index (1,1)
	menu.vboData[4] = menu.lowerLeft.X + menu.Width
	menu.vboData[5] = menu.lowerLeft.Y + menu.Height

	// index (0,1)
	menu.vboData[6] = menu.lowerLeft.X
	menu.vboData[7] = menu.lowerLeft.Y + menu.Height

	menu.eboData[0] = 0
	menu.eboData[1] = 1
	menu.eboData[2] = 2
	menu.eboData[3] = 0
	menu.eboData[4] = 2
	menu.eboData[5] = 3
}

func (menu *Menu) Release() {
	gl.DeleteBuffers(1, &menu.vbo)
	gl.DeleteBuffers(1, &menu.ebo)
	gl.DeleteBuffers(1, &menu.vao)
	for i := range menu.Labels {
		menu.Labels[i].Text.Release()
		if menu.Labels[i].Shadow.Text != nil {
			menu.Labels[i].Shadow.Text.Release()
		}
	}
}

func (menu *Menu) Draw() bool {
	if !menu.Visible {
		return menu.Visible
	}
	gl.UseProgram(menu.program)

	gl.UniformMatrix4fv(menu.glMatrix, 1, false, &menu.ortho[0])
	gl.Uniform4fv(menu.backgroundUniform, 1, &menu.Background[0])

	gl.BindVertexArray(menu.vao)
	gl.DrawElements(gl.TRIANGLES, int32(menu.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	for i := range menu.Labels {
		if !menu.Labels[i].IsHover {
			if menu.Labels[i].OnNotHover != nil {
				menu.Labels[i].OnNotHover(menu.Labels[i])
				if menu.Labels[i].Shadow != nil {
					menu.Labels[i].OnNotHover(&menu.Labels[i].Shadow.Label)
				}
			}
		}
		menu.Labels[i].Draw()
	}
	return menu.Visible
}

func (menu *Menu) OrthoToScreenCoord() (x, y float32) {
	x = menu.lowerLeft.X + menu.WindowWidth/2
	y = menu.lowerLeft.Y + menu.WindowHeight/2
	return
}

func (menu *Menu) ScreenClick(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].IsClicked != nil {
			menu.Labels[i].IsClicked(xPos, yPos)
		}
	}
}

func (menu *Menu) ScreenHover(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].IsHovered != nil {
			menu.Labels[i].IsHovered(xPos, yPos)
		}
	}
}

func (menu *Menu) findCenter() (lowerLeft Point) {
	menuWidthHalf := menu.Width / 2
	menuHeightHalf := menu.Height / 2

	lowerLeft.X = -menuWidthHalf
	lowerLeft.Y = -menuHeightHalf
	return
}
<|endoftext|>


<commit_before>package connections

import (
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/pquerna/ffjson/ffjson"
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/broadcast"
	"github.com/ivan1993spb/snake-server/game"
	"github.com/ivan1993spb/snake-server/player"
)

const (
	chanPlayerOutputMessageBuffer  = 256
	chanPlayerEncodedMessageBuffer = 1024

	chanMergePreparedMessageBuffer = 8192

	chanReadMessagesBuffer           = 64
	chanDecodeMessageBuffer          = 64
	chanProxyInputMessageBuffer      = 64
	chanInputMessagesSnakeBuffer     = 64
	chanInputMessagesBroadcastBuffer = 64
	chanSnakeCommandsBuffer          = 64

	sendInputMessageTimeout  = time.Millisecond * 5
	sendOutputMessageTimeout = time.Millisecond * 25

	broadcastDelay = time.Second * 15

	ignoredBroadcastsCountToDisconnect = 40
)

type ConnectionWorker struct {
	conn   *websocket.Conn
	logger logrus.FieldLogger

	chsInput    []chan InputMessage
	chsInputMux *sync.RWMutex

	flagStarted bool
	startedMux  *sync.Mutex
}

func NewConnectionWorker(conn *websocket.Conn, logger logrus.FieldLogger) *ConnectionWorker {
	return &ConnectionWorker{
		conn:        conn,
		logger:      logger,
		chsInput:    make([]chan InputMessage, 0),
		chsInputMux: &sync.RWMutex{},

		flagStarted: false,
		startedMux:  &sync.Mutex{},
	}
}

type ErrStartConnectionWorker string

func (e ErrStartConnectionWorker) Error() string {
	return "error start connection worker: " + string(e)
}

func (cw *ConnectionWorker) Start(stop <-chan struct{}, game *game.Game, broadcast *broadcast.GroupBroadcast, gamePreparedMessages <-chan *websocket.PreparedMessage) error {
	cw.startedMux.Lock()
	if cw.flagStarted {
		cw.startedMux.Unlock()
		return ErrStartConnectionWorker("connection worker already started")
	}
	cw.flagStarted = true
	cw.startedMux.Unlock()

	broadcast.BroadcastMessage("user joined your game group")

	// Input
	chInputBytes, chStop := cw.read()
	chInputMessages := cw.decode(chInputBytes, chStop)
	cw.broadcastInputMessage(chInputMessages, chStop)
	chCommands := cw.listenSnakeCommands(chStop, cw.input(chStop, chanInputMessagesSnakeBuffer))
	cw.listenPlayerBroadcasts(chStop, cw.input(chStop, chanInputMessagesBroadcastBuffer), broadcast, broadcastDelay)

	p := player.NewPlayer(cw.logger, game.World())

	// Output
	chPlayer := p.Start(chStop, chCommands)
	chOutputBytes := cw.encode(chStop, cw.listenPlayer(chStop, chPlayer))
	chPlayerPreparedMessages := cw.prepare(chStop, chOutputBytes)
	chPreparedMessages := cw.mergePreparedMessagesChs(chStop, chPlayerPreparedMessages, gamePreparedMessages)
	chPreparedMessagesTimeout := cw.chPreparedMessageTimeout(chPreparedMessages, chStop, sendOutputMessageTimeout)
	cw.write(chPreparedMessagesTimeout, chStop)

	select {
	case <-chStop:
		// On connection error
	case <-stop:
		// External stop
		cw.logger.Warn("stop connection worker from external stopper channel")
	}

	broadcast.BroadcastMessage("user left your game group")

	cw.stopInputs()

	return nil
}

func (cw *ConnectionWorker) stopInputs() {
	cw.chsInputMux.Lock()
	defer cw.chsInputMux.Unlock()

	for _, ch := range cw.chsInput {
		close(ch)
	}

	cw.chsInput = cw.chsInput[:0]
}

func (cw *ConnectionWorker) read() (<-chan []byte, <-chan struct{}) {
	chout := make(chan []byte, chanReadMessagesBuffer)
	chstop := make(chan struct{}, 0)

	go func() {
		defer close(chout)
		defer close(chstop)

		for {
			messageType, data, err := cw.conn.ReadMessage()
			if err != nil {
				if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
					cw.logger.Errorln("read input message error:", err)
				}
				return
			}

			if websocket.TextMessage != messageType {
				cw.logger.Warning("unexpected input message type")
				continue
			}

			chout <- data
		}
	}()

	return chout, chstop
}

func (cw *ConnectionWorker) decode(chin <-chan []byte, stop <-chan struct{}) <-chan InputMessage {
	chout := make(chan InputMessage, chanDecodeMessageBuffer)

	go func() {
		defer close(chout)

		var decoder = ffjson.NewDecoder()

		for {
			select {
			case data, ok := <-chin:
				if !ok {
					return
				}

				var inputMessage InputMessage
				if err := decoder.Decode(data, &inputMessage); err != nil {
					cw.logger.Errorln("decode input message error:", err)
				} else {
					select {
					case <-stop:
						return
					case chout <- inputMessage:
					}
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) broadcastInputMessage(chin <-chan InputMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case inputMessage, ok := <-chin:
				if !ok {
					return
				}

				cw.doBroadcast(inputMessage, stop)
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) doBroadcast(message InputMessage, stop <-chan struct{}) {
	cw.chsInputMux.RLock()
	defer cw.chsInputMux.RUnlock()

	for _, ch := range cw.chsInput {
		select {
		case ch <- message:
		case <-stop:
			return
		}
	}
}

func (cw *ConnectionWorker) input(stop <-chan struct{}, buffer uint) <-chan InputMessage {
	chProxy := make(chan InputMessage, chanProxyInputMessageBuffer)

	cw.chsInputMux.Lock()
	cw.chsInput = append(cw.chsInput, chProxy)
	cw.chsInputMux.Unlock()

	chout := make(chan InputMessage, buffer)

	go func() {
		defer close(chout)
		defer func() {
			go func() {
				for range chProxy {
				}
			}()

			cw.chsInputMux.Lock()
			for i := range cw.chsInput {
				if cw.chsInput[i] == chProxy {
					cw.chsInput = append(cw.chsInput[:i], cw.chsInput[i+1:]...)
					close(chProxy)
					break
				}
			}
			cw.chsInputMux.Unlock()
		}()

		for {
			select {
			case <-stop:
				return
			case inputMessage, ok := <-chProxy:
				if !ok {
					return
				}
				cw.sendInputMessage(chout, inputMessage, stop, sendInputMessageTimeout)
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) sendInputMessage(ch chan InputMessage, inputMessage InputMessage, stop <-chan struct{}, timeout time.Duration) {
	var timer = time.NewTimer(timeout)
	defer timer.Stop()

	select {
	case ch <- inputMessage:
	case <-stop:
	case <-timer.C:
		cw.logger.WithFields(logrus.Fields{
			"timeout":      timeout,
			"message_type": inputMessage.Type,
		}).Warn("send input message to processing: time is out")
	}
}

func (cw *ConnectionWorker) listenSnakeCommands(stop <-chan struct{}, chin <-chan InputMessage) <-chan string {
	chout := make(chan string, chanSnakeCommandsBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case message, ok := <-chin:
				if !ok {
					return
				}

				if message.Type == InputMessageTypeSnakeCommand {
					select {
					case chout <- message.Payload:
					case <-stop:
						return
					}
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) listenPlayerBroadcasts(stop <-chan struct{}, chin <-chan InputMessage, b *broadcast.GroupBroadcast, delay time.Duration) {
	go func() {
		var (
			lastBroadcastTime time.Time
			ignored           int
		)

		for {
			select {
			case message, ok := <-chin:
				if !ok {
					return
				}

				if message.Type == InputMessageTypeBroadcast {
					if time.Since(lastBroadcastTime) > delay {
						b.BroadcastMessage(broadcast.Message(message.Payload))
						lastBroadcastTime = time.Now()
						ignored = 0
					} else {
						ignored += 1
						cw.logger.Warn("ignore broadcast: delay")

						if ignored > ignoredBroadcastsCountToDisconnect {
							cw.logger.Warn("ignored broadcasts limit reached")
							if err := cw.conn.Close(); err != nil {
								cw.logger.WithError(err).Error("close connection error")
							}
						}
					}
				}
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) listenPlayer(stop <-chan struct{}, chin <-chan player.Message) <-chan OutputMessage {
	chout := make(chan OutputMessage, chanPlayerOutputMessageBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case event, ok := <-chin:
				if !ok {
					return
				}

				outputMessage := OutputMessage{
					Type:    OutputMessageTypePlayer,
					Payload: event,
				}

				select {
				case chout <- outputMessage:
				case <-stop:
					return
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) encode(stop <-chan struct{}, chins ...<-chan OutputMessage) <-chan []byte {
	chout := make(chan []byte, chanPlayerEncodedMessageBuffer)

	wg := sync.WaitGroup{}
	wg.Add(len(chins))

	for _, chin := range chins {
		go func(chin <-chan OutputMessage) {
			defer wg.Done()
			for {
				select {
				case <-stop:
					return
				case message, ok := <-chin:
					if !ok {
						return
					}

					if data, err := ffjson.Marshal(message); err != nil {
						cw.logger.Errorln("encode output message error:", err)
					} else {
						select {
						case <-stop:
							return
						case chout <- data:
						}
					}
				}
			}
		}(chin)
	}

	go func() {
		wg.Wait()
		close(chout)
	}()

	return chout
}

func (cw *ConnectionWorker) prepare(stop <-chan struct{}, chin <-chan []byte) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, cap(chin))

	go func() {
		defer close(chout)

		for {
			select {
			case data, ok := <-chin:
				if !ok {
					return
				}

				if pm, err := websocket.NewPreparedMessage(websocket.TextMessage, data); err != nil {
					cw.logger.Errorln("prepare player output message error:", err)
				} else {
					select {
					case chout <- pm:
					case <-stop:
						return
					}
				}

			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) mergePreparedMessagesChs(stop <-chan struct{}, chins ...<-chan *websocket.PreparedMessage) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, chanMergePreparedMessageBuffer)

	wg := sync.WaitGroup{}
	wg.Add(len(chins))

	for _, chin := range chins {
		go func(chin <-chan *websocket.PreparedMessage) {
			defer wg.Done()

			for {
				select {
				case <-stop:
					return
				case data, ok := <-chin:
					if !ok {
						return
					}

					select {
					case chout <- data:
					case <-stop:
						return
					}
				}
			}
		}(chin)
	}

	go func() {
		wg.Wait()
		close(chout)
	}()

	return chout
}

func (cw *ConnectionWorker) chPreparedMessageTimeout(chin <-chan *websocket.PreparedMessage, stop <-chan struct{}, timeout time.Duration) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, cap(chin))

	go func() {
		defer close(chout)

		for {
			select {
			case pm, ok := <-chin:
				if !ok {
					return
				}

				timer := time.NewTimer(timeout)

				select {
				case chout <- pm:
					timer.Stop()
				case <-timer.C:
					cw.logger.Warn("send message for writing time is out")
					timer.Stop()
					continue
				case <-stop:
					timer.Stop()
					return
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) write(chin <-chan *websocket.PreparedMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case pm, ok := <-chin:
				if !ok {
					return
				}

				if err := cw.conn.WritePreparedMessage(pm); err != nil {
					if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
						cw.logger.Errorln("write output message error:", err)
					}
					return
				}
			case <-stop:
				return
			}
		}
	}()
}
<commit_msg>Create todo in connection worker<commit_after>package connections

import (
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/pquerna/ffjson/ffjson"
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/broadcast"
	"github.com/ivan1993spb/snake-server/game"
	"github.com/ivan1993spb/snake-server/player"
)

const (
	chanPlayerOutputMessageBuffer  = 256
	chanPlayerEncodedMessageBuffer = 1024

	chanMergePreparedMessageBuffer = 8192

	chanReadMessagesBuffer           = 64
	chanDecodeMessageBuffer          = 64
	chanProxyInputMessageBuffer      = 64
	chanInputMessagesSnakeBuffer     = 64
	chanInputMessagesBroadcastBuffer = 64
	chanSnakeCommandsBuffer          = 64

	sendInputMessageTimeout  = time.Millisecond * 5
	sendOutputMessageTimeout = time.Millisecond * 25

	broadcastDelay = time.Second * 15

	ignoredBroadcastsCountToDisconnect = 40
)

type ConnectionWorker struct {
	conn   *websocket.Conn
	logger logrus.FieldLogger

	chsInput    []chan InputMessage
	chsInputMux *sync.RWMutex

	flagStarted bool
	startedMux  *sync.Mutex
}

func NewConnectionWorker(conn *websocket.Conn, logger logrus.FieldLogger) *ConnectionWorker {
	return &ConnectionWorker{
		conn:        conn,
		logger:      logger,
		chsInput:    make([]chan InputMessage, 0),
		chsInputMux: &sync.RWMutex{},

		flagStarted: false,
		startedMux:  &sync.Mutex{},
	}
}

type ErrStartConnectionWorker string

func (e ErrStartConnectionWorker) Error() string {
	return "error start connection worker: " + string(e)
}

func (cw *ConnectionWorker) Start(stop <-chan struct{}, game *game.Game, broadcast *broadcast.GroupBroadcast, gamePreparedMessages <-chan *websocket.PreparedMessage) error {
	cw.startedMux.Lock()
	if cw.flagStarted {
		cw.startedMux.Unlock()
		return ErrStartConnectionWorker("connection worker already started")
	}
	cw.flagStarted = true
	cw.startedMux.Unlock()

	broadcast.BroadcastMessage("user joined your game group")

	// Input
	chInputBytes, chStop := cw.read()
	chInputMessages := cw.decode(chInputBytes, chStop)
	cw.broadcastInputMessage(chInputMessages, chStop)
	chCommands := cw.listenSnakeCommands(chStop, cw.input(chStop, chanInputMessagesSnakeBuffer))
	cw.listenPlayerBroadcasts(chStop, cw.input(chStop, chanInputMessagesBroadcastBuffer), broadcast, broadcastDelay)

	p := player.NewPlayer(cw.logger, game.World())

	// Output
	chPlayer := p.Start(chStop, chCommands)
	chOutputBytes := cw.encode(chStop, cw.listenPlayer(chStop, chPlayer))
	chPlayerPreparedMessages := cw.prepare(chStop, chOutputBytes)
	chPreparedMessages := cw.mergePreparedMessagesChs(chStop, chPlayerPreparedMessages, gamePreparedMessages)
	chPreparedMessagesTimeout := cw.chPreparedMessageTimeout(chPreparedMessages, chStop, sendOutputMessageTimeout)
	cw.write(chPreparedMessagesTimeout, chStop)

	select {
	case <-chStop:
		// On connection error
	case <-stop:
		// External stop
		cw.logger.Warn("stop connection worker from external stopper channel")
	}

	broadcast.BroadcastMessage("user left your game group")

	cw.stopInputs()

	return nil
}

func (cw *ConnectionWorker) stopInputs() {
	cw.chsInputMux.Lock()
	defer cw.chsInputMux.Unlock()

	for _, ch := range cw.chsInput {
		close(ch)
	}

	cw.chsInput = cw.chsInput[:0]
}

func (cw *ConnectionWorker) read() (<-chan []byte, <-chan struct{}) {
	chout := make(chan []byte, chanReadMessagesBuffer)
	chstop := make(chan struct{}, 0)

	go func() {
		defer close(chout)
		defer close(chstop)

		for {
			messageType, data, err := cw.conn.ReadMessage()
			if err != nil {
				if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
					cw.logger.Errorln("read input message error:", err)
				}
				return
			}

			if websocket.TextMessage != messageType {
				cw.logger.Warning("unexpected input message type")
				continue
			}

			chout <- data
		}
	}()

	return chout, chstop
}

func (cw *ConnectionWorker) decode(chin <-chan []byte, stop <-chan struct{}) <-chan InputMessage {
	chout := make(chan InputMessage, chanDecodeMessageBuffer)

	go func() {
		defer close(chout)

		var decoder = ffjson.NewDecoder()

		for {
			select {
			case data, ok := <-chin:
				if !ok {
					return
				}

				var inputMessage InputMessage
				if err := decoder.Decode(data, &inputMessage); err != nil {
					cw.logger.Errorln("decode input message error:", err)
				} else {
					select {
					case <-stop:
						return
					case chout <- inputMessage:
					}
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) broadcastInputMessage(chin <-chan InputMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case inputMessage, ok := <-chin:
				if !ok {
					return
				}

				cw.doBroadcast(inputMessage, stop)
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) doBroadcast(message InputMessage, stop <-chan struct{}) {
	cw.chsInputMux.RLock()
	defer cw.chsInputMux.RUnlock()

	for _, ch := range cw.chsInput {
		select {
		case ch <- message:
		case <-stop:
			return
		}
	}
}

func (cw *ConnectionWorker) input(stop <-chan struct{}, buffer uint) <-chan InputMessage {
	chProxy := make(chan InputMessage, chanProxyInputMessageBuffer)

	cw.chsInputMux.Lock()
	cw.chsInput = append(cw.chsInput, chProxy)
	cw.chsInputMux.Unlock()

	chout := make(chan InputMessage, buffer)

	go func() {
		defer close(chout)
		defer func() {
			go func() {
				for range chProxy {
				}
			}()

			cw.chsInputMux.Lock()
			for i := range cw.chsInput {
				if cw.chsInput[i] == chProxy {
					cw.chsInput = append(cw.chsInput[:i], cw.chsInput[i+1:]...)
					close(chProxy)
					break
				}
			}
			cw.chsInputMux.Unlock()
		}()

		for {
			select {
			case <-stop:
				return
			case inputMessage, ok := <-chProxy:
				if !ok {
					return
				}
				cw.sendInputMessage(chout, inputMessage, stop, sendInputMessageTimeout)
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) sendInputMessage(ch chan InputMessage, inputMessage InputMessage, stop <-chan struct{}, timeout time.Duration) {
	var timer = time.NewTimer(timeout)
	defer timer.Stop()

	select {
	case ch <- inputMessage:
	case <-stop:
	case <-timer.C:
		cw.logger.WithFields(logrus.Fields{
			"timeout":      timeout,
			"message_type": inputMessage.Type,
		}).Warn("send input message to processing: time is out")
	}
}

func (cw *ConnectionWorker) listenSnakeCommands(stop <-chan struct{}, chin <-chan InputMessage) <-chan string {
	chout := make(chan string, chanSnakeCommandsBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case message, ok := <-chin:
				if !ok {
					return
				}

				if message.Type == InputMessageTypeSnakeCommand {
					select {
					case chout <- message.Payload:
					case <-stop:
						return
					}
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) listenPlayerBroadcasts(stop <-chan struct{}, chin <-chan InputMessage, b *broadcast.GroupBroadcast, delay time.Duration) {
	go func() {
		var (
			lastBroadcastTime time.Time
			ignored           int
		)

		for {
			select {
			case message, ok := <-chin:
				if !ok {
					return
				}

				if message.Type == InputMessageTypeBroadcast {
					if time.Since(lastBroadcastTime) > delay {
						b.BroadcastMessage(broadcast.Message(message.Payload))
						lastBroadcastTime = time.Now()
						ignored = 0
					} else {
						ignored += 1
						cw.logger.Warn("ignore broadcast: delay")

						if ignored > ignoredBroadcastsCountToDisconnect {
							// TODO: Send an error message to the connection before closing
							cw.logger.Warn("ignored broadcasts limit reached")
							if err := cw.conn.Close(); err != nil {
								cw.logger.WithError(err).Error("close connection error")
							}
						}
					}
				}
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) listenPlayer(stop <-chan struct{}, chin <-chan player.Message) <-chan OutputMessage {
	chout := make(chan OutputMessage, chanPlayerOutputMessageBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case event, ok := <-chin:
				if !ok {
					return
				}

				outputMessage := OutputMessage{
					Type:    OutputMessageTypePlayer,
					Payload: event,
				}

				select {
				case chout <- outputMessage:
				case <-stop:
					return
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) encode(stop <-chan struct{}, chins ...<-chan OutputMessage) <-chan []byte {
	chout := make(chan []byte, chanPlayerEncodedMessageBuffer)

	wg := sync.WaitGroup{}
	wg.Add(len(chins))

	for _, chin := range chins {
		go func(chin <-chan OutputMessage) {
			defer wg.Done()
			for {
				select {
				case <-stop:
					return
				case message, ok := <-chin:
					if !ok {
						return
					}

					if data, err := ffjson.Marshal(message); err != nil {
						cw.logger.Errorln("encode output message error:", err)
					} else {
						select {
						case <-stop:
							return
						case chout <- data:
						}
					}
				}
			}
		}(chin)
	}

	go func() {
		wg.Wait()
		close(chout)
	}()

	return chout
}

func (cw *ConnectionWorker) prepare(stop <-chan struct{}, chin <-chan []byte) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, cap(chin))

	go func() {
		defer close(chout)

		for {
			select {
			case data, ok := <-chin:
				if !ok {
					return
				}

				if pm, err := websocket.NewPreparedMessage(websocket.TextMessage, data); err != nil {
					cw.logger.Errorln("prepare player output message error:", err)
				} else {
					select {
					case chout <- pm:
					case <-stop:
						return
					}
				}

			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) mergePreparedMessagesChs(stop <-chan struct{}, chins ...<-chan *websocket.PreparedMessage) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, chanMergePreparedMessageBuffer)

	wg := sync.WaitGroup{}
	wg.Add(len(chins))

	for _, chin := range chins {
		go func(chin <-chan *websocket.PreparedMessage) {
			defer wg.Done()

			for {
				select {
				case <-stop:
					return
				case data, ok := <-chin:
					if !ok {
						return
					}

					select {
					case chout <- data:
					case <-stop:
						return
					}
				}
			}
		}(chin)
	}

	go func() {
		wg.Wait()
		close(chout)
	}()

	return chout
}

func (cw *ConnectionWorker) chPreparedMessageTimeout(chin <-chan *websocket.PreparedMessage, stop <-chan struct{}, timeout time.Duration) <-chan *websocket.PreparedMessage {
	chout := make(chan *websocket.PreparedMessage, cap(chin))

	go func() {
		defer close(chout)

		for {
			select {
			case pm, ok := <-chin:
				if !ok {
					return
				}

				timer := time.NewTimer(timeout)

				select {
				case chout <- pm:
					timer.Stop()
				case <-timer.C:
					cw.logger.Warn("send message for writing time is out")
					timer.Stop()
					continue
				case <-stop:
					timer.Stop()
					return
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) write(chin <-chan *websocket.PreparedMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case pm, ok := <-chin:
				if !ok {
					return
				}

				if err := cw.conn.WritePreparedMessage(pm); err != nil {
					if !websocket.IsCloseError(err, websocket.CloseNormalClosure) {
						cw.logger.Errorln("write output message error:", err)
					}
					return
				}
			case <-stop:
				return
			}
		}
	}()
}
<|endoftext|>


<commit_before>package boardgame

import (
	"encoding/json"
)

//A JSONObject is an object that is ready to be serialized to JSON.
type JSONObject interface{}

//JSONMap is just convenient where you need to create a map
type JSONMap map[string]JSONObject

type JSONer interface {
	//Returns the canonical JSON representation of this object, suitable to
	//being communicated across the wire or saved in a DB.
	JSON() JSONObject
}

//Serialize converts the JSONObject into bytes, suitable for being transferred
//across the wire or written to disk.
func Serialize(o JSONObject) []byte {
	result, err := json.Marshal(o)
	if err != nil {
		return []byte("")
	}
	return result
}
<commit_msg>Serialize on JSONObject outputs indented output<commit_after>package boardgame

import (
	"encoding/json"
)

//A JSONObject is an object that is ready to be serialized to JSON.
type JSONObject interface{}

//JSONMap is just convenient where you need to create a map
type JSONMap map[string]JSONObject

type JSONer interface {
	//Returns the canonical JSON representation of this object, suitable to
	//being communicated across the wire or saved in a DB.
	JSON() JSONObject
}

//Serialize converts the JSONObject into bytes, suitable for being transferred
//across the wire or written to disk.
func Serialize(o JSONObject) []byte {
	result, err := json.MarshalIndent(o, "", "  ")
	if err != nil {
		return []byte("")
	}
	return result
}
<|endoftext|>


<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package juju

// temporary types to break the state <> apiserver dependency loop

// UnitSettings holds information about a service unit's settings
// within a relation.
type UnitSettings struct {
	Version int64
}

// MachineJob values define responsibilities that machines may be
// expected to fulfil.
type MachineJob string

const (
	JobHostUnits        MachineJob = "JobHostUnits"
	JobManageEnviron    MachineJob = "JobManageEnviron"
	JobManageNetworking MachineJob = "JobManageNetworking"

	// Deprecated in 1.18
	JobManageStateDeprecated MachineJob = "JobManageState"
)

// NeedsState returns true if the job requires a state connection.
func (job MachineJob) NeedsState() bool {
	return job == JobManageEnviron
}

// AnyJobNeedsState returns true if any of the provided jobs
// require a state connection.
func AnyJobNeedsState(jobs ...MachineJob) bool {
	for _, j := range jobs {
		if j.NeedsState() {
			return true
		}
	}
	return false
}
<commit_msg>remove vestigial type decl<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package juju

// temporary types to break the state <> apiserver dependency loop

// MachineJob values define responsibilities that machines may be
// expected to fulfil.
type MachineJob string

const (
	JobHostUnits        MachineJob = "JobHostUnits"
	JobManageEnviron    MachineJob = "JobManageEnviron"
	JobManageNetworking MachineJob = "JobManageNetworking"

	// Deprecated in 1.18
	JobManageStateDeprecated MachineJob = "JobManageState"
)

// NeedsState returns true if the job requires a state connection.
func (job MachineJob) NeedsState() bool {
	return job == JobManageEnviron
}

// AnyJobNeedsState returns true if any of the provided jobs
// require a state connection.
func AnyJobNeedsState(jobs ...MachineJob) bool {
	for _, j := range jobs {
		if j.NeedsState() {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"github.com/thoj/go-ircevent"
	"hash"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

type GitHubAdapter struct {
	ircbot   *irc.Connection
	config   *GitHubConfig
	channels map[string]*GitHubRepositoryConfig
}

// Every GithubEvent has a method Strings that returns a slice of string with the text for the irc PRIVMSG.
type GithubEvent interface {
        Strings() []string
        GetRepository() string
}

type GithubCreate struct {
	Ref          string
	RefType      string `json:"ref_type"`
	MasterBranch string
	Description  string
	PusherType   string
	Repository   GithubRepository
	Sender       GithubUser
}

type GithubDelete struct {
        Ref             string
        RefType         string `json:"ref_type"`
        PusherType      string
        Repository      GithubRepository
        Sender          GithubUser
}

type GithubPush struct {
	Ref        string
	After      string
	Before     string
	Created    bool
	Deleted    bool
	Forced     bool
	Compare    string
	Commits    []GithubCommit
	HeadCommit GithubCommit `json:"head_commit"`
	Repository GithubRepository
	Pusher     GithubUser
}

type GithubCommit struct {
	Id        string
	Distinct  bool
	Message   string
	Timestamp string
	Url       string
	Author    GithubUser
	Committer GithubUser
	Added     []string
	Removed   []string
	Modified  []string
}

type GithubRepository struct {
	Id               uint64
	Name             string
	FullName         string `json:"full_name"`
	Owner            GithubUser
	Private          bool
	HtmlUrl          string
	Description      string
	Fork             bool
	Url              string
	ForksUrl         string
	KeysUrl          string
	CollaboratorsUrl string
	TeamsUrl         string
	HooksUrl         string
	IssueEventsUrl   string
	EventsUrl        string
	AssigneesUrl     string
	BranchesUrl      string
	TagsUrl          string
	BlobsUrl         string
	GitTagsUrl       string
	GitRefsUrl       string
	TreesUrl         string
	StatusesUrl      string
	LanguagesUrl     string
	StargazersUrl    string
	ContributorsUrl  string
	SubscribersUrl   string
	SubscriptionUrl  string
	CommitsUrl       string
	GitCommitsUrl    string
	CommentsUrl      string
	CompareUrl       string
	MergesUrl        string
	ArchiveUrl       string
	DownloadsUrl     string
	IssuesUrl        string
	PullsUrl         string
	MilestonesUrl    string
	NotificationsUrl string
	LabelsUrl        string
	ReleasesUrl      string
	CreatedAt        string
	UpdatedAt        string
	PushedAt         string
	GitUrl           string
	SshUrl           string
	CloneUrl         string
	SvnUrl           string
	Homepage         string
	Size             uint64
	StargazersCount  uint64
	WatchersCount    uint64
	Language         string
	HasIssues        bool
	HasDownloads     bool
	HasWiki          bool
	ForksCount       uint64
	MirrorUrl        string
	OpenIssuesCount  uint64
	Forks            uint64
	OpenIssues       uint64
	Watchers         uint64
	DefaultBranch    string
}

type GithubUser struct {
	Login             string
	Id                uint64
	AvatarURL         string
	GravatarId        string
	Url               string
	HtmlUrl           string
	FollowersUrl      string
	FollowingUrl      string
	GistsUrl          string
	StarredUrl        string
	SubscriptionsUrl  string
	OrganizationsUrl  string
	ReposUrl          string
	EventsUrl         string
	ReceivedEventsUrl string
	Type              string
	SiteAdmin         bool
	Name              string
	Email             string
}

// CheckMAC returns true if messageMAC is a valid HMAC tag for message.
func CheckMAC(message []byte, messageMAC string, key string) bool {
	var err error
	var mac hash.Hash
	var macdata []byte
	var macparts = strings.Split(messageMAC, "=")
	macdata, err = hex.DecodeString(macparts[1])
	if err != nil {
		log.Print("Error decoding hex digest: ", err)
		return false
	}
	switch macparts[0] {
	case "md5":
		mac = hmac.New(md5.New, []byte(key))
	case "sha1":
		mac = hmac.New(sha1.New, []byte(key))
	case "sha256":
		mac = hmac.New(sha256.New, []byte(key))
	case "sha512":
		mac = hmac.New(sha512.New, []byte(key))
	default:
		log.Print("Unsupported hash: ", macparts[0])
		return false
	}
	mac.Write(message)
	expectedMAC := mac.Sum(nil)
	return hmac.Equal(macdata, expectedMAC)
}

func (g *GithubUser) String() string {
	if len(g.Name) > 0 && len(g.Email) > 0 {
		return g.Name + " <" + g.Email + ">"
	} else if len(g.Name) > 0 {
		return g.Name
	} else if len(g.Email) > 0 {
		return g.Email
	}
	return g.Login
}

func (g *GithubRepository) String() string {
	if len(g.FullName) > 0 {
		return g.FullName
	}
	return g.Name
}

func (g *GithubCommit) String() string {
	var lines []string = strings.Split(g.Message, "\n")                // Commit message
	var text string
        text = g.Author.String() + " \x02" + g.Id[0:7] + "\x0f" // First 7 characters

	if len(g.Added) > 0 {
		text += " \x0303" + strings.Join(g.Added, " ") + "\x0f"
	}

	if len(g.Removed) > 0 {
		text += " \x0304" + strings.Join(g.Removed, " ") + "\x0f"
	}

	if len(g.Modified) > 0 {
		text += " \x0310" + strings.Join(g.Modified, " ") + "\x0f"
	}
	if len(lines) > 0 {
		text += " " + lines[0]
	}
	return text
}

func (g *GithubPush) Strings() []string {
	var refs []string = strings.Split(g.Ref, "/")
	var prefix string = "\x0303" + g.Repository.String() + "\x0f \x0305" + refs[len(refs)-1] + "\x0f"
	var pushes []string = make([]string, 0)

	for _, commit := range g.Commits {
		pushes = append(pushes, prefix+" "+commit.String())
		pushes = append(pushes, prefix+" "+commit.Url)
	}
	return pushes
}

func (g *GithubPush) GetRepository() string {
       return g.Repository.String()
}

func (g *GithubCreate) Strings() []string {
	return []string{"\x0303" + g.Sender.String() + "\x0f has pushed a new " + g.RefType + " \x0305" + g.Ref + "\x0f to \x0303" + g.Repository.String() + "\x0f"}
}

func (g *GithubCreate) GetRepository() string {
       return g.Repository.String()
}

func (g *GithubDelete) Strings() []string {
	return []string{"\x0303" + g.Sender.String() + "\x0f has deleted a " + g.RefType + " \x0305" + g.Ref + "\x0f from \x0303" + g.Repository.String() + "\x0f"}
}

func (g *GithubDelete) GetRepository() string {
       return g.Repository.String()
}

func (g *GitHubAdapter) WriteGithubEvent(event GithubEvent, body []byte, signature string) error {
        var ok bool
        var githubconf *GitHubRepositoryConfig
        var err error

        err = json.Unmarshal(body, &event)

        if err != nil {
                log.Print("Error decoding github event: ", err)
                return err
        }

        githubconf, ok = g.channels[event.GetRepository()]
        if !ok {
                log.Print("Repository ", event.GetRepository(), " not configured.")
                return err
        }
        if !CheckMAC(body, signature, githubconf.GetSecret()) {
                log.Print("DEBUG Spam, spam spam")
                return err
        }
        for _, channel := range githubconf.GetIrcChannel() {
                for _, commit := range event.Strings() {
                        g.ircbot.Privmsg(channel, commit)
                }
        }
        return err
}

func NewGitHubAdapter(ircbot *irc.Connection, config *GitHubConfig) *GitHubAdapter {
	var channels = make(map[string]*GitHubRepositoryConfig)
	for _, repo := range config.Repo {
		channels[repo.GetName()] = repo
	}
	return &GitHubAdapter{
		ircbot:   ircbot,
		config:   config,
		channels: channels,
	}
}

func (g *GitHubAdapter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	var body []byte
	var err error
	body, err = ioutil.ReadAll(req.Body)
	if err != nil {
		log.Print("Error reading body: ", err)
		return
	}

	switch req.Header.Get("X-GitHub-Event") {
	case "create":
                var create GithubCreate
                g.WriteGithubEvent(&create, body, req.Header.Get("X-Hub-Signature"))
        case "delete":
                var del GithubDelete
                g.WriteGithubEvent(&del, body, req.Header.Get("X-Hub-Signature"))
	case "push":
		var push GithubPush
                g.WriteGithubEvent(&push, body, req.Header.Get("X-Hub-Signature"))
	default:
		log.Print("Unknown GitHub event.", req.Header.Get("X-GitHub-Event"))
	}
}
<commit_msg>If more than 6 github files are changed, aggregate.<commit_after>package main

import (
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"github.com/thoj/go-ircevent"
	"hash"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
        "strconv"
)

type GitHubAdapter struct {
	ircbot   *irc.Connection
	config   *GitHubConfig
	channels map[string]*GitHubRepositoryConfig
}

// Every GithubEvent has a method Strings that returns a slice of string with the text for the irc PRIVMSG.
type GithubEvent interface {
        Strings() []string
        GetRepository() string
}

type GithubCreate struct {
	Ref          string
	RefType      string `json:"ref_type"`
	MasterBranch string
	Description  string
	PusherType   string
	Repository   GithubRepository
	Sender       GithubUser
}

type GithubDelete struct {
        Ref             string
        RefType         string `json:"ref_type"`
        PusherType      string
        Repository      GithubRepository
        Sender          GithubUser
}

type GithubPush struct {
	Ref        string
	After      string
	Before     string
	Created    bool
	Deleted    bool
	Forced     bool
	Compare    string
	Commits    []GithubCommit
	HeadCommit GithubCommit `json:"head_commit"`
	Repository GithubRepository
	Pusher     GithubUser
}

type GithubCommit struct {
	Id        string
	Distinct  bool
	Message   string
	Timestamp string
	Url       string
	Author    GithubUser
	Committer GithubUser
	Added     []string
	Removed   []string
	Modified  []string
}

type GithubRepository struct {
	Id               uint64
	Name             string
	FullName         string `json:"full_name"`
	Owner            GithubUser
	Private          bool
	HtmlUrl          string
	Description      string
	Fork             bool
	Url              string
	ForksUrl         string
	KeysUrl          string
	CollaboratorsUrl string
	TeamsUrl         string
	HooksUrl         string
	IssueEventsUrl   string
	EventsUrl        string
	AssigneesUrl     string
	BranchesUrl      string
	TagsUrl          string
	BlobsUrl         string
	GitTagsUrl       string
	GitRefsUrl       string
	TreesUrl         string
	StatusesUrl      string
	LanguagesUrl     string
	StargazersUrl    string
	ContributorsUrl  string
	SubscribersUrl   string
	SubscriptionUrl  string
	CommitsUrl       string
	GitCommitsUrl    string
	CommentsUrl      string
	CompareUrl       string
	MergesUrl        string
	ArchiveUrl       string
	DownloadsUrl     string
	IssuesUrl        string
	PullsUrl         string
	MilestonesUrl    string
	NotificationsUrl string
	LabelsUrl        string
	ReleasesUrl      string
	CreatedAt        string
	UpdatedAt        string
	PushedAt         string
	GitUrl           string
	SshUrl           string
	CloneUrl         string
	SvnUrl           string
	Homepage         string
	Size             uint64
	StargazersCount  uint64
	WatchersCount    uint64
	Language         string
	HasIssues        bool
	HasDownloads     bool
	HasWiki          bool
	ForksCount       uint64
	MirrorUrl        string
	OpenIssuesCount  uint64
	Forks            uint64
	OpenIssues       uint64
	Watchers         uint64
	DefaultBranch    string
}

type GithubUser struct {
	Login             string
	Id                uint64
	AvatarURL         string
	GravatarId        string
	Url               string
	HtmlUrl           string
	FollowersUrl      string
	FollowingUrl      string
	GistsUrl          string
	StarredUrl        string
	SubscriptionsUrl  string
	OrganizationsUrl  string
	ReposUrl          string
	EventsUrl         string
	ReceivedEventsUrl string
	Type              string
	SiteAdmin         bool
	Name              string
	Email             string
}

// CheckMAC returns true if messageMAC is a valid HMAC tag for message.
func CheckMAC(message []byte, messageMAC string, key string) bool {
	var err error
	var mac hash.Hash
	var macdata []byte
	var macparts = strings.Split(messageMAC, "=")
	macdata, err = hex.DecodeString(macparts[1])
	if err != nil {
		log.Print("Error decoding hex digest: ", err)
		return false
	}
	switch macparts[0] {
	case "md5":
		mac = hmac.New(md5.New, []byte(key))
	case "sha1":
		mac = hmac.New(sha1.New, []byte(key))
	case "sha256":
		mac = hmac.New(sha256.New, []byte(key))
	case "sha512":
		mac = hmac.New(sha512.New, []byte(key))
	default:
		log.Print("Unsupported hash: ", macparts[0])
		return false
	}
	mac.Write(message)
	expectedMAC := mac.Sum(nil)
	return hmac.Equal(macdata, expectedMAC)
}

func (g *GithubUser) String() string {
	if len(g.Name) > 0 && len(g.Email) > 0 {
		return g.Name + " <" + g.Email + ">"
	} else if len(g.Name) > 0 {
		return g.Name
	} else if len(g.Email) > 0 {
		return g.Email
	}
	return g.Login
}

func (g *GithubRepository) String() string {
	if len(g.FullName) > 0 {
		return g.FullName
	}
	return g.Name
}

func (g *GithubCommit) String() string {
	var lines []string = strings.Split(g.Message, "\n")                // Commit message
	var text string
        var short bool = len(g.Added) + len(g.Removed) + len(g.Modified) < 6 // true, if more than 6 files changed

        text = g.Author.String() + " \x02" + g.Id[0:7] + "\x0f" // First 7 characters

        if short {
                if len(g.Added) > 0 {
                        text += " \x0303" + strconv.Itoa(len(g.Added)) + " files added." + "\x0f"
                }
                if len(g.Removed) > 0 {
                        text += " \x0304" + strconv.Itoa(len(g.Removed)) + " files removed." + "\x0f"
                }

                if len(g.Modified) > 0 {
                        text += " \x0310" + strconv.Itoa(len(g.Modified)) + "files modified." + "\x0f"
                }

        } else {
                if len(g.Added) > 0 {
                        text += " \x0303" + strings.Join(g.Added, " ") + "\x0f"
                }

                if len(g.Removed) > 0 {
                        text += " \x0304" + strings.Join(g.Removed, " ") + "\x0f"
                }

                if len(g.Modified) > 0 {
                        text += " \x0310" + strings.Join(g.Modified, " ") + "\x0f"
                }
        }
	if len(lines) > 0 {
		text += " " + lines[0]
	}
	return text
}

func (g *GithubPush) Strings() []string {
	var refs []string = strings.Split(g.Ref, "/")
	var prefix string = "\x0303" + g.Repository.String() + "\x0f \x0305" + refs[len(refs)-1] + "\x0f"
	var pushes []string = make([]string, 0)

	for _, commit := range g.Commits {
		pushes = append(pushes, prefix+" "+commit.String())
		pushes = append(pushes, prefix+" "+commit.Url)
	}
	return pushes
}

func (g *GithubPush) GetRepository() string {
       return g.Repository.String()
}

func (g *GithubCreate) Strings() []string {
	return []string{"\x0303" + g.Sender.String() + "\x0f has pushed a new " + g.RefType + " \x0305" + g.Ref + "\x0f to \x0303" + g.Repository.String() + "\x0f"}
}

func (g *GithubCreate) GetRepository() string {
       return g.Repository.String()
}

func (g *GithubDelete) Strings() []string {
	return []string{"\x0303" + g.Sender.String() + "\x0f has deleted a " + g.RefType + " \x0305" + g.Ref + "\x0f from \x0303" + g.Repository.String() + "\x0f"}
}

func (g *GithubDelete) GetRepository() string {
       return g.Repository.String()
}

func (g *GitHubAdapter) WriteGithubEvent(event GithubEvent, body []byte, signature string) error {
        var ok bool
        var githubconf *GitHubRepositoryConfig
        var err error

        err = json.Unmarshal(body, &event)

        if err != nil {
                log.Print("Error decoding github event: ", err)
                return err
        }

        githubconf, ok = g.channels[event.GetRepository()]
        if !ok {
                log.Print("Repository ", event.GetRepository(), " not configured.")
                return err
        }
        if !CheckMAC(body, signature, githubconf.GetSecret()) {
                log.Print("DEBUG Signature: ", signature)
                return err
        }
        for _, channel := range githubconf.GetIrcChannel() {
                for _, commit := range event.Strings() {
                        g.ircbot.Privmsg(channel, commit)
                }
        }
        return err
}

func NewGitHubAdapter(ircbot *irc.Connection, config *GitHubConfig) *GitHubAdapter {
	var channels = make(map[string]*GitHubRepositoryConfig)
	for _, repo := range config.Repo {
		channels[repo.GetName()] = repo
	}
	return &GitHubAdapter{
		ircbot:   ircbot,
		config:   config,
		channels: channels,
	}
}

func (g *GitHubAdapter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	var body []byte
	var err error
	body, err = ioutil.ReadAll(req.Body)
	if err != nil {
		log.Print("Error reading body: ", err)
		return
	}

	switch req.Header.Get("X-GitHub-Event") {
	case "create":
                var create GithubCreate
                g.WriteGithubEvent(&create, body, req.Header.Get("X-Hub-Signature"))
        case "delete":
                var del GithubDelete
                g.WriteGithubEvent(&del, body, req.Header.Get("X-Hub-Signature"))
	case "push":
		var push GithubPush
                g.WriteGithubEvent(&push, body, req.Header.Get("X-Hub-Signature"))
	default:
		log.Print("Unknown GitHub event.", req.Header.Get("X-GitHub-Event"))
	}
}
<|endoftext|>