<commit_before>package web

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/gorilla/mux"
	"github.com/zenoss/glog"
	"github.com/zenoss/go-json-rest"
	"github.com/zenoss/serviced"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/domain/service"
	"github.com/zenoss/serviced/domain/servicestate"
	"github.com/zenoss/serviced/proxy"
	"github.com/zenoss/serviced/rpc/master"

	"mime"
	"net/http"
	"net/http/httputil"
	"net/url"
)

type ServiceConfig struct {
	bindPort    string
	agentPort   string
	zookeepers  []string
	stats       bool
	hostaliases []string
}

func NewServiceConfig(bindPort string, agentPort string, zookeepers []string, stats bool, hostaliases []string) *ServiceConfig {
	cfg := ServiceConfig{bindPort, agentPort, zookeepers, stats, []string{}}
	if len(cfg.agentPort) == 0 {
		cfg.agentPort = "127.0.0.1:4979"
	}
	if len(cfg.zookeepers) == 0 {
		cfg.zookeepers = []string{"127.0.0.1:2181"}
	}
	return &cfg
}

// Serve handles control plane web UI requests and virtual host requests for zenoss web based services.
// The UI server actually listens on port 7878, the uihandler defined here just reverse proxies to it.
// Virutal host routing to zenoss web based services is done by the vhosthandler function.
func (sc *ServiceConfig) Serve() {
	client, err := sc.getClient()
	if err != nil {
		glog.Errorf("Unable to get control plane client: %v", err)
		return
	}

	// Reverse proxy to the web UI server.
	uihandler := func(w http.ResponseWriter, r *http.Request) {
		uiUrl, err := url.Parse("http://127.0.0.1:7878")
		if err != nil {
			glog.Errorf("Can't parse UI URL: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		ui := httputil.NewSingleHostReverseProxy(uiUrl)
		if ui == nil {
			glog.Errorf("Can't proxy UI request: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		ui.ServeHTTP(w, r)
	}

	// Lookup the appropriate virtual host and forward the request to it.
	// TODO: when zookeeper registration is integrated we can be more event
	// driven and only refresh the vhost map when service states change.
	vhosthandler := func(w http.ResponseWriter, r *http.Request) {
		glog.V(0).Infof("vhosthandler handling: %v", r)

		var empty interface{}
		services := []*dao.RunningService{}
		client.GetRunningServices(&empty, &services)

		vhosts := make(map[string][]*servicestate.ServiceState, 0)

		for _, s := range services {
			var svc service.Service

			if err := client.GetService(s.ServiceId, &svc); err != nil {
				glog.Errorf("Can't get service: %s (%v)", s.Id, err)
			}

			vheps := svc.GetServiceVHosts()

			for _, vhep := range vheps {
				for _, vh := range vhep.VHosts {
					svcstates := []*servicestate.ServiceState{}
					if err := client.GetServiceStates(s.ServiceId, &svcstates); err != nil {
						http.Error(w, fmt.Sprintf("can't retrieve service states for %s (%v)", s.ServiceId, err), http.StatusInternalServerError)
						return
					}

					for _, ss := range svcstates {
						vhosts[vh] = append(vhosts[vh], ss)
					}
				}
			}

		}

		glog.V(0).Infof("vhosthandler VHost map: %v", vhosts)

		muxvars := mux.Vars(r)
		svcstates, ok := vhosts[muxvars["subdomain"]]
		if !ok {
			http.Error(w, fmt.Sprintf("service associated with vhost %v is not running", muxvars["subdomain"]), http.StatusNotFound)
			return
		}

		// TODO: implement a more intelligent strategy than "always pick the first one" when more
		// than one service state is mapped to a given virtual host
		for _, svcep := range svcstates[0].Endpoints {
			for _, vh := range svcep.VHosts {
				if vh == muxvars["subdomain"] {
					rpurl := url.URL{Scheme: "http", Host: fmt.Sprintf("%s:%d", svcstates[0].PrivateIp, svcep.PortNumber)}

					glog.V(1).Infof("vhosthandler reverse proxy to: %v", rpurl)

					rp := httputil.NewSingleHostReverseProxy(&rpurl)
					rp.ServeHTTP(w, r)
					return
				}
			}
		}

		http.Error(w, fmt.Sprintf("unrecognized endpoint: %s", muxvars["subdomain"]), http.StatusNotImplemented)
	}

	r := mux.NewRouter()

	if hnm, err := os.Hostname(); err == nil {
		sc.hostaliases = append(sc.hostaliases, hnm)
	}

	cmd := exec.Command("hostname", "--fqdn")
	if hnm, err := cmd.CombinedOutput(); err == nil {
		sc.hostaliases = append(sc.hostaliases, string(hnm[:len(hnm)-1]))
	}

	for _, ha := range sc.hostaliases {
		glog.V(0).Infof("Use vhosthandler for: %s", fmt.Sprintf("{subdomain}.%s", ha))
		r.HandleFunc("/{path:.*}", vhosthandler).Host(fmt.Sprintf("{subdomain}.%s", ha))
		r.HandleFunc("/", vhosthandler).Host(fmt.Sprintf("{subdomain}.%s", ha))
	}

	r.HandleFunc("/{path:.*}", uihandler)

	http.Handle("/", r)

	// FIXME: bubble up these errors to the caller
	certfile, err := proxy.TempCertFile()
	if err != nil {
		glog.Fatalf("Could not prepare cert.pem file: %s", err)
	}
	keyfile, err := proxy.TempKeyFile()
	if err != nil {
		glog.Fatalf("Could not prepare key.pem file: %s", err)
	}
	err = http.ListenAndServeTLS(sc.bindPort, certfile, keyfile, nil)
	if err != nil {
		glog.Fatalf("could not setup webserver: %s", err)
	}
}

func (this *ServiceConfig) ServeUI() {
	mime.AddExtensionType(".json", "application/json")
	mime.AddExtensionType(".woff", "application/font-woff")

	handler := rest.ResourceHandler{
		EnableRelaxedContentType: true,
	}

	routes := this.getRoutes()
	handler.SetRoutes(routes...)

	// FIXME: bubble up these errors to the caller
	if err := http.ListenAndServe(":7878", &handler); err != nil {
		glog.Fatalf("could not setup internal web server: %s", err)
	}
}

var methods []string = []string{"GET", "POST", "PUT", "DELETE"}

func routeToInternalServiceProxy(path string, target string, routes []rest.Route) []rest.Route {
	targetUrl, err := url.Parse(target)
	if err != nil {
		glog.Errorf("Unable to parse proxy target URL: %s", target)
		return routes
	}
	// Wrap the normal http.Handler in a rest.HandlerFunc
	handlerFunc := func(w *rest.ResponseWriter, r *rest.Request) {
		proxy := serviced.NewReverseProxy(path, targetUrl)
		proxy.ServeHTTP(w.ResponseWriter, r.Request)
	}
	// Add on a glob to match subpaths
	andsubpath := path + "*x"
	for _, method := range methods {
		routes = append(routes, rest.Route{method, path, handlerFunc})
		routes = append(routes, rest.Route{method, andsubpath, handlerFunc})
	}
	return routes
}

func (this *ServiceConfig) UnAuthorizedClient(realfunc HandlerClientFunc) HandlerFunc {
	return func(w *rest.ResponseWriter, r *rest.Request) {
		client, err := this.getClient()
		if err != nil {
			glog.Errorf("Unable to acquire client: %v", err)
			RestServerError(w)
			return
		}
		defer client.Close()
		realfunc(w, r, client)
	}
}

func (this *ServiceConfig) AuthorizedClient(realfunc HandlerClientFunc) HandlerFunc {
	return func(w *rest.ResponseWriter, r *rest.Request) {
		if !LoginOk(r) {
			RestUnauthorized(w)
			return
		}
		client, err := this.getClient()
		if err != nil {
			glog.Errorf("Unable to acquire client: %v", err)
			RestServerError(w)
			return
		}
		defer client.Close()
		realfunc(w, r, client)
	}
}

func (this *ServiceConfig) IsCollectingStats() HandlerFunc {
	if this.stats {
		return func(w *rest.ResponseWriter, r *rest.Request) {
			w.WriteHeader(http.StatusOK)
		}
	} else {
		return func(w *rest.ResponseWriter, r *rest.Request) {
			w.WriteHeader(http.StatusNotImplemented)
		}
	}
}

func (this *ServiceConfig) getClient() (c *serviced.ControlClient, err error) {
	// setup the client
	c, err = serviced.NewControlClient(this.agentPort)
	if err != nil {
		glog.Fatalf("Could not create a control plane client: %v", err)
	}
	return c, err
}

func (sc *ServiceConfig) newRequestHandler(check CheckFunc, realfunc CtxHandlerFunc) HandlerFunc {
	return func(w *rest.ResponseWriter, r *rest.Request) {
		if !check(w, r) {
			return
		}
		reqCtx := newRequestContext(sc)
		defer reqCtx.end()
		realfunc(w, r, reqCtx)
	}
}

func (sc *ServiceConfig) CheckAuth(realfunc CtxHandlerFunc) HandlerFunc {
	check := func(w *rest.ResponseWriter, r *rest.Request) bool {
		if !LoginOk(r) {
			RestUnauthorized(w)
			return false
		}
		return true
	}
	return sc.newRequestHandler(check, realfunc)
}

func (sc *ServiceConfig) NoAuth(realfunc CtxHandlerFunc) HandlerFunc {
	check := func(w *rest.ResponseWriter, r *rest.Request) bool {
		return true
	}
	return sc.newRequestHandler(check, realfunc)
}

type Close interface {
	Close() error
}

type requestContext struct {
	sc     *ServiceConfig
	master *master.Client
}

func newRequestContext(sc *ServiceConfig) *requestContext {
	return &requestContext{sc: sc}
}

func (ctx *requestContext) getMasterClient() (*master.Client, error) {
	if ctx.master == nil {
		if c, err := master.NewClient(ctx.sc.agentPort); err != nil {
			glog.Errorf("Could not create a control plane client to %v: %v", ctx.sc.agentPort, err)
			return nil, err
		} else {
			ctx.master = c
		}
	}
	return ctx.master, nil
}

func (ctx *requestContext) end() error {
	if ctx.master != nil {
		return ctx.master.Close()
	}
	return nil
}

type CtxHandlerFunc func(w *rest.ResponseWriter, r *rest.Request, ctx *requestContext)
type CheckFunc func(w *rest.ResponseWriter, r *rest.Request) bool

type getRoutes func(sc *ServiceConfig) []rest.Route
<commit_msg>revert loglevels for vhosthandler<commit_after>package web

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/gorilla/mux"
	"github.com/zenoss/glog"
	"github.com/zenoss/go-json-rest"
	"github.com/zenoss/serviced"
	"github.com/zenoss/serviced/dao"
	"github.com/zenoss/serviced/domain/service"
	"github.com/zenoss/serviced/domain/servicestate"
	"github.com/zenoss/serviced/proxy"
	"github.com/zenoss/serviced/rpc/master"

	"mime"
	"net/http"
	"net/http/httputil"
	"net/url"
)

type ServiceConfig struct {
	bindPort    string
	agentPort   string
	zookeepers  []string
	stats       bool
	hostaliases []string
}

func NewServiceConfig(bindPort string, agentPort string, zookeepers []string, stats bool, hostaliases []string) *ServiceConfig {
	cfg := ServiceConfig{bindPort, agentPort, zookeepers, stats, []string{}}
	if len(cfg.agentPort) == 0 {
		cfg.agentPort = "127.0.0.1:4979"
	}
	if len(cfg.zookeepers) == 0 {
		cfg.zookeepers = []string{"127.0.0.1:2181"}
	}
	return &cfg
}

// Serve handles control plane web UI requests and virtual host requests for zenoss web based services.
// The UI server actually listens on port 7878, the uihandler defined here just reverse proxies to it.
// Virutal host routing to zenoss web based services is done by the vhosthandler function.
func (sc *ServiceConfig) Serve() {
	client, err := sc.getClient()
	if err != nil {
		glog.Errorf("Unable to get control plane client: %v", err)
		return
	}

	// Reverse proxy to the web UI server.
	uihandler := func(w http.ResponseWriter, r *http.Request) {
		uiUrl, err := url.Parse("http://127.0.0.1:7878")
		if err != nil {
			glog.Errorf("Can't parse UI URL: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		ui := httputil.NewSingleHostReverseProxy(uiUrl)
		if ui == nil {
			glog.Errorf("Can't proxy UI request: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		ui.ServeHTTP(w, r)
	}

	// Lookup the appropriate virtual host and forward the request to it.
	// TODO: when zookeeper registration is integrated we can be more event
	// driven and only refresh the vhost map when service states change.
	vhosthandler := func(w http.ResponseWriter, r *http.Request) {
		glog.V(1).Infof("vhosthandler handling: %v", r)

		var empty interface{}
		services := []*dao.RunningService{}
		client.GetRunningServices(&empty, &services)

		vhosts := make(map[string][]*servicestate.ServiceState, 0)

		for _, s := range services {
			var svc service.Service

			if err := client.GetService(s.ServiceId, &svc); err != nil {
				glog.Errorf("Can't get service: %s (%v)", s.Id, err)
			}

			vheps := svc.GetServiceVHosts()

			for _, vhep := range vheps {
				for _, vh := range vhep.VHosts {
					svcstates := []*servicestate.ServiceState{}
					if err := client.GetServiceStates(s.ServiceId, &svcstates); err != nil {
						http.Error(w, fmt.Sprintf("can't retrieve service states for %s (%v)", s.ServiceId, err), http.StatusInternalServerError)
						return
					}

					for _, ss := range svcstates {
						vhosts[vh] = append(vhosts[vh], ss)
					}
				}
			}

		}

		glog.V(1).Infof("vhosthandler VHost map: %v", vhosts)

		muxvars := mux.Vars(r)
		svcstates, ok := vhosts[muxvars["subdomain"]]
		if !ok {
			http.Error(w, fmt.Sprintf("service associated with vhost %v is not running", muxvars["subdomain"]), http.StatusNotFound)
			return
		}

		// TODO: implement a more intelligent strategy than "always pick the first one" when more
		// than one service state is mapped to a given virtual host
		for _, svcep := range svcstates[0].Endpoints {
			for _, vh := range svcep.VHosts {
				if vh == muxvars["subdomain"] {
					rpurl := url.URL{Scheme: "http", Host: fmt.Sprintf("%s:%d", svcstates[0].PrivateIp, svcep.PortNumber)}

					glog.V(1).Infof("vhosthandler reverse proxy to: %v", rpurl)

					rp := httputil.NewSingleHostReverseProxy(&rpurl)
					rp.ServeHTTP(w, r)
					return
				}
			}
		}

		http.Error(w, fmt.Sprintf("unrecognized endpoint: %s", muxvars["subdomain"]), http.StatusNotImplemented)
	}

	r := mux.NewRouter()

	if hnm, err := os.Hostname(); err == nil {
		sc.hostaliases = append(sc.hostaliases, hnm)
	}

	cmd := exec.Command("hostname", "--fqdn")
	if hnm, err := cmd.CombinedOutput(); err == nil {
		sc.hostaliases = append(sc.hostaliases, string(hnm[:len(hnm)-1]))
	}

	for _, ha := range sc.hostaliases {
		glog.V(1).Infof("Use vhosthandler for: %s", fmt.Sprintf("{subdomain}.%s", ha))
		r.HandleFunc("/{path:.*}", vhosthandler).Host(fmt.Sprintf("{subdomain}.%s", ha))
		r.HandleFunc("/", vhosthandler).Host(fmt.Sprintf("{subdomain}.%s", ha))
	}

	r.HandleFunc("/{path:.*}", uihandler)

	http.Handle("/", r)

	// FIXME: bubble up these errors to the caller
	certfile, err := proxy.TempCertFile()
	if err != nil {
		glog.Fatalf("Could not prepare cert.pem file: %s", err)
	}
	keyfile, err := proxy.TempKeyFile()
	if err != nil {
		glog.Fatalf("Could not prepare key.pem file: %s", err)
	}
	err = http.ListenAndServeTLS(sc.bindPort, certfile, keyfile, nil)
	if err != nil {
		glog.Fatalf("could not setup webserver: %s", err)
	}
}

func (this *ServiceConfig) ServeUI() {
	mime.AddExtensionType(".json", "application/json")
	mime.AddExtensionType(".woff", "application/font-woff")

	handler := rest.ResourceHandler{
		EnableRelaxedContentType: true,
	}

	routes := this.getRoutes()
	handler.SetRoutes(routes...)

	// FIXME: bubble up these errors to the caller
	if err := http.ListenAndServe(":7878", &handler); err != nil {
		glog.Fatalf("could not setup internal web server: %s", err)
	}
}

var methods []string = []string{"GET", "POST", "PUT", "DELETE"}

func routeToInternalServiceProxy(path string, target string, routes []rest.Route) []rest.Route {
	targetUrl, err := url.Parse(target)
	if err != nil {
		glog.Errorf("Unable to parse proxy target URL: %s", target)
		return routes
	}
	// Wrap the normal http.Handler in a rest.HandlerFunc
	handlerFunc := func(w *rest.ResponseWriter, r *rest.Request) {
		proxy := serviced.NewReverseProxy(path, targetUrl)
		proxy.ServeHTTP(w.ResponseWriter, r.Request)
	}
	// Add on a glob to match subpaths
	andsubpath := path + "*x"
	for _, method := range methods {
		routes = append(routes, rest.Route{method, path, handlerFunc})
		routes = append(routes, rest.Route{method, andsubpath, handlerFunc})
	}
	return routes
}

func (this *ServiceConfig) UnAuthorizedClient(realfunc HandlerClientFunc) HandlerFunc {
	return func(w *rest.ResponseWriter, r *rest.Request) {
		client, err := this.getClient()
		if err != nil {
			glog.Errorf("Unable to acquire client: %v", err)
			RestServerError(w)
			return
		}
		defer client.Close()
		realfunc(w, r, client)
	}
}

func (this *ServiceConfig) AuthorizedClient(realfunc HandlerClientFunc) HandlerFunc {
	return func(w *rest.ResponseWriter, r *rest.Request) {
		if !LoginOk(r) {
			RestUnauthorized(w)
			return
		}
		client, err := this.getClient()
		if err != nil {
			glog.Errorf("Unable to acquire client: %v", err)
			RestServerError(w)
			return
		}
		defer client.Close()
		realfunc(w, r, client)
	}
}

func (this *ServiceConfig) IsCollectingStats() HandlerFunc {
	if this.stats {
		return func(w *rest.ResponseWriter, r *rest.Request) {
			w.WriteHeader(http.StatusOK)
		}
	} else {
		return func(w *rest.ResponseWriter, r *rest.Request) {
			w.WriteHeader(http.StatusNotImplemented)
		}
	}
}

func (this *ServiceConfig) getClient() (c *serviced.ControlClient, err error) {
	// setup the client
	c, err = serviced.NewControlClient(this.agentPort)
	if err != nil {
		glog.Fatalf("Could not create a control plane client: %v", err)
	}
	return c, err
}

func (sc *ServiceConfig) newRequestHandler(check CheckFunc, realfunc CtxHandlerFunc) HandlerFunc {
	return func(w *rest.ResponseWriter, r *rest.Request) {
		if !check(w, r) {
			return
		}
		reqCtx := newRequestContext(sc)
		defer reqCtx.end()
		realfunc(w, r, reqCtx)
	}
}

func (sc *ServiceConfig) CheckAuth(realfunc CtxHandlerFunc) HandlerFunc {
	check := func(w *rest.ResponseWriter, r *rest.Request) bool {
		if !LoginOk(r) {
			RestUnauthorized(w)
			return false
		}
		return true
	}
	return sc.newRequestHandler(check, realfunc)
}

func (sc *ServiceConfig) NoAuth(realfunc CtxHandlerFunc) HandlerFunc {
	check := func(w *rest.ResponseWriter, r *rest.Request) bool {
		return true
	}
	return sc.newRequestHandler(check, realfunc)
}

type Close interface {
	Close() error
}

type requestContext struct {
	sc     *ServiceConfig
	master *master.Client
}

func newRequestContext(sc *ServiceConfig) *requestContext {
	return &requestContext{sc: sc}
}

func (ctx *requestContext) getMasterClient() (*master.Client, error) {
	if ctx.master == nil {
		if c, err := master.NewClient(ctx.sc.agentPort); err != nil {
			glog.Errorf("Could not create a control plane client to %v: %v", ctx.sc.agentPort, err)
			return nil, err
		} else {
			ctx.master = c
		}
	}
	return ctx.master, nil
}

func (ctx *requestContext) end() error {
	if ctx.master != nil {
		return ctx.master.Close()
	}
	return nil
}

type CtxHandlerFunc func(w *rest.ResponseWriter, r *rest.Request, ctx *requestContext)
type CheckFunc func(w *rest.ResponseWriter, r *rest.Request) bool

type getRoutes func(sc *ServiceConfig) []rest.Route
<|endoftext|>


<commit_before>package common

import (
	"encoding/binary"
	"fmt"
	"io"
	"xd/lib/log"
	"xd/lib/util"
)

// WireMessageType is type for wire message id
type WireMessageType byte

// Chock is message id for choke message
const Choke = WireMessageType(0)

// UnChoke is message id for unchoke message
const UnChoke = WireMessageType(1)

// Interested is messageid for interested message
const Interested = WireMessageType(2)

// NotInterested is messageid for not-interested message
const NotInterested = WireMessageType(3)

// Have is messageid for have message
const Have = WireMessageType(4)

// BitField is messageid for bitfield message
const BitField = WireMessageType(5)

// Request is messageid for piece request message
const Request = WireMessageType(6)

// Piece is messageid for response to Request message
const Piece = WireMessageType(7)

// Cancel is messageid for a Cancel message, used to cancel a pending request
const Cancel = WireMessageType(8)

// Extended is messageid for ExtendedOptions message
const Extended = WireMessageType(20)

func (t WireMessageType) String() string {
	switch t {
	case Choke:
		return "Choke"
	case UnChoke:
		return "UnChoke"
	case Interested:
		return "Interested"
	case NotInterested:
		return "NotInterested"
	case Have:
		return "Have"
	case BitField:
		return "BitField"
	case Request:
		return "Request"
	case Piece:
		return "Piece"
	case Cancel:
		return "Cancel"
	case Extended:
		return "Extended"
	default:
		return fmt.Sprintf("??? (%d)", uint8(t))
	}
}

// WireMessage is a serializable bittorrent wire message
type WireMessage struct {
	data []byte
}

// KeepAlive makes a WireMessage of size 0
func KeepAlive() *WireMessage {
	return &WireMessage{
		data: []byte{0, 0, 0, 0},
	}
}

// NewWireMessage creates new wire message with id and body
func NewWireMessage(id WireMessageType, body []byte) (msg *WireMessage) {
	if body == nil {
		body = []byte{}
	}
	var hdr [5]byte
	l := uint32(len(body)) + 5
	binary.BigEndian.PutUint32(hdr[:], l-4)
	hdr[4] = byte(id)
	msg = new(WireMessage)
	msg.data = append(hdr[:], body...)
	return msg
}

// KeepAlive returns true if this message is a keepalive message
func (msg *WireMessage) KeepAlive() bool {
	return msg.Len() == 0
}

// Len returns the length of the body of this message
func (msg *WireMessage) Len() uint32 {
	if msg.data == nil {
		return 0
	}
	return binary.BigEndian.Uint32(msg.data)
}

// Payload returns a byteslice for the body of this message
func (msg *WireMessage) Payload() []byte {
	if msg.Len() > 0 {
		return msg.data[5:]
	} else {
		return nil
	}
}

// MessageID returns the id of this message
func (msg *WireMessage) MessageID() WireMessageType {
	return WireMessageType(msg.data[4])
}

// Recv reads message from reader
func (msg *WireMessage) Recv(r io.Reader) (err error) {
	// read header
	var hdr [4]byte
	var n int
	n, err = io.ReadFull(r, hdr[:])
	msg.data = hdr[:]
	if err == nil {
		l := binary.BigEndian.Uint32(msg.data[:])
		if l > 0 {
			// read body
			log.Debugf("read message of size %d bytes", l)
			// XXX: yes this is a magic number
			var buf [1730]byte
			for l > 0 && err == nil {
				var readbuf []byte
				if l <= uint32(len(buf)) {
					readbuf = buf[:l]
				} else {
					readbuf = buf[:]
				}
				n, err = r.Read(readbuf)
				if n > 0 {
					l -= uint32(n)
					readbuf = readbuf[:n]
					msg.data = append(msg.data, readbuf...)
				} else {
					log.Warnf("read bittorrent message failed: %s", err)
				}
			}
		} else {
			// zero size
		}
	}
	return
}

// Send writes WireMessage via writer
func (msg *WireMessage) Send(w io.Writer) (err error) {
	err = util.WriteFull(w, msg.data)
	return
}

// ToWireMessage serialize to BitTorrent wire message
func (p *PieceData) ToWireMessage() *WireMessage {
	var hdr [8]byte
	var body []byte
	binary.BigEndian.PutUint32(hdr[:], p.Index)
	binary.BigEndian.PutUint32(hdr[4:], p.Begin)
	body = append(hdr[:], p.Data...)
	return NewWireMessage(Piece, body)
}

// ToWireMessage serialize to BitTorrent wire message
func (req *PieceRequest) ToWireMessage() *WireMessage {
	var body [12]byte
	binary.BigEndian.PutUint32(body[:], req.Index)
	binary.BigEndian.PutUint32(body[4:], req.Begin)
	binary.BigEndian.PutUint32(body[8:], req.Length)
	return NewWireMessage(Request, body[:])
}

// GetPieceData gets this wire message as a PieceData if applicable
func (msg *WireMessage) GetPieceData() (p *PieceData) {

	if msg.MessageID() == Piece {
		data := msg.Payload()
		if len(data) > 8 {
			p = new(PieceData)
			p.Index = binary.BigEndian.Uint32(data)
			p.Begin = binary.BigEndian.Uint32(data[4:])
			p.Data = data[8:]
		}
	}
	return
}

// GetPieceRequest gets piece request from wire message
func (msg WireMessage) GetPieceRequest() (req *PieceRequest) {
	if msg.MessageID() == Request {
		data := msg.Payload()
		if len(data) == 12 {
			req = new(PieceRequest)
			req.Index = binary.BigEndian.Uint32(data[:])
			req.Begin = binary.BigEndian.Uint32(data[4:])
			req.Length = binary.BigEndian.Uint32(data[8:])
		}
	}
	return
}

// GetHave gets the piece index of a have message
func (msg *WireMessage) GetHave() (h uint32) {
	if msg.MessageID() == Have {
		data := msg.Payload()
		if len(data) == 4 {
			h = binary.BigEndian.Uint32(data[:])
		}
	}
	return
}

// NewHave creates a new have message
func NewHave(idx uint32) *WireMessage {
	var body [4]byte
	binary.BigEndian.PutUint32(body[:], idx)
	return NewWireMessage(Have, body[:])
}

// NewNotInterested creates a new NotInterested message
func NewNotInterested() *WireMessage {
	return NewWireMessage(NotInterested, nil)
}

// NewInterested creates a new Interested message
func NewInterested() *WireMessage {
	return NewWireMessage(Interested, nil)
}
<commit_msg>use make([]byte) for wire messages<commit_after>package common

import (
	"encoding/binary"
	"fmt"
	"io"
	"xd/lib/log"
	"xd/lib/util"
)

// WireMessageType is type for wire message id
type WireMessageType byte

// Chock is message id for choke message
const Choke = WireMessageType(0)

// UnChoke is message id for unchoke message
const UnChoke = WireMessageType(1)

// Interested is messageid for interested message
const Interested = WireMessageType(2)

// NotInterested is messageid for not-interested message
const NotInterested = WireMessageType(3)

// Have is messageid for have message
const Have = WireMessageType(4)

// BitField is messageid for bitfield message
const BitField = WireMessageType(5)

// Request is messageid for piece request message
const Request = WireMessageType(6)

// Piece is messageid for response to Request message
const Piece = WireMessageType(7)

// Cancel is messageid for a Cancel message, used to cancel a pending request
const Cancel = WireMessageType(8)

// Extended is messageid for ExtendedOptions message
const Extended = WireMessageType(20)

func (t WireMessageType) String() string {
	switch t {
	case Choke:
		return "Choke"
	case UnChoke:
		return "UnChoke"
	case Interested:
		return "Interested"
	case NotInterested:
		return "NotInterested"
	case Have:
		return "Have"
	case BitField:
		return "BitField"
	case Request:
		return "Request"
	case Piece:
		return "Piece"
	case Cancel:
		return "Cancel"
	case Extended:
		return "Extended"
	default:
		return fmt.Sprintf("??? (%d)", uint8(t))
	}
}

// WireMessage is a serializable bittorrent wire message
type WireMessage struct {
	data []byte
}

// KeepAlive makes a WireMessage of size 0
func KeepAlive() *WireMessage {
	return &WireMessage{
		data: []byte{0, 0, 0, 0},
	}
}

// NewWireMessage creates new wire message with id and body
func NewWireMessage(id WireMessageType, body []byte) (msg *WireMessage) {
	if body == nil {
		body = []byte{}
	}
	var hdr [5]byte
	l := uint32(len(body)) + 5
	binary.BigEndian.PutUint32(hdr[:], l-4)
	hdr[4] = byte(id)
	msg = new(WireMessage)
	msg.data = append(hdr[:], body...)
	return msg
}

// KeepAlive returns true if this message is a keepalive message
func (msg *WireMessage) KeepAlive() bool {
	return msg.Len() == 0
}

// Len returns the length of the body of this message
func (msg *WireMessage) Len() uint32 {
	if msg.data == nil {
		return 0
	}
	return binary.BigEndian.Uint32(msg.data)
}

// Payload returns a byteslice for the body of this message
func (msg *WireMessage) Payload() []byte {
	if msg.Len() > 0 {
		return msg.data[5:]
	} else {
		return nil
	}
}

// MessageID returns the id of this message
func (msg *WireMessage) MessageID() WireMessageType {
	return WireMessageType(msg.data[4])
}

// Recv reads message from reader
func (msg *WireMessage) Recv(r io.Reader) (err error) {
	// read header
	var hdr [4]byte
	_, err = io.ReadFull(r, hdr[:])
	msg.data = hdr[:]
	if err == nil {
		l := binary.BigEndian.Uint32(msg.data[:])
		if l > 0 {
			// read body
			log.Debugf("read message of size %d bytes", l)
			msg.data = make([]byte, l)
			_, err = io.ReadFull(r, msg.data)
			/*
				// XXX: yes this is a magic number
				var buf [1730]byte
				for l > 0 && err == nil {
					var readbuf []byte
					if l <= uint32(len(buf)) {
						readbuf = buf[:l]
					} else {
						readbuf = buf[:]
					}
					n, err = r.Read(readbuf)
					if n > 0 {
						l -= uint32(n)
						readbuf = readbuf[:n]
						msg.data = append(msg.data, readbuf...)
					} else {
						log.Warnf("read bittorrent message failed: %s", err)
					}
				}
			*/
		} else {
			// zero size
		}
	}
	return
}

// Send writes WireMessage via writer
func (msg *WireMessage) Send(w io.Writer) (err error) {
	err = util.WriteFull(w, msg.data)
	return
}

// ToWireMessage serialize to BitTorrent wire message
func (p *PieceData) ToWireMessage() *WireMessage {
	var hdr [8]byte
	var body []byte
	binary.BigEndian.PutUint32(hdr[:], p.Index)
	binary.BigEndian.PutUint32(hdr[4:], p.Begin)
	body = append(hdr[:], p.Data...)
	return NewWireMessage(Piece, body)
}

// ToWireMessage serialize to BitTorrent wire message
func (req *PieceRequest) ToWireMessage() *WireMessage {
	var body [12]byte
	binary.BigEndian.PutUint32(body[:], req.Index)
	binary.BigEndian.PutUint32(body[4:], req.Begin)
	binary.BigEndian.PutUint32(body[8:], req.Length)
	return NewWireMessage(Request, body[:])
}

// GetPieceData gets this wire message as a PieceData if applicable
func (msg *WireMessage) GetPieceData() (p *PieceData) {

	if msg.MessageID() == Piece {
		data := msg.Payload()
		if len(data) > 8 {
			p = new(PieceData)
			p.Index = binary.BigEndian.Uint32(data)
			p.Begin = binary.BigEndian.Uint32(data[4:])
			p.Data = data[8:]
		}
	}
	return
}

// GetPieceRequest gets piece request from wire message
func (msg WireMessage) GetPieceRequest() (req *PieceRequest) {
	if msg.MessageID() == Request {
		data := msg.Payload()
		if len(data) == 12 {
			req = new(PieceRequest)
			req.Index = binary.BigEndian.Uint32(data[:])
			req.Begin = binary.BigEndian.Uint32(data[4:])
			req.Length = binary.BigEndian.Uint32(data[8:])
		}
	}
	return
}

// GetHave gets the piece index of a have message
func (msg *WireMessage) GetHave() (h uint32) {
	if msg.MessageID() == Have {
		data := msg.Payload()
		if len(data) == 4 {
			h = binary.BigEndian.Uint32(data[:])
		}
	}
	return
}

// NewHave creates a new have message
func NewHave(idx uint32) *WireMessage {
	var body [4]byte
	binary.BigEndian.PutUint32(body[:], idx)
	return NewWireMessage(Have, body[:])
}

// NewNotInterested creates a new NotInterested message
func NewNotInterested() *WireMessage {
	return NewWireMessage(NotInterested, nil)
}

// NewInterested creates a new Interested message
func NewInterested() *WireMessage {
	return NewWireMessage(Interested, nil)
}
<|endoftext|>


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

package dashboard

// This file handles operations on the CL entity kind.

import (
	"bytes"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
	netmail "net/mail"
	"net/url"
	"regexp"
	"sort"
	"strings"
	"time"

	"appengine"
	"appengine/datastore"
	"appengine/mail"
	"appengine/taskqueue"
	"appengine/urlfetch"
	"appengine/user"
)

func init() {
	http.HandleFunc("/assign", handleAssign)
	http.HandleFunc("/update-cl", handleUpdateCL)
}

const codereviewBase = "http://codereview.appspot.com"

var clRegexp = regexp.MustCompile(`\d+`)

// CL represents a code review.
type CL struct {
	Number string // e.g. "5903061"
	Closed bool
	Owner  string // email address

	Created, Modified time.Time

	Description []byte `datastore:",noindex"`
	FirstLine   string `datastore:",noindex"`
	LGTMs       []string
	NotLGTMs    []string

	// Mail information.
	Subject       string   `datastore:",noindex"`
	Recipients    []string `datastore:",noindex"`
	LastMessageID string   `datastore:",noindex"`

	// These are person IDs (e.g. "rsc"); they may be empty
	Author   string
	Reviewer string
}

// DisplayOwner returns the CL's owner, either as their email address
// or the person ID if it's a reviewer. It is for display only.
func (cl *CL) DisplayOwner() string {
	if p, ok := emailToPerson[cl.Owner]; ok {
		return p
	}
	return cl.Owner
}

func (cl *CL) FirstLineHTML() template.HTML {
	s := template.HTMLEscapeString(cl.FirstLine)
	// Embolden the package name.
	if i := strings.Index(s, ":"); i >= 0 {
		s = "" + s[:i] + "" + s[i:]
	}
	return template.HTML(s)
}

func formatEmails(e []string) template.HTML {
	x := make([]string, len(e))
	for i, s := range e {
		s = template.HTMLEscapeString(s)
		if !strings.Contains(s, "@") {
			s = "" + s + ""
		}
		s = `<span class="email">` + s + ""
		x[i] = s
	}
	return template.HTML(strings.Join(x, ", "))
}

func (cl *CL) LGTMHTML() template.HTML {
	return formatEmails(cl.LGTMs)
}

func (cl *CL) NotLGTMHTML() template.HTML {
	return formatEmails(cl.NotLGTMs)
}

func (cl *CL) ModifiedAgo() string {
	// Just the first non-zero unit.
	units := [...]struct {
		suffix string
		unit   time.Duration
	}{
		{"d", 24 * time.Hour},
		{"h", time.Hour},
		{"m", time.Minute},
		{"s", time.Second},
	}
	d := time.Now().Sub(cl.Modified)
	for _, u := range units {
		if d > u.unit {
			return fmt.Sprintf("%d%s", d/u.unit, u.suffix)
		}
	}
	return "just now"
}

func handleAssign(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	if r.Method != "POST" {
		http.Error(w, "Bad method "+r.Method, 400)
		return
	}

	u := user.Current(c)
	if _, ok := emailToPerson[u.Email]; !ok {
		http.Error(w, "Not allowed", http.StatusUnauthorized)
		return
	}

	n, rev := r.FormValue("cl"), r.FormValue("r")
	if !clRegexp.MatchString(n) {
		c.Errorf("Bad CL %q", n)
		http.Error(w, "Bad CL", 400)
		return
	}
	if _, ok := preferredEmail[rev]; !ok && rev != "" {
		c.Errorf("Unknown reviewer %q", rev)
		http.Error(w, "Unknown reviewer", 400)
		return
	}

	key := datastore.NewKey(c, "CL", n, 0, nil)

	if rev != "" {
		// Make sure the reviewer is listed in Rietveld as a reviewer.
		url := codereviewBase + "/" + n + "/fields"
		resp, err := urlfetch.Client(c).Get(url + "?field=reviewers")
		if err != nil {
			c.Errorf("Retrieving CL reviewer list failed: %v", err)
			http.Error(w, err.Error(), 500)
			return
		}
		defer resp.Body.Close()
		if resp.StatusCode != 200 {
			c.Errorf("Retrieving CL reviewer list failed: got HTTP response %d", resp.StatusCode)
			http.Error(w, "Failed contacting Rietveld", 500)
			return
		}

		var apiResp struct {
			Reviewers []string `json:"reviewers"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
			// probably can't be retried
			msg := fmt.Sprintf("Malformed JSON from %v: %v", url, err)
			c.Errorf("%s", msg)
			http.Error(w, msg, 500)
			return
		}
		found := false
		for _, r := range apiResp.Reviewers {
			if emailToPerson[r] == rev {
				found = true
				break
			}
		}
		if !found {
			c.Infof("Adding %v as a reviewer of CL %v", rev, n)

			// We can't do this easily, as we need authentication to edit
			// an issue on behalf of a user, which is non-trivial. For now,
			// just send a mail with the body "R=<reviewer>", Cc'ing that person,
			// and rely on social convention.
			cl := new(CL)
			err := datastore.Get(c, key, cl)
			if err != nil {
				c.Errorf("%s", err)
				http.Error(w, err.Error(), 500)
				return
			}
			msg := &mail.Message{
				Sender: u.Email,
				To:     []string{preferredEmail[rev]},
				Cc:     cl.Recipients,
				// Take care to match Rietveld's subject line
				// so that Gmail will correctly thread mail.
				Subject: cl.Subject + " (issue " + n + ")",
				Body:    "R=" + rev + "\n\n(sent by gocodereview)",
			}
			if cl.LastMessageID != "" {
				msg.Headers = netmail.Header{
					"In-Reply-To": []string{cl.LastMessageID},
				}
			}
			if err := mail.Send(c, msg); err != nil {
				c.Errorf("mail.Send: %v", err)
			}
		}
	}

	// Update our own record.
	err := datastore.RunInTransaction(c, func(c appengine.Context) error {
		cl := new(CL)
		err := datastore.Get(c, key, cl)
		if err != nil {
			return err
		}
		cl.Reviewer = rev
		_, err = datastore.Put(c, key, cl)
		return err
	}, nil)
	if err != nil {
		msg := fmt.Sprintf("Assignment failed: %v", err)
		c.Errorf("%s", msg)
		http.Error(w, msg, 500)
		return
	}
	c.Infof("Assigned CL %v to %v", n, rev)
}

func UpdateCLLater(c appengine.Context, n string, delay time.Duration) {
	t := taskqueue.NewPOSTTask("/update-cl", url.Values{
		"cl": []string{n},
	})
	t.Delay = delay
	if _, err := taskqueue.Add(c, t, "update-cl"); err != nil {
		c.Errorf("Failed adding task: %v", err)
	}
}

func handleUpdateCL(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	n := r.FormValue("cl")
	if !clRegexp.MatchString(n) {
		c.Errorf("Bad CL %q", n)
		http.Error(w, "Bad CL", 400)
		return
	}

	if err := updateCL(c, n); err != nil {
		c.Errorf("Failed updating CL %v: %v", n, err)
		http.Error(w, "Failed update", 500)
		return
	}

	io.WriteString(w, "OK")
}

// updateCL updates a single CL. If a retryable failure occurs, an error is returned.
func updateCL(c appengine.Context, n string) error {
	c.Debugf("Updating CL %v", n)
	key := datastore.NewKey(c, "CL", n, 0, nil)

	url := codereviewBase + "/api/" + n + "?messages=true"
	resp, err := urlfetch.Client(c).Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	raw, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("Failed reading HTTP body: %v", err)
	}

	// Special case for abandoned CLs.
	if resp.StatusCode == 404 && bytes.Contains(raw, []byte("No issue exists with that id")) {
		// Don't bother checking for errors. The CL might never have been saved, for instance.
		datastore.Delete(c, key)
		c.Infof("Deleted abandoned CL %v", n)
		return nil
	}

	if resp.StatusCode != 200 {
		return fmt.Errorf("Update: got HTTP response %d", resp.StatusCode)
	}

	var apiResp struct {
		Description string `json:"description"`
		Created     string `json:"created"`
		OwnerEmail  string `json:"owner_email"`
		Modified    string `json:"modified"`
		Closed      bool   `json:"closed"`
		Subject     string `json:"subject"`
		Messages    []struct {
			Text       string   `json:"text"`
			Sender     string   `json:"sender"`
			Recipients []string `json:"recipients"`
			Approval   bool     `json:"approval"`
		} `json:"messages"`
	}
	if err := json.Unmarshal(raw, &apiResp); err != nil {
		// probably can't be retried
		c.Errorf("Malformed JSON from %v: %v", url, err)
		return nil
	}
	//c.Infof("RAW: %+v", apiResp)

	cl := &CL{
		Number:      n,
		Closed:      apiResp.Closed,
		Owner:       apiResp.OwnerEmail,
		Description: []byte(apiResp.Description),
		FirstLine:   apiResp.Description,
		Subject:     apiResp.Subject,
		Author:      emailToPerson[apiResp.OwnerEmail],
	}
	cl.Created, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Created)
	if err != nil {
		c.Errorf("Bad creation time %q: %v", apiResp.Created, err)
	}
	cl.Modified, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Modified)
	if err != nil {
		c.Errorf("Bad modification time %q: %v", apiResp.Modified, err)
	}
	if i := strings.Index(cl.FirstLine, "\n"); i >= 0 {
		cl.FirstLine = cl.FirstLine[:i]
	}
	lgtm := make(map[string]bool)
	notLGTM := make(map[string]bool)
	rcpt := make(map[string]bool)
	for _, msg := range apiResp.Messages {
		s, rev := msg.Sender, false
		if p, ok := emailToPerson[s]; ok {
			s, rev = p, true
		}

		// CLs submitted by someone other than the CL owner do not immediately
		// transition to "closed". Let's simulate the intention by treating
		// messages starting with "*** Submitted as " from a reviewer as a
		// signal that the CL is now closed.
		if rev && strings.HasPrefix(msg.Text, "*** Submitted as ") {
			cl.Closed = true
		}

		if msg.Approval {
			lgtm[s] = true
			delete(notLGTM, s) // "LGTM" overrules previous "NOT LGTM"
		}
		if strings.Contains(msg.Text, "NOT LGTM") {
			notLGTM[s] = true
			delete(lgtm, s) // "NOT LGTM" overrules previous "LGTM"
		}

		for _, r := range msg.Recipients {
			rcpt[r] = true
		}
	}
	for l := range lgtm {
		cl.LGTMs = append(cl.LGTMs, l)
	}
	for l := range notLGTM {
		cl.NotLGTMs = append(cl.NotLGTMs, l)
	}
	for r := range rcpt {
		cl.Recipients = append(cl.Recipients, r)
	}
	sort.Strings(cl.LGTMs)
	sort.Strings(cl.NotLGTMs)
	sort.Strings(cl.Recipients)

	err = datastore.RunInTransaction(c, func(c appengine.Context) error {
		ocl := new(CL)
		err := datastore.Get(c, key, ocl)
		if err != nil && err != datastore.ErrNoSuchEntity {
			return err
		} else if err == nil {
			// LastMessageID and Reviewer need preserving.
			cl.LastMessageID = ocl.LastMessageID
			cl.Reviewer = ocl.Reviewer
		}
		_, err = datastore.Put(c, key, cl)
		return err
	}, nil)
	if err != nil {
		return err
	}
	c.Infof("Updated CL %v", n)
	return nil
}
<commit_msg>misc/dashboard/codereview: interpret zero reviewers as the CL being closed.<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dashboard

// This file handles operations on the CL entity kind.

import (
	"bytes"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
	netmail "net/mail"
	"net/url"
	"regexp"
	"sort"
	"strings"
	"time"

	"appengine"
	"appengine/datastore"
	"appengine/mail"
	"appengine/taskqueue"
	"appengine/urlfetch"
	"appengine/user"
)

func init() {
	http.HandleFunc("/assign", handleAssign)
	http.HandleFunc("/update-cl", handleUpdateCL)
}

const codereviewBase = "http://codereview.appspot.com"

var clRegexp = regexp.MustCompile(`\d+`)

// CL represents a code review.
type CL struct {
	Number string // e.g. "5903061"
	Closed bool
	Owner  string // email address

	Created, Modified time.Time

	Description []byte `datastore:",noindex"`
	FirstLine   string `datastore:",noindex"`
	LGTMs       []string
	NotLGTMs    []string

	// Mail information.
	Subject       string   `datastore:",noindex"`
	Recipients    []string `datastore:",noindex"`
	LastMessageID string   `datastore:",noindex"`

	// These are person IDs (e.g. "rsc"); they may be empty
	Author   string
	Reviewer string
}

// DisplayOwner returns the CL's owner, either as their email address
// or the person ID if it's a reviewer. It is for display only.
func (cl *CL) DisplayOwner() string {
	if p, ok := emailToPerson[cl.Owner]; ok {
		return p
	}
	return cl.Owner
}

func (cl *CL) FirstLineHTML() template.HTML {
	s := template.HTMLEscapeString(cl.FirstLine)
	// Embolden the package name.
	if i := strings.Index(s, ":"); i >= 0 {
		s = "" + s[:i] + "" + s[i:]
	}
	return template.HTML(s)
}

func formatEmails(e []string) template.HTML {
	x := make([]string, len(e))
	for i, s := range e {
		s = template.HTMLEscapeString(s)
		if !strings.Contains(s, "@") {
			s = "" + s + ""
		}
		s = `<span class="email">` + s + ""
		x[i] = s
	}
	return template.HTML(strings.Join(x, ", "))
}

func (cl *CL) LGTMHTML() template.HTML {
	return formatEmails(cl.LGTMs)
}

func (cl *CL) NotLGTMHTML() template.HTML {
	return formatEmails(cl.NotLGTMs)
}

func (cl *CL) ModifiedAgo() string {
	// Just the first non-zero unit.
	units := [...]struct {
		suffix string
		unit   time.Duration
	}{
		{"d", 24 * time.Hour},
		{"h", time.Hour},
		{"m", time.Minute},
		{"s", time.Second},
	}
	d := time.Now().Sub(cl.Modified)
	for _, u := range units {
		if d > u.unit {
			return fmt.Sprintf("%d%s", d/u.unit, u.suffix)
		}
	}
	return "just now"
}

func handleAssign(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	if r.Method != "POST" {
		http.Error(w, "Bad method "+r.Method, 400)
		return
	}

	u := user.Current(c)
	if _, ok := emailToPerson[u.Email]; !ok {
		http.Error(w, "Not allowed", http.StatusUnauthorized)
		return
	}

	n, rev := r.FormValue("cl"), r.FormValue("r")
	if !clRegexp.MatchString(n) {
		c.Errorf("Bad CL %q", n)
		http.Error(w, "Bad CL", 400)
		return
	}
	if _, ok := preferredEmail[rev]; !ok && rev != "" {
		c.Errorf("Unknown reviewer %q", rev)
		http.Error(w, "Unknown reviewer", 400)
		return
	}

	key := datastore.NewKey(c, "CL", n, 0, nil)

	if rev != "" {
		// Make sure the reviewer is listed in Rietveld as a reviewer.
		url := codereviewBase + "/" + n + "/fields"
		resp, err := urlfetch.Client(c).Get(url + "?field=reviewers")
		if err != nil {
			c.Errorf("Retrieving CL reviewer list failed: %v", err)
			http.Error(w, err.Error(), 500)
			return
		}
		defer resp.Body.Close()
		if resp.StatusCode != 200 {
			c.Errorf("Retrieving CL reviewer list failed: got HTTP response %d", resp.StatusCode)
			http.Error(w, "Failed contacting Rietveld", 500)
			return
		}

		var apiResp struct {
			Reviewers []string `json:"reviewers"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
			// probably can't be retried
			msg := fmt.Sprintf("Malformed JSON from %v: %v", url, err)
			c.Errorf("%s", msg)
			http.Error(w, msg, 500)
			return
		}
		found := false
		for _, r := range apiResp.Reviewers {
			if emailToPerson[r] == rev {
				found = true
				break
			}
		}
		if !found {
			c.Infof("Adding %v as a reviewer of CL %v", rev, n)

			// We can't do this easily, as we need authentication to edit
			// an issue on behalf of a user, which is non-trivial. For now,
			// just send a mail with the body "R=<reviewer>", Cc'ing that person,
			// and rely on social convention.
			cl := new(CL)
			err := datastore.Get(c, key, cl)
			if err != nil {
				c.Errorf("%s", err)
				http.Error(w, err.Error(), 500)
				return
			}
			msg := &mail.Message{
				Sender: u.Email,
				To:     []string{preferredEmail[rev]},
				Cc:     cl.Recipients,
				// Take care to match Rietveld's subject line
				// so that Gmail will correctly thread mail.
				Subject: cl.Subject + " (issue " + n + ")",
				Body:    "R=" + rev + "\n\n(sent by gocodereview)",
			}
			if cl.LastMessageID != "" {
				msg.Headers = netmail.Header{
					"In-Reply-To": []string{cl.LastMessageID},
				}
			}
			if err := mail.Send(c, msg); err != nil {
				c.Errorf("mail.Send: %v", err)
			}
		}
	}

	// Update our own record.
	err := datastore.RunInTransaction(c, func(c appengine.Context) error {
		cl := new(CL)
		err := datastore.Get(c, key, cl)
		if err != nil {
			return err
		}
		cl.Reviewer = rev
		_, err = datastore.Put(c, key, cl)
		return err
	}, nil)
	if err != nil {
		msg := fmt.Sprintf("Assignment failed: %v", err)
		c.Errorf("%s", msg)
		http.Error(w, msg, 500)
		return
	}
	c.Infof("Assigned CL %v to %v", n, rev)
}

func UpdateCLLater(c appengine.Context, n string, delay time.Duration) {
	t := taskqueue.NewPOSTTask("/update-cl", url.Values{
		"cl": []string{n},
	})
	t.Delay = delay
	if _, err := taskqueue.Add(c, t, "update-cl"); err != nil {
		c.Errorf("Failed adding task: %v", err)
	}
}

func handleUpdateCL(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	n := r.FormValue("cl")
	if !clRegexp.MatchString(n) {
		c.Errorf("Bad CL %q", n)
		http.Error(w, "Bad CL", 400)
		return
	}

	if err := updateCL(c, n); err != nil {
		c.Errorf("Failed updating CL %v: %v", n, err)
		http.Error(w, "Failed update", 500)
		return
	}

	io.WriteString(w, "OK")
}

// updateCL updates a single CL. If a retryable failure occurs, an error is returned.
func updateCL(c appengine.Context, n string) error {
	c.Debugf("Updating CL %v", n)
	key := datastore.NewKey(c, "CL", n, 0, nil)

	url := codereviewBase + "/api/" + n + "?messages=true"
	resp, err := urlfetch.Client(c).Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	raw, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("Failed reading HTTP body: %v", err)
	}

	// Special case for abandoned CLs.
	if resp.StatusCode == 404 && bytes.Contains(raw, []byte("No issue exists with that id")) {
		// Don't bother checking for errors. The CL might never have been saved, for instance.
		datastore.Delete(c, key)
		c.Infof("Deleted abandoned CL %v", n)
		return nil
	}

	if resp.StatusCode != 200 {
		return fmt.Errorf("Update: got HTTP response %d", resp.StatusCode)
	}

	var apiResp struct {
		Description string   `json:"description"`
		Reviewers   []string `json:"reviewers"`
		Created     string   `json:"created"`
		OwnerEmail  string   `json:"owner_email"`
		Modified    string   `json:"modified"`
		Closed      bool     `json:"closed"`
		Subject     string   `json:"subject"`
		Messages    []struct {
			Text       string   `json:"text"`
			Sender     string   `json:"sender"`
			Recipients []string `json:"recipients"`
			Approval   bool     `json:"approval"`
		} `json:"messages"`
	}
	if err := json.Unmarshal(raw, &apiResp); err != nil {
		// probably can't be retried
		c.Errorf("Malformed JSON from %v: %v", url, err)
		return nil
	}
	//c.Infof("RAW: %+v", apiResp)

	cl := &CL{
		Number:      n,
		Closed:      apiResp.Closed,
		Owner:       apiResp.OwnerEmail,
		Description: []byte(apiResp.Description),
		FirstLine:   apiResp.Description,
		Subject:     apiResp.Subject,
		Author:      emailToPerson[apiResp.OwnerEmail],
	}
	cl.Created, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Created)
	if err != nil {
		c.Errorf("Bad creation time %q: %v", apiResp.Created, err)
	}
	cl.Modified, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Modified)
	if err != nil {
		c.Errorf("Bad modification time %q: %v", apiResp.Modified, err)
	}
	if i := strings.Index(cl.FirstLine, "\n"); i >= 0 {
		cl.FirstLine = cl.FirstLine[:i]
	}
	// Treat zero reviewers as a signal that the CL is completed.
	// This could be after the CL has been submitted, but before the CL author has synced,
	// but it could also be a CL manually edited to remove reviewers.
	if len(apiResp.Reviewers) == 0 {
		cl.Closed = true
	}

	lgtm := make(map[string]bool)
	notLGTM := make(map[string]bool)
	rcpt := make(map[string]bool)
	for _, msg := range apiResp.Messages {
		s, rev := msg.Sender, false
		if p, ok := emailToPerson[s]; ok {
			s, rev = p, true
		}

		// CLs submitted by someone other than the CL owner do not immediately
		// transition to "closed". Let's simulate the intention by treating
		// messages starting with "*** Submitted as " from a reviewer as a
		// signal that the CL is now closed.
		if rev && strings.HasPrefix(msg.Text, "*** Submitted as ") {
			cl.Closed = true
		}

		if msg.Approval {
			lgtm[s] = true
			delete(notLGTM, s) // "LGTM" overrules previous "NOT LGTM"
		}
		if strings.Contains(msg.Text, "NOT LGTM") {
			notLGTM[s] = true
			delete(lgtm, s) // "NOT LGTM" overrules previous "LGTM"
		}

		for _, r := range msg.Recipients {
			rcpt[r] = true
		}
	}
	for l := range lgtm {
		cl.LGTMs = append(cl.LGTMs, l)
	}
	for l := range notLGTM {
		cl.NotLGTMs = append(cl.NotLGTMs, l)
	}
	for r := range rcpt {
		cl.Recipients = append(cl.Recipients, r)
	}
	sort.Strings(cl.LGTMs)
	sort.Strings(cl.NotLGTMs)
	sort.Strings(cl.Recipients)

	err = datastore.RunInTransaction(c, func(c appengine.Context) error {
		ocl := new(CL)
		err := datastore.Get(c, key, ocl)
		if err != nil && err != datastore.ErrNoSuchEntity {
			return err
		} else if err == nil {
			// LastMessageID and Reviewer need preserving.
			cl.LastMessageID = ocl.LastMessageID
			cl.Reviewer = ocl.Reviewer
		}
		_, err = datastore.Put(c, key, cl)
		return err
	}, nil)
	if err != nil {
		return err
	}
	c.Infof("Updated CL %v", n)
	return nil
}
<|endoftext|>


<commit_before>// This program is an IRC bot that just sits in a channel and keeps the
// connection open. It is different in that it is more accepting of errors that
// over the lifetime of a connection. This is to try to see if such behaviour
// helps recovery. Specifically I want to see if it is possible for retrying to
// make any difference in keeping a connection alive in the face of an
// unreliable connection.
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/horgh/irc"
	"github.com/horgh/irc/client"
)

func main() {
	args, err := getArgs()
	if err != nil {
		log.Fatal(err)
		return
	}

	conf, err := parseConfig(args.Config)
	if err != nil {
		log.Fatal(err)
	}

	client := client.New(conf.Nick, conf.Nick, conf.Nick, conf.ServerHost,
		conf.ServerPort, true)
	client.SetTimeoutTime(7 * time.Minute)

	run(conf, client)
}

// Args hold command line arguments.
type Args struct {
	Config string
}

func getArgs() (*Args, error) {
	config := flag.String("conf", "", "Configuration file.")

	flag.Parse()

	if *config == "" {
		flag.PrintDefaults()
		return nil, fmt.Errorf("no config file provided")
	}

	return &Args{
		Config: *config,
	}, nil
}

// Config holds what we parsed from a config file.
type Config struct {
	Channels   []string
	Nick       string
	ServerHost string
	ServerPort int
}

func parseConfig(path string) (*Config, error) {
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("error reading file: %s: %s", path, err)
	}

	reader := bytes.NewReader(buf)
	scanner := bufio.NewScanner(reader)

	m := map[string]string{}

	for scanner.Scan() {
		text := scanner.Text()
		text = strings.TrimSpace(text)
		if text == "" || text[0] == '#' {
			continue
		}

		pieces := strings.SplitN(text, "=", 2)
		if len(pieces) != 2 {
			return nil, fmt.Errorf("malformed line: %s", text)
		}

		key := strings.TrimSpace(pieces[0])
		value := strings.TrimSpace(pieces[1])

		if key == "" {
			return nil, fmt.Errorf("key is blank: %s", text)
		}

		// Allow value to be blank

		if _, ok := m[key]; ok {
			return nil, fmt.Errorf("duplicate key: %s", key)
		}

		m[key] = value
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("error scanning file: %s: %s", path, err)
	}

	conf := &Config{}

	channelsRaw := strings.Split(m["channels"], ",")
	for _, c := range channelsRaw {
		c = strings.TrimSpace(c)
		if c == "" {
			continue
		}
		if c[0] != '#' {
			return nil, fmt.Errorf("malformed channel name: %s", c)
		}
		// We could look for dupes.
		conf.Channels = append(conf.Channels, c)
	}
	if len(conf.Channels) == 0 {
		return nil, fmt.Errorf("you must specify at least one channel")
	}

	if v := m["nick"]; v == "" {
		return nil, fmt.Errorf("you must specify a nick")
	}
	conf.Nick = m["nick"]

	if v := m["server-host"]; v == "" {
		return nil, fmt.Errorf("you must specify a server-host")
	}
	conf.ServerHost = m["server-host"]

	p, err := strconv.ParseInt(m["server-port"], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid server-port: %s", err)
	}
	conf.ServerPort = int(p)

	return conf, nil
}

type state struct {
	lastActivityTime time.Time
	errors           []string
}

const (
	waitPeriod = 15 * time.Minute
)

func run(conf *Config, c *client.Client) {
	s := &state{}

	for {
		if !c.IsConnected() {
			if err := connect(conf, c); err != nil {
				s.addError("error connecting: %s", err)
				_ = c.Close()
				continue
			}
			s.lastActivityTime = time.Now()
			continue
		}

		m, err := c.ReadMessage()
		if err != nil {
			s.addError("error reading: %s", err)
			// If we have EOF then we'll just see that from now on, so abort.
			if s.shouldGiveUp() || err == io.EOF {
				_ = c.Close()
			}
			continue
		}

		s.lastActivityTime = time.Now()

		if m.Command == "ERROR" {
			s.addError("got ERROR: %s", m)
			_ = c.Close()
			continue
		}

		if m.Command != "PING" {
			continue
		}

		if err := c.Pong(m); err != nil {
			s.addError("error PONGing: %s", err)
			if s.shouldGiveUp() {
				_ = c.Close()
			}
			continue
		}

		s.lastActivityTime = time.Now()

		if err := sendMessages(conf, c, s); err != nil {
			s.addError("error messaging: %s", err)
			if s.shouldGiveUp() {
				_ = c.Close()
			}
			continue
		}
	}
}

func connect(conf *Config, c *client.Client) error {
	if err := c.Connect(); err != nil {
		return err
	}

	if err := c.Register(); err != nil {
		return err
	}

	for {
		m, err := c.ReadMessage()
		if err != nil {
			return err
		}

		if m.Command == irc.ReplyWelcome {
			c.SetRegistered()

			for _, ch := range conf.Channels {
				if err := c.Join(ch); err != nil {
					return fmt.Errorf("error joining channel: %s: %s", ch, err)
				}
			}

			return nil
		}

		if m.Command == "ERROR" {
			return fmt.Errorf("received ERROR: %s", m)
		}
	}
}

func (s *state) addError(format string, args ...interface{}) {
	finalArgs := []interface{}{time.Now().Format(time.RFC3339)}
	if len(args) > 0 {
		finalArgs = append(finalArgs, args...)
	}

	m := fmt.Sprintf("%s: "+format, finalArgs...)
	log.Print(m)

	s.errors = append(s.errors, m)
}

func (s *state) shouldGiveUp() bool {
	return time.Now().Sub(s.lastActivityTime) > waitPeriod
}

func sendMessages(conf *Config, c *client.Client, s *state) error {
	for _, ch := range conf.Channels {
		for i, e := range s.errors {
			if err := c.Message(ch, e); err != nil {
				s.errors = s.errors[i:]
				return err
			}

			s.lastActivityTime = time.Now()
		}
	}

	s.errors = nil
	return nil
}
<commit_msg>Sleep every time through the loop<commit_after>// This program is an IRC bot that just sits in a channel and keeps the
// connection open. It is different in that it is more accepting of errors that
// over the lifetime of a connection. This is to try to see if such behaviour
// helps recovery. Specifically I want to see if it is possible for retrying to
// make any difference in keeping a connection alive in the face of an
// unreliable connection.
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/horgh/irc"
	"github.com/horgh/irc/client"
)

func main() {
	args, err := getArgs()
	if err != nil {
		log.Fatal(err)
		return
	}

	conf, err := parseConfig(args.Config)
	if err != nil {
		log.Fatal(err)
	}

	client := client.New(conf.Nick, conf.Nick, conf.Nick, conf.ServerHost,
		conf.ServerPort, true)
	client.SetTimeoutTime(7 * time.Minute)

	run(conf, client)
}

// Args hold command line arguments.
type Args struct {
	Config string
}

func getArgs() (*Args, error) {
	config := flag.String("conf", "", "Configuration file.")

	flag.Parse()

	if *config == "" {
		flag.PrintDefaults()
		return nil, fmt.Errorf("no config file provided")
	}

	return &Args{
		Config: *config,
	}, nil
}

// Config holds what we parsed from a config file.
type Config struct {
	Channels   []string
	Nick       string
	ServerHost string
	ServerPort int
}

func parseConfig(path string) (*Config, error) {
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("error reading file: %s: %s", path, err)
	}

	reader := bytes.NewReader(buf)
	scanner := bufio.NewScanner(reader)

	m := map[string]string{}

	for scanner.Scan() {
		text := scanner.Text()
		text = strings.TrimSpace(text)
		if text == "" || text[0] == '#' {
			continue
		}

		pieces := strings.SplitN(text, "=", 2)
		if len(pieces) != 2 {
			return nil, fmt.Errorf("malformed line: %s", text)
		}

		key := strings.TrimSpace(pieces[0])
		value := strings.TrimSpace(pieces[1])

		if key == "" {
			return nil, fmt.Errorf("key is blank: %s", text)
		}

		// Allow value to be blank

		if _, ok := m[key]; ok {
			return nil, fmt.Errorf("duplicate key: %s", key)
		}

		m[key] = value
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("error scanning file: %s: %s", path, err)
	}

	conf := &Config{}

	channelsRaw := strings.Split(m["channels"], ",")
	for _, c := range channelsRaw {
		c = strings.TrimSpace(c)
		if c == "" {
			continue
		}
		if c[0] != '#' {
			return nil, fmt.Errorf("malformed channel name: %s", c)
		}
		// We could look for dupes.
		conf.Channels = append(conf.Channels, c)
	}
	if len(conf.Channels) == 0 {
		return nil, fmt.Errorf("you must specify at least one channel")
	}

	if v := m["nick"]; v == "" {
		return nil, fmt.Errorf("you must specify a nick")
	}
	conf.Nick = m["nick"]

	if v := m["server-host"]; v == "" {
		return nil, fmt.Errorf("you must specify a server-host")
	}
	conf.ServerHost = m["server-host"]

	p, err := strconv.ParseInt(m["server-port"], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid server-port: %s", err)
	}
	conf.ServerPort = int(p)

	return conf, nil
}

