<commit_before>package collect

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/StackExchange/slog"
	"github.com/bosun-monitor/opentsdb"
)

func queuer() {
	for dp := range tchan {
		qlock.Lock()
		for {
			if len(queue) > MaxQueueLen {
				slock.Lock()
				dropped++
				slock.Unlock()
				break
			}
			m, err := json.Marshal(dp)
			if err != nil {
				slog.Error(err)
			} else {
				queue = append(queue, m)
			}
			select {
			case dp = <-tchan:
				continue
			default:
			}
			break
		}
		qlock.Unlock()
	}
}

func send() {
	for {
		qlock.Lock()
		if i := len(queue); i > 0 {
			if i > BatchSize {
				i = BatchSize
			}
			sending := queue[:i]
			queue = queue[i:]
			if Debug {
				slog.Infof("sending: %d, remaining: %d", i, len(queue))
			}
			qlock.Unlock()
			sendBatch(sending)
		} else {
			qlock.Unlock()
			time.Sleep(time.Second)
		}
	}
}

func sendBatch(batch []json.RawMessage) {
	if Print {
		for _, d := range batch {
			slog.Info(string(d))
		}
		recordSent(len(batch))
		return
	}
	var buf bytes.Buffer
	g := gzip.NewWriter(&buf)
	if err := json.NewEncoder(g).Encode(batch); err != nil {
		slog.Error(err)
		return
	}
	if err := g.Close(); err != nil {
		slog.Error(err)
		return
	}
	req, err := http.NewRequest("POST", tsdbURL, &buf)
	if err != nil {
		slog.Error(err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Content-Encoding", "gzip")
	now := time.Now()
	resp, err := client.Do(req)
	d := time.Since(now).Nanoseconds() / 1e6
	if err == nil {
		defer resp.Body.Close()
	}
	Add("collect.post.total_duration", nil, d)
	Add("collect.post.count", nil, 1)
	// Some problem with connecting to the server; retry later.
	if err != nil || resp.StatusCode != http.StatusNoContent {
		if err != nil {
			slog.Error(err)
		} else if resp.StatusCode != http.StatusNoContent {
			slog.Errorln(resp.Status)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				slog.Error(err)
			}
			if len(body) > 0 {
				slog.Error(string(body))
			}
		}
		restored := 0
		for _, msg := range batch {
			var dp opentsdb.DataPoint
			if err := json.Unmarshal(msg, &dp); err != nil {
				slog.Error(err)
				continue
			}
			restored++
			tchan <- &dp
		}
		d := time.Second * 5
		slog.Infof("restored %d, sleeping %s", restored, d)
		time.Sleep(d)
		return
	}
	recordSent(len(batch))
}

func recordSent(num int) {
	if Debug {
		slog.Infoln("sent", num)
	}
	slock.Lock()
	sent += int64(num)
	slock.Unlock()
}
<commit_msg>collect: Collect some error stats<commit_after>package collect

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/StackExchange/slog"
	"github.com/bosun-monitor/opentsdb"
)

func queuer() {
	for dp := range tchan {
		qlock.Lock()
		for {
			if len(queue) > MaxQueueLen {
				slock.Lock()
				dropped++
				slock.Unlock()
				break
			}
			m, err := json.Marshal(dp)
			if err != nil {
				slog.Error(err)
			} else {
				queue = append(queue, m)
			}
			select {
			case dp = <-tchan:
				continue
			default:
			}
			break
		}
		qlock.Unlock()
	}
}

func send() {
	for {
		qlock.Lock()
		if i := len(queue); i > 0 {
			if i > BatchSize {
				i = BatchSize
			}
			sending := queue[:i]
			queue = queue[i:]
			if Debug {
				slog.Infof("sending: %d, remaining: %d", i, len(queue))
			}
			qlock.Unlock()
			sendBatch(sending)
		} else {
			qlock.Unlock()
			time.Sleep(time.Second)
		}
	}
}

func sendBatch(batch []json.RawMessage) {
	if Print {
		for _, d := range batch {
			slog.Info(string(d))
		}
		recordSent(len(batch))
		return
	}
	var buf bytes.Buffer
	g := gzip.NewWriter(&buf)
	if err := json.NewEncoder(g).Encode(batch); err != nil {
		slog.Error(err)
		return
	}
	if err := g.Close(); err != nil {
		slog.Error(err)
		return
	}
	req, err := http.NewRequest("POST", tsdbURL, &buf)
	if err != nil {
		slog.Error(err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Content-Encoding", "gzip")
	now := time.Now()
	resp, err := client.Do(req)
	d := time.Since(now).Nanoseconds() / 1e6
	if err == nil {
		defer resp.Body.Close()
	}
	Add("collect.post.total_duration", nil, d)
	Add("collect.post.count", nil, 1)
	// Some problem with connecting to the server; retry later.
	if err != nil || resp.StatusCode != http.StatusNoContent {
		if err != nil {
			Add("collect.post.error", nil, 1)
			slog.Error(err)
		} else if resp.StatusCode != http.StatusNoContent {
			Add("collect.post.bad_status", nil, 1)
			slog.Errorln(resp.Status)
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				slog.Error(err)
			}
			if len(body) > 0 {
				slog.Error(string(body))
			}
		}
		restored := 0
		for _, msg := range batch {
			var dp opentsdb.DataPoint
			if err := json.Unmarshal(msg, &dp); err != nil {
				slog.Error(err)
				continue
			}
			restored++
			tchan <- &dp
		}
		d := time.Second * 5
		Add("collect.post.restore", nil, int64(restored))
		slog.Infof("restored %d, sleeping %s", restored, d)
		time.Sleep(d)
		return
	}
	recordSent(len(batch))
}

func recordSent(num int) {
	if Debug {
		slog.Infoln("sent", num)
	}
	slock.Lock()
	sent += int64(num)
	slock.Unlock()
}
<|endoftext|>


<commit_before>package cribbage

type Sequence []Play

type Play struct {
	Card   *Card
	Player int
}

func NewSeq() *Sequence {
	plays := make([]Play, 0)
	seq := Sequence(plays)
	return &seq
}

func (s *Sequence) CanPlay(card *Card) bool {
	if card != nil {
		return s.Total()+card.Value() < 32
	}
	return false
}

func (s *Sequence) Play(player int, card *Card) {
	if s.CanPlay(card) {
		*s = append(*s, Play{Card: card.Copy(), Player: player})
	}
}

func (s *Sequence) Get(index int) (int, *Card) {
	return (*s)[index].Player, (*s)[index].Card
}

func (s *Sequence) Size() int {
	return len(*s)
}

func (s *Sequence) Last() (int, *Card) {
	return s.Get(s.Size() - 1)
}

func (s *Sequence) Total() int {
	val := 0
	for _, play := range *s {
		val += play.Card.Value()
	}
	return val
}
<commit_msg>Start implementing point claiming<commit_after>package cribbage

type Sequence []Play

type Play struct {
	Card   *Card
	Player int
}

func NewSeq() *Sequence {
	plays := make([]Play, 0)
	seq := Sequence(plays)
	return &seq
}

func (s *Sequence) CanPlay(card *Card) bool {
	if card != nil {
		return s.Total()+card.Value() < 32
	}
	return false
}

func (s *Sequence) Play(player int, card *Card) {
	if s.CanPlay(card) {
		*s = append(*s, Play{Card: card.Copy(), Player: player})
	}
}

func (s *Sequence) Get(index int) (int, *Card) {
	return (*s)[index].Player, (*s)[index].Card
}

func (s *Sequence) Size() int {
	return len(*s)
}

func (s *Sequence) Last() (int, *Card) {
	return s.Get(s.Size() - 1)
}

func (s *Sequence) Total() int {
	val := 0
	for _, play := range *s {
		val += play.Card.Value()
	}
	return val
}

const (
	CLAIM_PAIR    = 0
	CLAIM_FIFTEEN = 1
)

func (s *Sequence) WorthPoints(pointType int) bool {
	switch pointType {
	case CLAIM_PAIR:
		if s.Size() < 2 {
			return false
		}
		lastIdx := s.Size() - 1
		secLastIdx := lastIdx - 1
		_, lastCard := s.Get(lastIdx)
		_, secLastCard := s.Get(secLastIdx)
		return lastCard.Rank == secLastCard.Rank
	case CLAIM_FIFTEEN:
		if s.Size() < 2 {
			return false
		}
		total := 0
		for start := s.Size() - 1; total < 15; start++ {
			_, card := s.Get(start)
			total += card.Value()
		}
		return total == 15
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"path/filepath"
	"strings"
)

func main() {
	paths := []string{
		"/tmp/test/working/animals",
		"/tmp/test/something",
		"/tmp/test/working/../../something",
	}
	base := "/tmp/test/working"

	fmt.Println("On Unix:")
	for _, p := range paths {
		rel, err := filepath.Rel(base, p)
		fmt.Printf("%q: %q %v\n", p, rel, err)

		if strings.HasPrefix(rel, "..") {
			fmt.Printf("DENIED MF! %s\n", rel)
		} else {
			fmt.Printf("G2G! %s\n", rel)
		}
	}

}
<commit_msg>Removed junk folder.<commit_after><|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"strings"

	"github.com/mitchellh/cli"
	"github.com/mohae/rancher/ranchr"
)

// BuildCommand is a Command implementation that generates Packer templates
// from named named builds and passed build arguments.
type BuildCommand struct {
	Ui cli.Ui
}

// Rancher help text.
func (c *BuildCommand) Help() string {
	helpText := `
Usage: rancher build [options]

Rancher creates Packer templates. At minimum, this command needs to be run with
either the -distro flag or a build name. The simplest way to generate a Packer
template is to run Rancher with just the target distribution name, which must
be supported, i.e. exists within Rancher's supported.toml file:

	% rancher build -distro=<distro name>
	% rancher build -distro=ubuntu

The above command generates a Packer template, targeting Ubuntu, using the
defaults for that distribution. See the options section for the other flags.

Rancher can also generate Packer templates using preconfigured Rancher build
templates via the builds.toml file. The name of the build is used to specify
which build configuration should be used:

	% rancher build <buildName...>
	% rancher build 1204-amd64-server 1404-amd64-desktop

The above command generates two Packer templates using the 1204-amd64-server
and 1404-amd64-desktop build configurations. The list of build names is
variadic, accepting 1 or more build names.

For builds using the -distro flag, the -arch, -image, and -release flags are
optional. If any of them are missing, the distribution's default value for that
flag will be used.

Options:
-distro=<distroName>	If provided, Rancher will create a Packer template for
			the passed distro, e.g. ubuntu. This flag can be used
			with the -arch, -image, and -release flags to override
			the distro's default values for those settings.

-arch=<architecture>	Specify whether 32 or 64 bit code should be used. These
			values are distro dependent. This flag is only used
			with the -distro flag.

-image=<imageType>	The ISO image that the Packer template will use, e.g.
			server or desktop. These values are distro dependent.
			This flag is only used with the -distro flag.

-release=<releaseNum>	The release number that the Packer template will use,
			e.g. 12.04, etc. Only the targeted distro's currently
			supported releases are valid. This flag is only used
			with the -distro flag.
`
	return strings.TrimSpace(helpText)
}

func (c *BuildCommand) Run(args []string) int {
	var distroFilter, archFilter, imageFilter, releaseFilter, logDirFilter string
	cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
	cmdFlags.StringVar(&distroFilter, "distro", "", "distro filter")
	cmdFlags.StringVar(&archFilter, "arch", "", "arch filter")
	cmdFlags.StringVar(&imageFilter, "image", "", "image filter")
	cmdFlags.StringVar(&releaseFilter, "release", "", "release filter")
	cmdFlags.StringVar(&logDirFilter, "log_dir", "", "log directory")
	if err := cmdFlags.Parse(args); err != nil {
		c.Ui.Error(fmt.Sprintf("Parse of command-line arguments failed: %s", err))
		return 1
	}
	buildArgs := cmdFlags.Args()
	if distroFilter != "" {
		args := ranchr.ArgsFilter{Arch: archFilter, Distro: distroFilter, Image: imageFilter, Release: releaseFilter}
		// TODO go it
		if err := ranchr.BuildDistro(args); err != nil {
			c.Ui.Output(err.Error())
			return 1
		}
	}
	if len(buildArgs) > 0 {
		var message string
		var err error
		if message, err = ranchr.BuildBuilds(buildArgs...); err != nil {
			c.Ui.Error(err.Error())
			return 1
		}
		c.Ui.Output(message)
	}
	c.Ui.Output("Rancher Build complete.")
	return 0
}

func (c *BuildCommand) Synopsis() string {
	return "Create a Packer template from either distribution defaults or pre-defined Rancher Build templates."
}
<commit_msg>updated build synopsis<commit_after>package command

import (
	"flag"
	"fmt"
	"strings"

	"github.com/mitchellh/cli"
	"github.com/mohae/rancher/ranchr"
)

// BuildCommand is a Command implementation that generates Packer templates
// from named named builds and passed build arguments.
type BuildCommand struct {
	Ui cli.Ui
}

// Rancher help text.
func (c *BuildCommand) Help() string {
	helpText := `
Usage: rancher build [options]

Rancher creates Packer templates. At minimum, this command needs to be run with
either the -distro flag or a build name. The simplest way to generate a Packer
template is to run Rancher with just the target distribution name, which must
be supported, i.e. exists within Rancher's supported.toml file:

	% rancher build -distro=<distro name>
	% rancher build -distro=ubuntu

The above command generates a Packer template, targeting Ubuntu, using the
defaults for that distribution. See the options section for the other flags.

Rancher can also generate Packer templates using preconfigured Rancher build
templates via the builds.toml file. The name of the build is used to specify
which build configuration should be used:

	% rancher build <buildName...>
	% rancher build 1204-amd64-server 1404-amd64-desktop

The above command generates two Packer templates using the 1204-amd64-server
and 1404-amd64-desktop build configurations. The list of build names is
variadic, accepting 1 or more build names.

For builds using the -distro flag, the -arch, -image, and -release flags are
optional. If any of them are missing, the distribution's default value for that
flag will be used.

Options:
-distro=<distroName>	If provided, Rancher will create a Packer template for
			the passed distro, e.g. ubuntu. This flag can be used
			with the -arch, -image, and -release flags to override
			the distro's default values for those settings.

-arch=<architecture>	Specify whether 32 or 64 bit code should be used. These
			values are distro dependent. This flag is only used
			with the -distro flag.

-image=<imageType>	The ISO image that the Packer template will use, e.g.
			server or desktop. These values are distro dependent.
			This flag is only used with the -distro flag.

-release=<releaseNum>	The release number that the Packer template will use,
			e.g. 12.04, etc. Only the targeted distro's currently
			supported releases are valid. This flag is only used
			with the -distro flag.
`
	return strings.TrimSpace(helpText)
}

func (c *BuildCommand) Run(args []string) int {
	var distroFilter, archFilter, imageFilter, releaseFilter, logDirFilter string
	cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError)
	cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
	cmdFlags.StringVar(&distroFilter, "distro", "", "distro filter")
	cmdFlags.StringVar(&archFilter, "arch", "", "arch filter")
	cmdFlags.StringVar(&imageFilter, "image", "", "image filter")
	cmdFlags.StringVar(&releaseFilter, "release", "", "release filter")
	cmdFlags.StringVar(&logDirFilter, "log_dir", "", "log directory")
	if err := cmdFlags.Parse(args); err != nil {
		c.Ui.Error(fmt.Sprintf("Parse of command-line arguments failed: %s", err))
		return 1
	}
	buildArgs := cmdFlags.Args()
	if distroFilter != "" {
		args := ranchr.ArgsFilter{Arch: archFilter, Distro: distroFilter, Image: imageFilter, Release: releaseFilter}
		if err := ranchr.BuildDistro(args); err != nil {
			c.Ui.Output(err.Error())
			return 1
		}
	}
	if len(buildArgs) > 0 {
		var message string
		var err error
		if message, err = ranchr.BuildBuilds(buildArgs...); err != nil {
			c.Ui.Error(err.Error())
			return 1
		}
		c.Ui.Output(message)
	}
	c.Ui.Output("Rancher Build complete.")
	return 0
}

