<commit_before>package cas

import (
	"crypto/rand"
	"fmt"
	"net/http"
	"net/url"
	"sync"

	"github.com/golang/glog"
)

// Client configuration options
type Options struct {
	URL         *url.URL     // URL to the CAS service
	Store       TicketStore  // Custom TicketStore, if nil a MemoryStore will be used
	Client      *http.Client // Custom http client to allow options for http connections
	SendService bool         // Custom sendService to determine whether you need to send service param
	URLScheme 	URLScheme	 // Custom url scheme, can be used to modify the request urls for the client
}

// Client implements the main protocol
type Client struct {
	tickets   TicketStore
	client    *http.Client
	urlScheme URLScheme

	mu          sync.Mutex
	sessions    map[string]string
	sendService bool

	stValidator *ServiceTicketValidator
}

// NewClient creates a Client with the provided Options.
func NewClient(options *Options) *Client {
	if glog.V(2) {
		glog.Infof("cas: new client with options %v", options)
	}

	var tickets TicketStore
	if options.Store != nil {
		tickets = options.Store
	} else {
		tickets = &MemoryStore{}
	}

	var urlScheme URLScheme
	if options.URLScheme != nil {
		urlScheme = options.URLScheme
	} else {
		urlScheme = NewDefaultURLScheme(options.URL)
	}

	var client *http.Client
	if options.Client != nil {
		client = options.Client
	} else {
		client = &http.Client{}
	}

	return &Client{
		tickets:     tickets,
		client:      client,
		urlScheme:   urlScheme,
		sessions:    make(map[string]string),
		sendService: options.SendService,
		stValidator: NewServiceTicketValidator(client, options.URL),
	}
}

// Handle wraps a http.Handler to provide CAS authentication for the handler.
func (c *Client) Handle(h http.Handler) http.Handler {
	return &clientHandler{
		c: c,
		h: h,
	}
}

// HandleFunc wraps a function to provide CAS authentication for the handler function.
func (c *Client) HandleFunc(h func(http.ResponseWriter, *http.Request)) http.Handler {
	return c.Handle(http.HandlerFunc(h))
}

// requestURL determines an absolute URL from the http.Request.
func requestURL(r *http.Request) (*url.URL, error) {
	u, err := url.Parse(r.URL.String())
	if err != nil {
		return nil, err
	}

	u.Host = r.Host
	if host := r.Header.Get("X-Forwarded-Host"); host != "" {
		u.Host = host
	}

	u.Scheme = "http"
	if scheme := r.Header.Get("X-Forwarded-Proto"); scheme != "" {
		u.Scheme = scheme
	} else if r.TLS != nil {
		u.Scheme = "https"
	}

	return u, nil
}

// LoginUrlForRequest determines the CAS login URL for the http.Request.
func (c *Client) LoginUrlForRequest(r *http.Request) (string, error) {
	u, err := c.urlScheme.Login()
	if err != nil {
		return "", err
	}

	service, err := requestURL(r)
	if err != nil {
		return "", err
	}

	q := u.Query()
	q.Add("service", sanitisedURLString(service))
	u.RawQuery = q.Encode()

	return u.String(), nil
}

// LogoutUrlForRequest determines the CAS logout URL for the http.Request.
func (c *Client) LogoutUrlForRequest(r *http.Request) (string, error) {
	u, err := c.urlScheme.Logout()
	if err != nil {
		return "", err
	}

	if c.sendService {
		service, err := requestURL(r)
		if err != nil {
			return "", err
		}

		q := u.Query()
		q.Add("service", sanitisedURLString(service))
		u.RawQuery = q.Encode()
	}

	return u.String(), nil
}

// ServiceValidateUrlForRequest determines the CAS serviceValidate URL for the ticket and http.Request.
func (c *Client) ServiceValidateUrlForRequest(ticket string, r *http.Request) (string, error) {
	service, err := requestURL(r)
	if err != nil {
		return "", err
	}
	return c.stValidator.ServiceValidateUrl(service, ticket)
}

// ValidateUrlForRequest determines the CAS validate URL for the ticket and http.Request.
func (c *Client) ValidateUrlForRequest(ticket string, r *http.Request) (string, error) {
	service, err := requestURL(r)
	if err != nil {
		return "", err
	}
	return c.stValidator.ValidateUrl(service, ticket)
}

