<commit_before>package main

import (
	"fmt"
	"io"
)

// Parser represents a parser.
type Parser struct {
	s   *Scanner
	buf struct {
		tok  Token
		val  string
		size int
	}
}

// NewParser returns a new instance of Parser.
func NewParser(r io.Reader) *Parser {
	return &Parser{
		s: NewScanner(r),
	}
}

// Parse parses a Collection.
func (p *Parser) Parse() (*Collection, error) {
	col := &Collection{
		Games: make([]Game, 0),
	}

	for {
		tok, val := p.scanIgnoreWhitespace()
		if tok == EOF {
			break
		}

		if tok == IDENT {
			switch val {
			case "clrmamepro":
				p.unscan()

				info, err := p.parseFileInfo()
				if err != nil {
					return nil, err
				}

				col.FileInfo = info
			case "game":
				p.unscan()

				g, err := p.parseGame()
				if err != nil {
					return nil, err
				}

				col.Games = append(col.Games, g)
			}
		}
	}

	return col, nil
}

func (p *Parser) parseFileInfo() (FileInfo, error) {
	info := FileInfo{}

	var (
		token Token
		value string
	)

	token, value = p.scanIgnoreWhitespace()
	if token != IDENT || value != "clrmamepro" {
		return FileInfo{}, fmt.Errorf("unexpected token %s", value)
	}

	token, value = p.scanIgnoreWhitespace()
	if token != LEFTPAREN {
		return FileInfo{}, fmt.Errorf("unexpected token %s", value)
	}

	for {
		token, value = p.scanIgnoreWhitespace()
		if token == EOF || token == RIGHTPAREN {
			break
		}

		if token == IDENT {
			switch value {
			case "name":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return FileInfo{}, fmt.Errorf("unexpected token %s", v)
				}
				info.Name = v
			case "description":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return FileInfo{}, fmt.Errorf("unexpected token %s", v)
				}
				info.Description = v
			case "version":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return FileInfo{}, fmt.Errorf("unexpected token %s", v)
				}
				info.Version = v
			case "comment":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return FileInfo{}, fmt.Errorf("unexpected token %s", v)
				}
				info.Comment = v
			default:
				return FileInfo{}, fmt.Errorf("unexpected token %s", value)
			}
		}
	}

	return info, nil
}

func (p *Parser) parseGame() (Game, error) {
	game := Game{}

	var (
		token Token
		value string
	)

	token, value = p.scanIgnoreWhitespace()
	if token != IDENT || value != "game" {
		return Game{}, fmt.Errorf("unexpected token %s", value)
	}

	token, value = p.scanIgnoreWhitespace()
	if token != LEFTPAREN {
		return Game{}, fmt.Errorf("unexpected token %s", value)
	}

	for {
		token, value = p.scanIgnoreWhitespace()
		if token == EOF || token == RIGHTPAREN {
			break
		}

		if token == IDENT {
			switch value {
			case "name":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return Game{}, fmt.Errorf("unexpected token %s", v)
				}
				game.Name = v
			case "description":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return Game{}, fmt.Errorf("unexpected token %s", v)
				}
				game.Description = v
			case "serial":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return Game{}, fmt.Errorf("unexpected token %s", v)
				}
				game.Serial = v
			case "rom":
				p.unscan()
				r, err := p.parseROM()
				if err != nil {
					return Game{}, err
				}
				game.ROM = append(game.ROM, r)
			default:
				return Game{}, fmt.Errorf("unexpected token %s", value)
			}
		}
	}

	return game, nil
}

func (p *Parser) parseROM() (ROM, error) {
	rom := ROM{}

	var (
		token Token
		value string
	)

	token, value = p.scanIgnoreWhitespace()
	if token != IDENT || value != "rom" {
		return ROM{}, fmt.Errorf("unexpected token %s", value)
	}

	token, value = p.scanIgnoreWhitespace()
	if token != LEFTPAREN {
		return ROM{}, fmt.Errorf("unexpected token %s", value)
	}

	for {
		token, value = p.scanIgnoreWhitespace()
		if token == EOF || token == RIGHTPAREN {
			break
		}

		if token == IDENT {
			switch value {
			case "name":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return ROM{}, fmt.Errorf("unexpected token %s", v)
				}
				rom.Name = v
			case "size":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return ROM{}, fmt.Errorf("unexpected token %s", v)
				}
				rom.Size = v
			case "crc":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return ROM{}, fmt.Errorf("unexpected token %s", v)
				}
				rom.CRC = v
			case "md5":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return ROM{}, fmt.Errorf("unexpected token %s", v)
				}
				rom.MD5 = v
			case "sha1":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return ROM{}, fmt.Errorf("unexpected token %s", v)
				}
				rom.SHA1 = v
			case "flags":
				t, v := p.scanIgnoreWhitespace()
				if t != IDENT {
					return ROM{}, fmt.Errorf("unexpected token %s", v)
				}
				rom.Flags = v
			}
		}
	}

	return rom, nil
}

func (p *Parser) scanIgnoreWhitespace() (Token, string) {
	tok, val := p.scan()
	if tok == WS {
		return p.scan()
	}
	return tok, val
}

func (p *Parser) scan() (tok Token, val string) {
	if p.buf.size != 0 {
		p.buf.size = 0
		return p.buf.tok, p.buf.val
	}

	tok, val = p.s.Scan()

	p.buf.tok, p.buf.val = tok, val

	return
}

func (p *Parser) unscan() {
	p.buf.size = 1
}
<commit_msg>Refactor Parser<commit_after>package main

import (
	"fmt"
	"io"
)

// Parser represents a parser.
type Parser struct {
	s   *Scanner
	buf struct {
		tok  Token
		val  string
		size int
	}
}

// NewParser returns a new instance of Parser.
func NewParser(r io.Reader) *Parser {
	return &Parser{
		s: NewScanner(r),
	}
}

// Parse parses a Collection.
func (p *Parser) Parse() (*Collection, error) {
	col := &Collection{
		Games: make([]Game, 0),
	}

	for {
		tok, val := p.scanIgnoreWhitespace()
		if tok == EOF {
			break
		}

		if tok == IDENT {
			switch val {
			case "clrmamepro":
				p.unscan()

				info, err := p.parseFileInfo()
				if err != nil {
					return nil, err
				}

				col.FileInfo = info
			case "game":
				p.unscan()

				g, err := p.parseGame()
				if err != nil {
					return nil, err
				}

				col.Games = append(col.Games, g)
			}
		}
	}

	return col, nil
}

func (p *Parser) parseFileInfo() (FileInfo, error) {
	info := FileInfo{}

	var (
		token Token
		value string
	)

	token, value = p.scanIgnoreWhitespace()
	if token != IDENT || value != "clrmamepro" {
		return FileInfo{}, errUnexpectedToken(value)
	}

	token, value = p.scanIgnoreWhitespace()
	if token != LEFTPAREN {
		return FileInfo{}, errUnexpectedToken(value)
	}

	for {
		token, value = p.scanIgnoreWhitespace()
		if token == EOF || token == RIGHTPAREN {
			break
		} else if token != IDENT {
			continue
		}

		t, v := p.scanIgnoreWhitespace()
		if t != IDENT {
			return FileInfo{}, errUnexpectedToken(v)
		}

		switch value {
		case "name":
			info.Name = v
		case "description":
			info.Description = v
		case "version":
			info.Version = v
		case "comment":
			info.Comment = v
		default:
			return FileInfo{}, errUnexpectedToken(value)
		}
	}

	return info, nil
}

func (p *Parser) parseGame() (Game, error) {
	game := Game{}

	var (
		token Token
		value string
	)

	token, value = p.scanIgnoreWhitespace()
	if token != IDENT || value != "game" {
		return Game{}, errUnexpectedToken(value)
	}

	token, value = p.scanIgnoreWhitespace()
	if token != LEFTPAREN {
		return Game{}, errUnexpectedToken(value)
	}

	for {
		token, value = p.scanIgnoreWhitespace()
		if token == EOF || token == RIGHTPAREN {
			break
		} else if token != IDENT {
			continue
		}

		if value == "rom" {
			p.unscan()
			r, err := p.parseROM()
			if err != nil {
				return Game{}, err
			}
			game.ROM = append(game.ROM, r)
		} else {
			t, v := p.scanIgnoreWhitespace()
			if t != IDENT {
				return Game{}, errUnexpectedToken(v)
			}

			switch value {
			case "name":
				game.Name = v
			case "description":
				game.Description = v
			case "serial":
				game.Serial = v
			default:
				return Game{}, errUnexpectedToken(value)
			}
		}
	}

	return game, nil
}

func (p *Parser) parseROM() (ROM, error) {
	rom := ROM{}

	var (
		token Token
		value string
	)

	token, value = p.scanIgnoreWhitespace()
	if token != IDENT || value != "rom" {
		return ROM{}, errUnexpectedToken(value)
	}

	token, value = p.scanIgnoreWhitespace()
	if token != LEFTPAREN {
		return ROM{}, errUnexpectedToken(value)
	}

	for {
		token, value = p.scanIgnoreWhitespace()
		if token == EOF || token == RIGHTPAREN {
			break
		} else if token != IDENT {
			continue
		}

		t, v := p.scanIgnoreWhitespace()
		if t != IDENT {
			return ROM{}, errUnexpectedToken(v)
		}

		switch value {
		case "name":
			rom.Name = v
		case "size":
			rom.Size = v
		case "crc":
			rom.CRC = v
		case "md5":
			rom.MD5 = v
		case "sha1":
			rom.SHA1 = v
		case "flags":
			rom.Flags = v
		default:
			return ROM{}, errUnexpectedToken(value)
		}
	}

	return rom, nil
}

func (p *Parser) scanIgnoreWhitespace() (Token, string) {
	tok, val := p.scan()
	if tok == WS {
		return p.scan()
	}
	return tok, val
}

func (p *Parser) scan() (tok Token, val string) {
	if p.buf.size != 0 {
		p.buf.size = 0
		return p.buf.tok, p.buf.val
	}

	tok, val = p.s.Scan()

	p.buf.tok, p.buf.val = tok, val

	return
}

func (p *Parser) unscan() {
	p.buf.size = 1
}

func errUnexpectedToken(t string) error {
	return fmt.Errorf("unexpected token  %s", t)
}
<|endoftext|>


<commit_before>package sprite_sass

import (
	"bytes"
	"fmt"
	"io"
	"log"
)

func init() {
	log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime)
}

type Replace struct {
	Start, End int
	Value      []byte
}

type Parser struct {
	Index, shift         int
	Chop                 []Replace
	Pwd, Input, ImageDir string
	Includes             []string
	Items                []Item
	Output               []byte
	Sprites              map[string]ImageList
	Vars                 map[string]string
}

// Parser reads the tokens from the lexer and performs
// conversions and/or substitutions for sprite*() calls.
//
// Parser creates a map of all variables and sprites
// (created via sprite-map calls).
func (p *Parser) Start(in io.Reader, pkgdir string) []byte {
	p.Vars = make(map[string]string)
	p.Sprites = make(map[string]ImageList)

	if p.ImageDir == "" {
		p.ImageDir = pkgdir
	}
	buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
	buf.ReadFrom(in)

	tokens, input, err := p.start(pkgdir, string(buf.Bytes()))
	p.Input = input
	p.Items = tokens
	if err != nil {
		panic(err)
	}
	var (
		def, cmd string
	)
	for i := 0; i < len(tokens); i = i + 1 {
		token := tokens[i]
		last := i
		// Generate list of vars
		if token.Type == VAR {
			def = fmt.Sprintf("%s", token)
			val := ""
			nested := false
			for {
				i++
				token = tokens[i]
				switch token.Type {
				case LPAREN:
					nested = true
				case RPAREN:
					nested = false
				case CMD:
					// Changing the behavior of CMD!
					cmd = fmt.Sprintf("%s", token)
					val += cmd
				case FILE:
					i = p.File(cmd, last, i)
					def = ""
					cmd = ""
				case SUB:
					// fmt.Println("SUB:", tokens[i-1], tokens[i], tokens[i+1])
					// fmt.Println(p.Input[tokens[i-20].Pos:tokens[i+20].Pos])
					// Cowardly give up and hope these variables do not matter
					// Cases:
					// - @for $i from 1 through $variable
					// - $variable: ($i - 1)
					// -
					fallthrough
				default:
					// fmt.Printf("Default: %s\n", token)
					val += fmt.Sprintf("%s", token)
				}

				if !nested && tokens[i].Type != CMD {
					break
				}
			}
			if def != "" {
				p.Vars[def] = val
			}
			//Replace subsitution tokens
		} else if token.Type == SUB {
			repl := ""
			switch cmd {
			case "sprite":
				//Capture sprite
				sprite := p.Sprites[fmt.Sprintf("%s", token)]
				//Capture filename
				i++
				name := fmt.Sprintf("%s", tokens[i])
				repl = sprite.CSS(name)

				p.Mark(tokens[i-3].Pos, tokens[i+2].Pos, repl)
				tokens = append(tokens[:i-3], tokens[i:]...)
				i = i - 3
				def = ""
				cmd = ""
			case "sprite-height":
				sprite := p.Sprites[fmt.Sprintf("%s", token)]
				repl = fmt.Sprintf("height: %dpx;",
					sprite.ImageHeight(tokens[i+1].String()))
				// Walk forward to file name
				i++
				p.Mark(tokens[i-4].Pos, tokens[i+3].Pos, repl)
				tokens = append(tokens[:i-4], tokens[i:]...)
				i = i - 4
				def = ""
				cmd = ""
			case "sprite-width":
				sprite := p.Sprites[fmt.Sprintf("%s", token)]
				repl = fmt.Sprintf("width: %dpx;",
					sprite.ImageWidth(tokens[i+1].String()))
				// Walk forward to file name
				i++
				p.Mark(tokens[i-4].Pos, tokens[i+3].Pos, repl)
				tokens = append(tokens[:i-4], tokens[i:]...)
				i = i - 4
				def = ""
				cmd = ""
			case "sprite-dimensions":
				sprite := p.Sprites[fmt.Sprintf("%s", token)]
				repl = sprite.Dimensions(tokens[i+1].String())
				// Walk forward to file name
				i++
				p.Mark(tokens[i-4].Pos, tokens[i+3].Pos, repl)
				tokens = append(tokens[:i-4], tokens[i:]...)
				i = i - 4
				def = ""
				cmd = ""
			default:
				tokens[i].Value = p.Vars[token.Value]
			}
		} else if token.Type == CMD {
			// Sync the index during the refactor
			p.Index = i
			cmd = fmt.Sprintf("%s", token)
			switch token.Value {
			case "inline-image":
				cmd = ""
				p.Mixin()
			default:
				//log.Fatal("Danger will robinson")
			}
			i = p.Index
		}
	}
	// I don't recall the point of this, but process
	// will result in whitespace errors in the output.
	// p.Output = process(p.Input, p.Items, 0)
	p.Output = []byte(p.Input)
	p.Replace()
	// DEBUG
	for _, item := range p.Items {
		_ = item
		// fmt.Printf("%s %s\n", item.Type, item)
	}
	return p.Output
}