func (c *BuildCommand) Synopsis() string {
	return "Create a Packer template from either supported distro defaults or pre-defined Rancher build configurations."
}
<|endoftext|>


<commit_before>package moon

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
)

const ()

// Reads a moon document from a given io.Reader. The io.Reader is advanced to
// EOF. The reader is not closed after reading, since it's an io.Reader and not
// an io.ReadCloser. In the event of error, the state that the source reader
// will be left in is undefined.
func Read(r io.Reader) (*Doc, error) {
	tree, err := parse(r)
	if err != nil {
		return nil, err
	}
	ctx := newContext()
	if _, err := tree.eval(ctx); err != nil {
		return nil, fmt.Errorf("eval error: %s\n", err)
	}
	return &Doc{items: ctx.public}, nil
}

// Reads a moon document from a string. This is purely a convenience method;
// all it does is create a buffer and call the moon.Read function.
func ReadString(source string) (*Doc, error) {
	return Read(strings.NewReader(source))
}

// Reads a moon document from a slice of bytes. This is purely a concenience
// method; like ReadString, it simply creates a buffer and calls moon.Read
func ReadBytes(b []byte) (*Doc, error) {
	return Read(bytes.NewBuffer(b))
}

func ReadFile(path string) (*Doc, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	return Read(f)
}

func parse(r io.Reader) (node, error) {
	p := &parser{
		root:   newRootNode(),
		input:  lex(r),
		backup: make([]token, 0, 8),
	}
	if err := p.parse(); err != nil {
		return nil, err
	}
	return p.root, nil
}

type parser struct {
	root   node
	input  chan token
	backup []token
}

func (p *parser) parse() error {
	if p.root == nil {
		p.root = newRootNode()
	}
	return p.root.parse(p)
}

// returns the next token and advances the input stream
func (p *parser) next() token {
	if len(p.backup) > 0 {
		t := p.backup[len(p.backup)-1]
		p.backup = p.backup[:len(p.backup)-1]
		return t
	}
SKIP_COMMENTS:
	t, ok := <-p.input
	if !ok {
		return token{t_eof, "eof"}
	}
	if t.t == t_comment {
		goto SKIP_COMMENTS
	}
	return t
}

func (p *parser) peek() token {
	t := p.next()
	p.unread(t)
	return t
}

func (p *parser) unread(t token) {
	if p.backup == nil {
		p.backup = make([]token, 0, 8)
	}
	p.backup = append(p.backup, t)
}

func (p *parser) ensureNext(tt tokenType, context string) error {
	if p.peek().t != tt {
		return fmt.Errorf("unexpected %v in %s: expected %v", p.peek().t, context, tt)
	}
	return nil
}

// parse the next value.  This is to be executed in a context where we know we
// want something that is a value to come next, such as after an equals sign.
func (p *parser) parseValue() (node, error) {
	for {
		t := p.peek()
		switch t.t {
		case t_error:
			return nil, fmt.Errorf("parse error: saw lex error when looking for value: %v", t.s)
		case t_eof:
			return nil, fmt.Errorf("parse error: unexpected eof when looking for value")
		case t_string:
			n := new(stringNode)
			if err := n.parse(p); err != nil {
				return nil, err
			}
			return n, nil
		case t_real_number:
			n := new(numberNode)
			if err := n.parse(p); err != nil {
				return nil, err
			}
			return n, nil
		case t_list_start:
			p.next()
			n := new(listNode)
			if err := n.parse(p); err != nil {
				return nil, err
			}
			return n, nil
		case t_object_start:
			p.next()
			n := &objectNode{}
			if err := n.parse(p); err != nil {
				return nil, err
			}
			return n, nil
		case t_variable:
			n := new(variableNode)
			if err := n.parse(p); err != nil {
				return nil, err
			}
			return n, nil
		case t_bool:
			n := new(boolNode)
			if err := n.parse(p); err != nil {
				return nil, err
			}
			return n, nil
		default:
			return nil, fmt.Errorf("parse error: unexpected %v token while looking for value", t.t)
		}
	}
}
<commit_msg>deleting code is fun<commit_after>package moon

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
)

const ()

var nodes = map[tokenType]func(p *parser) node{
	t_string:       func(p *parser) node { return new(stringNode) },
	t_real_number:  func(p *parser) node { return new(numberNode) },
	t_list_start:   func(p *parser) node { p.next(); return &listNode{} },
	t_object_start: func(p *parser) node { p.next(); return &objectNode{} },
	t_variable:     func(p *parser) node { return new(variableNode) },
	t_bool:         func(p *parser) node { return new(boolNode) },
}

// Reads a moon document from a given io.Reader. The io.Reader is advanced to
// EOF. The reader is not closed after reading, since it's an io.Reader and not
// an io.ReadCloser. In the event of error, the state that the source reader
// will be left in is undefined.
func Read(r io.Reader) (*Doc, error) {
	tree, err := parse(r)
	if err != nil {
		return nil, err
	}
	ctx := newContext()
	if _, err := tree.eval(ctx); err != nil {
		return nil, fmt.Errorf("eval error: %s\n", err)
	}
	return &Doc{items: ctx.public}, nil
}

// Reads a moon document from a string. This is purely a convenience method;
// all it does is create a buffer and call the moon.Read function.
func ReadString(source string) (*Doc, error) {
	return Read(strings.NewReader(source))
}

// Reads a moon document from a slice of bytes. This is purely a concenience
// method; like ReadString, it simply creates a buffer and calls moon.Read
func ReadBytes(b []byte) (*Doc, error) {
	return Read(bytes.NewBuffer(b))
}

func ReadFile(path string) (*Doc, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	return Read(f)
}

func parse(r io.Reader) (node, error) {
	p := &parser{
		root:   newRootNode(),
		input:  lex(r),
		backup: make([]token, 0, 8),
	}
	if err := p.parse(); err != nil {
		return nil, err
	}
	return p.root, nil
}

type parser struct {
	root   node
	input  chan token
	backup []token
}

func (p *parser) parse() error {
	if p.root == nil {
		p.root = newRootNode()
	}
	return p.root.parse(p)
}

// returns the next token and advances the input stream
func (p *parser) next() token {
	if len(p.backup) > 0 {
		t := p.backup[len(p.backup)-1]
		p.backup = p.backup[:len(p.backup)-1]
		return t
	}
SKIP_COMMENTS:
	t, ok := <-p.input
	if !ok {
		return token{t_eof, "eof"}
	}
	if t.t == t_comment {
		goto SKIP_COMMENTS
	}
	return t
}

func (p *parser) peek() token {
	t := p.next()
	p.unread(t)
	return t
}

func (p *parser) unread(t token) {
	if p.backup == nil {
		p.backup = make([]token, 0, 8)
	}
	p.backup = append(p.backup, t)
}

func (p *parser) ensureNext(tt tokenType, context string) error {
	if p.peek().t != tt {
		return fmt.Errorf("unexpected %v in %s: expected %v", p.peek().t, context, tt)
	}
	return nil
}

// parse the next value.  This is to be executed in a context where we know we
// want something that is a value to come next, such as after an equals sign.
func (p *parser) parseValue() (node, error) {
	for {
		t := p.peek()
		switch t.t {
		case t_error:
			return nil, fmt.Errorf("parse error: saw lex error when looking for value: %v", t.s)
		case t_eof:
			return nil, fmt.Errorf("parse error: unexpected eof when looking for value")
		}

		fn, ok := nodes[t.t]
		if !ok {
			return nil, fmt.Errorf("parse error: unexpected %v token while looking for value", t.t)
		}
		n := fn(p)
		if err := n.parse(p); err != nil {
			return nil, err
		}
		return n, nil
	}
}
<|endoftext|>


<commit_before>// Copyright 2012, The gohg Authors. All rights reserved.
// Use of this source code is governed by a BSD style license
// that can be found in the LICENSE file.

package gohg

// import (
// 	"strings"
// 	"testing"
// )

// func TestCommand_Ok(t *testing.T) {
// 	// unnecessary in fact, as all the command tests going ok yields the same result

// 	hct := setup(t)
// 	defer teardown(t, hct)

// 	data, err := commandOld(hct, "identify", []string{})
// 	if err != nil || data == nil || string(data) == "" {
// 		t.Fatalf("Did not perform correctly.")
// 	}
// }

// func TestCommand_runError(t *testing.T) {
// 	hct := setup(t)
// 	// do not defer, causing an error in the Command call
// 	// and so catching err in Command()
// 	teardown(t, hct)

// 	_, err := commandOld(hct, "", []string{})
// 	if err == nil || !strings.Contains(err.Error(), "from runInHg():") {
// 		t.Fatalf("Did not get runInHg() error.")
// 	}
// }

// func TestCommand_HgError(t *testing.T) {
// 	hct := setup(t)
// 	defer teardown(t, hct)

// 	// catching hgerr in Command()
// 	_, err := commandOld(hct, "nonexistinghgcommand", []string{})
// 	if err == nil {
// 		t.Fatalf("Did not catch hgerr.")
// 	}
// }
<commit_msg>removed obsolete testfile<commit_after><|endoftext|>


<commit_before>// Package common contains common types, constants and functions used over different demoinfocs packages.
// Some constants prefixes:
// MVPReason - the reason why someone got the MVP award.
// HG - HitGroup - where a bullet hit the player.
// EE - EquipmentElement - basically the weapon identifiers.
// RER - RoundEndReason - why the round ended (bomb exploded, defused, time ran out. . .).
// EC - EquipmentClass - type of equipment (pistol, smg, heavy. . .).
package common

import (
	"strings"
)

var eqNameToWeapon map[string]EquipmentElement

var eqElementToName map[EquipmentElement]string

func init() {
	initEqNameToWeapon()
	initEqEementToName()
}