// RedirectToLogout replies to the request with a redirect URL to log out of CAS.
func (c *Client) RedirectToLogout(w http.ResponseWriter, r *http.Request) {
	u, err := c.LogoutUrlForRequest(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if glog.V(2) {
		glog.Info("Logging out, redirecting client to %v with status %v",
			u, http.StatusFound)
	}

	c.clearSession(w, r)
	http.Redirect(w, r, u, http.StatusFound)
}

// RedirectToLogout replies to the request with a redirect URL to authenticate with CAS.
func (c *Client) RedirectToLogin(w http.ResponseWriter, r *http.Request) {
	u, err := c.LoginUrlForRequest(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if glog.V(2) {
		glog.Infof("Redirecting client to %v with status %v", u, http.StatusFound)
	}

	http.Redirect(w, r, u, http.StatusFound)
}

// validateTicket performs CAS ticket validation with the given ticket and service.
func (c *Client) validateTicket(ticket string, service *http.Request) error {
	serviceUrl, err := requestURL(service)
	if err != nil {
		return err
	}

	success, err := c.stValidator.ValidateTicket(serviceUrl, ticket)
	if err != nil {
		return err
	}

	if err := c.tickets.Write(ticket, success); err != nil {
		return err
	}

	return nil
}

// getSession finds or creates a session for the request.
//
// A cookie is set on the response if one is not provided with the request.
// Validates the ticket if the URL parameter is provided.
func (c *Client) getSession(w http.ResponseWriter, r *http.Request) {
	cookie := getCookie(w, r)

	if s, ok := c.sessions[cookie.Value]; ok {
		if t, err := c.tickets.Read(s); err == nil {
			if glog.V(1) {
				glog.Infof("Re-used ticket %s for %s", s, t.User)
			}

			setAuthenticationResponse(r, t)
			return
		} else {
			if glog.V(2) {
				glog.Infof("Ticket %v not in %T: %v", s, c.tickets, err)
			}

			if glog.V(1) {
				glog.Infof("Clearing ticket %s, no longer exists in ticket store", s)
			}

			clearCookie(w, cookie)
		}
	}

	if ticket := r.URL.Query().Get("ticket"); ticket != "" {
		if err := c.validateTicket(ticket, r); err != nil {
			if glog.V(2) {
				glog.Infof("Error validating ticket: %v", err)
			}
			return // allow ServeHTTP()
		}

		c.setSession(cookie.Value, ticket)

		if t, err := c.tickets.Read(ticket); err == nil {
			if glog.V(1) {
				glog.Infof("Validated ticket %s for %s", ticket, t.User)
			}

			setAuthenticationResponse(r, t)
			return
		} else {
			if glog.V(2) {
				glog.Infof("Ticket %v not in %T: %v", ticket, c.tickets, err)
			}

			if glog.V(1) {
				glog.Infof("Clearing ticket %s, no longer exists in ticket store", ticket)
			}

			clearCookie(w, cookie)
		}
	}
}

// getCookie finds or creates the session cookie on the response.
func getCookie(w http.ResponseWriter, r *http.Request) *http.Cookie {
	c, err := r.Cookie(sessionCookieName)
	if err != nil {
		// NOTE: Intentionally not enabling HttpOnly so the cookie can
		//       still be used by Ajax requests.
		c = &http.Cookie{
			Name:     sessionCookieName,
			Value:    newSessionID(),
			MaxAge:   86400,
			HttpOnly: false,
		}

		if glog.V(2) {
			glog.Infof("Setting %v cookie with value: %v", c.Name, c.Value)
		}

		r.AddCookie(c) // so we can find it later if required
		http.SetCookie(w, c)
	}

	return c
}

// newSessionId generates a new opaque session identifier for use in the cookie.
func newSessionID() string {
	const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	// generate 64 character string
	bytes := make([]byte, 64)
	rand.Read(bytes)

	for k, v := range bytes {
		bytes[k] = alphabet[v%byte(len(alphabet))]
	}

	return string(bytes)
}

// clearCookie invalidates and removes the cookie from the client.
func clearCookie(w http.ResponseWriter, c *http.Cookie) {
	c.MaxAge = -1
	http.SetCookie(w, c)
}

// setSession stores the session id to ticket mapping in the Client.
func (c *Client) setSession(id string, ticket string) {
	if glog.V(2) {
		glog.Infof("Recording session, %v -> %v", id, ticket)
	}

	c.mu.Lock()
	c.sessions[id] = ticket
	c.mu.Unlock()
}

// clearSession removes the session from the client and clears the cookie.
func (c *Client) clearSession(w http.ResponseWriter, r *http.Request) {
	cookie := getCookie(w, r)

	if s, ok := c.sessions[cookie.Value]; ok {
		if err := c.tickets.Delete(s); err != nil {
			fmt.Printf("Failed to remove %v from %T: %v\n", cookie.Value, c.tickets, err)
			if glog.V(2) {
				glog.Errorf("Failed to remove %v from %T: %v", cookie.Value, c.tickets, err)
			}
		}

		c.deleteSession(s)
	}

	clearCookie(w, cookie)
}

// deleteSession removes the session from the client
func (c *Client) deleteSession(id string) {
	c.mu.Lock()
	delete(c.sessions, id)
	c.mu.Unlock()
}

// findAndDeleteSessionWithTicket removes the session from the client via Single Log Out
//
// When a Single Log Out request is received we receive the service ticket identidier. This
// function loops through the sessions to find the matching session id. Once retrieved the
// session is removed from the client. When the session is next requested the getSession
// function will notice the session is invalid and revalidate the user.
func (c *Client) findAndDeleteSessionWithTicket(ticket string) {
	var id string
	for s, t := range c.sessions {
		if t == ticket {
			id = s
			break
		}
	}

	if id == "" {
		return
	}

	c.deleteSession(id)
}
<commit_msg>Allow configuration of Cookie properties<commit_after>package cas

import (
	"crypto/rand"
	"fmt"
	"net/http"
	"net/url"
	"sync"

	"github.com/golang/glog"
)

// Client configuration options
type Options struct {
	URL         *url.URL     // URL to the CAS service
	Store       TicketStore  // Custom TicketStore, if nil a MemoryStore will be used
	Client      *http.Client // Custom http client to allow options for http connections
	SendService bool         // Custom sendService to determine whether you need to send service param
	URLScheme 	URLScheme	 // Custom url scheme, can be used to modify the request urls for the client
	Cookie      *http.Cookie // http.Cookie options, uses Path, Domain, MaxAge, HttpOnly, & Secure
}

// Client implements the main protocol
type Client struct {
	tickets   TicketStore
	client    *http.Client
	urlScheme URLScheme
	cookie    *http.Cookie

	mu          sync.Mutex
	sessions    map[string]string
	sendService bool

	stValidator *ServiceTicketValidator
}

// NewClient creates a Client with the provided Options.
func NewClient(options *Options) *Client {
	if glog.V(2) {
		glog.Infof("cas: new client with options %v", options)
	}

	var tickets TicketStore
	if options.Store != nil {
		tickets = options.Store
	} else {
		tickets = &MemoryStore{}
	}

	var urlScheme URLScheme
	if options.URLScheme != nil {
		urlScheme = options.URLScheme
	} else {
		urlScheme = NewDefaultURLScheme(options.URL)
	}

	var client *http.Client
	if options.Client != nil {
		client = options.Client
	} else {
		client = &http.Client{}
	}

	var cookie *http.Cookie
	if options.Cookie != nil {
		cookie = options.Cookie
	} else {
		cookie = &http.Cookie{
			MaxAge:   86400,
			HttpOnly: false,
			Secure:   false,
		}
	}

	return &Client{
		tickets:     tickets,
		client:      client,
		urlScheme:   urlScheme,
		cookie:      cookie,
		sessions:    make(map[string]string),
		sendService: options.SendService,
		stValidator: NewServiceTicketValidator(client, options.URL),
	}
}

// Handle wraps a http.Handler to provide CAS authentication for the handler.
func (c *Client) Handle(h http.Handler) http.Handler {
	return &clientHandler{
		c: c,
		h: h,
	}
}

// HandleFunc wraps a function to provide CAS authentication for the handler function.
func (c *Client) HandleFunc(h func(http.ResponseWriter, *http.Request)) http.Handler {
	return c.Handle(http.HandlerFunc(h))
}

// requestURL determines an absolute URL from the http.Request.
func requestURL(r *http.Request) (*url.URL, error) {
	u, err := url.Parse(r.URL.String())
	if err != nil {
		return nil, err
	}

	u.Host = r.Host
	if host := r.Header.Get("X-Forwarded-Host"); host != "" {
		u.Host = host
	}

	u.Scheme = "http"
	if scheme := r.Header.Get("X-Forwarded-Proto"); scheme != "" {
		u.Scheme = scheme
	} else if r.TLS != nil {
		u.Scheme = "https"
	}

	return u, nil
}

// LoginUrlForRequest determines the CAS login URL for the http.Request.
func (c *Client) LoginUrlForRequest(r *http.Request) (string, error) {
	u, err := c.urlScheme.Login()
	if err != nil {
		return "", err
	}

	service, err := requestURL(r)
	if err != nil {
		return "", err
	}

	q := u.Query()
	q.Add("service", sanitisedURLString(service))
	u.RawQuery = q.Encode()

	return u.String(), nil
}

// LogoutUrlForRequest determines the CAS logout URL for the http.Request.
func (c *Client) LogoutUrlForRequest(r *http.Request) (string, error) {
	u, err := c.urlScheme.Logout()
	if err != nil {
		return "", err
	}

	if c.sendService {
		service, err := requestURL(r)
		if err != nil {
			return "", err
		}

		q := u.Query()
		q.Add("service", sanitisedURLString(service))
		u.RawQuery = q.Encode()
	}

	return u.String(), nil
}

// ServiceValidateUrlForRequest determines the CAS serviceValidate URL for the ticket and http.Request.
func (c *Client) ServiceValidateUrlForRequest(ticket string, r *http.Request) (string, error) {
	service, err := requestURL(r)
	if err != nil {
		return "", err
	}
	return c.stValidator.ServiceValidateUrl(service, ticket)
}

// ValidateUrlForRequest determines the CAS validate URL for the ticket and http.Request.
func (c *Client) ValidateUrlForRequest(ticket string, r *http.Request) (string, error) {
	service, err := requestURL(r)
	if err != nil {
		return "", err
	}
	return c.stValidator.ValidateUrl(service, ticket)
}

// RedirectToLogout replies to the request with a redirect URL to log out of CAS.
func (c *Client) RedirectToLogout(w http.ResponseWriter, r *http.Request) {
	u, err := c.LogoutUrlForRequest(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if glog.V(2) {
		glog.Info("Logging out, redirecting client to %v with status %v",
			u, http.StatusFound)
	}

	c.clearSession(w, r)
	http.Redirect(w, r, u, http.StatusFound)
}

// RedirectToLogout replies to the request with a redirect URL to authenticate with CAS.
func (c *Client) RedirectToLogin(w http.ResponseWriter, r *http.Request) {
	u, err := c.LoginUrlForRequest(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if glog.V(2) {
		glog.Infof("Redirecting client to %v with status %v", u, http.StatusFound)
	}

	http.Redirect(w, r, u, http.StatusFound)
}

// validateTicket performs CAS ticket validation with the given ticket and service.
func (c *Client) validateTicket(ticket string, service *http.Request) error {
	serviceUrl, err := requestURL(service)
	if err != nil {
		return err
	}

	success, err := c.stValidator.ValidateTicket(serviceUrl, ticket)
	if err != nil {
		return err
	}

	if err := c.tickets.Write(ticket, success); err != nil {
		return err
	}

	return nil
}

// getSession finds or creates a session for the request.
//
// A cookie is set on the response if one is not provided with the request.
// Validates the ticket if the URL parameter is provided.
func (c *Client) getSession(w http.ResponseWriter, r *http.Request) {
	cookie := c.getCookie(w, r)

	if s, ok := c.sessions[cookie.Value]; ok {
		if t, err := c.tickets.Read(s); err == nil {
			if glog.V(1) {
				glog.Infof("Re-used ticket %s for %s", s, t.User)
			}

			setAuthenticationResponse(r, t)
			return
		} else {
			if glog.V(2) {
				glog.Infof("Ticket %v not in %T: %v", s, c.tickets, err)
			}

			if glog.V(1) {
				glog.Infof("Clearing ticket %s, no longer exists in ticket store", s)
			}

			clearCookie(w, cookie)
		}
	}

	if ticket := r.URL.Query().Get("ticket"); ticket != "" {
		if err := c.validateTicket(ticket, r); err != nil {
			if glog.V(2) {
				glog.Infof("Error validating ticket: %v", err)
			}
			return // allow ServeHTTP()
		}

		c.setSession(cookie.Value, ticket)

		if t, err := c.tickets.Read(ticket); err == nil {
			if glog.V(1) {
				glog.Infof("Validated ticket %s for %s", ticket, t.User)
			}

			setAuthenticationResponse(r, t)
			return
		} else {
			if glog.V(2) {
				glog.Infof("Ticket %v not in %T: %v", ticket, c.tickets, err)
			}

			if glog.V(1) {
				glog.Infof("Clearing ticket %s, no longer exists in ticket store", ticket)
			}

			clearCookie(w, cookie)
		}
	}
}

// getCookie finds or creates the session cookie on the response.
func (c *Client) getCookie(w http.ResponseWriter, r *http.Request) *http.Cookie {
	cookie, err := r.Cookie(sessionCookieName)
	if err != nil {
		// NOTE: Intentionally not enabling HttpOnly so the cookie can
		//       still be used by Ajax requests.
		cookie = &http.Cookie{
			Name:     sessionCookieName,
			Value:    newSessionID(),
			Path:     c.cookie.Path,
			Domain:   c.cookie.Domain,
			MaxAge:   c.cookie.MaxAge,
			HttpOnly: c.cookie.HttpOnly,
			Secure:   c.cookie.Secure,
		}

		if glog.V(2) {
			glog.Infof("Setting %v cookie with value: %v", cookie.Name, cookie.Value)
		}

		r.AddCookie(cookie) // so we can find it later if required
		http.SetCookie(w, cookie)
	}

	return cookie
}

// newSessionId generates a new opaque session identifier for use in the cookie.
func newSessionID() string {
	const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	// generate 64 character string
	bytes := make([]byte, 64)
	rand.Read(bytes)

	for k, v := range bytes {
		bytes[k] = alphabet[v%byte(len(alphabet))]
	}

	return string(bytes)
}

// clearCookie invalidates and removes the cookie from the client.
func clearCookie(w http.ResponseWriter, c *http.Cookie) {
	c.MaxAge = -1
	http.SetCookie(w, c)
}

// setSession stores the session id to ticket mapping in the Client.
func (c *Client) setSession(id string, ticket string) {
	if glog.V(2) {
		glog.Infof("Recording session, %v -> %v", id, ticket)
	}

	c.mu.Lock()
	c.sessions[id] = ticket
	c.mu.Unlock()
}

// clearSession removes the session from the client and clears the cookie.
func (c *Client) clearSession(w http.ResponseWriter, r *http.Request) {
	cookie := c.getCookie(w, r)

	if s, ok := c.sessions[cookie.Value]; ok {
		if err := c.tickets.Delete(s); err != nil {
			fmt.Printf("Failed to remove %v from %T: %v\n", cookie.Value, c.tickets, err)
			if glog.V(2) {
				glog.Errorf("Failed to remove %v from %T: %v", cookie.Value, c.tickets, err)
			}
		}

		c.deleteSession(s)
	}

	clearCookie(w, cookie)
}

// deleteSession removes the session from the client
func (c *Client) deleteSession(id string) {
	c.mu.Lock()
	delete(c.sessions, id)
	c.mu.Unlock()
}

// findAndDeleteSessionWithTicket removes the session from the client via Single Log Out
//
// When a Single Log Out request is received we receive the service ticket identidier. This
// function loops through the sessions to find the matching session id. Once retrieved the
// session is removed from the client. When the session is next requested the getSession
// function will notice the session is invalid and revalidate the user.
func (c *Client) findAndDeleteSessionWithTicket(ticket string) {
	var id string
	for s, t := range c.sessions {
		if t == ticket {
			id = s
			break
		}
	}

	if id == "" {
		return
	}

	c.deleteSession(id)
}
<|endoftext|>


<commit_before>package circuit

import (
	"io"
	"net/http"
	"net/url"
	"time"
)

// HTTPClient is a wrapper around http.Client that provides circuit breaker capabilities.
//
// By default, the client will use its defaultBreaker. A BreakerLookup function may be
// provided to allow different breakers to be used based on the circumstance. See the
// implementation of NewHostBasedHTTPClient for an example of this.
type HTTPClient struct {
	Client         *http.Client
	BreakerTripped func()
	BreakerReset   func()
	BreakerLookup  func(*HTTPClient, interface{}) *Breaker
	Panel          *Panel
	timeout        time.Duration
}

var defaultBreakerName = "_default"

// NewHTTPClient provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions. Specifying 0 for timeout will
// give a breaker that does not check for time outs.
func NewHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
	breaker := NewThresholdBreaker(threshold)
	return NewHTTPClientWithBreaker(breaker, timeout, client)
}

// NewHostBasedHTTPClient provides a circuit breaker wrapper around http.Client. This
// client will use one circuit breaker per host parsed from the request URL. This allows
// you to use a single HTTPClient for multiple hosts with one host's breaker not affecting
// the other hosts.
func NewHostBasedHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
	brclient := NewHTTPClient(timeout, threshold, client)

	brclient.BreakerLookup = func(c *HTTPClient, val interface{}) *Breaker {
		rawURL := val.(string)
		parsedURL, err := url.Parse(rawURL)
		if err != nil {
			breaker, _ := c.Panel.Get(defaultBreakerName)
			return breaker
		}
		host := parsedURL.Host

		cb, ok := c.Panel.Get(host)
		if !ok {
			cb = NewThresholdBreaker(threshold)
			c.Panel.Add(host, cb)
		}

		return cb
	}

	return brclient
}

// NewHTTPClientWithBreaker provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions using the provided Breaker.
func NewHTTPClientWithBreaker(breaker *Breaker, timeout time.Duration, client *http.Client) *HTTPClient {
	if client == nil {
		client = &http.Client{}
	}

	panel := NewPanel()
	panel.Add(defaultBreakerName, breaker)

	brclient := &HTTPClient{Client: client, Panel: panel, timeout: timeout}
	brclient.BreakerLookup = func(c *HTTPClient, val interface{}) *Breaker {
		cb, _ := c.Panel.Get(defaultBreakerName)
		return cb
	}

	events := breaker.Subscribe()
	go func() {
		event := <-events
		switch event {
		case BreakerTripped:
			brclient.runBreakerTripped()
		case BreakerReset:
			brclient.runBreakerReset()
		}
	}()

	return brclient
}

// Do wraps http.Client Do()
func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
	var resp *http.Response
	var err error
	breaker := c.breakerLookup(req.URL.String())
	breaker.Call(func() error {
		resp, err = c.Client.Do(req)
		return err
	}, c.timeout)
	return resp, err
}

// Get wraps http.Client Get()
func (c *HTTPClient) Get(url string) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Get(url)
		resp = aresp
		return err
	}, c.timeout)
	return resp, err
}

// Head wraps http.Client Head()
func (c *HTTPClient) Head(url string) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Head(url)
		resp = aresp
		return err
	}, c.timeout)
	return resp, err
}

// Post wraps http.Client Post()
func (c *HTTPClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Post(url, bodyType, body)
		resp = aresp
		return err
	}, c.timeout)
	return resp, err
}

// PostForm wraps http.Client PostForm()
func (c *HTTPClient) PostForm(url string, data url.Values) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.PostForm(url, data)
		resp = aresp
		return err
	}, c.timeout)
	return resp, err
}

func (c *HTTPClient) breakerLookup(val interface{}) *Breaker {
	if c.BreakerLookup != nil {
		return c.BreakerLookup(c, val)
	}
	cb, _ := c.Panel.Get(defaultBreakerName)
	return cb
}

func (c *HTTPClient) runBreakerTripped() {
	if c.BreakerTripped != nil {
		c.BreakerTripped()
	}
}

func (c *HTTPClient) runBreakerReset() {
	if c.BreakerReset != nil {
		c.BreakerReset()
	}
}
<commit_msg>Properly return the error when client.Do() times out<commit_after>package circuit

import (
	"io"
	"net/http"
	"net/url"
	"time"
)

// HTTPClient is a wrapper around http.Client that provides circuit breaker capabilities.
//
// By default, the client will use its defaultBreaker. A BreakerLookup function may be
// provided to allow different breakers to be used based on the circumstance. See the
// implementation of NewHostBasedHTTPClient for an example of this.
type HTTPClient struct {
	Client         *http.Client
	BreakerTripped func()
	BreakerReset   func()
	BreakerLookup  func(*HTTPClient, interface{}) *Breaker
	Panel          *Panel
	timeout        time.Duration
}

var defaultBreakerName = "_default"

// NewHTTPClient provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions. Specifying 0 for timeout will
// give a breaker that does not check for time outs.
func NewHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
	breaker := NewThresholdBreaker(threshold)
	return NewHTTPClientWithBreaker(breaker, timeout, client)
}

// NewHostBasedHTTPClient provides a circuit breaker wrapper around http.Client. This
// client will use one circuit breaker per host parsed from the request URL. This allows
// you to use a single HTTPClient for multiple hosts with one host's breaker not affecting
// the other hosts.
func NewHostBasedHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
	brclient := NewHTTPClient(timeout, threshold, client)

	brclient.BreakerLookup = func(c *HTTPClient, val interface{}) *Breaker {
		rawURL := val.(string)
		parsedURL, err := url.Parse(rawURL)
		if err != nil {
			breaker, _ := c.Panel.Get(defaultBreakerName)
			return breaker
		}
		host := parsedURL.Host

		cb, ok := c.Panel.Get(host)
		if !ok {
			cb = NewThresholdBreaker(threshold)
			c.Panel.Add(host, cb)
		}

		return cb
	}

	return brclient
}

// NewHTTPClientWithBreaker provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions using the provided Breaker.
func NewHTTPClientWithBreaker(breaker *Breaker, timeout time.Duration, client *http.Client) *HTTPClient {
	if client == nil {
		client = &http.Client{}
	}

	panel := NewPanel()
	panel.Add(defaultBreakerName, breaker)

	brclient := &HTTPClient{Client: client, Panel: panel, timeout: timeout}
	brclient.BreakerLookup = func(c *HTTPClient, val interface{}) *Breaker {
		cb, _ := c.Panel.Get(defaultBreakerName)
		return cb
	}

	events := breaker.Subscribe()
	go func() {
		event := <-events
		switch event {
		case BreakerTripped:
			brclient.runBreakerTripped()
		case BreakerReset:
			brclient.runBreakerReset()
		}
	}()

	return brclient
}

// Do wraps http.Client Do()
func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
	var resp *http.Response
	var err error
	breaker := c.breakerLookup(req.URL.String())
	err = breaker.Call(func() error {
		resp, err = c.Client.Do(req)
		return err
	}, c.timeout)
	return resp, err
}

