<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	flag "github.com/ogier/pflag"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"runtime"
	"time"
)

const (
	version string = "0.1"
)

var (
	downloaded []int64
	board      string
	threadId   string
	boards     string = "a|b|c|d|e|f|g|gif|h|hr|k|m|o|p|r|s|t|u|v|vg|w|wg|i|ic|r9k|cm|hm|y|3|adv|an|cgl|ck|co|diy|fa|fit|hc|int|jp|lit|mlp|mu|n|po|pol|sci|soc|sp|tg|toy|trv|tv|vp|wsg|x|q"

	// Options
	minWidth     *int64         = flag.Int64P("min-width", "w", 0, "Minimum width of images")
	minHeight    *int64         = flag.Int64P("min-height", "h", 0, "Minimum height of images")
	refresh      *time.Duration = flag.DurationP("refresh", "r", time.Second*30, "Refresh rate (min 30s)")
	orignalNames *bool          = flag.BoolP("original-names", "o", false, "Save images under original filenames")
	showVersion  *bool          = flag.BoolP("version", "v", false, "Show version")
)

type Post struct {
	No             int64
	Resto          int64
	Sticky         bool
	Closed         bool
	Now            string
	Time           int64
	Name           string
	Trip           string
	Id             string
	Capcode        string
	Country        string
	Country_name   string
	Email          string
	Sub            string
	Com            string
	Tim            int64
	Filename       string
	Ext            string
	Fsize          int64
	Md5            string
	W              int64
	H              int64
	Tn_w           int64
	Tn_h           int64
	Filedeleted    bool
	Spoiler        bool
	Custom_spoiler bool
	Omitted_posts  int64
	Omitted_images int64
}

func (p *Post) FullFileName(original bool) (s string) {
	if original {
		s = fmt.Sprintf("%s%s", p.Filename, p.Ext)
	} else {
		s = fmt.Sprintf("%d%s", p.Tim, p.Ext)
	}
	return
}

type Thread struct {
	Posts []Post
}

func checkError(err error) {
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}
}

func parseThreadId(s string) (b, id string, err error) {
	r, err := regexp.Compile(fmt.Sprintf("^/?(%s)/(?:res/)?(\\d+)", boards))
	if err != nil {
		return b, id, err
	}

	groups := r.FindSubmatch([]byte(s))
	if len(groups) < 1 {
		return b, id, errors.New("Input does not match regex")
	}

	b = string(groups[1])
	id = string(groups[2])

	return b, id, nil
}

func parseThreadFromJson(r io.Reader) (Thread, error) {
	dec := json.NewDecoder(r)
	var t Thread
	for {
		if err := dec.Decode(&t); err == io.EOF {
			break
		} else if err != nil {
			return t, err
		}
	}
	return t, nil
}

func downloadImage(p Post) {
	if p.W < *minWidth || p.H < *minHeight {
		return
	}

	url := fmt.Sprintf("https://images.4chan.org/%s/src/%d%s", board, p.Tim, p.Ext)
	img, _, err := getUrl(url)
	checkError(err)

	err = ioutil.WriteFile(p.FullFileName(*orignalNames), img, 0644)
	checkError(err)

	downloaded = append(downloaded, p.Tim)
}

func getUrl(url string) ([]byte, int, error) {
	resp, err := http.Get(url)
	if err != nil {
		return []byte{}, 0, nil
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, 0, nil
	}
	return body, resp.StatusCode, nil

}

func wasDownloaded(p Post) bool {
	for _, e := range downloaded {
		if e == p.Tim {
			return true
		}
	}
	return false
}

func loadThread() {
	url := fmt.Sprintf("https://api.4chan.org/%s/res/%s.json", board, threadId)
	resp, status, err := getUrl(url)
	checkError(err)
	if status != 200 {
		if status == 404 {
			fmt.Println("Thread 404'd")
			os.Exit(1)
		}
		fmt.Printf("Something went wrong\nGot return code %d at '%s'.\n", status, url)
		return
	}

	thread, err := parseThreadFromJson(bytes.NewReader(resp))
	if err != nil {
		fmt.Println("Could not parse thread")
	}

	for _, e := range thread.Posts {
		if !wasDownloaded(e) {
			go downloadImage(e)
		}
	}
}

func init() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage: chanloader [options] /b/res/123456\nOptions:")
		flag.PrintDefaults()
	}
	flag.Parse()

	if *showVersion {
		fmt.Println(version)
		os.Exit(0)
	}

	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(1)
	}

	runtime.GOMAXPROCS(runtime.NumCPU())
}

func main() {
	b, id, err := parseThreadId(flag.Arg(0))
	if err != nil {
		fmt.Printf("Invalid input: %s\n", flag.Arg(0))
		os.Exit(1)
	}
	board, threadId = b, id

	ticker := time.NewTicker(*refresh)
	for {
		fmt.Println("Loading thread")
		go loadThread()
		<-ticker.C
	}

	var x string
	fmt.Scanln(&x)
}
<commit_msg>Show help if input is invalid<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	flag "github.com/ogier/pflag"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"runtime"
	"time"
)

const (
	version string = "0.1"
)

var (
	downloaded []int64
	board      string
	threadId   string
	boards     string = "a|b|c|d|e|f|g|gif|h|hr|k|m|o|p|r|s|t|u|v|vg|w|wg|i|ic|r9k|cm|hm|y|3|adv|an|cgl|ck|co|diy|fa|fit|hc|int|jp|lit|mlp|mu|n|po|pol|sci|soc|sp|tg|toy|trv|tv|vp|wsg|x|q"

	// Options
	minWidth     *int64         = flag.Int64P("min-width", "w", 0, "Minimum width of images")
	minHeight    *int64         = flag.Int64P("min-height", "h", 0, "Minimum height of images")
	refresh      *time.Duration = flag.DurationP("refresh", "r", time.Second*30, "Refresh rate (min 30s)")
	orignalNames *bool          = flag.BoolP("original-names", "o", false, "Save images under original filenames")
	showVersion  *bool          = flag.BoolP("version", "v", false, "Show version")
)

type Post struct {
	No             int64
	Resto          int64
	Sticky         bool
	Closed         bool
	Now            string
	Time           int64
	Name           string
	Trip           string
	Id             string
	Capcode        string
	Country        string
	Country_name   string
	Email          string
	Sub            string
	Com            string
	Tim            int64
	Filename       string
	Ext            string
	Fsize          int64
	Md5            string
	W              int64
	H              int64
	Tn_w           int64
	Tn_h           int64
	Filedeleted    bool
	Spoiler        bool
	Custom_spoiler bool
	Omitted_posts  int64
	Omitted_images int64
}

func (p *Post) FullFileName(original bool) (s string) {
	if original {
		s = fmt.Sprintf("%s%s", p.Filename, p.Ext)
	} else {
		s = fmt.Sprintf("%d%s", p.Tim, p.Ext)
	}
	return
}

type Thread struct {
	Posts []Post
}

func checkError(err error) {
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}
}

func parseThreadId(s string) (b, id string, err error) {
	r, err := regexp.Compile(fmt.Sprintf("^/?(%s)/(?:res/)?(\\d+)", boards))
	if err != nil {
		return b, id, err
	}

	groups := r.FindSubmatch([]byte(s))
	if len(groups) < 1 {
		return b, id, errors.New("Input does not match regex")
	}

	b = string(groups[1])
	id = string(groups[2])

	return b, id, nil
}

func parseThreadFromJson(r io.Reader) (Thread, error) {
	dec := json.NewDecoder(r)
	var t Thread
	for {
		if err := dec.Decode(&t); err == io.EOF {
			break
		} else if err != nil {
			return t, err
		}
	}
	return t, nil
}

func downloadImage(p Post) {
	if p.W < *minWidth || p.H < *minHeight {
		return
	}

	url := fmt.Sprintf("https://images.4chan.org/%s/src/%d%s", board, p.Tim, p.Ext)
	img, _, err := getUrl(url)
	checkError(err)

	err = ioutil.WriteFile(p.FullFileName(*orignalNames), img, 0644)
	checkError(err)

	downloaded = append(downloaded, p.Tim)
}

func getUrl(url string) ([]byte, int, error) {
	resp, err := http.Get(url)
	if err != nil {
		return []byte{}, 0, nil
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, 0, nil
	}
	return body, resp.StatusCode, nil

}

func wasDownloaded(p Post) bool {
	for _, e := range downloaded {
		if e == p.Tim {
			return true
		}
	}
	return false
}

func loadThread() {
	url := fmt.Sprintf("https://api.4chan.org/%s/res/%s.json", board, threadId)
	resp, status, err := getUrl(url)
	checkError(err)
	if status != 200 {
		if status == 404 {
			fmt.Println("Thread 404'd")
			os.Exit(1)
		}
		fmt.Printf("Something went wrong\nGot return code %d at '%s'.\n", status, url)
		return
	}

	thread, err := parseThreadFromJson(bytes.NewReader(resp))
	if err != nil {
		fmt.Println("Could not parse thread")
	}

	for _, e := range thread.Posts {
		if !wasDownloaded(e) {
			go downloadImage(e)
		}
	}
}

func init() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage: chanloader [options] /b/res/123456\nOptions:")
		flag.PrintDefaults()
	}
	flag.Parse()

	if *showVersion {
		fmt.Println(version)
		os.Exit(0)
	}

	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(1)
	}

	runtime.GOMAXPROCS(runtime.NumCPU())
}

func main() {
	b, id, err := parseThreadId(flag.Arg(0))
	if err != nil {
		fmt.Printf("Invalid input: %s\n", flag.Arg(0))
		flag.Usage()
		os.Exit(1)
	}
	board, threadId = b, id

	ticker := time.NewTicker(*refresh)
	for {
		fmt.Println("Loading thread")
		go loadThread()
		<-ticker.C
	}
}
<|endoftext|>


<commit_before>// Package mail implements a parser for electronic mail messages as specified
// in RFC5322.
//
// We allow both CRLF and LF to be used in the input, possibly mixed.
package mail

import (
	"bytes"
	"encoding/base64"
	"errors"
	"strings"
	"time"
)

var benc = base64.URLEncoding

type Message struct {
	FullHeaders []Header // all headers
	OptHeaders  []Header // unprocessed headers

	MessageId   string
	Id          string
	Date        time.Time
	From        []Address
	Sender      Address
	ReplyTo     []Address
	To          []Address
	Cc          []Address
	Bcc         []Address
	Subject     string
	Comments    []string
	Keywords    []string

	Text        string
}

type Header struct {
	Key, Value string
}

func Parse(s []byte) (m Message, e error) {
	r, e := ParseRaw(s)
	if e != nil {
		return
	}
	return Process(r)
}

func Process(r RawMessage) (m Message, e error) {
	m.FullHeaders = []Header{}
	m.OptHeaders = []Header{}
	m.Text = string(r.Body) // TODO mime
	for _, rh := range r.RawHeaders {
		h := Header{string(rh.Key), string(rh.Value)}
		m.FullHeaders = append(m.FullHeaders, h)
		switch string(rh.Key) {
		case `Message-ID`:
			m.MessageId = string(rh.Value)
			m.Id = benc.EncodeToString(rh.Value)
		case `Date`:
			m.Date = ParseDate(string(rh.Value))
		case `From`:
			m.From, e = parseAddressList(rh.Value)
		case `Sender`:
			m.Sender, e = ParseAddress(rh.Value)
		case `Reply-To`:
			m.ReplyTo, e = parseAddressList(rh.Value)
		case `To`:
			m.To, e = parseAddressList(rh.Value)
		case `Cc`:
			m.Cc, e = parseAddressList(rh.Value)
		case `Bcc`:
			m.Bcc, e = parseAddressList(rh.Value)
		case `Subject`:
			m.Subject = string(rh.Value)
		case `Comments`:
			m.Comments = append(m.Comments, string(rh.Value))
		case `Keywords`:
			ks := strings.Split(string(rh.Value), ",")
			for _, k := range ks {
				m.Keywords = append(m.Keywords, strings.TrimSpace(k))
			}
		default:
			m.OptHeaders = append(m.OptHeaders, h)
		}
		if e != nil {
			return
		}
	}
	if m.Sender == nil && len(m.From) > 0 {
		m.Sender = m.From[0]
	}
	return
}