func initEqNameToWeapon() {
	eqNameToWeapon = make(map[string]EquipmentElement)
	eqNameToWeapon["ak47"] = EqAK47
	eqNameToWeapon["aug"] = EqAUG
	eqNameToWeapon["awp"] = EqAWP
	eqNameToWeapon["bizon"] = EqBizon
	eqNameToWeapon["c4"] = EqBomb
	eqNameToWeapon["deagle"] = EqDeagle
	eqNameToWeapon["decoy"] = EqDecoy
	eqNameToWeapon["decoygrenade"] = EqDecoy
	eqNameToWeapon["decoy_projectile"] = EqDecoy
	eqNameToWeapon["elite"] = EqDualBarettas
	eqNameToWeapon["famas"] = EqFamas
	eqNameToWeapon["fiveseven"] = EqFiveSeven
	eqNameToWeapon["flashbang"] = EqFlash
	eqNameToWeapon["g3sg1"] = EqG3SG1
	eqNameToWeapon["galil"] = EqGalil
	eqNameToWeapon["galilar"] = EqGalil
	eqNameToWeapon["glock"] = EqGlock
	eqNameToWeapon["hegrenade"] = EqHE
	eqNameToWeapon["hkp2000"] = EqP2000
	eqNameToWeapon["incgrenade"] = EqIncendiary
	eqNameToWeapon["incendiarygrenade"] = EqIncendiary
	eqNameToWeapon["m249"] = EqM249
	eqNameToWeapon["m4a1"] = EqM4A4
	eqNameToWeapon["mac10"] = EqMac10
	eqNameToWeapon["mag7"] = EqSwag7
	eqNameToWeapon["molotov"] = EqMolotov
	eqNameToWeapon["molotovgrenade"] = EqMolotov
	eqNameToWeapon["molotov_projectile"] = EqMolotov
	eqNameToWeapon["mp7"] = EqMP7
	eqNameToWeapon["mp9"] = EqMP9
	eqNameToWeapon["negev"] = EqNegev
	eqNameToWeapon["nova"] = EqNova
	eqNameToWeapon["p250"] = EqP250
	eqNameToWeapon["p90"] = EqP90
	eqNameToWeapon["sawedoff"] = EqSawedOff
	eqNameToWeapon["scar20"] = EqScar20
	eqNameToWeapon["sg556"] = EqSG556
	eqNameToWeapon["smokegrenade"] = EqSmoke
	eqNameToWeapon["ssg08"] = EqScout
	eqNameToWeapon["taser"] = EqZeus
	eqNameToWeapon["tec9"] = EqTec9
	eqNameToWeapon["ump45"] = EqUMP
	eqNameToWeapon["xm1014"] = EqXM1014
	eqNameToWeapon["m4a1_silencer"] = EqM4A1
	eqNameToWeapon["m4a1_silencer_off"] = EqM4A1
	eqNameToWeapon["cz75a"] = EqCZ
	eqNameToWeapon["usp"] = EqUSP
	eqNameToWeapon["usp_silencer"] = EqUSP
	eqNameToWeapon["usp_silencer_off"] = EqUSP
	eqNameToWeapon["world"] = EqWorld
	eqNameToWeapon["inferno"] = EqIncendiary
	eqNameToWeapon["revolver"] = EqRevolver
	eqNameToWeapon["vest"] = EqKevlar
	eqNameToWeapon["vesthelm"] = EqHelmet
	eqNameToWeapon["defuser"] = EqDefuseKit
}

func initEqEementToName() {
	eqElementToName = make(map[EquipmentElement]string)
	eqElementToName[EqAK47] = "AK-47"
	eqElementToName[EqAUG] = "AUG"
	eqElementToName[EqAWP] = "AWP"
	eqElementToName[EqBizon] = "PP-Bizon"
	eqElementToName[EqBomb] = "C4"
	eqElementToName[EqDeagle] = "Desert Eagle"
	eqElementToName[EqDecoy] = "Decoy Grenade"
	eqElementToName[EqDualBarettas] = "Dual Barettas"
	eqElementToName[EqFamas] = "FAMAS"
	eqElementToName[EqFiveSeven] = "Five-SeveN"
	eqElementToName[EqFlash] = "Flashbang"
	eqElementToName[EqG3SG1] = "G3SG1"
	eqElementToName[EqGalil] = "Galil AR"
	eqElementToName[EqGlock] = "Glock-18"
	eqElementToName[EqHE] = "HE Grenade"
	eqElementToName[EqP2000] = "P2000"
	eqElementToName[EqIncendiary] = "Incendiary Grenade"
	eqElementToName[EqM249] = "M249"
	eqElementToName[EqM4A4] = "M4A1"
	eqElementToName[EqMac10] = "MAC-10"
	eqElementToName[EqSwag7] = "MAG-7"
	eqElementToName[EqMolotov] = "Molotov"
	eqElementToName[EqMP7] = "MP7"
	eqElementToName[EqMP9] = "MP9"
	eqElementToName[EqNegev] = "Negev"
	eqElementToName[EqNova] = "Nova"
	eqElementToName[EqP250] = "p250"
	eqElementToName[EqP90] = "P90"
	eqElementToName[EqSawedOff] = "Sawed-Off"
	eqElementToName[EqScar20] = "SCAR-20"
	eqElementToName[EqSG553] = "SG 553"
	eqElementToName[EqSmoke] = "Smoke Grenade"
	eqElementToName[EqScout] = "SSG 08"
	eqElementToName[EqZeus] = "Zeus x27"
	eqElementToName[EqTec9] = "Tec-9"
	eqElementToName[EqUMP] = "UMP-45"
	eqElementToName[EqXM1014] = "XM1014"
	eqElementToName[EqM4A1] = "M4A1"
	eqElementToName[EqCZ] = "CZ75 Auto"
	eqElementToName[EqUSP] = "USP-S"
	eqElementToName[EqWorld] = "World"
	eqElementToName[EqRevolver] = "R8 Revolver"
	eqElementToName[EqKevlar] = "Kevlar Vest"
	eqElementToName[EqHelmet] = "Kevlar + Helmet"
	eqElementToName[EqDefuseKit] = "Defuse Kit"
	eqElementToName[EqKnife] = "Knife"
}

// MapEquipment creates an EquipmentElement from the name of the weapon / equipment.
func MapEquipment(eqName string) EquipmentElement {
	eqName = strings.TrimPrefix(eqName, weaponPrefix)

	var wep EquipmentElement
	if strings.Contains(eqName, "knife") || strings.Contains(eqName, "bayonet") {
		wep = EqKnife
	} else {
		// If the eqName isn't known it will be EqUnknown as that is the default value for EquipmentElement
		wep = eqNameToWeapon[eqName]
		// TODO: Return error / warning for EqUnknown?
	}

	return wep
}
<commit_msg>Map buggy weapons to EqUnknown<commit_after>// Package common contains common types, constants and functions used over different demoinfocs packages.
// Some constants prefixes:
// MVPReason - the reason why someone got the MVP award.
// HG - HitGroup - where a bullet hit the player.
// EE - EquipmentElement - basically the weapon identifiers.
// RER - RoundEndReason - why the round ended (bomb exploded, defused, time ran out. . .).
// EC - EquipmentClass - type of equipment (pistol, smg, heavy. . .).
package common

import (
	"strings"
)

var eqNameToWeapon map[string]EquipmentElement

var eqElementToName map[EquipmentElement]string

func init() {
	initEqNameToWeapon()
	initEqEementToName()
}

func initEqNameToWeapon() {
	eqNameToWeapon = make(map[string]EquipmentElement)
	eqNameToWeapon["ak47"] = EqAK47
	eqNameToWeapon["aug"] = EqAUG
	eqNameToWeapon["awp"] = EqAWP
	eqNameToWeapon["bizon"] = EqBizon
	eqNameToWeapon["c4"] = EqBomb
	eqNameToWeapon["deagle"] = EqDeagle
	eqNameToWeapon["decoy"] = EqDecoy
	eqNameToWeapon["decoygrenade"] = EqDecoy
	eqNameToWeapon["decoy_projectile"] = EqDecoy
	eqNameToWeapon["elite"] = EqDualBarettas
	eqNameToWeapon["famas"] = EqFamas
	eqNameToWeapon["fiveseven"] = EqFiveSeven
	eqNameToWeapon["flashbang"] = EqFlash
	eqNameToWeapon["g3sg1"] = EqG3SG1
	eqNameToWeapon["galil"] = EqGalil
	eqNameToWeapon["galilar"] = EqGalil
	eqNameToWeapon["glock"] = EqGlock
	eqNameToWeapon["hegrenade"] = EqHE
	eqNameToWeapon["hkp2000"] = EqP2000
	eqNameToWeapon["incgrenade"] = EqIncendiary
	eqNameToWeapon["incendiarygrenade"] = EqIncendiary
	eqNameToWeapon["m249"] = EqM249
	eqNameToWeapon["m4a1"] = EqM4A4
	eqNameToWeapon["mac10"] = EqMac10
	eqNameToWeapon["mag7"] = EqSwag7
	eqNameToWeapon["molotov"] = EqMolotov
	eqNameToWeapon["molotovgrenade"] = EqMolotov
	eqNameToWeapon["molotov_projectile"] = EqMolotov
	eqNameToWeapon["mp7"] = EqMP7
	eqNameToWeapon["mp9"] = EqMP9
	eqNameToWeapon["negev"] = EqNegev
	eqNameToWeapon["nova"] = EqNova
	eqNameToWeapon["p250"] = EqP250
	eqNameToWeapon["p90"] = EqP90
	eqNameToWeapon["sawedoff"] = EqSawedOff
	eqNameToWeapon["scar20"] = EqScar20
	eqNameToWeapon["sg556"] = EqSG556
	eqNameToWeapon["smokegrenade"] = EqSmoke
	eqNameToWeapon["ssg08"] = EqScout
	eqNameToWeapon["taser"] = EqZeus
	eqNameToWeapon["tec9"] = EqTec9
	eqNameToWeapon["ump45"] = EqUMP
	eqNameToWeapon["xm1014"] = EqXM1014
	eqNameToWeapon["m4a1_silencer"] = EqM4A1
	eqNameToWeapon["m4a1_silencer_off"] = EqM4A1
	eqNameToWeapon["cz75a"] = EqCZ
	eqNameToWeapon["usp"] = EqUSP
	eqNameToWeapon["usp_silencer"] = EqUSP
	eqNameToWeapon["usp_silencer_off"] = EqUSP
	eqNameToWeapon["world"] = EqWorld
	eqNameToWeapon["inferno"] = EqIncendiary
	eqNameToWeapon["revolver"] = EqRevolver
	eqNameToWeapon["vest"] = EqKevlar
	eqNameToWeapon["vesthelm"] = EqHelmet
	eqNameToWeapon["defuser"] = EqDefuseKit

	// These don't exist and / or used to crash the game with the give command
	eqNameToWeapon["scar17"] = EqUnknown
	eqNameToWeapon["sensorgrenade"] = EqUnknown
	eqNameToWeapon["mp5navy"] = EqUnknown
	eqNameToWeapon["p228"] = EqUnknown
	eqNameToWeapon["scout"] = EqUnknown
	eqNameToWeapon["sg550"] = EqUnknown
	eqNameToWeapon["sg552"] = EqUnknown // This one still crashes the game :)
	eqNameToWeapon["tmp"] = EqUnknown
	eqNameToWeapon["worldspawn"] = EqUnknown
}

func initEqEementToName() {
	eqElementToName = make(map[EquipmentElement]string)
	eqElementToName[EqAK47] = "AK-47"
	eqElementToName[EqAUG] = "AUG"
	eqElementToName[EqAWP] = "AWP"
	eqElementToName[EqBizon] = "PP-Bizon"
	eqElementToName[EqBomb] = "C4"
	eqElementToName[EqDeagle] = "Desert Eagle"
	eqElementToName[EqDecoy] = "Decoy Grenade"
	eqElementToName[EqDualBarettas] = "Dual Barettas"
	eqElementToName[EqFamas] = "FAMAS"
	eqElementToName[EqFiveSeven] = "Five-SeveN"
	eqElementToName[EqFlash] = "Flashbang"
	eqElementToName[EqG3SG1] = "G3SG1"
	eqElementToName[EqGalil] = "Galil AR"
	eqElementToName[EqGlock] = "Glock-18"
	eqElementToName[EqHE] = "HE Grenade"
	eqElementToName[EqP2000] = "P2000"
	eqElementToName[EqIncendiary] = "Incendiary Grenade"
	eqElementToName[EqM249] = "M249"
	eqElementToName[EqM4A4] = "M4A1"
	eqElementToName[EqMac10] = "MAC-10"
	eqElementToName[EqSwag7] = "MAG-7"
	eqElementToName[EqMolotov] = "Molotov"
	eqElementToName[EqMP7] = "MP7"
	eqElementToName[EqMP9] = "MP9"
	eqElementToName[EqNegev] = "Negev"
	eqElementToName[EqNova] = "Nova"
	eqElementToName[EqP250] = "p250"
	eqElementToName[EqP90] = "P90"
	eqElementToName[EqSawedOff] = "Sawed-Off"
	eqElementToName[EqScar20] = "SCAR-20"
	eqElementToName[EqSG553] = "SG 553"
	eqElementToName[EqSmoke] = "Smoke Grenade"
	eqElementToName[EqScout] = "SSG 08"
	eqElementToName[EqZeus] = "Zeus x27"
	eqElementToName[EqTec9] = "Tec-9"
	eqElementToName[EqUMP] = "UMP-45"
	eqElementToName[EqXM1014] = "XM1014"
	eqElementToName[EqM4A1] = "M4A1"
	eqElementToName[EqCZ] = "CZ75 Auto"
	eqElementToName[EqUSP] = "USP-S"
	eqElementToName[EqWorld] = "World"
	eqElementToName[EqRevolver] = "R8 Revolver"
	eqElementToName[EqKevlar] = "Kevlar Vest"
	eqElementToName[EqHelmet] = "Kevlar + Helmet"
	eqElementToName[EqDefuseKit] = "Defuse Kit"
	eqElementToName[EqKnife] = "Knife"
}