// Get wraps http.Client Get()
func (c *HTTPClient) Get(url string) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Get(url)
		resp = aresp
		return err
	}, c.timeout)
	return resp, err
}

// Head wraps http.Client Head()
func (c *HTTPClient) Head(url string) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Head(url)
		resp = aresp
		return err
	}, c.timeout)
	return resp, err
}

// Post wraps http.Client Post()
func (c *HTTPClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Post(url, bodyType, body)
		resp = aresp
		return err
	}, c.timeout)
	return resp, err
}

// PostForm wraps http.Client PostForm()
func (c *HTTPClient) PostForm(url string, data url.Values) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.PostForm(url, data)
		resp = aresp
		return err
	}, c.timeout)
	return resp, err
}

func (c *HTTPClient) breakerLookup(val interface{}) *Breaker {
	if c.BreakerLookup != nil {
		return c.BreakerLookup(c, val)
	}
	cb, _ := c.Panel.Get(defaultBreakerName)
	return cb
}

func (c *HTTPClient) runBreakerTripped() {
	if c.BreakerTripped != nil {
		c.BreakerTripped()
	}
}

func (c *HTTPClient) runBreakerReset() {
	if c.BreakerReset != nil {
		c.BreakerReset()
	}
}
<|endoftext|>


<commit_before>// MPD (Music Player Daemon) client

package main

import (
	"bufio";
	"fmt";
	"net";
	"os";
	"strconv";
	"strings";
)

type Client struct {
	conn	net.Conn;
	rw	*bufio.ReadWriter;
}

type Song struct {
	file		string;
	time		int;
	title		string;
	artist		string;
	album		string;
	track		int;
	performer	string;
	pos		int;
	id		int;
}

func Connect(addr string) (c *Client, err os.Error) {
	conn, err := net.Dial("tcp", "", addr);
	if err != nil {
		return nil, err
	}
	c = new(Client);
	c.rw = bufio.NewReadWriter(bufio.NewReader(conn),
		bufio.NewWriter(conn));
	line, err := c.readLine();
	if err != nil {
		return nil, err
	}
	if line[0:6] != "OK MPD" {
		return nil, os.NewError("no greeting")
	}
	return;
}

func (c *Client) Disconnect() {
	if c.conn != nil {
		c.conn.Close()
	}
}

func (c *Client) readLine() (line string, err os.Error) {
	line, err = c.rw.ReadString('\n');
	if err != nil {
		return
	}
	if line[len(line)-1] == '\n' {
		line = line[0 : len(line)-1]
	}
	fmt.Println("-->", line);
	return;
}

func (c *Client) writeLine(line string) (err os.Error) {
	fmt.Println("<--", line);
	_, err = c.rw.Write(strings.Bytes(line + "\n"));
	// TODO: try again if # written != len(buf)
	c.rw.Flush();
	return;
}

func (c *Client) getAttrs() (attrs map[string]string, err os.Error) {
	attrs = make(map[string]string);
	for {
		line, err := c.readLine();
		if err != nil {
			return nil, err
		}
		if line == "OK" {
			break
		}
		i := strings.Index(line, ": ");
		if i < 0 {
			return nil, os.NewError("can't parse line: " + line)
		}
		key := line[0:i];
		attrs[key] = line[i+2:];
	}
	return;
}

func atoi(s string) (n int) {
	n, _ = strconv.Atoi(s);
	return;
}

func (c *Client) CurrentSong() (song *Song, err os.Error) {
	c.writeLine("currentsong");
	song = new(Song);
	attrs, err := c.getAttrs();
	if err != nil {
		return nil, err
	}
	for key, val := range attrs {
		switch key {
		case "file":
			song.file = val
		case "Time":
			song.time = atoi(val)
		case "Title":
			song.title = val
		case "Artist":
			song.artist = val
		case "Album":
			song.album = val
		case "Track":
			song.track = atoi(val)
		case "Performer":
			song.performer = val
		case "Pos":
			song.pos = atoi(val)
		case "Id":
			song.id = atoi(val)
		}
	}
	return;
}

func main() {
	cli, err := Connect("127.0.0.1:6600");
	if err != nil {
		goto err
	}
	song, err := cli.CurrentSong();
	if err != nil {
		goto err
	}
	fmt.Println(song);
	cli.Disconnect();
	return;
err:
	fmt.Fprintln(os.Stderr, err);
	os.Exit(1);
}
<commit_msg>don't parse key/value pairs received<commit_after>// MPD (Music Player Daemon) client
// Protocol Reference: http://www.musicpd.org/doc/protocol/index.html

package main

import (
	"bufio";
	"fmt";
	"net";
	"os";
	"strings";
)

type Client struct {
	conn	net.Conn;
	rw	*bufio.ReadWriter;
}

type Attrs	map[string]string

func Connect(addr string) (c *Client, err os.Error) {
	conn, err := net.Dial("tcp", "", addr);
	if err != nil {
		return nil, err
	}
	c = new(Client);
	c.rw = bufio.NewReadWriter(bufio.NewReader(conn),
		bufio.NewWriter(conn));
	line, err := c.readLine();
	if err != nil {
		return nil, err
	}
	if line[0:6] != "OK MPD" {
		return nil, os.NewError("no greeting")
	}
	return;
}

func (c *Client) Disconnect() {
	if c.conn != nil {
		c.conn.Close()
	}
}

func (c *Client) readLine() (line string, err os.Error) {
	line, err = c.rw.ReadString('\n');
	if err != nil {
		return
	}
	if line[len(line)-1] == '\n' {
		line = line[0 : len(line)-1]
	}
	fmt.Println("-->", line);
	return;
}

func (c *Client) writeLine(line string) (err os.Error) {
	fmt.Println("<--", line);
	_, err = c.rw.Write(strings.Bytes(line + "\n"));
	// TODO: try again if # written != len(buf)
	c.rw.Flush();
	return;
}

func (c *Client) getAttrs() (attrs Attrs, err os.Error) {
	attrs = make(Attrs);
	for {
		line, err := c.readLine();
		if err != nil {
			return nil, err
		}
		if line == "OK" {
			break
		}
		i := strings.Index(line, ": ");
		if i < 0 {
			return nil, os.NewError("can't parse line: " + line)
		}
		key := line[0:i];
		attrs[key] = line[i+2:];
	}
	return;
}

func (c *Client) CurrentSong() (Attrs, os.Error) {
	c.writeLine("currentsong");
	return c.getAttrs();
}

func (c *Client) Status() (Attrs, os.Error) {
	c.writeLine("status");
	return c.getAttrs();
}

func main() {
	cli, err := Connect("127.0.0.1:6600");
	if err != nil {
		goto err
	}
	song, err := cli.CurrentSong();
	if err != nil {
		goto err
	}
	fmt.Println("current song:", song);
	status, err := cli.Status();
	if err != nil {
		goto err;
	}
	fmt.Println("status:", status);
	cli.Disconnect();
	return;
err:
	fmt.Fprintln(os.Stderr, err);
	os.Exit(1);
}
<|endoftext|>


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

package main

import (
	"fmt"
	"math"
	"runtime"
	"time"

	"github.com/thinkofdeath/steven/protocol"
	"github.com/thinkofdeath/steven/render"
	"github.com/thinkofdeath/steven/type/direction"
	"github.com/thinkofdeath/steven/type/vmath"
)

const (
	playerHeight = 1.62
)

var Client = ClientState{
	Bounds: vmath.AABB{
		Min: vmath.Vector3{-0.3, 0, -0.3},
		Max: vmath.Vector3{0.3, 1.8, 0.3},
	},
}

type ClientState struct {
	X, Y, Z    float64
	Yaw, Pitch float64

	Jumping  bool
	VSpeed   float64
	KeyState [4]bool
	OnGround bool

	GameMode gameMode
	HardCore bool

	Bounds vmath.AABB

	positionText  *render.UIText
	directionText *render.UIText
	memoryText    *render.UIText

	fps       int
	frames    int
	lastCount time.Time
	fpsText   *render.UIText

	chat ChatUI
}

var memoryStats runtime.MemStats

func (c *ClientState) renderTick(delta float64) {
	c.frames++

	forward, yaw := c.calculateMovement()

	if c.GameMode.Fly() {
		c.X += forward * math.Cos(yaw) * -math.Cos(c.Pitch) * delta * 0.2
		c.Z -= forward * math.Sin(yaw) * -math.Cos(c.Pitch) * delta * 0.2
		c.Y -= forward * math.Sin(c.Pitch) * delta * 0.2
	} else {
		c.X += forward * math.Cos(yaw) * delta * 0.1
		c.Z -= forward * math.Sin(yaw) * delta * 0.1
		if !c.OnGround {
			c.VSpeed -= 0.01 * delta
			if c.VSpeed < -0.3 {
				c.VSpeed = -0.3
			}
		} else if c.Jumping {
			c.VSpeed = 0.15
		} else {
			c.VSpeed = 0
		}
		c.Y += c.VSpeed * delta
	}

	if !c.GameMode.NoClip() && chunkMap[chunkPosition{int(c.X) >> 4, int(c.Z) >> 4}] != nil {
		cy := c.Y
		cz := c.Z
		c.Y = render.Camera.Y - playerHeight
		c.Z = render.Camera.Z

		// We handle each axis separately to allow for a sliding
		// effect when pushing up against walls.

		bounds, _ := c.checkCollisions(c.Bounds)
		c.X = bounds.Min.X + 0.3

		c.Z = cz
		bounds, _ = c.checkCollisions(c.Bounds)
		c.Z = bounds.Min.Z + 0.3

		c.Y = cy
		bounds, _ = c.checkCollisions(c.Bounds)
		c.Y = bounds.Min.Y

		ground := vmath.AABB{
			Min: vmath.Vector3{-0.3, -0.05, -0.3},
			Max: vmath.Vector3{0.3, 0, 0.3},
		}
		_, c.OnGround = c.checkCollisions(ground)
	}

	c.copyToCamera()

	// Temp displays

	c.positionText = setText(c.positionText,
		fmt.Sprintf("X: %.2f, Y: %.2f, Z: %.2f", c.X, c.Y, c.Z),
		5, 5, 255, 255, 255,
	)
	c.directionText = setText(c.directionText,
		fmt.Sprintf("Facing: %s", c.facingDirection()),
		5, 23, 255, 255, 255,
	)

	runtime.ReadMemStats(&memoryStats)
	text := fmt.Sprintf("%s", formatMemory(memoryStats.Alloc))
	c.memoryText = setText(c.memoryText, text, 800-5-float64(render.SizeOfString(text)), 23, 255, 255, 255)

	now := time.Now()
	if now.Sub(c.lastCount) > time.Second {
		c.lastCount = now
		c.fps = c.frames
		c.frames = 0
	}
	text = fmt.Sprintf("FPS: %d", c.fps)
	c.fpsText = setText(c.fpsText, text, 800-5-float64(render.SizeOfString(text)), 5, 255, 255, 255)

	c.chat.render(delta)
}

func setText(txt *render.UIText, str string, x, y float64, rr, gg, bb int) *render.UIText {
	if txt != nil {
		txt.Free()
	}
	return render.AddUIText(str, x, y, rr, gg, bb)
}

func (c *ClientState) calculateMovement() (float64, float64) {
	forward := 0.0
	yaw := c.Yaw - math.Pi/2
	if c.KeyState[KeyForward] || c.KeyState[KeyBackwards] {
		forward = 1
		if c.KeyState[KeyBackwards] {
			yaw += math.Pi
		}
	}
	change := 0.0
	if c.KeyState[KeyLeft] {
		change = (math.Pi / 2) / (math.Abs(forward) + 1)
	}
	if c.KeyState[KeyRight] {
		change = -(math.Pi / 2) / (math.Abs(forward) + 1)
	}
	if c.KeyState[KeyRight] || c.KeyState[KeyLeft] {
		forward = 1
	}
	if c.KeyState[KeyBackwards] {
		yaw -= change
	} else {
		yaw += change
	}
	return forward, yaw
}

func (c *ClientState) facingDirection() direction.Type {
	var viewVector vmath.Vector3
	viewVector.X = math.Cos(c.Yaw-math.Pi/2) * -math.Cos(c.Pitch)
	viewVector.Z = -math.Sin(c.Yaw-math.Pi/2) * -math.Cos(c.Pitch)
	viewVector.Y = -math.Sin(c.Pitch)
	for _, d := range direction.Values {
		if d.AsVector().Dot(viewVector) > 0.5 {
			return d
		}
	}
	return direction.Invalid
}

func formatMemory(alloc uint64) string {
	const letters = "BKMG"
	i := 0
	for {
		check := alloc
		check >>= 10
		if check == 0 {
			break
		}
		alloc = check
		i++
	}
	l := string(letters[i])
	if l != "B" {
		l += "B"
	}
	return fmt.Sprintf("%d%s", alloc, l)
}

func (c *ClientState) checkCollisions(bounds vmath.AABB) (vmath.AABB, bool) {
	bounds.Shift(c.X, c.Y, c.Z)

	dir := &vmath.Vector3{
		X: -(render.Camera.X - c.X),
		Y: -(render.Camera.Y - playerHeight - c.Y),
		Z: -(render.Camera.Z - c.Z),
	}

	minX, minY, minZ := int(bounds.Min.X-1), int(bounds.Min.Y-1), int(bounds.Min.Z-1)
	maxX, maxY, maxZ := int(bounds.Max.X+1), int(bounds.Max.Y+1), int(bounds.Max.Z+1)

	hit := false
	for y := minY; y < maxY; y++ {
		for z := minZ; z < maxZ; z++ {
			for x := minX; x < maxX; x++ {
				b := chunkMap.Block(x, y, z)

				if b.Collidable() {
					for _, bb := range b.CollisionBounds() {
						bb.Shift(float64(x), float64(y), float64(z))
						if bb.Intersects(&bounds) {
							bounds.MoveOutOf(&bb, dir)
							hit = true
						}
					}
				}
			}
		}
	}
	return bounds, hit
}