// Mixin processes tokens in the format @include mixin(args...)
// and perform requested actions.
func (p *Parser) Mixin() {

	// Commands always end in ); else panic
	start := p.Index
	cmd := p.Items[start]

	var file Item
	for {
		cur := p.Items[p.Index]
		if cur.Type == RPAREN {
			p.Index++
			if p.Items[p.Index].Type != SEMIC {
				f, l := p.Items[start].Pos, p.Items[p.Index].Pos
				log.Fatal("Commands must end with semicolon",
					fmt.Sprintf(p.Input[f:l]))
			} else {
				break
			}
		} else if cur.Type == FILE {
			file = cur
		}
		p.Index++
	}
	if file.Type != FILE {
		log.Fatal("File for command was not found")
	}
	if cmd.Value == "inline-image" {
		img := ImageList{}
		img.Decode(p.ImageDir + "/" + file.Value)
		repl := img.Inline()
		p.Index-- // Preserve the final semic
		p.Mark(cmd.Pos-1, p.Items[p.Index].Pos+1, repl)
	}
}

// Replace iterates through the list of substrings to
// cut or replace, adjusting for shift of the output
// buffer as a result of these ops.
func (p *Parser) Replace() {

	for _, c := range p.Chop {

		begin := c.Start - p.shift
		end := c.End - p.shift
		// fmt.Println(string(p.Input[begin:end]), "~>`"+string(c.Value)+"`")
		// fmt.Println("*******")
		// Adjust shift for number of bytes deleted and inserted
		p.shift += end - begin
		p.shift -= len(c.Value)
		suf := append(c.Value, p.Output[end:]...)

		p.Output = append(p.Output[:begin], suf...)
	}
}

// Mark segments of the input string for future deletion.
func (p *Parser) Mark(start, end int, val string) {
	// fmt.Println("Mark:", string(p.Input[start:end]), "~")
	p.Chop = append(p.Chop, Replace{start, end, []byte(val)})
}

// Processes file which usually mean cutting some of the input
// text.
func (p *Parser) File(cmd string, start, end int) int {
	first := p.Items[start]
	item := p.Items[end]
	// Find the next newline, failing that find the semicolon
	i := end
	if cmd == "sprite-map" {
		for ; p.Items[i].Type != RPAREN; i++ {
		}
		i = i - 1
		// Verify that the statement ends with semicolon
		interest := p.Items[i+3]
		// Mark this area for deletion, since doing so now would
		// invalidate all subsequent tokens positions.
		p.Mark(first.Pos, interest.Pos, "")
		imgs := ImageList{}
		glob := fmt.Sprintf("%s", item)
		name := fmt.Sprintf("%s", p.Items[start])
		imgs.Decode(p.ImageDir + "/" + glob)
		imgs.Vertical = true
		imgs.Combine()
		p.Sprites[name] = imgs
		//TODO: Generate filename
		//imgs.Export("generated.png")
	}
	return i
}

func process(in string, items []Item, pos int) []byte {

	var out []byte
	l := len(items)

	if pos >= len(in) {
		return []byte("")
	}

	// TODO: There's an error where items[1] has an invalid
	// position.
	if l > 1 && items[1].Pos > items[0].Pos {
		out = append(out, in[items[0].Pos:items[1].Pos]...)
		out = append(out, process(in, items[1:], pos)...)
	} else {
		out = append(out, in[items[0].Pos:]...)
	}

	return out
}

// parser recrusively resolves all imports and tokenizes the
// input string
func (p *Parser) start(pwd, input string) ([]Item, string, error) {

	var (
		status    []Item
		importing bool
		output    []byte
		pos       int
		last      *Item
	)

	lex := New(func(lex *Lexer) StateFn {
		return lex.Action()
	}, input)

	for {
		item := lex.Next()
		err := item.Error()
		//fmt.Println(item.Type, item.Value)
		if err != nil {
			return nil, string(output),
				fmt.Errorf("Error: %v (pos %d)", err, item.Pos)
		}
		switch item.Type {
		case ItemEOF:
			output = append(output, input[pos:]...)
			return status, string(output), nil
		case IMPORT:
			output = append(output, input[pos:item.Pos]...)
			last = item
			importing = true
		case INCLUDE, CMT:
			output = append(output, input[pos:item.Pos]...)
			pos = item.Pos
			status = append(status, *item)
		default:
			if importing {

				pwd, contents, err := p.ImportPath(pwd, fmt.Sprintf("%s", *item))

				if err != nil {
					log.Fatal(err)
				}
				//Eat the semicolon
				item := lex.Next()
				pos = item.Pos + len(item.Value)
				if item.Type != SEMIC {
					panic("@import statement must be followed by ;")
				}

				moreTokens, moreOutput, err := p.start(
					pwd,
					contents)
				// Lexer needs to be adjusted for current
				// position of end of @import
				for i, _ := range moreTokens {
					moreTokens[i].Pos += last.Pos
				}

				if err != nil {
					panic(err)
				}
				output = append(output, moreOutput...)
				status = append(status, moreTokens...)
				importing = false
			} else {
				output = append(output, input[pos:item.Pos]...)
				pos = item.Pos
				status = append(status, *item)
			}
		}
	}
}
<commit_msg>Comment updates<commit_after>package sprite_sass

import (
	"bytes"
	"fmt"
	"io"
	"log"
)

func init() {
	log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime)
}

type Replace struct {
	Start, End int
	Value      []byte
}

type Parser struct {
	Index, shift         int
	Chop                 []Replace
	Pwd, Input, ImageDir string
	Includes             []string
	Items                []Item
	Output               []byte
	Sprites              map[string]ImageList
	Vars                 map[string]string
}

// Parser reads the tokens from the lexer and performs
// conversions and/or substitutions for sprite*() calls.
//
// Parser creates a map of all variables and sprites
// (created via sprite-map calls).
func (p *Parser) Start(in io.Reader, pkgdir string) []byte {
	p.Vars = make(map[string]string)
	p.Sprites = make(map[string]ImageList)

	if p.ImageDir == "" {
		p.ImageDir = pkgdir
	}
	buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
	buf.ReadFrom(in)

	tokens, input, err := p.start(pkgdir, string(buf.Bytes()))
	p.Input = input
	p.Items = tokens
	if err != nil {
		panic(err)
	}
	var (
		def, cmd string
	)
	for i := 0; i < len(tokens); i = i + 1 {
		token := tokens[i]
		last := i
		// Generate list of vars
		if token.Type == VAR {
			def = fmt.Sprintf("%s", token)
			val := ""
			nested := false
			for {
				i++
				token = tokens[i]
				switch token.Type {
				case LPAREN:
					nested = true
				case RPAREN:
					nested = false
				case CMD:
					// Changing the behavior of CMD!
					cmd = fmt.Sprintf("%s", token)
					val += cmd
				case FILE:
					i = p.File(cmd, last, i)
					def = ""
					cmd = ""
				case SUB:
					// fmt.Println("SUB:", tokens[i-1], tokens[i], tokens[i+1])
					// fmt.Println(p.Input[tokens[i-20].Pos:tokens[i+20].Pos])
					// Cowardly give up and hope these variables do not matter
					// Cases:
					// - @for $i from 1 through $variable
					// - $variable: ($i - 1)
					// -
					fallthrough
				default:
					// fmt.Printf("Default: %s\n", token)
					val += fmt.Sprintf("%s", token)
				}

				if !nested && tokens[i].Type != CMD {
					break
				}
			}
			if def != "" {
				p.Vars[def] = val
			}
			//Replace subsitution tokens
		} else if token.Type == SUB {
			repl := ""
			switch cmd {
			case "sprite":
				//Capture sprite
				sprite := p.Sprites[fmt.Sprintf("%s", token)]
				//Capture filename
				i++
				name := fmt.Sprintf("%s", tokens[i])
				repl = sprite.CSS(name)

				p.Mark(tokens[i-3].Pos, tokens[i+2].Pos, repl)
				tokens = append(tokens[:i-3], tokens[i:]...)
				i = i - 3
				def = ""
				cmd = ""
			case "sprite-height":
				sprite := p.Sprites[fmt.Sprintf("%s", token)]
				repl = fmt.Sprintf("height: %dpx;",
					sprite.ImageHeight(tokens[i+1].String()))
				// Walk forward to file name
				i++
				p.Mark(tokens[i-4].Pos, tokens[i+3].Pos, repl)
				tokens = append(tokens[:i-4], tokens[i:]...)
				i = i - 4
				def = ""
				cmd = ""
			case "sprite-width":
				sprite := p.Sprites[fmt.Sprintf("%s", token)]
				repl = fmt.Sprintf("width: %dpx;",
					sprite.ImageWidth(tokens[i+1].String()))
				// Walk forward to file name
				i++
				p.Mark(tokens[i-4].Pos, tokens[i+3].Pos, repl)
				tokens = append(tokens[:i-4], tokens[i:]...)
				i = i - 4
				def = ""
				cmd = ""
			case "sprite-dimensions":
				sprite := p.Sprites[fmt.Sprintf("%s", token)]
				repl = sprite.Dimensions(tokens[i+1].String())
				// Walk forward to file name
				i++
				p.Mark(tokens[i-4].Pos, tokens[i+3].Pos, repl)
				tokens = append(tokens[:i-4], tokens[i:]...)
				i = i - 4
				def = ""
				cmd = ""
			default:
				tokens[i].Value = p.Vars[token.Value]
			}
		} else if token.Type == CMD {
			// Sync the index during the refactor
			p.Index = i
			cmd = fmt.Sprintf("%s", token)
			switch token.Value {
			case "inline-image":
				cmd = ""
				p.Mixin()
			default:
				//log.Fatal("Danger will robinson")
			}
			i = p.Index
		}
	}
	// I don't recall the point of this, but process
	// will result in whitespace errors in the output.
	// p.Output = process(p.Input, p.Items, 0)
	p.Output = []byte(p.Input)
	p.Replace()
	// DEBUG
	for _, item := range p.Items {
		_ = item
		// fmt.Printf("%s %s\n", item.Type, item)
	}
	return p.Output
}

// Mixin processes tokens in the format @include mixin(args...)
// and perform requested actions.
func (p *Parser) Mixin() {

	// Commands always end in ); else panic
	start := p.Index
	cmd := p.Items[start]

	var file Item
	for {
		cur := p.Items[p.Index]
		if cur.Type == RPAREN {
			p.Index++
			if p.Items[p.Index].Type != SEMIC {
				f, l := p.Items[start].Pos, p.Items[p.Index].Pos
				log.Fatal("Commands must end with semicolon",
					fmt.Sprintf(p.Input[f:l]))
			} else {
				break
			}
		} else if cur.Type == FILE {
			file = cur
		}
		p.Index++
	}
	if file.Type != FILE {
		log.Fatal("File for command was not found")
	}
	if cmd.Value == "inline-image" {
		img := ImageList{}
		img.Decode(p.ImageDir + "/" + file.Value)
		repl := img.Inline()
		p.Index-- // Preserve the final semic
		p.Mark(cmd.Pos-1, p.Items[p.Index].Pos+1, repl)
	}
}