// MapEquipment creates an EquipmentElement from the name of the weapon / equipment.
func MapEquipment(eqName string) EquipmentElement {
	eqName = strings.TrimPrefix(eqName, weaponPrefix)

	var wep EquipmentElement
	if strings.Contains(eqName, "knife") || strings.Contains(eqName, "bayonet") {
		wep = EqKnife
	} else {
		// If the eqName isn't known it will be EqUnknown as that is the default value for EquipmentElement
		var ok bool
		if wep, ok = eqNameToWeapon[eqName]; !ok {
			// TODO: Return error / warning for unmapped weapons
		}
	}

	return wep
}
<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"image/color"
	"sort"

	"sync"

	"engo.io/ecs"
	"engo.io/engo"
	"engo.io/gl"
)

const (
	// RenderSystemPriority is the priority of the RenderSystem
	RenderSystemPriority = -1000
)

type renderChangeMessage struct{}

func (renderChangeMessage) Type() string {
	return "renderChangeMessage"
}

// Drawable is that which can be rendered to OpenGL.
type Drawable interface {
	Texture() *gl.Texture
	Width() float32
	Height() float32
	View() (float32, float32, float32, float32)
	Close()
}

// TextureRepeating is the method used to repeat a texture in OpenGL.
type TextureRepeating uint8

const (
	// NoRepeat does not repeat the texture.
	NoRepeat TextureRepeating = iota
	// ClampToEdge stretches the texture to the edge of the viewport.
	ClampToEdge
	// ClampToBorder stretches the texture to the border of the viewpport.
	ClampToBorder
	// Repeat repeats the texture until the border of the viewport.
	Repeat
	// MirroredRepeat repeats a mirror image of the texture until the border of the viewport.
	MirroredRepeat
)

// ZoomFilter is a filter used when zooming in or out of a texture.
type ZoomFilter uint8

const (
	// FilterNearest is a simple nearest neighbor algorithm
	FilterNearest ZoomFilter = iota
	// FilterLinear is a bilinear interpolation algorithm
	FilterLinear
)

// RenderComponent is the component needed to render an entity.
type RenderComponent struct {
	// Hidden is used to prevent drawing by OpenGL
	Hidden bool
	// Scale is the scale at which to render, in the X and Y axis. Not defining Scale, will default to engo.Point{1, 1}
	Scale engo.Point
	// Color defines how much of the color-components of the texture get used
	Color color.Color
	// Drawable refers to the Texture that should be drawn
	Drawable Drawable
	// Repeat defines how to repeat the Texture if the SpaceComponent of the entity
	// is larger than the texture itself, after applying scale. Defaults to NoRepeat
	// which allows the texture to draw entirely without regard to th SpaceComponent
	// Do not set to anything other than NoRepeat for textures in a sprite sheet.
	// This does not yet work with sprite sheets.
	Repeat TextureRepeating
	// Buffer represents the buffer object itself
	// Avoid using it unless your are writing a custom shader
	Buffer *gl.Buffer
	// BufferContent contains the buffer data
	// Avoid using it unless your are writing a custom shader
	BufferContent []float32

	magFilter, minFilter               ZoomFilter
	magFilterChanged, minFilterChanged bool

	shader Shader
	zIndex float32
}

// SetShader sets the shader used by the RenderComponent.
func (r *RenderComponent) SetShader(s Shader) {
	r.shader = s
	engo.Mailbox.Dispatch(&renderChangeMessage{})
}

// SetZIndex sets the order that the RenderComponent is drawn to the screen. Higher z-indices are drawn on top of
// lower ones if they overlap.
func (r *RenderComponent) SetZIndex(index float32) {
	r.zIndex = index
	engo.Mailbox.Dispatch(&renderChangeMessage{})
}

// SetMinFilter sets the ZoomFilter used for minimizing the RenderComponent
func (r *RenderComponent) SetMinFilter(z ZoomFilter) {
	r.minFilter = z
	r.minFilterChanged = true
}

// SetMagFilter sets the ZoomFilter used for magnifying the RenderComponent
func (r *RenderComponent) SetMagFilter(z ZoomFilter) {
	r.magFilter = z
	r.magFilterChanged = true
}

type renderEntity struct {
	*ecs.BasicEntity
	*RenderComponent
	*SpaceComponent
}

type renderEntityList []renderEntity

func (r renderEntityList) Len() int {
	return len(r)
}

func (r renderEntityList) Less(i, j int) bool {
	// Sort by shader-pointer if they have the same zIndex
	if r[i].RenderComponent.zIndex == r[j].RenderComponent.zIndex {
		// TODO: optimize this for performance
		return fmt.Sprintf("%p", &r[i].RenderComponent.shader) < fmt.Sprintf("%p", &r[j].RenderComponent.shader)
	}

	return r[i].RenderComponent.zIndex < r[j].RenderComponent.zIndex
}

func (r renderEntityList) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

// RenderSystem is the system that draws entities on the OpenGL surface. It requires
// a CameraSystem to work. If a CameraSystem is not in the World when you add RenderSystem
// one is automatically added to the world.
type RenderSystem struct {
	entities renderEntityList
	world    *ecs.World

	sortingNeeded, newCamera bool
	currentShader            Shader
}

// Priority implements the ecs.Prioritizer interface.
func (*RenderSystem) Priority() int { return RenderSystemPriority }

// New initializes the RenderSystem
func (rs *RenderSystem) New(w *ecs.World) {
	rs.world = w

	engo.Mailbox.Listen("NewCameraMessage", func(engo.Message) {
		rs.newCamera = true
	})

	addCameraSystemOnce(w)

	if !engo.Headless() {
		initShaders(w)
		engo.Gl.Enable(engo.Gl.MULTISAMPLE)
	}

	engo.Mailbox.Listen("renderChangeMessage", func(engo.Message) {
		rs.sortingNeeded = true
	})
}

var cameraInitMutex sync.Mutex

func addCameraSystemOnce(w *ecs.World) {
	cameraInitMutex.Lock()
	defer cameraInitMutex.Unlock()

	camSystemAlreadyAdded := false
	for _, system := range w.Systems() {
		switch system.(type) {
		case *CameraSystem:
			camSystemAlreadyAdded = true
		}
	}
	if !camSystemAlreadyAdded {
		w.AddSystem(&CameraSystem{})
	}
}

// Add adds an entity to the RenderSystem. The entity needs a basic, render, and space component to be added to the system.
func (rs *RenderSystem) Add(basic *ecs.BasicEntity, render *RenderComponent, space *SpaceComponent) {
	// Do nothing if entity already exists
	if rs.EntityExists(basic) >= 0 {
		return
	}

	// Setting default shader
	if render.shader == nil {
		switch render.Drawable.(type) {
		case Triangle:
			render.shader = LegacyShader
		case Circle:
			render.shader = LegacyShader
		case Rectangle:
			render.shader = LegacyShader
		case ComplexTriangles:
			render.shader = LegacyShader
		case Text:
			render.shader = TextShader
		default:
			render.shader = DefaultShader
		}
	}

	// This is to prevent users from using the wrong one
	if render.shader == HUDShader {
		switch render.Drawable.(type) {
		case Triangle:
			render.shader = LegacyHUDShader
		case Circle:
			render.shader = LegacyHUDShader
		case Rectangle:
			render.shader = LegacyHUDShader
		case ComplexTriangles:
			render.shader = LegacyHUDShader
		case Text:
			render.shader = TextHUDShader
		default:
			render.shader = HUDShader
		}
	}

	rs.entities = append(rs.entities, renderEntity{basic, render, space})
	rs.sortingNeeded = true
}

// EntityExists looks if the entity is already into the System's entities. It will return the index >= 0 of the object into de rs.entities or -1 if it could not be found.
func (rs *RenderSystem) EntityExists(basic *ecs.BasicEntity) int {
	for index, entity := range rs.entities {
		if entity.ID() == basic.ID() {
			return index
		}
	}

	return -1
}

// AddByInterface adds any Renderable to the render system. Any Entity containing a BasicEntity,RenderComponent, and SpaceComponent anonymously does this automatically
func (rs *RenderSystem) AddByInterface(i ecs.Identifier) {
	o, _ := i.(Renderable)
	rs.Add(o.GetBasicEntity(), o.GetRenderComponent(), o.GetSpaceComponent())
}

// Remove removes an entity from the RenderSystem
func (rs *RenderSystem) Remove(basic ecs.BasicEntity) {
	var delete = rs.EntityExists(&basic)
	if delete >= 0 {
		rs.entities = append(rs.entities[:delete], rs.entities[delete+1:]...)
		rs.sortingNeeded = true
	}
}

// Update draws the entities in the RenderSystem to the OpenGL Surface.
func (rs *RenderSystem) Update(dt float32) {
	if engo.Headless() {
		return
	}

	if rs.sortingNeeded {
		sort.Sort(rs.entities)
		rs.sortingNeeded = false
	}

	if rs.newCamera {
		newCamera(rs.world)
		rs.newCamera = false
	}

	engo.Gl.Clear(engo.Gl.COLOR_BUFFER_BIT)

	// TODO: it's linear for now, but that might very well be a bad idea
	for _, e := range rs.entities {
		if e.RenderComponent.Hidden {
			continue // with other entities
		}

		// Retrieve a shader, may be the default one -- then use it if we aren't already using it
		shader := e.RenderComponent.shader

		// Change Shader if we have to
		if shader != rs.currentShader {
			if rs.currentShader != nil {
				rs.currentShader.Post()
			}
			shader.Pre()
			rs.currentShader = shader
		}

		// Setting default scale to 1
		if e.RenderComponent.Scale.X == 0 && e.RenderComponent.Scale.Y == 0 {
			e.RenderComponent.Scale = engo.Point{X: 1, Y: 1}
		}

		// Setting default to white
		if e.RenderComponent.Color == nil {
			e.RenderComponent.Color = color.White
		}

		rs.currentShader.Draw(e.RenderComponent, e.SpaceComponent)
	}

	if rs.currentShader != nil {
		rs.currentShader.Post()
		rs.currentShader = nil
	}
}

// SetBackground sets the OpenGL ClearColor to the provided color.
func SetBackground(c color.Color) {
	if !engo.Headless() {
		r, g, b, a := c.RGBA()

		engo.Gl.ClearColor(float32(r)/0xffff, float32(g)/0xffff, float32(b)/0xffff, float32(a)/0xffff)
	}
}
<commit_msg>add panic if shaders don't compile. This will allow custom shaders to know if their shaders have errors<commit_after>package common

import (
	"fmt"
	"image/color"
	"sort"

	"sync"

	"engo.io/ecs"
	"engo.io/engo"
	"engo.io/gl"
)

const (
	// RenderSystemPriority is the priority of the RenderSystem
	RenderSystemPriority = -1000
)

type renderChangeMessage struct{}

func (renderChangeMessage) Type() string {
	return "renderChangeMessage"
}

// Drawable is that which can be rendered to OpenGL.
type Drawable interface {
	Texture() *gl.Texture
	Width() float32
	Height() float32
	View() (float32, float32, float32, float32)
	Close()
}

// TextureRepeating is the method used to repeat a texture in OpenGL.
type TextureRepeating uint8

const (
	// NoRepeat does not repeat the texture.
	NoRepeat TextureRepeating = iota
	// ClampToEdge stretches the texture to the edge of the viewport.
	ClampToEdge
	// ClampToBorder stretches the texture to the border of the viewpport.
	ClampToBorder
	// Repeat repeats the texture until the border of the viewport.
	Repeat
	// MirroredRepeat repeats a mirror image of the texture until the border of the viewport.
	MirroredRepeat
)

// ZoomFilter is a filter used when zooming in or out of a texture.
type ZoomFilter uint8

const (
	// FilterNearest is a simple nearest neighbor algorithm
	FilterNearest ZoomFilter = iota
	// FilterLinear is a bilinear interpolation algorithm
	FilterLinear
)