func (c *ClientState) copyToCamera() {
	render.Camera.X = c.X
	render.Camera.Y = c.Y + playerHeight
	render.Camera.Z = c.Z
	render.Camera.Yaw = c.Yaw
	render.Camera.Pitch = c.Pitch
}

func (c *ClientState) tick() {
	// Now you may be wondering why we have to spam movement
	// packets (any of the Player* move/look packets) 20 times
	// a second instead of only sending when something changes.
	// This is because the server only ticks certain parts of
	// the player when a movement packet is recieved meaning
	// if we sent them any slower health regen would be slowed
	// down as well and various other things too (potions, speed
	// hack check). This also has issues if we send them too
	// fast as well since we will regen health at much faster
	// rates than normal players and some modded servers will
	// (correctly) detect this as cheating. Its Minecraft
	// what did you expect?
	// TODO(Think) Use the smaller packets when possible
	writeChan <- &protocol.PlayerPositionLook{
		X:        c.X,
		Y:        c.Y,
		Z:        c.Z,
		Yaw:      float32(-c.Yaw * (180 / math.Pi)),
		Pitch:    float32((-c.Pitch - math.Pi) * (180 / math.Pi)),
		OnGround: c.OnGround,
	}
}

type gameMode int

const (
	gmSurvival gameMode = iota
	gmCreative
	gmAdventure
	gmSpecator
)

func (g gameMode) Fly() bool {
	switch g {
	case gmCreative, gmSpecator:
		return true
	}
	return false
}

func (g gameMode) NoClip() bool {
	switch g {
	case gmSpecator:
		return true
	}
	return false
}

type teleportFlag byte

const (
	teleportRelX teleportFlag = 1 << iota
	teleportRelY
	teleportRelZ
	teleportRelYaw
	teleportRelPitch
)

func calculateTeleport(flag teleportFlag, flags byte, base, val float64) float64 {
	if flags&byte(flag) != 0 {
		return base + val
	}
	return val
}
<commit_msg>steven: fix collision bugs<commit_after>// Copyright 2015 Matthew Collins
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"math"
	"runtime"
	"time"

	"github.com/thinkofdeath/steven/protocol"
	"github.com/thinkofdeath/steven/render"
	"github.com/thinkofdeath/steven/type/direction"
	"github.com/thinkofdeath/steven/type/vmath"
)

const (
	playerHeight = 1.62
)

var Client = ClientState{
	Bounds: vmath.AABB{
		Min: vmath.Vector3{-0.3, 0, -0.3},
		Max: vmath.Vector3{0.3, 1.8, 0.3},
	},
}

type ClientState struct {
	X, Y, Z    float64
	Yaw, Pitch float64

	Jumping  bool
	VSpeed   float64
	KeyState [4]bool
	OnGround bool

	GameMode gameMode
	HardCore bool

	Bounds vmath.AABB

	positionText  *render.UIText
	directionText *render.UIText
	memoryText    *render.UIText

	fps       int
	frames    int
	lastCount time.Time
	fpsText   *render.UIText

	chat ChatUI
}

var memoryStats runtime.MemStats

func (c *ClientState) renderTick(delta float64) {
	c.frames++

	forward, yaw := c.calculateMovement()

	if c.GameMode.Fly() {
		c.X += forward * math.Cos(yaw) * -math.Cos(c.Pitch) * delta * 0.2
		c.Z -= forward * math.Sin(yaw) * -math.Cos(c.Pitch) * delta * 0.2
		c.Y -= forward * math.Sin(c.Pitch) * delta * 0.2
	} else {
		c.X += forward * math.Cos(yaw) * delta * 0.1
		c.Z -= forward * math.Sin(yaw) * delta * 0.1
		if !c.OnGround {
			c.VSpeed -= 0.01 * delta
			if c.VSpeed < -0.3 {
				c.VSpeed = -0.3
			}
		} else if c.Jumping {
			c.VSpeed = 0.15
		} else {
			c.VSpeed = 0
		}
		c.Y += c.VSpeed * delta
	}

	if !c.GameMode.NoClip() && chunkMap[chunkPosition{int(c.X) >> 4, int(c.Z) >> 4}] != nil {
		cy := c.Y
		cz := c.Z
		c.Y = render.Camera.Y - playerHeight
		c.Z = render.Camera.Z

		// We handle each axis separately to allow for a sliding
		// effect when pushing up against walls.

		bounds, _ := c.checkCollisions(c.Bounds)
		c.X = bounds.Min.X + 0.3
		c.copyToCamera()

		c.Z = cz
		bounds, _ = c.checkCollisions(c.Bounds)
		c.Z = bounds.Min.Z + 0.3
		c.copyToCamera()

		c.Y = cy
		bounds, _ = c.checkCollisions(c.Bounds)
		c.Y = bounds.Min.Y

		ground := vmath.AABB{
			Min: vmath.Vector3{-0.3, -0.05, -0.3},
			Max: vmath.Vector3{0.3, 0, 0.3},
		}
		_, c.OnGround = c.checkCollisions(ground)
	}

	c.copyToCamera()

	// Temp displays

	c.positionText = setText(c.positionText,
		fmt.Sprintf("X: %.2f, Y: %.2f, Z: %.2f", c.X, c.Y, c.Z),
		5, 5, 255, 255, 255,
	)
	c.directionText = setText(c.directionText,
		fmt.Sprintf("Facing: %s", c.facingDirection()),
		5, 23, 255, 255, 255,
	)

	runtime.ReadMemStats(&memoryStats)
	text := fmt.Sprintf("%s", formatMemory(memoryStats.Alloc))
	c.memoryText = setText(c.memoryText, text, 800-5-float64(render.SizeOfString(text)), 23, 255, 255, 255)

	now := time.Now()
	if now.Sub(c.lastCount) > time.Second {
		c.lastCount = now
		c.fps = c.frames
		c.frames = 0
	}
	text = fmt.Sprintf("FPS: %d", c.fps)
	c.fpsText = setText(c.fpsText, text, 800-5-float64(render.SizeOfString(text)), 5, 255, 255, 255)

	c.chat.render(delta)
}

func setText(txt *render.UIText, str string, x, y float64, rr, gg, bb int) *render.UIText {
	if txt != nil {
		txt.Free()
	}
	return render.AddUIText(str, x, y, rr, gg, bb)
}

func (c *ClientState) calculateMovement() (float64, float64) {
	forward := 0.0
	yaw := c.Yaw - math.Pi/2
	if c.KeyState[KeyForward] || c.KeyState[KeyBackwards] {
		forward = 1
		if c.KeyState[KeyBackwards] {
			yaw += math.Pi
		}
	}
	change := 0.0
	if c.KeyState[KeyLeft] {
		change = (math.Pi / 2) / (math.Abs(forward) + 1)
	}
	if c.KeyState[KeyRight] {
		change = -(math.Pi / 2) / (math.Abs(forward) + 1)
	}
	if c.KeyState[KeyRight] || c.KeyState[KeyLeft] {
		forward = 1
	}
	if c.KeyState[KeyBackwards] {
		yaw -= change
	} else {
		yaw += change
	}
	return forward, yaw
}

func (c *ClientState) facingDirection() direction.Type {
	var viewVector vmath.Vector3
	viewVector.X = math.Cos(c.Yaw-math.Pi/2) * -math.Cos(c.Pitch)
	viewVector.Z = -math.Sin(c.Yaw-math.Pi/2) * -math.Cos(c.Pitch)
	viewVector.Y = -math.Sin(c.Pitch)
	for _, d := range direction.Values {
		if d.AsVector().Dot(viewVector) > 0.5 {
			return d
		}
	}
	return direction.Invalid
}

func formatMemory(alloc uint64) string {
	const letters = "BKMG"
	i := 0
	for {
		check := alloc
		check >>= 10
		if check == 0 {
			break
		}
		alloc = check
		i++
	}
	l := string(letters[i])
	if l != "B" {
		l += "B"
	}
	return fmt.Sprintf("%d%s", alloc, l)
}

func (c *ClientState) checkCollisions(bounds vmath.AABB) (vmath.AABB, bool) {
	bounds.Shift(c.X, c.Y, c.Z)

	dir := &vmath.Vector3{
		X: -(render.Camera.X - c.X),
		Y: -(render.Camera.Y - playerHeight - c.Y),
		Z: -(render.Camera.Z - c.Z),
	}

	minX, minY, minZ := int(bounds.Min.X-1), int(bounds.Min.Y-1), int(bounds.Min.Z-1)
	maxX, maxY, maxZ := int(bounds.Max.X+1), int(bounds.Max.Y+1), int(bounds.Max.Z+1)

	hit := false
	for y := minY; y < maxY; y++ {
		for z := minZ; z < maxZ; z++ {
			for x := minX; x < maxX; x++ {
				b := chunkMap.Block(x, y, z)

				if b.Collidable() {
					for _, bb := range b.CollisionBounds() {
						bb.Shift(float64(x), float64(y), float64(z))
						if bb.Intersects(&bounds) {
							bounds.MoveOutOf(&bb, dir)
							hit = true
						}
					}
				}
			}
		}
	}
	return bounds, hit
}

func (c *ClientState) copyToCamera() {
	render.Camera.X = c.X
	render.Camera.Y = c.Y + playerHeight
	render.Camera.Z = c.Z
	render.Camera.Yaw = c.Yaw
	render.Camera.Pitch = c.Pitch
}

func (c *ClientState) tick() {
	// Now you may be wondering why we have to spam movement
	// packets (any of the Player* move/look packets) 20 times
	// a second instead of only sending when something changes.
	// This is because the server only ticks certain parts of
	// the player when a movement packet is recieved meaning
	// if we sent them any slower health regen would be slowed
	// down as well and various other things too (potions, speed
	// hack check). This also has issues if we send them too
	// fast as well since we will regen health at much faster
	// rates than normal players and some modded servers will
	// (correctly) detect this as cheating. Its Minecraft
	// what did you expect?
	// TODO(Think) Use the smaller packets when possible
	writeChan <- &protocol.PlayerPositionLook{
		X:        c.X,
		Y:        c.Y,
		Z:        c.Z,
		Yaw:      float32(-c.Yaw * (180 / math.Pi)),
		Pitch:    float32((-c.Pitch - math.Pi) * (180 / math.Pi)),
		OnGround: c.OnGround,
	}
}

type gameMode int

const (
	gmSurvival gameMode = iota
	gmCreative
	gmAdventure
	gmSpecator
)

func (g gameMode) Fly() bool {
	switch g {
	case gmCreative, gmSpecator:
		return true
	}
	return false
}

func (g gameMode) NoClip() bool {
	switch g {
	case gmSpecator:
		return true
	}
	return false
}

type teleportFlag byte

const (
	teleportRelX teleportFlag = 1 << iota
	teleportRelY
	teleportRelZ
	teleportRelYaw
	teleportRelPitch
)

func calculateTeleport(flag teleportFlag, flags byte, base, val float64) float64 {
	if flags&byte(flag) != 0 {
		return base + val
	}
	return val
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"unicode"
)

type line struct {
	label     int
	statement string
}
type labelMap map[int]int
type corrector func(string) string

var (
	usageDescription     = makeUsageDescription()
	linePattern          = regexp.MustCompile(`^\s*(\d+)?(.*?)\s*$`)
	jumpPattern          = regexp.MustCompile(`\bGOTO\s+\d+$`)
	conditionJumpPattern = regexp.MustCompile(`\bTHEN\s+\d+$`)
	labelPattern         = regexp.MustCompile(`\d+$`)
)

func main() {
	filename := processArguments()
	code := readFile(filename)
	cuttedCode := removeEndingWhitespaces(code)
	rawLines := splitLines(cuttedCode)
	parsedLines := parseLines(rawLines)
	labels := makelabels(parsedLines)
	renumberedLines := renumberLines(parsedLines, labels)
	printLines(renumberedLines)
}

func makeUsageDescription() string {
	_, scriptPath, _, ok := runtime.Caller(0)
	if !ok {
		return ""
	}

	return fmt.Sprintf(
		"Usage:\n"+
			"\tgo run %s [options] <filename>\n"+
			"\n"+
			"Options:\n"+
			"\t-h, --help - show help.\n",
		filepath.Base(scriptPath),
	)
}

func processArguments() string {
	testArguments()

	firstArgument := os.Args[1]
	processHelpOption(firstArgument)

	return firstArgument
}

func testArguments() {
	numberOfArguments := len(os.Args)
	if numberOfArguments < 2 {
		fmt.Print(
			"Error: filename not specified.\n" +
				"\n" +
				usageDescription,
		)

		os.Exit(1)
	}
}

func processHelpOption(firstArgument string) {
	if firstArgument == "-h" || firstArgument == "--help" {
		fmt.Print(usageDescription)
		os.Exit(0)
	}
}

func readFile(filename string) string {
	code, error := ioutil.ReadFile(filename)
	if error != nil {
		fmt.Printf(
			"Error: unable to read file \"%s\" (%v).\n",
			filename,
			error,
		)

		os.Exit(1)
	}

	return string(code)
}

func removeEndingWhitespaces(code string) string {
	return strings.TrimRightFunc(code, unicode.IsSpace)
}

func splitLines(code string) []string {
	return strings.Split(code, "\n")
}

func parseLines(lines []string) []line {
	var parsedLines []line
	for index, content := range lines {
		parsedLine := parseLine(index, content)
		parsedLines = append(parsedLines, parsedLine)
	}

	return parsedLines
}

func parseLine(index int, content string) line {
	lineParts := linePattern.FindStringSubmatch(content)
	if len(lineParts) != 3 {
		fmt.Printf("Warning: invalid line #%d.\n", index+1)
		return line{}
	}

	label := parseLabel(index, lineParts[1])
	return line{label, lineParts[2]}
}