type RawHeader struct {
	Key, Value []byte
}

type RawMessage struct {
	RawHeaders []RawHeader
	Body       []byte
}

func isWSP(b byte) bool {
	return b == ' ' || b == '\t'
}

func ParseRaw(s []byte) (m RawMessage, e error) {
	// parser states
	const (
		READY = iota
		HKEY
		HVWS
		HVAL
	)

	const (
		CR = '\r'
		LF = '\n'
	)
	CRLF := []byte{CR, LF}

	state := READY
	kstart, kend, vstart := 0, 0, 0
	done := false

	m.RawHeaders = []RawHeader{}

	for i := 0; i < len(s); i++ {
		b := s[i]
		switch state {
		case READY:
			if b == CR && i < len(s)-1 && s[i+1] == LF {
				// we are at the beginning of an empty header
				m.Body = s[i+2:]
				done = true
				goto Done
			}
			if b == LF {
				m.Body = s[i+1:]
				done = true
				goto Done
			}
			// otherwise this character is the first in a header
			// key
			kstart = i
			state = HKEY
		case HKEY:
			if b == ':' {
				kend = i
				state = HVWS
			}
		case HVWS:
			if !isWSP(b) {
				vstart = i
				state = HVAL
			}
		case HVAL:
			if b == CR && i < len(s)-2 && s[i+1] == LF && !isWSP(s[i+2]) {
				v := bytes.Replace(s[vstart:i], CRLF, nil, -1)
				hdr := RawHeader{s[kstart:kend], v}
				m.RawHeaders = append(m.RawHeaders, hdr)
				state = READY
				i++
			} else if b == LF && i < len(s)-1 && !isWSP(s[i+1]) {
				v := bytes.Replace(s[vstart:i], CRLF, nil, -1)
				hdr := RawHeader{s[kstart:kend], v}
				m.RawHeaders = append(m.RawHeaders, hdr)
				state = READY
			}
		}
	}
Done:
	if !done {
		e = errors.New("unexpected EOF")
	}
	return
}
<commit_msg>Bugfix<commit_after>// Package mail implements a parser for electronic mail messages as specified
// in RFC5322.
//
// We allow both CRLF and LF to be used in the input, possibly mixed.
package mail

import (
	"bytes"
	"encoding/base64"
	"errors"
	"strings"
	"time"
)

var benc = base64.URLEncoding

type Message struct {
	FullHeaders []Header // all headers
	OptHeaders  []Header // unprocessed headers

	MessageId   string
	Id          string
	Date        time.Time
	From        []Address
	Sender      Address
	ReplyTo     []Address
	To          []Address
	Cc          []Address
	Bcc         []Address
	Subject     string
	Comments    []string
	Keywords    []string

	InReply     []string
	References  []string

	Text        string
}

type Header struct {
	Key, Value string
}

func Parse(s []byte) (m Message, e error) {
	r, e := ParseRaw(s)
	if e != nil {
		return
	}
	return Process(r)
}

func Process(r RawMessage) (m Message, e error) {
	m.FullHeaders = []Header{}
	m.OptHeaders = []Header{}
	m.Text = string(r.Body) // TODO mime
	for _, rh := range r.RawHeaders {
		h := Header{string(rh.Key), string(rh.Value)}
		m.FullHeaders = append(m.FullHeaders, h)
		switch string(rh.Key) {
		case `Message-ID`:
			v := bytes.Trim(rh.Value, `<>`)
			m.MessageId = string(v)
			m.Id = benc.EncodeToString(v)
		case `Date`:
			m.Date = ParseDate(string(rh.Value))
		case `From`:
			m.From, e = parseAddressList(rh.Value)
		case `Sender`:
			m.Sender, e = ParseAddress(rh.Value)
		case `Reply-To`:
			m.ReplyTo, e = parseAddressList(rh.Value)
		case `To`:
			m.To, e = parseAddressList(rh.Value)
		case `Cc`:
			m.Cc, e = parseAddressList(rh.Value)
		case `Bcc`:
			m.Bcc, e = parseAddressList(rh.Value)
		case `Subject`:
			m.Subject = string(rh.Value)
		case `Comments`:
			m.Comments = append(m.Comments, string(rh.Value))
		case `Keywords`:
			ks := strings.Split(string(rh.Value), ",")
			for _, k := range ks {
				m.Keywords = append(m.Keywords, strings.TrimSpace(k))
			}
		default:
			m.OptHeaders = append(m.OptHeaders, h)
		}
		if e != nil {
			return
		}
	}
	if m.Sender == nil && len(m.From) > 0 {
		m.Sender = m.From[0]
	}
	return
}

type RawHeader struct {
	Key, Value []byte
}

type RawMessage struct {
	RawHeaders []RawHeader
	Body       []byte
}

func isWSP(b byte) bool {
	return b == ' ' || b == '\t'
}

func ParseRaw(s []byte) (m RawMessage, e error) {
	// parser states
	const (
		READY = iota
		HKEY
		HVWS
		HVAL
	)

	const (
		CR = '\r'
		LF = '\n'
	)
	CRLF := []byte{CR, LF}

	state := READY
	kstart, kend, vstart := 0, 0, 0
	done := false

	m.RawHeaders = []RawHeader{}

	for i := 0; i < len(s); i++ {
		b := s[i]
		switch state {
		case READY:
			if b == CR && i < len(s)-1 && s[i+1] == LF {
				// we are at the beginning of an empty header
				m.Body = s[i+2:]
				done = true
				goto Done
			}
			if b == LF {
				m.Body = s[i+1:]
				done = true
				goto Done
			}
			// otherwise this character is the first in a header
			// key
			kstart = i
			state = HKEY
		case HKEY:
			if b == ':' {
				kend = i
				state = HVWS
			}
		case HVWS:
			if !isWSP(b) {
				vstart = i
				state = HVAL
			}
		case HVAL:
			if b == CR && i < len(s)-2 && s[i+1] == LF && !isWSP(s[i+2]) {
				v := bytes.Replace(s[vstart:i], CRLF, nil, -1)
				hdr := RawHeader{s[kstart:kend], v}
				m.RawHeaders = append(m.RawHeaders, hdr)
				state = READY
				i++
			} else if b == LF && i < len(s)-1 && !isWSP(s[i+1]) {
				v := bytes.Replace(s[vstart:i], CRLF, nil, -1)
				hdr := RawHeader{s[kstart:kend], v}
				m.RawHeaders = append(m.RawHeaders, hdr)
				state = READY
			}
		}
	}
Done:
	if !done {
		e = errors.New("unexpected EOF")
	}
	return
}
<|endoftext|>


<commit_before>package cli

//Filter reporting filter (use package name, or package name request/event prefix

//DefaultFilter create default filter
func DefaultFilter() map[string]bool {
	var result = make(map[string]bool)
	result["dsunit"] = true
	result["vc"] = true
	result["sdk"] = true
	result["workflow.print"] = true
	result["build"] = true
	result["deploy"] = true
	result["storage"] = true
	result["exec.stdin"] = true
	result["exec.stdout"] = true
	result["endly"] = true
	result["workflow"] = true
	result["msg"] = true
	result["lambda"] = true
	result["apigateway"] = true
	result["iam"] = true

	return result
}

//WildcardFilter
func WildcardFilter() map[string]bool {
	return map[string]bool{
		"*": true,
	}
}
<commit_msg>added filter for aws<commit_after>package cli

//Filter reporting filter (use package name, or package name request/event prefix

//DefaultFilter create default filter
func DefaultFilter() map[string]bool {
	var result = make(map[string]bool)
	result["dsunit"] = true
	result["vc"] = true
	result["sdk"] = true
	result["workflow.print"] = true
	result["build"] = true
	result["deploy"] = true
	result["storage"] = true
	result["exec.stdin"] = true
	result["exec.stdout"] = true
	result["endly"] = true
	result["workflow"] = true
	result["msg"] = true
	result["lambda"] = true
	result["apigateway"] = true
	result["iam"] = true
	result["aws"] = true

	return result
}

//WildcardFilter
func WildcardFilter() map[string]bool {
	return map[string]bool{
		"*": true,
	}
}
<|endoftext|>


<commit_before>package client

import (
    "fmt"
    "io"
    "log"
    "net"
    "time"
    "encoding/xml"
)

type xmlResponse struct {
    ID          int         `xml:"id,attr"`
    Reset       string      `xml:"reset,attr"`

    GUID        string      `xml:"GuiId"`
    Type        int         `xml:"XMLType"`
    Resp        int         `xml:"Resp"`

    *System
}

// Decode packet from server, based on response type.
// The given *System will be reset or updated
type xmlPacket struct {
    XMLName     xml.Name    `xml:"System"`

    xmlResponse

    // decoding state
    reset       bool
}

func (r *xmlPacket) UnmarshalXML(d *xml.Decoder, e xml.StartElement) error {
    reset := xmlAttr(e, "reset")

    if reset == "yes" {
        r.reset = true

        // reset
        r.System.Reset()
    }

    return d.DecodeElement(&r.xmlResponse, &e)
}

type xmlPing struct {
    XMLName     xml.Name    `xml:"System"`
    ID          int         `xml:"id,attr"`
}

type xmlQuery struct {
    XMLName     xml.Name    `xml:"System"`
    ID          int         `xml:"id,attr"`
    Reset       string      `xml:"reset,attr"`

    Type        int         `xml:"XMLType"`
    Query       int         `xml:"Query"`
    Recursive   int         `xml:"Recursive"`
}

type XMLClient struct {
    conn            net.Conn
    timeout         time.Duration

    readChan        chan xmlPacket
    readError       error           // set once readChan is closed, nil on clean EOF
    listenChan      chan System
}

func (options Options) XMLClient() (*XMLClient, error) {
    xmlClient := XMLClient{
        timeout:        options.Timeout,
    }

    if tcpAddr, err := net.ResolveTCPAddr("tcp4", net.JoinHostPort(options.Address, options.XMLPort)); err != nil {
        return nil, fmt.Errorf("Client.initXML: ResolveTCPAddr: %v", err)
    } else if tcpConn, err := net.DialTCP("tcp4", nil, tcpAddr); err != nil {
        return nil, fmt.Errorf("Client.initXML: DialTCP %v: %v", tcpAddr, err)
    } else {
        xmlClient.conn = tcpConn
    }

    if err := xmlClient.start(); err != nil {
        return nil, err
    }

    return &xmlClient, nil
}

// Blocking read and decode of a complete <System> state into the given xmlPacket.
// Any existing xmlPacket.System state is updated, or reset if the server returns a <System reset="yes">
//
// The read is performed using a timeout deadline for the entire <System> state
func (xmlClient *XMLClient) read(packet *xmlPacket) error {
    // applies to the complete XML packet read by the decoder..?
    if err := xmlClient.conn.SetReadDeadline(time.Now().Add(xmlClient.timeout)); err != nil {
        return err
    }

    if err := xml.NewDecoder(xmlClient.conn).Decode(packet); err != nil {
        return err
    } else {
        return nil
    }
}

// Blocking encode and write of an arbitrary XML packet to the server.
func (xmlClient *XMLClient) write(packet interface{}) error {
    if err := xmlClient.conn.SetWriteDeadline(time.Now().Add(xmlClient.timeout)); err != nil {
        return err
    }

    return xml.NewEncoder(xmlClient.conn).Encode(packet)
}

// Send a <System> reset query
func (xmlClient *XMLClient) writeReset() error {
    return xmlClient.write(xmlQuery{Reset: "yes",
        Type:       3,
        Query:      3,
        Recursive:  1,
    })
}

// Send a <System> ping
func (xmlClient *XMLClient) writePing() error {
    return xmlClient.write(xmlPing{})
}

// Launch background goroutines
func (xmlClient *XMLClient) start() error {
    xmlClient.listenChan = make(chan System)
    xmlClient.readChan = make(chan xmlPacket)

    go xmlClient.reader()
    go xmlClient.writer()

    return nil
}