// Replace iterates through the list of substrings to
// cut or replace, adjusting for shift of the output
// buffer as a result of these ops.
func (p *Parser) Replace() {

	for _, c := range p.Chop {

		begin := c.Start - p.shift
		end := c.End - p.shift
		// fmt.Println(string(p.Input[begin:end]), "~>`"+string(c.Value)+"`")
		// fmt.Println("*******")
		// Adjust shift for number of bytes deleted and inserted
		p.shift += end - begin
		p.shift -= len(c.Value)
		suf := append(c.Value, p.Output[end:]...)

		p.Output = append(p.Output[:begin], suf...)
	}
}

// Mark segments of the input string for future deletion.
func (p *Parser) Mark(start, end int, val string) {
	// fmt.Println("Mark:", string(p.Input[start:end]), "~")
	p.Chop = append(p.Chop, Replace{start, end, []byte(val)})
}

// Processes file which usually mean cutting some of the input
// text.
func (p *Parser) File(cmd string, start, end int) int {
	first := p.Items[start]
	item := p.Items[end]
	// Find the next newline, failing that find the semicolon
	i := end
	if cmd == "sprite-map" {
		for ; p.Items[i].Type != RPAREN; i++ {
		}
		i = i - 1
		// Verify that the statement ends with semicolon
		interest := p.Items[i+3]
		// Mark this area for deletion, since doing so now would
		// invalidate all subsequent tokens positions.
		p.Mark(first.Pos, interest.Pos, "")
		imgs := ImageList{}
		glob := fmt.Sprintf("%s", item)
		name := fmt.Sprintf("%s", p.Items[start])
		imgs.Decode(p.ImageDir + "/" + glob)
		imgs.Vertical = true
		imgs.Combine()
		p.Sprites[name] = imgs
		//TODO: Generate filename
		//imgs.Export("generated.png")
	}
	return i
}

func process(in string, items []Item, pos int) []byte {

	var out []byte
	l := len(items)

	if pos >= len(in) {
		return []byte("")
	}

	// TODO: There's an error where items[1] has an invalid
	// position.
	if l > 1 && items[1].Pos > items[0].Pos {
		out = append(out, in[items[0].Pos:items[1].Pos]...)
		out = append(out, process(in, items[1:], pos)...)
	} else {
		out = append(out, in[items[0].Pos:]...)
	}

	return out
}

// start recursively resolves all imports.  It lexes the input
// adding the tokens to the Parser object.
// TODO: Convert this to byte slice in/out
func (p *Parser) start(pwd, input string) ([]Item, string, error) {

	var (
		status    []Item
		importing bool
		output    []byte
		pos       int
		last      *Item
	)

	lex := New(func(lex *Lexer) StateFn {
		return lex.Action()
	}, input)

	for {
		item := lex.Next()
		err := item.Error()
		//fmt.Println(item.Type, item.Value)
		if err != nil {
			return nil, string(output),
				fmt.Errorf("Error: %v (pos %d)", err, item.Pos)
		}
		switch item.Type {
		case ItemEOF:
			output = append(output, input[pos:]...)
			return status, string(output), nil
		case IMPORT:
			output = append(output, input[pos:item.Pos]...)
			last = item
			importing = true
		case INCLUDE, CMT:
			output = append(output, input[pos:item.Pos]...)
			pos = item.Pos
			status = append(status, *item)
		default:
			if importing {

				pwd, contents, err := p.ImportPath(pwd, fmt.Sprintf("%s", *item))

				if err != nil {
					log.Fatal(err)
				}
				//Eat the semicolon
				item := lex.Next()
				pos = item.Pos + len(item.Value)
				if item.Type != SEMIC {
					panic("@import statement must be followed by ;")
				}

				moreTokens, moreOutput, err := p.start(
					pwd,
					contents)
				// Lexer needs to be adjusted for current
				// position of end of @import
				for i, _ := range moreTokens {
					moreTokens[i].Pos += last.Pos
				}

				if err != nil {
					panic(err)
				}
				output = append(output, moreOutput...)
				status = append(status, moreTokens...)
				importing = false
			} else {
				output = append(output, input[pos:item.Pos]...)
				pos = item.Pos
				status = append(status, *item)
			}
		}
	}
}
<|endoftext|>


<commit_before>package logpeck

import (
	"errors"
	log "github.com/Sirupsen/logrus"
	"sync"
	"time"
)

type Pecker struct {
	logTasks map[string]*LogTask
	nametopath map[string] string
	mu       sync.Mutex
	db       *DB
	stop     bool
}

func NewPecker(db *DB) (*Pecker, error) {
	pecker := &Pecker{
		logTasks: make(map[string]*LogTask),
		nametopath :make(map[string] string),
		db:       db,
		stop:     true,
	}
	err := pecker.restorePeckTasks(db)
	if err != nil {
		return nil, err
	}
	return pecker, nil
}

func (p *Pecker) restorePeckTasks(db *DB) error {
	defer LogExecTime(time.Now(), "Restore PeckTaskConfig")
	configs, err := p.db.GetAllConfigs()
	if err != nil {
		return err
	}
	for i, config := range configs {
		stat, _ := p.db.GetStat(config.LogPath, config.Name)
		p.AddPeckTask(&config, stat)
		log.Infof("[Pecker] Restore PeckTask[%d] : %s", i, config)
	}
	return nil
}

func (p *Pecker) AddPeckTask(config *PeckTaskConfig, stat *PeckTaskStat) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	log.Infof("[Pecker] AddPeckTask %s", *config)
	log_path := config.LogPath
	log_task, ok := p.logTasks[log_path]
	if !ok {
		if _, ok2 := p.nametopath[config.Name];ok2{
			return errors.New("Peck task name already exist")
		} else{
		    log_task = NewLogTask(log_path)
		    p.logTasks[log_path] = log_task
		    p.nametopath[config.Name]=log_path
		}
 	}

	if log_task.Exist(config) {
		return errors.New("Peck task already exist")
	}

	task, err := NewPeckTask(config, stat)
	if err != nil {
		return err
	}

	{
		err1 := db.SaveConfig(&task.Config)
		err2 := db.SaveStat(&task.Stat)
		if err1 != nil || err2 != nil {
			panic(err1.Error() + " " + err2.Error())
		}
	}
	err = log_task.AddPeckTask(task)
	if err != nil {
		// AddPeckTask must be successful
		panic(err)
	}
	return nil
}

func (p *Pecker) UpdatePeckTask(config *PeckTaskConfig) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	log.Infof("[Pecker] UpdatePeckTask %s", *config)
	if _, ok := p.nametopath[config.Name];!ok{
		return errors.New("Peck task name not exist")
	}
	log_path := p.nametopath[config.Name]
	log_task, ok := p.logTasks[log_path]
	if !ok {
		return errors.New("Peck task not exist")
	}

	if !log_task.Exist(config) {
		return errors.New("Peck task not exist")
	}

	task, err := NewPeckTask(config, nil)
	if err != nil {
		return err
	}

	{
		err := db.SaveConfig(&task.Config)
		if err != nil {
			panic(err.Error())
		}
	}
	err = log_task.UpdatePeckTask(task)
	if err != nil {
		// UpdatePeckTask must be successful
		panic(err)
	}
	return nil
}

func (p *Pecker) RemovePeckTask(config *PeckTaskConfig) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	if _, ok := p.nametopath[config.Name];!ok{
		return errors.New("Peck task name not exist")
	}
	log_path := p.nametopath[config.Name]
	log_task, ok := p.logTasks[log_path]
	if !ok {
		return errors.New("Task not exist")
	}

	{
		log.Infof("[Pecker] Remove PeckTask try clean db: %s", config)
		err1 := db.RemoveConfig(log_path, config.Name)
		err2 := db.RemoveStat(log_path, config.Name)
		if err1 != nil || err2 != nil {
			panic(err1.Error() + " " + err2.Error())
		}
	}

	log_task.RemovePeckTask(config)
	delete(p.nametopath,config.Name)
	if log_task.Empty() {
		log_task.Close()
		delete(p.logTasks, log_path)
	}
	log.Infof("[Pecker] Remove PeckTask finish: %s", config)
	return nil
}

func (p *Pecker) ListPeckTask() ([]PeckTaskConfig, error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	configs, err := p.db.GetAllConfigs()
	if err != nil {
		return nil, err
	}
	return configs, nil
}

func (p *Pecker) StartPeckTask(config *PeckTaskConfig) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	log_path := p.nametopath[config.Name]
	log_task, ok := p.logTasks[log_path]
	if !ok {
		return errors.New("Task not exist")
	}

	{
		// Try update peck task stat in boltdb
		// stat, err := db.GetStat(config.LogPath, config.Name)
		stat, err := db.GetStat(log_path, config.Name)
		if err != nil {
			return err
		}
		if !stat.Stop {
			return errors.New("Task already started")
		}
		stat.Stop = false
		err = db .SaveStat(stat)
	}
	if log_task.IsStop() {
		log_task.Start()
	}

	return log_task.StartPeckTask(config)
}

func (p *Pecker) StopPeckTask(config *PeckTaskConfig) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	if _, ok := p.nametopath[config.Name];!ok{
		return errors.New("Peck task name not exist")
	}
	log_path := p.nametopath[config.Name]
	log_task, ok := p.logTasks[log_path]
	if !ok {
			return errors.New("Task not exist")
	}

	{
		// Try update peck task stat in boltdb
		stat, err := db.GetStat(log_path, config.Name)
		if err != nil {
			return err
		}
		if stat.Stop {
			return errors.New("Task already stopped")
		}
		stat.Stop = true
		err = db.SaveStat(stat)
	}

	return log_task.StopPeckTask(config)
}

func (p *Pecker) Start() error {
	p.mu.Lock()
	defer p.mu.Unlock()
	if !p.stop {
		return errors.New("Pecker already started")
	}
	for path, logTask := range p.logTasks {
		log.Infof("[Pecker] Start LogTask %s", path)
		logTask.Start()
	}
	return nil
}

func (p *Pecker) GetStat() *PeckerStat {
	p.mu.Lock()
	defer p.mu.Unlock()
	return nil
}

<commit_msg>Modify format<commit_after>package logpeck

import (
	"errors"
	log "github.com/Sirupsen/logrus"
	"sync"
	"time"
)

type Pecker struct {
	logTasks   map[string]*LogTask
	nameToPath map[string]string
	mu         sync.Mutex
	db         *DB
	stop       bool
}

func NewPecker(db *DB) (*Pecker, error) {
	pecker := &Pecker{
		logTasks:   make(map[string]*LogTask),
		nameToPath: make(map[string]string),
		db:         db,
		stop:       true,
	}
	err := pecker.restorePeckTasks(db)
	if err != nil {
		return nil, err
	}
	return pecker, nil
}

func (p *Pecker) restorePeckTasks(db *DB) error {
	defer LogExecTime(time.Now(), "Restore PeckTaskConfig")
	configs, err := p.db.GetAllConfigs()
	if err != nil {
		return err
	}
	for i, config := range configs {
		stat, _ := p.db.GetStat(config.LogPath, config.Name)
		p.AddPeckTask(&config, stat)
		log.Infof("[Pecker] Restore PeckTask[%d] : %s", i, config)
	}
	return nil
}

func (p *Pecker) AddPeckTask(config *PeckTaskConfig, stat *PeckTaskStat) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	log.Infof("[Pecker] AddPeckTask %s", *config)
	log_path := config.LogPath
	log_task, ok := p.logTasks[log_path]
	if !ok {
		if _, ok2 := p.nameToPath[config.Name]; ok2 {
			return errors.New("Peck task name already exist")
		} else {
			log_task = NewLogTask(log_path)
			p.logTasks[log_path] = log_task
			p.nameToPath[config.Name] = log_path
		}
	}

	if log_task.Exist(config) {
		return errors.New("Peck task already exist")
	}

	task, err := NewPeckTask(config, stat)
	if err != nil {
		return err
	}

	{
		err1 := db.SaveConfig(&task.Config)
		err2 := db.SaveStat(&task.Stat)
		if err1 != nil || err2 != nil {
			panic(err1.Error() + " " + err2.Error())
		}
	}
	err = log_task.AddPeckTask(task)
	if err != nil {
		// AddPeckTask must be successful
		panic(err)
	}
	return nil
}

func (p *Pecker) UpdatePeckTask(config *PeckTaskConfig) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	log.Infof("[Pecker] UpdatePeckTask %s", *config)
	if _, ok := p.nameToPath[config.Name]; !ok {
		return errors.New("Peck task name not exist")
	}
	log_path := p.nameToPath[config.Name]
	log_task, ok := p.logTasks[log_path]
	if !ok {
		return errors.New("Peck task not exist")
	}

	if !log_task.Exist(config) {
		return errors.New("Peck task not exist")
	}

	task, err := NewPeckTask(config, nil)
	if err != nil {
		return err
	}

	{
		err := db.SaveConfig(&task.Config)
		if err != nil {
			panic(err.Error())
		}
	}
	err = log_task.UpdatePeckTask(task)
	if err != nil {
		// UpdatePeckTask must be successful
		panic(err)
	}
	return nil
}