func parseLabel(index int, stringLabel string) int {
	integralLabel, error := strconv.Atoi(stringLabel)
	if error != nil && len(stringLabel) != 0 {
		fmt.Printf(
			"Warning: invalid label \"%s\" on line #%d.\n",
			stringLabel,
			index+1,
		)
	}

	return integralLabel
}

func makelabels(lines []line) labelMap {
	labels := make(labelMap)
	for index, parsedLine := range lines {
		labels[parsedLine.label] = (index + 1) * 10
	}

	return labels
}

func renumberLines(lines []line, labels labelMap) []line {
	var renumberedLines []line
	for index, parsedLine := range lines {
		renumberedLine := renumberLine(index, parsedLine, labels)
		renumberedLines = append(renumberedLines, renumberedLine)
	}

	return renumberedLines
}

func renumberLine(index int, parsedLine line, labels labelMap) line {
	correctedLabel := labels[parsedLine.label]
	correctedStatement := correctStatement(
		index,
		parsedLine.statement,
		labels,
	)
	return line{correctedLabel, correctedStatement}
}

func correctStatement(index int, statement string, labels labelMap) string {
	statement = jumpPattern.ReplaceAllStringFunc(
		statement,
		jumpCorrector(index, labels),
	)
	statement = conditionJumpPattern.ReplaceAllStringFunc(
		statement,
		jumpCorrector(index, labels),
	)
	return statement
}

func jumpCorrector(index int, labels labelMap) corrector {
	return func(match string) string {
		return labelPattern.ReplaceAllStringFunc(
			match,
			labelCorrector(index, labels),
		)
	}
}

func labelCorrector(index int, labels labelMap) corrector {
	return func(match string) string {
		oldLabel := parseLabel(index, match)
		newLabel := labels[oldLabel]
		return strconv.Itoa(newLabel)
	}
}

func printLines(lines []line) {
	for _, renumberedLine := range lines {
		fmt.Printf(
			"%d%s\n",
			renumberedLine.label,
			renumberedLine.statement,
		)
	}
}
<commit_msg>Fixes #6: add file updating.<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"unicode"
)

type line struct {
	label     int
	statement string
}
type labelMap map[int]int
type corrector func(string) string

var (
	usageDescription     = makeUsageDescription()
	linePattern          = regexp.MustCompile(`^\s*(\d+)?(.*?)\s*$`)
	jumpPattern          = regexp.MustCompile(`\bGOTO\s+\d+$`)
	conditionJumpPattern = regexp.MustCompile(`\bTHEN\s+\d+$`)
	labelPattern         = regexp.MustCompile(`\d+$`)
)

func main() {
	filename := processArguments()
	code := readFile(filename)
	cuttedCode := removeEndingWhitespaces(code)
	rawLines := splitLines(cuttedCode)
	parsedLines := parseLines(rawLines)
	labels := makelabels(parsedLines)
	renumberedLines := renumberLines(parsedLines, labels)
	renumberedCode := combineLines(renumberedLines)
	updateFile(filename, renumberedCode)
}

func makeUsageDescription() string {
	_, scriptPath, _, ok := runtime.Caller(0)
	if !ok {
		return ""
	}

	return fmt.Sprintf(
		"Usage:\n"+
			"\tgo run %s [options] <filename>\n"+
			"\n"+
			"Options:\n"+
			"\t-h, --help - show help.\n",
		filepath.Base(scriptPath),
	)
}

func processArguments() string {
	testArguments()

	firstArgument := os.Args[1]
	processHelpOption(firstArgument)

	return firstArgument
}

func testArguments() {
	numberOfArguments := len(os.Args)
	if numberOfArguments < 2 {
		fmt.Print(
			"Error: filename not specified.\n" +
				"\n" +
				usageDescription,
		)

		os.Exit(1)
	}
}

func processHelpOption(firstArgument string) {
	if firstArgument == "-h" || firstArgument == "--help" {
		fmt.Print(usageDescription)
		os.Exit(0)
	}
}

func readFile(filename string) string {
	code, error := ioutil.ReadFile(filename)
	if error != nil {
		fmt.Printf(
			"Error: unable to read file \"%s\" (%v).\n",
			filename,
			error,
		)

		os.Exit(1)
	}

	return string(code)
}

func removeEndingWhitespaces(code string) string {
	return strings.TrimRightFunc(code, unicode.IsSpace)
}

func splitLines(code string) []string {
	return strings.Split(code, "\n")
}

func parseLines(lines []string) []line {
	var parsedLines []line
	for index, content := range lines {
		parsedLine := parseLine(index, content)
		parsedLines = append(parsedLines, parsedLine)
	}

	return parsedLines
}

func parseLine(index int, content string) line {
	lineParts := linePattern.FindStringSubmatch(content)
	if len(lineParts) != 3 {
		fmt.Printf("Warning: invalid line #%d.\n", index+1)
		return line{}
	}

	label := parseLabel(index, lineParts[1])
	return line{label, lineParts[2]}
}

func parseLabel(index int, stringLabel string) int {
	integralLabel, error := strconv.Atoi(stringLabel)
	if error != nil && len(stringLabel) != 0 {
		fmt.Printf(
			"Warning: invalid label \"%s\" on line #%d.\n",
			stringLabel,
			index+1,
		)
	}

	return integralLabel
}

func makelabels(lines []line) labelMap {
	labels := make(labelMap)
	for index, parsedLine := range lines {
		labels[parsedLine.label] = (index + 1) * 10
	}

	return labels
}

func renumberLines(lines []line, labels labelMap) []line {
	var renumberedLines []line
	for index, parsedLine := range lines {
		renumberedLine := renumberLine(index, parsedLine, labels)
		renumberedLines = append(renumberedLines, renumberedLine)
	}

	return renumberedLines
}

func renumberLine(index int, parsedLine line, labels labelMap) line {
	correctedLabel := labels[parsedLine.label]
	correctedStatement := correctStatement(
		index,
		parsedLine.statement,
		labels,
	)
	return line{correctedLabel, correctedStatement}
}

func correctStatement(index int, statement string, labels labelMap) string {
	statement = jumpPattern.ReplaceAllStringFunc(
		statement,
		jumpCorrector(index, labels),
	)
	statement = conditionJumpPattern.ReplaceAllStringFunc(
		statement,
		jumpCorrector(index, labels),
	)
	return statement
}

func jumpCorrector(index int, labels labelMap) corrector {
	return func(match string) string {
		return labelPattern.ReplaceAllStringFunc(
			match,
			labelCorrector(index, labels),
		)
	}
}

func labelCorrector(index int, labels labelMap) corrector {
	return func(match string) string {
		oldLabel := parseLabel(index, match)
		newLabel := labels[oldLabel]
		return strconv.Itoa(newLabel)
	}
}

func combineLines(lines []line) string {
	code := ""
	for _, renumberedLine := range lines {
		code += fmt.Sprintf(
			"%d%s\n",
			renumberedLine.label,
			renumberedLine.statement,
		)
	}

	return code
}

func updateFile(filename string, code string) {
	error := ioutil.WriteFile(filename, []byte(code), 0644)
	if error != nil {
		fmt.Printf(
			"Error: unable to update file \"%s\" (%v).\n",
			filename,
			error,
		)

		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package core

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

	dguard "github.com/90TechSAS/libgo-docker-guard"
)

var (
	HTTPClient *http.Client = &http.Client{}
)

type Probe struct {
	Name        string  `yaml:"name"`
	URI         string  `yaml:"uri"`
	APIPassword string  `yaml:"api-password"`
	ReloadTime  float64 `yaml:"reload-time"`
}

/*
	Initialize Core
*/
func Init() {
	InitSQL()

	for _, probe := range DGConfig.Probes {
		go probe.MonitorProbe()
	}

	for {
		time.Sleep(time.Minute)
	}
}

/*
	Loop for monitoring a probe
*/
func (p *Probe) MonitorProbe() {
	var resp *http.Response                     // Http response
	var req *http.Request                       // Http response
	var body []byte                             // Http body
	var err error                               // Error handling
	var containers map[string]*dguard.Container // Returned container list
	var dbContainers []Container                // Containers in DB
	var probeID int                             // Probe ID

	probeID, err = GetProbeID(p.Name)
	if err != nil {
		l.Critical("MonitorProbe: Can't get probe ID", err)
	}

	// Reloading loop
	for {
		containers = nil
		l.Verbose("Reloading", p.Name)

		// Make HTTP GET request
		reqURI := p.URI + "/list"
		l.Debug("GET", reqURI)
		req, err = http.NewRequest("GET", reqURI, bytes.NewBufferString(""))
		if err != nil {
			l.Error("MonitorProbe: Can't create", p.Name, "HTTP request:", err)
			time.Sleep(time.Second * time.Duration(p.ReloadTime))
			continue
		}
		req.Header.Set("Auth", p.APIPassword)

		// Do request
		resp, err = HTTPClient.Do(req)
		if err != nil {
			l.Error("MonitorProbe: Can't get", p.Name, "container list:", err)
			time.Sleep(time.Second * time.Duration(p.ReloadTime))
			continue
		}

		// Get request body
		body, err = ioutil.ReadAll(resp.Body)
		if err != nil {
			l.Error("MonitorProbe: Can't get", p.Name, "container list body:", err)
			time.Sleep(time.Second * time.Duration(p.ReloadTime))
			continue
		}

		l.Silly("MonitorProbe:", "GET", reqURI, "body:\n", string(body))

		// Parse body
		err = json.Unmarshal([]byte(body), &containers)
		if err != nil {
			l.Error("MonitorProbe: Parsing container list:", err)
		}

		// Remove in DB old removed containers
		dbContainers, err = GetContainersBy("probeid", probeID)
		if err != nil {
			l.Error("MonitorProbe: Can't get", p.Name, "container list in DB")
			time.Sleep(time.Second * time.Duration(p.ReloadTime))
			continue
		}
		for _, dbC := range dbContainers {
			var containerStillExist = false
			for _, c := range containers {
				if dbC.CID == c.ID {
					containerStillExist = true
				}
			}
			if !containerStillExist {
				dbC.Delete()
			}
		}

		// Add containers and stats in DB
		for _, c := range containers {
			var id int64
			var tmpContainer Container

			// Add containers in DB
			tmpContainer, err = GetContainerByCID(c.ID)
			if err != nil {
				if err.Error() == "sql: no rows in result set" {
					sqlContainer := Container{0, c.ID, probeID, c.Hostname, c.Image, c.IPAddress, c.MacAddress}
					id, err = sqlContainer.Insert()
					if err != nil {
						l.Error("MonitorProbe: container insert:", err)
						continue
					}
				} else {
					l.Error("MonitorProbe: GetContainerById:", err)
					continue
				}
			} else {
				id = int64(tmpContainer.ID)
			}

			// Add stats in DB
			sqlStat := Stat{int(id), int64(c.Time), uint64(c.SizeRootFs), uint64(c.SizeRw), uint64(c.MemoryUsed), c.Running}
			err = sqlStat.Insert()
			if err != nil {
				l.Error("MonitorProbe: stat insert:", err)
				continue
			}
		}

		// time.Sleep(time.Second * time.Duration(p.ReloadTime))
	}
}
<commit_msg>Fix random derps + comments<commit_after>package core

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

	dguard "github.com/90TechSAS/libgo-docker-guard"
)

var (
	// HTTP client used to get probe infos
	HTTPClient *http.Client = &http.Client{}
)

/*
	Probe
*/
type Probe struct {
	Name        string  `yaml:"name"`
	URI         string  `yaml:"uri"`
	APIPassword string  `yaml:"api-password"`
	ReloadTime  float64 `yaml:"reload-time"`
}

/*
	Initialize Core
*/
func Init() {
	// Init SQL client
	InitSQL()

	// Launch probe monitors
	for _, probe := range DGConfig.Probes {
		go probe.MonitorProbe()
	}

	// Launch API
	// Temporary dirty loop to avoid program shutdown
	// TODO: API
	for {
		time.Sleep(time.Minute)
	}
}