// Read and handle messages from the server, dispatching readChan for timing and listenChan for readers
//
// Guarantees completion by closing the readChan and setting readError
func (xmlClient *XMLClient) reader() {
    defer close(xmlClient.listenChan)
    defer close(xmlClient.readChan)
    defer func(){
        panicValue := recover()

        if panicValue == nil {
            xmlClient.readError = nil
        } else if panicError, ok := panicValue.(Error); ok {
            xmlClient.readError = panicError
        } else {
            xmlClient.readError = fmt.Errorf("%v", panicValue)
        }
    }()

    var system System
    var wantReset = true

    for {
        packet := xmlPacket{xmlResponse: xmlResponse{System: &system}}

        if err := xmlClient.read(&packet); err != nil {
            log.Printf("xmlClient.read: %v\n", err)

            if err == io.EOF {
                // done
                return

            } else {
                // quit with error
                panic(err)
            }

        } else if wantReset && !packet.reset {
            // skip packets before initial reset-sync
            log.Printf("xmlClient.read: skip")

        } else {
            wantReset = false

            // timeout handling
            xmlClient.readChan <- packet

            if packet.reset {
                log.Printf("xmlClient.read: reset")

                xmlClient.listenChan <- system

            } else if packet.Type == 0 {
                log.Printf("xmlClient.read: update")

                xmlClient.listenChan <- system

            } else {
                // log.Printf("xmlClient.read: pong")
            }
        }
    }
}

// Write requests, including the initial reset to sync state, and periodic pings on idle timeout
func (xmlClient *XMLClient) writer() {
    defer xmlClient.conn.Close()

    timer := time.NewTimer(xmlClient.timeout / 2)

    // initialize
    if err := xmlClient.writeReset(); err != nil {
        log.Printf("xmlClient.writeReset: %v\n", err)
        return
    }

    for {
        select {
        case <-timer.C:
            if err := xmlClient.writePing(); err != nil {
                log.Printf("xmlClient.writePing: %v\n", err)
                return
            }

            // fast-retry ping on timeout
            timer.Reset(xmlClient.timeout / 8)

        case _, ok := <-xmlClient.readChan:
            if !ok {
                return
            }

            // got update, reschedule idle ping
            timer.Reset(xmlClient.timeout / 2)
        }
    }
}

// Listen for system updates.
//
// Read updated System states from the given chan, starting from the initial reset state.
//
// The received System is safe for concurrent reads, but do *not* write to it! Use the public methods if possible.
//
// XXX: only one shared chan for all callers
func (xmlClient *XMLClient) Listen() (chan System, error) {
    if xmlClient.readError != nil {
        return nil, xmlClient.readError
    } else {
        return xmlClient.listenChan, nil
    }
}

// Return any Error after Listen() returns, or nil if the connection was closed cleanly by the other end (EOF)
func (xmlClient *XMLClient) ListenError() error {
    return xmlClient.readError
}

// Read system updates.
//
// Blocking read of updated System state, or error (including io.EOF on clean shutdown).
//
// The received System is safe for concurrent reads, but do *not* write to it! Use the public methods if possible.
func (xmlClient *XMLClient) Read() (System, error) {
    if system, ok := <-xmlClient.listenChan; ok {
        return system, nil
    } else if xmlClient.readError != nil {
        // chan was closed with an error
        return system, xmlClient.readError
    } else {
        // chan was closed after EOF
        return system, io.EOF
    }
}
<commit_msg>client: XMLClient.reader: preserve panic stack<commit_after>package client

import (
    "runtime/debug"
    "fmt"
    "io"
    "log"
    "net"
    "time"
    "encoding/xml"
)

type xmlResponse struct {
    ID          int         `xml:"id,attr"`
    Reset       string      `xml:"reset,attr"`

    GUID        string      `xml:"GuiId"`
    Type        int         `xml:"XMLType"`
    Resp        int         `xml:"Resp"`

    *System
}

// Decode packet from server, based on response type.
// The given *System will be reset or updated
type xmlPacket struct {
    XMLName     xml.Name    `xml:"System"`

    xmlResponse

    // decoding state
    reset       bool
}

func (r *xmlPacket) UnmarshalXML(d *xml.Decoder, e xml.StartElement) error {
    reset := xmlAttr(e, "reset")

    if reset == "yes" {
        r.reset = true

        // reset
        r.System.Reset()
    }

    return d.DecodeElement(&r.xmlResponse, &e)
}

type xmlPing struct {
    XMLName     xml.Name    `xml:"System"`
    ID          int         `xml:"id,attr"`
}

type xmlQuery struct {
    XMLName     xml.Name    `xml:"System"`
    ID          int         `xml:"id,attr"`
    Reset       string      `xml:"reset,attr"`

    Type        int         `xml:"XMLType"`
    Query       int         `xml:"Query"`
    Recursive   int         `xml:"Recursive"`
}

type XMLClient struct {
    conn            net.Conn
    timeout         time.Duration

    readChan        chan xmlPacket
    readError       error           // set once readChan is closed, nil on clean EOF
    listenChan      chan System
}

func (options Options) XMLClient() (*XMLClient, error) {
    xmlClient := XMLClient{
        timeout:        options.Timeout,
    }

    if tcpAddr, err := net.ResolveTCPAddr("tcp4", net.JoinHostPort(options.Address, options.XMLPort)); err != nil {
        return nil, fmt.Errorf("Client.initXML: ResolveTCPAddr: %v", err)
    } else if tcpConn, err := net.DialTCP("tcp4", nil, tcpAddr); err != nil {
        return nil, fmt.Errorf("Client.initXML: DialTCP %v: %v", tcpAddr, err)
    } else {
        xmlClient.conn = tcpConn
    }

    if err := xmlClient.start(); err != nil {
        return nil, err
    }

    return &xmlClient, nil
}

// Blocking read and decode of a complete <System> state into the given xmlPacket.
// Any existing xmlPacket.System state is updated, or reset if the server returns a <System reset="yes">
//
// The read is performed using a timeout deadline for the entire <System> state
func (xmlClient *XMLClient) read(packet *xmlPacket) error {
    // applies to the complete XML packet read by the decoder..?
    if err := xmlClient.conn.SetReadDeadline(time.Now().Add(xmlClient.timeout)); err != nil {
        return err
    }

    if err := xml.NewDecoder(xmlClient.conn).Decode(packet); err != nil {
        return err
    } else {
        return nil
    }
}

// Blocking encode and write of an arbitrary XML packet to the server.
func (xmlClient *XMLClient) write(packet interface{}) error {
    if err := xmlClient.conn.SetWriteDeadline(time.Now().Add(xmlClient.timeout)); err != nil {
        return err
    }

    return xml.NewEncoder(xmlClient.conn).Encode(packet)
}

// Send a <System> reset query
func (xmlClient *XMLClient) writeReset() error {
    return xmlClient.write(xmlQuery{Reset: "yes",
        Type:       3,
        Query:      3,
        Recursive:  1,
    })
}

// Send a <System> ping
func (xmlClient *XMLClient) writePing() error {
    return xmlClient.write(xmlPing{})
}

// Launch background goroutines
func (xmlClient *XMLClient) start() error {
    xmlClient.listenChan = make(chan System)
    xmlClient.readChan = make(chan xmlPacket)

    go xmlClient.reader()
    go xmlClient.writer()

    return nil
}

// Read and handle messages from the server, dispatching readChan for timing and listenChan for readers
//
// Guarantees completion by closing the readChan and setting readError
func (xmlClient *XMLClient) reader() {
    defer close(xmlClient.listenChan)
    defer close(xmlClient.readChan)

    // wrap to return panics via .readError
    defer func(){
        panicValue := recover()

        if panicValue == nil {
            xmlClient.readError = nil
        } else if panicError, ok := panicValue.(Error); ok {
            xmlClient.readError = panicError
        } else {
            xmlClient.readError = fmt.Errorf("%v\n%v", panicValue, string(debug.Stack()))
        }
    }()

    var system System
    var wantReset = true

    for {
        packet := xmlPacket{xmlResponse: xmlResponse{System: &system}}

        if err := xmlClient.read(&packet); err != nil {
            log.Printf("xmlClient.read: %v\n", err)

            if err == io.EOF {
                // done
                return

            } else {
                // quit with error
                panic(err)
            }

        } else if wantReset && !packet.reset {
            // skip packets before initial reset-sync
            log.Printf("xmlClient.read: skip")

        } else {
            wantReset = false

            // timeout handling
            xmlClient.readChan <- packet

            if packet.reset {
                log.Printf("xmlClient.read: reset")

                xmlClient.listenChan <- system

            } else if packet.Type == 0 {
                log.Printf("xmlClient.read: update")

                xmlClient.listenChan <- system

            } else {
                // log.Printf("xmlClient.read: pong")
            }
        }
    }
}

// Write requests, including the initial reset to sync state, and periodic pings on idle timeout
func (xmlClient *XMLClient) writer() {
    defer xmlClient.conn.Close()

    timer := time.NewTimer(xmlClient.timeout / 2)

    // initialize
    if err := xmlClient.writeReset(); err != nil {
        log.Printf("xmlClient.writeReset: %v\n", err)
        return
    }

    for {
        select {
        case <-timer.C:
            if err := xmlClient.writePing(); err != nil {
                log.Printf("xmlClient.writePing: %v\n", err)
                return
            }

            // fast-retry ping on timeout
            timer.Reset(xmlClient.timeout / 8)

        case _, ok := <-xmlClient.readChan:
            if !ok {
                return
            }

            // got update, reschedule idle ping
            timer.Reset(xmlClient.timeout / 2)
        }
    }
}

// Listen for system updates.
//
// Read updated System states from the given chan, starting from the initial reset state.
//
// The received System is safe for concurrent reads, but do *not* write to it! Use the public methods if possible.
//
// XXX: only one shared chan for all callers
func (xmlClient *XMLClient) Listen() (chan System, error) {
    if xmlClient.readError != nil {
        return nil, xmlClient.readError
    } else {
        return xmlClient.listenChan, nil
    }
}

// Return any Error after Listen() returns, or nil if the connection was closed cleanly by the other end (EOF)
func (xmlClient *XMLClient) ListenError() error {
    return xmlClient.readError
}