// RenderComponent is the component needed to render an entity.
type RenderComponent struct {
	// Hidden is used to prevent drawing by OpenGL
	Hidden bool
	// Scale is the scale at which to render, in the X and Y axis. Not defining Scale, will default to engo.Point{1, 1}
	Scale engo.Point
	// Color defines how much of the color-components of the texture get used
	Color color.Color
	// Drawable refers to the Texture that should be drawn
	Drawable Drawable
	// Repeat defines how to repeat the Texture if the SpaceComponent of the entity
	// is larger than the texture itself, after applying scale. Defaults to NoRepeat
	// which allows the texture to draw entirely without regard to th SpaceComponent
	// Do not set to anything other than NoRepeat for textures in a sprite sheet.
	// This does not yet work with sprite sheets.
	Repeat TextureRepeating
	// Buffer represents the buffer object itself
	// Avoid using it unless your are writing a custom shader
	Buffer *gl.Buffer
	// BufferContent contains the buffer data
	// Avoid using it unless your are writing a custom shader
	BufferContent []float32

	magFilter, minFilter               ZoomFilter
	magFilterChanged, minFilterChanged bool

	shader Shader
	zIndex float32
}

// SetShader sets the shader used by the RenderComponent.
func (r *RenderComponent) SetShader(s Shader) {
	r.shader = s
	engo.Mailbox.Dispatch(&renderChangeMessage{})
}

// SetZIndex sets the order that the RenderComponent is drawn to the screen. Higher z-indices are drawn on top of
// lower ones if they overlap.
func (r *RenderComponent) SetZIndex(index float32) {
	r.zIndex = index
	engo.Mailbox.Dispatch(&renderChangeMessage{})
}

// SetMinFilter sets the ZoomFilter used for minimizing the RenderComponent
func (r *RenderComponent) SetMinFilter(z ZoomFilter) {
	r.minFilter = z
	r.minFilterChanged = true
}

// SetMagFilter sets the ZoomFilter used for magnifying the RenderComponent
func (r *RenderComponent) SetMagFilter(z ZoomFilter) {
	r.magFilter = z
	r.magFilterChanged = true
}

type renderEntity struct {
	*ecs.BasicEntity
	*RenderComponent
	*SpaceComponent
}

type renderEntityList []renderEntity

func (r renderEntityList) Len() int {
	return len(r)
}

func (r renderEntityList) Less(i, j int) bool {
	// Sort by shader-pointer if they have the same zIndex
	if r[i].RenderComponent.zIndex == r[j].RenderComponent.zIndex {
		// TODO: optimize this for performance
		return fmt.Sprintf("%p", &r[i].RenderComponent.shader) < fmt.Sprintf("%p", &r[j].RenderComponent.shader)
	}

	return r[i].RenderComponent.zIndex < r[j].RenderComponent.zIndex
}

func (r renderEntityList) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

// RenderSystem is the system that draws entities on the OpenGL surface. It requires
// a CameraSystem to work. If a CameraSystem is not in the World when you add RenderSystem
// one is automatically added to the world.
type RenderSystem struct {
	entities renderEntityList
	world    *ecs.World

	sortingNeeded, newCamera bool
	currentShader            Shader
}

// Priority implements the ecs.Prioritizer interface.
func (*RenderSystem) Priority() int { return RenderSystemPriority }

// New initializes the RenderSystem
func (rs *RenderSystem) New(w *ecs.World) {
	rs.world = w

	engo.Mailbox.Listen("NewCameraMessage", func(engo.Message) {
		rs.newCamera = true
	})

	addCameraSystemOnce(w)

	if !engo.Headless() {
		if err := initShaders(w); err != nil {
			panic(err)
		}
		engo.Gl.Enable(engo.Gl.MULTISAMPLE)
	}

	engo.Mailbox.Listen("renderChangeMessage", func(engo.Message) {
		rs.sortingNeeded = true
	})
}

var cameraInitMutex sync.Mutex

func addCameraSystemOnce(w *ecs.World) {
	cameraInitMutex.Lock()
	defer cameraInitMutex.Unlock()

	camSystemAlreadyAdded := false
	for _, system := range w.Systems() {
		switch system.(type) {
		case *CameraSystem:
			camSystemAlreadyAdded = true
		}
	}
	if !camSystemAlreadyAdded {
		w.AddSystem(&CameraSystem{})
	}
}

// Add adds an entity to the RenderSystem. The entity needs a basic, render, and space component to be added to the system.
func (rs *RenderSystem) Add(basic *ecs.BasicEntity, render *RenderComponent, space *SpaceComponent) {
	// Do nothing if entity already exists
	if rs.EntityExists(basic) >= 0 {
		return
	}

	// Setting default shader
	if render.shader == nil {
		switch render.Drawable.(type) {
		case Triangle:
			render.shader = LegacyShader
		case Circle:
			render.shader = LegacyShader
		case Rectangle:
			render.shader = LegacyShader
		case ComplexTriangles:
			render.shader = LegacyShader
		case Text:
			render.shader = TextShader
		default:
			render.shader = DefaultShader
		}
	}

	// This is to prevent users from using the wrong one
	if render.shader == HUDShader {
		switch render.Drawable.(type) {
		case Triangle:
			render.shader = LegacyHUDShader
		case Circle:
			render.shader = LegacyHUDShader
		case Rectangle:
			render.shader = LegacyHUDShader
		case ComplexTriangles:
			render.shader = LegacyHUDShader
		case Text:
			render.shader = TextHUDShader
		default:
			render.shader = HUDShader
		}
	}

	rs.entities = append(rs.entities, renderEntity{basic, render, space})
	rs.sortingNeeded = true
}

// EntityExists looks if the entity is already into the System's entities. It will return the index >= 0 of the object into de rs.entities or -1 if it could not be found.
func (rs *RenderSystem) EntityExists(basic *ecs.BasicEntity) int {
	for index, entity := range rs.entities {
		if entity.ID() == basic.ID() {
			return index
		}
	}

	return -1
}

// AddByInterface adds any Renderable to the render system. Any Entity containing a BasicEntity,RenderComponent, and SpaceComponent anonymously does this automatically
func (rs *RenderSystem) AddByInterface(i ecs.Identifier) {
	o, _ := i.(Renderable)
	rs.Add(o.GetBasicEntity(), o.GetRenderComponent(), o.GetSpaceComponent())
}

// Remove removes an entity from the RenderSystem
func (rs *RenderSystem) Remove(basic ecs.BasicEntity) {
	var delete = rs.EntityExists(&basic)
	if delete >= 0 {
		rs.entities = append(rs.entities[:delete], rs.entities[delete+1:]...)
		rs.sortingNeeded = true
	}
}

// Update draws the entities in the RenderSystem to the OpenGL Surface.
func (rs *RenderSystem) Update(dt float32) {
	if engo.Headless() {
		return
	}

	if rs.sortingNeeded {
		sort.Sort(rs.entities)
		rs.sortingNeeded = false
	}

	if rs.newCamera {
		newCamera(rs.world)
		rs.newCamera = false
	}

	engo.Gl.Clear(engo.Gl.COLOR_BUFFER_BIT)

	// TODO: it's linear for now, but that might very well be a bad idea
	for _, e := range rs.entities {
		if e.RenderComponent.Hidden {
			continue // with other entities
		}

		// Retrieve a shader, may be the default one -- then use it if we aren't already using it
		shader := e.RenderComponent.shader

		// Change Shader if we have to
		if shader != rs.currentShader {
			if rs.currentShader != nil {
				rs.currentShader.Post()
			}
			shader.Pre()
			rs.currentShader = shader
		}

		// Setting default scale to 1
		if e.RenderComponent.Scale.X == 0 && e.RenderComponent.Scale.Y == 0 {
			e.RenderComponent.Scale = engo.Point{X: 1, Y: 1}
		}

		// Setting default to white
		if e.RenderComponent.Color == nil {
			e.RenderComponent.Color = color.White
		}

		rs.currentShader.Draw(e.RenderComponent, e.SpaceComponent)
	}

	if rs.currentShader != nil {
		rs.currentShader.Post()
		rs.currentShader = nil
	}
}

// SetBackground sets the OpenGL ClearColor to the provided color.
func SetBackground(c color.Color) {
	if !engo.Headless() {
		r, g, b, a := c.RGBA()

		engo.Gl.ClearColor(float32(r)/0xffff, float32(g)/0xffff, float32(b)/0xffff, float32(a)/0xffff)
	}
}
<|endoftext|>


<commit_before>// +build !no_gzip

package compress

import (
	"bytes"
	"compress/gzip"
	"github.com/xitongsys/parquet-go/parquet"
	"io/ioutil"
	"sync"
)

var gzipWriterPool sync.Pool
var buffersPool sync.Pool

func init() {
	gzipWriterPool = sync.Pool{
		New: func() interface{} {
			return gzip.NewWriter(nil)
		},
	}

	buffersPool = sync.Pool{
		New: func() interface{} {
			return new(bytes.Buffer)
		},
	}

	compressors[parquet.CompressionCodec_GZIP] = &Compressor{
		Compress: func(buf []byte) []byte {
			res := buffersPool.Get().(*bytes.Buffer)
			res.Reset()
			gzipWriter := gzipWriterPool.Get().(*gzip.Writer)
			gzipWriter.Reset(res)
			gzipWriter.Write(buf)
			gzipWriter.Close()
			buffersPool.Put(res)
			gzipWriterPool.Put(gzipWriter)
			return res.Bytes()
		},
		Uncompress: func(buf []byte) (i []byte, err error) {
			rbuf := bytes.NewReader(buf)
			gzipReader, _ := gzip.NewReader(rbuf)
			res, err := ioutil.ReadAll(gzipReader)
			return res, err
		},
	}
}
<commit_msg>prevent buffer leak<commit_after>// +build !no_gzip

package compress

import (
	"bytes"
	"compress/gzip"
	"github.com/xitongsys/parquet-go/parquet"
	"io/ioutil"
	"sync"
)

var gzipWriterPool sync.Pool