func (p *Pecker) RemovePeckTask(config *PeckTaskConfig) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	if _, ok := p.nameToPath[config.Name]; !ok {
		return errors.New("Peck task name not exist")
	}
	log_path := p.nameToPath[config.Name]
	log_task, ok := p.logTasks[log_path]
	if !ok {
		return errors.New("Task not exist")
	}

	{
		log.Infof("[Pecker] Remove PeckTask try clean db: %s", config)
		err1 := db.RemoveConfig(log_path, config.Name)
		err2 := db.RemoveStat(log_path, config.Name)
		if err1 != nil || err2 != nil {
			panic(err1.Error() + " " + err2.Error())
		}
	}

	log_task.RemovePeckTask(config)
	delete(p.nameToPath, config.Name)
	if log_task.Empty() {
		log_task.Close()
		delete(p.logTasks, log_path)
	}
	log.Infof("[Pecker] Remove PeckTask finish: %s", config)
	return nil
}

func (p *Pecker) ListPeckTask() ([]PeckTaskConfig, error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	configs, err := p.db.GetAllConfigs()
	if err != nil {
		return nil, err
	}
	return configs, nil
}

func (p *Pecker) StartPeckTask(config *PeckTaskConfig) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	log_path := p.nameToPath[config.Name]
	log_task, ok := p.logTasks[log_path]
	if !ok {
		return errors.New("Task not exist")
	}

	{
		// Try update peck task stat in boltdb
		// stat, err := db.GetStat(config.LogPath, config.Name)
		stat, err := db.GetStat(log_path, config.Name)
		if err != nil {
			return err
		}
		if !stat.Stop {
			return errors.New("Task already started")
		}
		stat.Stop = false
		err = db.SaveStat(stat)
	}
	if log_task.IsStop() {
		log_task.Start()
	}

	return log_task.StartPeckTask(config)
}

func (p *Pecker) StopPeckTask(config *PeckTaskConfig) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	if _, ok := p.nameToPath[config.Name]; !ok {
		return errors.New("Peck task name not exist")
	}
	log_path := p.nameToPath[config.Name]
	log_task, ok := p.logTasks[log_path]
	if !ok {
		return errors.New("Task not exist")
	}

	{
		// Try update peck task stat in boltdb
		stat, err := db.GetStat(log_path, config.Name)
		if err != nil {
			return err
		}
		if stat.Stop {
			return errors.New("Task already stopped")
		}
		stat.Stop = true
		err = db.SaveStat(stat)
	}

	return log_task.StopPeckTask(config)
}

func (p *Pecker) Start() error {
	p.mu.Lock()
	defer p.mu.Unlock()
	if !p.stop {
		return errors.New("Pecker already started")
	}
	for path, logTask := range p.logTasks {
		log.Infof("[Pecker] Start LogTask %s", path)
		logTask.Start()
	}
	return nil
}