type state struct {
	lastActivityTime time.Time
	errors           []string
}

const (
	waitPeriod = 15 * time.Minute
)

func run(conf *Config, c *client.Client) {
	s := &state{}

	for {
		time.Sleep(time.Second)

		if !c.IsConnected() {
			if err := connect(conf, c); err != nil {
				s.addError("error connecting: %s", err)
				_ = c.Close()
				continue
			}
			s.lastActivityTime = time.Now()
			continue
		}

		m, err := c.ReadMessage()
		if err != nil {
			s.addError("error reading: %s", err)
			// If we have EOF then we'll just see that from now on, so abort.
			if s.shouldGiveUp() || err == io.EOF {
				_ = c.Close()
			}
			continue
		}

		s.lastActivityTime = time.Now()

		if m.Command == "ERROR" {
			s.addError("got ERROR: %s", m)
			_ = c.Close()
			continue
		}

		if m.Command != "PING" {
			continue
		}

		if err := c.Pong(m); err != nil {
			s.addError("error PONGing: %s", err)
			if s.shouldGiveUp() {
				_ = c.Close()
			}
			continue
		}

		s.lastActivityTime = time.Now()

		if err := sendMessages(conf, c, s); err != nil {
			s.addError("error messaging: %s", err)
			if s.shouldGiveUp() {
				_ = c.Close()
			}
			continue
		}
	}
}