// Read system updates.
//
// Blocking read of updated System state, or error (including io.EOF on clean shutdown).
//
// The received System is safe for concurrent reads, but do *not* write to it! Use the public methods if possible.
func (xmlClient *XMLClient) Read() (System, error) {
    if system, ok := <-xmlClient.listenChan; ok {
        return system, nil
    } else if xmlClient.readError != nil {
        // chan was closed with an error
        return system, xmlClient.readError
    } else {
        // chan was closed after EOF
        return system, io.EOF
    }
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/yanc0/collectd-http-server/plugins"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

var pluginList []plugins.Plugin
var config Config

type BasicAuth struct {
	Active   bool     `toml:"active"`
	Accounts []string `toml:"accounts"`
}

type Config struct {
	Listen         string                        `toml:"listen"`
	Port           int                           `toml:"port"`
	BasicAuth      *BasicAuth                    `toml:"basic_auth"`
	GraphitePlugin *plugins.GraphitePluginConfig `toml:"graphite_plugin"`
	ConsolePlugin  *plugins.ConsolePluginConfig  `toml:"console_plugin"`
}

func loadConfig(configPath string) {
	configPath = os.ExpandEnv(configPath)

	configStr, err := ioutil.ReadFile(configPath)
	if err != nil {
		log.Fatalln(err.Error())
	}

	err = toml.Unmarshal(configStr, &config) //global config
	if err != nil {
		log.Fatalln(err.Error())
	}
}

func loadPlugins(config *Config) {
	if config.GraphitePlugin != nil && config.GraphitePlugin.Active {
		pluginList = append(pluginList, plugins.NewGraphitePlugin(config.GraphitePlugin))
	}
	if config.ConsolePlugin != nil && config.ConsolePlugin.Active {
		pluginList = append(pluginList, plugins.NewConsolePlugin(config.ConsolePlugin))
	}

	if len(pluginList) < 1 {
		log.Println("[WARN] No plugins loaded")
	} else {
		log.Println("[INFO] Plugins loaded")
	}
}

func initPlugins() {
	for _, p := range pluginList {
		err := p.Init()
		if err != nil {
			log.Fatalln("[FATAL]", p.Name(), err.Error())
		}
	}
}

func main() {
	configPath := flag.String("configPath",
		"/etc/collectd-http-server/config.toml",
		"Config path")
	flag.Parse()

	loadConfig(*configPath)
	loadPlugins(&config)
	initPlugins()

	listen := fmt.Sprintf("%s:%d", config.Listen, config.Port)

	http.HandleFunc("/", auth(handlerMetricPost))
	log.Fatal(http.ListenAndServe(listen, nil))
}
<commit_msg>default main config<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/yanc0/collectd-http-server/plugins"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

var pluginList []plugins.Plugin
var config Config

type BasicAuth struct {
	Active   bool     `toml:"active"`
	Accounts []string `toml:"accounts"`
}

type Config struct {
	Listen         string                        `toml:"listen"`
	Port           int                           `toml:"port"`
	BasicAuth      *BasicAuth                    `toml:"basic_auth"`
	GraphitePlugin *plugins.GraphitePluginConfig `toml:"graphite_plugin"`
	ConsolePlugin  *plugins.ConsolePluginConfig  `toml:"console_plugin"`
}

func loadConfig(configPath string) {
	configPath = os.ExpandEnv(configPath)

	configStr, err := ioutil.ReadFile(configPath)
	if err != nil {
		log.Fatalln(err.Error())
	}

	err = toml.Unmarshal(configStr, &config) //global config
	if err != nil {
		log.Fatalln(err.Error())
	}

	if config.Listen == "" {
		config.Listen = "127.0.0.1"
	}

	if config.Port == 0 {
		config.Port = 9223
	}
}

func loadPlugins(config *Config) {
	if config.GraphitePlugin != nil && config.GraphitePlugin.Active {
		pluginList = append(pluginList, plugins.NewGraphitePlugin(config.GraphitePlugin))
	}
	if config.ConsolePlugin != nil && config.ConsolePlugin.Active {
		pluginList = append(pluginList, plugins.NewConsolePlugin(config.ConsolePlugin))
	}

	if len(pluginList) < 1 {
		log.Println("[WARN] No plugins loaded")
	} else {
		log.Println("[INFO] Plugins loaded")
	}
}

func initPlugins() {
	for _, p := range pluginList {
		err := p.Init()
		if err != nil {
			log.Fatalln("[FATAL]", p.Name(), err.Error())
		}
	}
}

func main() {
	configPath := flag.String("configPath",
		"/etc/collectd-http-server/config.toml",
		"Config path")
	flag.Parse()

	loadConfig(*configPath)
	loadPlugins(&config)
	initPlugins()

	listen := fmt.Sprintf("%s:%d", config.Listen, config.Port)

	http.HandleFunc("/", auth(handlerMetricPost))
	log.Fatal(http.ListenAndServe(listen, nil))
}
<|endoftext|>


<commit_before>package lmx

// #include <lmx.h>
//
// void goConnectionLost(void*, char*, int, int);
// void goCheckoutFailure(void*, char*, int, LMX_STATUS);
// void goCheckoutSuccess(void*, char*, int);
// void goCheckoutRetry(void*, char*, int);
// void goExit(void*);
import "C"

import (
	"net"
	"strconv"
	"sync"
	"time"
	"unsafe"
)

// TODO(rjeczalik): ~QuoteASCII every string

//export goConnectionLost
func goConnectionLost(p unsafe.Pointer, host *C.char, port C.int, n C.int) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type:       OptHeartbeatConnectionLost,
		Addr:       net.JoinHostPort(C.GoString(host), strconv.Itoa(int(port))),
		Heartbeats: int(n),
	})
}

//export goCheckoutFailure
func goCheckoutFailure(p unsafe.Pointer, feature *C.char, n C.int, s C.LMX_STATUS) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type:     OptHeartbeatCheckoutFailure,
		Feature:  C.GoString(feature),
		Err:      ToError(Status(s)),
		Licenses: int(n),
	})
}

//export goCheckoutSuccess
func goCheckoutSuccess(p unsafe.Pointer, feature *C.char, n C.int) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type:     OptHeartbeatCheckoutSuccess,
		Feature:  C.GoString(feature),
		Licenses: int(n),
	})
}

//export goCheckoutRetry
func goCheckoutRetry(p unsafe.Pointer, feature *C.char, n C.int) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type:     OptHeartbeatRetryFeature,
		Feature:  C.GoString(feature),
		Licenses: int(n),
	})
}

//export goExit
func goExit(p unsafe.Pointer) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type: OptHeartbeatExit,
	})
}

func lookupCallback(opt OptionType) unsafe.Pointer {
	switch opt {
	case OptHeartbeatCheckoutFailure:
		return unsafe.Pointer(C.goCheckoutFailure)
	case OptHeartbeatCheckoutSuccess:
		return unsafe.Pointer(C.goCheckoutSuccess)
	case OptHeartbeatRetryFeature:
		return unsafe.Pointer(C.goCheckoutRetry)
	case OptHeartbeatConnectionLost:
		return unsafe.Pointer(C.goConnectionLost)
	case OptHeartbeatExit:
		return unsafe.Pointer(C.goExit)
	default:
		return unsafe.Pointer(uintptr(0))
	}
}

type cgoClient struct {
	m         sync.Mutex // protects callbacks
	callbacks map[OptionType]HeartbeatFunc
	vendor    interface{}
	handle    C.LMX_HANDLE
}

var _ Client = (*cgoClient)(nil)

func newClient() (Client, error) {
	c := &cgoClient{}

	if s := Status(C.LMX_Init((*C.LMX_HANDLE)(unsafe.Pointer(&c.handle)))); s != StatSuccess {
		return nil, ToError(s)
	}
	return c, nil
}

func (c *cgoClient) Close() error {
	if c.handle == nil {
		return ErrNotInitialized
	}
	C.LMX_Free(c.handle)
	return nil
}

func (c *cgoClient) Checkout(feature string, major, minor, count int) error {
	cfeature := C.CString(feature)
	s := Status(C.LMX_Checkout(c.handle, cfeature, C.int(major), C.int(minor), C.int(count)))
	C.free(unsafe.Pointer(cfeature))
	return ToError(s)
}

func (c *cgoClient) Checkin(feature string, count int) error {
	cfeature := C.CString(feature)
	s := Status(C.LMX_Checkin(c.handle, cfeature, C.int(count)))
	C.free(unsafe.Pointer(cfeature))
	return ToError(s)
}

func (c *cgoClient) Error() string {
	return C.GoString(C.LMX_GetErrorMessage(c.handle))
}

func (c *cgoClient) ErrorInfo() *ErrorInfo {
	cerr := C.LMX_GetError(c.handle)
	return &ErrorInfo{
		Status:      Status(cerr.LmxStat),
		Internal:    int(cerr.nInternal),
		Context:     int(cerr.nContext),
		Desc:        C.GoString(&cerr.szDescription[0]),
		FeatureName: C.GoString(&cerr.szFeatureName[0]),
	}
}

func (c *cgoClient) HostID(t HostIDType) ([]HostID, error) {
	backing := [C.LMX_MAX_HOSTIDS]C.LMX_HOSTID{}
	cids := (*C.LMX_HOSTID)(unsafe.Pointer(&backing[0]))
	clen := C.int(0)
	s := Status(C.LMX_Hostid(c.handle, C.LMX_HOSTID_TYPE(t), cids, &clen))
	if s != StatSuccess {
		return nil, ToError(s)
	}
	return goHostId(cids, int(clen)), nil
}

func (c *cgoClient) HostIDString(t HostIDType) (string, error) {
	backing := [C.LMX_MAX_LONG_STRING_LENGTH]C.char{}
	cids := (*C.char)(unsafe.Pointer(&backing[0]))
	s := Status(C.LMX_HostidSimple(c.handle, C.LMX_HOSTID_TYPE(t), cids))
	if s != StatSuccess {
		return "", ToError(s)
	}
	return C.GoString(cids), nil
}

func (c *cgoClient) LicenseInfo() ([]LicenseInfo, error) {
	var cLicInfo *C.LMX_LICENSE_INFO
	if s := Status(C.LMX_GetLicenseInfo(c.handle, &cLicInfo)); s != StatSuccess {
		return nil, ToError(s)
	}
	return goLicenseInfo(cLicInfo), nil
}

func (c *cgoClient) FeatureInfo(feature string) ([]FeatureInfo, error) {
	var cfi C.LMX_FEATURE_INFO
	cfeature := C.CString(feature)
	s := Status(C.LMX_GetFeatureInfo(c.handle, cfeature, &cfi))
	C.free(unsafe.Pointer(cfeature))
	if s != StatSuccess {
		return nil, ToError(s)
	}
	return goFeatureInfo(&cfi), nil
}

func (c *cgoClient) Heartbeat(feature string) error {
	cfeature := C.CString(feature)
	s := Status(C.LMX_Heartbeat(c.handle, cfeature))
	C.free(unsafe.Pointer(cfeature))
	return ToError(s)
}

func (c *cgoClient) ExpireTime(feature string) (t time.Duration, err error) {
	cfeature := C.CString(feature)
	ret := time.Duration(C.LMX_GetExpireTime(c.handle, cfeature))
	C.free(unsafe.Pointer(cfeature))
	switch {
	case ret >= 0:
		t = time.Hour * ret
	case ret == -1:
		err = ErrTooLateDate
	case ret == -2:
		err = ErrDoesNotExpire
	case ret < -2:
		err = ErrUnknownFailure
	}
	return
}

func (c *cgoClient) ServerLog(feature, message string) error {
	cfeature, cmessage := C.CString(feature), C.CString(message)
	s := Status(C.LMX_ServerLog(c.handle, cfeature, cmessage))
	C.free(unsafe.Pointer(cfeature))
	C.free(unsafe.Pointer(cmessage))
	return ToError(s)
}

func (c *cgoClient) ServerFunction(feature, message string) (string, error) {
	if len(message) >= int(C.LMX_MAX_LONG_STRING_LENGTH) {
		return "", ErrInvalidParameter
	}
	cfeature := C.CString(feature)
	backing := [C.LMX_MAX_LONG_STRING_LENGTH]C.char{}
	for i := 0; i < len(message); i++ {
		backing[i] = C.char(message[i])
	}
	cresponse := (*C.char)(unsafe.Pointer(&backing[0]))
	s := Status(C.LMX_ServerFunction(c.handle, cfeature, cresponse))
	C.free(unsafe.Pointer(cfeature))
	if s != StatSuccess {
		return "", ToError(s)
	}
	return C.GoString(cresponse), nil
}

func (c *cgoClient) ClientStoreSave(filename, content string) error {
	cfilename := C.CString(filename)
	ccontent := C.CString(content)
	s := Status(C.LMX_ClientStoreSave(c.handle, cfilename, ccontent))
	C.free(unsafe.Pointer(cfilename))
	C.free(unsafe.Pointer(ccontent))
	return ToError(s)
}

func (c *cgoClient) ClientStoreLoad(filename string) (string, error) {
	cfilename := C.CString(filename)
	backing := [C.LMX_MAX_LONG_STRING_LENGTH]C.char{}
	ccontent := (*C.char)(unsafe.Pointer(&backing[0]))
	s := Status(C.LMX_ClientStoreLoad(c.handle, cfilename, ccontent))
	C.free(unsafe.Pointer(cfilename))
	if s != StatSuccess {
		return "", ToError(s)
	}
	return C.GoString(ccontent), nil
}

func (c *cgoClient) SetOption(option OptionType, value interface{}) error {
	s := StatInvalidParameter
	switch option {
	case OptExactVersion, OptAllowBorrow, OptAllowGrace, OptTrialVirtualMachine,
		OptTrialTerminalServer, OptBlacklist, OptLicenseIdle, OptAllowMultipleServers,
		OptClientHostIDToServer, OptAllowCheckoutLessLicenses:
		s = c.setBooleanOpt(option, value)
	case OptLicensePath, OptCustomShareString, OptLicenseString, OptServersideRequestString,
		OptCustomUsername, OptCustomHostname, OptReservationToken, OptBindAddress:
		s = c.setStringOpt(option, value)
	case OptTrialDays, OptTrialUses, OptAutomaticHeartbeatAttempts, OptAutomaticHeartbeatInterval,
		OptHostIDCacheCleanupInterval:
		s = c.setIntegerOpt(option, value)
	case OptHostIDEnabled, OptHostIDDisabled:
		s = c.setHostIdTypeOpt(option, value)
	case OptCustomHostIDFunction:
		s = StatNotImplemented
	case OptHostIDCompareFunction:
		s = StatNotImplemented
	case OptHeartbeatCheckoutFailure, OptHeartbeatCheckoutSuccess, OptHeartbeatRetryFeature,
		OptHeartbeatConnectionLost, OptHeartbeatExit:
		s = c.setCallback(option, value)
	case OptHeartbeatCallbackVendordata:
		s = c.setVendordata(value)
	}
	return ToError(s)
}