func (p *Pecker) GetStat() *PeckerStat {
	p.mu.Lock()
	defer p.mu.Unlock()
	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2017 The Kubicorn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package retry

import (
	"os"
	"time"

	"github.com/kris-nova/kubicorn/cutil/signals"
)

type Retry interface {
	Work()
}

type Worker struct {
	Function func()
	Retries int
	Timeout time.Duration
	Handler signals.Handler
}

func (w *Worker) Work() {
	done := make(chan bool, 1)

loop:
	for i := 0; i < w.Retries; i++ {
		if w.Handler.GetState() != 0 {
			os.Exit(3)
		}
		go func() {
			w.Function()
			done <- true
		}()
		select {
		case <-done:
			break loop
		case <-time.After(w.Timeout):
			break loop
		}
	}
}
<commit_msg>signals: gofmt<commit_after>// Copyright © 2017 The Kubicorn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package retry

import (
	"os"
	"time"

	"github.com/kris-nova/kubicorn/cutil/signals"
)

type Retry interface {
	Work()
}

type Worker struct {
	Function func()
	Retries  int
	Timeout  time.Duration
	Handler  signals.Handler
}

func (w *Worker) Work() {
	done := make(chan bool, 1)

loop:
	for i := 0; i < w.Retries; i++ {
		if w.Handler.GetState() != 0 {
			os.Exit(3)
		}
		go func() {
			w.Function()
			done <- true
		}()
		select {
		case <-done:
			break loop
		case <-time.After(w.Timeout):
			break loop
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
)

// Person someone having a role in the tournament
type Person struct {
	ID              string   `json:"id"`
	Name            string   `json:"name"`
	Email           string   `json:"email"`
	Nick            string   `json:"nick"`
	ColorPreference []string `json:"color_preference"`
	FacebookID      string   `json:"facebook_id"`
	FacebookToken   string   `json:"facebook_token"`
	AvatarURL       string   `json:"avatar_url"`
	Userlevel       int      `json:"userlevel"`
}

// Userlevels. Designed so that we can insert new ones in between them.
const (
	PermissionProducer    = 100
	PermissionCommentator = 50
	PermissionJudge       = 30
	PermissionPlayer      = 10
)

type score map[int]string

// ScoreSummary is a collection of scores for a Person
type ScoreSummary struct {
	Totals      score
	Tournaments map[string]score
}

func (p *Person) String() string {
	return fmt.Sprintf(
		"<Player %s (%s)>",
		p.Name,
		p.Nick,
	)
}

// JSON returns the person as a JSON representation
func (p *Person) JSON() (out []byte, err error) {
	out, err = json.Marshal(p)
	return
}

// Score gets the score of the Person
//
// Returned as a map of the total score and an array of maps - one per
// tournament participated in.
func (p *Person) Score() *ScoreSummary {
	return nil
}

// CreateFromFacebook adds a new player via Facebook login
func CreateFromFacebook(s *Server, req *FacebookAuthResponse) *Person {
	p := &Person{
		ID:            req.ID,
		FacebookID:    req.ID,
		FacebookToken: req.Token,
		Name:          req.Name,
		Email:         req.Email,
		Userlevel:     PermissionPlayer,
	}

	p.PrefillNickname()

	s.DB.SavePerson(p)

	return p
}

// PrefillNickname makes a suggestion to the nick based on the person
// nolint: gocyclo
func (p *Person) PrefillNickname() {
	// TODO(thiderman): Move this into data files
	switch p.Name {
	case "Karl Johan Krantz":
		p.Nick = "Qrl-Astrid"
		p.Userlevel = PermissionProducer
	case "Ida Andreasson":
		p.Nick = "Blue"
		p.Userlevel = PermissionProducer
	case "Daniel Dala Tiderman":
		p.Nick = "Dala"
		p.Userlevel = PermissionProducer
	case "Lowe Thiderman":
		p.Nick = "thiderman"
		p.Userlevel = PermissionProducer
	case "Magnus Ulenius":
		p.Nick = "Goose"
		p.Userlevel = PermissionProducer
	case "Jonathan Gustafsson":
		p.Nick = "hest"
		p.Userlevel = PermissionProducer
	case "Barney Trotwell":
		p.Nick = "FrontierPsycho"
		p.Userlevel = PermissionProducer
	case "Yasa Akbulut":
		p.Nick = "yasa"
		p.Userlevel = PermissionProducer

	// Commentators
	case "Daniel McHugh":
		p.Nick = "Radcliffe"
		p.Userlevel = PermissionCommentator

	// Judges
	case "Daniele Sluijters":
		p.Nick = "Daenney"
		p.Userlevel = PermissionJudge

	// Other lovelies
	case "Agnes Skoog":
		p.Nick = "#swagnes"
	case "Mattias Aali Ahlström":
		p.Nick = "Aali"
	case "Sam Wise Ingberg":
		p.Nick = "Samselott"
	}
}

// UpdatePerson updates a person from a JoinRequest
func (p *Person) UpdatePerson(r *FacebookJoinRequest) {
	p.ID = r.ID
	p.Name = r.Name
	p.Nick = r.Nick
	p.ColorPreference = []string{r.Color}
}

// PreferredColor returns the preferred color
func (p *Person) PreferredColor() string {
	return p.ColorPreference[0]
}

// Correct sets a name and a color if they are missing
//
// This happens if someone did not complete the registration, and we need to
// have something on their Person{} objects so that the app isn't overly
// confused.
func (p *Person) Correct() {
	if p.Nick == "" {
		// Pick the first name, just to have something
		p.Nick = strings.Split(p.Name, " ")[0]
		log.Printf("Corrected nick for %s", p)
	}

	if len(p.ColorPreference) == 0 {
		// Grab a random color and insert it into the preference.
		p.ColorPreference = append(p.ColorPreference, RandomColor(Colors))
		log.Printf("Corrected color for %s", p)
	}
}

// PersonFromSession returns the Person{} object attached to the session
func PersonFromSession(s *Server, r *http.Request) *Person {
	session, _ := CookieStore.Get(r, "session")
	id := session.Values["user"].(string)

	p := s.DB.GetPerson(id)
	return p
}

// LoadPerson loads a person from the database
func LoadPerson(data []byte) (*Person, error) {
	p := &Person{}
	err := json.Unmarshal(data, p)

	if err != nil {
		log.Print(err)
		return p, err
	}

	return p, nil
}
<commit_msg>Jag är långsam. (#120)<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
)

// Person someone having a role in the tournament
type Person struct {
	ID              string   `json:"id"`
	Name            string   `json:"name"`
	Email           string   `json:"email"`
	Nick            string   `json:"nick"`
	ColorPreference []string `json:"color_preference"`
	FacebookID      string   `json:"facebook_id"`
	FacebookToken   string   `json:"facebook_token"`
	AvatarURL       string   `json:"avatar_url"`
	Userlevel       int      `json:"userlevel"`
}

// Userlevels. Designed so that we can insert new ones in between them.
const (
	PermissionProducer    = 100
	PermissionCommentator = 50
	PermissionJudge       = 30
	PermissionPlayer      = 10
)

type score map[int]string

// ScoreSummary is a collection of scores for a Person
type ScoreSummary struct {
	Totals      score
	Tournaments map[string]score
}

func (p *Person) String() string {
	return fmt.Sprintf(
		"<Player %s (%s)>",
		p.Name,
		p.Nick,
	)
}

// JSON returns the person as a JSON representation
func (p *Person) JSON() (out []byte, err error) {
	out, err = json.Marshal(p)
	return
}

// Score gets the score of the Person
//
// Returned as a map of the total score and an array of maps - one per
// tournament participated in.
func (p *Person) Score() *ScoreSummary {
	return nil
}

// CreateFromFacebook adds a new player via Facebook login
func CreateFromFacebook(s *Server, req *FacebookAuthResponse) *Person {
	p := &Person{
		ID:            req.ID,
		FacebookID:    req.ID,
		FacebookToken: req.Token,
		Name:          req.Name,
		Email:         req.Email,
		Userlevel:     PermissionPlayer,
	}

	p.PrefillNickname()

	s.DB.SavePerson(p)

	return p
}

// PrefillNickname makes a suggestion to the nick based on the person
// nolint: gocyclo
func (p *Person) PrefillNickname() {
	// TODO(thiderman): Move this into data files
	switch p.Name {
	case "Karl Johan Krantz":
		p.Nick = "Qrl-Astrid"
		p.Userlevel = PermissionProducer
	case "Ida Andreasson":
		p.Nick = "Blue"
		p.Userlevel = PermissionProducer
	case "Daniel Dala Tiderman":
		p.Nick = "Dala"
		p.Userlevel = PermissionProducer
	case "Lowe Thiderman":
		p.Nick = "thiderman"
		p.Userlevel = PermissionProducer
	case "Magnus Ulenius":
		p.Nick = "Goose"
		p.Userlevel = PermissionProducer
	case "Jonathan Gustafsson":
		p.Nick = "hest"
		p.Userlevel = PermissionProducer
	case "Barney Trotwell":
		p.Nick = "FrontierPsycho"
		p.Userlevel = PermissionProducer
	case "Yasa Akbulut":
		p.Nick = "yasa"
		p.Userlevel = PermissionProducer
	case "Mike Goeppner":
		p.Nick = "skolpadda"
		p.Userlevel = PermissionProducer

	// Commentators
	case "Daniel McHugh":
		p.Nick = "Radcliffe"
		p.Userlevel = PermissionCommentator

	// Judges
	case "Daniele Sluijters":
		p.Nick = "Daenney"
		p.Userlevel = PermissionJudge

	// Other lovelies
	case "Agnes Skoog":
		p.Nick = "#swagnes"
	case "Mattias Aali Ahlström":
		p.Nick = "Aali"
	case "Sam Wise Ingberg":
		p.Nick = "Samselott"
	}
}

// UpdatePerson updates a person from a JoinRequest
func (p *Person) UpdatePerson(r *FacebookJoinRequest) {
	p.ID = r.ID
	p.Name = r.Name
	p.Nick = r.Nick
	p.ColorPreference = []string{r.Color}
}

// PreferredColor returns the preferred color
func (p *Person) PreferredColor() string {
	return p.ColorPreference[0]
}

// Correct sets a name and a color if they are missing
//
// This happens if someone did not complete the registration, and we need to
// have something on their Person{} objects so that the app isn't overly
// confused.
func (p *Person) Correct() {
	if p.Nick == "" {
		// Pick the first name, just to have something
		p.Nick = strings.Split(p.Name, " ")[0]
		log.Printf("Corrected nick for %s", p)
	}

	if len(p.ColorPreference) == 0 {
		// Grab a random color and insert it into the preference.
		p.ColorPreference = append(p.ColorPreference, RandomColor(Colors))
		log.Printf("Corrected color for %s", p)
	}
}

// PersonFromSession returns the Person{} object attached to the session
func PersonFromSession(s *Server, r *http.Request) *Person {
	session, _ := CookieStore.Get(r, "session")
	id := session.Values["user"].(string)

	p := s.DB.GetPerson(id)
	return p
}

// LoadPerson loads a person from the database
func LoadPerson(data []byte) (*Person, error) {
	p := &Person{}
	err := json.Unmarshal(data, p)

	if err != nil {
		log.Print(err)
		return p, err
	}

	return p, nil
}
<|endoftext|>


<commit_before>// Copyright 2015, Cyrill @ Schumacher.fm and the CoreStore contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config_test

import (
	"testing"
	"time"

	"github.com/corestoreio/csfw/config"
	"github.com/corestoreio/csfw/storage/csdb"
	"github.com/corestoreio/csfw/utils"
	"github.com/stretchr/testify/assert"
	"strings"
)

func TestDBStorageOneStmt(t *testing.T) {
	defer debugLogBuf.Reset() // contains only data from the debug level, info level will be dumped to os.Stdout

	dbc := csdb.MustConnectTest()
	defer func() { assert.NoError(t, dbc.Close()) }()

	sdb := config.NewDBStorage(dbc.DB)
	sdb.Read.Idle = time.Second * 2
	sdb.Write.Idle = time.Second * 3

	sdb.Start()
	// Stop() would only be called under rare circumstances on a production system
	defer func() { assert.NoError(t, sdb.Stop()) }()

	tests := []struct {
		key       string
		value     interface{}
		wantNil   bool
		wantValue string
	}{
		{"stores/1/testDBStorage/secure/base_url", "http://corestore.io", false, "http://corestore.io"},
		{"stores/1/testDBStorage/log/active", 1, false, "1"},
		{"stores/99999/testDBStorage/log/clean", 19.999, false, "19.999"},
		{"stores/99999/testDBStorage/log/clean", 29.999, false, "29.999"},
		{"default/1/testDBStorage/catalog/purge", true, false, "true"},
		{"default/1/testDBStorage/catalog/clean", 0, false, "0"},
	}
	for _, test := range tests {
		sdb.Set(test.key, test.value)
		if test.wantNil {
			assert.Nil(t, sdb.Get(test.key), "Test: %v", test)
		} else {
			assert.Exactly(t, test.wantValue, sdb.Get(test.key), "Test: %v", test)
		}
	}

	assert.Exactly(t, 1, strings.Count(debugLogBuf.String(), "(`scope`,`scope_id`,`path`,`value`) VALUES"))
	assert.Exactly(t, 1, strings.Count(debugLogBuf.String(), "WHERE `scope`=? AND `scope_id`=? AND `path`=?"))

	ak := utils.StringSlice(sdb.AllKeys())
	for _, test := range tests {
		assert.True(t, ak.Include(test.key), "Missing Key: %s", test.key)
	}
	assert.Exactly(t, 1, strings.Count(debugLogBuf.String(), "CONCAT(scope,'%s',scope_id,'%s',path) AS `fqpath`"))
}
<commit_msg>config: Add test TestDBStorageMultipleStmt (with race condition)<commit_after>// Copyright 2015, Cyrill @ Schumacher.fm and the CoreStore contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config_test

import (
	"testing"
	"time"

	"fmt"
	"strings"

	"github.com/corestoreio/csfw/config"
	"github.com/corestoreio/csfw/config/scope"
	"github.com/corestoreio/csfw/storage/csdb"
	"github.com/corestoreio/csfw/utils"
	"github.com/stretchr/testify/assert"
)

func TestDBStorageOneStmt(t *testing.T) {
	defer debugLogBuf.Reset() // contains only data from the debug level, info level will be dumped to os.Stdout

	dbc := csdb.MustConnectTest()
	defer func() { assert.NoError(t, dbc.Close()) }()

	sdb := config.NewDBStorage(dbc.DB)

	sdb.Start()
	// Stop() would only be called under rare circumstances on a production system
	defer func() { assert.NoError(t, sdb.Stop()) }()

	tests := []struct {
		key       string
		value     interface{}
		wantNil   bool
		wantValue string
	}{
		{"stores/1/testDBStorage/secure/base_url", "http://corestore.io", false, "http://corestore.io"},
		{"stores/1/testDBStorage/log/active", 1, false, "1"},
		{"stores/99999/testDBStorage/log/clean", 19.999, false, "19.999"},
		{"stores/99999/testDBStorage/log/clean", 29.999, false, "29.999"},
		{"default/1/testDBStorage/catalog/purge", true, false, "true"},
		{"default/1/testDBStorage/catalog/clean", 0, false, "0"},
	}
	for _, test := range tests {
		sdb.Set(test.key, test.value)
		if test.wantNil {
			assert.Nil(t, sdb.Get(test.key), "Test: %v", test)
		} else {
			assert.Exactly(t, test.wantValue, sdb.Get(test.key), "Test: %v", test)
		}
	}

	assert.Exactly(t, 1, strings.Count(debugLogBuf.String(), "(`scope`,`scope_id`,`path`,`value`) VALUES"))
	assert.Exactly(t, 1, strings.Count(debugLogBuf.String(), "WHERE `scope`=? AND `scope_id`=? AND `path`=?"))

	for _, test := range tests {
		ak := utils.StringSlice(sdb.AllKeys()) // trigger many queries with one statement
		assert.True(t, ak.Include(test.key), "Missing Key: %s", test.key)
	}
	assert.Exactly(t, 1, strings.Count(debugLogBuf.String(), "CONCAT(scope,'%s',scope_id,'%s',path) AS `fqpath`"))
}

func TestDBStorageMultipleStmt(t *testing.T) {
	defer debugLogBuf.Reset() // contains only data from the debug level, info level will be dumped to os.Stdout

	if testing.Short() {
		t.Skip("Test skipped in short mode")
	}

	dbc := csdb.MustConnectTest()
	defer func() { assert.NoError(t, dbc.Close()) }()

	sdb := config.NewDBStorage(dbc.DB)
	sdb.All.Idle = time.Second * 1
	sdb.Read.Idle = time.Second * 1
	sdb.Write.Idle = time.Second * 1

	sdb.Start()

	tests := []struct {
		key       string
		value     interface{}
		wantValue string
	}{
		{"websites/10/testDBStorage/secure/base_url", "http://corestore.io", "http://corestore.io"},
		{"websites/10/testDBStorage/log/active", 1, "1"},
		{"websites/20/testDBStorage/log/clean", 19.999, "19.999"},
		{"websites/20/testDBStorage/product/shipping", 29.999, "29.999"},
		{"default/0/testDBStorage/checkout/multishipping", false, "false"},
	}
	for i, test := range tests {
		sdb.Set(test.key, test.value)
		assert.Exactly(t, test.wantValue, sdb.Get(test.key), "Test: %v", test)
		if i < 2 {
			// last two iterations reopen a new statement, not closing it and reusing it
			time.Sleep(time.Millisecond * 1500) // trigger ticker to close statements
		}
	}

	assert.Exactly(t, 5, strings.Count(debugLogBuf.String(), "(`scope`,`scope_id`,`path`,`value`) VALUES"))
	assert.Exactly(t, 5, strings.Count(debugLogBuf.String(), "WHERE `scope`=? AND `scope_id`=? AND `path`=?"))

	for i, test := range tests {
		ak := utils.StringSlice(sdb.AllKeys())
		assert.True(t, ak.Include(test.key), "Missing Key: %s", test.key)
		if i < 2 {
			time.Sleep(time.Millisecond * 1500) // trigger ticker to close statements
		}
	}
	assert.NoError(t, sdb.Stop())
	assert.Exactly(t, 6, strings.Count(debugLogBuf.String(), fmt.Sprintf("CONCAT(scope,'%s',scope_id,'%s',path) AS `fqpath`", scope.PS, scope.PS)))
}
<|endoftext|>


<commit_before>package main

import (
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/jacksontj/dnms/mapper"
)

// TODO: move to another package??

type Pinger struct {
	M *mapper.Mapper

	Self mapper.Peer
}

func (p *Pinger) Start() {
	go p.PingPeers()
}

// TODO implement
func (p *Pinger) Stop() {

}

// ping all the things
func (p *Pinger) PingPeers() {
	for {
		peerChan := p.M.IterPeers()
		for peer := range peerChan {
			p.PingPeer(peer)
			// TODO configurable rate
			time.Sleep(time.Millisecond * 100)
		}
	}
}

func (p *Pinger) PingPeer(peer *mapper.Peer) {
	c := make(chan string)
	p.M.RouteMap.IterRoutes(peer.String(), c)
	for routeKey := range c {
		route := p.M.RouteMap.GetRoute(routeKey)
		// TODO: better
		if route == nil {
			continue
		}
		routeKeyParts := strings.SplitN(routeKey, ",", 2)
		srcKeyParts := strings.SplitN(routeKeyParts[0], ":", 2)
		srcPort, _ := strconv.Atoi(srcKeyParts[1])
		logrus.Debugf("Ping src=%s dst=%s", routeKeyParts[0], routeKeyParts[1])

		p := ping{
			SrcName:    p.Self.Name,
			SrcPort:    srcPort,
			DstName:    peer.Name,
			DstPort:    peer.Port,
			Path:       route.Hops(),
			PingTimeNS: time.Now().UnixNano(),
		}
		// TODO: major cleanup to encapsulate all this message sending
		// Encode as a user message
		encodedBuf, err := encode(pingMsg, p)
		if err != nil {
			logrus.Infof("Unable to encode pingMsg: %v", err)
			continue
		}
		msg := encodedBuf.Bytes()
		buf := make([]byte, 1, len(msg)+1)
		buf[0] = byte(8) // TODO: add sendFrom API to memberlist
		buf = append(buf, msg...)

		LocalAddr, err := net.ResolveUDPAddr("udp", "0.0.0.0:"+srcKeyParts[1])
		if err != nil {
			logrus.Errorf("unable to resolve source addr %v", err)
		}
		RemoteEP := net.UDPAddr{
			IP:   net.ParseIP(peer.Name), // TODO: have ip field
			Port: peer.Port,
		}
		conn, err := net.DialUDP("udp", LocalAddr, &RemoteEP)
		if err != nil {
			// If we got a non-retryable error, lets log it
			if netErr, ok := err.(net.Error); !(ok && netErr.Temporary()) {
				logrus.Errorf("unable to connect to peer: %v", err)
			} else {
				logrus.Debugf("Temporarily unable to connect to peer: %v", err)
			}
			continue
		}
		// TODO: configurable time
		conn.SetDeadline(time.Now().Add(time.Second))

		conn.Write(buf)

		// get a response from the ping
		retBuf := make([]byte, 2048)
		readRet, err := conn.Read(retBuf)

		// Whether we got an ACK
		passed := false

		// if there was a response
		if readRet > 0 {
			// Note: throwing away the first byte-- as its the memberlist header
			msgType := messageType(retBuf[1])
			retBuf = retBuf[2:]

			switch msgType {

			case ackMsg:
				a := ack{}
				err := decode(retBuf, &a)
				if err != nil {
					logrus.Warning("Unable to decode message: %v", err)
				} else {
					passed = true
				}

			default:
				logrus.Infof("Got unknown response type from ack: %v", msgType)
			}
		}
		route.HandleACK(passed, time.Now().UnixNano()-p.PingTimeNS)
		conn.Close()
		time.Sleep(time.Second)
	}
}
<commit_msg>Add some TODOs<commit_after>package main

import (
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/jacksontj/dnms/mapper"
)

// TODO: move to another package??

type Pinger struct {
	M *mapper.Mapper

	Self mapper.Peer
}

func (p *Pinger) Start() {
	go p.PingPeers()
}

// TODO implement
func (p *Pinger) Stop() {

}

// ping all the things
func (p *Pinger) PingPeers() {
	for {
		peerChan := p.M.IterPeers()
		for peer := range peerChan {
			p.PingPeer(peer)
			// TODO configurable rate
			time.Sleep(time.Millisecond * 100)
		}
	}
}

func (p *Pinger) PingPeer(peer *mapper.Peer) {
	c := make(chan string)
	p.M.RouteMap.IterRoutes(peer.String(), c)
	for routeKey := range c {
		route := p.M.RouteMap.GetRoute(routeKey)
		// TODO: better
		if route == nil {
			continue
		}
		routeKeyParts := strings.SplitN(routeKey, ",", 2)
		srcKeyParts := strings.SplitN(routeKeyParts[0], ":", 2)
		srcPort, _ := strconv.Atoi(srcKeyParts[1])
		logrus.Debugf("Ping src=%s dst=%s", routeKeyParts[0], routeKeyParts[1])

		p := ping{
			SrcName:    p.Self.Name,
			SrcPort:    srcPort,
			DstName:    peer.Name,
			DstPort:    peer.Port,
			Path:       route.Hops(),
			PingTimeNS: time.Now().UnixNano(),
		}
		// TODO: major cleanup to encapsulate all this message sending
		// Encode as a user message
		encodedBuf, err := encode(pingMsg, p)
		if err != nil {
			logrus.Infof("Unable to encode pingMsg: %v", err)
			continue
		}
		msg := encodedBuf.Bytes()
		buf := make([]byte, 1, len(msg)+1)
		buf[0] = byte(8) // TODO: add sendFrom API to memberlist
		buf = append(buf, msg...)

		LocalAddr, err := net.ResolveUDPAddr("udp", "0.0.0.0:"+srcKeyParts[1])
		if err != nil {
			logrus.Errorf("unable to resolve source addr %v", err)
		}
		RemoteEP := net.UDPAddr{
			IP:   net.ParseIP(peer.Name), // TODO: have ip field
			Port: peer.Port,
		}
		conn, err := net.DialUDP("udp", LocalAddr, &RemoteEP)
		if err != nil {
			// If we got a non-retryable error, lets log it
			if netErr, ok := err.(net.Error); !(ok && netErr.Temporary()) {
				logrus.Errorf("unable to connect to peer: %v", err)
			} else {
				logrus.Debugf("Temporarily unable to connect to peer: %v", err)
			}
			continue
		}
		// TODO: configurable time
		conn.SetDeadline(time.Now().Add(time.Second))

		// TODO: check for error
		conn.Write(buf)

		// get a response from the ping
		retBuf := make([]byte, 2048)
		// TODO: something with err
		readRet, err := conn.Read(retBuf)

		// Whether we got an ACK
		passed := false

		// if there was a response
		if readRet > 0 {
			// Note: throwing away the first byte-- as its the memberlist header
			msgType := messageType(retBuf[1])
			retBuf = retBuf[2:]

			switch msgType {

			case ackMsg:
				a := ack{}
				err := decode(retBuf, &a)
				if err != nil {
					logrus.Warning("Unable to decode message: %v", err)
				} else {
					passed = true
				}

			default:
				logrus.Infof("Got unknown response type from ack: %v", msgType)
			}
		}
		route.HandleACK(passed, time.Now().UnixNano()-p.PingTimeNS)
		conn.Close()
		// TODO: configurable ping sleep
		time.Sleep(time.Second)
	}
}
<|endoftext|>


<commit_before>// +build linux

package journald

import (
	"strconv"
	"time"

	"github.com/coreos/go-systemd/sdjournal"
	"github.com/segmentio/ecs-logs/lib"
)

func NewReader() (r ecslogs.Reader, err error) {
	var j *sdjournal.Journal

	if j, err = sdjournal.NewJournal(); err != nil {
		return
	}

	if err = j.SeekTail(); err != nil {
		j.Close()
		return
	}

	r = reader{j}
	return
}

type reader struct {
	*sdjournal.Journal
}

func (r reader) ReadMessage() (msg ecslogs.Message, err error) {
	for {
		var cur int
		var ok bool

		if cur, err = r.Next(); err != nil {
			return
		}

		if cur == 0 {
			r.Wait(sdjournal.IndefiniteWait)
			continue
		}

		if msg, ok, err = r.getMessage(); ok || err != nil {
			return
		}
	}
}

func (r reader) getMessage() (msg ecslogs.Message, ok bool, err error) {
	if msg.Group, err = r.GetDataValue("CONTAINER_TAG"); len(msg.Group) == 0 {
		// No CONTAINER_TAG, this must be a journal message from a process that
		// isn't running in a docker container.
		return
	}

	if msg.Stream, err = r.GetDataValue("CONTAINER_NAME"); err != nil {
		// There's a CONTAINER_TAG but no CONTAINER_NAME, something is seriously
		// wrong here, the log docker log driver is misbehaving.
		return
	}

	msg.Level = r.getPriority()
	msg.PID = r.getInt("_PID")
	msg.UID = r.getInt("_UID")
	msg.GID = r.getInt("_GID")
	msg.Errno = r.getInt("ERRNO")
	msg.Line = r.getInt("CODE_LINE")
	msg.Func = r.getString("CODE_FUNC")
	msg.File = r.getString("CODE_FILE")
	msg.ID = r.getString("MESSAGE_ID")
	msg.Host = r.getString("_HOSTNAME")
	msg.Content = r.getString("MESSAGE")
	msg.Time = r.getTime()
	ok = true
	return
}

func (r reader) getInt(k string) (v int) {
	v, _ = strconv.Atoi(r.getString(k))
	return
}

func (r reader) getTime() (t time.Time) {
	if u, e := r.GetRealtimeUsec(); e == nil {
		t = time.Unix(int64(usec/1000000), int64((usec%1000000)*1000))
	}
	return
}

func (r reader) getPriority() (p ecslogs.Level) {
	if v, e := strconv.Atoi(r.getString("PRIORITY")); e != nil {
		p = ecslogs.INFO
	} else {
		p = ecslogs.Level(v)
	}
	return
}

func (r reader) getString(k string) (s string) {
	s, _ = r.GetDataValue(k)
	return
}
<commit_msg>fix journal reader<commit_after>// +build linux

package journald

import (
	"strconv"
	"time"

	"github.com/coreos/go-systemd/sdjournal"
	"github.com/segmentio/ecs-logs/lib"
)

func NewReader() (r ecslogs.Reader, err error) {
	var j *sdjournal.Journal

	if j, err = sdjournal.NewJournal(); err != nil {
		return
	}

	if err = j.SeekTail(); err != nil {
		j.Close()
		return
	}

	r = reader{j}
	return
}

type reader struct {
	*sdjournal.Journal
}

func (r reader) ReadMessage() (msg ecslogs.Message, err error) {
	for {
		var cur int
		var ok bool

		if cur, err = r.Next(); err != nil {
			return
		}

		if cur == 0 {
			r.Wait(sdjournal.IndefiniteWait)
			continue
		}

		if msg, ok, err = r.getMessage(); ok || err != nil {
			return
		}
	}
}

func (r reader) getMessage() (msg ecslogs.Message, ok bool, err error) {
	if msg.Group, err = r.GetDataValue("CONTAINER_TAG"); len(msg.Group) == 0 {
		// No CONTAINER_TAG, this must be a journal message from a process that
		// isn't running in a docker container.
		return
	}

	if msg.Stream, err = r.GetDataValue("CONTAINER_NAME"); err != nil {
		// There's a CONTAINER_TAG but no CONTAINER_NAME, something is seriously
		// wrong here, the log docker log driver is misbehaving.
		return
	}

	msg.Level = r.getPriority()
	msg.PID = r.getInt("_PID")
	msg.UID = r.getInt("_UID")
	msg.GID = r.getInt("_GID")
	msg.Errno = r.getInt("ERRNO")
	msg.Line = r.getInt("CODE_LINE")
	msg.Func = r.getString("CODE_FUNC")
	msg.File = r.getString("CODE_FILE")
	msg.ID = r.getString("MESSAGE_ID")
	msg.Host = r.getString("_HOSTNAME")
	msg.Content = r.getString("MESSAGE")
	msg.Time = r.getTime()
	ok = true
	return
}

func (r reader) getInt(k string) (v int) {
	v, _ = strconv.Atoi(r.getString(k))
	return
}

func (r reader) getTime() (t time.Time) {
	if u, e := r.GetRealtimeUsec(); e == nil {
		t = time.Unix(int64(u/1000000), int64((u%1000000)*1000))
	}
	return
}

func (r reader) getPriority() (p ecslogs.Level) {
	if v, e := strconv.Atoi(r.getString("PRIORITY")); e != nil {
		p = ecslogs.INFO
	} else {
		p = ecslogs.Level(v)
	}
	return
}

func (r reader) getString(k string) (s string) {
	s, _ = r.GetDataValue(k)
	return
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/keybase/client/go/logger"
)

// Logs is the struct to specify the path of log files
type Logs struct {
	Desktop string
	Kbfs    string
	Service string
	Updater string
	Start   string
	Install string
	System  string
	Git     string
}

// LogSendContext for LogSend
type LogSendContext struct {
	Contextified
	Logs Logs
}

func addFile(mpart *multipart.Writer, param, filename, data string) error {
	if len(data) == 0 {
		return nil
	}

	part, err := mpart.CreateFormFile(param, filename)
	if err != nil {
		return err
	}
	gz := gzip.NewWriter(part)
	if _, err := gz.Write([]byte(data)); err != nil {
		return err
	}
	return gz.Close()
}

func (l *LogSendContext) post(status, feedback, kbfsLog, svcLog, desktopLog, updaterLog, startLog, installLog, systemLog, gitLog string) (string, error) {
	l.G().Log.Debug("sending status + logs to keybase")

	var body bytes.Buffer
	mpart := multipart.NewWriter(&body)

	if feedback != "" {
		mpart.WriteField("feedback", feedback)
	}

	if err := addFile(mpart, "status_gz", "status.gz", status); err != nil {
		return "", err
	}
	if err := addFile(mpart, "kbfs_log_gz", "kbfs_log.gz", kbfsLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "keybase_log_gz", "keybase_log.gz", svcLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "updater_log_gz", "updater_log.gz", updaterLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "gui_log_gz", "gui_log.gz", desktopLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "start_log_gz", "start_log.gz", startLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "install_log_gz", "install_log.gz", installLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "system_log_gz", "system_log.gz", systemLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "git_log_gz", "git_log.gz", gitLog); err != nil {
		return "", err
	}

	if err := mpart.Close(); err != nil {
		return "", err
	}

	l.G().Log.Debug("body size: %d\n", body.Len())

	arg := APIArg{
		Endpoint:    "logdump/send",
		SessionType: APISessionTypeOPTIONAL,
	}

	// Get the login session, if any
	l.G().LoginState().LoggedInLoad()

	resp, err := l.G().API.PostRaw(arg, mpart.FormDataContentType(), &body)
	if err != nil {
		l.G().Log.Debug("post error: %s", err)
		return "", err
	}

	id, err := resp.Body.AtKey("logdump_id").GetString()
	if err != nil {
		return "", err
	}

	return id, nil
}

// tail the logs that start with the stem `stem`, which are of type `which`.
// Get the most recent `numBytes` from the concatenation of the files.
func tail(log logger.Logger, which string, stem string, numBytes int) (ret string) {

	numFiles := 0

	log.Debug("+ tailing %s file with stem %q", which, stem)
	defer func() {
		log.Debug("- collected %d bytes from %d files", len(ret), numFiles)
	}()

	if len(stem) == 0 {
		log.Debug("| skipping %s logs (since no stem given)", which)
		return
	}

	lognames := listLogFiles(log, stem)
	var parts []string
	remaining := numBytes

	// Keep reading logs in reverse chronological order until we've read nothing
	// more, or we've filled up numBytes worth of buffer, or we didn't have to read
	// the whole file.
	for _, logname := range lognames {
		data, seeked := tailFile(log, which, logname, remaining)
		if len(data) == 0 {
			break
		}
		parts = append(parts, data)
		numFiles++
		remaining -= len(data)
		if remaining <= 0 || seeked {
			break
		}
	}

	// Reverse the array; took this one-line from StackOverflow answer
	for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
		parts[i], parts[j] = parts[j], parts[i]
	}

	return strings.Join(parts, "")
}

type nameAndMTime struct {
	name  string
	mtime time.Time
}

type nameAndMTimes []nameAndMTime

func (a nameAndMTimes) Len() int           { return len(a) }
func (a nameAndMTimes) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a nameAndMTimes) Less(i, j int) bool { return a[i].mtime.After(a[j].mtime) }

// List logfiles that match the glob filename*, and return then in reverse chronological order.
// We'll need to read the first, and maybe the second
func listLogFiles(log logger.Logger, stem string) (ret []string) {
	stem = filepath.Clean(stem)
	dir := filepath.Dir(stem)
	base := filepath.Base(stem)
	files, err := ioutil.ReadDir(dir)

	defer func() {
		log.Debug("listLogFiles(%q) -> %v", stem, ret)
	}()

	// In the worst case, just return the stem in an array
	defret := []string{stem}

	if err != nil {
		log.Debug("failed to list directory %q: %s", dir, err)
		return defret
	}

	var tmp []nameAndMTime
	for _, d := range files {
		fullname := filepath.Clean(filepath.Join(dir, d.Name()))
		// Use the stat on the file (and not from the directory read)
		// since the latter doesn't work reliably on Windows
		finfo, err := os.Stat(fullname)
		if err != nil {
			log.Debug("Cannot stat %q: %s", fullname, err)
			continue
		}
		if strings.HasPrefix(d.Name(), base) {
			tmp = append(tmp, nameAndMTime{fullname, finfo.ModTime()})
		}
	}
	if len(tmp) == 0 {
		log.Debug("no files found matching \"%s*\"; falling back to default glob", stem)
		return defret
	}

	// Sort the files in reverse chronological mtime order. We should get the raw stem first.
	sort.Sort(nameAndMTimes(tmp))
	log.Debug("Sorted file list: %+v", tmp)

	for _, f := range tmp {
		ret = append(ret, f.name)
	}

	// If we didn't get the raw stem first, then we have a problem, so just use only the
	// raw stem and ignore the rest of our work.
	if stem != ret[0] {
		log.Debug("Expected to get %q first, but got %q instead! Falling back to one log only", stem, ret[0])
		return defret
	}
	return ret
}

// findFirstNewline first the first newline in the given byte array, and then returns the
// rest of the byte array. Should be safe to use on utf-8 strings.
func findFirstNewline(b []byte) []byte {
	index := bytes.IndexByte(b, '\n')
	if index < 0 || index == len(b)-1 {
		return nil
	}
	return b[(index + 1):]
}

func appendError(log logger.Logger, collected []byte, format string, args ...interface{}) []byte {
	msg := "Error reading logs: " + fmt.Sprintf(format, args...)
	log.Errorf(msg)
	return append(collected, []byte("\n"+msg+"\n")...)
}

// If systemd is running, we look for logs in the systemd journal for our 3
// main services.
func tailSystemdJournal(log logger.Logger, which string, numBytes int) (ret string) {
	log.Debug("+ tailing journalctl for %s (%d bytes)", which, numBytes)
	defer func() {
		log.Debug("- scanned %d bytes", len(ret))
	}()

	// Journalctl doesn't provide a "last N bytes" flag directly, so instead we
	// use "last N lines". Large log files in practice seem to be about 150
	// bits per line. We'll request lines on that assumption, but if we get
	// more than 2x as many bytes as we expected, we'll stop reading and
	// include a big error.
	guessedLines := numBytes / 150
	maxBytes := numBytes * 2

	// We intentionally avoid the --user flag to journalctl. That would make us
	// skip over the system journal, but in e.g. Ubuntu 16.04, that's where
	// user units write their logs.
	journalCmd := exec.Command(
		"journalctl",
		"--user-unit="+which,
		"--lines="+strconv.Itoa(guessedLines),
		"--output=cat",
	)
	journalCmd.Stderr = os.Stderr
	stdout, err := journalCmd.StdoutPipe()
	if err != nil {
		msg := fmt.Sprintf("Failed to open a pipe for journalctl: %s", err)
		log.Errorf(msg)
		return msg
	}
	err = journalCmd.Start()
	if err != nil {
		msg := fmt.Sprintf("Failed to run journalctl: %s", err)
		log.Errorf(msg)
		return msg
	}

	// Once we start reading output, don't short-circuit on errors. Just log
	// them, and return whatever we got.
	stdoutLimited := io.LimitReader(stdout, int64(maxBytes))
	output, err := ioutil.ReadAll(stdoutLimited)
	if err != nil {
		output = appendError(log, output, "Error reading from journalctl pipe: %s", err)
	}
	// We must close stdout before Wait, or else Wait might deadlock.
	stdout.Close()
	err = journalCmd.Wait()
	if err != nil {
		output = appendError(log, output, "Journalctl exited with an error: %s", err)
	}
	if len(output) >= maxBytes {
		output = appendError(log, output, "Journal lines longer than expected. Logs truncated.")
	}
	return string(output)
}

// tailFile takes the last n bytes, but advances to the first newline. Returns the log (as a string)
// and a bool, indicating if we read the full log, or we had to advance into the log to find the newline.
func tailFile(log logger.Logger, which string, filename string, numBytes int) (ret string, seeked bool) {

	log.Debug("+ tailing %s log %q (%d bytes)", which, filename, numBytes)
	defer func() {
		log.Debug("- scanned %d bytes", len(ret))
	}()

	seeked = false
	if filename == "" {
		return ret, seeked
	}
	finfo, err := os.Stat(filename)
	if os.IsNotExist(err) {
		log.Debug("log %q doesn't exist", filename)
		return ret, seeked
	}
	f, err := os.Open(filename)
	if err != nil {
		log.Errorf("error opening log %q: %s", filename, err)
		return ret, seeked
	}
	defer f.Close()
	if finfo.Size() > int64(numBytes) {
		seeked = true
		_, err = f.Seek(int64(-numBytes), io.SeekEnd)
		if err != nil {
			log.Errorf("Can't seek log %q: %s", filename, err)
			return ret, seeked
		}
	}
	buf, err := ioutil.ReadAll(f)
	if err != nil {
		log.Errorf("Failure in reading file %q: %s", filename, err)
		return ret, seeked
	}
	if seeked {
		buf = findFirstNewline(buf)
	}
	return string(buf), seeked
}

// LogSend sends the the tails of log files to kb
func (l *LogSendContext) LogSend(statusJSON, feedback string, sendLogs bool, numBytes int) (string, error) {
	logs := l.Logs
	var kbfsLog string
	var svcLog string
	var desktopLog string
	var updaterLog string
	var startLog string
	var installLog string
	var systemLog string
	var gitLog string

	if sendLogs {
		svcLog = tail(l.G().Log, "service", logs.Service, numBytes)
		kbfsLog = tail(l.G().Log, "kbfs", logs.Kbfs, numBytes)
		desktopLog = tail(l.G().Log, "desktop", logs.Desktop, numBytes)
		updaterLog = tail(l.G().Log, "updater", logs.Updater, numBytes)
		// TODO: get start logs from systemd?
		startLog = tail(l.G().Log, "start", logs.Start, numBytes)
		installLog = tail(l.G().Log, "install", logs.Install, numBytes)
		systemLog = tail(l.G().Log, "system", logs.System, numBytes)
		gitLog = tail(l.G().Log, "git", logs.Git, numBytes)
	} else {
		kbfsLog = ""
		svcLog = ""
		desktopLog = ""
		updaterLog = ""
		startLog = ""
		installLog = ""
		systemLog = ""
		gitLog = ""
	}

	return l.post(statusJSON, feedback, kbfsLog, svcLog, desktopLog, updaterLog, startLog, installLog, systemLog, gitLog)
}
<commit_msg>use the systemd journal for startup logs<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/keybase/client/go/logger"
)