func connect(conf *Config, c *client.Client) error {
	if err := c.Connect(); err != nil {
		return err
	}

	if err := c.Register(); err != nil {
		return err
	}

	for {
		m, err := c.ReadMessage()
		if err != nil {
			return err
		}

		if m.Command == irc.ReplyWelcome {
			c.SetRegistered()

			for _, ch := range conf.Channels {
				if err := c.Join(ch); err != nil {
					return fmt.Errorf("error joining channel: %s: %s", ch, err)
				}
			}

			return nil
		}

		if m.Command == "ERROR" {
			return fmt.Errorf("received ERROR: %s", m)
		}
	}
}

func (s *state) addError(format string, args ...interface{}) {
	finalArgs := []interface{}{time.Now().Format(time.RFC3339)}
	if len(args) > 0 {
		finalArgs = append(finalArgs, args...)
	}

	m := fmt.Sprintf("%s: "+format, finalArgs...)
	log.Print(m)

	s.errors = append(s.errors, m)
}

func (s *state) shouldGiveUp() bool {
	return time.Now().Sub(s.lastActivityTime) > waitPeriod
}

func sendMessages(conf *Config, c *client.Client, s *state) error {
	for _, ch := range conf.Channels {
		for i, e := range s.errors {
			if err := c.Message(ch, e); err != nil {
				s.errors = s.errors[i:]
				return err
			}

			s.lastActivityTime = time.Now()
		}
	}

	s.errors = nil
	return nil
}
<|endoftext|>