func (c *cgoClient) heartbeat(h *Heartbeat) {
	c.m.Lock()
	if c.callbacks == nil {
		c.m.Unlock()
		return
	}
	v := c.vendor
	fn, ok := c.callbacks[h.Type]
	c.m.Unlock()
	if !ok {
		return
	}
	fn(v, h) // shouldn't be executed in critical section
}

func (c *cgoClient) setCallback(opt OptionType, val interface{}) Status {
	// Initialize heartbeat callbacks. It's effectively a nop if it's already
	// been initialized.
	if s := c.setVendordata(c.vendor); s != StatSuccess {
		return s
	}
	if val == nil {
		s := Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), unsafe.Pointer(uintptr(0))))
		if s != StatSuccess {
			return s
		}
		c.m.Lock()
		delete(c.callbacks, opt)
		c.m.Unlock()
		return StatSuccess
	}
	if fn, ok := val.(HeartbeatFunc); ok {
		c.m.Lock()
		defer c.m.Unlock()
		if _, ok = c.callbacks[opt]; !ok {
			s := Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), lookupCallback(opt)))
			if s != StatSuccess {
				return s
			}
		}
		c.callbacks[opt] = fn
		return StatSuccess
	}
	return StatInvalidParameter
}

func (c *cgoClient) setVendordata(val interface{}) Status {
	const opt = C.LMX_SETTINGS(OptHeartbeatCallbackVendordata)
	c.m.Lock()
	defer c.m.Unlock()
	if c.callbacks == nil {
		s := Status(C.LMX_SetOption(c.handle, opt, unsafe.Pointer(c)))
		if s != StatSuccess {
			return s
		}
		c.callbacks = make(map[OptionType]HeartbeatFunc)
	}
	c.vendor = val
	return StatSuccess
}

func (c *cgoClient) setBooleanOpt(opt OptionType, val interface{}) Status {
	var p unsafe.Pointer
	if val != nil {
		switch value := val.(type) {
		case int:
			if value == 1 {
				p = unsafe.Pointer(uintptr(1))
			}
		case bool:
			if value {
				p = unsafe.Pointer(uintptr(1))
			}
		default:
			return StatInvalidParameter
		}
	}
	return Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), p))
}

func (c *cgoClient) setStringOpt(opt OptionType, val interface{}) Status {
	var p unsafe.Pointer
	if val != nil {
		s, ok := val.(string)
		if !ok {
			return StatInvalidParameter
		}
		p = unsafe.Pointer(C.CString(s))
		defer C.free(unsafe.Pointer(p))
	}
	return Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), p))
}

func (c *cgoClient) setIntegerOpt(opt OptionType, val interface{}) Status {
	var p unsafe.Pointer
	if val != nil {
		n, ok := val.(int)
		if !ok {
			return StatInvalidParameter
		}
		p = unsafe.Pointer(uintptr(n))
	}
	return Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), p))
}

func (c *cgoClient) setHostIdTypeOpt(opt OptionType, val interface{}) Status {
	if valHostID, ok := val.(HostIDType); ok {
		return Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), unsafe.Pointer(uintptr(valHostID))))
	}
	return StatInvalidParameter
}
<commit_msg>Zero handle on (Client).Close<commit_after>package lmx

// #include <lmx.h>
//
// void goConnectionLost(void*, char*, int, int);
// void goCheckoutFailure(void*, char*, int, LMX_STATUS);
// void goCheckoutSuccess(void*, char*, int);
// void goCheckoutRetry(void*, char*, int);
// void goExit(void*);
import "C"

import (
	"net"
	"strconv"
	"sync"
	"time"
	"unsafe"
)

// TODO(rjeczalik): ~QuoteASCII every string

//export goConnectionLost
func goConnectionLost(p unsafe.Pointer, host *C.char, port C.int, n C.int) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type:       OptHeartbeatConnectionLost,
		Addr:       net.JoinHostPort(C.GoString(host), strconv.Itoa(int(port))),
		Heartbeats: int(n),
	})
}

//export goCheckoutFailure
func goCheckoutFailure(p unsafe.Pointer, feature *C.char, n C.int, s C.LMX_STATUS) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type:     OptHeartbeatCheckoutFailure,
		Feature:  C.GoString(feature),
		Err:      ToError(Status(s)),
		Licenses: int(n),
	})
}

//export goCheckoutSuccess
func goCheckoutSuccess(p unsafe.Pointer, feature *C.char, n C.int) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type:     OptHeartbeatCheckoutSuccess,
		Feature:  C.GoString(feature),
		Licenses: int(n),
	})
}

//export goCheckoutRetry
func goCheckoutRetry(p unsafe.Pointer, feature *C.char, n C.int) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type:     OptHeartbeatRetryFeature,
		Feature:  C.GoString(feature),
		Licenses: int(n),
	})
}

//export goExit
func goExit(p unsafe.Pointer) {
	(*cgoClient)(p).heartbeat(&Heartbeat{
		Type: OptHeartbeatExit,
	})
}

func lookupCallback(opt OptionType) unsafe.Pointer {
	switch opt {
	case OptHeartbeatCheckoutFailure:
		return unsafe.Pointer(C.goCheckoutFailure)
	case OptHeartbeatCheckoutSuccess:
		return unsafe.Pointer(C.goCheckoutSuccess)
	case OptHeartbeatRetryFeature:
		return unsafe.Pointer(C.goCheckoutRetry)
	case OptHeartbeatConnectionLost:
		return unsafe.Pointer(C.goConnectionLost)
	case OptHeartbeatExit:
		return unsafe.Pointer(C.goExit)
	default:
		return unsafe.Pointer(uintptr(0))
	}
}

type cgoClient struct {
	m         sync.Mutex // protects callbacks
	callbacks map[OptionType]HeartbeatFunc
	vendor    interface{}
	handle    C.LMX_HANDLE
}

var _ Client = (*cgoClient)(nil)

func newClient() (Client, error) {
	c := &cgoClient{}

	if s := Status(C.LMX_Init((*C.LMX_HANDLE)(unsafe.Pointer(&c.handle)))); s != StatSuccess {
		return nil, ToError(s)
	}
	return c, nil
}

func (c *cgoClient) Close() error {
	if c.handle == nil {
		return ErrNotInitialized
	}
	C.LMX_Free(c.handle)
	c.handle = nil
	return nil
}

func (c *cgoClient) Checkout(feature string, major, minor, count int) error {
	cfeature := C.CString(feature)
	s := Status(C.LMX_Checkout(c.handle, cfeature, C.int(major), C.int(minor), C.int(count)))
	C.free(unsafe.Pointer(cfeature))
	return ToError(s)
}

func (c *cgoClient) Checkin(feature string, count int) error {
	cfeature := C.CString(feature)
	s := Status(C.LMX_Checkin(c.handle, cfeature, C.int(count)))
	C.free(unsafe.Pointer(cfeature))
	return ToError(s)
}

func (c *cgoClient) Error() string {
	return C.GoString(C.LMX_GetErrorMessage(c.handle))
}

func (c *cgoClient) ErrorInfo() *ErrorInfo {
	cerr := C.LMX_GetError(c.handle)
	return &ErrorInfo{
		Status:      Status(cerr.LmxStat),
		Internal:    int(cerr.nInternal),
		Context:     int(cerr.nContext),
		Desc:        C.GoString(&cerr.szDescription[0]),
		FeatureName: C.GoString(&cerr.szFeatureName[0]),
	}
}

func (c *cgoClient) HostID(t HostIDType) ([]HostID, error) {
	backing := [C.LMX_MAX_HOSTIDS]C.LMX_HOSTID{}
	cids := (*C.LMX_HOSTID)(unsafe.Pointer(&backing[0]))
	clen := C.int(0)
	s := Status(C.LMX_Hostid(c.handle, C.LMX_HOSTID_TYPE(t), cids, &clen))
	if s != StatSuccess {
		return nil, ToError(s)
	}
	return goHostId(cids, int(clen)), nil
}

func (c *cgoClient) HostIDString(t HostIDType) (string, error) {
	backing := [C.LMX_MAX_LONG_STRING_LENGTH]C.char{}
	cids := (*C.char)(unsafe.Pointer(&backing[0]))
	s := Status(C.LMX_HostidSimple(c.handle, C.LMX_HOSTID_TYPE(t), cids))
	if s != StatSuccess {
		return "", ToError(s)
	}
	return C.GoString(cids), nil
}

func (c *cgoClient) LicenseInfo() ([]LicenseInfo, error) {
	var cLicInfo *C.LMX_LICENSE_INFO
	if s := Status(C.LMX_GetLicenseInfo(c.handle, &cLicInfo)); s != StatSuccess {
		return nil, ToError(s)
	}
	return goLicenseInfo(cLicInfo), nil
}

func (c *cgoClient) FeatureInfo(feature string) ([]FeatureInfo, error) {
	var cfi C.LMX_FEATURE_INFO
	cfeature := C.CString(feature)
	s := Status(C.LMX_GetFeatureInfo(c.handle, cfeature, &cfi))
	C.free(unsafe.Pointer(cfeature))
	if s != StatSuccess {
		return nil, ToError(s)
	}
	return goFeatureInfo(&cfi), nil
}

func (c *cgoClient) Heartbeat(feature string) error {
	cfeature := C.CString(feature)
	s := Status(C.LMX_Heartbeat(c.handle, cfeature))
	C.free(unsafe.Pointer(cfeature))
	return ToError(s)
}

func (c *cgoClient) ExpireTime(feature string) (t time.Duration, err error) {
	cfeature := C.CString(feature)
	ret := time.Duration(C.LMX_GetExpireTime(c.handle, cfeature))
	C.free(unsafe.Pointer(cfeature))
	switch {
	case ret >= 0:
		t = time.Hour * ret
	case ret == -1:
		err = ErrTooLateDate
	case ret == -2:
		err = ErrDoesNotExpire
	case ret < -2:
		err = ErrUnknownFailure
	}
	return
}

func (c *cgoClient) ServerLog(feature, message string) error {
	cfeature, cmessage := C.CString(feature), C.CString(message)
	s := Status(C.LMX_ServerLog(c.handle, cfeature, cmessage))
	C.free(unsafe.Pointer(cfeature))
	C.free(unsafe.Pointer(cmessage))
	return ToError(s)
}

func (c *cgoClient) ServerFunction(feature, message string) (string, error) {
	if len(message) >= int(C.LMX_MAX_LONG_STRING_LENGTH) {
		return "", ErrInvalidParameter
	}
	cfeature := C.CString(feature)
	backing := [C.LMX_MAX_LONG_STRING_LENGTH]C.char{}
	for i := 0; i < len(message); i++ {
		backing[i] = C.char(message[i])
	}
	cresponse := (*C.char)(unsafe.Pointer(&backing[0]))
	s := Status(C.LMX_ServerFunction(c.handle, cfeature, cresponse))
	C.free(unsafe.Pointer(cfeature))
	if s != StatSuccess {
		return "", ToError(s)
	}
	return C.GoString(cresponse), nil
}

func (c *cgoClient) ClientStoreSave(filename, content string) error {
	cfilename := C.CString(filename)
	ccontent := C.CString(content)
	s := Status(C.LMX_ClientStoreSave(c.handle, cfilename, ccontent))
	C.free(unsafe.Pointer(cfilename))
	C.free(unsafe.Pointer(ccontent))
	return ToError(s)
}

