<commit_before>// Package imap partially implements the Internet Message Access Protocol as
// defined in RFC 3501. Specifically, AUTHENTICATE, STARTLS, SEARCH, and STORE
// remain unimplemented. Note also that UIDs are used in place of sequence
// numbers for all commands.
//
// Untagged IMAP responses are parsed into a Mailbox struct, which tracks all
// currently known information concerning the state of the remote mailbox.
// Because no significant information is returned through tagged responses,
// interaction with Mailbox is necessary for all queries.

package main

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	tp "net/textproto"
	"strings"
	"sync"
)

// The IMAP client. All methods on the client are thread-safe and executed
// synchronously.
type Client struct {
	// The underlying textproto connection may be used to extend the
	// functionality of this package; however, using Client.Cmd instead is
	// recommended, as doing so better preserves thread-safety.
	Text *tp.Conn

	Box  *Mailbox

	conn net.Conn // underlying raw connection.
	tags map[string]chan string
	tMut *sync.Mutex

	lit  chan string // channel where the literal string to be dumped is stored
}

// Represents the current known state of the remote server.
type Mailbox struct {
	capabilities []string
	mut          *sync.RWMutex
}

func (m *Mailbox) Capable(c string) bool {
	m.mut.RLock()
	for _, ca := range m.capabilities {
		if c == ca {
			return true
		}
	}
	m.mut.RUnlock()
	return false
}

// Dial creates an unsecured connection to the IMAP server at the given address
// and returns the corresponding Client.
func Dial(addr string) (*Client, error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	return NewClient(conn)
}

// DialTLS creates a TLS_secured connection to the IMAP server at the given
// address and returns the corresponding Client.
func DialTLS(addr string) (*Client, error) {
	conn, err := tls.Dial("tcp", addr, nil)
	if err != nil {
		return nil, err
	}
	return NewClient(conn)
}

// NewClient returns a new Client using an existing connection.
func NewClient(conn net.Conn) (*Client, error) {
	text := tp.NewConn(conn)
	client := &Client{
		Text: text,
		Box:  &Mailbox{
			capabilities: []string{},
			mut: new(sync.RWMutex),
		},
		conn: conn,
		tags: map[string]chan string{},
		tMut: new(sync.Mutex),
		lit: make(chan string),
	}

	input := make(chan string)

	// Read all input from conn
	go func() {
		l, err := text.ReadLine()
		for err == nil {
			input <- l
			l, err = text.ReadLine()
		}
		if err == io.EOF {
			close(input)
		} else {
			panic(err)
		}
	}()

	// Start the serving goroutine
	go func() {
		for {
			select {
			case l := <-input:
				if len(l) == 0 {
					// the channel is closed; theres nothing more
					client.tMut.Lock()
					for _, c := range client.tags {
						close(c)
					}
					client.tMut.Unlock()
					return
				}
				if l[0] == '+' {
					// server is ready for transmission of literal string
					client.Text.PrintfLine(<-client.lit)
					continue
				} else if isUntagged(l) {
					client.handleUntagged(l[2:])
					continue
				}
				// handle tagged response
				ps := strings.SplitN(l, " ", 2)
				tag := ps[0]
				l = ps[1]
				client.tMut.Lock()
				client.tags[tag] <- l
				close(client.tags[tag])
				delete(client.tags, tag)
				client.tMut.Unlock()
			}
		}
	}()
	return client, nil
}

func (c *Client) handleUntagged(l string) {
	c.Box.mut.Lock()
	switch l[0:strings.Index(l, " ")] {
	case "CAPABILITY":
		c.Box.capabilities = strings.Split(l, " ")[1:]
	default:
		println(l)
	}
	c.Box.mut.Unlock()
}

// Sends a command and retreives the tagged response.
func (c *Client) Cmd(format string, args ...interface{}) error {
	c.tMut.Lock()
	t := c.Text
	id := t.Next()
	tag := fmt.Sprintf("x%d", id)
	t.StartRequest(id)
	err := t.PrintfLine("%s %s", tag, fmt.Sprintf(format, args...))
	if err != nil {
		return err
	}
	t.EndRequest(id)

	t.StartResponse(id)
	defer t.EndResponse(id)

	ch := make(chan string)
	c.tags[tag] = ch
	c.tMut.Unlock()

	l := <-ch
	if l[0:2] == "OK" {
		return nil
	}
	return errors.New(l)
}

// Equivalent to Cmd, but the first argument (which will be rotated to be the
// last) is sent as a literal string.
func (c *Client) CmdLit(lit, format string, args ...interface{}) error {
	c.tMut.Lock()
	t := c.Text
	id := t.Next()
	tag := fmt.Sprintf("x%d", id)
	t.StartRequest(id)
	err := t.PrintfLine("%s %s {%d}", tag, fmt.Sprintf(format, args...), len(lit))
	if err != nil {
		return err
	}
	t.EndRequest(id)

	c.lit <- lit

	t.StartResponse(id)
	defer t.EndResponse(id)

	ch := make(chan string)
	c.tags[tag] = ch
	c.tMut.Unlock()

	l := <-ch
	if l[0:2] == "OK" {
		return nil
	}
	return errors.New(l)
}

func isUntagged(l string) bool {
	return l[0] != 'x' // all tags are x00
}

// Noop sends a NOOP command to the server, which may be abused to test that
// the connection is still working, or keep it active.
func (c *Client) Noop() error {
	return c.Cmd("NOOP")
}

// Capability determines the server's capabilities.
func (c *Client) Capability() error {
	return c.Cmd("CAPABILITY")
}

// Login authenticates a client using the provided username and password. This
// method is only secure if TLS is being used. AUTHENTICATE and STARTTLS are
// not supported.
func (c *Client) Login(username, password string) error {
	return c.Cmd("LOGIN %s %s", username, password)
}

// Logout closes the connection, after instructing the server to do the same.
func (c *Client) Logout() error {
	return c.Cmd("LOGOUT")
}

// Select selects the specified IMAP mailbox, updating its information in the
// Mailbox object.
func (c *Client) Select(mb string) error {
	return c.Cmd(`SELECT "%s"`, mb)
}

// Examine is identical to select, but marks the mailbox read-only.
func (c *Client) Examine(mb string) error {
	return c.Cmd(`EXAMINE "%s"`, mb)
}

// Create creates the named mailbox.
func (c *Client) Create(mb string) error {
	return c.Cmd(`CREATE "%s"`, mb)
}

// Delete deletes the named mailbox.
func (c *Client) Delete(mb string) error {
	return c.Cmd(`DELETE "%s"`, mb)
}

// Rename renames the named mailbox to the new name.
func (c *Client) Rename(mb, name string) error {
	return c.Cmd(`RENAME "%s" "%s"`, mb, name)
}

// Subscribe adds the named mailbox to the list of "active" or "subscribed"
// mailboxes, to be used with Lsub .
func (c *Client) Subscribe(mb string) error {
	return c.Cmd(`SUBSCRIBE "%s"`, mb)
}

// Unsubscribe removes the named mailbox from the server's list of "active"
// mailboxes.
func (c *Client) Unsubscribe(mb string) error {
	return c.Cmd(`UNSUBSCRIBE "%s"`, mb)
}

// List lists all folders within basename that match the wildcard expression
// mb.  The result is put into the Client's Mailbox struct.
func (c *Client) List(basename, mb string) error {
	return c.Cmd(`LIST "%s" "%s"`, basename, mb)
}

// Lsub is like List, but only operates on "active" mailboxes, as set with
// Subscribe and Unsubscribe.
func (c *Client) Lsub(basename, mb string) error {
	return c.Cmd(`LSUB "%s" "%s"`, basename, mb)
}

// Status queries the specified statuses of the indicated mailbox. This command
// should not be used on the currently selected mailbox. The legal status items
// are:
//
//	MESSAGES	The number of messages in the mailbox.
//	RECENT		The number of messages with the \Recent flag set.
//	UIDNEXT		The next unique identifier value of the mailbox.
//	UIDVALIDITY	The unique identifier validity value of the mailbox.
//	UNSEEN		The number of messages which do not have the \Seen flag set.
//
func (c *Client) Status(mb string, ss ...string) error {
	st := sliceAsString(ss)
	return c.Cmd(`STATUS "%s" %s`, mb, st)
}

// Append appends a message to the specified mailbox, which must exist.
//
// TODO handle flags and the optional date/time string.
func (c *Client) Append(mb, message string) error {
	return c.CmdLit(message, "APPEND \"%s\"", mb)
}

// Check tells the server to perform any necessary housekeeping.
func (c *Client) Check() error {
	return c.Cmd(`CHECK`)
}

// Close closes the selected mailbox, permanently deleting any marked messages
// in the process.
func (c *Client) Close() error {
	return c.Cmd(`CLOSE`)
}

// Expunge permanently removes all marked messages in the selected mailbox.
func (c *Client) Expunge() error {
	return c.Cmd(`EXPUNGE`)
}

// SEARCH remains unimplemented.

// FETCH

// STORE remains unimplemented.

// Copy copied the specified message(s) to the destination mailbox. The order
// of arguments to this method is the opposite of that actually sent to the
// server. Note that the messages are to be specified via UIDs, rather than
// sequence numbers.
func (c *Client) Copy(dest string, msgs ...string) error {
	return c.Cmd(`UID COPY %s %s`, strings.Join(msgs, ","), dest)
}

// Converts a slice of strings to a parenthesized list of space-separated
// strings.
func sliceAsString(ss []string) string {
	return "(" + strings.Join(ss, " ") + ")"
}
<commit_msg>Abandoning textproto<commit_after>// Package imap partially implements the Internet Message Access Protocol as
// defined in RFC 3501. Specifically, AUTHENTICATE, STARTLS, SEARCH, and STORE
// remain unimplemented. Note also that UIDs are used in place of sequence
// numbers for all commands.
//
// Untagged IMAP responses are parsed into a Mailbox struct, which tracks all
// currently known information concerning the state of the remote mailbox.
// Because no significant information is returned through tagged responses,
// interaction with Mailbox is necessary for all queries.

package main

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"sync"
)

// The IMAP client. All methods on the client are thread-safe and executed
// synchronously.
type Client struct {
	Box  *Mailbox

	conn net.Conn // underlying raw connection.
	bIn  *bufio.Reader
	bOut *bufio.Writer
	tags map[string]chan string
	tMut *sync.Mutex

	lit  chan string // channel where the literal string to be dumped is stored
}

// Represents the current known state of the remote server.
type Mailbox struct {
	capabilities []string
	mut          *sync.RWMutex
}

func (m *Mailbox) Capable(c string) bool {
	m.mut.RLock()
	for _, ca := range m.capabilities {
		if c == ca {
			return true
		}
	}
	m.mut.RUnlock()
	return false
}

// Dial creates an unsecured connection to the IMAP server at the given address
// and returns the corresponding Client.
func Dial(addr string) (*Client, error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	return NewClient(conn)
}

// DialTLS creates a TLS_secured connection to the IMAP server at the given
// address and returns the corresponding Client.
func DialTLS(addr string) (*Client, error) {
	conn, err := tls.Dial("tcp", addr, nil)
	if err != nil {
		return nil, err
	}
	return NewClient(conn)
}

// NewClient returns a new Client using an existing connection.
func NewClient(conn net.Conn) (*Client, error) {
	bOut := bufio.NewWriter(conn)
	bIn := bufio.NewReader(conn)
	client := &Client{
		Box:  &Mailbox{
			capabilities: []string{},
			mut: new(sync.RWMutex),
		},
		conn: conn,
		bIn: bIn,
		bOut: bOut,
		tags: map[string]chan string{},
		tMut: new(sync.Mutex),
		lit: make(chan string),
	}

	input := make(chan string)

	// Read all input from conn
	go func() {
		l, _, err := bIn.ReadLine()
		for err == nil {
			input <- string(l)
			l, _, err = bIn.ReadLine()
		}
		if err == io.EOF {
			close(input)
		} else {
			panic(err)
		}
	}()

	// Start the serving goroutine
	go func() {
		for {
			select {
			case l := <-input:
				if len(l) == 0 {
					// the channel is closed; theres nothing more
					client.tMut.Lock()
					for _, c := range client.tags {
						close(c)
					}
					client.tMut.Unlock()
					return
				}
				if l[0] == '+' {
					// server is ready for transmission of literal string
					fmt.Printf("%s\n", <-client.lit)
					continue
				} else if isUntagged(l) {
					client.handleUntagged(l[2:])
					continue
				}
				// handle tagged response
				ps := strings.SplitN(l, " ", 2)
				tag := ps[0]
				l = ps[1]
				client.tMut.Lock()
				client.tags[tag] <- l
				close(client.tags[tag])
				delete(client.tags, tag)
				client.tMut.Unlock()
			}
		}
	}()
	return client, nil
}