<commit_before>package zkill

import (
	"fmt"
	"strconv"
	"time"
)

// Historical returns all kill ID's and their accompanying hashes submittted to
// zKillboard on the provided date.
func (client *Client) Historical(date time.Time) (killmails map[int]string, err error) {
	// TODO
	results := make(map[string]string)
	client.fetch(fmt.Sprintf("/history/%v/", date.Format("20060102")), &results)
	var id int
	for key, val := range results {
		if id, err = strconv.Atoi(key); err != nil {
			killmails[id] = val
		} else {
			err = fmt.Errorf("unable to conver string value '%v' to type int: %v", key, err)
			return
		}
	}
	return
}
<commit_msg>fixed bug where Client.Historical improperly returned an error<commit_after>package zkill

import (
	"fmt"
	"strconv"
	"time"
)

// Historical returns all kill ID's and their accompanying hashes submittted to
// zKillboard on the provided date.
func (client *Client) Historical(date time.Time) (killmails map[int]string, err error) {
	// TODO
	results := make(map[string]string)
	client.fetch(fmt.Sprintf("/history/%v/", date.Format("20060102")), &results)
	var id int
	for key, val := range results {
		if id, err = strconv.Atoi(key); err != nil {
			err = fmt.Errorf("unable to convert string value '%v' to type int: %v", key, err)
			return
		} else {
			killmails[id] = val
		}
	}
	return
}
<|endoftext|>