/*
	Loop for monitoring a probe
*/
func (p *Probe) MonitorProbe() {
	var resp *http.Response                     // Http response
	var req *http.Request                       // Http response
	var body []byte                             // Http body
	var err error                               // Error handling
	var containers map[string]*dguard.Container // Returned container list
	var dbContainers []Container                // Containers in DB
	var probeID int                             // Probe ID

	// Get probe ID in DB, create it if does not exists
	probeID, err = GetProbeID(p.Name)
	if err != nil {
		l.Critical("MonitorProbe: Can't get probe ID", err)
	}

	// Reloading loop
	for {
		containers = nil
		l.Verbose("Reloading", p.Name)

		// Make HTTP GET request
		reqURI := p.URI + "/list"
		l.Debug("GET", reqURI)
		req, err = http.NewRequest("GET", reqURI, bytes.NewBufferString(""))
		if err != nil {
			l.Error("MonitorProbe: Can't create", p.Name, "HTTP request:", err)
			time.Sleep(time.Second * time.Duration(p.ReloadTime))
			continue
		}
		req.Header.Set("Auth", p.APIPassword)

		// Do request
		resp, err = HTTPClient.Do(req)
		if err != nil {
			l.Error("MonitorProbe: Can't get", p.Name, "container list:", err)
			time.Sleep(time.Second * time.Duration(p.ReloadTime))
			continue
		}

		// Get request body
		body, err = ioutil.ReadAll(resp.Body)
		if err != nil {
			l.Error("MonitorProbe: Can't get", p.Name, "container list body:", err)
			time.Sleep(time.Second * time.Duration(p.ReloadTime))
			continue
		}

		l.Silly("MonitorProbe:", "GET", reqURI, "body:\n", string(body))

		// Parse body
		err = json.Unmarshal([]byte(body), &containers)
		if err != nil {
			l.Error("MonitorProbe: Parsing container list:", err)
		}

		// Remove in DB old removed containers
		dbContainers, err = GetContainersBy("probeid", probeID)
		if err != nil {
			l.Error("MonitorProbe: Can't get", p.Name, "container list in DB")
			time.Sleep(time.Second * time.Duration(p.ReloadTime))
			continue
		}
		for _, dbC := range dbContainers {
			var containerStillExist = false
			for _, c := range containers {
				if dbC.CID == c.ID {
					containerStillExist = true
				}
			}
			if !containerStillExist {
				dbC.Delete()
			}
		}

		// Add containers and stats in DB
		for _, c := range containers {
			var id int64
			var tmpContainer Container

			// Add containers in DB
			tmpContainer, err = GetContainerByCID(c.ID)
			if err != nil {
				if err.Error() == "sql: no rows in result set" {
					sqlContainer := Container{0, c.ID, probeID, c.Hostname, c.Image, c.IPAddress, c.MacAddress}
					id, err = sqlContainer.Insert()
					if err != nil {
						l.Error("MonitorProbe: container insert:", err)
						continue
					}
				} else {
					l.Error("MonitorProbe: GetContainerById:", err)
					continue
				}
			} else {
				id = int64(tmpContainer.ID)
			}

			// Add stats in DB
			sqlStat := Stat{int(id), int64(c.Time), uint64(c.SizeRootFs), uint64(c.SizeRw), uint64(c.MemoryUsed), c.Running}
			err = sqlStat.Insert()
			if err != nil {
				l.Error("MonitorProbe: stat insert:", err)
				continue
			}
		}

		time.Sleep(time.Second * time.Duration(p.ReloadTime))
	}
}
<|endoftext|>


<commit_before>package core

import (
	"errors"
	"fmt"
	"net/http"
	"path"
	"sync"
	"time"

	libp2p "gx/ipfs/QmTW4SdgBWq9GjsBsHeUx8WuGxzhgzAf88UMH2w62PC8yK/go-libp2p-crypto"
	ma "gx/ipfs/QmTZBfrPJmjWsCvHEtX5FE6KimVJhsJg5sBbqEFYf4UZtL/go-multiaddr"
	cid "gx/ipfs/QmTbxNB1NwDesLmKTscr4udL2tVP7MaxvXnD1D9yX7g3PN/go-cid"
	peer "gx/ipfs/QmYVXrKrKHDC9FobgmcmshCDyWwdrfwfanNQN4oxJ9Fk3h/go-libp2p-peer"
	routing "gx/ipfs/QmYxUdYY9S6yg5tSPVin5GFTvtfsLauVcr7reHDD3dM8xf/go-libp2p-routing"

	"github.com/OpenBazaar/multiwallet"
	"github.com/OpenBazaar/openbazaar-go/ipfs"
	"github.com/OpenBazaar/openbazaar-go/net"
	rep "github.com/OpenBazaar/openbazaar-go/net/repointer"
	ret "github.com/OpenBazaar/openbazaar-go/net/retriever"
	"github.com/OpenBazaar/openbazaar-go/pb"
	"github.com/OpenBazaar/openbazaar-go/repo"
	sto "github.com/OpenBazaar/openbazaar-go/storage"
	"github.com/btcsuite/btcutil/hdkeychain"
	"github.com/ipfs/go-ipfs/core"
	logging "github.com/op/go-logging"
	"golang.org/x/net/context"
	"golang.org/x/net/proxy"
)

const (
	// VERSION - current version
	VERSION = "0.14.5"
	// USERAGENT - user-agent header string
	USERAGENT = "/openbazaar-go:" + VERSION + "/"
)

var log = logging.MustGetLogger("core")

// Node - ob node
var Node *OpenBazaarNode

var inflightPublishRequests int

// OpenBazaarNode - represent ob node which encapsulates ipfsnode, wallet etc
type OpenBazaarNode struct {
	// IPFS node object
	IpfsNode *core.IpfsNode

	// An implementation of the custom DHT used by OpenBazaar
	DHT *dht.IpfsDHT

	// The roothash of the node directory inside the openbazaar repo.
	// This directory hash is published on IPNS at our peer ID making
	// the directory publicly viewable on the network.
	RootHash string

	// The path to the openbazaar repo in the file system
	RepoPath string

	// The OpenBazaar network service for direct communication between peers
	Service net.NetworkService

	// Database for storing node specific data
	Datastore repo.Datastore

	// Websocket channel used for pushing data to the UI
	Broadcast chan repo.Notifier

	// A map of cryptocurrency wallets
	Multiwallet multiwallet.MultiWallet

	// Storage for our outgoing messages
	MessageStorage sto.OfflineMessagingStorage

	// A service that periodically checks the dht for outstanding messages
	MessageRetriever *ret.MessageRetriever

	// OfflineMessageFailoverTimeout is the duration until the protocol
	// will stop looking for the peer to send a direct message and failover to
	// sending an offline message
	OfflineMessageFailoverTimeout time.Duration

	// A service that periodically republishes active pointers
	PointerRepublisher *rep.PointerRepublisher

	// Optional nodes to push user data to
	PushNodes []peer.ID

	// The user-agent for this node
	UserAgent string

	// A dialer for Tor if available
	TorDialer proxy.Dialer

	// Manage blocked peers
	BanManager *net.BanManager

	// Allow other nodes to push data to this node for storage
	AcceptStoreRequests bool

	// RecordAgingNotifier is a worker that walks the cases datastore to
	// notify the user as disputes age past certain thresholds
	RecordAgingNotifier *recordAgingNotifier

	// Generic pubsub interface
	Pubsub ipfs.Pubsub

	// The master private key derived from the mnemonic
	MasterPrivateKey *hdkeychain.ExtendedKey

	// The number of DHT records to collect before returning. The larger the number
	// the slower the query but the less likely we will get an old record.
	IPNSQuorumSize uint

	TestnetEnable        bool
	RegressionTestEnable bool

	PublishLock sync.Mutex
	seedLock    sync.Mutex

	InitalPublishComplete bool

	// InboundMsgScanner is a worker that scans the messages
	// table and tries to retry a failed order message
	InboundMsgScanner *inboundMessageScanner
}

// TestNetworkEnabled indicates whether the node is operating with test parameters
func (n *OpenBazaarNode) TestNetworkEnabled() bool { return n.TestnetEnable }

// RegressionNetworkEnabled indicates whether the node is operating with regression parameters
func (n *OpenBazaarNode) RegressionNetworkEnabled() bool { return n.RegressionTestEnable }

// SeedNode - publish to IPNS
func (n *OpenBazaarNode) SeedNode() error {
	n.seedLock.Lock()
	err := ipfs.UnPinDir(n.IpfsNode, n.RootHash)
	if err != nil {
		log.Errorf("unpinning old root: %s", err.Error())
	}
	var aerr error
	var rootHash string
	// There's an IPFS bug on Windows that might be related to the Windows indexer that could cause this to fail
	// If we fail the first time, let's retry a couple times before giving up.
	for i := 0; i < 3; i++ {
		rootHash, aerr = ipfs.AddDirectory(n.IpfsNode, path.Join(n.RepoPath, "root"))
		if aerr == nil {
			break
		}
		time.Sleep(time.Millisecond * 500)
	}
	if aerr != nil {
		n.seedLock.Unlock()
		return aerr
	}
	n.RootHash = rootHash

	n.seedLock.Unlock()
	n.InitalPublishComplete = true
	go n.publish(rootHash)
	return nil
}

func (n *OpenBazaarNode) publish(hash string) {
	// Multiple publishes may have been queued
	// We only need to publish the most recent
	n.PublishLock.Lock()
	defer n.PublishLock.Unlock()
	if hash != n.RootHash {
		return
	}

	if inflightPublishRequests == 0 {
		n.Broadcast <- repo.StatusNotification{Status: "publishing"}
	}

	err := n.sendToPushNodes(hash)
	if err != nil {
		log.Error(err)
		return
	}

	go func() {
		// Update search endpoint with published hash
		peerId, _ := n.GetNodeID()
		endpoint := fmt.Sprintf("https://search.ob1.io/update/%s/%s", peerId.PeerID, hash)
		log.Infof("Publishing new rootHash to: %s\n", endpoint)

		var client *http.Client
		if n.TorDialer != nil {
			tbTransport := &http.Transport{Dial: n.TorDialer.Dial}
			client = &http.Client{Transport: tbTransport, Timeout: time.Second * 30}
		} else {
			client = &http.Client{Timeout: time.Second * 30}
		}

		resp, err := client.Get(endpoint)
		if err != nil {
			log.Errorf("Search update did not succeed. %v\n", err)
		}
		log.Debugf("%s response: %v", endpoint, resp)
	}()

	inflightPublishRequests++
	err = ipfs.Publish(n.IpfsNode, hash)

	inflightPublishRequests--
	if inflightPublishRequests == 0 {
		if err != nil {
			log.Error(err)
			n.Broadcast <- repo.StatusNotification{Status: "error publishing"}
		} else {
			n.Broadcast <- repo.StatusNotification{Status: "publish complete"}
		}
	}
}

func (n *OpenBazaarNode) sendToPushNodes(hash string) error {
	id, err := cid.Decode(hash)
	if err != nil {
		return err
	}

	var graph []cid.Cid
	if len(n.PushNodes) > 0 {
		graph, err = ipfs.FetchGraph(n.IpfsNode, &id)
		if err != nil {
			return err
		}
		pointers, err := n.Datastore.Pointers().GetByPurpose(ipfs.MESSAGE)
		if err != nil {
			return err
		}
		// Check if we're seeding any outgoing messages and add their CIDs to the graph
		for _, p := range pointers {
			if len(p.Value.Addrs) > 0 {
				s, err := p.Value.Addrs[0].ValueForProtocol(ma.P_IPFS)
				if err != nil {
					continue
				}
				c, err := cid.Decode(s)
				if err != nil {
					continue
				}
				graph = append(graph, c)
			}
		}
	}
	for _, p := range n.PushNodes {
		go n.retryableSeedStoreToPeer(p, hash, graph)
	}

	return nil
}

func (n *OpenBazaarNode) retryableSeedStoreToPeer(pid peer.ID, graphHash string, graph []cid.Cid) {
	var retryTimeout = 2 * time.Second
	for {
		if graphHash != n.RootHash {
			log.Errorf("root hash has changed, aborting push to %s", pid.Pretty())
			return
		}
		err := n.SendStore(pid.Pretty(), graph)
		if err != nil {
			if retryTimeout > 8*time.Second {
				log.Errorf("error pushing to peer %s: %s", pid.Pretty(), err.Error())
				return
			}
			log.Errorf("error pushing to peer %s...backing off: %s", pid.Pretty(), err.Error())
			time.Sleep(retryTimeout)
			retryTimeout *= 2
			continue
		}
		return
	}
}

// SetUpRepublisher - periodic publishing to IPNS
func (n *OpenBazaarNode) SetUpRepublisher(interval time.Duration) {
	if interval == 0 {
		return
	}
	ticker := time.NewTicker(interval)
	go func() {
		for range ticker.C {
			err := n.UpdateFollow()
			if err != nil {
				log.Error(err)
			}
			err = n.SeedNode()
			if err != nil {
				log.Error(err)
			}
		}
	}()
}

/*EncryptMessage This is a placeholder until the libsignal is operational.
  For now we will just encrypt outgoing offline messages with the long lived identity key.
  Optionally you may provide a public key, to avoid doing an IPFS lookup */
func (n *OpenBazaarNode) EncryptMessage(peerID peer.ID, peerKey *libp2p.PubKey, message []byte) (ct []byte, rerr error) {
	ctx, cancel := context.WithTimeout(context.Background(), n.OfflineMessageFailoverTimeout)
	defer cancel()
	if peerKey == nil {
		var (
			pubKey libp2p.PubKey
			store  = n.IpfsNode.Repo.Datastore()
		)
		keyval, err := ipfs.GetCachedPubkey(store, peerID.Pretty())
		if err != nil {
			pubKey, err = routing.GetPublicKey(n.IpfsNode.Routing, ctx, peerID)
			if err != nil {
				log.Errorf("Failed to find public key for %s", peerID.Pretty())
				return nil, err
			}
		} else {
			pubKey, err = libp2p.UnmarshalPublicKey(keyval)
			if err != nil {
				log.Errorf("Failed to find public key for %s", peerID.Pretty())
				return nil, err
			}
		}
		peerKey = &pubKey
	}
	if peerID.MatchesPublicKey(*peerKey) {
		ciphertext, err := net.Encrypt(*peerKey, message)
		if err != nil {
			return nil, err
		}
		return ciphertext, nil
	}
	log.Errorf("peer public key and id do not match for peer: %s", peerID.Pretty())
	return nil, errors.New("peer public key and id do not match")
}

// IPFSIdentityString - IPFS identifier
func (n *OpenBazaarNode) IPFSIdentityString() string {
	return n.IpfsNode.Identity.Pretty()
}

// GetNodeID returns the protobuf representing the node's identity and crypto
// keys with the peer ID
func (n *OpenBazaarNode) GetNodeID() (*pb.ID, error) {
	var id = new(pb.ID)
	id.PeerID = n.IpfsNode.Identity.Pretty()

	if p, err := n.GetProfile(); err == nil {
		id.Handle = p.Handle
	}

	p := new(pb.ID_Pubkeys)
	pubkey, err := n.IpfsNode.PrivateKey.GetPublic().Bytes()
	if err != nil {
		return nil, fmt.Errorf("ipfs pubkey bytes: %s", err.Error())
	}
	p.Identity = pubkey
	coinPubkey, err := n.MasterPrivateKey.ECPubKey()
	if err != nil {
		return nil, fmt.Errorf("master pubkey: %s", err.Error())
	}
	p.Bitcoin = coinPubkey.SerializeCompressed()
	id.Pubkeys = p

	coinPrivKey, err := n.MasterPrivateKey.ECPrivKey()
	if err != nil {
		return nil, fmt.Errorf("master privkey: %s", err.Error())
	}
	coinSig, err := coinPrivKey.Sign([]byte(id.PeerID))
	if err != nil {
		return nil, fmt.Errorf("sign id: %s", err.Error())
	}
	id.BitcoinSig = coinSig.Serialize()

	return id, nil
}