func (c *cgoClient) ClientStoreLoad(filename string) (string, error) {
	cfilename := C.CString(filename)
	backing := [C.LMX_MAX_LONG_STRING_LENGTH]C.char{}
	ccontent := (*C.char)(unsafe.Pointer(&backing[0]))
	s := Status(C.LMX_ClientStoreLoad(c.handle, cfilename, ccontent))
	C.free(unsafe.Pointer(cfilename))
	if s != StatSuccess {
		return "", ToError(s)
	}
	return C.GoString(ccontent), nil
}

func (c *cgoClient) SetOption(option OptionType, value interface{}) error {
	s := StatInvalidParameter
	switch option {
	case OptExactVersion, OptAllowBorrow, OptAllowGrace, OptTrialVirtualMachine,
		OptTrialTerminalServer, OptBlacklist, OptLicenseIdle, OptAllowMultipleServers,
		OptClientHostIDToServer, OptAllowCheckoutLessLicenses:
		s = c.setBooleanOpt(option, value)
	case OptLicensePath, OptCustomShareString, OptLicenseString, OptServersideRequestString,
		OptCustomUsername, OptCustomHostname, OptReservationToken, OptBindAddress:
		s = c.setStringOpt(option, value)
	case OptTrialDays, OptTrialUses, OptAutomaticHeartbeatAttempts, OptAutomaticHeartbeatInterval,
		OptHostIDCacheCleanupInterval:
		s = c.setIntegerOpt(option, value)
	case OptHostIDEnabled, OptHostIDDisabled:
		s = c.setHostIdTypeOpt(option, value)
	case OptCustomHostIDFunction:
		s = StatNotImplemented
	case OptHostIDCompareFunction:
		s = StatNotImplemented
	case OptHeartbeatCheckoutFailure, OptHeartbeatCheckoutSuccess, OptHeartbeatRetryFeature,
		OptHeartbeatConnectionLost, OptHeartbeatExit:
		s = c.setCallback(option, value)
	case OptHeartbeatCallbackVendordata:
		s = c.setVendordata(value)
	}
	return ToError(s)
}

func (c *cgoClient) heartbeat(h *Heartbeat) {
	c.m.Lock()
	if c.callbacks == nil {
		c.m.Unlock()
		return
	}
	v := c.vendor
	fn, ok := c.callbacks[h.Type]
	c.m.Unlock()
	if !ok {
		return
	}
	fn(v, h) // shouldn't be executed in critical section
}

func (c *cgoClient) setCallback(opt OptionType, val interface{}) Status {
	// Initialize heartbeat callbacks. It's effectively a nop if it's already
	// been initialized.
	if s := c.setVendordata(c.vendor); s != StatSuccess {
		return s
	}
	if val == nil {
		s := Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), unsafe.Pointer(uintptr(0))))
		if s != StatSuccess {
			return s
		}
		c.m.Lock()
		delete(c.callbacks, opt)
		c.m.Unlock()
		return StatSuccess
	}
	if fn, ok := val.(HeartbeatFunc); ok {
		c.m.Lock()
		defer c.m.Unlock()
		if _, ok = c.callbacks[opt]; !ok {
			s := Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), lookupCallback(opt)))
			if s != StatSuccess {
				return s
			}
		}
		c.callbacks[opt] = fn
		return StatSuccess
	}
	return StatInvalidParameter
}

func (c *cgoClient) setVendordata(val interface{}) Status {
	const opt = C.LMX_SETTINGS(OptHeartbeatCallbackVendordata)
	c.m.Lock()
	defer c.m.Unlock()
	if c.callbacks == nil {
		s := Status(C.LMX_SetOption(c.handle, opt, unsafe.Pointer(c)))
		if s != StatSuccess {
			return s
		}
		c.callbacks = make(map[OptionType]HeartbeatFunc)
	}
	c.vendor = val
	return StatSuccess
}

func (c *cgoClient) setBooleanOpt(opt OptionType, val interface{}) Status {
	var p unsafe.Pointer
	if val != nil {
		switch value := val.(type) {
		case int:
			if value == 1 {
				p = unsafe.Pointer(uintptr(1))
			}
		case bool:
			if value {
				p = unsafe.Pointer(uintptr(1))
			}
		default:
			return StatInvalidParameter
		}
	}
	return Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), p))
}

func (c *cgoClient) setStringOpt(opt OptionType, val interface{}) Status {
	var p unsafe.Pointer
	if val != nil {
		s, ok := val.(string)
		if !ok {
			return StatInvalidParameter
		}
		p = unsafe.Pointer(C.CString(s))
		defer C.free(unsafe.Pointer(p))
	}
	return Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), p))
}

func (c *cgoClient) setIntegerOpt(opt OptionType, val interface{}) Status {
	var p unsafe.Pointer
	if val != nil {
		n, ok := val.(int)
		if !ok {
			return StatInvalidParameter
		}
		p = unsafe.Pointer(uintptr(n))
	}
	return Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), p))
}

func (c *cgoClient) setHostIdTypeOpt(opt OptionType, val interface{}) Status {
	if valHostID, ok := val.(HostIDType); ok {
		return Status(C.LMX_SetOption(c.handle, C.LMX_SETTINGS(opt), unsafe.Pointer(uintptr(valHostID))))
	}
	return StatInvalidParameter
}
<|endoftext|>


<commit_before>package main

import (
    "flag"
    "fmt"
    "qmsk.net/clusterf/ipvs"
    "log"
    "os"
    "qmsk.net/clusterf/server"
    "syscall"
)

var (
    etcdConfig server.EtcdConfig
)

func init() {
    flag.StringVar(&etcdConfig.Machines, "etcd-machines", "http://127.0.0.1:2379",
        "Client endpoint for etcd")
    flag.StringVar(&etcdConfig.Prefix, "etcd-prefix", "/clusterf",
        "Etcd tree prefix")
}

type Server struct {
    etcd    *server.Etcd
    ipvs    *ipvs.Client
}

func main() {
    log.Println("Hello World")

    flag.Parse()

    if len(flag.Args()) > 0 {
        flag.Usage()
        os.Exit(1)
    }

    var self Server

    // IPVS
    if ipvsClient, err := ipvs.Open(); err != nil {
        log.Fatalf("ipvs.Open: %v\n", err)
    } else {
        log.Printf("ipvs.Open: %+v\n", ipvsClient)

        if err := ipvsClient.GetInfo(); err != nil {
            log.Fatalf("ipvs.GetInfo: %v\n", err)
        }

        self.ipvs = ipvsClient

        if services, err := ipvsClient.ListServices(); err != nil {
            log.Fatalf("ipvs.ListServices: %v\n", err)
        } else {
            fmt.Printf("Proto                           Addr:Port  Sched\n")
            for _, service := range services {
                fmt.Printf("%5d %30s:%-5d %s\n",
                    service.Protocol,
                    service.Addr, service.Port,
                    service.SchedName,
                )

                if dests, err := ipvsClient.ListDests(service); err != nil {
                    log.Fatalf("ipvs.ListDests: %v\n", err)
                } else {
                    for _, dest := range dests {
                        fmt.Printf("%5s %30s:%-5d %#04x\n",
                            "->",
                            dest.Addr, dest.Port,
                            dest.FwdMethod,
                        )
                    }
                }
            }
        }

        if err := ipvsClient.Flush(); err != nil {
            log.Fatalf("ipvs.Flush: %v\n", err)
        }
    }

    // etcd
    if etcdClient, err := etcdConfig.Open(); err != nil {
        log.Fatalf("etcd.Open: %s\n", err)
    } else {
        log.Printf("etcd.Open: %s\n", etcdClient)

        self.etcd = etcdClient

        // start
        err := self.etcd.Sync(func (service server.Service) {
            log.Printf("etcd.Sync %s: Frontend %+v\n", service.Name, service.Frontend)

            ipvsService := ipvs.Service{
                Af:         syscall.AF_INET,
                Protocol:   syscall.IPPROTO_TCP,
                Addr:       service.Frontend.IPv4,
                Port:       service.Frontend.TCP,

                SchedName:  "wlc",
                Timeout:    0,
                Flags:      ipvs.IPVSFlags{Flags: 0, Mask: 0xffffffff},
                Netmask:    0xffffffff,
            }

            if err := self.ipvs.NewService(ipvsService); err != nil  {
                log.Fatalf("ipvs.NewService %s: %s\n", service.Name, err)
            }

            for serverName, server := range service.Servers {
                log.Printf("etcd.Sync %s: Server %s: %+v\n", service.Name, serverName, server)

                ipvsDest := ipvs.Dest{
                    Addr:       server.IPv4,
                    Port:       server.TCP,
                    FwdMethod:  ipvs.IP_VS_CONN_F_MASQ,
                    Weight:     10,
                }

                if err := self.ipvs.NewDest(ipvsService, ipvsDest); err != nil {
                    log.Fatalf("ipvs.NewDest %s %s: %s\n", service.Name, serverName, err)
                }
            }
        })
        if err != nil {
            log.Fatalf("etcd.Sync: %s\n", err)
        }
    }
}
<commit_msg>fixup logging, add -ipvs-debug flag<commit_after>package main

import (
    "flag"
    "fmt"
    "qmsk.net/clusterf/ipvs"
    "log"
    "os"
    "qmsk.net/clusterf/server"
    "syscall"
)

var (
    etcdConfig server.EtcdConfig
    ipvsDebug   bool
)

func init() {
    flag.StringVar(&etcdConfig.Machines, "etcd-machines", "http://127.0.0.1:2379",
        "Client endpoint for etcd")
    flag.StringVar(&etcdConfig.Prefix, "etcd-prefix", "/clusterf",
        "Etcd tree prefix")

    flag.BoolVar(&ipvsDebug, "ipvs-debug", false,
        "IPVS debugging")
}

type Server struct {
    etcd    *server.Etcd
    ipvs    *ipvs.Client
}

func protoString (proto uint16) string {
    switch (proto) {
    case syscall.IPPROTO_TCP:   return "TCP"
    case syscall.IPPROTO_UDP:   return "UDP"
    default: return fmt.Sprintf("%d", proto)
    }
}

func (self *Server) printIPVS () {
    if services, err := self.ipvs.ListServices(); err != nil {
        log.Fatalf("ipvs.ListServices: %v\n", err)
    } else {
        fmt.Printf("Proto                           Addr:Port\n")
        for _, service := range services {
            fmt.Printf("%-5s %30s:%-5d %s\n",
                protoString(service.Protocol),
                service.Addr, service.Port,
                service.SchedName,
            )

            if dests, err := self.ipvs.ListDests(service); err != nil {
                log.Fatalf("ipvs.ListDests: %v\n", err)
            } else {
                for _, dest := range dests {
                    fmt.Printf("%5s %30s:%-5d %#04x\n",
                        "",
                        dest.Addr, dest.Port,
                        dest.FwdMethod,
                    )
                }
            }
        }
    }
}

func main() {
    flag.Parse()

    if len(flag.Args()) > 0 {
        flag.Usage()
        os.Exit(1)
    }

    var self Server

    // IPVS
    if ipvsClient, err := ipvs.Open(); err != nil {
        log.Fatalf("ipvs.Open: %v\n", err)
    } else {
        log.Printf("ipvs.Open\n")

        if ipvsDebug {
            ipvsClient.SetDebug()
        }

        if info, err := ipvsClient.GetInfo(); err != nil {
            log.Fatalf("ipvs.GetInfo: %v\n", err)
        } else {
            log.Printf("ipvs.GetInfo: version=%s, conn_tab_size=%d\n", info.Version, info.ConnTabSize)
        }

        self.ipvs = ipvsClient
    }

    if err := self.ipvs.Flush(); err != nil {
        log.Fatalf("ipvs.Flush: %v\n", err)
    }

    // etcd
    if etcdClient, err := etcdConfig.Open(); err != nil {
        log.Fatalf("etcd.Open: %s\n", err)
    } else {
        log.Printf("etcd.Open: %s\n", etcdClient)

        self.etcd = etcdClient

        // start
        err := self.etcd.Sync(func (service server.Service) {
            log.Printf("Sync %s: Frontend %+v\n", service.Name, service.Frontend)

            ipvsService := ipvs.Service{
                Af:         syscall.AF_INET,
                Protocol:   syscall.IPPROTO_TCP,
                Addr:       service.Frontend.IPv4,
                Port:       service.Frontend.TCP,

                SchedName:  "wlc",
                Timeout:    0,
                Flags:      ipvs.IPVSFlags{Flags: 0, Mask: 0xffffffff},
                Netmask:    0xffffffff,
            }

            if err := self.ipvs.NewService(ipvsService); err != nil  {
                log.Fatalf("ipvs.NewService %s: %s\n", service.Name, err)
            }

            for serverName, server := range service.Servers {
                log.Printf("Sync %s: Server %s: %+v\n", service.Name, serverName, server)

                ipvsDest := ipvs.Dest{
                    Addr:       server.IPv4,
                    Port:       server.TCP,
                    FwdMethod:  ipvs.IP_VS_CONN_F_MASQ,
                    Weight:     10,
                }

                if err := self.ipvs.NewDest(ipvsService, ipvsDest); err != nil {
                    log.Fatalf("ipvs.NewDest %s %s: %s\n", service.Name, serverName, err)
                }
            }
        })
        if err != nil {
            log.Fatalf("etcd.Sync: %s\n", err)
        }
    }

    // self.printIPVS()
}
<|endoftext|>