<commit_before>package pgconn_test

import (
	"context"
	"os"
	"testing"

	"github.com/jackc/pgconn"
	"github.com/stretchr/testify/require"
)

func BenchmarkConnect(b *testing.B) {
	benchmarks := []struct {
		name string
		env  string
	}{
		{"Unix socket", "PGX_TEST_UNIX_SOCKET_CONN_STRING"},
		{"TCP", "PGX_TEST_TCP_CONN_STRING"},
	}

	for _, bm := range benchmarks {
		b.Run(bm.name, func(b *testing.B) {
			connString := os.Getenv(bm.env)
			if connString == "" {
				b.Skipf("Skipping due to missing environment variable %v", bm.env)
			}

			for i := 0; i < b.N; i++ {
				conn, err := pgconn.Connect(context.Background(), connString)
				require.Nil(b, err)

				err = conn.Close(context.Background())
				require.Nil(b, err)
			}
		})
	}
}

func BenchmarkExec(b *testing.B) {
	conn, err := pgconn.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
	require.Nil(b, err)
	defer closeConn(b, conn)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, err := conn.Exec(context.Background(), "select 'hello'::text as a, 42::int4 as b, '2019-01-01'::date").ReadAll()
		require.Nil(b, err)
	}
}

func BenchmarkExecPossibleToCancel(b *testing.B) {
	conn, err := pgconn.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
	require.Nil(b, err)
	defer closeConn(b, conn)

	b.ResetTimer()

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

	for i := 0; i < b.N; i++ {
		_, err := conn.Exec(ctx, "select 'hello'::text as a, 42::int4 as b, '2019-01-01'::date").ReadAll()
		require.Nil(b, err)
	}
}