// Logs is the struct to specify the path of log files
type Logs struct {
	Desktop string
	Kbfs    string
	Service string
	Updater string
	Start   string
	Install string
	System  string
	Git     string
}

// LogSendContext for LogSend
type LogSendContext struct {
	Contextified
	Logs Logs
}

func addFile(mpart *multipart.Writer, param, filename, data string) error {
	if len(data) == 0 {
		return nil
	}

	part, err := mpart.CreateFormFile(param, filename)
	if err != nil {
		return err
	}
	gz := gzip.NewWriter(part)
	if _, err := gz.Write([]byte(data)); err != nil {
		return err
	}
	return gz.Close()
}

func (l *LogSendContext) post(status, feedback, kbfsLog, svcLog, desktopLog, updaterLog, startLog, installLog, systemLog, gitLog string) (string, error) {
	l.G().Log.Debug("sending status + logs to keybase")

	var body bytes.Buffer
	mpart := multipart.NewWriter(&body)

	if feedback != "" {
		mpart.WriteField("feedback", feedback)
	}

	if err := addFile(mpart, "status_gz", "status.gz", status); err != nil {
		return "", err
	}
	if err := addFile(mpart, "kbfs_log_gz", "kbfs_log.gz", kbfsLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "keybase_log_gz", "keybase_log.gz", svcLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "updater_log_gz", "updater_log.gz", updaterLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "gui_log_gz", "gui_log.gz", desktopLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "start_log_gz", "start_log.gz", startLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "install_log_gz", "install_log.gz", installLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "system_log_gz", "system_log.gz", systemLog); err != nil {
		return "", err
	}
	if err := addFile(mpart, "git_log_gz", "git_log.gz", gitLog); err != nil {
		return "", err
	}

	if err := mpart.Close(); err != nil {
		return "", err
	}

	l.G().Log.Debug("body size: %d\n", body.Len())

	arg := APIArg{
		Endpoint:    "logdump/send",
		SessionType: APISessionTypeOPTIONAL,
	}

	// Get the login session, if any
	l.G().LoginState().LoggedInLoad()

	resp, err := l.G().API.PostRaw(arg, mpart.FormDataContentType(), &body)
	if err != nil {
		l.G().Log.Debug("post error: %s", err)
		return "", err
	}

	id, err := resp.Body.AtKey("logdump_id").GetString()
	if err != nil {
		return "", err
	}

	return id, nil
}