<commit_before>package main

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

	"github.com/trinchan/slackbot/Godeps/_workspace/src/github.com/gorilla/schema"
	_ "github.com/trinchan/slackbot/importer"
	"github.com/trinchan/slackbot/robots"
)

func main() {
	http.HandleFunc("/slack", slashCommandHandler)
	http.HandleFunc("/slack_hook", hookHandler)
	startServer()
}

func hookHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	d := schema.NewDecoder()
	command := new(robots.OutgoingWebHook)
	err = d.Decode(command, r.PostForm)
	if err != nil {
		log.Println("Couldn't parse post request:", err)
	}
	if command.Text == "" || command.Token != os.Getenv(fmt.Sprintf("%s_OUT_TOKEN", strings.ToUpper(command.TeamDomain))) {
		log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	log.Printf("Recieved command: %s from \"%s\"\n", command.Text[1:len(command.Text)], command.TeamDomain)
	//Assume outgoing webhooks are preceded with a character
	c := strings.Split(command.Text[1:len(command.Text)], " ")
	command.Robot = c[0]
	command.Text = strings.Join(c[1:], " ")

	robots := getRobots(command.Robot)
	if len(robots) == 0 {
		jsonResp(w, "No robot for that command yet :(")
		return
	}
	resp := ""
	for _, robot := range robots {
		resp += fmt.Sprintf("\n%s", robot.Run(&command.Payload))
	}
	w.WriteHeader(http.StatusOK)
	jsonResp(w, resp)
}

func slashCommandHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	d := schema.NewDecoder()
	command := new(robots.SlashCommand)
	err = d.Decode(command, r.PostForm)
	if err != nil {
		log.Println("Couldn't parse post request:", err)
	}
	if command.Command == "" || command.Token == "" {
		log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	command.Robot = command.Command[1:]

	if token := os.Getenv(fmt.Sprintf("%s_SLACK_TOKEN", strings.ToUpper(command.Robot))); token != "" && token != command.Token {
		log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
		w.WriteHeader(http.StatusBadRequest)
	}
	robots := getRobots(command.Robot)
	if len(robots) == 0 {
		plainResp(w, "No robot for that command yet :(")
		return
	}
	resp := ""
	for _, robot := range robots {
		resp += fmt.Sprintf("\n%s", robot.Run(&command.Payload))
	}
	plainResp(w, strings.TrimSpace(resp))
}

func jsonResp(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	resp := map[string]string{"text": msg}
	r, err := json.Marshal(resp)
	if err != nil {
		log.Println("Couldn't marshal hook response:", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.Write(r)
}

func plainResp(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Write([]byte(msg))
}

func startServer() {
	port := os.Getenv("PORT")
	if port == "" {
		log.Fatal("PORT not set")
	}
	log.Printf("Starting HTTP server on %s", port)
	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Fatal("Server start error: ", err)
	}
}

func getRobots(command string) []robots.Robot {
	if r, ok := robots.Robots[command]; ok {
		return r
	}
	return nil
}
<commit_msg>fix outgoing webhook s<commit_after>package main

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

	"github.com/trinchan/slackbot/Godeps/_workspace/src/github.com/gorilla/schema"
	_ "github.com/trinchan/slackbot/importer"
	"github.com/trinchan/slackbot/robots"
)

func main() {
	http.HandleFunc("/slack", slashCommandHandler)
	http.HandleFunc("/slack_hook", hookHandler)
	startServer()
}

func hookHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	d := schema.NewDecoder()
	command := new(robots.OutgoingWebHook)
	err = d.Decode(command, r.PostForm)
	if err != nil {
		log.Println("Couldn't parse post request:", err)
	}
	if command.Text == "" || command.Token != os.Getenv(fmt.Sprintf("%s_OUT_TOKEN", strings.ToUpper(command.TeamDomain))) {
		log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	com := strings.TrimPrefix(command.Text, command.TriggerWord+" ")
	c := strings.Split(com, " ")
	command.Robot = c[0]
	command.Text = strings.Join(c[1:], " ")

	robots := getRobots(command.Robot)
	if len(robots) == 0 {
		jsonResp(w, "No robot for that command yet :(")
		return
	}
	resp := ""
	for _, robot := range robots {
		resp += fmt.Sprintf("\n%s", robot.Run(&command.Payload))
	}
	w.WriteHeader(http.StatusOK)
	jsonResp(w, resp)
}

func slashCommandHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	d := schema.NewDecoder()
	command := new(robots.SlashCommand)
	err = d.Decode(command, r.PostForm)
	if err != nil {
		log.Println("Couldn't parse post request:", err)
	}
	if command.Command == "" || command.Token == "" {
		log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	command.Robot = command.Command[1:]

	if token := os.Getenv(fmt.Sprintf("%s_SLACK_TOKEN", strings.ToUpper(command.Robot))); token != "" && token != command.Token {
		log.Printf("[DEBUG] Ignoring request from unidentified source: %s - %s", command.Token, r.Host)
		w.WriteHeader(http.StatusBadRequest)
	}
	robots := getRobots(command.Robot)
	if len(robots) == 0 {
		plainResp(w, "No robot for that command yet :(")
		return
	}
	resp := ""
	for _, robot := range robots {
		resp += fmt.Sprintf("\n%s", robot.Run(&command.Payload))
	}
	plainResp(w, strings.TrimSpace(resp))
}

func jsonResp(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	resp := map[string]string{"text": msg}
	r, err := json.Marshal(resp)
	if err != nil {
		log.Println("Couldn't marshal hook response:", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.Write(r)
}

func plainResp(w http.ResponseWriter, msg string) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Write([]byte(msg))
}

func startServer() {
	port := os.Getenv("PORT")
	if port == "" {
		log.Fatal("PORT not set")
	}
	log.Printf("Starting HTTP server on %s", port)
	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Fatal("Server start error: ", err)
	}
}

func getRobots(command string) []robots.Robot {
	if r, ok := robots.Robots[command]; ok {
		return r
	}
	return nil
}
<|endoftext|>


<commit_before>// This software is direct fork of https://github.com/knq/chromedp/tree/master/cmd/chromedp-proxy
// with couple of features added
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"path"

	"github.com/gorilla/websocket"
	"github.com/fatih/color"
)

var (
	flagListen   = flag.String("l", "localhost:9223", "listen address")
	flagRemote   = flag.String("r", "localhost:9222", "remote address")
	flagNoLog    = flag.Bool("n", false, "disable logging to file")
	flagLogMask  = flag.String("log", "logs/cdp-%s.log", "log file mask")
	flagEllipsis = flag.Bool("s", false, "shorten requests and responses")
	flagOnce = flag.Bool("once", false, "debug single session")
)

var (
	responseColor = color.New(color.FgHiGreen).SprintfFunc()
	requestColor = color.New(color.FgGreen).SprintfFunc()
	eventsColor = color.New(color.FgHiRed).SprintfFunc()
	protocolColor = color.New(color.FgYellow).SprintfFunc()
	protocolError = color.New(color.FgHiYellow, color.BgRed).SprintfFunc()
	targetColor = color.New(color.FgHiWhite).SprintfFunc()
	methodColor = color.New(color.FgHiYellow).SprintfFunc()
	errorColor = color.New(color.BgRed, color.FgWhite).SprintfFunc()
)

const (
	incomingBufferSize = 10 * 1024 * 1024
	outgoingBufferSize = 25 * 1024 * 1024
	ellipsisLength     = 80
)

var protocolTargetId = center("protocol message", 36)

var wsUpgrader = &websocket.Upgrader{
	ReadBufferSize:  incomingBufferSize,
	WriteBufferSize: outgoingBufferSize,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

var wsDialer = &websocket.Dialer{
	ReadBufferSize:  outgoingBufferSize,
	WriteBufferSize: incomingBufferSize,
}

func main() {
	flag.Parse()

	mux := http.NewServeMux()

	simplep := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: *flagRemote})

	mux.Handle("/json", simplep)
	mux.Handle("/", simplep)

	mux.HandleFunc("/devtools/page/", func(res http.ResponseWriter, req *http.Request) {

		id := path.Base(req.URL.Path)
		f, logger := createLog(id)
		if f != nil {
			defer f.Close()
		}

		stream := make(chan *protocolMessage, 1024)
		go dumpStream(logger, stream)

		logger.Printf(protocolColor("---------- connection from %s ----------", req.RemoteAddr))

		ver, err := checkVersion()
		if err != nil {
			msg := fmt.Sprintf("version error, got: %v", err)
			logger.Println(protocolError(msg))
			http.Error(res, msg, 500)
			return
		}
		logger.Printf(protocolColor("endpoint %s reported: %s", *flagRemote, serialize(ver)))

		endpoint := "ws://" + *flagRemote + "/devtools/page/" + id

		// connect outgoing websocket
		logger.Printf(protocolColor("connecting to %s", endpoint))
		out, pres, err := wsDialer.Dial(endpoint, nil)
		if err != nil {
			msg := fmt.Sprintf("could not connect to %s, got: %v", endpoint, err)
			logger.Println(protocolError(msg))
			http.Error(res, msg, 500)
			return
		}
		defer pres.Body.Close()
		defer out.Close()

		logger.Printf(protocolColor("connected to %s", endpoint))

		// connect incoming websocket
		logger.Printf(protocolColor("upgrading connection on %s", req.RemoteAddr))
		in, err := wsUpgrader.Upgrade(res, req, nil)
		if err != nil {
			msg := fmt.Sprintf("could not upgrade websocket from %s, got: %v", req.RemoteAddr, err)
			logger.Println(protocolError(msg))
			http.Error(res, msg, 500)
			return
		}
		defer in.Close()
		logger.Printf(protocolColor("upgraded connection on %s", req.RemoteAddr))

		ctxt, cancel := context.WithCancel(context.Background())
		defer cancel()

		errc := make(chan error, 1)
		go proxyWS(ctxt, stream, in, out, errc)
		go proxyWS(ctxt, stream, out, in, errc)
		<-errc
		logger.Printf(protocolColor("---------- closing %s ----------", req.RemoteAddr))

		if *flagOnce {
			os.Exit(0)
		}
	})

	log.Fatal(http.ListenAndServe(*flagListen, mux))
}