func BenchmarkExecPrepared(b *testing.B) {
	conn, err := pgconn.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
	require.Nil(b, err)
	defer closeConn(b, conn)

	_, err = conn.Prepare(context.Background(), "ps1", "select 'hello'::text as a, 42::int4 as b, '2019-01-01'::date", nil)
	require.Nil(b, err)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		result := conn.ExecPrepared(context.Background(), "ps1", nil, nil, nil).Read()
		require.Nil(b, result.Err)
	}
}

func BenchmarkExecPreparedPossibleToCancel(b *testing.B) {
	conn, err := pgconn.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
	require.Nil(b, err)
	defer closeConn(b, conn)

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

	_, err = conn.Prepare(ctx, "ps1", "select 'hello'::text as a, 42::int4 as b, '2019-01-01'::date", nil)
	require.Nil(b, err)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		result := conn.ExecPrepared(ctx, "ps1", nil, nil, nil).Read()
		require.Nil(b, result.Err)
	}
}
<commit_msg>Do not buffer results in benchmarks<commit_after>package pgconn_test

import (
	"bytes"
	"context"
	"os"
	"testing"

	"github.com/jackc/pgconn"
	"github.com/stretchr/testify/require"
)

func BenchmarkConnect(b *testing.B) {
	benchmarks := []struct {
		name string
		env  string
	}{
		{"Unix socket", "PGX_TEST_UNIX_SOCKET_CONN_STRING"},
		{"TCP", "PGX_TEST_TCP_CONN_STRING"},
	}

	for _, bm := range benchmarks {
		b.Run(bm.name, func(b *testing.B) {
			connString := os.Getenv(bm.env)
			if connString == "" {
				b.Skipf("Skipping due to missing environment variable %v", bm.env)
			}

			for i := 0; i < b.N; i++ {
				conn, err := pgconn.Connect(context.Background(), connString)
				require.Nil(b, err)

				err = conn.Close(context.Background())
				require.Nil(b, err)
			}
		})
	}
}

func BenchmarkExec(b *testing.B) {
	conn, err := pgconn.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
	require.Nil(b, err)
	defer closeConn(b, conn)

	expectedValues := [][]byte{[]byte("hello"), []byte("42"), []byte("2019-01-01")}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		mrr := conn.Exec(context.Background(), "select 'hello'::text as a, 42::int4 as b, '2019-01-01'::date")

		for mrr.NextResult() {
			rr := mrr.ResultReader()

			rowCount := 0
			for rr.NextRow() {
				rowCount += 1
				if len(rr.Values()) != len(expectedValues) {
					b.Fatalf("unexpected number of values: %d", len(rr.Values()))
				}
				for i := range rr.Values() {
					if bytes.Compare(rr.Values()[i], expectedValues[i]) != 0 {
						b.Fatalf("unexpected values: %s %s", rr.Values()[i], expectedValues[i])
					}
				}
			}
			_, err = rr.Close()

			if err != nil {
				b.Fatal(err)
			}
			if rowCount != 1 {
				b.Fatalf("unexpected rowCount: %d", rowCount)
			}
		}

		err := mrr.Close()
		if err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkExecPossibleToCancel(b *testing.B) {
	conn, err := pgconn.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
	require.Nil(b, err)
	defer closeConn(b, conn)

	expectedValues := [][]byte{[]byte("hello"), []byte("42"), []byte("2019-01-01")}

	b.ResetTimer()

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

	for i := 0; i < b.N; i++ {
		mrr := conn.Exec(ctx, "select 'hello'::text as a, 42::int4 as b, '2019-01-01'::date")

		for mrr.NextResult() {
			rr := mrr.ResultReader()

			rowCount := 0
			for rr.NextRow() {
				rowCount += 1
				if len(rr.Values()) != len(expectedValues) {
					b.Fatalf("unexpected number of values: %d", len(rr.Values()))
				}
				for i := range rr.Values() {
					if bytes.Compare(rr.Values()[i], expectedValues[i]) != 0 {
						b.Fatalf("unexpected values: %s %s", rr.Values()[i], expectedValues[i])
					}
				}
			}
			_, err = rr.Close()

			if err != nil {
				b.Fatal(err)
			}
			if rowCount != 1 {
				b.Fatalf("unexpected rowCount: %d", rowCount)
			}
		}

		err := mrr.Close()
		if err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkExecPrepared(b *testing.B) {
	conn, err := pgconn.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
	require.Nil(b, err)
	defer closeConn(b, conn)

	_, err = conn.Prepare(context.Background(), "ps1", "select 'hello'::text as a, 42::int4 as b, '2019-01-01'::date", nil)
	require.Nil(b, err)

	expectedValues := [][]byte{[]byte("hello"), []byte("42"), []byte("2019-01-01")}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rr := conn.ExecPrepared(context.Background(), "ps1", nil, nil, nil)

		rowCount := 0
		for rr.NextRow() {
			rowCount += 1
			if len(rr.Values()) != len(expectedValues) {
				b.Fatalf("unexpected number of values: %d", len(rr.Values()))
			}
			for i := range rr.Values() {
				if bytes.Compare(rr.Values()[i], expectedValues[i]) != 0 {
					b.Fatalf("unexpected values: %s %s", rr.Values()[i], expectedValues[i])
				}
			}
		}
		_, err = rr.Close()

		if err != nil {
			b.Fatal(err)
		}
		if rowCount != 1 {
			b.Fatalf("unexpected rowCount: %d", rowCount)
		}
	}
}

func BenchmarkExecPreparedPossibleToCancel(b *testing.B) {
	conn, err := pgconn.Connect(context.Background(), os.Getenv("PGX_TEST_DATABASE"))
	require.Nil(b, err)
	defer closeConn(b, conn)

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

	_, err = conn.Prepare(ctx, "ps1", "select 'hello'::text as a, 42::int4 as b, '2019-01-01'::date", nil)
	require.Nil(b, err)

	expectedValues := [][]byte{[]byte("hello"), []byte("42"), []byte("2019-01-01")}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		rr := conn.ExecPrepared(ctx, "ps1", nil, nil, nil)

		rowCount := 0
		for rr.NextRow() {
			rowCount += 1
			if len(rr.Values()) != len(expectedValues) {
				b.Fatalf("unexpected number of values: %d", len(rr.Values()))
			}
			for i := range rr.Values() {
				if bytes.Compare(rr.Values()[i], expectedValues[i]) != 0 {
					b.Fatalf("unexpected values: %s %s", rr.Values()[i], expectedValues[i])
				}
			}
		}
		_, err = rr.Close()

		if err != nil {
			b.Fatal(err)
		}
		if rowCount != 1 {
			b.Fatalf("unexpected rowCount: %d", rowCount)
		}
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"

	cmds "github.com/ipfs/go-ipfs/commands"
	e "github.com/ipfs/go-ipfs/core/commands/e"

	logging "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log"
	lwriter "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log/writer"
	"gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
)

// Golang os.Args overrides * and replaces the character argument with
// an array which includes every file in the user's CWD. As a
// workaround, we use 'all' instead. The util library still uses * so
// we convert it at this step.
var logAllKeyword = "all"

var LogCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Interact with the daemon log output.",
		ShortDescription: `
'ipfs log' contains utility commands to affect or read the logging
output of a running daemon.
`,
	},

	Subcommands: map[string]*cmds.Command{
		"level": logLevelCmd,
		"ls":    logLsCmd,
		"tail":  logTailCmd,
	},
}

var logLevelCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Change the logging level.",
		ShortDescription: `
Change the verbosity of one or all subsystems log output. This does not affect
the event log.
`,
	},

	Arguments: []cmdkit.Argument{
		// TODO use a different keyword for 'all' because all can theoretically
		// clash with a subsystem name
		cmdkit.StringArg("subsystem", true, false, fmt.Sprintf("The subsystem logging identifier. Use '%s' for all subsystems.", logAllKeyword)),
		cmdkit.StringArg("level", true, false, `The log level, with 'debug' the most verbose and 'critical' the least verbose.
			One of: debug, info, warning, error, critical.
		`),
	},
	Run: func(req cmds.Request, res cmds.Response) {

		args := req.Arguments()
		subsystem, level := args[0], args[1]

		if subsystem == logAllKeyword {
			subsystem = "*"
		}

		if err := logging.SetLogLevel(subsystem, level); err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		s := fmt.Sprintf("Changed log level of '%s' to '%s'\n", subsystem, level)
		log.Info(s)
		res.SetOutput(&MessageOutput{s})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: MessageTextMarshaler,
	},
	Type: MessageOutput{},
}

var logLsCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "List the logging subsystems.",
		ShortDescription: `
'ipfs log ls' is a utility command used to list the logging
subsystems of a running daemon.
`,
	},
	Run: func(req cmds.Request, res cmds.Response) {
		res.SetOutput(&stringList{logging.GetSubsystems()})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
	Type: stringList{},
}

var logTailCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Read the event log.",
		ShortDescription: `
Outputs event log messages (not other log messages) as they are generated.
`,
	},

	Run: func(req cmds.Request, res cmds.Response) {
		ctx := req.Context()
		r1, w1 := io.Pipe()
		r2, w2 := io.Pipe()
		go func() {
			defer w1.Close()
			<-ctx.Done()
		}()
		go func() {
			defer w2.Close()
			decoder := json.NewDecoder(r1)
			encoder := json.NewEncoder(w2)
			for {
				var obj interface{}
				if decoder.Decode(&obj) != nil || encoder.Encode(obj) != nil {
					return
				}
			}
		}()

		lwriter.WriterGroup.AddWriter(w1)
		res.SetOutput(r2)
	},
}

func stringListMarshaler(res cmds.Response) (io.Reader, error) {
	v, err := unwrapOutput(res.Output())
	if err != nil {
		return nil, err
	}

	list, ok := v.(*stringList)
	if !ok {
		return nil, e.TypeErr(list, v)
	}

	buf := new(bytes.Buffer)
	for _, s := range list.Strings {
		buf.WriteString(s)
		buf.WriteString("\n")
	}

	return buf, nil
}
<commit_msg>add a todo to remember to remove log reformatting<commit_after>package commands

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"

	cmds "github.com/ipfs/go-ipfs/commands"
	e "github.com/ipfs/go-ipfs/core/commands/e"

	logging "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log"
	lwriter "gx/ipfs/QmZChCsSt8DctjceaL56Eibc29CVQq4dGKRXC5JRZ6Ppae/go-log/writer"
	"gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
)

// Golang os.Args overrides * and replaces the character argument with
// an array which includes every file in the user's CWD. As a
// workaround, we use 'all' instead. The util library still uses * so
// we convert it at this step.
var logAllKeyword = "all"

var LogCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Interact with the daemon log output.",
		ShortDescription: `
'ipfs log' contains utility commands to affect or read the logging
output of a running daemon.
`,
	},

	Subcommands: map[string]*cmds.Command{
		"level": logLevelCmd,
		"ls":    logLsCmd,
		"tail":  logTailCmd,
	},
}

var logLevelCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Change the logging level.",
		ShortDescription: `
Change the verbosity of one or all subsystems log output. This does not affect
the event log.
`,
	},

	Arguments: []cmdkit.Argument{
		// TODO use a different keyword for 'all' because all can theoretically
		// clash with a subsystem name
		cmdkit.StringArg("subsystem", true, false, fmt.Sprintf("The subsystem logging identifier. Use '%s' for all subsystems.", logAllKeyword)),
		cmdkit.StringArg("level", true, false, `The log level, with 'debug' the most verbose and 'critical' the least verbose.
			One of: debug, info, warning, error, critical.
		`),
	},
	Run: func(req cmds.Request, res cmds.Response) {

		args := req.Arguments()
		subsystem, level := args[0], args[1]

		if subsystem == logAllKeyword {
			subsystem = "*"
		}

		if err := logging.SetLogLevel(subsystem, level); err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		s := fmt.Sprintf("Changed log level of '%s' to '%s'\n", subsystem, level)
		log.Info(s)
		res.SetOutput(&MessageOutput{s})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: MessageTextMarshaler,
	},
	Type: MessageOutput{},
}

var logLsCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "List the logging subsystems.",
		ShortDescription: `
'ipfs log ls' is a utility command used to list the logging
subsystems of a running daemon.
`,
	},
	Run: func(req cmds.Request, res cmds.Response) {
		res.SetOutput(&stringList{logging.GetSubsystems()})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: stringListMarshaler,
	},
	Type: stringList{},
}

var logTailCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Read the event log.",
		ShortDescription: `
Outputs event log messages (not other log messages) as they are generated.
`,
	},

	Run: func(req cmds.Request, res cmds.Response) {
		ctx := req.Context()
		r1, w1 := io.Pipe()
		r2, w2 := io.Pipe()
		go func() {
			defer w1.Close()
			<-ctx.Done()
		}()
		// Reformat the logs as ndjson
		// TODO: remove this: #5709
		go func() {
			defer w2.Close()
			decoder := json.NewDecoder(r1)
			encoder := json.NewEncoder(w2)
			for {
				var obj interface{}
				if decoder.Decode(&obj) != nil || encoder.Encode(obj) != nil {
					return
				}
			}
		}()

		lwriter.WriterGroup.AddWriter(w1)
		res.SetOutput(r2)
	},
}

func stringListMarshaler(res cmds.Response) (io.Reader, error) {
	v, err := unwrapOutput(res.Output())
	if err != nil {
		return nil, err
	}

	list, ok := v.(*stringList)
	if !ok {
		return nil, e.TypeErr(list, v)
	}

	buf := new(bytes.Buffer)
	for _, s := range list.Strings {
		buf.WriteString(s)
		buf.WriteString("\n")
	}

	return buf, nil
}
<|endoftext|>


<commit_before>// go-camo daemon (go-camod)
package main

import (
	"errors"
	"fmt"
	"log"
	"net/url"
	"os"
	"strings"

	"github.com/cactus/go-camo/camoproxy/encoding"
	flags "github.com/jessevdk/go-flags"
)

type EncodeCommand struct {
	Base string `short:"b" long:"base" default:"hex" description:"Encode/Decode base. Either hex or base64"`
	Prefix  string `short:"p" long:"prefix" default:"" description:"Optional url prefix used by encode output"`
}

func (c *EncodeCommand) Execute(args []string) error {
	if opts.HmacKey == "" {
		return errors.New("Empty HMAC")
	}

	if len(args) == 0 {
		return errors.New("No url argument provided")
	}

	oURL := args[0]
	if oURL == "" {
		return errors.New("No url argument provided")
	}

	hmacKeyBytes := []byte(opts.HmacKey)
	var outURL string
	switch c.Base {
	case "base64":
		outURL = encoding.B64EncodeURL(hmacKeyBytes, oURL)
	case "hex":
		outURL = encoding.HexEncodeURL(hmacKeyBytes, oURL)
	default:
		return errors.New("Invalid base provided")
	}
	fmt.Println(c.Prefix + outURL)
	return nil
}

type DecodeCommand struct{}

func (c *DecodeCommand) Execute(args []string) error {
	if opts.HmacKey == "" {
		return errors.New("Empty HMAC")
	}

	if len(args) == 0 {
		return errors.New("No url argument provided")
	}

	oURL := args[0]
	if oURL == "" {
		return errors.New("No url argument provided")
	}

	hmacKeyBytes := []byte(opts.HmacKey)

	u, err := url.Parse(oURL)
	if err != nil {
		return err
	}
	comp := strings.SplitN(u.Path, "/", 3)
	decURL, valid := encoding.DecodeURL(hmacKeyBytes, comp[1], comp[2])
	if !valid {
		return errors.New("hmac is invalid")
	}
	log.Println(decURL)
	return nil
}

var opts struct {
	HmacKey string `short:"k" long:"key" description:"HMAC key"`
}

func main() {
	// clear log prefix -- not needed for tool
	log.SetFlags(0)

	parser := flags.NewParser(&opts, flags.Default)
	parser.AddCommand("encode", "Encode a url and print result",
		"Encode a url and print result", &EncodeCommand{})
	parser.AddCommand("decode", "Decode a url and print result",
		"Decode a url and print result", &DecodeCommand{})

	// parse said flags
	_, err := parser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); ok {
			if e.Type == flags.ErrHelp {
				os.Exit(0)
			}
		}
		os.Exit(1)
	}
}
<commit_msg>go fmt<commit_after>// go-camo daemon (go-camod)
package main

import (
	"errors"
	"fmt"
	"log"
	"net/url"
	"os"
	"strings"

	"github.com/cactus/go-camo/camoproxy/encoding"
	flags "github.com/jessevdk/go-flags"
)

type EncodeCommand struct {
	Base   string `short:"b" long:"base" default:"hex" description:"Encode/Decode base. Either hex or base64"`
	Prefix string `short:"p" long:"prefix" default:"" description:"Optional url prefix used by encode output"`
}

func (c *EncodeCommand) Execute(args []string) error {
	if opts.HmacKey == "" {
		return errors.New("Empty HMAC")
	}

	if len(args) == 0 {
		return errors.New("No url argument provided")
	}

	oURL := args[0]
	if oURL == "" {
		return errors.New("No url argument provided")
	}

	hmacKeyBytes := []byte(opts.HmacKey)
	var outURL string
	switch c.Base {
	case "base64":
		outURL = encoding.B64EncodeURL(hmacKeyBytes, oURL)
	case "hex":
		outURL = encoding.HexEncodeURL(hmacKeyBytes, oURL)
	default:
		return errors.New("Invalid base provided")
	}
	fmt.Println(c.Prefix + outURL)
	return nil
}

type DecodeCommand struct{}

func (c *DecodeCommand) Execute(args []string) error {
	if opts.HmacKey == "" {
		return errors.New("Empty HMAC")
	}

	if len(args) == 0 {
		return errors.New("No url argument provided")
	}

	oURL := args[0]
	if oURL == "" {
		return errors.New("No url argument provided")
	}

	hmacKeyBytes := []byte(opts.HmacKey)

	u, err := url.Parse(oURL)
	if err != nil {
		return err
	}
	comp := strings.SplitN(u.Path, "/", 3)
	decURL, valid := encoding.DecodeURL(hmacKeyBytes, comp[1], comp[2])
	if !valid {
		return errors.New("hmac is invalid")
	}
	log.Println(decURL)
	return nil
}

var opts struct {
	HmacKey string `short:"k" long:"key" description:"HMAC key"`
}

func main() {
	// clear log prefix -- not needed for tool
	log.SetFlags(0)

	parser := flags.NewParser(&opts, flags.Default)
	parser.AddCommand("encode", "Encode a url and print result",
		"Encode a url and print result", &EncodeCommand{})
	parser.AddCommand("decode", "Decode a url and print result",
		"Decode a url and print result", &DecodeCommand{})

	// parse said flags
	_, err := parser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); ok {
			if e.Type == flags.ErrHelp {
				os.Exit(0)
			}
		}
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package wechat

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/KevinGong2013/ggbot/utils"
	"github.com/skratchdot/open-golang/open"
)

// UUIDProcessor scan this uuid
type UUIDProcessor interface {
	ProcessUUID(uuid string) error
	UUIDDidConfirm(err error)
}

// implements UUIDProcessor
type defaultUUIDProcessor struct {
	path string
}

func (dp *defaultUUIDProcessor) ProcessUUID(uuid string) error {
	// 2.``
	path, err := utils.FetchORCodeImage(uuid)

	if err != nil {
		return err
	}
	logger.Debugf(`qrcode image path: %s`, path)

	// 3.
	go func() {
		dp.path = path
		open.Start(path)
	}()
	logger.Info(`please scan ORCode by wechat mobile application`)

	return nil
}

func (dp *defaultUUIDProcessor) UUIDDidConfirm(err error) {
	if len(dp.path) > 0 {
		utils.DeleteFile(dp.path)
	}
}

var retryTimes = time.Duration(1)

var loginState = make(chan int) // -1 登录失败 1登录成功

type initRequest struct {
	BaseRequest *BaseRequest
}

type initResp struct {
	Response
	User    Contact
	Skey    string
	SyncKey map[string]interface{}
}

func (wechat *WeChat) reLogin() error {

	client, err := newClient()
	if err != nil {
		return err
	}

	wechat.Client = client

	err = wechat.beginLoginFlow()
	if err != nil {
		return err
	}

	return nil
}

// run is used to login to wechat server. Need end user scan orcode.
func (wechat *WeChat) beginLoginFlow() error {

	logger.Info(`wait a moment, prepare login parameters ... ...`)

	redirectURL, err := wechat.quickLogin()

	if err != nil {

		redirectURL = ``
		logger.Warn(err)

		// 1.
		uuid, err := wechat.fetchUUID()

		if err != nil {
			return err
		}

		// 2.
		err = wechat.UUIDProcessor.ProcessUUID(uuid)

		if err != nil {
			return err
		}

		// 3.
		code, tip := ``, 1

		for code != httpOK {
			redirectURL, code, tip, err = wechat.waitConfirmUUID(uuid, tip)
			if err != nil {
				wechat.UUIDProcessor.UUIDDidConfirm(err)
				return err
			}
		}

		wechat.UUIDProcessor.UUIDDidConfirm(nil)

		req, _ := http.NewRequest(`GET`, redirectURL, nil)

		// 4.
		if err = wechat.login(req); err != nil {
			return err
		}
	}

	//5.
	index := strings.LastIndex(redirectURL, "/")
	if index == -1 {
		index = len(redirectURL)
	}
	wechat.BaseURL = redirectURL[:index]

	// 6.
	return wechat.init()
}

func (wechat *WeChat) quickLogin() (string, error) {

	file, err := os.Open(`.ggbot/cookie-cache`)
	if err != nil {
		return ``, err
	}
	bs, err := ioutil.ReadAll(file)
	if err != nil {
		return ``, err
	}

	var info map[string]interface{}
	err = json.Unmarshal(bs, &info)
	if err != nil {
		return ``, err
	}
	url := info[`url`].(string)
	cookies := info[`cookies`].([]interface{})
	req, _ := http.NewRequest(`GET`, url, nil)
	for _, c := range cookies {
		b, _ := json.Marshal(c)
		var cookie *http.Cookie
		e := json.Unmarshal(b, cookie)
		if e == nil {
			req.AddCookie(cookie)
		}
	}

	return url, wechat.login(req)
}

func (wechat *WeChat) fetchUUID() (string, error) {

	jsloginURL := "https://login.weixin.qq.com/jslogin"

	params := url.Values{}
	params.Set("appid", "wx782c26e4c19acffb")
	params.Set("fun", "new")
	params.Set("lang", "zh_CN")
	params.Set("_", strconv.FormatInt(time.Now().Unix(), 10))

	resp, err := wechat.Client.PostForm(jsloginURL, params)
	if err != nil {
		return ``, err
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return ``, err
	}
	ds := string(data)

	code, err := utils.Search(ds, `window.QRLogin.code = `, `;`)

	if err != nil {
		return ``, err
	}

	if code != httpOK {
		err = fmt.Errorf("error code is unexpect:[%s], api result:[%s]", code, ds)
		return ``, err
	}

	uuid, err := utils.Search(ds, `window.QRLogin.uuid = "`, `";`)
	if err != nil {
		return ``, err
	}

	return uuid, nil
}

func (wechat *WeChat) waitConfirmUUID(uuid string, tip int) (redirectURI, code string, rt int, err error) {

	loginURL, rt := fmt.Sprintf("https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%d&uuid=%s&_=%s", tip, uuid, strconv.FormatInt(time.Now().Unix(), 10)), tip
	resp, err := wechat.Client.Get(loginURL)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	ds := string(data)

	code, err = utils.Search(ds, `window.code=`, `;`)
	if err != nil {
		return
	}

	rt = 0
	switch code {
	case "201":
		logger.Debug(`scan successed, waitting wechat app send confirm request.`)
	case httpOK:
		redirectURI, err = utils.Search(ds, `window.redirect_uri="`, `";`)
		if err != nil {
			return
		}
		redirectURI += "&fun=new"
	default:
		err = fmt.Errorf("time out, will retry %v", err)
	}
	return
}