// Sign returns a signature for the payload signed by the IPFS private key
func (n *OpenBazaarNode) Sign(payload []byte) ([]byte, error) {
	return n.IpfsNode.PrivateKey.Sign(payload)
}
<commit_msg>Add dht back<commit_after>package core

import (
	"errors"
	"fmt"
	"net/http"
	"path"
	"sync"
	"time"

	"gx/ipfs/QmSY3nkMNLzh9GdbFKK5tT7YMfLpf52iUZ8ZRkr29MJaa5/go-libp2p-kad-dht"
	libp2p "gx/ipfs/QmTW4SdgBWq9GjsBsHeUx8WuGxzhgzAf88UMH2w62PC8yK/go-libp2p-crypto"
	ma "gx/ipfs/QmTZBfrPJmjWsCvHEtX5FE6KimVJhsJg5sBbqEFYf4UZtL/go-multiaddr"
	cid "gx/ipfs/QmTbxNB1NwDesLmKTscr4udL2tVP7MaxvXnD1D9yX7g3PN/go-cid"
	peer "gx/ipfs/QmYVXrKrKHDC9FobgmcmshCDyWwdrfwfanNQN4oxJ9Fk3h/go-libp2p-peer"
	routing "gx/ipfs/QmYxUdYY9S6yg5tSPVin5GFTvtfsLauVcr7reHDD3dM8xf/go-libp2p-routing"

	"github.com/OpenBazaar/multiwallet"
	"github.com/OpenBazaar/openbazaar-go/ipfs"
	"github.com/OpenBazaar/openbazaar-go/net"
	rep "github.com/OpenBazaar/openbazaar-go/net/repointer"
	ret "github.com/OpenBazaar/openbazaar-go/net/retriever"
	"github.com/OpenBazaar/openbazaar-go/pb"
	"github.com/OpenBazaar/openbazaar-go/repo"
	sto "github.com/OpenBazaar/openbazaar-go/storage"
	"github.com/btcsuite/btcutil/hdkeychain"
	"github.com/ipfs/go-ipfs/core"
	logging "github.com/op/go-logging"
	"golang.org/x/net/context"
	"golang.org/x/net/proxy"
)

const (
	// VERSION - current version
	VERSION = "0.14.5"
	// USERAGENT - user-agent header string
	USERAGENT = "/openbazaar-go:" + VERSION + "/"
)

var log = logging.MustGetLogger("core")

// Node - ob node
var Node *OpenBazaarNode

var inflightPublishRequests int

// OpenBazaarNode - represent ob node which encapsulates ipfsnode, wallet etc
type OpenBazaarNode struct {
	// IPFS node object
	IpfsNode *core.IpfsNode

	// An implementation of the custom DHT used by OpenBazaar
	DHT *dht.IpfsDHT

	// The roothash of the node directory inside the openbazaar repo.
	// This directory hash is published on IPNS at our peer ID making
	// the directory publicly viewable on the network.
	RootHash string

	// The path to the openbazaar repo in the file system
	RepoPath string

	// The OpenBazaar network service for direct communication between peers
	Service net.NetworkService

	// Database for storing node specific data
	Datastore repo.Datastore

	// Websocket channel used for pushing data to the UI
	Broadcast chan repo.Notifier

	// A map of cryptocurrency wallets
	Multiwallet multiwallet.MultiWallet

	// Storage for our outgoing messages
	MessageStorage sto.OfflineMessagingStorage

	// A service that periodically checks the dht for outstanding messages
	MessageRetriever *ret.MessageRetriever

	// OfflineMessageFailoverTimeout is the duration until the protocol
	// will stop looking for the peer to send a direct message and failover to
	// sending an offline message
	OfflineMessageFailoverTimeout time.Duration

	// A service that periodically republishes active pointers
	PointerRepublisher *rep.PointerRepublisher

	// Optional nodes to push user data to
	PushNodes []peer.ID

	// The user-agent for this node
	UserAgent string

	// A dialer for Tor if available
	TorDialer proxy.Dialer

	// Manage blocked peers
	BanManager *net.BanManager

	// Allow other nodes to push data to this node for storage
	AcceptStoreRequests bool

	// RecordAgingNotifier is a worker that walks the cases datastore to
	// notify the user as disputes age past certain thresholds
	RecordAgingNotifier *recordAgingNotifier

	// Generic pubsub interface
	Pubsub ipfs.Pubsub

	// The master private key derived from the mnemonic
	MasterPrivateKey *hdkeychain.ExtendedKey

	// The number of DHT records to collect before returning. The larger the number
	// the slower the query but the less likely we will get an old record.
	IPNSQuorumSize uint

	TestnetEnable        bool
	RegressionTestEnable bool

	PublishLock sync.Mutex
	seedLock    sync.Mutex

	InitalPublishComplete bool

	// InboundMsgScanner is a worker that scans the messages
	// table and tries to retry a failed order message
	InboundMsgScanner *inboundMessageScanner
}

// TestNetworkEnabled indicates whether the node is operating with test parameters
func (n *OpenBazaarNode) TestNetworkEnabled() bool { return n.TestnetEnable }

// RegressionNetworkEnabled indicates whether the node is operating with regression parameters
func (n *OpenBazaarNode) RegressionNetworkEnabled() bool { return n.RegressionTestEnable }

// SeedNode - publish to IPNS
func (n *OpenBazaarNode) SeedNode() error {
	n.seedLock.Lock()
	err := ipfs.UnPinDir(n.IpfsNode, n.RootHash)
	if err != nil {
		log.Errorf("unpinning old root: %s", err.Error())
	}
	var aerr error
	var rootHash string
	// There's an IPFS bug on Windows that might be related to the Windows indexer that could cause this to fail
	// If we fail the first time, let's retry a couple times before giving up.
	for i := 0; i < 3; i++ {
		rootHash, aerr = ipfs.AddDirectory(n.IpfsNode, path.Join(n.RepoPath, "root"))
		if aerr == nil {
			break
		}
		time.Sleep(time.Millisecond * 500)
	}
	if aerr != nil {
		n.seedLock.Unlock()
		return aerr
	}
	n.RootHash = rootHash

	n.seedLock.Unlock()
	n.InitalPublishComplete = true
	go n.publish(rootHash)
	return nil
}

func (n *OpenBazaarNode) publish(hash string) {
	// Multiple publishes may have been queued
	// We only need to publish the most recent
	n.PublishLock.Lock()
	defer n.PublishLock.Unlock()
	if hash != n.RootHash {
		return
	}

	if inflightPublishRequests == 0 {
		n.Broadcast <- repo.StatusNotification{Status: "publishing"}
	}

	err := n.sendToPushNodes(hash)
	if err != nil {
		log.Error(err)
		return
	}

	go func() {
		// Update search endpoint with published hash
		peerId, _ := n.GetNodeID()
		endpoint := fmt.Sprintf("https://search.ob1.io/update/%s/%s", peerId.PeerID, hash)
		log.Infof("Publishing new rootHash to: %s\n", endpoint)

		var client *http.Client
		if n.TorDialer != nil {
			tbTransport := &http.Transport{Dial: n.TorDialer.Dial}
			client = &http.Client{Transport: tbTransport, Timeout: time.Second * 30}
		} else {
			client = &http.Client{Timeout: time.Second * 30}
		}

		resp, err := client.Get(endpoint)
		if err != nil {
			log.Errorf("Search update did not succeed. %v\n", err)
		}
		log.Debugf("%s response: %v", endpoint, resp)
	}()

	inflightPublishRequests++
	err = ipfs.Publish(n.IpfsNode, hash)

	inflightPublishRequests--
	if inflightPublishRequests == 0 {
		if err != nil {
			log.Error(err)
			n.Broadcast <- repo.StatusNotification{Status: "error publishing"}
		} else {
			n.Broadcast <- repo.StatusNotification{Status: "publish complete"}
		}
	}
}

func (n *OpenBazaarNode) sendToPushNodes(hash string) error {
	id, err := cid.Decode(hash)
	if err != nil {
		return err
	}

	var graph []cid.Cid
	if len(n.PushNodes) > 0 {
		graph, err = ipfs.FetchGraph(n.IpfsNode, &id)
		if err != nil {
			return err
		}
		pointers, err := n.Datastore.Pointers().GetByPurpose(ipfs.MESSAGE)
		if err != nil {
			return err
		}
		// Check if we're seeding any outgoing messages and add their CIDs to the graph
		for _, p := range pointers {
			if len(p.Value.Addrs) > 0 {
				s, err := p.Value.Addrs[0].ValueForProtocol(ma.P_IPFS)
				if err != nil {
					continue
				}
				c, err := cid.Decode(s)
				if err != nil {
					continue
				}
				graph = append(graph, c)
			}
		}
	}
	for _, p := range n.PushNodes {
		go n.retryableSeedStoreToPeer(p, hash, graph)
	}

	return nil
}

func (n *OpenBazaarNode) retryableSeedStoreToPeer(pid peer.ID, graphHash string, graph []cid.Cid) {
	var retryTimeout = 2 * time.Second
	for {
		if graphHash != n.RootHash {
			log.Errorf("root hash has changed, aborting push to %s", pid.Pretty())
			return
		}
		err := n.SendStore(pid.Pretty(), graph)
		if err != nil {
			if retryTimeout > 8*time.Second {
				log.Errorf("error pushing to peer %s: %s", pid.Pretty(), err.Error())
				return
			}
			log.Errorf("error pushing to peer %s...backing off: %s", pid.Pretty(), err.Error())
			time.Sleep(retryTimeout)
			retryTimeout *= 2
			continue
		}
		return
	}
}

// SetUpRepublisher - periodic publishing to IPNS
func (n *OpenBazaarNode) SetUpRepublisher(interval time.Duration) {
	if interval == 0 {
		return
	}
	ticker := time.NewTicker(interval)
	go func() {
		for range ticker.C {
			err := n.UpdateFollow()
			if err != nil {
				log.Error(err)
			}
			err = n.SeedNode()
			if err != nil {
				log.Error(err)
			}
		}
	}()
}

/*EncryptMessage This is a placeholder until the libsignal is operational.
  For now we will just encrypt outgoing offline messages with the long lived identity key.
  Optionally you may provide a public key, to avoid doing an IPFS lookup */
func (n *OpenBazaarNode) EncryptMessage(peerID peer.ID, peerKey *libp2p.PubKey, message []byte) (ct []byte, rerr error) {
	ctx, cancel := context.WithTimeout(context.Background(), n.OfflineMessageFailoverTimeout)
	defer cancel()
	if peerKey == nil {
		var (
			pubKey libp2p.PubKey
			store  = n.IpfsNode.Repo.Datastore()
		)
		keyval, err := ipfs.GetCachedPubkey(store, peerID.Pretty())
		if err != nil {
			pubKey, err = routing.GetPublicKey(n.IpfsNode.Routing, ctx, peerID)
			if err != nil {
				log.Errorf("Failed to find public key for %s", peerID.Pretty())
				return nil, err
			}
		} else {
			pubKey, err = libp2p.UnmarshalPublicKey(keyval)
			if err != nil {
				log.Errorf("Failed to find public key for %s", peerID.Pretty())
				return nil, err
			}
		}
		peerKey = &pubKey
	}
	if peerID.MatchesPublicKey(*peerKey) {
		ciphertext, err := net.Encrypt(*peerKey, message)
		if err != nil {
			return nil, err
		}
		return ciphertext, nil
	}
	log.Errorf("peer public key and id do not match for peer: %s", peerID.Pretty())
	return nil, errors.New("peer public key and id do not match")
}

// IPFSIdentityString - IPFS identifier
func (n *OpenBazaarNode) IPFSIdentityString() string {
	return n.IpfsNode.Identity.Pretty()
}

// GetNodeID returns the protobuf representing the node's identity and crypto
// keys with the peer ID
func (n *OpenBazaarNode) GetNodeID() (*pb.ID, error) {
	var id = new(pb.ID)
	id.PeerID = n.IpfsNode.Identity.Pretty()

	if p, err := n.GetProfile(); err == nil {
		id.Handle = p.Handle
	}

	p := new(pb.ID_Pubkeys)
	pubkey, err := n.IpfsNode.PrivateKey.GetPublic().Bytes()
	if err != nil {
		return nil, fmt.Errorf("ipfs pubkey bytes: %s", err.Error())
	}
	p.Identity = pubkey
	coinPubkey, err := n.MasterPrivateKey.ECPubKey()
	if err != nil {
		return nil, fmt.Errorf("master pubkey: %s", err.Error())
	}
	p.Bitcoin = coinPubkey.SerializeCompressed()
	id.Pubkeys = p

	coinPrivKey, err := n.MasterPrivateKey.ECPrivKey()
	if err != nil {
		return nil, fmt.Errorf("master privkey: %s", err.Error())
	}
	coinSig, err := coinPrivKey.Sign([]byte(id.PeerID))
	if err != nil {
		return nil, fmt.Errorf("sign id: %s", err.Error())
	}
	id.BitcoinSig = coinSig.Serialize()

	return id, nil
}

// Sign returns a signature for the payload signed by the IPFS private key
func (n *OpenBazaarNode) Sign(payload []byte) ([]byte, error) {
	return n.IpfsNode.PrivateKey.Sign(payload)
}
<|endoftext|>