func init() {
	gzipWriterPool = sync.Pool{
		New: func() interface{} {
			return gzip.NewWriter(nil)
		},
	}

	compressors[parquet.CompressionCodec_GZIP] = &Compressor{
		Compress: func(buf []byte) []byte {
			res := new(bytes.Buffer)
			gzipWriter := gzipWriterPool.Get().(*gzip.Writer)
			gzipWriter.Reset(res)
			gzipWriter.Write(buf)
			gzipWriter.Close()
			gzipWriterPool.Put(gzipWriter)
			return res.Bytes()
		},
		Uncompress: func(buf []byte) (i []byte, err error) {
			rbuf := bytes.NewReader(buf)
			gzipReader, _ := gzip.NewReader(rbuf)
			res, err := ioutil.ReadAll(gzipReader)
			return res, err
		},
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"

type tableMap struct {
	Mob string
	Sex string
}

func main() {
	var (
		modelList map[string]tableMap=make(map[string]tableMap)
		itemList map[string]tableMap=make(map[string]tableMap)
	)
	modelList["邵炜"]=tableMap{
		Mob:"",
		Sex:"asdf",
	}
	modelList["邵炜1"]=tableMap{
		Mob:"",
		Sex:"asdf",
	}
	modelList["邵炜2"]=tableMap{
		Mob:"",
		Sex:"asdf",
	}
	modelList["邵炜3"]=tableMap{
		Mob:"",
		Sex:"asdf",
	}

	itemList["1邵炜"]=tableMap{
		Mob:"",
		Sex:"asdf",
	}
	itemList["2邵炜1"]=tableMap{
		Mob:"",
		Sex:"asdf",
	}
	itemList["3邵炜2"]=tableMap{
		Mob:"",
		Sex:"asdf",
	}
	itemList["4邵炜3"]=tableMap{
		Mob:"",
		Sex:"asdf",
	}
fmt.Println(len(itemList))
	updateMap(&itemList)
	fmt.Println(len(itemList))
	fmt.Println(itemList["123"].Mob)
}

func updateMap(model *map[string]tableMap) {
	models:=*model
	models["123"]=tableMap{
		Mob:"18551734732",
		Sex:"asdf",
	}
}<commit_msg>检测map[][]格式是否可行<commit_after>package main

import (
	"encoding/json"
	"fmt"
)

type tableMap struct {
	Mob string
	Sex string
}

func main() {
	var (
		modelList         map[string]tableMap          = make(map[string]tableMap)
		itemList          map[string]tableMap          = make(map[string]tableMap)
		resultMessageCode map[string]map[string]string = make(map[string]map[string]string)
	)

	fjsisfkl := "{ \"eucpResultCode\":{ \"0\":\"成功\", \"DELIVRD\":\"成功\", \"EM:000\":\"内容为空失败\", \"EM:001\":\"手机号和内容相同失败，频繁发送\", \"EM:101\":\"黑名单失败\", \"EM:102\":\"签名未报备，失败\", \"EM:103\":\"黑字典失败\", \"EM:104\":\"移动通道无签名失败\", \"EM:105\":\"联通通道/电信通道无签名失败\", \"EM:2000\":\"无签名失败\", \"EM:2001\":\"双签名，失败\", \"MOBFAIL\":\"运营商黑名单\", \"CJ:0007\":\"签名被运营商屏蔽\" } }"

	err := json.Unmarshal([]byte(fjsisfkl), &resultMessageCode)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	fmt.Println(resultMessageCode["eucpResultCode"]["0"])

	modelList["邵炜"] = tableMap{
		Mob: "",
		Sex: "asdf",
	}
	modelList["邵炜1"] = tableMap{
		Mob: "",
		Sex: "asdf",
	}
	modelList["邵炜2"] = tableMap{
		Mob: "",
		Sex: "asdf",
	}
	modelList["邵炜3"] = tableMap{
		Mob: "",
		Sex: "asdf",
	}

	itemList["1邵炜"] = tableMap{
		Mob: "",
		Sex: "asdf",
	}
	itemList["2邵炜1"] = tableMap{
		Mob: "",
		Sex: "asdf",
	}
	itemList["3邵炜2"] = tableMap{
		Mob: "",
		Sex: "asdf",
	}
	itemList["4邵炜3"] = tableMap{
		Mob: "",
		Sex: "asdf",
	}
	fmt.Println(len(itemList))
	updateMap(&itemList)
	fmt.Println(len(itemList))
	fmt.Println(itemList["123"].Mob)
}

func updateMap(model *map[string]tableMap) {
	models := *model
	models["123"] = tableMap{
		Mob: "18551734732",
		Sex: "asdf",
	}
}
<|endoftext|>


<commit_before>package dirx

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/json-iterator/go"

	"github.com/qiniu/log"

	"github.com/qiniu/logkit/conf"
	"github.com/qiniu/logkit/reader"
	. "github.com/qiniu/logkit/utils/models"
)

var (
	_ reader.DaemonReader = &Reader{}
	_ reader.StatsReader  = &Reader{}
	_ reader.Reader       = &Reader{}
	_ Resetable           = &Reader{}
)

func init() {
	reader.RegisterConstructor(reader.ModeDirx, NewReader)
}

type message struct {
	result  string
	logpath string
}

type Reader struct {
	meta *reader.Meta
	// Note: 原子操作，用于表示 reader 整体的运行状态
	status int32

	stopChan chan struct{}
	msgChan  chan message
	errChan  chan error

	stats     StatsInfo
	statsLock sync.RWMutex

	headRegexp  *regexp.Regexp
	currentFile string
	dirReaders  *dirReaders

	// 以下为传入参数
	logPathPattern     string
	statInterval       time.Duration
	maxOpenFiles       int
	ignoreHidden       bool
	skipFirstLine      bool
	newFileNewLine     bool
	ignoreFileSuffixes []string
	validFilesRegex    string
	whence             string
	bufferSize         int
}

func NewReader(meta *reader.Meta, conf conf.MapConf) (reader.Reader, error) {
	logPathPattern, err := conf.GetString(reader.KeyLogPath)
	if err != nil {
		return nil, err
	}

	statIntervalDur, _ := conf.GetStringOr(reader.KeyStatInterval, "3m")
	statInterval, err := time.ParseDuration(statIntervalDur)
	if err != nil {
		return nil, err
	}

	expireDur, _ := conf.GetStringOr(reader.KeyExpire, "720h")
	expire, err := time.ParseDuration(expireDur)
	if err != nil {
		return nil, err
	}

	maxOpenFiles, _ := conf.GetIntOr(reader.KeyMaxOpenFiles, 256)
	ignoreHidden, _ := conf.GetBoolOr(reader.KeyIgnoreHiddenFile, true) // 默认不读取隐藏文件
	skipFirstLine, _ := conf.GetBoolOr(reader.KeySkipFileFirstLine, false)
	newFileNewLine, _ := conf.GetBoolOr(reader.KeyNewFileNewLine, false)
	ignoreFileSuffixes, _ := conf.GetStringListOr(reader.KeyIgnoreFileSuffix, reader.DefaultIgnoreFileSuffixes)
	validFilesRegex, _ := conf.GetStringOr(reader.KeyValidFilePattern, "*")
	whence, _ := conf.GetStringOr(reader.KeyWhence, reader.WhenceOldest)
	bufferSize, _ := conf.GetIntOr(reader.KeyBufSize, reader.DefaultBufSize)

	_, _, bufsize, err := meta.ReadBufMeta()
	if err != nil {
		if os.IsNotExist(err) {
			log.Debugf("Runner[%v] %v recover from meta error %v, ignore...", meta.RunnerName, logPathPattern, err)
		} else {
			log.Warnf("Runner[%v] %v recover from meta error %v, ignore...", meta.RunnerName, logPathPattern, err)
		}
		bufsize = 0
	}

	cachedLines := make(map[string]string)
	buf := make([]byte, bufsize)
	if bufsize > 0 {
		if _, err = meta.ReadBuf(buf); err != nil {
			if os.IsNotExist(err) {
				log.Debugf("Runner[%v] read buf file[%v] failed: %v, ignore...", meta.RunnerName, meta.BufFile(), err)
			} else {
				log.Warnf("Runner[%v] read buf file[%v] failed: %v, ignore...", meta.RunnerName, meta.BufFile(), err)
			}
		} else {
			err = jsoniter.Unmarshal(buf, &cachedLines)
			if err != nil {
				log.Warnf("Runner[%v] unmarshal read buf cache failed: %v, ignore...", meta.RunnerName, err)
			}
		}
	}

	return &Reader{
		meta:               meta,
		status:             reader.StatusInit,
		stopChan:           make(chan struct{}),
		msgChan:            make(chan message),
		errChan:            make(chan error),
		dirReaders:         newDirReaders(meta, expire, cachedLines),
		logPathPattern:     strings.TrimSuffix(logPathPattern, "/"),
		statInterval:       statInterval,
		maxOpenFiles:       maxOpenFiles,
		ignoreHidden:       ignoreHidden,
		skipFirstLine:      skipFirstLine,
		newFileNewLine:     newFileNewLine,
		ignoreFileSuffixes: ignoreFileSuffixes,
		validFilesRegex:    validFilesRegex,
		whence:             whence,
		bufferSize:         bufferSize,
	}, nil
}

func (r *Reader) isStopping() bool {
	return atomic.LoadInt32(&r.status) == reader.StatusStopping
}

func (r *Reader) hasStopped() bool {
	return atomic.LoadInt32(&r.status) == reader.StatusStopped
}

func (r *Reader) Name() string {
	return "DirxReader: " + r.logPathPattern
}

func (r *Reader) SetMode(mode string, v interface{}) error {
	reg, err := reader.HeadPatternMode(mode, v)
	if err != nil {
		return fmt.Errorf("get head pattern mode: %v", err)
	}
	if reg != nil {
		r.headRegexp = reg
	}
	return nil
}

func (r *Reader) setStatsError(err string) {
	r.statsLock.Lock()
	defer r.statsLock.Unlock()
	r.stats.LastError = err
}

func (r *Reader) sendError(err error) {
	if err == nil {
		return
	}
	defer func() {
		if rec := recover(); rec != nil {
			log.Errorf("Reader %q was panicked and recovered from %v", r.Name(), rec)
		}
	}()
	r.errChan <- err
}

func (r *Reader) statLogPath() {
	// 达到最大打开文件数时不再追踪
	if r.dirReaders.Num() >= r.maxOpenFiles {
		log.Warnf("Runner[%v] has met 'maxOpenFiles' limit %v, stat new log ignored", r.meta.RunnerName, r.maxOpenFiles)
		return
	}

	matches, err := filepath.Glob(r.logPathPattern)
	if err != nil {
		errMsg := fmt.Sprintf("Runner[%v] stat log path failed: %v", r.meta.RunnerName, err)
		log.Error(errMsg)
		r.setStatsError(errMsg)
		return
	}
	if len(matches) == 0 {
		log.Debugf("Runner[%v] no match found after stated log path %q", r.meta.RunnerName, r.logPathPattern)
		return
	}
	log.Debugf("Runner[%v] %d matches found after stated log path %q: %v", r.meta.RunnerName, len(matches), r.logPathPattern, matches)

	var newPaths []string
	for _, m := range matches {
		logPath, fi, err := GetRealPath(m)
		if err != nil {
			log.Warnf("Runner[%v] file pattern %v match %v stat failed: %v, ignored this match", r.meta.RunnerName, r.logPathPattern, m, err)
			continue
		}
		if !fi.IsDir() {
			log.Debugf("Runner[%v] %v is a file, mode[dirx] only supports stat directory, ignored this match", r.meta.RunnerName, m)
			continue
		}
		if r.dirReaders.HasReader(logPath) {
			log.Debugf("Runner[%v] %q is collecting, ignored this path", r.meta.RunnerName, logPath)
			continue
		}

		// 过期的文件不追踪，除非之前追踪的并且有日志没读完
		if !r.dirReaders.hasCachedLine(logPath) && HasDirExpired(logPath, r.dirReaders.expire) {
			log.Debugf("Runner[%v] log path %q has expired, ignored this time", r.meta.RunnerName, logPath)
			continue
		}

		dr, err := r.dirReaders.NewReader(newReaderOptions{
			Meta:               r.meta,
			OriginalPath:       m,
			LogPath:            logPath,
			IgnoreHidden:       r.ignoreHidden,
			SkipFirstLine:      r.skipFirstLine,
			NewFileNewLine:     r.newFileNewLine,
			IgnoreFileSuffixes: r.ignoreFileSuffixes,
			ValidFilesRegex:    r.validFilesRegex,
			Whence:             r.whence,
			BufferSize:         r.bufferSize,
			MsgChan:            r.msgChan,
			ErrChan:            r.errChan,
		})
		if err != nil {
			err = fmt.Errorf("create new reader for log path %q failed: %v", logPath, err)
			r.sendError(err)
			log.Errorf("Runner[%v] %v, ignored this path", r.meta.RunnerName, err)
			continue
		}

		if r.headRegexp != nil {
			err = dr.br.SetMode(reader.ReadModeHeadPatternRegexp, r.headRegexp)
			if err != nil {
				errMsg := fmt.Sprintf("Runner[%v] set mode for log path %q failed: %v", r.meta.RunnerName, logPath, err)
				log.Error(errMsg)
				r.setStatsError(errMsg)
			}
		}
		newPaths = append(newPaths, logPath)

		if r.hasStopped() {
			log.Warnf("Runner[%v] created new reader for log path %q but daemon reader has stopped, will not run at this time", r.meta.RunnerName, logPath)
			continue
		}

		go dr.Run()
	}
	if len(newPaths) > 0 {
		log.Infof("Runner[%v] stat log path found %d new log paths: %v", r.meta.RunnerName, len(newPaths), newPaths)
	} else {
		log.Debugf("Runner[%v] stat log path has not found any new log path", r.meta.RunnerName)
	}
}

func (r *Reader) Start() error {
	if r.isStopping() || r.hasStopped() {
		return errors.New("reader is stopping or has stopped")
	} else if !atomic.CompareAndSwapInt32(&r.status, reader.StatusInit, reader.StatusRunning) {
		log.Warnf("Runner[%v] %q daemon has already started and is running", r.meta.RunnerName, r.Name())
		return nil
	}

	go func() {
		ticker := time.NewTicker(r.statInterval)
		defer ticker.Stop()
		for {
			r.dirReaders.checkExpiredDirs()
			r.statLogPath()

			select {
			case <-r.stopChan:
				atomic.StoreInt32(&r.status, reader.StatusStopped)
				log.Infof("Runner[%v] %q daemon has stopped from running", r.meta.RunnerName, r.Name())
				return
			case <-ticker.C:
			}
		}
	}()
	log.Infof("Runner[%v] %q daemon has started", r.meta.RunnerName, r.Name())
	return nil
}

func (r *Reader) Source() string {
	return r.currentFile
}

// Note: 对 currentFile 的操作非线程安全，需由上层逻辑保证同步调用 ReadLine
func (r *Reader) ReadLine() (string, error) {
	timer := time.NewTimer(time.Second)
	defer timer.Stop()
	select {
	case msg := <-r.msgChan:
		r.currentFile = msg.logpath
		return msg.result, nil
	case err := <-r.errChan:
		return "", err
	case <-timer.C:
	}

	return "", nil
}

func (r *Reader) Status() StatsInfo {
	r.statsLock.RLock()
	defer r.statsLock.RUnlock()

	for _, dr := range r.dirReaders.getReaders() {
		st := dr.Status()
		if st.LastError != "" {
			r.stats.LastError += "\n<" + dr.originalPath + ">: " + st.LastError
		}
	}
	return r.stats
}

// SyncMeta 从队列取数据时同步队列，作用在于保证数据不重复
func (r *Reader) SyncMeta() {
	data, err := r.dirReaders.SyncMeta()
	if err != nil {
		log.Errorf("Runner[%v] reader %q sync meta failed: %v", r.meta.RunnerName, r.Name(), err)
		return
	}

	err = r.meta.WriteBuf(data, 0, 0, len(data))
	if err != nil {
		log.Errorf("Runner[%v] write meta[%v] failed: %v", r.meta.RunnerName, string(data), err)
		return
	}
}

func (r *Reader) Close() error {
	if !atomic.CompareAndSwapInt32(&r.status, reader.StatusRunning, reader.StatusStopping) {
		return errors.New("reader is not running")
	}
	log.Debugf("Runner[%v] %q daemon is stopping", r.meta.RunnerName, r.Name())
	close(r.stopChan)

	r.dirReaders.Close()
	r.SyncMeta()

	// 在所有 dirReader 关闭完成后再关闭管道
	close(r.msgChan)
	close(r.errChan)
	return nil
}

func (r *Reader) Reset() error {
	return r.dirReaders.Reset()
}
<commit_msg>reader/dirx: warning when duplicated Close calls<commit_after>package dirx

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/json-iterator/go"

	"github.com/qiniu/log"

	"github.com/qiniu/logkit/conf"
	"github.com/qiniu/logkit/reader"
	. "github.com/qiniu/logkit/utils/models"
)