func (wechat *WeChat) login(req *http.Request) error {

	resp, err := wechat.Client.Do(req)

	if err != nil {
		return err
	}
	defer resp.Body.Close()

	reader := resp.Body.(io.Reader)

	// full fill base request
	if err = xml.NewDecoder(reader).Decode(wechat.BaseRequest); err != nil {
		logger.Debug(err)
		return err
	}

	if wechat.BaseRequest.Ret != 0 { // 0 is success
		err = fmt.Errorf("login failed message:[%s]", wechat.BaseRequest.Message)
		return err
	}

	// added device id
	wechat.BaseRequest.DeviceID = `e999471493880231`

	info := map[string]interface{}{
		`cookies`: resp.Cookies(),
		`url`:     req.URL.String(),
	}

	b, err := json.Marshal(info)
	if err != nil {
		logger.Warnf(`save cookie error: %v`, err)
	} else {
		utils.CreateFile(`.ggbot/cookie-cache`, b, false)
		logger.Info(`did upate cookie cache`)
	}

	return nil
}

func (wechat *WeChat) init() error {

	data, err := json.Marshal(initRequest{
		BaseRequest: wechat.BaseRequest,
	})
	if err != nil {
		return err
	}

	resp := new(initResp)
	apiURI := fmt.Sprintf("%s/webwxinit?%s&%s&r=%s", wechat.BaseURL, wechat.PassTicketKV(), wechat.SkeyKV(), utils.Now())

	if err = wechat.Excute(apiURI, bytes.NewReader(data), resp); err != nil {
		return err
	}

	wechat.BaseRequest.Skey = resp.Skey

	wechat.MySelf = resp.User
	wechat.syncKey = resp.SyncKey

	return nil
}

func (wechat *WeChat) keepAlive() {
	go func() {

		err := wechat.reLogin()

		if err != nil {
			logger.Errorf(`login failed: %v`, err)
			triggerAfter := time.After(time.Minute * retryTimes)
			logger.Warnf(`will retry login after %d minute(s)`, retryTimes)
			<-triggerAfter
			retryTimes++
			wechat.keepAlive()
			return
		}

		logger.Info(`CONGRATULATION login successed`)

		err = wechat.SyncContact()
		if err != nil {
			logger.Errorf(`sync contact error: %v`, err)
		}

		loginState <- 1

		err = wechat.listen(addMsg, modContact, delContact, modChatRoomMember)

		if err != nil {
			logger.Errorf(`can't listen server event %v`, err)
		}

		wechat.keepAlive() // if listen occured error will excute this cmd.
		loginState <- -1
		return
	}()
}
<commit_msg>delete cookie cache after login failed.<commit_after>package wechat

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/KevinGong2013/ggbot/utils"
	"github.com/skratchdot/open-golang/open"
)

var cookieCachePath = `.ggbot/cookie-cache`

// UUIDProcessor scan this uuid
type UUIDProcessor interface {
	ProcessUUID(uuid string) error
	UUIDDidConfirm(err error)
}

// implements UUIDProcessor
type defaultUUIDProcessor struct {
	path string
}

func (dp *defaultUUIDProcessor) ProcessUUID(uuid string) error {
	// 2.``
	path, err := utils.FetchORCodeImage(uuid)

	if err != nil {
		return err
	}
	logger.Debugf(`qrcode image path: %s`, path)

	// 3.
	go func() {
		dp.path = path
		open.Start(path)
	}()
	logger.Info(`please scan ORCode by wechat mobile application`)

	return nil
}

func (dp *defaultUUIDProcessor) UUIDDidConfirm(err error) {
	if len(dp.path) > 0 {
		utils.DeleteFile(dp.path)
	}
}

var retryTimes = time.Duration(1)

var loginState = make(chan int) // -1 登录失败 1登录成功

type initRequest struct {
	BaseRequest *BaseRequest
}

type initResp struct {
	Response
	User    Contact
	Skey    string
	SyncKey map[string]interface{}
}

func (wechat *WeChat) reLogin() error {

	client, err := newClient()
	if err != nil {
		return err
	}

	wechat.Client = client

	err = wechat.beginLoginFlow()
	if err != nil {
		return err
	}

	return nil
}

// run is used to login to wechat server. Need end user scan orcode.
func (wechat *WeChat) beginLoginFlow() error {

	logger.Info(`wait a moment, prepare login parameters ... ...`)

	redirectURL, err := wechat.quickLogin()

	if err != nil {
		utils.DeleteFile(cookieCachePath)
		redirectURL = ``
		logger.Warn(err)

		// 1.
		uuid, err := wechat.fetchUUID()

		if err != nil {
			return err
		}

		// 2.
		err = wechat.UUIDProcessor.ProcessUUID(uuid)

		if err != nil {
			return err
		}

		// 3.
		code, tip := ``, 1

		for code != httpOK {
			redirectURL, code, tip, err = wechat.waitConfirmUUID(uuid, tip)
			if err != nil {
				wechat.UUIDProcessor.UUIDDidConfirm(err)
				return err
			}
		}

		wechat.UUIDProcessor.UUIDDidConfirm(nil)

		req, _ := http.NewRequest(`GET`, redirectURL, nil)

		// 4.
		if err = wechat.login(req); err != nil {
			return err
		}
	}

	//5.
	index := strings.LastIndex(redirectURL, "/")
	if index == -1 {
		index = len(redirectURL)
	}
	wechat.BaseURL = redirectURL[:index]

	// 6.
	return wechat.init()
}

func (wechat *WeChat) quickLogin() (string, error) {

	file, err := os.Open(cookieCachePath)
	if err != nil {
		return ``, err
	}
	bs, err := ioutil.ReadAll(file)
	if err != nil {
		return ``, err
	}

	var info map[string]interface{}
	err = json.Unmarshal(bs, &info)
	if err != nil {
		return ``, err
	}
	url := info[`url`].(string)
	cookies := info[`cookies`].([]interface{})
	req, _ := http.NewRequest(`GET`, url, nil)
	for _, c := range cookies {
		b, _ := json.Marshal(c)
		var cookie *http.Cookie
		e := json.Unmarshal(b, cookie)
		if e == nil {
			req.AddCookie(cookie)
		}
	}

	logger.Debug(`quick login flow`)

	return url, wechat.login(req)
}

func (wechat *WeChat) fetchUUID() (string, error) {

	jsloginURL := "https://login.weixin.qq.com/jslogin"

	params := url.Values{}
	params.Set("appid", "wx782c26e4c19acffb")
	params.Set("fun", "new")
	params.Set("lang", "zh_CN")
	params.Set("_", strconv.FormatInt(time.Now().Unix(), 10))

	resp, err := wechat.Client.PostForm(jsloginURL, params)
	if err != nil {
		return ``, err
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return ``, err
	}
	ds := string(data)

	code, err := utils.Search(ds, `window.QRLogin.code = `, `;`)

	if err != nil {
		return ``, err
	}

	if code != httpOK {
		err = fmt.Errorf("error code is unexpect:[%s], api result:[%s]", code, ds)
		return ``, err
	}

	uuid, err := utils.Search(ds, `window.QRLogin.uuid = "`, `";`)
	if err != nil {
		return ``, err
	}

	return uuid, nil
}

func (wechat *WeChat) waitConfirmUUID(uuid string, tip int) (redirectURI, code string, rt int, err error) {

	loginURL, rt := fmt.Sprintf("https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%d&uuid=%s&_=%s", tip, uuid, strconv.FormatInt(time.Now().Unix(), 10)), tip
	resp, err := wechat.Client.Get(loginURL)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	ds := string(data)

	code, err = utils.Search(ds, `window.code=`, `;`)
	if err != nil {
		return
	}

	rt = 0
	switch code {
	case "201":
		logger.Debug(`scan successed, waitting wechat app send confirm request.`)
	case httpOK:
		redirectURI, err = utils.Search(ds, `window.redirect_uri="`, `";`)
		if err != nil {
			return
		}
		redirectURI += "&fun=new"
	default:
		err = fmt.Errorf("time out, will retry %v", err)
	}
	return
}

func (wechat *WeChat) login(req *http.Request) error {

	resp, err := wechat.Client.Do(req)

	if err != nil {
		return err
	}
	defer resp.Body.Close()

	reader := resp.Body.(io.Reader)

	// full fill base request
	if err = xml.NewDecoder(reader).Decode(wechat.BaseRequest); err != nil {
		logger.Debug(err)
		return err
	}

	if wechat.BaseRequest.Ret != 0 { // 0 is success
		err = fmt.Errorf("login failed message:[%s]", wechat.BaseRequest.Message)
		return err
	}

	// added device id
	wechat.BaseRequest.DeviceID = `e999471493880231`

	info := map[string]interface{}{
		`cookies`: resp.Cookies(),
		`url`:     req.URL.String(),
	}

	b, err := json.Marshal(info)
	if err != nil {
		logger.Warnf(`save cookie error: %v`, err)
	} else {
		utils.CreateFile(cookieCachePath, b, false)
		logger.Info(`did upate cookie cache`)
	}

	return nil
}

func (wechat *WeChat) init() error {

	data, err := json.Marshal(initRequest{
		BaseRequest: wechat.BaseRequest,
	})
	if err != nil {
		return err
	}

	resp := new(initResp)
	apiURI := fmt.Sprintf("%s/webwxinit?%s&%s&r=%s", wechat.BaseURL, wechat.PassTicketKV(), wechat.SkeyKV(), utils.Now())

	if err = wechat.Excute(apiURI, bytes.NewReader(data), resp); err != nil {
		return err
	}

	wechat.BaseRequest.Skey = resp.Skey

	wechat.MySelf = resp.User
	wechat.syncKey = resp.SyncKey

	return nil
}

func (wechat *WeChat) keepAlive() {
	go func() {

		err := wechat.reLogin()

		if err != nil {
			logger.Errorf(`login failed: %v`, err)
			triggerAfter := time.After(time.Minute * retryTimes)
			logger.Warnf(`will retry login after %d minute(s)`, retryTimes)
			<-triggerAfter
			retryTimes++
			wechat.keepAlive()
			return
		}

		logger.Info(`CONGRATULATION login successed`)

		err = wechat.SyncContact()
		if err != nil {
			logger.Errorf(`sync contact error: %v`, err)
		}

		loginState <- 1

		err = wechat.listen(addMsg, modContact, delContact, modChatRoomMember)

		if err != nil {
			logger.Errorf(`can't listen server event %v`, err)
		}

		wechat.keepAlive() // if listen occured error will excute this cmd.
		loginState <- -1
		return
	}()
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"

	cmds "github.com/jbenet/go-ipfs/commands"
)

type Link struct {
	Name, Hash string
	Size       uint64
}

type Object struct {
	Hash  string
	Links []Link
}

type LsOutput struct {
	Objects []Object
}

var lsCmd = &cmds.Command{
	Arguments: []cmds.Argument{
		cmds.Argument{"object", cmds.ArgString, false, true},
	},
	Help: "TODO",
	Run: func(res cmds.Response, req cmds.Request) {
		node := req.Context().Node
		output := make([]Object, len(req.Arguments()))

		for i, arg := range req.Arguments() {
			path := arg.(string)
			dagnode, err := node.Resolver.ResolvePath(path)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

			output[i] = Object{
				Hash:  path,
				Links: make([]Link, len(dagnode.Links)),
			}
			for j, link := range dagnode.Links {
				output[i].Links[j] = Link{
					Name: link.Name,
					Hash: link.Hash.B58String(),
					Size: link.Size,
				}
			}
		}

		res.SetOutput(&LsOutput{output})
	},
	Marshallers: map[cmds.EncodingType]cmds.Marshaller{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
			s := ""
			output := res.Output().(*LsOutput).Objects

			for _, object := range output {
				if len(output) > 1 {
					s += fmt.Sprintf("%s:\n", object.Hash)
				}

				for _, link := range object.Links {
					s += fmt.Sprintf("-> %s %s (%v bytes)\n", link.Name, link.Hash, link.Size)
				}

				if len(output) > 1 {
					s += "\n"
				}
			}

			return []byte(s), nil
		},
	},
	Type: &LsOutput{},
}
<commit_msg>refac(commands2/ls) split loop<commit_after>package commands

import (
	"errors"
	"fmt"

	cmds "github.com/jbenet/go-ipfs/commands"
	merkledag "github.com/jbenet/go-ipfs/merkledag"
)

type Link struct {
	Name, Hash string
	Size       uint64
}

type Object struct {
	Hash  string
	Links []Link
}

type LsOutput struct {
	Objects []Object
}

var lsCmd = &cmds.Command{
	Arguments: []cmds.Argument{
		cmds.Argument{"object", cmds.ArgString, false, true},
	},
	Help: "TODO",
	Run: func(res cmds.Response, req cmds.Request) {
		node := req.Context().Node

		paths := make([]string, 0)
		for _, arg := range req.Arguments() {
			path, ok := arg.(string)
			if !ok {
				res.SetError(errors.New("cast error"), cmds.ErrNormal)
				return
			}
			paths = append(paths, path)
		}

		dagnodes := make([]*merkledag.Node, 0)
		for _, path := range paths {
			dagnode, err := node.Resolver.ResolvePath(path)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			dagnodes = append(dagnodes, dagnode)
		}

		output := make([]Object, len(req.Arguments()))
		for i, dagnode := range dagnodes {
			output[i] = Object{
				Hash:  paths[i],
				Links: make([]Link, len(dagnode.Links)),
			}
			for j, link := range dagnode.Links {
				output[i].Links[j] = Link{
					Name: link.Name,
					Hash: link.Hash.B58String(),
					Size: link.Size,
				}
			}
		}

		res.SetOutput(&LsOutput{output})
	},
	Marshallers: map[cmds.EncodingType]cmds.Marshaller{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
			s := ""
			output := res.Output().(*LsOutput).Objects

			for _, object := range output {
				if len(output) > 1 {
					s += fmt.Sprintf("%s:\n", object.Hash)
				}

				for _, link := range object.Links {
					s += fmt.Sprintf("-> %s %s (%v bytes)\n", link.Name, link.Hash, link.Size)
				}

				if len(output) > 1 {
					s += "\n"
				}
			}

			return []byte(s), nil
		},
	},
	Type: &LsOutput{},
}
<|endoftext|>