// tail the logs that start with the stem `stem`, which are of type `which`.
// Get the most recent `numBytes` from the concatenation of the files.
func tail(log logger.Logger, which string, stem string, numBytes int) (ret string) {

	numFiles := 0

	log.Debug("+ tailing %s file with stem %q", which, stem)
	defer func() {
		log.Debug("- collected %d bytes from %d files", len(ret), numFiles)
	}()

	if len(stem) == 0 {
		log.Debug("| skipping %s logs (since no stem given)", which)
		return
	}

	lognames := listLogFiles(log, stem)
	var parts []string
	remaining := numBytes

	// Keep reading logs in reverse chronological order until we've read nothing
	// more, or we've filled up numBytes worth of buffer, or we didn't have to read
	// the whole file.
	for _, logname := range lognames {
		data, seeked := tailFile(log, which, logname, remaining)
		if len(data) == 0 {
			break
		}
		parts = append(parts, data)
		numFiles++
		remaining -= len(data)
		if remaining <= 0 || seeked {
			break
		}
	}

	// Reverse the array; took this one-line from StackOverflow answer
	for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
		parts[i], parts[j] = parts[j], parts[i]
	}

	return strings.Join(parts, "")
}

type nameAndMTime struct {
	name  string
	mtime time.Time
}

type nameAndMTimes []nameAndMTime

func (a nameAndMTimes) Len() int           { return len(a) }
func (a nameAndMTimes) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a nameAndMTimes) Less(i, j int) bool { return a[i].mtime.After(a[j].mtime) }

// List logfiles that match the glob filename*, and return then in reverse chronological order.
// We'll need to read the first, and maybe the second
func listLogFiles(log logger.Logger, stem string) (ret []string) {
	stem = filepath.Clean(stem)
	dir := filepath.Dir(stem)
	base := filepath.Base(stem)
	files, err := ioutil.ReadDir(dir)

	defer func() {
		log.Debug("listLogFiles(%q) -> %v", stem, ret)
	}()

	// In the worst case, just return the stem in an array
	defret := []string{stem}

	if err != nil {
		log.Debug("failed to list directory %q: %s", dir, err)
		return defret
	}

	var tmp []nameAndMTime
	for _, d := range files {
		fullname := filepath.Clean(filepath.Join(dir, d.Name()))
		// Use the stat on the file (and not from the directory read)
		// since the latter doesn't work reliably on Windows
		finfo, err := os.Stat(fullname)
		if err != nil {
			log.Debug("Cannot stat %q: %s", fullname, err)
			continue
		}
		if strings.HasPrefix(d.Name(), base) {
			tmp = append(tmp, nameAndMTime{fullname, finfo.ModTime()})
		}
	}
	if len(tmp) == 0 {
		log.Debug("no files found matching \"%s*\"; falling back to default glob", stem)
		return defret
	}

	// Sort the files in reverse chronological mtime order. We should get the raw stem first.
	sort.Sort(nameAndMTimes(tmp))
	log.Debug("Sorted file list: %+v", tmp)

	for _, f := range tmp {
		ret = append(ret, f.name)
	}

	// If we didn't get the raw stem first, then we have a problem, so just use only the
	// raw stem and ignore the rest of our work.
	if stem != ret[0] {
		log.Debug("Expected to get %q first, but got %q instead! Falling back to one log only", stem, ret[0])
		return defret
	}
	return ret
}

// findFirstNewline first the first newline in the given byte array, and then returns the
// rest of the byte array. Should be safe to use on utf-8 strings.
func findFirstNewline(b []byte) []byte {
	index := bytes.IndexByte(b, '\n')
	if index < 0 || index == len(b)-1 {
		return nil
	}
	return b[(index + 1):]
}

func appendError(log logger.Logger, collected []byte, format string, args ...interface{}) []byte {
	msg := "Error reading logs: " + fmt.Sprintf(format, args...)
	log.Errorf(msg)
	return append(collected, []byte("\n"+msg+"\n")...)
}