var (
	_ reader.DaemonReader = &Reader{}
	_ reader.StatsReader  = &Reader{}
	_ reader.Reader       = &Reader{}
	_ Resetable           = &Reader{}
)

func init() {
	reader.RegisterConstructor(reader.ModeDirx, NewReader)
}

type message struct {
	result  string
	logpath string
}

type Reader struct {
	meta *reader.Meta
	// Note: 原子操作，用于表示 reader 整体的运行状态
	status int32

	stopChan chan struct{}
	msgChan  chan message
	errChan  chan error

	stats     StatsInfo
	statsLock sync.RWMutex

	headRegexp  *regexp.Regexp
	currentFile string
	dirReaders  *dirReaders

	// 以下为传入参数
	logPathPattern     string
	statInterval       time.Duration
	maxOpenFiles       int
	ignoreHidden       bool
	skipFirstLine      bool
	newFileNewLine     bool
	ignoreFileSuffixes []string
	validFilesRegex    string
	whence             string
	bufferSize         int
}

func NewReader(meta *reader.Meta, conf conf.MapConf) (reader.Reader, error) {
	logPathPattern, err := conf.GetString(reader.KeyLogPath)
	if err != nil {
		return nil, err
	}

	statIntervalDur, _ := conf.GetStringOr(reader.KeyStatInterval, "3m")
	statInterval, err := time.ParseDuration(statIntervalDur)
	if err != nil {
		return nil, err
	}

	expireDur, _ := conf.GetStringOr(reader.KeyExpire, "720h")
	expire, err := time.ParseDuration(expireDur)
	if err != nil {
		return nil, err
	}

	maxOpenFiles, _ := conf.GetIntOr(reader.KeyMaxOpenFiles, 256)
	ignoreHidden, _ := conf.GetBoolOr(reader.KeyIgnoreHiddenFile, true) // 默认不读取隐藏文件
	skipFirstLine, _ := conf.GetBoolOr(reader.KeySkipFileFirstLine, false)
	newFileNewLine, _ := conf.GetBoolOr(reader.KeyNewFileNewLine, false)
	ignoreFileSuffixes, _ := conf.GetStringListOr(reader.KeyIgnoreFileSuffix, reader.DefaultIgnoreFileSuffixes)
	validFilesRegex, _ := conf.GetStringOr(reader.KeyValidFilePattern, "*")
	whence, _ := conf.GetStringOr(reader.KeyWhence, reader.WhenceOldest)
	bufferSize, _ := conf.GetIntOr(reader.KeyBufSize, reader.DefaultBufSize)

	_, _, bufsize, err := meta.ReadBufMeta()
	if err != nil {
		if os.IsNotExist(err) {
			log.Debugf("Runner[%v] %v recover from meta error %v, ignore...", meta.RunnerName, logPathPattern, err)
		} else {
			log.Warnf("Runner[%v] %v recover from meta error %v, ignore...", meta.RunnerName, logPathPattern, err)
		}
		bufsize = 0
	}

	cachedLines := make(map[string]string)
	buf := make([]byte, bufsize)
	if bufsize > 0 {
		if _, err = meta.ReadBuf(buf); err != nil {
			if os.IsNotExist(err) {
				log.Debugf("Runner[%v] read buf file[%v] failed: %v, ignore...", meta.RunnerName, meta.BufFile(), err)
			} else {
				log.Warnf("Runner[%v] read buf file[%v] failed: %v, ignore...", meta.RunnerName, meta.BufFile(), err)
			}
		} else {
			err = jsoniter.Unmarshal(buf, &cachedLines)
			if err != nil {
				log.Warnf("Runner[%v] unmarshal read buf cache failed: %v, ignore...", meta.RunnerName, err)
			}
		}
	}

	return &Reader{
		meta:               meta,
		status:             reader.StatusInit,
		stopChan:           make(chan struct{}),
		msgChan:            make(chan message),
		errChan:            make(chan error),
		dirReaders:         newDirReaders(meta, expire, cachedLines),
		logPathPattern:     strings.TrimSuffix(logPathPattern, "/"),
		statInterval:       statInterval,
		maxOpenFiles:       maxOpenFiles,
		ignoreHidden:       ignoreHidden,
		skipFirstLine:      skipFirstLine,
		newFileNewLine:     newFileNewLine,
		ignoreFileSuffixes: ignoreFileSuffixes,
		validFilesRegex:    validFilesRegex,
		whence:             whence,
		bufferSize:         bufferSize,
	}, nil
}

func (r *Reader) isStopping() bool {
	return atomic.LoadInt32(&r.status) == reader.StatusStopping
}

func (r *Reader) hasStopped() bool {
	return atomic.LoadInt32(&r.status) == reader.StatusStopped
}

func (r *Reader) Name() string {
	return "DirxReader: " + r.logPathPattern
}

func (r *Reader) SetMode(mode string, v interface{}) error {
	reg, err := reader.HeadPatternMode(mode, v)
	if err != nil {
		return fmt.Errorf("get head pattern mode: %v", err)
	}
	if reg != nil {
		r.headRegexp = reg
	}
	return nil
}

func (r *Reader) setStatsError(err string) {
	r.statsLock.Lock()
	defer r.statsLock.Unlock()
	r.stats.LastError = err
}

func (r *Reader) sendError(err error) {
	if err == nil {
		return
	}
	defer func() {
		if rec := recover(); rec != nil {
			log.Errorf("Reader %q was panicked and recovered from %v", r.Name(), rec)
		}
	}()
	r.errChan <- err
}

func (r *Reader) statLogPath() {
	// 达到最大打开文件数时不再追踪
	if r.dirReaders.Num() >= r.maxOpenFiles {
		log.Warnf("Runner[%v] has met 'maxOpenFiles' limit %v, stat new log ignored", r.meta.RunnerName, r.maxOpenFiles)
		return
	}

	matches, err := filepath.Glob(r.logPathPattern)
	if err != nil {
		errMsg := fmt.Sprintf("Runner[%v] stat log path failed: %v", r.meta.RunnerName, err)
		log.Error(errMsg)
		r.setStatsError(errMsg)
		return
	}
	if len(matches) == 0 {
		log.Debugf("Runner[%v] no match found after stated log path %q", r.meta.RunnerName, r.logPathPattern)
		return
	}
	log.Debugf("Runner[%v] %d matches found after stated log path %q: %v", r.meta.RunnerName, len(matches), r.logPathPattern, matches)

	var newPaths []string
	for _, m := range matches {
		logPath, fi, err := GetRealPath(m)
		if err != nil {
			log.Warnf("Runner[%v] file pattern %v match %v stat failed: %v, ignored this match", r.meta.RunnerName, r.logPathPattern, m, err)
			continue
		}
		if !fi.IsDir() {
			log.Debugf("Runner[%v] %v is a file, mode[dirx] only supports stat directory, ignored this match", r.meta.RunnerName, m)
			continue
		}
		if r.dirReaders.HasReader(logPath) {
			log.Debugf("Runner[%v] %q is collecting, ignored this path", r.meta.RunnerName, logPath)
			continue
		}

		// 过期的文件不追踪，除非之前追踪的并且有日志没读完
		if !r.dirReaders.hasCachedLine(logPath) && HasDirExpired(logPath, r.dirReaders.expire) {
			log.Debugf("Runner[%v] log path %q has expired, ignored this time", r.meta.RunnerName, logPath)
			continue
		}

		dr, err := r.dirReaders.NewReader(newReaderOptions{
			Meta:               r.meta,
			OriginalPath:       m,
			LogPath:            logPath,
			IgnoreHidden:       r.ignoreHidden,
			SkipFirstLine:      r.skipFirstLine,
			NewFileNewLine:     r.newFileNewLine,
			IgnoreFileSuffixes: r.ignoreFileSuffixes,
			ValidFilesRegex:    r.validFilesRegex,
			Whence:             r.whence,
			BufferSize:         r.bufferSize,
			MsgChan:            r.msgChan,
			ErrChan:            r.errChan,
		})
		if err != nil {
			err = fmt.Errorf("create new reader for log path %q failed: %v", logPath, err)
			r.sendError(err)
			log.Errorf("Runner[%v] %v, ignored this path", r.meta.RunnerName, err)
			continue
		}

		if r.headRegexp != nil {
			err = dr.br.SetMode(reader.ReadModeHeadPatternRegexp, r.headRegexp)
			if err != nil {
				errMsg := fmt.Sprintf("Runner[%v] set mode for log path %q failed: %v", r.meta.RunnerName, logPath, err)
				log.Error(errMsg)
				r.setStatsError(errMsg)
			}
		}
		newPaths = append(newPaths, logPath)

		if r.hasStopped() {
			log.Warnf("Runner[%v] created new reader for log path %q but daemon reader has stopped, will not run at this time", r.meta.RunnerName, logPath)
			continue
		}

		go dr.Run()
	}
	if len(newPaths) > 0 {
		log.Infof("Runner[%v] stat log path found %d new log paths: %v", r.meta.RunnerName, len(newPaths), newPaths)
	} else {
		log.Debugf("Runner[%v] stat log path has not found any new log path", r.meta.RunnerName)
	}
}

func (r *Reader) Start() error {
	if r.isStopping() || r.hasStopped() {
		return errors.New("reader is stopping or has stopped")
	} else if !atomic.CompareAndSwapInt32(&r.status, reader.StatusInit, reader.StatusRunning) {
		log.Warnf("Runner[%v] %q daemon has already started and is running", r.meta.RunnerName, r.Name())
		return nil
	}

	go func() {
		ticker := time.NewTicker(r.statInterval)
		defer ticker.Stop()
		for {
			r.dirReaders.checkExpiredDirs()
			r.statLogPath()

			select {
			case <-r.stopChan:
				atomic.StoreInt32(&r.status, reader.StatusStopped)
				log.Infof("Runner[%v] %q daemon has stopped from running", r.meta.RunnerName, r.Name())
				return
			case <-ticker.C:
			}
		}
	}()
	log.Infof("Runner[%v] %q daemon has started", r.meta.RunnerName, r.Name())
	return nil
}

func (r *Reader) Source() string {
	return r.currentFile
}

// Note: 对 currentFile 的操作非线程安全，需由上层逻辑保证同步调用 ReadLine
func (r *Reader) ReadLine() (string, error) {
	timer := time.NewTimer(time.Second)
	defer timer.Stop()
	select {
	case msg := <-r.msgChan:
		r.currentFile = msg.logpath
		return msg.result, nil
	case err := <-r.errChan:
		return "", err
	case <-timer.C:
	}

	return "", nil
}

func (r *Reader) Status() StatsInfo {
	r.statsLock.RLock()
	defer r.statsLock.RUnlock()

	for _, dr := range r.dirReaders.getReaders() {
		st := dr.Status()
		if st.LastError != "" {
			r.stats.LastError += "\n<" + dr.originalPath + ">: " + st.LastError
		}
	}
	return r.stats
}

// SyncMeta 从队列取数据时同步队列，作用在于保证数据不重复
func (r *Reader) SyncMeta() {
	data, err := r.dirReaders.SyncMeta()
	if err != nil {
		log.Errorf("Runner[%v] reader %q sync meta failed: %v", r.meta.RunnerName, r.Name(), err)
		return
	}

	err = r.meta.WriteBuf(data, 0, 0, len(data))
	if err != nil {
		log.Errorf("Runner[%v] write meta[%v] failed: %v", r.meta.RunnerName, string(data), err)
		return
	}
}

func (r *Reader) Close() error {
	if !atomic.CompareAndSwapInt32(&r.status, reader.StatusRunning, reader.StatusStopping) {
		log.Warnf("Runner[%v] reader %q is not running, close operation ignored", r.meta.RunnerName, r.Name())
		return nil
	}
	log.Debugf("Runner[%v] %q daemon is stopping", r.meta.RunnerName, r.Name())
	close(r.stopChan)

	r.dirReaders.Close()
	r.SyncMeta()

	// 在所有 dirReader 关闭完成后再关闭管道
	close(r.msgChan)
	close(r.errChan)
	return nil
}