func (c *Client) handleUntagged(l string) {
	c.Box.mut.Lock()
	switch l[0:strings.Index(l, " ")] {
	case "CAPABILITY":
		c.Box.capabilities = strings.Split(l, " ")[1:]
	default:
		println(l)
	}
	c.Box.mut.Unlock()
}

var last_id = 0

func (c *Client) Next() int {
	last_id = last_id + 1
	return last_id
}

// Sends a command and retreives the tagged response.
func (c *Client) Cmd(format string, args ...interface{}) error {
	c.tMut.Lock()
	id := c.Next()
	tag := fmt.Sprintf("x%d", id)
	_, err := fmt.Fprintf(c.bOut, "%s %s\r\n", tag, fmt.Sprintf(format, args...))
	if err != nil { return err }
	err = c.bOut.Flush()
	if err != nil { return err }

	ch := make(chan string)
	c.tags[tag] = ch
	c.tMut.Unlock()

	l := <-ch
	if l[0:2] == "OK" {
		return nil
	}
	return errors.New(l)
}

// Equivalent to Cmd, but the first argument (which will be rotated to be the
// last) is sent as a literal string.
func (c *Client) CmdLit(lit, format string, args ...interface{}) error {
	c.tMut.Lock()
	id := c.Next()
	tag := fmt.Sprintf("x%d", id)
	_, err := fmt.Fprintf(c.bOut, "%s %s {%d}", tag, fmt.Sprintf(format, args...), len(lit))
	if err != nil {
		return err
	}

	c.lit <- lit

	ch := make(chan string)
	c.tags[tag] = ch
	c.tMut.Unlock()

	l := <-ch
	if l[0:2] == "OK" {
		return nil
	}
	return errors.New(l)
}

func isUntagged(l string) bool {
	return l[0] != 'x' // all tags are x00
}

// Noop sends a NOOP command to the server, which may be abused to test that
// the connection is still working, or keep it active.
func (c *Client) Noop() error {
	return c.Cmd("NOOP")
}

// Capability determines the server's capabilities.
func (c *Client) Capability() error {
	return c.Cmd("CAPABILITY")
}

// Login authenticates a client using the provided username and password. This
// method is only secure if TLS is being used. AUTHENTICATE and STARTTLS are
// not supported.
func (c *Client) Login(username, password string) error {
	return c.Cmd("LOGIN %s %s", username, password)
}

// Logout closes the connection, after instructing the server to do the same.
func (c *Client) Logout() error {
	return c.Cmd("LOGOUT")
}

// Select selects the specified IMAP mailbox, updating its information in the
// Mailbox object.
func (c *Client) Select(mb string) error {
	return c.Cmd(`SELECT "%s"`, mb)
}

// Examine is identical to select, but marks the mailbox read-only.
func (c *Client) Examine(mb string) error {
	return c.Cmd(`EXAMINE "%s"`, mb)
}

// Create creates the named mailbox.
func (c *Client) Create(mb string) error {
	return c.Cmd(`CREATE "%s"`, mb)
}

// Delete deletes the named mailbox.
func (c *Client) Delete(mb string) error {
	return c.Cmd(`DELETE "%s"`, mb)
}

// Rename renames the named mailbox to the new name.
func (c *Client) Rename(mb, name string) error {
	return c.Cmd(`RENAME "%s" "%s"`, mb, name)
}

// Subscribe adds the named mailbox to the list of "active" or "subscribed"
// mailboxes, to be used with Lsub .
func (c *Client) Subscribe(mb string) error {
	return c.Cmd(`SUBSCRIBE "%s"`, mb)
}

// Unsubscribe removes the named mailbox from the server's list of "active"
// mailboxes.
func (c *Client) Unsubscribe(mb string) error {
	return c.Cmd(`UNSUBSCRIBE "%s"`, mb)
}

// List lists all folders within basename that match the wildcard expression
// mb.  The result is put into the Client's Mailbox struct.
func (c *Client) List(basename, mb string) error {
	return c.Cmd(`LIST "%s" "%s"`, basename, mb)
}

// Lsub is like List, but only operates on "active" mailboxes, as set with
// Subscribe and Unsubscribe.
func (c *Client) Lsub(basename, mb string) error {
	return c.Cmd(`LSUB "%s" "%s"`, basename, mb)
}

// Status queries the specified statuses of the indicated mailbox. This command
// should not be used on the currently selected mailbox. The legal status items
// are:
//
//	MESSAGES	The number of messages in the mailbox.
//	RECENT		The number of messages with the \Recent flag set.
//	UIDNEXT		The next unique identifier value of the mailbox.
//	UIDVALIDITY	The unique identifier validity value of the mailbox.
//	UNSEEN		The number of messages which do not have the \Seen flag set.
//
func (c *Client) Status(mb string, ss ...string) error {
	st := sliceAsString(ss)
	return c.Cmd(`STATUS "%s" %s`, mb, st)
}

// Append appends a message to the specified mailbox, which must exist.
//
// TODO handle flags and the optional date/time string.
func (c *Client) Append(mb, message string) error {
	return c.CmdLit(message, "APPEND \"%s\"", mb)
}

// Check tells the server to perform any necessary housekeeping.
func (c *Client) Check() error {
	return c.Cmd(`CHECK`)
}

// Close closes the selected mailbox, permanently deleting any marked messages
// in the process.
func (c *Client) Close() error {
	return c.Cmd(`CLOSE`)
}

// Expunge permanently removes all marked messages in the selected mailbox.
func (c *Client) Expunge() error {
	return c.Cmd(`EXPUNGE`)
}

// SEARCH remains unimplemented.

// FETCH

// STORE remains unimplemented.

// Copy copied the specified message(s) to the destination mailbox. The order
// of arguments to this method is the opposite of that actually sent to the
// server. Note that the messages are to be specified via UIDs, rather than
// sequence numbers.
func (c *Client) Copy(dest string, msgs ...string) error {
	return c.Cmd(`UID COPY %s %s`, strings.Join(msgs, ","), dest)
}

// Converts a slice of strings to a parenthesized list of space-separated
// strings.
func sliceAsString(ss []string) string {
	return "(" + strings.Join(ss, " ") + ")"
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor:
// - Aaron Meihm ameihm@mozilla.com
package oval

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
)

// An externalized version of package information. Data managers maintain
// their own format, but when a package is represented outside this library
// it will be converted to this type.
type ExternalizedPackage struct {
	Name    string
	Version string
}

type ParserError struct {
	s string
}

func (pe *ParserError) Error() string {
	return pe.s
}

type config struct {
	flagDebug          bool
	maxChecks          int
	centosRedhatKludge int
}

type dataMgr struct {
	dmwg        sync.WaitGroup
	initialized bool
	running     bool
	dpkg        dpkgDataMgr
	rpm         rpmDataMgr
}

func (d *dataMgr) dataMgrInit() {
	if d.initialized {
		panic("data manager already initialized")
	}
	d.dpkg.init()
	d.rpm.init()
	d.initialized = true
}

func (d *dataMgr) dataMgrRun(precognition bool) {
	if d.running {
		panic("data manager already running")
	}
	if !d.initialized {
		panic("data manager not initialized")
	}
	// If the precognition flag is set, the data manager will build it's
	// package database before being invoked.
	if precognition {
		d.dpkg.prepare()
		d.rpm.prepare()
	}
	d.dmwg.Add(1)
	go func() {
		d.dpkg.run()
		d.dmwg.Done()
	}()
	d.dmwg.Add(1)
	go func() {
		d.rpm.run()
		d.dmwg.Done()
	}()
	d.running = true
}

func (d *dataMgr) dataMgrClose() {
	close(d.dpkg.schan)
	close(d.rpm.schan)
	d.dmwg.Wait()
	d.running = false
	d.initialized = false
}

var parserCfg config
var dmgr dataMgr

func defaultParserConfig() config {
	return config{
		flagDebug:          false,
		maxChecks:          10,
		centosRedhatKludge: 0,
	}
}

func SetDebug(f bool) {
	parserCfg.flagDebug = f
}

func SetMaxChecks(i int) {
	parserCfg.maxChecks = i
}

func debugPrint(s string, args ...interface{}) {
	if !parserCfg.flagDebug {
		return
	}
	fmt.Fprintf(os.Stdout, s, args...)
}

func PackageQuery(tests []string) (matches []ExternalizedPackage) {
	dmgr.dataMgrInit()
	dmgr.dataMgrRun(true)
	defer func() {
		dmgr.dataMgrClose()
	}()

	var dr dpkgResponse
	var rr rpmResponse
	for _, x := range tests {
		dr = dmgr.dpkg.makeRequest(x, DPKG_SUBSTRING_MATCH)
		for _, y := range dr.pkgdata {
			matches = append(matches, y.externalize())
		}
		rr = dmgr.rpm.makeRequest(x, RPM_SUBSTRING_MATCH)
		for _, y := range rr.pkgdata {
			matches = append(matches, y.externalize())
		}
	}

	return matches
}

func Execute(od *GOvalDefinitions) []GOvalResult {
	var precognition bool = false
	debugPrint("executing all applicable checks\n")

	precognition = true

	dmgr.dataMgrInit()
	dmgr.dataMgrRun(precognition)

	results := make([]GOvalResult, 0)
	reschan := make(chan GOvalResult)
	curchecks := 0
	expect := len(od.Definitions.Definitions)
	for _, v := range od.Definitions.Definitions {
		debugPrint("executing definition %s...\n", v.ID)

		for {
			nodata := false
			select {
			case s := <-reschan:
				results = append(results, s)
				curchecks--
				expect--
			default:
				nodata = true
				break
			}
			if nodata {
				break
			}
		}

		if curchecks == parserCfg.maxChecks {
			// Block and wait for a free slot.
			s := <-reschan
			results = append(results, s)
			curchecks--
			expect--
		}
		go v.evaluate(reschan, od)
		curchecks++
	}

	for expect > 0 {
		s := <-reschan
		results = append(results, s)
		expect--
	}

	dmgr.dataMgrClose()

	return results
}

func Init() {
	parserCfg = defaultParserConfig()
}

func ParseBuffer(ovalbuf string) (*GOvalDefinitions, error) {
	var od GOvalDefinitions
	var perr ParserError

	parserCfg.centosRedhatKludge = centosDetection()

	bufrdr := strings.NewReader(ovalbuf)
	decoder := xml.NewDecoder(bufrdr)
	ok := decoder.Decode(&od)
	if ok != nil {
		perr.s = "error parsing data: invalid xml format?"
		return nil, &perr
	}

	return &od, nil
}

func Parse(path string) (*GOvalDefinitions, error) {
	var perr ParserError
	var b bytes.Buffer

	parserCfg.centosRedhatKludge = centosDetection()

	debugPrint("parsing %s\n", path)

	xfd, err := os.Open(path)
	if err != nil {
		perr.s = fmt.Sprintf("error opening file: %v", err)
		return nil, &perr
	}
	io.Copy(&b, xfd)

	xfd.Close()

	return ParseBuffer(b.String())
}
<commit_msg>remove precognition setting for data manager, always build on startup<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor:
// - Aaron Meihm ameihm@mozilla.com
package oval

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
)

// An externalized version of package information. Data managers maintain
// their own format, but when a package is represented outside this library
// it will be converted to this type.
type ExternalizedPackage struct {
	Name    string
	Version string
}

type ParserError struct {
	s string
}

func (pe *ParserError) Error() string {
	return pe.s
}

type config struct {
	flagDebug          bool
	maxChecks          int
	centosRedhatKludge int
}

type dataMgr struct {
	dmwg        sync.WaitGroup
	initialized bool
	running     bool
	dpkg        dpkgDataMgr
	rpm         rpmDataMgr
}

func (d *dataMgr) dataMgrInit() {
	if d.initialized {
		panic("data manager already initialized")
	}
	d.dpkg.init()
	d.rpm.init()
	d.initialized = true
}

func (d *dataMgr) dataMgrRun() {
	if d.running {
		panic("data manager already running")
	}
	if !d.initialized {
		panic("data manager not initialized")
	}

	d.dpkg.prepare()
	d.rpm.prepare()

	d.dmwg.Add(1)
	go func() {
		d.dpkg.run()
		d.dmwg.Done()
	}()
	d.dmwg.Add(1)
	go func() {
		d.rpm.run()
		d.dmwg.Done()
	}()

	d.running = true
}

func (d *dataMgr) dataMgrClose() {
	close(d.dpkg.schan)
	close(d.rpm.schan)
	d.dmwg.Wait()
	d.running = false
	d.initialized = false
}

var parserCfg config
var dmgr dataMgr

func defaultParserConfig() config {
	return config{
		flagDebug:          false,
		maxChecks:          10,
		centosRedhatKludge: 0,
	}
}

func SetDebug(f bool) {
	parserCfg.flagDebug = f
}

func SetMaxChecks(i int) {
	parserCfg.maxChecks = i
}