<commit_before>package core

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MG-RAST/AWE/conf"
	"io/ioutil"
	"labix.org/v2/mgo/bson"
	"net/http"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

type ShockResponse struct {
	Code int       `bson:"status" json:"status"`
	Data ShockNode `bson:"data" json:"data"`
	Errs []string  `bson:"error" json:"error"`
}

type ShockNode struct {
	Id         string             `bson:"id" json:"id"`
	Version    string             `bson:"version" json:"version"`
	File       shockfile          `bson:"file" json:"file"`
	Attributes interface{}        `bson:"attributes" json:"attributes"`
	Indexes    map[string]IdxInfo `bson:"indexes" json:"indexes"`
	//Acl          Acl                `bson:"acl" json:"-"`
	VersionParts map[string]string `bson:"version_parts" json:"-"`
	Tags         []string          `bson:"tags" json:"tags"`
	//	Revisions    []ShockNode       `bson:"revisions" json:"-"`
	Linkages []linkage `bson:"linkage" json:"linkages"`
}

type shockfile struct {
	Name         string            `bson:"name" json:"name"`
	Size         int64             `bson:"size" json:"size"`
	Checksum     map[string]string `bson:"checksum" json:"checksum"`
	Format       string            `bson:"format" json:"format"`
	Path         string            `bson:"path" json:"-"`
	Virtual      bool              `bson:"virtual" json:"virtual"`
	VirtualParts []string          `bson:"virtual_parts" json:"virtual_parts"`
}

type IdxInfo struct {
	Type        string `bson:"index_type" json:"-"`
	TotalUnits  int64  `bson:"total_units" json:"total_units"`
	AvgUnitSize int64  `bson:"average_unit_size" json:"average_unit_size"`
}

type FormFiles map[string]FormFile

type FormFile struct {
	Name     string
	Path     string
	Checksum map[string]string
}

type linkage struct {
	Type      string   `bson: "relation" json:"relation"`
	Ids       []string `bson:"ids" json:"ids"`
	Operation string   `bson:"operation" json:"operation"`
}

func CreateJobUpload(params map[string]string, files FormFiles, jid string) (job *Job, err error) {

	if _, has_upload := files["upload"]; has_upload {
		job, err = ParseJobTasks(files["upload"].Path, jid)
	} else {
		job, err = ParseAwf(files["awf"].Path, jid)
	}

	if err != nil {
		return
	}
	err = job.Mkdir()
	if err != nil {
		return
	}
	err = job.UpdateFile(params, files)
	if err != nil {
		return
	}

	err = job.Save()
	return
}

func LoadJob(id string) (job *Job, err error) {
	if db, err := DBConnect(); err == nil {
		defer db.Close()
		job = new(Job)
		if err = db.FindById(id, job); err == nil {
			return job, nil
		} else {
			return nil, err
		}
	}
	return nil, err
}

func LoadJobs(ids []string) (jobs []*Job, err error) {
	if db, err := DBConnect(); err == nil {
		defer db.Close()
		if err = db.FindJobs(ids, &jobs); err == nil {
			return jobs, err
		} else {
			return nil, err
		}
	}
	return nil, err
}

func ReloadFromDisk(path string) (err error) {
	id := filepath.Base(path)
	jobbson, err := ioutil.ReadFile(path + "/" + id + ".bson")
	if err != nil {
		return
	}
	job := new(Job)
	err = bson.Unmarshal(jobbson, &job)
	if err == nil {
		db, er := DBConnect()
		if er != nil {
			err = er
		}
		defer db.Close()
		err = db.Upsert(job)
		if err != nil {
			err = er
		}
	}
	return
}

//create a shock node for output
func PostNode(io *IO, numParts int) (nodeid string, err error) {
	var res *http.Response
	shockurl := fmt.Sprintf("%s/node", io.Host)

	c := make(chan int, 1)

	go func() {
		res, err = http.Post(shockurl, "", strings.NewReader(""))
		c <- 1 //we are ending
	}()

	select {
	case <-c:
		//go ahead
	case <-time.After(conf.SHOCK_TIMEOUT):
		fmt.Printf("timeout when creating node in shock, url=" + shockurl)
		return "", errors.New("timeout when creating node in shock, url=" + shockurl)
	}

	//fmt.Printf("shockurl=%s\n", shockurl)
	if err != nil {
		return "", err
	}

	jsonstream, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	response := new(ShockResponse)
	if err := json.Unmarshal(jsonstream, response); err != nil {
		return "", errors.New(fmt.Sprintf("failed to marshal post response:\"%s\"", jsonstream))
	}
	if len(response.Errs) > 0 {
		return "", errors.New(strings.Join(response.Errs, ","))
	}

	shocknode := &response.Data
	nodeid = shocknode.Id

	if numParts > 1 {
		putParts(io.Host, nodeid, numParts)
	}
	return
}

//create parts
func putParts(host string, nodeid string, numParts int) (err error) {
	argv := []string{}
	argv = append(argv, "-X")
	argv = append(argv, "PUT")
	argv = append(argv, "-F")
	argv = append(argv, fmt.Sprintf("parts=%d", numParts))
	target_url := fmt.Sprintf("%s/node/%s", host, nodeid)
	argv = append(argv, target_url)

	cmd := exec.Command("curl", argv...)
	err = cmd.Run()
	if err != nil {
		return
	}
	return
}

//get jobid from task id or workunit id
func getParentJobId(id string) (jobid string) {
	parts := strings.Split(id, "_")
	return parts[0]
}

//parse job by job script
func ParseJobTasks(filename string, jid string) (job *Job, err error) {
	job = new(Job)

	jsonstream, err := ioutil.ReadFile(filename)

	if err != nil {
		return nil, errors.New("error in reading job json file")
	}

	json.Unmarshal(jsonstream, job)

	if len(job.Tasks) == 0 {
		return nil, errors.New("invalid job script: task list empty")
	}

	if job.Info == nil {
		job.Info = NewInfo()
	}

	job.Info.SubmitTime = time.Now()
	job.Info.Priority = conf.BasePriority

	job.setId()     //uuid for the job
	job.setJid(jid) //an incremental id for the jobs within a AWE server domain
	job.State = JOB_STAT_SUBMITTED

	for i := 0; i < len(job.Tasks); i++ {
		if err := job.Tasks[i].InitTask(job, i); err != nil {
			return nil, err
		}
	}

	job.RemainTasks = len(job.Tasks)

	return
}

//parse .awf.json - sudo-function only, to be finished
func ParseAwf(filename string, jid string) (job *Job, err error) {
	job = new(Job)
	jsonstream, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, errors.New("error in reading job json file")
	}
	fmt.Printf("jsonstream=%s\n", jsonstream)
	return
}

func GetJobIdByTaskId(taskid string) (jobid string, err error) {
	parts := strings.Split(taskid, "_")
	if len(parts) == 2 {
		return parts[0], nil
	} else {
		return "", errors.New("invalid task id: " + taskid)
	}
}
<commit_msg>go 1.0.3 compatibility<commit_after>package core

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MG-RAST/AWE/conf"
	"io/ioutil"
	"labix.org/v2/mgo/bson"
	"net/http"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

type ShockResponse struct {
	Code int       `bson:"status" json:"status"`
	Data ShockNode `bson:"data" json:"data"`
	Errs []string  `bson:"error" json:"error"`
}

type ShockNode struct {
	Id         string             `bson:"id" json:"id"`
	Version    string             `bson:"version" json:"version"`
	File       shockfile          `bson:"file" json:"file"`
	Attributes interface{}        `bson:"attributes" json:"attributes"`
	Indexes    map[string]IdxInfo `bson:"indexes" json:"indexes"`
	//Acl          Acl                `bson:"acl" json:"-"`
	VersionParts map[string]string `bson:"version_parts" json:"-"`
	Tags         []string          `bson:"tags" json:"tags"`
	//	Revisions    []ShockNode       `bson:"revisions" json:"-"`
	Linkages []linkage `bson:"linkage" json:"linkages"`
}

type shockfile struct {
	Name         string            `bson:"name" json:"name"`
	Size         int64             `bson:"size" json:"size"`
	Checksum     map[string]string `bson:"checksum" json:"checksum"`
	Format       string            `bson:"format" json:"format"`
	Path         string            `bson:"path" json:"-"`
	Virtual      bool              `bson:"virtual" json:"virtual"`
	VirtualParts []string          `bson:"virtual_parts" json:"virtual_parts"`
}

type IdxInfo struct {
	Type        string `bson:"index_type" json:"-"`
	TotalUnits  int64  `bson:"total_units" json:"total_units"`
	AvgUnitSize int64  `bson:"average_unit_size" json:"average_unit_size"`
}

type FormFiles map[string]FormFile

type FormFile struct {
	Name     string
	Path     string
	Checksum map[string]string
}

type linkage struct {
	Type      string   `bson: "relation" json:"relation"`
	Ids       []string `bson:"ids" json:"ids"`
	Operation string   `bson:"operation" json:"operation"`
}

func CreateJobUpload(params map[string]string, files FormFiles, jid string) (job *Job, err error) {

	if _, has_upload := files["upload"]; has_upload {
		job, err = ParseJobTasks(files["upload"].Path, jid)
	} else {
		job, err = ParseAwf(files["awf"].Path, jid)
	}

	if err != nil {
		return
	}
	err = job.Mkdir()
	if err != nil {
		return
	}
	err = job.UpdateFile(params, files)
	if err != nil {
		return
	}

	err = job.Save()
	return
}

func LoadJob(id string) (job *Job, err error) {
	if db, err := DBConnect(); err == nil {
		defer db.Close()
		job = new(Job)
		if err = db.FindById(id, job); err == nil {
			return job, nil
		} else {
			return nil, err
		}
	}
	return nil, err
}

func LoadJobs(ids []string) (jobs []*Job, err error) {
	if db, err := DBConnect(); err == nil {
		defer db.Close()
		if err = db.FindJobs(ids, &jobs); err == nil {
			return jobs, err
		} else {
			return nil, err
		}
	}
	return nil, err
}

func ReloadFromDisk(path string) (err error) {
	id := filepath.Base(path)
	jobbson, err := ioutil.ReadFile(path + "/" + id + ".bson")
	if err != nil {
		return
	}
	job := new(Job)
	err = bson.Unmarshal(jobbson, &job)
	if err == nil {
		db, er := DBConnect()
		if er != nil {
			err = er
		}
		defer db.Close()
		err = db.Upsert(job)
		if err != nil {
			err = er
		}
	}
	return
}

//create a shock node for output
func PostNode(io *IO, numParts int) (nodeid string, err error) {
	var res *http.Response
	shockurl := fmt.Sprintf("%s/node", io.Host)

	c := make(chan int, 1)

	go func() {
		res, err = http.Post(shockurl, "", strings.NewReader(""))
		c <- 1 //we are ending
	}()

	select {
	case <-c:
		//go ahead
	case <-time.After(conf.SHOCK_TIMEOUT):
		fmt.Printf("timeout when creating node in shock, url=" + shockurl)
		return "", errors.New("timeout when creating node in shock, url=" + shockurl)
	}

	//fmt.Printf("shockurl=%s\n", shockurl)
	if err != nil {
		return "", err
	}

	jsonstream, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	response := new(ShockResponse)
	if err := json.Unmarshal(jsonstream, response); err != nil {
		return "", errors.New(fmt.Sprintf("failed to marshal post response:\"%s\"", jsonstream))
	}
	if len(response.Errs) > 0 {
		return "", errors.New(strings.Join(response.Errs, ","))
	}

	shocknode := &response.Data
	nodeid = shocknode.Id

	if numParts > 1 {
		putParts(io.Host, nodeid, numParts)
	}
	return
}

//create parts
func putParts(host string, nodeid string, numParts int) (err error) {
	argv := []string{}
	argv = append(argv, "-X")
	argv = append(argv, "PUT")
	argv = append(argv, "-F")
	argv = append(argv, fmt.Sprintf("parts=%d", numParts))
	target_url := fmt.Sprintf("%s/node/%s", host, nodeid)
	argv = append(argv, target_url)

	cmd := exec.Command("curl", argv...)
	err = cmd.Run()
	if err != nil {
		return
	}
	return
}

//get jobid from task id or workunit id
func getParentJobId(id string) (jobid string) {
	parts := strings.Split(id, "_")
	return parts[0]
}

//parse job by job script
func ParseJobTasks(filename string, jid string) (job *Job, err error) {
	job = new(Job)

	jsonstream, err := ioutil.ReadFile(filename)

	if err != nil {
		return nil, errors.New("error in reading job json file")
	}

	json.Unmarshal(jsonstream, job)

	if len(job.Tasks) == 0 {
		return nil, errors.New("invalid job script: task list empty")
	}

	if job.Info == nil {
		job.Info = NewInfo()
	}

	job.Info.SubmitTime = time.Now()
	job.Info.Priority = conf.BasePriority

	job.setId()     //uuid for the job
	job.setJid(jid) //an incremental id for the jobs within a AWE server domain
	job.State = JOB_STAT_SUBMITTED

	for i := 0; i < len(job.Tasks); i++ {
		if err := job.Tasks[i].InitTask(job, i); err != nil {
			return nil, err
		}
	}

	job.RemainTasks = len(job.Tasks)

	return
}

//parse .awf.json - sudo-function only, to be finished
func ParseAwf(filename string, jid string) (job *Job, err error) {
	job = new(Job)
	jsonstream, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, errors.New("error in reading job json file")
	}
	fmt.Printf("jsonstream=%s\n", jsonstream)
	return
}

func GetJobIdByTaskId(taskid string) (jobid string, err error) {
	parts := strings.Split(taskid, "_")
	if len(parts) == 2 {
		return parts[0], nil
	} else {
		return "", errors.New("invalid task id: " + taskid)
	}
	return
}
<|endoftext|>