func (r *Reader) Reset() error {
	return r.dirReaders.Reset()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/hoisie/redis"
	"github.com/miekg/dns"
	"log"
	"net"
	"strings"
	"time"
)

// TTL Time to Live in seconds
const TTL uint32 = 300

// Record is the json format message that is stored in Redis
type Record struct {
	CName         string    `json:"cname"`
	PublicIP      net.IP    `json:"public_ip"`
	PrivateIP     net.IP    `json:"private_ip"`
	ValidUntil    time.Time `json:"valid_until"`
	IPv4PublicIP  net.IP    `json:"ipv4_public_ip"`
	IPv6PublicIP  net.IP    `json:"ipv6_public_ip"`
	IPv4PrivateIP net.IP    `json:"ipv4_private_ip"`
}

// RedisDNSServer contains the configuration details for the server
type RedisDNSServer struct {
	domain      string
	hostname    string
	redisClient redis.Client
	mbox        string
}

// response is the dns message
type response struct {
	*dns.Msg
}

// NewRedisDNSServer is a convienence for creating a new server
func NewRedisDNSServer(domain string, hostname string, redisClient redis.Client, mbox string) *RedisDNSServer {
	if !strings.HasSuffix(domain, ".") {
		domain += "."
	}

	if !strings.HasSuffix(hostname, ".") {
		hostname += "."
	}

	server := &RedisDNSServer{
		domain:      domain,
		hostname:    hostname,
		redisClient: redisClient,
		mbox:        mbox,
	}

	dns.HandleFunc(server.domain, server.handleRequest)
	return server
}

func (s *RedisDNSServer) listenAndServe(port, net string) {
	server := &dns.Server{Addr: port, Net: net}
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("%s", err)
	}
}

func (s *RedisDNSServer) handleRequest(w dns.ResponseWriter, request *dns.Msg) {
	r := new(dns.Msg)
	r.SetReply(request)
	r.Authoritative = true

	for _, msg := range request.Question {
		log.Printf("%v %#v %v (id=%v)", dns.TypeToString[msg.Qtype], msg.Name, w.RemoteAddr(), request.Id)

		answers := s.Answer(msg)

		if len(answers) > 0 {
			log.Println("Answers present")
			r.Answer = append(r.Answer, answers...)
		} else {
			log.Println("No Answers present")
			r.Answer = append(r.Ns, s.SOA(msg))
			log.Println("Appended...")
		}
	}
	log.Printf("Send Reply: %+v", r)
	w.WriteMsg(r)
	log.Printf("SentReply")
}

// Answer crafts a response to the DNS Question
func (s *RedisDNSServer) Answer(msg dns.Question) []dns.RR {
	var answers []dns.RR
	record := s.Lookup(msg)
	ttl := TTL
	// Bail out early if the record isn't found in the key store
	if record == nil || record.CName == "" {
		log.Println("No record in key store, returning nil")
		return nil
	}

	switch msg.Qtype {
	case dns.TypeNS:
		log.Println("Processing NS request")
		if msg.Name == s.domain {
			answers = append(answers, &dns.NS{
				Hdr: dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 300},
				Ns:  s.hostname,
			})
		}
	case dns.TypeSOA:
		log.Println("Processing SOA request")
		log.Println("  Domain is", s.domain)
		if msg.Name == s.domain {
			log.Println("  msg.Name == s.domain", msg.Name, s.domain)

			r := new(dns.SOA)
			r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 60}
			r.Ns = s.hostname
			r.Mbox = "d.ascreen.co"
			r.Serial = uint32(time.Now().Unix())
			r.Refresh = 60
			r.Retry = 60
			r.Expire = 86400
			r.Minttl = 60
			answers = append(answers, r)
		} else {
			log.Println("  no match!", msg.Name, s.domain)
		}
	case dns.TypeA:
		log.Println("Processing A request")
		addr := record.IPv4PublicIP
		// bail if no ip address
		if addr == nil {
			log.Println("No ip address, returning nil")
			return nil
		}
		r := new(dns.A)
		r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl}
		r.A = addr
		answers = append(answers, r)
	case dns.TypeAAAA:
		log.Println("Processing AAAA request")
		addr := record.IPv6PublicIP
		// bail if no ip address
		if addr == nil {
			return nil
		}

		r := new(dns.AAAA)
		r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl}
		r.AAAA = addr
		answers = append(answers, r)
	case dns.TypeCNAME:
		log.Println("Processing CNAME request")
		r := new(dns.CNAME)
		r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: ttl}
		r.Target = msg.Name
		answers = append(answers, r)
	case dns.TypeMX:
		log.Println("Processing MX request")
		r := new(dns.MX)
		r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: ttl}
		r.Preference = 10
		r.Mx = msg.Name
		answers = append(answers, r)
	}
	return answers
}

// Lookup will locate the details in Redis for the fqdn, if not found
// lookup will try to locate a wildcard entry for the fqdn
func (s *RedisDNSServer) Lookup(msg dns.Question) *Record {
	log.Printf("LOOKUP: Looking for '%s'\n", msg.Name)
	str, err := s.redisClient.Get(msg.Name)
	log.Printf(" found str\n%s", str)
	var result Record

	// error indicates that the record was not found
	if err != nil {
		wildcard := wildCardHostName(msg.Name)
		log.Printf("No record for %s, trying wildcard %s\n", msg.Name, wildcard)

		domainDots := strings.Count(s.domain, ".") + 1
		msgDots := strings.Count(msg.Name, ".")
		if msgDots <= domainDots {
			log.Printf("msgDots <= domainDots returning nil")
			return nil
		}

		str, err = s.redisClient.Get(wildcard)
		if err != nil {
			log.Printf("No record for %s\n", wildcard)
			return nil
		}
	}
	json.Unmarshal([]byte(str), &result)
	return &result
}

func wildCardHostName(hostName string) string {
	nameParts := strings.SplitAfterN(hostName, ".", 2)
	return "*." + nameParts[1]
}

// SOA returns the Server of Authority record response
func (s *RedisDNSServer) SOA(msg dns.Question) dns.RR {
	r := new(dns.SOA)
	r.Hdr = dns.RR_Header{Name: s.domain, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 60}
	r.Ns = s.hostname
	r.Mbox = s.mbox
	r.Serial = uint32(time.Now().Unix())
	r.Refresh = 86400
	r.Retry = 7200
	r.Expire = 86400
	r.Minttl = 60
	return r
}
<commit_msg>Use r.mbox<commit_after>package main

import (
	"encoding/json"
	"github.com/hoisie/redis"
	"github.com/miekg/dns"
	"log"
	"net"
	"strings"
	"time"
)

// TTL Time to Live in seconds
const TTL uint32 = 300

// Record is the json format message that is stored in Redis
type Record struct {
	CName         string    `json:"cname"`
	PublicIP      net.IP    `json:"public_ip"`
	PrivateIP     net.IP    `json:"private_ip"`
	ValidUntil    time.Time `json:"valid_until"`
	IPv4PublicIP  net.IP    `json:"ipv4_public_ip"`
	IPv6PublicIP  net.IP    `json:"ipv6_public_ip"`
	IPv4PrivateIP net.IP    `json:"ipv4_private_ip"`
}

// RedisDNSServer contains the configuration details for the server
type RedisDNSServer struct {
	domain      string
	hostname    string
	redisClient redis.Client
	mbox        string
}

// response is the dns message
type response struct {
	*dns.Msg
}

// NewRedisDNSServer is a convienence for creating a new server
func NewRedisDNSServer(domain string, hostname string, redisClient redis.Client, mbox string) *RedisDNSServer {
	if !strings.HasSuffix(domain, ".") {
		domain += "."
	}

	if !strings.HasSuffix(hostname, ".") {
		hostname += "."
	}

	server := &RedisDNSServer{
		domain:      domain,
		hostname:    hostname,
		redisClient: redisClient,
		mbox:        mbox,
	}

	dns.HandleFunc(server.domain, server.handleRequest)
	return server
}

func (s *RedisDNSServer) listenAndServe(port, net string) {
	server := &dns.Server{Addr: port, Net: net}
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("%s", err)
	}
}

func (s *RedisDNSServer) handleRequest(w dns.ResponseWriter, request *dns.Msg) {
	r := new(dns.Msg)
	r.SetReply(request)
	r.Authoritative = true

	for _, msg := range request.Question {
		log.Printf("%v %#v %v (id=%v)", dns.TypeToString[msg.Qtype], msg.Name, w.RemoteAddr(), request.Id)

		answers := s.Answer(msg)

		if len(answers) > 0 {
			log.Println("Answers present")
			r.Answer = append(r.Answer, answers...)
		} else {
			log.Println("No Answers present")
			r.Answer = append(r.Ns, s.SOA(msg))
			log.Println("Appended...")
		}
	}
	log.Printf("Send Reply: %+v", r)
	w.WriteMsg(r)
	log.Printf("SentReply")
}

// Answer crafts a response to the DNS Question
func (s *RedisDNSServer) Answer(msg dns.Question) []dns.RR {
	var answers []dns.RR
	record := s.Lookup(msg)
	ttl := TTL
	// Bail out early if the record isn't found in the key store
	if record == nil || record.CName == "" {
		log.Println("No record in key store, returning nil")
		return nil
	}

	switch msg.Qtype {
	case dns.TypeNS:
		log.Println("Processing NS request")
		if msg.Name == s.domain {
			answers = append(answers, &dns.NS{
				Hdr: dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 300},
				Ns:  s.hostname,
			})
		}
	case dns.TypeSOA:
		log.Println("Processing SOA request")
		log.Println("  Domain is", s.domain)
		if msg.Name == s.domain {
			log.Println("  msg.Name == s.domain", msg.Name, s.domain)

			r := new(dns.SOA)
			r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 60}
			r.Ns = s.hostname
			r.Mbox = s.mbox
			r.Serial = uint32(time.Now().Unix())
			r.Refresh = 60
			r.Retry = 60
			r.Expire = 86400
			r.Minttl = 60
			answers = append(answers, r)
		} else {
			log.Println("  no match!", msg.Name, s.domain)
		}
	case dns.TypeA:
		log.Println("Processing A request")
		addr := record.IPv4PublicIP
		// bail if no ip address
		if addr == nil {
			log.Println("No ip address, returning nil")
			return nil
		}
		r := new(dns.A)
		r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl}
		r.A = addr
		answers = append(answers, r)
	case dns.TypeAAAA:
		log.Println("Processing AAAA request")
		addr := record.IPv6PublicIP
		// bail if no ip address
		if addr == nil {
			return nil
		}

		r := new(dns.AAAA)
		r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl}
		r.AAAA = addr
		answers = append(answers, r)
	case dns.TypeCNAME:
		log.Println("Processing CNAME request")
		r := new(dns.CNAME)
		r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: ttl}
		r.Target = msg.Name
		answers = append(answers, r)
	case dns.TypeMX:
		log.Println("Processing MX request")
		r := new(dns.MX)
		r.Hdr = dns.RR_Header{Name: msg.Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: ttl}
		r.Preference = 10
		r.Mx = msg.Name
		answers = append(answers, r)
	}
	return answers
}

// Lookup will locate the details in Redis for the fqdn, if not found
// lookup will try to locate a wildcard entry for the fqdn
func (s *RedisDNSServer) Lookup(msg dns.Question) *Record {
	log.Printf("LOOKUP: Looking for '%s'\n", msg.Name)
	str, err := s.redisClient.Get(msg.Name)
	log.Printf(" found str\n%s", str)
	var result Record

	// error indicates that the record was not found
	if err != nil {
		wildcard := wildCardHostName(msg.Name)
		log.Printf("No record for %s, trying wildcard %s\n", msg.Name, wildcard)

		domainDots := strings.Count(s.domain, ".") + 1
		msgDots := strings.Count(msg.Name, ".")
		if msgDots <= domainDots {
			log.Printf("msgDots <= domainDots returning nil")
			return nil
		}

		str, err = s.redisClient.Get(wildcard)
		if err != nil {
			log.Printf("No record for %s\n", wildcard)
			return nil
		}
	}
	json.Unmarshal([]byte(str), &result)
	return &result
}

func wildCardHostName(hostName string) string {
	nameParts := strings.SplitAfterN(hostName, ".", 2)
	return "*." + nameParts[1]
}

// SOA returns the Server of Authority record response
func (s *RedisDNSServer) SOA(msg dns.Question) dns.RR {
	r := new(dns.SOA)
	r.Hdr = dns.RR_Header{Name: s.domain, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 60}
	r.Ns = s.hostname
	r.Mbox = s.mbox
	r.Serial = uint32(time.Now().Unix())
	r.Refresh = 86400
	r.Retry = 7200
	r.Expire = 86400
	r.Minttl = 60
	return r
}
<|endoftext|>