func debugPrint(s string, args ...interface{}) {
	if !parserCfg.flagDebug {
		return
	}
	fmt.Fprintf(os.Stdout, s, args...)
}

func PackageQuery(tests []string) (matches []ExternalizedPackage) {
	dmgr.dataMgrInit()
	dmgr.dataMgrRun()
	defer func() {
		dmgr.dataMgrClose()
	}()

	var dr dpkgResponse
	var rr rpmResponse
	for _, x := range tests {
		dr = dmgr.dpkg.makeRequest(x, DPKG_SUBSTRING_MATCH)
		for _, y := range dr.pkgdata {
			matches = append(matches, y.externalize())
		}
		rr = dmgr.rpm.makeRequest(x, RPM_SUBSTRING_MATCH)
		for _, y := range rr.pkgdata {
			matches = append(matches, y.externalize())
		}
	}

	return matches
}

func Execute(od *GOvalDefinitions) []GOvalResult {
	debugPrint("executing all applicable checks\n")

	dmgr.dataMgrInit()
	dmgr.dataMgrRun()

	results := make([]GOvalResult, 0)
	reschan := make(chan GOvalResult)
	curchecks := 0
	expect := len(od.Definitions.Definitions)
	for _, v := range od.Definitions.Definitions {
		debugPrint("executing definition %s...\n", v.ID)

		for {
			nodata := false
			select {
			case s := <-reschan:
				results = append(results, s)
				curchecks--
				expect--
			default:
				nodata = true
				break
			}
			if nodata {
				break
			}
		}

		if curchecks == parserCfg.maxChecks {
			// Block and wait for a free slot.
			s := <-reschan
			results = append(results, s)
			curchecks--
			expect--
		}
		go v.evaluate(reschan, od)
		curchecks++
	}

	for expect > 0 {
		s := <-reschan
		results = append(results, s)
		expect--
	}

	dmgr.dataMgrClose()

	return results
}

func Init() {
	parserCfg = defaultParserConfig()
}

func ParseBuffer(ovalbuf string) (*GOvalDefinitions, error) {
	var od GOvalDefinitions
	var perr ParserError

	parserCfg.centosRedhatKludge = centosDetection()

	bufrdr := strings.NewReader(ovalbuf)
	decoder := xml.NewDecoder(bufrdr)
	ok := decoder.Decode(&od)
	if ok != nil {
		perr.s = "error parsing data: invalid xml format?"
		return nil, &perr
	}

	return &od, nil
}

func Parse(path string) (*GOvalDefinitions, error) {
	var perr ParserError
	var b bytes.Buffer

	parserCfg.centosRedhatKludge = centosDetection()

	debugPrint("parsing %s\n", path)

	xfd, err := os.Open(path)
	if err != nil {
		perr.s = fmt.Sprintf("error opening file: %v", err)
		return nil, &perr
	}
	io.Copy(&b, xfd)

	xfd.Close()

	return ParseBuffer(b.String())
}
<|endoftext|>


<commit_before>package charmap

// Private types for rune and string slices
type unicodeSequence []string

type sequenceIndex interface {
	index(char string) int
}

// Private map to hold all sequences
type charMap map[string]sequenceIndex

// Languagewise unicode ranges
var langBases = map[string]int{
	"en_US": 0,
	"en_IN": 0,
	"hi_IN": '\u0901',
	"bn_IN": '\u0981',
	"pa_IN": '\u0a01',
	"gu_IN": '\u0a81',
	"or_IN": '\u0b01',
	"ta_IN": '\u0b81',
	"te_IN": '\u0c01',
	"kn_IN": '\u0c81',
	"ml_IN": '\u0D01',
}

// Slices to hold unicode range for each languagges
var devaAlphabets = make(unicodeSequence, 80)
var bengAlphabets = make(unicodeSequence, 80)
var guruAlphabets = make(unicodeSequence, 80)
var gujrAlphabets = make(unicodeSequence, 80)
var oryaAlphabets = make(unicodeSequence, 80)
var tamlAlphabets = make(unicodeSequence, 80)
var teluAlphabets = make(unicodeSequence, 80)
var kndaAlphabets = make(unicodeSequence, 80)
var mlymAlphabets = make(unicodeSequence, 80)

// Soundex values for English alphabet series
var soundexEnglish = unicodeSequence{`0`, `1`, `2`, `3`, `0`, `1`, `2`, `0`, `0`, `2`, `2`, `4`, `5`, `5`, `0`, `1`, `2`, `6`, `2`, `3`, `0`, `1`, `0`, `2`, `0`, `2`}