func dumpStream(logger *log.Logger, stream chan *protocolMessage) {
	logger.Printf("Legend: %s, %s, %s, %s",
		protocolColor("protocol informations"),
		eventsColor("received events"),
		requestColor("sent requests"),
		responseColor("received responses."),
	)

	requests := make(map[uint64]*protocolMessage)
	targetRequests := make(map[uint64]*protocolMessage)

	for {
		select {
		case msg := <-stream:
			if msg.InTarget() {
				if msg.IsRequest() {
					requests[msg.Id] = nil

					if protocolMessage, err := decodeMessage([]byte(asString(msg.Params["message"]))); err == nil {
						targetRequests[protocolMessage.Id] = protocolMessage
					} else {
						logger.Printf(protocolColor("Could not deserialize message: %+v", err))
					}
				}

				if msg.IsEvent() {
					if protocolMessage, err := decodeMessage([]byte(asString(msg.Params["message"]))); err == nil {
						if protocolMessage.IsEvent() {
							logger.Printf("%s %36s(%s)", targetColor("%s", msg.Params["targetId"]), methodColor(protocolMessage.Method), eventsColor(serialize(protocolMessage.Params)))
						}

						if protocolMessage.IsResponse() {
							if request, ok := targetRequests[protocolMessage.Id]; ok {
								delete(targetRequests, protocolMessage.Id)

								if protocolMessage.IsError() {
									logger.Printf("%s %36s(%s) = %s", targetColor("%s", msg.Params["targetId"]), methodColor(request.Method), requestColor(serialize(request.Params)), errorColor(serialize(protocolMessage.Error)))
								} else {
									logger.Printf("%s %36s(%s) = %s", targetColor("%s", msg.Params["targetId"]), methodColor(request.Method), requestColor(serialize(request.Params)), responseColor(serialize(protocolMessage.Result)))
								}
							} else {
								logger.Printf(protocolColor("Could not find target request with id: %d", protocolMessage.Id))
							}
						}
					} else {
						logger.Printf(protocolColor("Could not deserialize message: %+v", err))
					}
				}

			} else {
				if msg.IsRequest() {
					requests[msg.Id] = msg
				}

				if msg.IsResponse() {
					if request, ok := requests[msg.Id]; ok {
						delete(requests, msg.Id)

						if request != nil {
							if msg.IsError() {
								logger.Printf("%s %36s(%s) = %s", targetColor(protocolTargetId), methodColor(request.Method), requestColor(serialize(request.Params)), errorColor(serialize(msg.Error)))
							} else {
								logger.Printf("%s %36s(%s) = %s", targetColor(protocolTargetId), methodColor(request.Method), requestColor(serialize(request.Params)), responseColor(serialize(msg.Result)))
							}
						}
					} else {
					logger.Printf(protocolColor("Could not find request with id: %d", msg.Id))
					}
				}

				if msg.IsEvent() {
					logger.Printf("%s %36s(%s)", targetColor(protocolTargetId), methodColor(msg.Method), eventsColor(serialize(msg.Params)))
				}
			}
		}
	}
}

func proxyWS(ctxt context.Context, stream chan *protocolMessage, in, out *websocket.Conn, errc chan error) {
	var mt int
	var buf []byte
	var err error

	for {
		select {
		default:
			mt, buf, err = in.ReadMessage()
			if err != nil {
				errc <- err
				return
			}

			if msg, err := decodeMessage(buf); err == nil {
				stream <- msg
			}

			err = out.WriteMessage(mt, buf)

			if err != nil {
				errc <- err
				return
			}

		case <-ctxt.Done():
			return
		}
	}
}

func checkVersion() (map[string]string, error) {
	cl := &http.Client{}
	req, err := http.NewRequest("GET", "http://" + *flagRemote + "/json/version", nil)
	if err != nil {
		return nil, err
	}

	res, err := cl.Do(req)
	if err != nil {
		return nil, err
	}

	defer res.Body.Close()

	var v map[string]string
	if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
		return nil, fmt.Errorf("expected json result")
	}

	return v, nil
}

func createLog(id string) (io.Closer, *log.Logger) {
	var f io.Closer
	var w io.Writer = os.Stdout
	if !*flagNoLog && *flagLogMask != "" {
		l, err := os.OpenFile(fmt.Sprintf(*flagLogMask, id), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err != nil {
			log.Fatal(err)
		}

		f = l
		w = io.MultiWriter(os.Stdout, l)
	}
	return f, log.New(w, "", log.Lmicroseconds)
}

<commit_msg>Little code refactoring<commit_after>// This software is direct fork of https://github.com/knq/chromedp/tree/master/cmd/chromedp-proxy
// with couple of features added
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"path"

	"github.com/gorilla/websocket"
	"github.com/fatih/color"
	"errors"
)

var (
	flagListen   = flag.String("l", "localhost:9223", "listen address")
	flagRemote   = flag.String("r", "localhost:9222", "remote address")
	flagNoLog    = flag.Bool("n", false, "disable logging to file")
	flagLogMask  = flag.String("log", "logs/cdp-%s.log", "log file mask")
	flagEllipsis = flag.Bool("s", false, "shorten requests and responses")
	flagOnce = flag.Bool("once", false, "debug single session")
)

var (
	responseColor = color.New(color.FgHiGreen).SprintfFunc()
	requestColor = color.New(color.FgGreen).SprintfFunc()
	eventsColor = color.New(color.FgHiRed).SprintfFunc()
	protocolColor = color.New(color.FgYellow).SprintfFunc()
	protocolError = color.New(color.FgHiYellow, color.BgRed).SprintfFunc()
	targetColor = color.New(color.FgHiWhite).SprintfFunc()
	methodColor = color.New(color.FgHiYellow).SprintfFunc()
	errorColor = color.New(color.BgRed, color.FgWhite).SprintfFunc()
)

const (
	incomingBufferSize = 10 * 1024 * 1024
	outgoingBufferSize = 25 * 1024 * 1024
	ellipsisLength     = 80
)

var protocolTargetId = center("protocol message", 36)

var wsUpgrader = &websocket.Upgrader{
	ReadBufferSize:  incomingBufferSize,
	WriteBufferSize: outgoingBufferSize,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

var wsDialer = &websocket.Dialer{
	ReadBufferSize:  outgoingBufferSize,
	WriteBufferSize: incomingBufferSize,
}

func main() {
	flag.Parse()

	mux := http.NewServeMux()

	simplep := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: *flagRemote})

	mux.Handle("/json", simplep)
	mux.Handle("/", simplep)

	mux.HandleFunc("/devtools/page/", func(res http.ResponseWriter, req *http.Request) {

		stream := make(chan *protocolMessage, 1024)

		id := path.Base(req.URL.Path)
		f, logger := createLog(id)
		if f != nil {
			defer f.Close()
		}
		go dumpStream(logger, stream)

		endpoint := "ws://" + *flagRemote + "/devtools/page/" + id

		logger.Print(protocolColor("---------- connection from %s ----------", req.RemoteAddr))
		logger.Print(protocolColor("checking protocol versions on: %s", endpoint))

		ver, err := checkVersion()
		if err != nil {
			logger.Println(protocolError("could not check version: %v", err))
			http.Error(res, "could not check version", 500)
			return
		}

		logger.Print(protocolColor("protocol version: %s", ver["Protocol-Version"]))
		logger.Print(protocolColor("versions: Chrome(%s), V8(%s), Webkit(%s)", ver["Browser"], ver["V8-Version"], ver["WebKit-Version"]))
		logger.Print(protocolColor("browser user agent: %s", ver["User-Agent"]))

		// connect outgoing websocket
		logger.Print(protocolColor("connecting to %s... ", endpoint))
		out, pres, err := wsDialer.Dial(endpoint, nil)
		if err != nil {
			msg := fmt.Sprintf("could not connect to %s: %v", endpoint, err)
			logger.Println(protocolError(msg))
			http.Error(res, msg, 500)
			return
		}
		defer pres.Body.Close()
		defer out.Close()

		// connect incoming websocket
		logger.Print(protocolColor("upgrading connection on %s...", req.RemoteAddr))
		in, err := wsUpgrader.Upgrade(res, req, nil)
		if err != nil {
			logger.Println(protocolError("could not upgrade websocket from %s: %v", req.RemoteAddr, err))
			http.Error(res, "could not upgrade websocket connection", 500)
			return
		}
		defer in.Close()

		ctxt, cancel := context.WithCancel(context.Background())
		defer cancel()

		errc := make(chan error, 1)
		go proxyWS(ctxt, stream, in, out, errc)
		go proxyWS(ctxt, stream, out, in, errc)
		<-errc
		logger.Printf(protocolColor("---------- closing %s ----------", req.RemoteAddr))

		if *flagOnce {
			os.Exit(0)
		}
	})

	log.Fatal(http.ListenAndServe(*flagListen, mux))
}

func dumpStream(logger *log.Logger, stream chan *protocolMessage) {
	logger.Printf("Legend: %s, %s, %s, %s",
		protocolColor("protocol informations"),
		eventsColor("received events"),
		requestColor("sent requests"),
		responseColor("received responses."),
	)

	requests := make(map[uint64]*protocolMessage)
	targetRequests := make(map[uint64]*protocolMessage)

	for {
		select {
		case msg := <-stream:
			if msg.InTarget() {
				if msg.IsRequest() {
					requests[msg.Id] = nil

					if protocolMessage, err := decodeMessage([]byte(asString(msg.Params["message"]))); err == nil {
						targetRequests[protocolMessage.Id] = protocolMessage
					} else {
						logger.Printf(protocolColor("Could not deserialize message: %+v", err))
					}
				}

				if msg.IsEvent() {
					if protocolMessage, err := decodeMessage([]byte(asString(msg.Params["message"]))); err == nil {
						if protocolMessage.IsEvent() {
							logger.Printf("%s %36s(%s)", targetColor("%s", msg.Params["targetId"]), methodColor(protocolMessage.Method), eventsColor(serialize(protocolMessage.Params)))
						}

						if protocolMessage.IsResponse() {
							if request, ok := targetRequests[protocolMessage.Id]; ok {
								delete(targetRequests, protocolMessage.Id)

								if protocolMessage.IsError() {
									logger.Printf("%s %36s(%s) = %s", targetColor("%s", msg.Params["targetId"]), methodColor(request.Method), requestColor(serialize(request.Params)), errorColor(serialize(protocolMessage.Error)))
								} else {
									logger.Printf("%s %36s(%s) = %s", targetColor("%s", msg.Params["targetId"]), methodColor(request.Method), requestColor(serialize(request.Params)), responseColor(serialize(protocolMessage.Result)))
								}
							} else {
								logger.Printf(protocolColor("Could not find target request with id: %d", protocolMessage.Id))
							}
						}
					} else {
						logger.Printf(protocolColor("Could not deserialize message: %+v", err))
					}
				}

			} else {
				if msg.IsRequest() {
					requests[msg.Id] = msg
				}

				if msg.IsResponse() {
					if request, ok := requests[msg.Id]; ok {
						delete(requests, msg.Id)

						if request != nil {
							if msg.IsError() {
								logger.Printf("%s %36s(%s) = %s", targetColor(protocolTargetId), methodColor(request.Method), requestColor(serialize(request.Params)), errorColor(serialize(msg.Error)))
							} else {
								logger.Printf("%s %36s(%s) = %s", targetColor(protocolTargetId), methodColor(request.Method), requestColor(serialize(request.Params)), responseColor(serialize(msg.Result)))
							}
						}
					} else {
					logger.Printf(protocolColor("Could not find request with id: %d", msg.Id))
					}
				}

				if msg.IsEvent() {
					logger.Printf("%s %36s(%s)", targetColor(protocolTargetId), methodColor(msg.Method), eventsColor(serialize(msg.Params)))
				}
			}
		}
	}
}

func proxyWS(ctxt context.Context, stream chan *protocolMessage, in, out *websocket.Conn, errc chan error) {
	var mt int
	var buf []byte
	var err error

	for {
		select {
		default:
			mt, buf, err = in.ReadMessage()
			if err != nil {
				errc <- err
				return
			}

			if msg, err := decodeMessage(buf); err == nil {
				stream <- msg
			}

			err = out.WriteMessage(mt, buf)

			if err != nil {
				errc <- err
				return
			}

		case <-ctxt.Done():
			return
		}
	}
}

func checkVersion() (map[string]string, error) {
	cl := &http.Client{}
	req, err := http.NewRequest("GET", "http://" + *flagRemote + "/json/version", nil)
	if err != nil {
		return nil, err
	}

	res, err := cl.Do(req)
	if err != nil {
		return nil, err
	}

	defer res.Body.Close()

	var v map[string]string
	if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
		return nil, errors.New("expected json result")
	}

	return v, nil
}

func createLog(id string) (io.Closer, *log.Logger) {
	var f io.Closer
	var w io.Writer = os.Stdout
	if !*flagNoLog && *flagLogMask != "" {
		l, err := os.OpenFile(fmt.Sprintf(*flagLogMask, id), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err != nil {
			log.Fatal(err)
		}

		f = l
		w = io.MultiWriter(os.Stdout, l)
	}
	return f, log.New(w, "", log.Lmicroseconds)
}

<|endoftext|>