// Get logs from the systemd journal. Currently we don't use this for most of
// our logging, since it's not persisted across boot on some systems. But we do
// use it for startup logs.
func tailSystemdJournal(log logger.Logger, userUnits []string, numBytes int) (ret string) {
	log.Debug("+ tailing journalctl for %#v (%d bytes)", userUnits, numBytes)
	defer func() {
		log.Debug("- scanned %d bytes", len(ret))
	}()

	// Journalctl doesn't provide a "last N bytes" flag directly, so instead we
	// use "last N lines". Large log files in practice seem to be about 150
	// bits per line. We'll request lines on that assumption, but if we get
	// more than 2x as many bytes as we expected, we'll stop reading and
	// include a big error.
	guessedLines := numBytes / 150
	maxBytes := numBytes * 2

	// We intentionally avoid the --user flag to journalctl. That would make us
	// skip over the system journal, but in e.g. Ubuntu 16.04, that's where
	// user units write their logs.
	args := []string{
		"--lines=" + strconv.Itoa(guessedLines),
	}
	if len(userUnits) == 0 {
		panic("without --user-unit we would scrape all system logs!!!")
	}
	for _, userUnit := range userUnits {
		args = append(args, "--user-unit="+userUnit)
	}
	journalCmd := exec.Command("journalctl", args...)
	journalCmd.Stderr = os.Stderr
	stdout, err := journalCmd.StdoutPipe()
	if err != nil {
		msg := fmt.Sprintf("Failed to open a pipe for journalctl: %s", err)
		log.Errorf(msg)
		return msg
	}
	err = journalCmd.Start()
	if err != nil {
		msg := fmt.Sprintf("Failed to run journalctl: %s", err)
		log.Errorf(msg)
		return msg
	}

	// Once we start reading output, don't short-circuit on errors. Just log
	// them, and return whatever we got.
	stdoutLimited := io.LimitReader(stdout, int64(maxBytes))
	output, err := ioutil.ReadAll(stdoutLimited)
	if err != nil {
		output = appendError(log, output, "Error reading from journalctl pipe: %s", err)
	}
	// We must close stdout before Wait, or else Wait might deadlock.
	stdout.Close()
	err = journalCmd.Wait()
	if err != nil {
		output = appendError(log, output, "Journalctl exited with an error: %s", err)
	}
	if len(output) >= maxBytes {
		output = appendError(log, output, "Journal lines longer than expected. Logs truncated.")
	}
	return string(output)
}

// tailFile takes the last n bytes, but advances to the first newline. Returns the log (as a string)
// and a bool, indicating if we read the full log, or we had to advance into the log to find the newline.
func tailFile(log logger.Logger, which string, filename string, numBytes int) (ret string, seeked bool) {

	log.Debug("+ tailing %s log %q (%d bytes)", which, filename, numBytes)
	defer func() {
		log.Debug("- scanned %d bytes", len(ret))
	}()

	seeked = false
	if filename == "" {
		return ret, seeked
	}
	finfo, err := os.Stat(filename)
	if os.IsNotExist(err) {
		log.Debug("log %q doesn't exist", filename)
		return ret, seeked
	}
	f, err := os.Open(filename)
	if err != nil {
		log.Errorf("error opening log %q: %s", filename, err)
		return ret, seeked
	}
	defer f.Close()
	if finfo.Size() > int64(numBytes) {
		seeked = true
		_, err = f.Seek(int64(-numBytes), io.SeekEnd)
		if err != nil {
			log.Errorf("Can't seek log %q: %s", filename, err)
			return ret, seeked
		}
	}
	buf, err := ioutil.ReadAll(f)
	if err != nil {
		log.Errorf("Failure in reading file %q: %s", filename, err)
		return ret, seeked
	}
	if seeked {
		buf = findFirstNewline(buf)
	}
	return string(buf), seeked
}

// LogSend sends the the tails of log files to kb
func (l *LogSendContext) LogSend(statusJSON, feedback string, sendLogs bool, numBytes int) (string, error) {
	logs := l.Logs
	var kbfsLog string
	var svcLog string
	var desktopLog string
	var updaterLog string
	var startLog string
	var installLog string
	var systemLog string
	var gitLog string

	if sendLogs {
		svcLog = tail(l.G().Log, "service", logs.Service, numBytes)
		kbfsLog = tail(l.G().Log, "kbfs", logs.Kbfs, numBytes)
		desktopLog = tail(l.G().Log, "desktop", logs.Desktop, numBytes)
		updaterLog = tail(l.G().Log, "updater", logs.Updater, numBytes)
		// We don't use the systemd journal to store regular logs, since on
		// some systems (e.g. Ubuntu 16.04) it's not persisted across boots.
		// However we do use it for startup logs, since that's the only place
		// to get them in systemd mode.
		if l.G().Env.WantsSystemd() {
			startLog = tailSystemdJournal(l.G().Log, []string{"keybase.service", "kbfs.service", "keybase.gui.service"}, numBytes)
		} else {
			startLog = tail(l.G().Log, "start", logs.Start, numBytes)
		}
		installLog = tail(l.G().Log, "install", logs.Install, numBytes)
		systemLog = tail(l.G().Log, "system", logs.System, numBytes)
		gitLog = tail(l.G().Log, "git", logs.Git, numBytes)
	} else {
		kbfsLog = ""
		svcLog = ""
		desktopLog = ""
		updaterLog = ""
		startLog = ""
		installLog = ""
		systemLog = ""
		gitLog = ""
	}

	return l.post(statusJSON, feedback, kbfsLog, svcLog, desktopLog, updaterLog, startLog, installLog, systemLog, gitLog)
}
<|endoftext|>


<commit_before>package service

import (
	"bytes"
	"fmt"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
	"github.com/keybase/gregor/protocol/gregor1"
)

type gregorHandler struct {
	libkb.Contextified
	cli       rpc.GenericClient
	sessionID gregor1.SessionID
}

func newGregorHandler(g *libkb.GlobalContext) *gregorHandler {
	return &gregorHandler{Contextified: libkb.NewContextified(g)}
}

func (g *gregorHandler) HandlerName() string {
	return "keybase service"
}

func (g *gregorHandler) OnConnect(ctx context.Context, conn *rpc.Connection, cli rpc.GenericClient, srv *rpc.Server) error {
	g.G().Log.Debug("gregor handler: connected")

	g.G().Log.Debug("gregor handler: registering protocols")
	if err := srv.Register(gregor1.OutgoingProtocol(g)); err != nil {
		return err
	}

	g.cli = cli

	// handler needs to know when login, logout happens
	g.G().AddLoginHook(g)
	g.G().AddLogoutHook(g)

	return g.tryAuth(ctx)
}

func (g *gregorHandler) OnConnectError(err error, reconnectThrottleDuration time.Duration) {
	g.G().Log.Debug("gregor handler: connect error %s, reconnect throttle duration: %s", err, reconnectThrottleDuration)
}

func (g *gregorHandler) OnDisconnected(ctx context.Context, status rpc.DisconnectStatus) {
	g.G().Log.Debug("gregor handler: disconnected: %v", status)
}

func (g *gregorHandler) OnDoCommandError(err error, nextTime time.Duration) {
	g.G().Log.Debug("gregor handler: do command error: %s, nextTime: %s", err, nextTime)
}

func (g *gregorHandler) ShouldRetry(name string, err error) bool {
	g.G().Log.Debug("gregor handler: should retry: name %s, err %v (returning false)", name, err)
	return false
}

func (g *gregorHandler) ShouldRetryOnConnect(err error) bool {
	if err == nil {
		return false
	}

	g.G().Log.Debug("gregor handler: should retry on connect, err %v (returning true)", err)
	return true
}

func (g *gregorHandler) BroadcastMessage(ctx context.Context, m gregor1.Message) error {
	g.G().Log.Debug("gregor handler: broadcast: %+v", m)
	return nil
}

func (g *gregorHandler) OnLogin() {
	g.G().Log.Debug("gregor handler: OnLogin")
	if err := g.tryAuth(context.Background()); err != nil {
		g.G().Log.Debug("gregor handler: OnLogin tryAuth error: %s", err)
	}
}

func (g *gregorHandler) OnLogout() {
	g.G().Log.Debug("gregor handler: OnLogout")
	if err := g.revokeAuth(context.Background()); err != nil {
		g.G().Log.Debug("gregor handler: OnLogin revokeAuth error: %s", err)
	}
}

func (g *gregorHandler) tryAuth(ctx context.Context) error {
	loggedIn, err := g.G().LoginState().LoggedInLoad()
	if err != nil {
		return err
	}
	if !loggedIn {
		g.G().Log.Debug("gregor handler: not logged in, so not authenticating")
		return nil
	}

	var token string
	var uid keybase1.UID
	aerr := g.G().LoginState().LocalSession(func(s *libkb.Session) {
		token = s.GetToken()
		uid = s.GetUID()
	}, "gregor handler - login session")
	if aerr != nil {
		return aerr
	}
	g.G().Log.Debug("gregor handler: have session token")

	g.G().Log.Debug("gregor handler: authenticating")
	ac := gregor1.AuthClient{Cli: g.cli}
	auth, err := ac.AuthenticateSessionToken(ctx, gregor1.SessionToken(token))
	if err != nil {
		g.G().Log.Debug("gregor handler: auth error: %s", err)
		return err
	}

	g.G().Log.Debug("gregor handler: auth result: %+v", auth)
	if !bytes.Equal(auth.Uid, uid.ToBytes()) {
		return fmt.Errorf("gregor handler: auth result uid %x doesn't match session uid %q", auth.Uid, uid)
	}
	g.sessionID = auth.Sid

	return nil
}

func (g *gregorHandler) revokeAuth(ctx context.Context) error {
	if len(g.sessionID) == 0 {
		return nil
	}

	ac := gregor1.AuthClient{Cli: g.cli}
	if err := ac.RevokeSessionIDs(ctx, []gregor1.SessionID{g.sessionID}); err != nil {
		g.G().Log.Debug("gregor handler: revoke session id error: %s", err)
		return err
	}
	return nil
}
<commit_msg>Remove OnLogout call to revoke session id<commit_after>package service

import (
	"bytes"
	"fmt"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
	"github.com/keybase/gregor/protocol/gregor1"
)

type gregorHandler struct {
	libkb.Contextified
	cli       rpc.GenericClient
	sessionID gregor1.SessionID
}

func newGregorHandler(g *libkb.GlobalContext) *gregorHandler {
	return &gregorHandler{Contextified: libkb.NewContextified(g)}
}

func (g *gregorHandler) HandlerName() string {
	return "keybase service"
}

func (g *gregorHandler) OnConnect(ctx context.Context, conn *rpc.Connection, cli rpc.GenericClient, srv *rpc.Server) error {
	g.G().Log.Debug("gregor handler: connected")

	g.G().Log.Debug("gregor handler: registering protocols")
	if err := srv.Register(gregor1.OutgoingProtocol(g)); err != nil {
		return err
	}

	g.cli = cli

	// handler needs to know when login happens
	g.G().AddLoginHook(g)

	return g.tryAuth(ctx)
}

func (g *gregorHandler) OnConnectError(err error, reconnectThrottleDuration time.Duration) {
	g.G().Log.Debug("gregor handler: connect error %s, reconnect throttle duration: %s", err, reconnectThrottleDuration)
}

func (g *gregorHandler) OnDisconnected(ctx context.Context, status rpc.DisconnectStatus) {
	g.G().Log.Debug("gregor handler: disconnected: %v", status)
}

func (g *gregorHandler) OnDoCommandError(err error, nextTime time.Duration) {
	g.G().Log.Debug("gregor handler: do command error: %s, nextTime: %s", err, nextTime)
}

func (g *gregorHandler) ShouldRetry(name string, err error) bool {
	g.G().Log.Debug("gregor handler: should retry: name %s, err %v (returning false)", name, err)
	return false
}

func (g *gregorHandler) ShouldRetryOnConnect(err error) bool {
	if err == nil {
		return false
	}

	g.G().Log.Debug("gregor handler: should retry on connect, err %v (returning true)", err)
	return true
}

func (g *gregorHandler) BroadcastMessage(ctx context.Context, m gregor1.Message) error {
	g.G().Log.Debug("gregor handler: broadcast: %+v", m)
	return nil
}

func (g *gregorHandler) OnLogin() {
	g.G().Log.Debug("gregor handler: OnLogin")
	if err := g.tryAuth(context.Background()); err != nil {
		g.G().Log.Debug("gregor handler: OnLogin tryAuth error: %s", err)
	}
}

func (g *gregorHandler) tryAuth(ctx context.Context) error {
	loggedIn, err := g.G().LoginState().LoggedInLoad()
	if err != nil {
		return err
	}
	if !loggedIn {
		g.G().Log.Debug("gregor handler: not logged in, so not authenticating")
		return nil
	}

	var token string
	var uid keybase1.UID
	aerr := g.G().LoginState().LocalSession(func(s *libkb.Session) {
		token = s.GetToken()
		uid = s.GetUID()
	}, "gregor handler - login session")
	if aerr != nil {
		return aerr
	}
	g.G().Log.Debug("gregor handler: have session token")

	g.G().Log.Debug("gregor handler: authenticating")
	ac := gregor1.AuthClient{Cli: g.cli}
	auth, err := ac.AuthenticateSessionToken(ctx, gregor1.SessionToken(token))
	if err != nil {
		g.G().Log.Debug("gregor handler: auth error: %s", err)
		return err
	}

	g.G().Log.Debug("gregor handler: auth result: %+v", auth)
	if !bytes.Equal(auth.Uid, uid.ToBytes()) {
		return fmt.Errorf("gregor handler: auth result uid %x doesn't match session uid %q", auth.Uid, uid)
	}
	g.sessionID = auth.Sid

	return nil
}
<|endoftext|>