// Soundex values for Indian language unicode series.
var soundexIndic = unicodeSequence{`0`, `N`, `0`, `0`, `A`, `A`, `B`, `B`, `C`, `C`, `P`, `Q`, `0`, `D`, `D`, `D`, `E`, `E`, `E`, `E`, `F`, `F`, `F`, `F`, `G`, `H`, `H`, `H`, `H`, `G`, `I`, `I`, `I`, `I`, `J`, `K`, `K`, `K`, `K`, `L`, `L`, `M`, `M`, `M`, `M`, `N`, `O`, `P`, `P`, `Q`, `Q`, `Q`, `R`, `S`, `S`, `S`, `T`, `0`, `0`, `0`, `0`, `A`, `B`, `B`, `C`, `C`, `P`, `P`, `E`, `D`, `D`, `D`, `D`, `E`, `E`, `E`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `E`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `P`, `Q`, `Q`, `Q`, `0`, `0`, `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `J`, `J`, `Q`, `P`, `P`, `F`}

// ISO15919 series specific to Indian languages
var iso15919IndicSeries = unicodeSequence{`m̐`, `ṁ`, `ḥ`, ``, `a`, `ā`, `i`, `ī`, `u`, `ū`, `ṛ`, `ḷ`, `ê`, `e`, `ē`, `ai`, `ô`, `o`, `ō`, `au`, `ka`, `kha`, `ga`, `gha`, `ṅa`, `ca`, `cha`, `ja`, `jha`, `ña`, `ṭa`, `ṭha`, `ḍa`, `ḍha`, `ṇa`, `ta`, `tha`, `da`, `dha`, `na`, `ṉa`, `pa`, `pha`, `ba`, `bha`, `ma`, `ya`, `ra`, `ṟa`, `la`, `ḷa`, `ḻa`, `va`, `śa`, `ṣa`, `sa`, `ha`, ``, ``, ``, `'`, `ā`, `i`, `ī`, `u`, `ū`, `ṛ`, `ṝ`, `ê`, `e`, `ē`, `ai`, `ô`, `o`, `ō`, `au`, ``, ``, ``, `oṃ`, ``, ``, ``, ``, ``, ``, ``, `qa`, `ḵẖa`, `ġ`, `za`, `ṛa`, `ṛha`, `fa`, `ẏa`, `ṝ`, `ḹ`, `ḷ`, `ḹ`, `.`, `..`, `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `…`, ``, ``, ``, ``, ``, ``, ``}

// IPA series specific for Indian languages
var ipaIndicSeries = unicodeSequence{`m`, `m`, ``, ``, `ə`, `aː`, `i`, `iː`, `u`, `uː`, `r̩`, `l̩`, `æ`, `e`, `eː`, `ɛː`, `ɔ`, `o`, `oː`, `ow`, `kə`, `kʰə`, `gə`, `gʱə`, `ŋə`, `ʧə`, `ʧʰə`, `ʤə`, `ʤʱə`, `ɲə`, `ʈə`, `ʈʰə`, `ɖə`, `ɖʱə`, `ɳə`, `t̪ə`, `t̪ʰə`, `d̪ə`, `d̪ʱə`, `n̪ə`, `nə`, `pə`, `pʰə`, `bə`, `bʱə`, `mə`, `jə`, `ɾə`, `rə`, `lə`, `ɭə`, `ɻə`, `ʋə`, `ɕə`, `ʂə`, `sə`, `ɦə`, ``, ``, ``, `ഽ`, `aː`, `i`, `iː`, `u`, `uː`, `r̩`, `l̩`, `e`, `eː`, `ɛː`, `ɔ`, `o`, `oː`, `ow`, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, `ow`, ``, ``, ``, ``, ``, ``, ``, ``, `r̩ː`, `l̩ː`, ``, ``, ``, ``, `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `൰`, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``}

// Map to hold rune sequence of each languages
var langMap = charMap{
	"hi_IN":      devaAlphabets,
	"bn_IN":      bengAlphabets,
	"pa_IN":      guruAlphabets,
	"gu_IN":      gujrAlphabets,
	"or_IN":      oryaAlphabets,
	"ta_IN":      tamlAlphabets,
	"te_IN":      teluAlphabets,
	"kn_IN":      kndaAlphabets,
	"ml_IN":      mlymAlphabets,
	"soundex_en": soundexEnglish,
	"soundex_in": soundexIndic,
	"IPA":        ipaIndicSeries,
	"ISO15919":   iso15919IndicSeries,
}

func initializeUnicodeRange(slice unicodeSequence, begin int) {
	for i := 0; i < len(slice); i++ {
		slice[i] = string(begin+i)
	}
}

func init() {
	for key, value := range langMap {
		if key != "ISO15919" && key != "IPA" {
			initializeUnicodeRange(value.(unicodeSequence), langBases[key])
		}
	}
}

func (r unicodeSequence) index(char string) int {
	for i, value := range r {
		if value == char {
			return i
		}
	}

	return -1
}

func LanguageOf(char string) string {
	for lang, langRange := range langMap {
		if langRange.index(char) != -1 {
			return lang
		}
	}
	// Still not found then something wrong
	return "unknown"
}

func CharCompare(char1, char2 string) bool {

	if char1 == char2 {
		return true
	}

	char1Index := langMap[LanguageOf(char1)].index(char1)
	char2Index := langMap[LanguageOf(char2)].index(char2)

	if char1Index == char2Index {
		return true
	}

	return false
}
<commit_msg>Introduce english alphabets in series<commit_after>package charmap

// Private types for rune and string slices
type unicodeSequence []string

type sequenceIndex interface {
	index(char string) int
}

// Private map to hold all sequences
type charMap map[string]sequenceIndex

// Languagewise unicode ranges
var langBases = map[string]int{
	"en_US": 0,
	"en_IN": 0,
	"hi_IN": '\u0901',
	"bn_IN": '\u0981',
	"pa_IN": '\u0a01',
	"gu_IN": '\u0a81',
	"or_IN": '\u0b01',
	"ta_IN": '\u0b81',
	"te_IN": '\u0c01',
	"kn_IN": '\u0c81',
	"ml_IN": '\u0D01',
}

// Slices to hold unicode range for each languagges
var devaAlphabets = make(unicodeSequence, 80)
var bengAlphabets = make(unicodeSequence, 80)
var guruAlphabets = make(unicodeSequence, 80)
var gujrAlphabets = make(unicodeSequence, 80)
var oryaAlphabets = make(unicodeSequence, 80)
var tamlAlphabets = make(unicodeSequence, 80)
var teluAlphabets = make(unicodeSequence, 80)
var kndaAlphabets = make(unicodeSequence, 80)
var mlymAlphabets = make(unicodeSequence, 80)

var enUsAlphabets = unicodeSequence{`a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`, `j`, `k`, `l`, `m`, `n`, `o`, `p`, `q`, `r`, `s`, `t`, `u`, `v`, `w`, `x`, `y`, `z`}

// Soundex values for English alphabet series
var soundexEnglish = unicodeSequence{`0`, `1`, `2`, `3`, `0`, `1`, `2`, `0`, `0`, `2`, `2`, `4`, `5`, `5`, `0`, `1`, `2`, `6`, `2`, `3`, `0`, `1`, `0`, `2`, `0`, `2`}

// Soundex values for Indian language unicode series.
var soundexIndic = unicodeSequence{`0`, `N`, `0`, `0`, `A`, `A`, `B`, `B`, `C`, `C`, `P`, `Q`, `0`, `D`, `D`, `D`, `E`, `E`, `E`, `E`, `F`, `F`, `F`, `F`, `G`, `H`, `H`, `H`, `H`, `G`, `I`, `I`, `I`, `I`, `J`, `K`, `K`, `K`, `K`, `L`, `L`, `M`, `M`, `M`, `M`, `N`, `O`, `P`, `P`, `Q`, `Q`, `Q`, `R`, `S`, `S`, `S`, `T`, `0`, `0`, `0`, `0`, `A`, `B`, `B`, `C`, `C`, `P`, `P`, `E`, `D`, `D`, `D`, `D`, `E`, `E`, `E`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `E`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `P`, `Q`, `Q`, `Q`, `0`, `0`, `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `J`, `J`, `Q`, `P`, `P`, `F`}

// ISO15919 series specific to Indian languages
var iso15919IndicSeries = unicodeSequence{`m̐`, `ṁ`, `ḥ`, ``, `a`, `ā`, `i`, `ī`, `u`, `ū`, `ṛ`, `ḷ`, `ê`, `e`, `ē`, `ai`, `ô`, `o`, `ō`, `au`, `ka`, `kha`, `ga`, `gha`, `ṅa`, `ca`, `cha`, `ja`, `jha`, `ña`, `ṭa`, `ṭha`, `ḍa`, `ḍha`, `ṇa`, `ta`, `tha`, `da`, `dha`, `na`, `ṉa`, `pa`, `pha`, `ba`, `bha`, `ma`, `ya`, `ra`, `ṟa`, `la`, `ḷa`, `ḻa`, `va`, `śa`, `ṣa`, `sa`, `ha`, ``, ``, ``, `'`, `ā`, `i`, `ī`, `u`, `ū`, `ṛ`, `ṝ`, `ê`, `e`, `ē`, `ai`, `ô`, `o`, `ō`, `au`, ``, ``, ``, `oṃ`, ``, ``, ``, ``, ``, ``, ``, `qa`, `ḵẖa`, `ġ`, `za`, `ṛa`, `ṛha`, `fa`, `ẏa`, `ṝ`, `ḹ`, `ḷ`, `ḹ`, `.`, `..`, `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `…`, ``, ``, ``, ``, ``, ``, ``}

// IPA series specific for Indian languages
var ipaIndicSeries = unicodeSequence{`m`, `m`, ``, ``, `ə`, `aː`, `i`, `iː`, `u`, `uː`, `r̩`, `l̩`, `æ`, `e`, `eː`, `ɛː`, `ɔ`, `o`, `oː`, `ow`, `kə`, `kʰə`, `gə`, `gʱə`, `ŋə`, `ʧə`, `ʧʰə`, `ʤə`, `ʤʱə`, `ɲə`, `ʈə`, `ʈʰə`, `ɖə`, `ɖʱə`, `ɳə`, `t̪ə`, `t̪ʰə`, `d̪ə`, `d̪ʱə`, `n̪ə`, `nə`, `pə`, `pʰə`, `bə`, `bʱə`, `mə`, `jə`, `ɾə`, `rə`, `lə`, `ɭə`, `ɻə`, `ʋə`, `ɕə`, `ʂə`, `sə`, `ɦə`, ``, ``, ``, `ഽ`, `aː`, `i`, `iː`, `u`, `uː`, `r̩`, `l̩`, `e`, `eː`, `ɛː`, `ɔ`, `o`, `oː`, `ow`, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, `ow`, ``, ``, ``, ``, ``, ``, ``, ``, `r̩ː`, `l̩ː`, ``, ``, ``, ``, `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `൰`, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``}

// Map to hold rune sequence of each languages
var langMap = charMap{
	"hi_IN":      devaAlphabets,
	"bn_IN":      bengAlphabets,
	"pa_IN":      guruAlphabets,
	"gu_IN":      gujrAlphabets,
	"or_IN":      oryaAlphabets,
	"ta_IN":      tamlAlphabets,
	"te_IN":      teluAlphabets,
	"kn_IN":      kndaAlphabets,
	"ml_IN":      mlymAlphabets,
	"soundex_en": soundexEnglish,
	"soundex_in": soundexIndic,
	"IPA":        ipaIndicSeries,
	"ISO15919":   iso15919IndicSeries,
}

func initializeUnicodeRange(slice unicodeSequence, begin int) {
	for i := 0; i < len(slice); i++ {
		slice[i] = string(begin+i)
	}
}

func init() {
	for key, value := range langMap {
		if key != "ISO15919" && key != "IPA" {
			initializeUnicodeRange(value.(unicodeSequence), langBases[key])
		}
	}
}

func (r unicodeSequence) index(char string) int {
	for i, value := range r {
		if value == char {
			return i
		}
	}

	return -1
}

func LanguageOf(char string) string {
	for lang, langRange := range langMap {
		if langRange.index(char) != -1 {
			return lang
		}
	}
	// Still not found then something wrong
	return "unknown"
}

func CharCompare(char1, char2 string) bool {

	if char1 == char2 {
		return true
	}

	char1Index := langMap[LanguageOf(char1)].index(char1)
	char2Index := langMap[LanguageOf(char2)].index(char2)

	if char1Index == char2Index {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>package twitch

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

var favorites []string
var tournaments []string

func WatchFavorites(callback func(m string)) {
	favorites = FavoriteDota2Streams()
	for {
		time.Sleep(time.Second * 30)
		newFavorites := FavoriteDota2Streams()
		if len(newFavorites) == 0 {
			continue // sometimes the api delivers no results
		}

		for _, g := range newFavorites {
			if !inside(favorites, g) {
				callback(g + " started streaming.")
			}
		}
		favorites = newFavorites
	}
}

func WatchTournaments(callback func(m string)) {
	tournaments = TournamentStreams()
	for {
		time.Sleep(time.Second * 30)
		newTournaments := TournamentStreams()
		if len(newTournaments) == 0 {
			continue // sometimes the api delivers no results
		}

		for _, g := range newTournaments {
			if !inside(tournaments, g) {
				callback(g)
			}
		}
		tournaments = newTournaments
	}
}

func inside(haystack []string, needle string) bool {
	for _, g := range haystack {
		if g == needle {
			return true
		}
	}
	return false
}

func FavoriteDota2Streams() []string {
	f := favoriteList()
	concatenated := strings.Replace(f, "\n", ",", -1)
	requestURL := "https://api.twitch.tv/kraken/streams?game=Dota+2&channel=" + concatenated
	res, err := http.Get(requestURL)
	if err != nil {
		log.Fatal(err)
	}
	streams, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var dat JSONResult
	if err := json.Unmarshal(streams, &dat); err != nil {
		log.Fatal(err)
	}

	sslice := make([]string, 0)
	for _, g := range dat.Streams {
		s := fmt.Sprintf("\u0002%s\u000F %s", g.Channel.DisplayName, g.Channel.URL)
		if len(g.Channel.URL) == 0 { // sometimes the url is non existent
			continue
		}
		sslice = append(sslice, s)
	}

	return sslice
}

func TournamentStreams() []string {
	t := tournamentsList()
	concatenated := strings.Replace(t, "\n", ",", -1)
	requestURL := "https://api.twitch.tv/kraken/streams?game=Dota+2&channel=" + concatenated
	res, err := http.Get(requestURL)
	if err != nil {
		log.Fatal(err)
	}
	streams, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var dat JSONResult
	if err := json.Unmarshal(streams, &dat); err != nil {
		log.Fatal(err)
	}

	sslice := make([]string, 0)
	for _, g := range dat.Streams {
		if isRebroadcast(g.Channel.Status) {
			continue
		}

		if containsVersus(g.Channel.Status) || containsLive(g.Channel.Status) {
			s := fmt.Sprintf("%s %s", g.Channel.Status, g.Channel.URL)
			sslice = append(sslice, s)
		}
	}

	return sslice
}

func TopDota2Streams() []string {
	requestURL := "https://api.twitch.tv/kraken/streams?game=Dota+2&language=en&limit=15"
	res, err := http.Get(requestURL)
	if err != nil {
		log.Fatal(err)
	}
	streams, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var dat JSONResult
	if err := json.Unmarshal(streams, &dat); err != nil {
		log.Fatal(err)
	}

	limitOfStreams := 5
	c := 0

	sslice := make([]string, 0)
	for _, g := range dat.Streams {
		if c == limitOfStreams {
			break
		}
		if !isBlacklisted(g.Channel.Name) && g.Viewers > 100 && !isRebroadcast(g.Channel.Status) {
			s := fmt.Sprintf("\u0002%s\u000F (%d) %s", g.Channel.DisplayName, g.Viewers, g.Channel.URL)
			sslice = append(sslice, s)
			c++
		}
	}

	return sslice
}

func Dota2Streams() []string {
	// get all dota streams, even russians oO
	requestURL := "https://api.twitch.tv/kraken/streams?game=Dota+2&limit=4"
	res, err := http.Get(requestURL)
	if err != nil {
		log.Fatal(err)
	}
	streams, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var dat JSONResult
	if err := json.Unmarshal(streams, &dat); err != nil {
		log.Fatal(err)
	}

	sslice := make([]string, 0)
	for _, g := range dat.Streams {
		s := fmt.Sprintf("\u0002%s\u000F (%d) %s", g.Channel.DisplayName, g.Viewers, g.Channel.URL)
		sslice = append(sslice, s)
	}
	return sslice
}

func clientID() string {
	file, e := ioutil.ReadFile("./client.id")
	if e != nil {
		panic(e)
	}
	return string(file)
}

func favoriteList() string {
	file, e := ioutil.ReadFile("./favorites.txt")
	if e != nil {
		panic(e)
	}
	return string(file)
}

func tournamentsList() string {
	file, e := ioutil.ReadFile("./tournaments.txt")
	if e != nil {
		panic(e)
	}
	return string(file)
}

func isRebroadcast(stream string) bool {
	s := strings.ToLower(stream)
	return strings.Contains(s, "rebroadcast")
}

func containsVersus(stream string) bool {
	s := strings.ToLower(stream)
	return strings.Contains(s, " vs ")
}

func containsLive(stream string) bool {
	s := strings.ToLower(stream)
	return strings.Contains(s, "live")
}

func blacklistStreams() []string {
	file, e := ioutil.ReadFile("./blacklist.txt")
	if e != nil {
		panic(e)
	}
	return strings.Split(string(file), "\n")
}

func isBlacklisted(stream string) bool {
	blacklist := blacklistStreams()
	for _, b := range blacklist {
		if b == stream {
			return true
		}
	}
	return false
}

// JSON structs
type JSONResult struct {
	Streams []JSONStreams `json:"streams"`
}

type JSONStreams struct {
	Channel JSONChannel `json:"channel"`
	Viewers int         `json:"viewers"`
}

type JSONChannel struct {
	DisplayName string `json:"display_name"`
	Name        string `json:"name"`
	URL         string `json:"url"`
	Status      string `json:"status"`
}
<commit_msg>increased all streams to 8<commit_after>package twitch

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

var favorites []string
var tournaments []string

func WatchFavorites(callback func(m string)) {
	favorites = FavoriteDota2Streams()
	for {
		time.Sleep(time.Second * 30)
		newFavorites := FavoriteDota2Streams()
		if len(newFavorites) == 0 {
			continue // sometimes the api delivers no results
		}

		for _, g := range newFavorites {
			if !inside(favorites, g) {
				callback(g + " started streaming.")
			}
		}
		favorites = newFavorites
	}
}

func WatchTournaments(callback func(m string)) {
	tournaments = TournamentStreams()
	for {
		time.Sleep(time.Second * 30)
		newTournaments := TournamentStreams()
		if len(newTournaments) == 0 {
			continue // sometimes the api delivers no results
		}

		for _, g := range newTournaments {
			if !inside(tournaments, g) {
				callback(g)
			}
		}
		tournaments = newTournaments
	}
}

func inside(haystack []string, needle string) bool {
	for _, g := range haystack {
		if g == needle {
			return true
		}
	}
	return false
}

func FavoriteDota2Streams() []string {
	f := favoriteList()
	concatenated := strings.Replace(f, "\n", ",", -1)
	requestURL := "https://api.twitch.tv/kraken/streams?game=Dota+2&channel=" + concatenated
	res, err := http.Get(requestURL)
	if err != nil {
		log.Fatal(err)
	}
	streams, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var dat JSONResult
	if err := json.Unmarshal(streams, &dat); err != nil {
		log.Fatal(err)
	}

	sslice := make([]string, 0)
	for _, g := range dat.Streams {
		s := fmt.Sprintf("\u0002%s\u000F %s", g.Channel.DisplayName, g.Channel.URL)
		if len(g.Channel.URL) == 0 { // sometimes the url is non existent
			continue
		}
		sslice = append(sslice, s)
	}

	return sslice
}

func TournamentStreams() []string {
	t := tournamentsList()
	concatenated := strings.Replace(t, "\n", ",", -1)
	requestURL := "https://api.twitch.tv/kraken/streams?game=Dota+2&channel=" + concatenated
	res, err := http.Get(requestURL)
	if err != nil {
		log.Fatal(err)
	}
	streams, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var dat JSONResult
	if err := json.Unmarshal(streams, &dat); err != nil {
		log.Fatal(err)
	}

	sslice := make([]string, 0)
	for _, g := range dat.Streams {
		if isRebroadcast(g.Channel.Status) {
			continue
		}

		if containsVersus(g.Channel.Status) || containsLive(g.Channel.Status) {
			s := fmt.Sprintf("%s %s", g.Channel.Status, g.Channel.URL)
			sslice = append(sslice, s)
		}
	}

	return sslice
}

func TopDota2Streams() []string {
	requestURL := "https://api.twitch.tv/kraken/streams?game=Dota+2&language=en&limit=15"
	res, err := http.Get(requestURL)
	if err != nil {
		log.Fatal(err)
	}
	streams, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var dat JSONResult
	if err := json.Unmarshal(streams, &dat); err != nil {
		log.Fatal(err)
	}

	limitOfStreams := 8
	c := 0

	sslice := make([]string, 0)
	for _, g := range dat.Streams {
		if c == limitOfStreams {
			break
		}
		if !isBlacklisted(g.Channel.Name) && g.Viewers > 100 && !isRebroadcast(g.Channel.Status) {
			s := fmt.Sprintf("\u0002%s\u000F (%d) %s", g.Channel.DisplayName, g.Viewers, g.Channel.URL)
			sslice = append(sslice, s)
			c++
		}
	}

	return sslice
}

func Dota2Streams() []string {
	// get all dota streams, even russians oO
	requestURL := "https://api.twitch.tv/kraken/streams?game=Dota+2&limit=4"
	res, err := http.Get(requestURL)
	if err != nil {
		log.Fatal(err)
	}
	streams, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var dat JSONResult
	if err := json.Unmarshal(streams, &dat); err != nil {
		log.Fatal(err)
	}

	sslice := make([]string, 0)
	for _, g := range dat.Streams {
		s := fmt.Sprintf("\u0002%s\u000F (%d) %s", g.Channel.DisplayName, g.Viewers, g.Channel.URL)
		sslice = append(sslice, s)
	}
	return sslice
}

func clientID() string {
	file, e := ioutil.ReadFile("./client.id")
	if e != nil {
		panic(e)
	}
	return string(file)
}

func favoriteList() string {
	file, e := ioutil.ReadFile("./favorites.txt")
	if e != nil {
		panic(e)
	}
	return string(file)
}

func tournamentsList() string {
	file, e := ioutil.ReadFile("./tournaments.txt")
	if e != nil {
		panic(e)
	}
	return string(file)
}

func isRebroadcast(stream string) bool {
	s := strings.ToLower(stream)
	return strings.Contains(s, "rebroadcast")
}

func containsVersus(stream string) bool {
	s := strings.ToLower(stream)
	return strings.Contains(s, " vs ")
}

func containsLive(stream string) bool {
	s := strings.ToLower(stream)
	return strings.Contains(s, "live")
}

func blacklistStreams() []string {
	file, e := ioutil.ReadFile("./blacklist.txt")
	if e != nil {
		panic(e)
	}
	return strings.Split(string(file), "\n")
}

func isBlacklisted(stream string) bool {
	blacklist := blacklistStreams()
	for _, b := range blacklist {
		if b == stream {
			return true
		}
	}
	return false
}

// JSON structs
type JSONResult struct {
	Streams []JSONStreams `json:"streams"`
}

type JSONStreams struct {
	Channel JSONChannel `json:"channel"`
	Viewers int         `json:"viewers"`
}

type JSONChannel struct {
	DisplayName string `json:"display_name"`
	Name        string `json:"name"`
	URL         string `json:"url"`
	Status      string `json:"status"`
}
<|endoftext|>


<commit_before>/*
 *  Charon: A game authentication server
 *  Copyright (C) 2014-2016  Alex Mayfield <alexmax2742@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package main

import (
	"log"

	"github.com/AlexMax/charon"
	"github.com/go-ini/ini"
)

func main() {
	log.Print("Starting Charon...")

	// Load configuration
	config, err := ini.Load("charon.ini")
	if err != nil {
		log.Fatal(err)
	}

	// Construct application.
	authApp, err := charon.NewAuthApp(config)
	if err != nil {
		log.Fatal(err)
	}

	// Start the application server.
	log.Fatal(authApp.ListenAndServe(":16666"))
}
<commit_msg>Log source file and line number.<commit_after>/*
 *  Charon: A game authentication server
 *  Copyright (C) 2014-2016  Alex Mayfield <alexmax2742@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package main

import (
	"log"

	"github.com/AlexMax/charon"
	"github.com/go-ini/ini"
)

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	log.Print("Starting Charon...")

	// Load configuration
	config, err := ini.Load("charon.ini")
	if err != nil {
		log.Fatal(err)
	}

	// Construct application.
	authApp, err := charon.NewAuthApp(config)
	if err != nil {
		log.Fatal(err)
	}

	// Start the application server.
	log.Fatal(authApp.ListenAndServe(":16666"))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/stianeikeland/go-rpio"
	"os"
	"time"
)

const (
	lightOne
	lightTwo
	lightThree
	lightFour
	lightFive
	lightSix
)

const (
	ledAInBCM = rpio.Pin(2)
	ledBInBCM = rpio.Pin(3)
	ledCInBCM = rpio.Pin(4)
	ledDInBCM = rpio.Pin(17)
	ledEInBCM = rpio.Pin(27)
	ledFInBCM = rpio.Pin(22)
	ledGInBCM = rpio.Pin(10)
	ledDFInBCM = rpio.Pin(9)
)

const (
	numOneInBCM = rpio.Pin(14)
	numTwoInBCM = rpio.Pin(15)
	numThreeInBCM = rpio.Pin(18)
	numFourInBCM = rpio.Pin(23)
	numFiveInBCM = rpio.Pin(24)
	numSixInBCM = rpio.Pin(25)
)

var (
	arrayLight = []rpio.Pin {numOneInBCM, numTwoInBCM, numThreeInBCM, numFourInBCM, numFiveInBCM, numSixInBCM}
	arrayLed = []rpio.Pin {ledAInBCM, ledBInBCM, ledCInBCM, ledDInBCM, ledEInBCM, ledFInBCM, ledGInBCM, ledDFInBCM}
	hour, min, sec int
	currentLight int = -1;
	currentNum int = 0;

)

func main() {
	ticker := time.NewTicker(time.Millisecond * 2)
	go func() {
		if err := rpio.Open(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		defer rpio.Close()

		for _, pin := range arrayLight{
			pin.Output()
		}

		for _, pin := range arrayLed{
			pin.Output()
		}

		for t := range ticker.C {
			fmt.Println("Currnt time is ", t)
			hour, min, sec = t.Clock()
			currentLight = (currentLight + 1) % 6
			switch currentLight {
			case lightOne:
				currentNum = hour / 10
			case lightTwo:
				currentNum = hour % 10
			case lightThree:
				currentNum = min / 10
			case lightFive:
				currentNum = min % 10
			case lightFive:
				currentNum = sec / 10
			case lightSix:
				currentNum = sec % 10
			}
			lightNumber(currentLight, currentNum)
		}
	}()
}

func lightNumber(light int, number int) {
	for _, pin := range arrayLight{
		pin.Low()
	}
	ledDFInBCM.High()
	switch number {
	case 0:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.Low()
		ledGInBCM.High()
	case 1:
		ledAInBCM.High()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.High()
		ledEInBCM.High()
		ledFInBCM.High()
		ledGInBCM.High()
	case 2:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.High()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.High()
		ledGInBCM.Low()
	case 3:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.High()
		ledFInBCM.High()
		ledGInBCM.Low()
	case 4:
		ledAInBCM.High()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.High()
		ledEInBCM.High()
		ledFInBCM.Low()
		ledGInBCM.Low()
	case 5:
		ledAInBCM.Low()
		ledBInBCM.High()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.Low()
		ledGInBCM.Low()
	case 6:
		ledAInBCM.Low()
		ledBInBCM.High()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.Low()
		ledGInBCM.Low()
	case 7:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.High()
		ledEInBCM.High()
		ledFInBCM.High()
		ledGInBCM.High()
	case 8:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.Low()
		ledGInBCM.Low()
	case 9:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.High()
		ledFInBCM.Low()
		ledGInBCM.Low()
	}
	if light == lightFour {
		ledDFInBCM.Low()
	}
	arrayLight[light].High()
}

<commit_msg>fix const init error<commit_after>package main

import (
	"fmt"
	"github.com/stianeikeland/go-rpio"
	"os"
	"time"
)

const (
	lightOne = iota
	lightTwo
	lightThree
	lightFour
	lightFive
	lightSix
)

const (
	ledAInBCM = rpio.Pin(2)
	ledBInBCM = rpio.Pin(3)
	ledCInBCM = rpio.Pin(4)
	ledDInBCM = rpio.Pin(17)
	ledEInBCM = rpio.Pin(27)
	ledFInBCM = rpio.Pin(22)
	ledGInBCM = rpio.Pin(10)
	ledDFInBCM = rpio.Pin(9)
)

const (
	numOneInBCM = rpio.Pin(14)
	numTwoInBCM = rpio.Pin(15)
	numThreeInBCM = rpio.Pin(18)
	numFourInBCM = rpio.Pin(23)
	numFiveInBCM = rpio.Pin(24)
	numSixInBCM = rpio.Pin(25)
)

var (
	arrayLight = []rpio.Pin {numOneInBCM, numTwoInBCM, numThreeInBCM, numFourInBCM, numFiveInBCM, numSixInBCM}
	arrayLed = []rpio.Pin {ledAInBCM, ledBInBCM, ledCInBCM, ledDInBCM, ledEInBCM, ledFInBCM, ledGInBCM, ledDFInBCM}
	hour, min, sec int
	currentLight int = -1;
	currentNum int = 0;

)

func main() {
	ticker := time.NewTicker(time.Millisecond * 2)
	go func() {
		if err := rpio.Open(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		defer rpio.Close()

		for _, pin := range arrayLight{
			pin.Output()
		}

		for _, pin := range arrayLed{
			pin.Output()
		}

		for t := range ticker.C {
			fmt.Println("Currnt time is ", t)
			hour, min, sec = t.Clock()
			currentLight = (currentLight + 1) % 6
			switch currentLight {
			case lightOne:
				currentNum = hour / 10
			case lightTwo:
				currentNum = hour % 10
			case lightThree:
				currentNum = min / 10
			case lightFive:
				currentNum = min % 10
			case lightFive:
				currentNum = sec / 10
			case lightSix:
				currentNum = sec % 10
			}
			lightNumber(currentLight, currentNum)
		}
	}()
}

func lightNumber(light int, number int) {
	for _, pin := range arrayLight{
		pin.Low()
	}
	ledDFInBCM.High()
	switch number {
	case 0:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.Low()
		ledGInBCM.High()
	case 1:
		ledAInBCM.High()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.High()
		ledEInBCM.High()
		ledFInBCM.High()
		ledGInBCM.High()
	case 2:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.High()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.High()
		ledGInBCM.Low()
	case 3:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.High()
		ledFInBCM.High()
		ledGInBCM.Low()
	case 4:
		ledAInBCM.High()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.High()
		ledEInBCM.High()
		ledFInBCM.Low()
		ledGInBCM.Low()
	case 5:
		ledAInBCM.Low()
		ledBInBCM.High()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.Low()
		ledGInBCM.Low()
	case 6:
		ledAInBCM.Low()
		ledBInBCM.High()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.Low()
		ledGInBCM.Low()
	case 7:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.High()
		ledEInBCM.High()
		ledFInBCM.High()
		ledGInBCM.High()
	case 8:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.Low()
		ledFInBCM.Low()
		ledGInBCM.Low()
	case 9:
		ledAInBCM.Low()
		ledBInBCM.Low()
		ledCInBCM.Low()
		ledDInBCM.Low()
		ledEInBCM.High()
		ledFInBCM.Low()
		ledGInBCM.Low()
	}
	if light == lightFour {
		ledDFInBCM.Low()
	}
	arrayLight[light].High()
}

<|endoftext|>


<commit_before>package golgoquery

import (
	"log"

	"github.com/PuerkitoBio/goquery"
)

type GoqueryResults struct {
	Results []string
}

func GoqueryDocument(url string) *goquery.Document {
	doc, err := goquery.NewDocument(url)
	if err != nil {
		log.Fatal(err)
	}
	return doc
}

func Goquery(url string, goquerySelector string) (domNode *goquery.Selection) {
	doc := GoqueryDocument(url)

	domNode = doc.Find(goquerySelector)
	return
}

func (results *GoqueryResults) GoqueryAttrsFrom(domNodes *goquery.Selection, attr string) {
	results.Results = make([]string, domNodes.Size())
	domNodes.Each(func(i int, s *goquery.Selection) {
		var attrValue string
		var attrPresent bool
		if attr == "text" {
			attrValue = s.Text()
			attrPresent = (attrValue != "")
		} else {
			attrValue, attrPresent = s.Attr(attr)
		}
		if !attrPresent {
			s_html, _ := s.Html()
			log.Printf("[warn] %s\n", s_html)
		}

		results.Results[i] = attrValue
	})
	return
}

func GoqueryHrefsFrom(url string, goquerySelector string) (results GoqueryResults) {
	results.GoqueryAttrsFrom(Goquery(url, goquerySelector), "href")
	return
}

func GoqueryTextFrom(url string, goquerySelector string) (results GoqueryResults) {
	results.GoqueryAttrsFrom(Goquery(url, goquerySelector), "text")
	return
}

func GoqueryHrefsFromParents(url string, selectors []string) (results GoqueryResults) {
	var domNodes *goquery.Selection
	last_idx := len(selectors) - 1
	for idx, selector := range selectors {
		if selector == ".." {
			domNodes = domNodes.Parent()
		} else {
			domNodes = Goquery(url, selector)
		}
		if idx == last_idx {
			results.GoqueryAttrsFrom(domNodes, "href")
		}
	}
	return
}
<commit_msg>[golgoquery] added GoqueryTextFromParents<commit_after>package golgoquery

import (
	"log"

	"github.com/PuerkitoBio/goquery"
)

type GoqueryResults struct {
	Results []string
}

func GoqueryDocument(url string) *goquery.Document {
	doc, err := goquery.NewDocument(url)
	if err != nil {
		log.Fatal(err)
	}
	return doc
}

func Goquery(url string, goquerySelector string) (domNode *goquery.Selection) {
	doc := GoqueryDocument(url)

	domNode = doc.Find(goquerySelector)
	return
}

func (results *GoqueryResults) GoqueryAttrsFrom(domNodes *goquery.Selection, attr string) {
	results.Results = make([]string, domNodes.Size())
	domNodes.Each(func(i int, s *goquery.Selection) {
		var attrValue string
		var attrPresent bool
		if attr == "text" {
			attrValue = s.Text()
			attrPresent = (attrValue != "")
		} else {
			attrValue, attrPresent = s.Attr(attr)
		}
		if !attrPresent {
			s_html, _ := s.Html()
			log.Printf("[warn] %s\n", s_html)
		}

		results.Results[i] = attrValue
	})
	return
}

func GoqueryHrefsFrom(url string, goquerySelector string) (results GoqueryResults) {
	results.GoqueryAttrsFrom(Goquery(url, goquerySelector), "href")
	return
}

func GoqueryTextFrom(url string, goquerySelector string) (results GoqueryResults) {
	results.GoqueryAttrsFrom(Goquery(url, goquerySelector), "text")
	return
}

func GoqueryAttrsFromParents(url string, selectors []string, attr string) (results GoqueryResults) {
	var domNodes *goquery.Selection
	last_idx := len(selectors) - 1
	for idx, selector := range selectors {
		if selector == ".." {
			domNodes = domNodes.Parent()
		} else {
			domNodes = Goquery(url, selector)
		}
		if idx == last_idx {
			results.GoqueryAttrsFrom(domNodes, attr)
		}
	}
	return
}

func GoqueryHrefsFromParents(url string, selectors []string) (results GoqueryResults) {
	GoqueryAttrsFromParents(url, selectors, "href")
}

func GoqueryTextFromParents(url string, selectors []string) (results GoqueryResults) {
	GoqueryAttrsFromParents(url, selectors, "text")
}
<|endoftext|>


<commit_before>package udp

import (
    "testing"
)

func BenchmarkSend(b *testing.B) {
    // setup
    sendConfig := SendConfig{
        DestAddr:   "127.0.0.1",
        DestPort:   PORT,
    }
    send, err := NewSend(sendConfig)
    if err != nil {
        b.Fatal(err)
    }

    b.ResetTimer()

    // run
    send.run(0, 0, 100000) // 100k packets at unbound rate
}
<commit_msg>test udp/send: benchmark UDP and IP<commit_after>package udp

import (
    "testing"
)

func BenchmarkSendUDP(b *testing.B) {
    // setup
    sendConfig := SendConfig{
        DestAddr:   "127.0.0.1",
        DestPort:   PORT,
    }
    send, err := NewSend(sendConfig)
    if err != nil {
        b.Fatal(err)
    }

    b.ResetTimer()

    // run
    send.run(0, 0, 1000000) // 1m packets at unbound rate
}

func BenchmarkSendIP(b *testing.B) {
    // setup
    sendConfig := SendConfig{
        SourceNet:      "127.0.1.0/24",
        SourcePortBits: 16,
        DestAddr:       "127.0.0.1",
        DestPort:       PORT,
    }
    send, err := NewSend(sendConfig)
    if err != nil {
        b.Fatal(err)
    }

    b.ResetTimer()

    // run
    send.run(0, 0, 1000000) // 1m packets at unbound rate
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/gin-gonic/gin"
	"go.bug.st/serial.v1"
)

func infoHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"version": version,
		"http":    "http://" + c.Request.Host + ":" + port,
		"https":   "https://" + c.Request.Host + ":" + portSSL,
		"ws":      "ws://" + c.Request.Host + ":" + port,
		"wss":     "wss://" + c.Request.Host + ":" + portSSL,
	})
}

func pauseHandler(c *gin.Context) {
	go func() {
		ports, _ := serial.GetPortsList()
		for _, element := range ports {
			spClose(element)
		}
		*hibernate = true
		restart("")
	}()
	c.JSON(200, nil)
}
<commit_msg>Fix duplicate port in info<commit_after>package main

import (
	"strings"

	"github.com/gin-gonic/gin"
	"go.bug.st/serial.v1"
)

func infoHandler(c *gin.Context) {
	host := c.Request.Host
	parts := strings.Split(host, ":")
	host = parts[0]

	c.JSON(200, gin.H{
		"version": version,
		"http":    "http://" + host + port,
		"https":   "https://" + host + portSSL,
		"ws":      "ws://" + host + port,
		"wss":     "wss://" + host + portSSL,
	})
}

func pauseHandler(c *gin.Context) {
	go func() {
		ports, _ := serial.GetPortsList()
		for _, element := range ports {
			spClose(element)
		}
		*hibernate = true
		restart("")
	}()
	c.JSON(200, nil)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/husobee/vestigo"
	"github.com/jroimartin/orujo"
	olog "github.com/jroimartin/orujo/log"
	"github.com/zlowram/zmiddlewares/jwtauth"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type server struct {
	config Config
	db     *mgo.Session
	auth   *jwtauth.AuthHandler
}

func newServer(config Config) *server {
	var err error
	db, err := mgo.Dial(config.DB.Host + ":" + config.DB.Port)
	if err != nil {
		log.Fatal("ERROR connecting to DB:", err)
	}

	queryResult := User{}
	c := db.DB("test").C("users")
	err = c.Find(bson.M{}).One(&queryResult)
	if err != nil {
		defaultUser := User{
			UserId:   "0",
			Username: "admin",
			Email:    "admin@localhost.com",
			Role:     "admin",
		}
		h := sha256.New()
		h.Write([]byte("admin"))
		defaultUser.Hash = hex.EncodeToString(h.Sum(nil))
		err = c.Insert(defaultUser)
		if err != nil {
			log.Fatal("Error adding default user:", err)
		}
	}

	return &server{
		config: config,
		db:     db,
		auth:   jwtauth.NewAuthHandler(config.Local.PrivateKey, config.Local.PublicKey),
	}
}

func (s *server) start() error {
	m := vestigo.NewRouter()
	logger := log.New(os.Stdout, "[GODAN-UI] ", log.LstdFlags)
	logHandler := olog.NewLogHandler(logger, logLine)

	m.SetGlobalCors(&vestigo.CorsAccessControl{
		AllowOrigin:      []string{"*"},
		AllowCredentials: true,
		MaxAge:           3600 * time.Second,
		AllowHeaders:     []string{"Authorization", "Content-Type"},
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
	})

	m.Get("/users/:id", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.getUserHandler)).ServeHTTP,
	)
	m.Put("/users/:id", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.updateUserHandler)).ServeHTTP,
	)
	m.Delete("/users/:id", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.deleteUserHandler)).ServeHTTP,
	)
	m.Get("/users", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.getUsersHandler)).ServeHTTP,
	)
	m.Post("/users", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.newUserHandler)).ServeHTTP,
	)
	m.Post("/login", orujo.NewPipe(
		orujo.M(logHandler),
		http.HandlerFunc(s.loginHandler)).ServeHTTP,
	)

	m.Post("/tasks", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.newTaskHandler)).ServeHTTP,
	)
	m.Get("/ips/:ip", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.getIpHandler)).ServeHTTP,
	)
	m.Get("/ips", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.getIpHandler)).ServeHTTP,
	)
	m.Get("/status", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.getStatusHandler)).ServeHTTP,
	)
	m.Post("/status", orujo.NewPipe(
		orujo.M(logHandler),
		//s.auth,
		http.HandlerFunc(s.setStatusHandler)).ServeHTTP,
	)

	//http.Handle("/", m)
	fmt.Println("Listening on " + s.config.Local.Host + ":" + s.config.Local.Port + "...")
	log.Fatalln(http.ListenAndServe(s.config.Local.Host+":"+s.config.Local.Port, m))

	return nil
}

const logLine = `{{.Req.RemoteAddr}} - {{.Req.Method}} {{.Req.RequestURI}}`
<commit_msg>Added auth back again<commit_after>package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/husobee/vestigo"
	"github.com/jroimartin/orujo"
	olog "github.com/jroimartin/orujo/log"
	"github.com/zlowram/zmiddlewares/jwtauth"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type server struct {
	config Config
	db     *mgo.Session
	auth   *jwtauth.AuthHandler
}

func newServer(config Config) *server {
	var err error
	db, err := mgo.Dial(config.DB.Host + ":" + config.DB.Port)
	if err != nil {
		log.Fatal("ERROR connecting to DB:", err)
	}

	queryResult := User{}
	c := db.DB("test").C("users")
	err = c.Find(bson.M{}).One(&queryResult)
	if err != nil {
		defaultUser := User{
			UserId:   "0",
			Username: "admin",
			Email:    "admin@localhost.com",
			Role:     "admin",
		}
		h := sha256.New()
		h.Write([]byte("admin"))
		defaultUser.Hash = hex.EncodeToString(h.Sum(nil))
		err = c.Insert(defaultUser)
		if err != nil {
			log.Fatal("Error adding default user:", err)
		}
	}

	return &server{
		config: config,
		db:     db,
		auth:   jwtauth.NewAuthHandler(config.Local.PrivateKey, config.Local.PublicKey),
	}
}

func (s *server) start() error {
	m := vestigo.NewRouter()
	logger := log.New(os.Stdout, "[GODAN-UI] ", log.LstdFlags)
	logHandler := olog.NewLogHandler(logger, logLine)

	m.SetGlobalCors(&vestigo.CorsAccessControl{
		AllowOrigin:      []string{"*"},
		AllowCredentials: false,
		MaxAge:           3600 * time.Second,
		AllowHeaders:     []string{"Authorization", "Content-Type"},
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
	})

	m.Get("/users/:id", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.getUserHandler)).ServeHTTP,
	)
	m.Put("/users/:id", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.updateUserHandler)).ServeHTTP,
	)
	m.Delete("/users/:id", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.deleteUserHandler)).ServeHTTP,
	)
	m.Get("/users", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.getUsersHandler)).ServeHTTP,
	)
	m.Post("/users", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.newUserHandler)).ServeHTTP,
	)
	m.Post("/login", orujo.NewPipe(
		orujo.M(logHandler),
		http.HandlerFunc(s.loginHandler)).ServeHTTP,
	)

	m.Post("/tasks", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.newTaskHandler)).ServeHTTP,
	)
	m.Get("/ips/:ip", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.getIpHandler)).ServeHTTP,
	)
	m.Get("/ips", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.getIpHandler)).ServeHTTP,
	)
	m.Get("/status", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.getStatusHandler)).ServeHTTP,
	)
	m.Post("/status", orujo.NewPipe(
		orujo.M(logHandler),
		s.auth,
		http.HandlerFunc(s.setStatusHandler)).ServeHTTP,
	)

	fmt.Println("Listening on " + s.config.Local.Host + ":" + s.config.Local.Port + "...")
	log.Fatalln(http.ListenAndServe(s.config.Local.Host+":"+s.config.Local.Port, m))

	return nil
}

const logLine = `{{.Req.RemoteAddr}} - {{.Req.Method}} {{.Req.RequestURI}}`
<|endoftext|>


<commit_before>package diag

import (
	"archive/zip"
	"encoding/json"
	"errors"
	"github.com/watermint/toolbox/domain/model/mo_path"
	"github.com/watermint/toolbox/domain/model/mo_time"
	"github.com/watermint/toolbox/domain/service/sv_file_content"
	"github.com/watermint/toolbox/domain/service/sv_profile"
	"github.com/watermint/toolbox/infra/app"
	"github.com/watermint/toolbox/infra/control/app_control"
	"github.com/watermint/toolbox/infra/recipe/rc_conn"
	"github.com/watermint/toolbox/infra/recipe/rc_exec"
	"github.com/watermint/toolbox/infra/recipe/rc_recipe"
	"github.com/watermint/toolbox/infra/util/ut_archive"
	"go.uber.org/zap"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"time"
)

const (
	procmonDownloadUrl = "https://download.sysinternals.com/files/ProcessMonitor.zip"
	procmonExe32       = "ProcMon.exe"
	procmonExe64       = "ProcMon64.exe"
	procmonLogPrefix   = "monitor"
	procmonLogSummary  = "info.json"
)

type Procmon struct {
	ProcmonUrl     string
	RepositoryPath mo_path.FileSystemPath
	DropboxPath    mo_path.DropboxPath
	Peer           rc_conn.ConnUserFile
	RunUntil       mo_time.Time
	RetainLogs     int
	Seconds        int
}

func (z *Procmon) downloadProcmon(c app_control.Control) error {
	l := c.Log()

	err := os.MkdirAll(z.RepositoryPath.Path(), 0755)
	if err != nil {
		l.Debug("Unable to create repository path", zap.Error(err))
		return err
	}
	procmonZip := filepath.Join(z.RepositoryPath.Path(), "procmon.zip")

	// Download
	{
		l.Info("Try download", zap.String("url", z.ProcmonUrl))
		resp, err := http.Get(z.ProcmonUrl)
		if err != nil {
			l.Debug("Unable to create download request")
			return err
		}
		defer resp.Body.Close()

		out, err := os.Create(procmonZip)
		if err != nil {
			l.Debug("Unable to create download file")
			return err
		}
		defer out.Close()

		_, err = io.Copy(out, resp.Body)
		if err != nil {
			l.Debug("Unable to copy from response", zap.Error(err))
			return err
		}
	}

	// Extract
	{
		l.Info("Extract downloaded zip", zap.String("zip", procmonZip))
		r, err := zip.OpenReader(procmonZip)
		if err != nil {
			l.Debug("Unable to open zip file", zap.Error(err))
			return err
		}

		for _, f := range r.File {
			compressed, err := f.Open()
			if err != nil {
				l.Debug("Unable to open compressed file", zap.Error(err), zap.String("name", f.Name))
				return err
			}

			extractPath := filepath.Join(z.RepositoryPath.Path(), filepath.Base(f.Name))
			l.Debug("Extract file", zap.String("extractPath", extractPath))
			extracted, err := os.Create(extractPath)
			if err != nil {
				l.Debug("Unable to create extract file", zap.Error(err))
				return err
			}

			_, err = io.Copy(extracted, compressed)
			if err != nil {
				l.Debug("Unable to copy from zip", zap.Error(err))
				return err
			}
			extracted.Close()
			compressed.Close()
		}
	}

	return err
}

func (z *Procmon) ensureProcmon(c app_control.Control) (exePath string, err error) {
	l := c.Log()
	if runtime.GOARCH == "amd64" {
		exePath = filepath.Join(z.RepositoryPath.Path(), procmonExe64)
	} else {
		exePath = filepath.Join(z.RepositoryPath.Path(), procmonExe32)
	}

	info, err := os.Lstat(exePath)
	if err != nil {
		l.Debug("Unable to find exe", zap.Error(err), zap.String("exe", exePath))
		err = z.downloadProcmon(c)
		if err != nil {
			return "", err
		}
	}
	l.Debug("Exe info", zap.Any("info", info))
	return exePath, err
}

func (z *Procmon) runProcmon(c app_control.Control, exePath string) (cmd *exec.Cmd, logPath string, err error) {
	l := c.Log()

	logName := c.Workspace().JobId()
	logPath = filepath.Join(z.RepositoryPath.Path(), "logs", logName)
	l.Debug("Creating log path", zap.String("path", logPath))
	err = os.MkdirAll(logPath, 0755)
	if err != nil {
		l.Debug("Unable to create log path", zap.Error(err))
		return nil, "", err
	}

	{
		hostname, _ := os.Hostname()
		usr, _ := user.Current()

		info := struct {
			TimeLocal string `json:"time_local"`
			TimeUTC   string `json:"time_utc"`
			Hostname  string `json:"hostname"`
			Username  string `json:"username"`
			UserHome  string `json:"user_home"`
			UserUID   string `json:"user_uid"`
			UserGID   string `json:"user_gid"`
		}{
			TimeLocal: time.Now().Local().Format(time.RFC3339),
			TimeUTC:   time.Now().UTC().Format(time.RFC3339),
			Hostname:  hostname,
			Username:  usr.Name,
			UserHome:  usr.HomeDir,
			UserUID:   usr.Uid,
			UserGID:   usr.Gid,
		}
		content, err := json.Marshal(&info)
		if err != nil {
			l.Error("Unable to create info file", zap.Error(err))
		}

		err = ioutil.WriteFile(filepath.Join(logPath, procmonLogSummary), content, 0644)
		if err != nil {
			l.Error("Unable to write info file", zap.Error(err))
		}
	}

	if !app.IsWindows() {
		l.Warn("Skip run procmon (Reason; not on Windows)")
		return nil, logPath, nil
	}

	cmd = exec.Command(exePath,
		"/AcceptEula",
		"/Quiet",
		"/Minimized",
		"/BackingFile",
		filepath.Join(logPath, procmonLogPrefix),
	)
	l.Info("Run Process monitor", zap.String("exe", exePath), zap.Strings("args", cmd.Args))

	err = cmd.Start()
	if err != nil {
		l.Debug("Unable to start program", zap.Error(err), zap.Any("cmd", cmd))
		return nil, logPath, err
	}

	return cmd, logPath, nil
}

func (z *Procmon) watchProcmon(c app_control.Control, exePath string, cmd *exec.Cmd, logPath string) error {
	l := c.Log().With(zap.String("logPath", logPath))

	if cmd == nil || !app.IsWindows() {
		l.Info("skip watching")
		return nil
	}

	go func() {
		for {
			time.Sleep(1 * time.Second)
			l.Debug("Process", zap.Any("status", cmd.ProcessState))

			entries, err := ioutil.ReadDir(logPath)
			if err != nil {
				l.Debug("Unable to list dir", zap.Error(err))
				continue
			}
			if z.RetainLogs == 0 {
				continue
			}

			logEntries := make([]os.FileInfo, 0)
			modTimes := make([]string, 0)
			cmpTimeFormat := "20060102-150405.000"

			for _, f := range entries {
				if f.IsDir() {
					continue
				}
				if strings.HasPrefix(strings.ToLower(f.Name()), procmonLogPrefix) {
					logEntries = append(logEntries, f)
					mt := f.ModTime().Format(cmpTimeFormat)
					modTimes = append(modTimes, mt)
					l.Debug("Log file found", zap.Any("entry", f), zap.String("modTime", mt))
				}
			}

			if len(modTimes) <= z.RetainLogs {
				l.Debug("Log files is less than threshold")
				continue
			}

			sort.Strings(modTimes)
			thresholdIndex := len(modTimes) - z.RetainLogs
			thresholdTime := modTimes[thresholdIndex]

			for _, f := range logEntries {
				et := f.ModTime().Format(cmpTimeFormat)
				if strings.Compare(et, thresholdTime) < 0 {
					l.Debug("Remove log", zap.Any("entry", f))
					lf := filepath.Join(logPath, f.Name())
					err = os.Remove(lf)
					l.Debug("Removed", zap.Error(err), zap.String("logFile", lf))
				} else {
					l.Debug("Retain file", zap.Any("entry", f))
				}
			}
		}
	}()

	l.Info("Waiting for duration", zap.Int("seconds", z.Seconds))
	time.Sleep(time.Duration(z.Seconds) * time.Second)

	return nil
}

func (z *Procmon) terminateProcmon(c app_control.Control, exePath string, cmd *exec.Cmd) error {
	l := c.Log()

	if !app.IsWindows() {
		l.Warn("Skip run procmon (Reason; not on Windows)")
		return nil
	}

	l.Info("Trying to terminate procmon")
	termCmd := exec.Command(exePath,
		"/Terminate",
	)
	err := termCmd.Start()
	if err != nil {
		l.Debug("Unable to invoke procmon", zap.Error(err), zap.Any("cmd", cmd))
		l.Debug("Trying to terminate thru cmd")
		err2 := cmd.Process.Kill()
		l.Debug("Kill sent", zap.Error(err2))
		return err
	}
	if err := termCmd.Wait(); err != nil {
		l.Debug("Terminate wait returned an error", zap.Error(err))
		return nil
	}

	l.Info("Waiting for termination", zap.Int("seconds", 60))
	time.Sleep(60 * time.Second)

	return nil
}

func (z *Procmon) compressProcmonLogs(c app_control.Control) (arcPath string, err error) {
	logPath := filepath.Join(z.RepositoryPath.Path(), "logs")
	l := c.Log().With(zap.String("logPath", logPath))

	arcName := c.Workspace().JobId()
	arcPath = filepath.Join(z.RepositoryPath.Path(), arcName+".zip")

	l.Info("Start compress logs", zap.String("archive", arcPath))
	if err := ut_archive.Create(arcPath, logPath, arcName); err != nil {
		l.Debug("Unable to create archive file", zap.Error(err))
		return "", err
	}

	return arcPath, nil
}

func (z *Procmon) uploadProcmonLogs(c app_control.Control, arcPath string) error {
	logPath := filepath.Join(z.RepositoryPath.Path(), "logs")
	l := c.Log().With(zap.String("logPath", logPath))
	l.Info("Start uploading logs", zap.String("archive", arcPath))

	prof, err := sv_profile.NewProfile(z.Peer.Context()).Current()
	if err != nil {
		l.Error("Unable to retrieve profile", zap.Error(err))
		return err
	}
	l.Info("Upload to the account", zap.Any("account", prof))

	e, err := sv_file_content.NewUpload(z.Peer.Context()).Add(z.DropboxPath, arcPath)
	if err != nil {
		l.Error("Unable to upload file", zap.Error(err))
		return err
	}
	l.Info("Uploaded", zap.Any("entry", e))
	err = os.Remove(arcPath)
	l.Debug("Removed", zap.Error(err))

	return nil
}

func (z *Procmon) cleanupProcmonLogs(c app_control.Control) error {
	logPath := filepath.Join(z.RepositoryPath.Path(), "logs")
	l := c.Log().With(zap.String("logPath", logPath))
	l.Debug("Start clean up logs")

	for i := 0; i < 10; i++ {
		err := os.RemoveAll(logPath)
		if err != nil {
			l.Debug("Unable to clean up logs", zap.Error(err))
			time.Sleep(10 * time.Second)
			continue
		}
		return nil
	}
	return nil
}

func (z *Procmon) Exec(c app_control.Control) error {
	l := c.Log()

	if z.Seconds < 10 {
		return errors.New("seconds must grater than 10 sec")
	}
	if z.RunUntil.Time().Before(time.Now()) {
		l.Info("Skip run")
		return nil
	}

	exe, err := z.ensureProcmon(c)
	if err != nil {
		return err
	}
	l.Debug("Procmon exe", zap.String("exe", exe))

	cmd, logPath, err := z.runProcmon(c, exe)
	if err != nil {
		return err
	}

	if err = z.watchProcmon(c, exe, cmd, logPath); err != nil {
		return err
	}
	if err = z.terminateProcmon(c, exe, cmd); err != nil {
		return err
	}
	logArc, err := z.compressProcmonLogs(c)
	if err != nil {
		return err
	}
	if err = z.uploadProcmonLogs(c, logArc); err != nil {
		return err
	}
	if err = z.cleanupProcmonLogs(c); err != nil {
		return err
	}
	return nil
}

func (z *Procmon) Test(c app_control.Control) error {
	tmpDir, err := ioutil.TempDir("", "procmon")
	if err != nil {
		return err
	}
	defer func() {
		os.RemoveAll(tmpDir)
	}()

	return rc_exec.Exec(c, &Procmon{}, func(r rc_recipe.Recipe) {
		m := r.(*Procmon)
		m.ProcmonUrl = procmonDownloadUrl
		m.Seconds = 30
		m.RetainLogs = 4
		m.RepositoryPath = mo_path.NewFileSystemPath(tmpDir)
	})
}

func (z *Procmon) Preset() {
	ru, err := mo_time.New(time.Now().Add(7 * 24 * time.Hour).Format("2006-01-02"))
	if err != nil {
		panic(err)
	}
	z.ProcmonUrl = procmonDownloadUrl
	z.Seconds = 1800
	z.RunUntil = ru
	z.RetainLogs = 4
}
<commit_msg>#263 : collect log on startup<commit_after>package diag

import (
	"archive/zip"
	"encoding/json"
	"errors"
	"github.com/watermint/toolbox/domain/model/mo_path"
	"github.com/watermint/toolbox/domain/model/mo_time"
	"github.com/watermint/toolbox/domain/service/sv_file_content"
	"github.com/watermint/toolbox/domain/service/sv_profile"
	"github.com/watermint/toolbox/infra/app"
	"github.com/watermint/toolbox/infra/control/app_control"
	"github.com/watermint/toolbox/infra/recipe/rc_conn"
	"github.com/watermint/toolbox/infra/recipe/rc_exec"
	"github.com/watermint/toolbox/infra/recipe/rc_recipe"
	"github.com/watermint/toolbox/infra/util/ut_archive"
	"go.uber.org/zap"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"time"
)

const (
	procmonDownloadUrl = "https://download.sysinternals.com/files/ProcessMonitor.zip"
	procmonExe32       = "ProcMon.exe"
	procmonExe64       = "ProcMon64.exe"
	procmonLogPrefix   = "monitor"
	procmonLogSummary  = "info.json"
)

type Procmon struct {
	ProcmonUrl     string
	RepositoryPath mo_path.FileSystemPath
	DropboxPath    mo_path.DropboxPath
	Peer           rc_conn.ConnUserFile
	RunUntil       mo_time.Time
	RetainLogs     int
	Seconds        int
}

func (z *Procmon) downloadProcmon(c app_control.Control) error {
	l := c.Log()

	err := os.MkdirAll(z.RepositoryPath.Path(), 0755)
	if err != nil {
		l.Debug("Unable to create repository path", zap.Error(err))
		return err
	}
	procmonZip := filepath.Join(z.RepositoryPath.Path(), "procmon.zip")

	// Download
	{
		l.Info("Try download", zap.String("url", z.ProcmonUrl))
		resp, err := http.Get(z.ProcmonUrl)
		if err != nil {
			l.Debug("Unable to create download request")
			return err
		}
		defer resp.Body.Close()

		out, err := os.Create(procmonZip)
		if err != nil {
			l.Debug("Unable to create download file")
			return err
		}
		defer out.Close()

		_, err = io.Copy(out, resp.Body)
		if err != nil {
			l.Debug("Unable to copy from response", zap.Error(err))
			return err
		}
	}

	// Extract
	{
		l.Info("Extract downloaded zip", zap.String("zip", procmonZip))
		r, err := zip.OpenReader(procmonZip)
		if err != nil {
			l.Debug("Unable to open zip file", zap.Error(err))
			return err
		}

		for _, f := range r.File {
			compressed, err := f.Open()
			if err != nil {
				l.Debug("Unable to open compressed file", zap.Error(err), zap.String("name", f.Name))
				return err
			}

			extractPath := filepath.Join(z.RepositoryPath.Path(), filepath.Base(f.Name))
			l.Debug("Extract file", zap.String("extractPath", extractPath))
			extracted, err := os.Create(extractPath)
			if err != nil {
				l.Debug("Unable to create extract file", zap.Error(err))
				return err
			}

			_, err = io.Copy(extracted, compressed)
			if err != nil {
				l.Debug("Unable to copy from zip", zap.Error(err))
				return err
			}
			extracted.Close()
			compressed.Close()
		}
	}

	return err
}

func (z *Procmon) ensureProcmon(c app_control.Control) (exePath string, err error) {
	l := c.Log()
	if runtime.GOARCH == "amd64" {
		exePath = filepath.Join(z.RepositoryPath.Path(), procmonExe64)
	} else {
		exePath = filepath.Join(z.RepositoryPath.Path(), procmonExe32)
	}

	info, err := os.Lstat(exePath)
	if err != nil {
		l.Debug("Unable to find exe", zap.Error(err), zap.String("exe", exePath))
		err = z.downloadProcmon(c)
		if err != nil {
			return "", err
		}
	}
	l.Debug("Exe info", zap.Any("info", info))
	return exePath, err
}

func (z *Procmon) runProcmon(c app_control.Control, exePath string) (cmd *exec.Cmd, logPath string, err error) {
	l := c.Log()

	logName := c.Workspace().JobId()
	logPath = filepath.Join(z.RepositoryPath.Path(), "logs", logName)
	l.Debug("Creating log path", zap.String("path", logPath))
	err = os.MkdirAll(logPath, 0755)
	if err != nil {
		l.Debug("Unable to create log path", zap.Error(err))
		return nil, "", err
	}

	{
		hostname, _ := os.Hostname()
		usr, _ := user.Current()

		info := struct {
			TimeLocal string `json:"time_local"`
			TimeUTC   string `json:"time_utc"`
			Hostname  string `json:"hostname"`
			Username  string `json:"username"`
			UserHome  string `json:"user_home"`
			UserUID   string `json:"user_uid"`
			UserGID   string `json:"user_gid"`
		}{
			TimeLocal: time.Now().Local().Format(time.RFC3339),
			TimeUTC:   time.Now().UTC().Format(time.RFC3339),
			Hostname:  hostname,
			Username:  usr.Name,
			UserHome:  usr.HomeDir,
			UserUID:   usr.Uid,
			UserGID:   usr.Gid,
		}
		content, err := json.Marshal(&info)
		if err != nil {
			l.Error("Unable to create info file", zap.Error(err))
		}

		err = ioutil.WriteFile(filepath.Join(logPath, procmonLogSummary), content, 0644)
		if err != nil {
			l.Error("Unable to write info file", zap.Error(err))
		}
	}

	if !app.IsWindows() {
		l.Warn("Skip run procmon (Reason; not on Windows)")
		return nil, logPath, nil
	}

	cmd = exec.Command(exePath,
		"/AcceptEula",
		"/Quiet",
		"/Minimized",
		"/BackingFile",
		filepath.Join(logPath, procmonLogPrefix),
	)
	l.Info("Run Process monitor", zap.String("exe", exePath), zap.Strings("args", cmd.Args))

	err = cmd.Start()
	if err != nil {
		l.Debug("Unable to start program", zap.Error(err), zap.Any("cmd", cmd))
		return nil, logPath, err
	}

	return cmd, logPath, nil
}

func (z *Procmon) watchProcmon(c app_control.Control, exePath string, cmd *exec.Cmd, logPath string) error {
	l := c.Log().With(zap.String("logPath", logPath))

	if cmd == nil || !app.IsWindows() {
		l.Info("skip watching")
		return nil
	}

	go func() {
		for {
			time.Sleep(1 * time.Second)
			l.Debug("Process", zap.Any("status", cmd.ProcessState))

			entries, err := ioutil.ReadDir(logPath)
			if err != nil {
				l.Debug("Unable to list dir", zap.Error(err))
				continue
			}
			if z.RetainLogs == 0 {
				continue
			}

			logEntries := make([]os.FileInfo, 0)
			modTimes := make([]string, 0)
			cmpTimeFormat := "20060102-150405.000"

			for _, f := range entries {
				if f.IsDir() {
					continue
				}
				if strings.HasPrefix(strings.ToLower(f.Name()), procmonLogPrefix) {
					logEntries = append(logEntries, f)
					mt := f.ModTime().Format(cmpTimeFormat)
					modTimes = append(modTimes, mt)
					l.Debug("Log file found", zap.Any("entry", f), zap.String("modTime", mt))
				}
			}

			if len(modTimes) <= z.RetainLogs {
				l.Debug("Log files is less than threshold")
				continue
			}

			sort.Strings(modTimes)
			thresholdIndex := len(modTimes) - z.RetainLogs
			thresholdTime := modTimes[thresholdIndex]

			for _, f := range logEntries {
				et := f.ModTime().Format(cmpTimeFormat)
				if strings.Compare(et, thresholdTime) < 0 {
					l.Debug("Remove log", zap.Any("entry", f))
					lf := filepath.Join(logPath, f.Name())
					err = os.Remove(lf)
					l.Debug("Removed", zap.Error(err), zap.String("logFile", lf))
				} else {
					l.Debug("Retain file", zap.Any("entry", f))
				}
			}
		}
	}()

	l.Info("Waiting for duration", zap.Int("seconds", z.Seconds))
	time.Sleep(time.Duration(z.Seconds) * time.Second)

	return nil
}

func (z *Procmon) terminateProcmon(c app_control.Control, exePath string, cmd *exec.Cmd) error {
	l := c.Log()

	if !app.IsWindows() {
		l.Warn("Skip run procmon (Reason; not on Windows)")
		return nil
	}

	l.Info("Trying to terminate procmon")
	termCmd := exec.Command(exePath,
		"/Terminate",
	)
	err := termCmd.Start()
	if err != nil {
		l.Debug("Unable to invoke procmon", zap.Error(err), zap.Any("cmd", cmd))
		l.Debug("Trying to terminate thru cmd")
		err2 := cmd.Process.Kill()
		l.Debug("Kill sent", zap.Error(err2))
		return err
	}
	if err := termCmd.Wait(); err != nil {
		l.Debug("Terminate wait returned an error", zap.Error(err))
		return nil
	}

	l.Info("Waiting for termination", zap.Int("seconds", 60))
	time.Sleep(60 * time.Second)

	return nil
}

func (z *Procmon) compressProcmonLogs(c app_control.Control) (arcPath string, err error) {
	logPath := filepath.Join(z.RepositoryPath.Path(), "logs")
	l := c.Log().With(zap.String("logPath", logPath))

	arcName := c.Workspace().JobId()
	arcPath = filepath.Join(z.RepositoryPath.Path(), arcName+".zip")

	l.Info("Start compress logs", zap.String("archive", arcPath))
	if err := ut_archive.Create(arcPath, logPath, arcName); err != nil {
		l.Debug("Unable to create archive file", zap.Error(err))
		return "", err
	}

	return arcPath, nil
}

func (z *Procmon) uploadProcmonLogs(c app_control.Control, arcPath string) error {
	logPath := filepath.Join(z.RepositoryPath.Path(), "logs")
	l := c.Log().With(zap.String("logPath", logPath))
	l.Info("Start uploading logs", zap.String("archive", arcPath))

	prof, err := sv_profile.NewProfile(z.Peer.Context()).Current()
	if err != nil {
		l.Error("Unable to retrieve profile", zap.Error(err))
		return err
	}
	l.Info("Upload to the account", zap.Any("account", prof))

	e, err := sv_file_content.NewUpload(z.Peer.Context()).Add(z.DropboxPath, arcPath)
	if err != nil {
		l.Error("Unable to upload file", zap.Error(err))
		return err
	}
	l.Info("Uploaded", zap.Any("entry", e))
	err = os.Remove(arcPath)
	l.Debug("Removed", zap.Error(err))

	return nil
}

func (z *Procmon) cleanupProcmonLogs(c app_control.Control) error {
	logPath := filepath.Join(z.RepositoryPath.Path(), "logs")
	l := c.Log().With(zap.String("logPath", logPath))
	l.Debug("Start clean up logs")

	for i := 0; i < 10; i++ {
		err := os.RemoveAll(logPath)
		if err != nil {
			l.Debug("Unable to clean up logs", zap.Error(err))
			time.Sleep(10 * time.Second)
			continue
		}
		return nil
	}
	return nil
}

func (z *Procmon) Exec(c app_control.Control) error {
	l := c.Log()

	if z.Seconds < 10 {
		return errors.New("seconds must grater than 10 sec")
	}
	if z.RunUntil.Time().Before(time.Now()) {
		l.Info("Skip run")
		return nil
	}

	processLogs := func() error {
		logArc, err := z.compressProcmonLogs(c)
		if err != nil {
			return err
		}
		if err = z.uploadProcmonLogs(c, logArc); err != nil {
			return err
		}
		if err = z.cleanupProcmonLogs(c); err != nil {
			return err
		}
		return nil
	}

	exe, err := z.ensureProcmon(c)
	if err != nil {
		return err
	}
	l.Debug("Procmon exe", zap.String("exe", exe))
	if err = processLogs(); err != nil {
		return err
	}

	cmd, logPath, err := z.runProcmon(c, exe)
	if err != nil {
		return err
	}

	if err = z.watchProcmon(c, exe, cmd, logPath); err != nil {
		return err
	}
	if err = z.terminateProcmon(c, exe, cmd); err != nil {
		return err
	}
	if err = processLogs(); err != nil {
		return err
	}

	return nil
}

func (z *Procmon) Test(c app_control.Control) error {
	tmpDir, err := ioutil.TempDir("", "procmon")
	if err != nil {
		return err
	}
	defer func() {
		os.RemoveAll(tmpDir)
	}()

	return rc_exec.Exec(c, &Procmon{}, func(r rc_recipe.Recipe) {
		m := r.(*Procmon)
		m.ProcmonUrl = procmonDownloadUrl
		m.Seconds = 30
		m.RetainLogs = 4
		m.RepositoryPath = mo_path.NewFileSystemPath(tmpDir)
	})
}

func (z *Procmon) Preset() {
	ru, err := mo_time.New(time.Now().Add(7 * 24 * time.Hour).Format("2006-01-02"))
	if err != nil {
		panic(err)
	}
	z.ProcmonUrl = procmonDownloadUrl
	z.Seconds = 1800
	z.RunUntil = ru
	z.RetainLogs = 4
}
<|endoftext|>