<commit_before>package elasticsearch

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
)

// Searcher set the contract to manage indices, synchronize data and request
type Client interface {
	CreateIndex(indexName, mapping string) (*Response, error)
	DeleteIndex(indexName string) (*Response, error)
	UpdateIndexSetting(indexName, mapping string) (*Response, error)
	IndexSettings(indexName string) (Settings, error)
	IndexExists(indexName string) (bool, error)
	Status(indices string) (*Settings, error)
	InsertDocument(indexName, documentType, identifier string, data []byte) (*InsertDocument, error)
	Document(indexName, documentType, identifier string) (*Document, error)
	DeleteDocument(indexName, documentType, identifier string) (*Document, error)
	Bulk(data []byte) (*Bulk, error)
	Search(indexName, documentType, data string, explain bool) (*SearchResult, error)
	MSearch(queries []MSearchQuery) (*MSearchResult, error)
	Suggest(indexName, data string) ([]byte, error)
	GetIndicesFromAlias(alias string) ([]string, error)
	UpdateAlias(remove []string, add []string, alias string) (*Response, error)
}

// A SearchClient describes the client configuration to manage an ElasticSearch index.
type client struct {
	Host url.URL
}

// NewSearchClient creates and initializes a new ElasticSearch client, implements core api for Indexing and searching.
func NewClient(scheme, host, port string) Client {
	u := url.URL{
		Scheme: scheme,
		Host:   host + ":" + port,
	}
	return &client{Host: u}
}

// NewSearchClient creates and initializes a new ElasticSearch client, implements core api for Indexing and searching.
func NewClientFromUrl(rawurl string) Client {
	u, err := url.Parse(rawurl)
	if err != nil {
		log.Fatal(err)
		return nil
	}
	return &client{Host: *u}
}

// CreateIndex instantiates an index
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-create-index.html
func (c *client) CreateIndex(indexName, mapping string) (*Response, error) {
	url := c.Host.String() + "/" + indexName
	reader := bytes.NewBufferString(mapping)
	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &Response{}, err
	}

	esResp := &Response{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Response{}, err
	}

	return esResp, nil
}

// DeleteIndex deletes an existing index.
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-delete-index.html
func (c *client) DeleteIndex(indexName string) (*Response, error) {
	url := c.Host.String() + "/" + indexName
	response, err := sendHTTPRequest("DELETE", url, nil)
	if err != nil {
		return &Response{}, err
	}

	esResp := &Response{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Response{}, err
	}

	return esResp, nil
}

// UpdateIndexSetting changes specific index level settings in real time
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-update-settings.html
func (c *client) UpdateIndexSetting(indexName, mapping string) (*Response, error) {
	url := c.Host.String() + "/" + indexName + "/_settings"
	reader := bytes.NewBufferString(mapping)
	response, err := sendHTTPRequest("PUT", url, reader)
	if err != nil {
		return &Response{}, err
	}

	esResp := &Response{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Response{}, err
	}

	return esResp, nil
}

// IndexSettings allows to retrieve settings of index
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-settings.html
func (c *client) IndexSettings(indexName string) (Settings, error) {
	url := c.Host.String() + "/" + indexName + "/_settings"
	response, err := sendHTTPRequest("GET", url, nil)
	if err != nil {
		return Settings{}, err
	}

	type settingsArray map[string]Settings
	dec := json.NewDecoder(bytes.NewBuffer(response))
	var info settingsArray
	err = dec.Decode(&info)
	if err != nil {
		return Settings{}, err
	}

	return info[indexName], nil
}

// IndexExists allows to check if the index exists or not.
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-exists.html
func (c *client) IndexExists(indexName string) (bool, error) {
	url := c.Host.String() + "/" + indexName
	httpClient := &http.Client{}
	newReq, err := httpClient.Head(url)
	if err != nil {
		return false, err
	}

	return newReq.StatusCode == http.StatusOK, nil
}

// Status allows to get a comprehensive status information
func (c *client) Status(indices string) (*Settings, error) {
	url := c.Host.String() + "/" + indices + "/_status"
	response, err := sendHTTPRequest("GET", url, nil)
	if err != nil {
		return &Settings{}, err
	}

	esResp := &Settings{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Settings{}, err
	}

	return esResp, nil
}

// InsertDocument adds or updates a typed JSON document in a specific index, making it searchable
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-index_.html
func (c *client) InsertDocument(indexName, documentType, identifier string, data []byte) (*InsertDocument, error) {
	url := c.Host.String() + "/" + indexName + "/" + documentType + "/" + identifier
	reader := bytes.NewBuffer(data)
	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &InsertDocument{}, err
	}

	esResp := &InsertDocument{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &InsertDocument{}, err
	}

	return esResp, nil
}

// Document gets a typed JSON document from the index based on its id
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html
func (c *client) Document(indexName, documentType, identifier string) (*Document, error) {
	url := c.Host.String() + "/" + indexName + "/" + documentType + "/" + identifier
	response, err := sendHTTPRequest("GET", url, nil)
	if err != nil {
		return &Document{}, err
	}

	esResp := &Document{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Document{}, err
	}

	return esResp, nil
}

// DeleteDocument deletes a typed JSON document from a specific index based on its id
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete.html
func (c *client) DeleteDocument(indexName, documentType, identifier string) (*Document, error) {
	url := c.Host.String() + "/" + indexName + "/" + documentType + "/" + identifier
	response, err := sendHTTPRequest("DELETE", url, nil)
	if err != nil {
		return &Document{}, err
	}

	esResp := &Document{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Document{}, err
	}

	return esResp, nil
}

// Bulk makes it possible to perform many index/delete operations in a single API call.
// This can greatly increase the indexing speed.
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-bulk.html
func (c *client) Bulk(data []byte) (*Bulk, error) {
	url := c.Host.String() + "/_bulk"
	reader := bytes.NewBuffer(data)
	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &Bulk{}, err
	}

	esResp := &Bulk{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Bulk{}, err
	}

	return esResp, nil
}

// Search allows to execute a search query and get back search hits that match the query
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete.html
func (c *client) Search(indexName, documentType, data string, explain bool) (*SearchResult, error) {
	if len(documentType) > 0 {
		documentType = documentType + "/"
	}

	url := c.Host.String() + "/" + indexName + "/" + documentType + "/_search"
	if explain {
		url += "?explain"
	}
	reader := bytes.NewBufferString(data)
	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &SearchResult{}, err
	}

	esResp := &SearchResult{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &SearchResult{}, err
	}

	return esResp, nil
}

// MSearch allows to execute a multi-search and get back result
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-multi-search.html
func (c *client) MSearch(queries []MSearchQuery) (*MSearchResult, error) {
	replacer := strings.NewReplacer("\n", " ")
	queriesList := make([]string, len(queries))
	for i, query := range queries {
		queriesList[i] = query.Header + "\n" + replacer.Replace(query.Body)
	}

	mSearchQuery := strings.Join(queriesList, "\n") + "\n" // Don't forget trailing \n
	url := c.Host.String() + "/_msearch"
	reader := bytes.NewBufferString(mSearchQuery)
	response, err := sendHTTPRequest("POST", url, reader)

	if err != nil {
		return &MSearchResult{}, err
	}

	esResp := &MSearchResult{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &MSearchResult{}, err
	}

	return esResp, nil
}

// Suggest allows basic auto-complete functionality.
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
func (c *client) Suggest(indexName, data string) ([]byte, error) {
	url := c.Host.String() + "/" + indexName + "/_suggest"
	reader := bytes.NewBufferString(data)
	response, err := sendHTTPRequest("POST", url, reader)
	return response, err
}

// GetIndicesFromAlias returns the list of indices the alias points to
func (c *client) GetIndicesFromAlias(alias string) ([]string, error) {
	url := c.Host.String() + "/*/_alias/" + alias
	response, err := sendHTTPRequest("GET", url, nil)
	if err != nil {
		return []string{}, err
	}

	esResp := make(map[string]*json.RawMessage)
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return []string{}, err
	}

	indices := make([]string, len(esResp))
	i := 0
	for k := range esResp {
		indices[i] = k
		i++
	}
	return indices, nil
}

// UpdateAlias updates the indices on which the alias point to.
// The change is atomic.
func (c *client) UpdateAlias(remove []string, add []string, alias string) (*Response, error) {
	url := c.Host.String() + "/_aliases"
	body := getAliasQuery(remove, add, alias)
	reader := bytes.NewBufferString(body)

	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &Response{}, err
	}

	esResp := &Response{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Response{}, err
	}

	return esResp, nil
}

func getAliasQuery(remove []string, add []string, alias string) string {
	var buffer bytes.Buffer

	i := 0
	if len(remove) > 0 {

		for ; i < len(remove)-1; i++ {
			buffer.WriteString("{ \"remove\": { \"index\": \"" + remove[i] + "\", \"alias\": \"" + alias + "\" }},")
		}
		buffer.WriteString("{ \"add\": { \"index\": \"" + remove[i] + "\", \"alias\": \"" + alias + "\" }}")
	}

	i = 0
	if len(add) > 0 {
		for ; i < len(add)-1; i++ {
			buffer.WriteString("{ \"add\": { \"index\": \"" + add[i] + "\", \"alias\": \"" + alias + "\" }},")
		}
		buffer.WriteString("{ \"add\": { \"index\": \"" + add[i] + "\", \"alias\": \"" + alias + "\" }}")
	}

	return "{\"actions\": [ " + buffer.String() + " ]}"
}

func sendHTTPRequest(method, url string, body io.Reader) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	if method == "POST" || method == "PUT" {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}

	newReq, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	defer newReq.Body.Close()
	response, err := ioutil.ReadAll(newReq.Body)
	if err != nil {
		return nil, err
	}

	if newReq.StatusCode > http.StatusCreated && newReq.StatusCode < http.StatusNotFound {
		return nil, errors.New(string(response))
	}

	return response, nil
}
<commit_msg>debug<commit_after>package elasticsearch

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
)

// Searcher set the contract to manage indices, synchronize data and request
type Client interface {
	CreateIndex(indexName, mapping string) (*Response, error)
	DeleteIndex(indexName string) (*Response, error)
	UpdateIndexSetting(indexName, mapping string) (*Response, error)
	IndexSettings(indexName string) (Settings, error)
	IndexExists(indexName string) (bool, error)
	Status(indices string) (*Settings, error)
	InsertDocument(indexName, documentType, identifier string, data []byte) (*InsertDocument, error)
	Document(indexName, documentType, identifier string) (*Document, error)
	DeleteDocument(indexName, documentType, identifier string) (*Document, error)
	Bulk(data []byte) (*Bulk, error)
	Search(indexName, documentType, data string, explain bool) (*SearchResult, error)
	MSearch(queries []MSearchQuery) (*MSearchResult, error)
	Suggest(indexName, data string) ([]byte, error)
	GetIndicesFromAlias(alias string) ([]string, error)
	UpdateAlias(remove []string, add []string, alias string) (*Response, error)
}

// A SearchClient describes the client configuration to manage an ElasticSearch index.
type client struct {
	Host url.URL
}

// NewSearchClient creates and initializes a new ElasticSearch client, implements core api for Indexing and searching.
func NewClient(scheme, host, port string) Client {
	u := url.URL{
		Scheme: scheme,
		Host:   host + ":" + port,
	}
	return &client{Host: u}
}

// NewSearchClient creates and initializes a new ElasticSearch client, implements core api for Indexing and searching.
func NewClientFromUrl(rawurl string) Client {
	u, err := url.Parse(rawurl)
	if err != nil {
		log.Fatal(err)
		return nil
	}
	return &client{Host: *u}
}

// CreateIndex instantiates an index
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-create-index.html
func (c *client) CreateIndex(indexName, mapping string) (*Response, error) {
	url := c.Host.String() + "/" + indexName
	reader := bytes.NewBufferString(mapping)
	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &Response{}, err
	}

	esResp := &Response{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Response{}, err
	}

	return esResp, nil
}

// DeleteIndex deletes an existing index.
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-delete-index.html
func (c *client) DeleteIndex(indexName string) (*Response, error) {
	url := c.Host.String() + "/" + indexName
	response, err := sendHTTPRequest("DELETE", url, nil)
	if err != nil {
		return &Response{}, err
	}

	esResp := &Response{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Response{}, err
	}

	return esResp, nil
}

// UpdateIndexSetting changes specific index level settings in real time
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-update-settings.html
func (c *client) UpdateIndexSetting(indexName, mapping string) (*Response, error) {
	url := c.Host.String() + "/" + indexName + "/_settings"
	reader := bytes.NewBufferString(mapping)
	response, err := sendHTTPRequest("PUT", url, reader)
	if err != nil {
		return &Response{}, err
	}

	esResp := &Response{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Response{}, err
	}

	return esResp, nil
}

// IndexSettings allows to retrieve settings of index
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-settings.html
func (c *client) IndexSettings(indexName string) (Settings, error) {
	url := c.Host.String() + "/" + indexName + "/_settings"
	response, err := sendHTTPRequest("GET", url, nil)
	if err != nil {
		return Settings{}, err
	}

	type settingsArray map[string]Settings
	dec := json.NewDecoder(bytes.NewBuffer(response))
	var info settingsArray
	err = dec.Decode(&info)
	if err != nil {
		return Settings{}, err
	}

	return info[indexName], nil
}

// IndexExists allows to check if the index exists or not.
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-exists.html
func (c *client) IndexExists(indexName string) (bool, error) {
	url := c.Host.String() + "/" + indexName
	httpClient := &http.Client{}
	newReq, err := httpClient.Head(url)
	if err != nil {
		return false, err
	}

	return newReq.StatusCode == http.StatusOK, nil
}

// Status allows to get a comprehensive status information
func (c *client) Status(indices string) (*Settings, error) {
	url := c.Host.String() + "/" + indices + "/_status"
	response, err := sendHTTPRequest("GET", url, nil)
	if err != nil {
		return &Settings{}, err
	}

	esResp := &Settings{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Settings{}, err
	}

	return esResp, nil
}

// InsertDocument adds or updates a typed JSON document in a specific index, making it searchable
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-index_.html
func (c *client) InsertDocument(indexName, documentType, identifier string, data []byte) (*InsertDocument, error) {
	url := c.Host.String() + "/" + indexName + "/" + documentType + "/" + identifier
	reader := bytes.NewBuffer(data)
	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &InsertDocument{}, err
	}

	esResp := &InsertDocument{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &InsertDocument{}, err
	}

	return esResp, nil
}

// Document gets a typed JSON document from the index based on its id
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html
func (c *client) Document(indexName, documentType, identifier string) (*Document, error) {
	url := c.Host.String() + "/" + indexName + "/" + documentType + "/" + identifier
	response, err := sendHTTPRequest("GET", url, nil)
	if err != nil {
		return &Document{}, err
	}

	esResp := &Document{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Document{}, err
	}

	return esResp, nil
}

// DeleteDocument deletes a typed JSON document from a specific index based on its id
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete.html
func (c *client) DeleteDocument(indexName, documentType, identifier string) (*Document, error) {
	url := c.Host.String() + "/" + indexName + "/" + documentType + "/" + identifier
	response, err := sendHTTPRequest("DELETE", url, nil)
	if err != nil {
		return &Document{}, err
	}

	esResp := &Document{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Document{}, err
	}

	return esResp, nil
}

// Bulk makes it possible to perform many index/delete operations in a single API call.
// This can greatly increase the indexing speed.
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-bulk.html
func (c *client) Bulk(data []byte) (*Bulk, error) {
	url := c.Host.String() + "/_bulk"
	reader := bytes.NewBuffer(data)
	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &Bulk{}, err
	}

	esResp := &Bulk{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Bulk{}, err
	}

	return esResp, nil
}

// Search allows to execute a search query and get back search hits that match the query
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete.html
func (c *client) Search(indexName, documentType, data string, explain bool) (*SearchResult, error) {
	if len(documentType) > 0 {
		documentType = documentType + "/"
	}

	url := c.Host.String() + "/" + indexName + "/" + documentType + "/_search"
	if explain {
		url += "?explain"
	}
	reader := bytes.NewBufferString(data)
	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &SearchResult{}, err
	}

	esResp := &SearchResult{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &SearchResult{}, err
	}

	return esResp, nil
}

// MSearch allows to execute a multi-search and get back result
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-multi-search.html
func (c *client) MSearch(queries []MSearchQuery) (*MSearchResult, error) {
	replacer := strings.NewReplacer("\n", " ")
	queriesList := make([]string, len(queries))
	for i, query := range queries {
		queriesList[i] = query.Header + "\n" + replacer.Replace(query.Body)
	}

	mSearchQuery := strings.Join(queriesList, "\n") + "\n" // Don't forget trailing \n
	url := c.Host.String() + "/_msearch"
	reader := bytes.NewBufferString(mSearchQuery)
	response, err := sendHTTPRequest("POST", url, reader)

	if err != nil {
		return &MSearchResult{}, err
	}

	esResp := &MSearchResult{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &MSearchResult{}, err
	}

	return esResp, nil
}

// Suggest allows basic auto-complete functionality.
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
func (c *client) Suggest(indexName, data string) ([]byte, error) {
	url := c.Host.String() + "/" + indexName + "/_suggest"
	reader := bytes.NewBufferString(data)
	response, err := sendHTTPRequest("POST", url, reader)
	return response, err
}

// GetIndicesFromAlias returns the list of indices the alias points to
func (c *client) GetIndicesFromAlias(alias string) ([]string, error) {
	url := c.Host.String() + "/*/_alias/" + alias
	response, err := sendHTTPRequest("GET", url, nil)
	if err != nil {
		return []string{}, err
	}

	esResp := make(map[string]*json.RawMessage)
	err = json.Unmarshal(response, &esResp)
	if err != nil {
		return []string{}, err
	}

	indices := make([]string, len(esResp))
	i := 0
	for k := range esResp {
		indices[i] = k
		i++
	}
	return indices, nil
}

// UpdateAlias updates the indices on which the alias point to.
// The change is atomic.
func (c *client) UpdateAlias(remove []string, add []string, alias string) (*Response, error) {
	url := c.Host.String() + "/_aliases"
	body := getAliasQuery(remove, add, alias)
	reader := bytes.NewBufferString(body)

	response, err := sendHTTPRequest("POST", url, reader)
	if err != nil {
		return &Response{}, err
	}

	esResp := &Response{}
	err = json.Unmarshal(response, esResp)
	if err != nil {
		return &Response{}, err
	}

	return esResp, nil
}

func getAliasQuery(remove []string, add []string, alias string) string {
	var buffer bytes.Buffer

	i := 0
	if len(remove) > 0 {

		for ; i < len(remove)-1; i++ {
			buffer.WriteString("{ \"remove\": { \"index\": \"" + remove[i] + "\", \"alias\": \"" + alias + "\" }},")
		}
		buffer.WriteString("{ \"add\": { \"index\": \"" + remove[i] + "\", \"alias\": \"" + alias + "\" }}")
	}

	i = 0
	if len(add) > 0 {
		for ; i < len(add)-1; i++ {
			buffer.WriteString("{ \"add\": { \"index\": \"" + add[i] + "\", \"alias\": \"" + alias + "\" }},")
		}
		buffer.WriteString("{ \"add\": { \"index\": \"" + add[i] + "\", \"alias\": \"" + alias + "\" }}")
	}

	return "{\"actions\": [ " + buffer.String() + " ]}"
}

func sendHTTPRequest(method, url string, body io.Reader) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	if method == "POST" || method == "PUT" {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}

	newReq, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	defer newReq.Body.Close()
	response, err := ioutil.ReadAll(newReq.Body)
	if err != nil {
		return nil, err
	}

	if newReq.StatusCode > http.StatusCreated && newReq.StatusCode < http.StatusNotFound {
		return nil, errors.New(string(response))
	}

	return response, nil
}
<|endoftext|>


<commit_before>package dropbox

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
)

// Client implements a Dropbox client. You may use the Files and Users
// clients directly if preferred, however Client exposes them both.
type Client struct {
	*Config
	Users   *Users
	Files   *Files
	Sharing *Sharing
}

// New client.
func New(config *Config) *Client {
	c := &Client{Config: config}
	c.Users = &Users{c}
	c.Files = &Files{c}
	c.Sharing = &Sharing{c}
	return c
}

// call rpc style endpoint.
func (c *Client) call(
	ctx context.Context,
	path string,
	in interface{},
) (io.ReadCloser, error) {
	url := "https://api.dropboxapi.com/2" + path

	body, err := json.Marshal(in)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", url, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req = req.WithContext(ctx)
	req.Header.Set("Authorization", "Bearer "+c.AccessToken)
	req.Header.Set("Content-Type", "application/json")
	if c.Namespace != nil {
		namespaceHeader, err := json.Marshal(c.Namespace)
		if err != nil {
			return nil, err
		}
		req.Header.Set("Dropbox-API-Path-Root", string(namespaceHeader))
	}

	r, _, err := c.do(req)
	return r, err
}

// download style endpoint.
func (c *Client) download(
	ctx context.Context,
	path string,
	in interface{},
	r io.Reader,
) (io.ReadCloser, int64, error) {
	url := "https://content.dropboxapi.com/2" + path

	body, err := json.Marshal(in)
	if err != nil {
		return nil, 0, err
	}

	req, err := http.NewRequest("POST", url, r)
	if err != nil {
		return nil, 0, err
	}
	req = req.WithContext(ctx)
	req.Header.Set("Authorization", "Bearer "+c.AccessToken)
	req.Header.Set("Dropbox-API-Arg", string(body))
	if c.Namespace != nil {
		namespaceHeader, err := json.Marshal(c.Namespace)
		if err != nil {
			return nil, 0, err
		}
		req.Header.Set("Dropbox-API-Path-Root", string(namespaceHeader))
	}

	if r != nil {
		req.Header.Set("Content-Type", "application/octet-stream")
	}

	return c.do(req)
}

// perform the request.
func (c *Client) do(req *http.Request) (io.ReadCloser, int64, error) {
	res, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, 0, err
	}

	if res.StatusCode < 400 {
		return res.Body, res.ContentLength, err
	}

	defer res.Body.Close()

	e := &Error{
		Status:     http.StatusText(res.StatusCode),
		StatusCode: res.StatusCode,
		Header:     res.Header,
	}

	kind := res.Header.Get("Content-Type")

	if strings.Contains(kind, "text/plain") {
		if b, err := ioutil.ReadAll(res.Body); err == nil {
			e.Summary = string(b)
			return nil, 0, e
		}
		return nil, 0, err
	}

	if err := json.NewDecoder(res.Body).Decode(e); err != nil {
		return nil, 0, err
	}

	return nil, 0, e
}
<commit_msg>treat any non-json errors as text<commit_after>package dropbox

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
)

// Client implements a Dropbox client. You may use the Files and Users
// clients directly if preferred, however Client exposes them both.
type Client struct {
	*Config
	Users   *Users
	Files   *Files
	Sharing *Sharing
}

// New client.
func New(config *Config) *Client {
	c := &Client{Config: config}
	c.Users = &Users{c}
	c.Files = &Files{c}
	c.Sharing = &Sharing{c}
	return c
}

// call rpc style endpoint.
func (c *Client) call(
	ctx context.Context,
	path string,
	in interface{},
) (io.ReadCloser, error) {
	url := "https://api.dropboxapi.com/2" + path

	body, err := json.Marshal(in)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", url, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req = req.WithContext(ctx)
	req.Header.Set("Authorization", "Bearer "+c.AccessToken)
	req.Header.Set("Content-Type", "application/json")
	if c.Namespace != nil {
		namespaceHeader, err := json.Marshal(c.Namespace)
		if err != nil {
			return nil, err
		}
		req.Header.Set("Dropbox-API-Path-Root", string(namespaceHeader))
	}

	r, _, err := c.do(req)
	return r, err
}

// download style endpoint.
func (c *Client) download(
	ctx context.Context,
	path string,
	in interface{},
	r io.Reader,
) (io.ReadCloser, int64, error) {
	url := "https://content.dropboxapi.com/2" + path

	body, err := json.Marshal(in)
	if err != nil {
		return nil, 0, err
	}

	req, err := http.NewRequest("POST", url, r)
	if err != nil {
		return nil, 0, err
	}
	req = req.WithContext(ctx)
	req.Header.Set("Authorization", "Bearer "+c.AccessToken)
	req.Header.Set("Dropbox-API-Arg", string(body))
	if c.Namespace != nil {
		namespaceHeader, err := json.Marshal(c.Namespace)
		if err != nil {
			return nil, 0, err
		}
		req.Header.Set("Dropbox-API-Path-Root", string(namespaceHeader))
	}

	if r != nil {
		req.Header.Set("Content-Type", "application/octet-stream")
	}

	return c.do(req)
}

// perform the request.
func (c *Client) do(req *http.Request) (io.ReadCloser, int64, error) {
	res, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, 0, err
	}

	if res.StatusCode < 400 {
		return res.Body, res.ContentLength, err
	}

	defer res.Body.Close()

	e := &Error{
		Status:     http.StatusText(res.StatusCode),
		StatusCode: res.StatusCode,
		Header:     res.Header,
	}

	kind := res.Header.Get("Content-Type")

	if !strings.Contains(kind, "json") {
		if b, err := ioutil.ReadAll(res.Body); err == nil {
			e.Summary = string(b)
			return nil, 0, e
		}
		return nil, 0, err
	}

	if err := json.NewDecoder(res.Body).Decode(e); err != nil {
		return nil, 0, err
	}

	return nil, 0, e
}
<|endoftext|>


<commit_before>package client

import (
	"errors"
	// RPC
	"github.com/asuleymanov/golos-go/apis/account_by_key"
	"github.com/asuleymanov/golos-go/apis/account_history"
	"github.com/asuleymanov/golos-go/apis/database"
	"github.com/asuleymanov/golos-go/apis/follow"
	"github.com/asuleymanov/golos-go/apis/market_history"
	"github.com/asuleymanov/golos-go/apis/network_broadcast"
	"github.com/asuleymanov/golos-go/apis/operation_history"
	"github.com/asuleymanov/golos-go/apis/private_message"
	"github.com/asuleymanov/golos-go/apis/social_network"
	"github.com/asuleymanov/golos-go/apis/tags"
	"github.com/asuleymanov/golos-go/apis/witness"
	"github.com/asuleymanov/golos-go/transactions"
	"github.com/asuleymanov/golos-go/transports"
	"github.com/asuleymanov/golos-go/transports/websocket"
)

// Client can be used to access Golos remote APIs.
// There is a public field for every Golos API available,
// e.g. Client.Database corresponds to database_api.
type Client struct {
	cc transports.CallCloser

	AsyncProtocol bool

	// Database represents database_api.
	Database *database.API

	// Follow represents follow_api.
	Follow *follow.API

	// Follow represents market_history_api.
	MarketHistory *market_history.API

	// NetworkBroadcast represents network_broadcast_api.
	NetworkBroadcast *network_broadcast.API

	// AccountByKey represents account_by_key.
	AccountByKey *account_by_key.API

	// SocialNetwork represents social_network.
	SocialNetwork *social_network.API

	// PrivateMessage represents social_network.
	PrivateMessage *private_message.API

	Witness *witness.API

	AccountHistory *account_history.API

	OperationHistory *operation_history.API

	Tags *tags.API

	//Chain Id
	Chain *transactions.Chain

	// Current keys for operations
	CurrentKeys *Keys
}

// NewClient creates a new RPC client that use the given CallCloser internally.
// Initialize only server present API. Absent API initialized as nil value.
func NewClient(url []string, chain string) (*Client, error) {
	call, err := initClient(url)
	if err != nil {
		return nil, err
	}
	client := &Client{cc: call}

	client.asyncProtocol = false

	client.Database = database.NewAPI(client.cc)

	client.Follow = follow.NewAPI(client.cc)

	client.MarketHistory = market_history.NewAPI(client.cc)

	client.NetworkBroadcast = network_broadcast.NewAPI(client.cc)

	client.AccountByKey = account_by_key.NewAPI(client.cc)

	client.SocialNetwork = social_network.NewAPI(client.cc)

	client.PrivateMessage = private_message.NewAPI(client.cc)

	client.Witness = witness.NewAPI(client.cc)

	client.AccountHistory = account_history.NewAPI(client.cc)

	client.OperationHistory = operation_history.NewAPI(client.cc)

	client.Tags = tags.NewAPI(client.cc)

	client.Chain, err = initChainID(chain)
	if err != nil {
		client.Chain = transactions.GolosChain
	}

	return client, nil
}

// Close should be used to close the client when no longer needed.
// It simply calls Close() on the underlying CallCloser.
func (client *Client) Close() error {
	return client.cc.Close()
}

func initClient(url []string) (*websocket.Transport, error) {
	// Инициализация Websocket
	t, err := websocket.NewTransport(url)
	if err != nil {
		return nil, err
	}

	return t, nil
}

func initChainID(str string) (*transactions.Chain, error) {
	var chainID transactions.Chain
	// Определяем ChainId
	switch str {
	case "golos":
		chainID = *transactions.GolosChain
	case "test":
		chainID = *transactions.TestChain
	default:
		return nil, errors.New("Chain not found")
	}
	return &chainID, nil
}
<commit_msg>Fix Client<commit_after>package client

import (
	"errors"
	// RPC
	"github.com/asuleymanov/golos-go/apis/account_by_key"
	"github.com/asuleymanov/golos-go/apis/account_history"
	"github.com/asuleymanov/golos-go/apis/database"
	"github.com/asuleymanov/golos-go/apis/follow"
	"github.com/asuleymanov/golos-go/apis/market_history"
	"github.com/asuleymanov/golos-go/apis/network_broadcast"
	"github.com/asuleymanov/golos-go/apis/operation_history"
	"github.com/asuleymanov/golos-go/apis/private_message"
	"github.com/asuleymanov/golos-go/apis/social_network"
	"github.com/asuleymanov/golos-go/apis/tags"
	"github.com/asuleymanov/golos-go/apis/witness"
	"github.com/asuleymanov/golos-go/transactions"
	"github.com/asuleymanov/golos-go/transports"
	"github.com/asuleymanov/golos-go/transports/websocket"
)

// Client can be used to access Golos remote APIs.
// There is a public field for every Golos API available,
// e.g. Client.Database corresponds to database_api.
type Client struct {
	cc transports.CallCloser

	AsyncProtocol bool

	// Database represents database_api.
	Database *database.API

	// Follow represents follow_api.
	Follow *follow.API

	// Follow represents market_history_api.
	MarketHistory *market_history.API

	// NetworkBroadcast represents network_broadcast_api.
	NetworkBroadcast *network_broadcast.API

	// AccountByKey represents account_by_key.
	AccountByKey *account_by_key.API

	// SocialNetwork represents social_network.
	SocialNetwork *social_network.API

	// PrivateMessage represents social_network.
	PrivateMessage *private_message.API

	Witness *witness.API

	AccountHistory *account_history.API

	OperationHistory *operation_history.API

	Tags *tags.API

	//Chain Id
	Chain *transactions.Chain

	// Current keys for operations
	CurrentKeys *Keys
}

// NewClient creates a new RPC client that use the given CallCloser internally.
// Initialize only server present API. Absent API initialized as nil value.
func NewClient(url []string, chain string) (*Client, error) {
	call, err := initClient(url)
	if err != nil {
		return nil, err
	}
	client := &Client{cc: call}

	client.AsyncProtocol = false

	client.Database = database.NewAPI(client.cc)

	client.Follow = follow.NewAPI(client.cc)

	client.MarketHistory = market_history.NewAPI(client.cc)

	client.NetworkBroadcast = network_broadcast.NewAPI(client.cc)

	client.AccountByKey = account_by_key.NewAPI(client.cc)

	client.SocialNetwork = social_network.NewAPI(client.cc)

	client.PrivateMessage = private_message.NewAPI(client.cc)

	client.Witness = witness.NewAPI(client.cc)

	client.AccountHistory = account_history.NewAPI(client.cc)

	client.OperationHistory = operation_history.NewAPI(client.cc)

	client.Tags = tags.NewAPI(client.cc)

	client.Chain, err = initChainID(chain)
	if err != nil {
		client.Chain = transactions.GolosChain
	}

	return client, nil
}

// Close should be used to close the client when no longer needed.
// It simply calls Close() on the underlying CallCloser.
func (client *Client) Close() error {
	return client.cc.Close()
}

func initClient(url []string) (*websocket.Transport, error) {
	// Инициализация Websocket
	t, err := websocket.NewTransport(url)
	if err != nil {
		return nil, err
	}

	return t, nil
}

func initChainID(str string) (*transactions.Chain, error) {
	var chainID transactions.Chain
	// Определяем ChainId
	switch str {
	case "golos":
		chainID = *transactions.GolosChain
	case "test":
		chainID = *transactions.TestChain
	default:
		return nil, errors.New("Chain not found")
	}
	return &chainID, nil
}
<|endoftext|>


<commit_before>package aws_signing_client

import (
	"net/http"

	"strings"
	"time"

	"io/ioutil"
	"log"

	"bytes"

	"github.com/aws/aws-sdk-go/aws/signer/v4"
	"github.com/aws/aws-sdk-go/private/protocol/rest"
)

type (
	// Signer implements the http.RoundTripper interface and houses an optional RoundTripper that will be called between
	// signing and response.
	Signer struct {
		transport http.RoundTripper
		v4        *v4.Signer
		service   string
		region    string
		logger    *log.Logger
	}

	// MissingSignerError is an implementation of the error interface that indicates that no AWS v4.Signer was
	// provided in order to create a client.
	MissingSignerError struct{}

	// MissingServiceError is an implementation of the error interface that indicates that no AWS service was
	// provided in order to create a client.
	MissingServiceError struct{}

	// MissingRegionError is an implementation of the error interface that indicates that no AWS region was
	// provided in order to create a client.
	MissingRegionError struct{}
)

var signer *Signer

// New obtains an HTTP client with a RoundTripper that signs AWS requests for the provided service. An
// existing client can be specified for the `client` value, or--if nil--a new HTTP client will be created.
func New(v4s *v4.Signer, client *http.Client, service string, region string) (*http.Client, error) {
	c := client
	switch {
	case v4s == nil:
		return nil, MissingSignerError{}
	case service == "":
		return nil, MissingServiceError{}
	case region == "":
		return nil, MissingRegionError{}
	case c == nil:
		c = http.DefaultClient
	}
	s := &Signer{
		transport: c.Transport,
		v4:        v4s,
		service:   service,
		region:    region,
		logger:    log.New(ioutil.Discard, "", 0),
	}
	if s.transport == nil {
		s.transport = http.DefaultTransport
	}
	signer = s
	c.Transport = s
	return c, nil
}

// SetDebugLog sets a logger for use in debugging requests and responses.
func SetDebugLog(l *log.Logger) {
	signer.logger = l
}

// RoundTrip implements the http.RoundTripper interface and is used to wrap HTTP requests in order to sign them for AWS
// API calls. The scheme for all requests will be changed to HTTPS.
func (s *Signer) RoundTrip(req *http.Request) (*http.Response, error) {
	if h, ok := req.Header["Authorization"]; ok && len(h) > 0 && strings.HasPrefix(h[0], "AWS4") {
		s.logger.Println("Received request to sign that is already signed. Skipping.")
		return s.transport.RoundTrip(req)
	}
	s.logger.Printf("Receiving request for signing: %+v", req)
	req.URL.Scheme = "https"
	if strings.Contains(req.URL.RawPath, "%2C") {
		s.logger.Printf("Escaping path for URL path '%s'", req.URL.RawPath)
		req.URL.RawPath = rest.EscapePath(req.URL.RawPath, false)
	}
	t := time.Now()
	req.Header.Set("Date", t.Format(time.RFC3339))
	s.logger.Printf("Final request to be signed: %+v", req)
	var err error
	switch req.Body {
	case nil:
		s.logger.Println("Signing request with no body...")
		_, err = s.v4.Sign(req, nil, s.service, s.region, t)
	default:
		d, err := ioutil.ReadAll(req.Body)
		if err != nil {
			return nil, err
		}
		req.Body = ioutil.NopCloser(bytes.NewReader(d))
		s.logger.Println("Signing request with body...")
		_, err = s.v4.Sign(req, bytes.NewReader(d), s.service, s.region, t)
	}
	if err != nil {
		s.logger.Printf("Error while attempting to sign request: '%s'", err)
		return nil, err
	}
	s.logger.Printf("Signing succesful. Set header to: '%+v'", req.Header)
	s.logger.Printf("Sending signed request to RoundTripper: %+v", req)
	resp, err := s.transport.RoundTrip(req)
	if err != nil {
		s.logger.Printf("Error from RoundTripper.\n\n\tResponse: %+v\n\n\tError: '%s'", resp, err)
		return resp, err
	}
	respBody := "<nil>"
	if resp.Body != nil {
		defer resp.Body.Close()
		buf := new(bytes.Buffer)
		buf.ReadFrom(resp.Body)
		respBody = buf.String()
		resp.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
	}
	s.logger.Printf("Successful response from RoundTripper: %+v\n\nBody: '%s'\n", resp, respBody)
	return resp, nil
}

// Error implements the error interface.
func (err MissingSignerError) Error() string {
	return "No signer was provided. Cannot create client."
}

// Error implements the error interface.
func (err MissingServiceError) Error() string {
	return "No AWS service abbreviation was provided. Cannot create client."
}

// Error implements the error interface.
func (err MissingRegionError) Error() string {
	return "No AWS region was provided. Cannot create client."
}
<commit_msg>Use a new default http client instead of the singleton<commit_after>package aws_signing_client

import (
	"net/http"

	"strings"
	"time"

	"io/ioutil"
	"log"

	"bytes"

	"github.com/aws/aws-sdk-go/aws/signer/v4"
	"github.com/aws/aws-sdk-go/private/protocol/rest"
)

type (
	// Signer implements the http.RoundTripper interface and houses an optional RoundTripper that will be called between
	// signing and response.
	Signer struct {
		transport http.RoundTripper
		v4        *v4.Signer
		service   string
		region    string
		logger    *log.Logger
	}

	// MissingSignerError is an implementation of the error interface that indicates that no AWS v4.Signer was
	// provided in order to create a client.
	MissingSignerError struct{}

	// MissingServiceError is an implementation of the error interface that indicates that no AWS service was
	// provided in order to create a client.
	MissingServiceError struct{}

	// MissingRegionError is an implementation of the error interface that indicates that no AWS region was
	// provided in order to create a client.
	MissingRegionError struct{}
)

var signer *Signer

// New obtains an HTTP client with a RoundTripper that signs AWS requests for the provided service. An
// existing client can be specified for the `client` value, or--if nil--a new HTTP client will be created.
func New(v4s *v4.Signer, client *http.Client, service string, region string) (*http.Client, error) {
	c := client
	switch {
	case v4s == nil:
		return nil, MissingSignerError{}
	case service == "":
		return nil, MissingServiceError{}
	case region == "":
		return nil, MissingRegionError{}
	case c == nil:
		c = &http.Client{}
	}
	s := &Signer{
		transport: c.Transport,
		v4:        v4s,
		service:   service,
		region:    region,
		logger:    log.New(ioutil.Discard, "", 0),
	}
	if s.transport == nil {
		s.transport = http.DefaultTransport
	}
	signer = s
	c.Transport = s
	return c, nil
}

// SetDebugLog sets a logger for use in debugging requests and responses.
func SetDebugLog(l *log.Logger) {
	signer.logger = l
}

// RoundTrip implements the http.RoundTripper interface and is used to wrap HTTP requests in order to sign them for AWS
// API calls. The scheme for all requests will be changed to HTTPS.
func (s *Signer) RoundTrip(req *http.Request) (*http.Response, error) {
	if h, ok := req.Header["Authorization"]; ok && len(h) > 0 && strings.HasPrefix(h[0], "AWS4") {
		s.logger.Println("Received request to sign that is already signed. Skipping.")
		return s.transport.RoundTrip(req)
	}
	s.logger.Printf("Receiving request for signing: %+v", req)
	req.URL.Scheme = "https"
	if strings.Contains(req.URL.RawPath, "%2C") {
		s.logger.Printf("Escaping path for URL path '%s'", req.URL.RawPath)
		req.URL.RawPath = rest.EscapePath(req.URL.RawPath, false)
	}
	t := time.Now()
	req.Header.Set("Date", t.Format(time.RFC3339))
	s.logger.Printf("Final request to be signed: %+v", req)
	var err error
	switch req.Body {
	case nil:
		s.logger.Println("Signing request with no body...")
		_, err = s.v4.Sign(req, nil, s.service, s.region, t)
	default:
		d, err := ioutil.ReadAll(req.Body)
		if err != nil {
			return nil, err
		}
		req.Body = ioutil.NopCloser(bytes.NewReader(d))
		s.logger.Println("Signing request with body...")
		_, err = s.v4.Sign(req, bytes.NewReader(d), s.service, s.region, t)
	}
	if err != nil {
		s.logger.Printf("Error while attempting to sign request: '%s'", err)
		return nil, err
	}
	s.logger.Printf("Signing succesful. Set header to: '%+v'", req.Header)
	s.logger.Printf("Sending signed request to RoundTripper: %+v", req)
	resp, err := s.transport.RoundTrip(req)
	if err != nil {
		s.logger.Printf("Error from RoundTripper.\n\n\tResponse: %+v\n\n\tError: '%s'", resp, err)
		return resp, err
	}
	respBody := "<nil>"
	if resp.Body != nil {
		defer resp.Body.Close()
		buf := new(bytes.Buffer)
		buf.ReadFrom(resp.Body)
		respBody = buf.String()
		resp.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
	}
	s.logger.Printf("Successful response from RoundTripper: %+v\n\nBody: '%s'\n", resp, respBody)
	return resp, nil
}

// Error implements the error interface.
func (err MissingSignerError) Error() string {
	return "No signer was provided. Cannot create client."
}

// Error implements the error interface.
func (err MissingServiceError) Error() string {
	return "No AWS service abbreviation was provided. Cannot create client."
}

// Error implements the error interface.
func (err MissingRegionError) Error() string {
	return "No AWS region was provided. Cannot create client."
}
<|endoftext|>


<commit_before>package updown

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
)

const (
	libraryVersion = "0.2"
	defaultBaseURL = "https://updown.io/api/"
	userAgent      = "Go Updown v" + libraryVersion
	mediaType      = "application/json"
)

// An ErrorResponse reports the error caused by an API request
type ErrorResponse struct {
	// HTTP response that caused this error
	Response *http.Response

	// Error message
	Message string
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d %v",
		r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Message)
}

// Client manages communication the API
type Client struct {
	// HTTP client used to communicate with the API
	client *http.Client

	// Base URL for API requests
	BaseURL *url.URL

	// User agent for client
	UserAgent string

	// APIKey to use for the API
	APIKey string

	// Services used for communications with the API
	Check    CheckService
	Downtime DowntimeService
	Metric   MetricService
	Node     NodeService
}

// NewClient returns a new DigitalOcean API client.
func NewClient(apiKey string, httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}

	baseURL, _ := url.Parse(defaultBaseURL)

	c := &Client{
		client:    httpClient,
		BaseURL:   baseURL,
		UserAgent: userAgent,
		APIKey:    apiKey,
	}
	c.Check = CheckService{client: c, cache: NewMemoryCache()}
	c.Downtime = DowntimeService{client: c}
	c.Metric = MetricService{client: c}
	c.Node = NodeService{client: c}

	return c
}

// NewRequest creates an API request. A relative URL can be provided in urlStr, which will be resolved to the
// BaseURL of the Client. Relative URLS should always be specified without a preceding slash.
// If specified, the value pointed to by body is JSON encoded and included in as the request body.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
	rel, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}

	u := c.BaseURL.ResolveReference(rel)

	buf := new(bytes.Buffer)
	if body != nil {
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, u.String(), buf)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", mediaType)
	req.Header.Add("Accept", mediaType)
	req.Header.Add("User-Agent", userAgent)
	req.Header.Add("X-API-KEY", c.APIKey)
	return req, nil
}

// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
	response, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	defer func() {
		if rerr := response.Body.Close(); err == nil {
			err = rerr
		}
	}()

	err = CheckResponse(response)
	if err != nil {
		return response, err
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			_, err := io.Copy(w, response.Body)
			if err != nil {
				return nil, err
			}
		} else {
			err := json.NewDecoder(response.Body).Decode(v)
			if err != nil {
				return nil, err
			}
		}
	}

	return response, err
}

// CheckResponse checks the API response for errors, and returns them if present. A response is considered an
// error if it has a status code outside the 200 range. API error responses are expected to have either no response
// body, or a JSON response body that maps to ErrorResponse. Any other response body will be silently ignored.
func CheckResponse(r *http.Response) error {
	if c := r.StatusCode; c >= 200 && c <= 299 {
		return nil
	}

	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && len(data) > 0 {
		err := json.Unmarshal(data, errorResponse)
		if err != nil {
			return err
		}
	}

	return errorResponse
}
<commit_msg>Upgrade version<commit_after>package updown

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
)

const (
	libraryVersion = "0.3"
	defaultBaseURL = "https://updown.io/api/"
	userAgent      = "Go Updown v" + libraryVersion
	mediaType      = "application/json"
)

// An ErrorResponse reports the error caused by an API request
type ErrorResponse struct {
	// HTTP response that caused this error
	Response *http.Response

	// Error message
	Message string
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d %v",
		r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Message)
}

// Client manages communication the API
type Client struct {
	// HTTP client used to communicate with the API
	client *http.Client

	// Base URL for API requests
	BaseURL *url.URL

	// User agent for client
	UserAgent string

	// APIKey to use for the API
	APIKey string

	// Services used for communications with the API
	Check    CheckService
	Downtime DowntimeService
	Metric   MetricService
	Node     NodeService
}

// NewClient returns a new DigitalOcean API client.
func NewClient(apiKey string, httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}

	baseURL, _ := url.Parse(defaultBaseURL)

	c := &Client{
		client:    httpClient,
		BaseURL:   baseURL,
		UserAgent: userAgent,
		APIKey:    apiKey,
	}
	c.Check = CheckService{client: c, cache: NewMemoryCache()}
	c.Downtime = DowntimeService{client: c}
	c.Metric = MetricService{client: c}
	c.Node = NodeService{client: c}

	return c
}

// NewRequest creates an API request. A relative URL can be provided in urlStr, which will be resolved to the
// BaseURL of the Client. Relative URLS should always be specified without a preceding slash.
// If specified, the value pointed to by body is JSON encoded and included in as the request body.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
	rel, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}

	u := c.BaseURL.ResolveReference(rel)

	buf := new(bytes.Buffer)
	if body != nil {
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, u.String(), buf)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", mediaType)
	req.Header.Add("Accept", mediaType)
	req.Header.Add("User-Agent", userAgent)
	req.Header.Add("X-API-KEY", c.APIKey)
	return req, nil
}

// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
	response, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	defer func() {
		if rerr := response.Body.Close(); err == nil {
			err = rerr
		}
	}()

	err = CheckResponse(response)
	if err != nil {
		return response, err
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			_, err := io.Copy(w, response.Body)
			if err != nil {
				return nil, err
			}
		} else {
			err := json.NewDecoder(response.Body).Decode(v)
			if err != nil {
				return nil, err
			}
		}
	}

	return response, err
}

// CheckResponse checks the API response for errors, and returns them if present. A response is considered an
// error if it has a status code outside the 200 range. API error responses are expected to have either no response
// body, or a JSON response body that maps to ErrorResponse. Any other response body will be silently ignored.
func CheckResponse(r *http.Response) error {
	if c := r.StatusCode; c >= 200 && c <= 299 {
		return nil
	}

	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && len(data) > 0 {
		err := json.Unmarshal(data, errorResponse)
		if err != nil {
			return err
		}
	}

	return errorResponse
}
<|endoftext|>


<commit_before>/*
client.go implements RFC 6143 §7.5 Client-to-Server Messages.
See http://tools.ietf.org/html/rfc6143#section-7.5 for more info.
*/
package vnc

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"unicode"
)

const (
	setPixelFormatMsg = uint8(iota)
	_
	setEncodingsMsg
	framebufferUpdateRequestMsg
	keyEventMsg
	pointerEventMsg
	clientCutTextMsg
)

// SetPixelFormat sets the format in which pixel values should be sent
// in FramebufferUpdate messages from the server.
//
// See RFC 6143 Section 7.5.1
func (c *ClientConn) SetPixelFormat(pf PixelFormat) error {
	var buf bytes.Buffer

	// message-type
	if err := binary.Write(&buf, binary.BigEndian, setPixelFormatMsg); err != nil {
		return err
	}
	//padding
	padding := [3]byte{}
	buf.Write(padding[:])
	// pixel-format
	pfBytes, err := pf.Bytes()
	if err != nil {
		return err
	}
	buf.Write(pfBytes)

	// Send the data down the connection.
	if _, err := c.c.Write(buf.Bytes()); err != nil {
		return err
	}

	// Invalidate the color map.
	if pf.TrueColor == RFBFalse {
		c.colorMap = [256]Color{}
	}

	c.pixelFormat = pf
	return nil
}

type SetEncodingsMessage struct {
	Msg     uint8   // message-type
	Pad     [1]byte // padding
	NumEncs uint16  // number-of-encodings
	Encs    []int32 // encoding-type
}

// SetEncodings sets the encoding types in which the pixel data can be sent
// from the server. After calling this method, the encs slice given should not
// be modified.
//
// See RFC 6143 Section 7.5.2
func (c *ClientConn) SetEncodings(e []Encoding) error {
	// Prepare message.
	msg := SetEncodingsMessage{
		Msg:     setEncodingsMsg,
		NumEncs: uint16(len(e)),
	}
	for _, v := range e {
		msg.Encs = append(msg.Encs, int32(v.Type()))
	}

	// Send message.
	if err := binary.Write(c.c, binary.BigEndian, &msg.Msg); err != nil {
		return err
	}
	for i := range msg.Pad {
		if err := binary.Write(c.c, binary.BigEndian, &msg.Pad[i]); err != nil {
			return err
		}
	}
	if err := binary.Write(c.c, binary.BigEndian, &msg.NumEncs); err != nil {
		return err
	}
	for i := range msg.Encs {
		if err := binary.Write(c.c, binary.BigEndian, &msg.Encs[i]); err != nil {
			return err
		}
	}
	c.encodings = e

	return nil
}

// FramebufferUpdateRequestMessage defines a FramebufferUpdateRequest message.
type FramebufferUpdateRequestMessage struct {
	Msg                 uint8
	Inc                 uint8
	X, Y, Width, Height uint16
}

// Requests a framebuffer update from the server. There may be an indefinite
// time between the request and the actual framebuffer update being received.
//
// See RFC 6143 Section 7.5.3
func (c *ClientConn) FramebufferUpdateRequest(inc uint8, x, y, w, h uint16) error {
	msg := FramebufferUpdateRequestMessage{framebufferUpdateRequestMsg, inc, x, y, w, h}
	if err := binary.Write(c.c, binary.BigEndian, &msg); err != nil {
		return err
	}
	return nil
}

// KeyEventMessage holds the wire format message.
type KeyEventMessage struct {
	Msg      uint8
	DownFlag uint8
	_        [2]byte
	Key      uint32
}

const (
	PressKey   = true
	ReleaseKey = false
)

// KeyEvent indicates a key press or release and sends it to the server.
// The key is indicated using the X Window System "keysym" value. Use
// Google to find a reference of these values. To simulate a key press,
// you must send a key with both a down event, and a non-down event.
//
// See RFC 6143 Section 7.5.4.
func (c *ClientConn) KeyEvent(keysym uint32, down bool) error {
	var downFlag uint8 = RFBFalse
	if down {
		downFlag = RFBTrue
	}

	msg := KeyEventMessage{keyEventMsg, downFlag, [2]byte{}, keysym}
	if err := binary.Write(c.c, binary.BigEndian, msg); err != nil {
		return err
	}

	settleUI()
	return nil
}

// ButtonMask represents a mask of pointer presses/releases.
type ButtonMask uint8

// All available button mask components.
const (
	ButtonLeft ButtonMask = 1 << iota
	ButtonMiddle
	ButtonRight
	Button4
	Button5
	Button6
	Button7
	Button8
	ButtonNone = ButtonMask(0)
)

// PointerEventMessage holds the wire format message.
type PointerEventMessage struct {
	Msg  uint8
	Mask uint8
	X, Y uint16
}

// PointerEvent indicates that pointer movement or a pointer button
// press or release.
//
// The mask is a bitwise mask of various ButtonMask values. When a button
// is set, it is pressed, when it is unset, it is released.
//
// See RFC 6143 Section 7.5.5
func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error {
	msg := PointerEventMessage{pointerEventMsg, uint8(mask), x, y}
	if err := binary.Write(c.c, binary.BigEndian, msg); err != nil {
		return err
	}

	settleUI()
	return nil
}

// ClientCutText tells the server that the client has new text in its cut buffer.
// The text string MUST only contain Latin-1 characters. This encoding
// is compatible with Go's native string format, but can only use up to
// unicode.MaxLatin values.
//
// See RFC 6143 Section 7.5.6
func (c *ClientConn) ClientCutText(text string) error {
	var buf bytes.Buffer

	// This is the fixed size data we'll send
	fixedData := []interface{}{
		clientCutTextMsg,
		uint8(0),
		uint8(0),
		uint8(0),
		uint32(len(text)),
	}

	for _, val := range fixedData {
		if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
			return err
		}
	}

	for _, char := range text {
		if char > unicode.MaxLatin1 {
			return fmt.Errorf("Character '%s' is not valid Latin-1", char)
		}

		if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil {
			return err
		}
	}

	dataLength := 8 + len(text)
	if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil {
		return err
	}

	return nil
}

//
// Constants to use for KeyEvents PointerEvents.
//

// Latin 1 (byte 3 = 0)
// ISO/IEC 8859-1 = Unicode U+0020..U+00FF
const (
	KeySpace = iota + 0x0020
	KeyExclam
	KeyQuoteDbl
	KeyNumberSign
	KeyDollar
	KeyPercent
	KeyAmpersand
	KeyApostrophe
	KeyParenLeft
	KeyParenRight
	KeyAsterisk
	KeyPlus
	KeyComma
	KeyMinus
	KeyPeriod
	KeySlash
	Key0
	Key1
	Key2
	Key3
	Key4
	Key5
	Key6
	Key7
	Key8
	Key9
	KeyColon
	KeySemicolon
	KeyLess
	KeyEqual
	KeyGreater
	KeyQuestion
	KeyAt
	KeyA
	KeyB
	KeyC
	KeyD
	KeyE
	KeyF
	KeyG
	KeyH
	KeyI
	KeyJ
	KeyK
	KeyL
	KeyM
	KeyN
	KeyO
	KeyP
	KeyQ
	KeyR
	KeyS
	KeyT
	KeyU
	KeyV
	KeyW
	KeyX
	KeyY
	KeyZ
	KeyBracketLeft
	KeyBackslash
	KeyBracketRight
	KeyAsciiCircum
	KeyUnderscore
	KeyGrave
	Keya
	Keyb
	Keyc
	Keyd
	Keye
	Keyf
	Keyg
	Keyh
	Keyi
	Keyj
	Keyk
	Keyl
	Keym
	Keyn
	Keyo
	Keyp
	Keyq
	Keyr
	Keys
	Keyt
	Keyu
	Keyv
	Keyw
	Keyx
	Keyy
	Keyz
	KeyBraceLeft
	KeyBar
	KeyBraceRight
	KeyAsciiTilde
)
const (
	KeyBackspace = iota + 0xff08
	KeyTab
	KeyLinefeed
	KeyClear
	_
	KeyReturn
)
const (
	KeyPause      = 0xff13
	KeyScrollLock = 0xff14
	KeySysReq     = 0xff15
	KeyEscape     = 0xff1b
)
const (
	KeyHome = iota + 0xff50
	KeyLeft
	KeyUp
	KeyRight
	KeyDown
	KeyPageUp
	KeyPageDown
	KeyEnd
	KeyInsert = 0xff63
)
const (
	KeyF1 = iota + 0xffbe
	KeyF2
	KeyF3
	KeyF4
	KeyF5
	KeyF6
	KeyF7
	KeyF8
	KeyF9
	KeyF10
	KeyF11
	KeyF12
)
const (
	KeyShiftLeft = iota + 0xffe1
	KeyShiftRight
	KeyControlLeft
	KeyControlRight
	KeyCapsLock
	_
	_
	_
	KeyAltLeft
	KeyAltRight

	KeyDelete = 0xffff
)
<commit_msg>comment improvements<commit_after>/*
client.go implements RFC 6143 §7.5 Client-to-Server Messages.
See http://tools.ietf.org/html/rfc6143#section-7.5 for more info.
*/
package vnc

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"unicode"
)

const (
	setPixelFormatMsg = uint8(iota)
	_
	setEncodingsMsg
	framebufferUpdateRequestMsg
	keyEventMsg
	pointerEventMsg
	clientCutTextMsg
)

// SetPixelFormat sets the format in which pixel values should be sent
// in FramebufferUpdate messages from the server.
//
// See RFC 6143 Section 7.5.1
func (c *ClientConn) SetPixelFormat(pf PixelFormat) error {
	var buf bytes.Buffer

	// message-type
	if err := binary.Write(&buf, binary.BigEndian, setPixelFormatMsg); err != nil {
		return err
	}
	//padding
	padding := [3]byte{}
	buf.Write(padding[:])
	// pixel-format
	pfBytes, err := pf.Bytes()
	if err != nil {
		return err
	}
	buf.Write(pfBytes)

	// Send the data down the connection.
	if _, err := c.c.Write(buf.Bytes()); err != nil {
		return err
	}

	// Invalidate the color map.
	if pf.TrueColor == RFBFalse {
		c.colorMap = [256]Color{}
	}

	c.pixelFormat = pf
	return nil
}

// SetEncodingsMessage holds the wire format message.
type SetEncodingsMessage struct {
	Msg     uint8   // message-type
	Pad     [1]byte // padding
	NumEncs uint16  // number-of-encodings
	Encs    []int32 // encoding-type
}

// SetEncodings sets the encoding types in which the pixel data can be sent
// from the server. After calling this method, the encs slice given should not
// be modified.
//
// See RFC 6143 Section 7.5.2
func (c *ClientConn) SetEncodings(e []Encoding) error {
	// Prepare message.
	msg := SetEncodingsMessage{
		Msg:     setEncodingsMsg,
		NumEncs: uint16(len(e)),
	}
	for _, v := range e {
		msg.Encs = append(msg.Encs, int32(v.Type()))
	}

	// Send message.
	if err := binary.Write(c.c, binary.BigEndian, &msg.Msg); err != nil {
		return err
	}
	for i := range msg.Pad {
		if err := binary.Write(c.c, binary.BigEndian, &msg.Pad[i]); err != nil {
			return err
		}
	}
	if err := binary.Write(c.c, binary.BigEndian, &msg.NumEncs); err != nil {
		return err
	}
	for i := range msg.Encs {
		if err := binary.Write(c.c, binary.BigEndian, &msg.Encs[i]); err != nil {
			return err
		}
	}
	c.encodings = e

	return nil
}

// FramebufferUpdateRequestMessage holds the wire format message.
type FramebufferUpdateRequestMessage struct {
	Msg           uint8  // message-type
	Inc           uint8  // incremental
	X, Y          uint16 // x-, y-position
	Width, Height uint16 // width, height
}

// Requests a framebuffer update from the server. There may be an indefinite
// time between the request and the actual framebuffer update being received.
//
// See RFC 6143 Section 7.5.3
func (c *ClientConn) FramebufferUpdateRequest(inc uint8, x, y, w, h uint16) error {
	msg := FramebufferUpdateRequestMessage{framebufferUpdateRequestMsg, inc, x, y, w, h}
	if err := binary.Write(c.c, binary.BigEndian, &msg); err != nil {
		return err
	}
	return nil
}

// KeyEventMessage holds the wire format message.
type KeyEventMessage struct {
	Msg      uint8   // message-type
	DownFlag uint8   // down-flag
	_        [2]byte // padding
	Key      uint32  // key
}

const (
	PressKey   = true
	ReleaseKey = false
)

// KeyEvent indicates a key press or release and sends it to the server.
// The key is indicated using the X Window System "keysym" value. Use
// Google to find a reference of these values. To simulate a key press,
// you must send a key with both a down event, and a non-down event.
//
// See RFC 6143 Section 7.5.4.
func (c *ClientConn) KeyEvent(keysym uint32, down bool) error {
	var downFlag uint8 = RFBFalse
	if down {
		downFlag = RFBTrue
	}

	msg := KeyEventMessage{keyEventMsg, downFlag, [2]byte{}, keysym}
	if err := binary.Write(c.c, binary.BigEndian, msg); err != nil {
		return err
	}

	settleUI()
	return nil
}

// ButtonMask represents a mask of pointer presses/releases.
type ButtonMask uint8

// All available button mask components.
const (
	ButtonLeft ButtonMask = 1 << iota
	ButtonMiddle
	ButtonRight
	Button4
	Button5
	Button6
	Button7
	Button8
	ButtonNone = ButtonMask(0)
)

// PointerEventMessage holds the wire format message.
type PointerEventMessage struct {
	Msg  uint8  // message-type
	Mask uint8  // button-mask
	X, Y uint16 // x-, y-position
}

// PointerEvent indicates that pointer movement or a pointer button
// press or release.
//
// The mask is a bitwise mask of various ButtonMask values. When a button
// is set, it is pressed, when it is unset, it is released.
//
// See RFC 6143 Section 7.5.5
func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error {
	msg := PointerEventMessage{pointerEventMsg, uint8(mask), x, y}
	if err := binary.Write(c.c, binary.BigEndian, msg); err != nil {
		return err
	}

	settleUI()
	return nil
}

// ClientCutText tells the server that the client has new text in its cut buffer.
// The text string MUST only contain Latin-1 characters. This encoding
// is compatible with Go's native string format, but can only use up to
// unicode.MaxLatin1 values.
//
// See RFC 6143 Section 7.5.6
func (c *ClientConn) ClientCutText(text string) error {
	var buf bytes.Buffer

	// This is the fixed size data we'll send
	fixedData := []interface{}{
		clientCutTextMsg,
		uint8(0),
		uint8(0),
		uint8(0),
		uint32(len(text)),
	}

	for _, val := range fixedData {
		if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
			return err
		}
	}

	for _, char := range text {
		if char > unicode.MaxLatin1 {
			return fmt.Errorf("Character '%s' is not valid Latin-1", char)
		}

		if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil {
			return err
		}
	}

	dataLength := 8 + len(text)
	if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil {
		return err
	}

	return nil
}

//
// Constants to use for KeyEvents PointerEvents.
//

// Latin 1 (byte 3 = 0)
// ISO/IEC 8859-1 = Unicode U+0020..U+00FF
const (
	KeySpace = iota + 0x0020
	KeyExclam
	KeyQuoteDbl
	KeyNumberSign
	KeyDollar
	KeyPercent
	KeyAmpersand
	KeyApostrophe
	KeyParenLeft
	KeyParenRight
	KeyAsterisk
	KeyPlus
	KeyComma
	KeyMinus
	KeyPeriod
	KeySlash
	Key0
	Key1
	Key2
	Key3
	Key4
	Key5
	Key6
	Key7
	Key8
	Key9
	KeyColon
	KeySemicolon
	KeyLess
	KeyEqual
	KeyGreater
	KeyQuestion
	KeyAt
	KeyA
	KeyB
	KeyC
	KeyD
	KeyE
	KeyF
	KeyG
	KeyH
	KeyI
	KeyJ
	KeyK
	KeyL
	KeyM
	KeyN
	KeyO
	KeyP
	KeyQ
	KeyR
	KeyS
	KeyT
	KeyU
	KeyV
	KeyW
	KeyX
	KeyY
	KeyZ
	KeyBracketLeft
	KeyBackslash
	KeyBracketRight
	KeyAsciiCircum
	KeyUnderscore
	KeyGrave
	Keya
	Keyb
	Keyc
	Keyd
	Keye
	Keyf
	Keyg
	Keyh
	Keyi
	Keyj
	Keyk
	Keyl
	Keym
	Keyn
	Keyo
	Keyp
	Keyq
	Keyr
	Keys
	Keyt
	Keyu
	Keyv
	Keyw
	Keyx
	Keyy
	Keyz
	KeyBraceLeft
	KeyBar
	KeyBraceRight
	KeyAsciiTilde
)
const (
	KeyBackspace = iota + 0xff08
	KeyTab
	KeyLinefeed
	KeyClear
	_
	KeyReturn
)
const (
	KeyPause      = 0xff13
	KeyScrollLock = 0xff14
	KeySysReq     = 0xff15
	KeyEscape     = 0xff1b
)
const (
	KeyHome = iota + 0xff50
	KeyLeft
	KeyUp
	KeyRight
	KeyDown
	KeyPageUp
	KeyPageDown
	KeyEnd
	KeyInsert = 0xff63
)
const (
	KeyF1 = iota + 0xffbe
	KeyF2
	KeyF3
	KeyF4
	KeyF5
	KeyF6
	KeyF7
	KeyF8
	KeyF9
	KeyF10
	KeyF11
	KeyF12
)
const (
	KeyShiftLeft = iota + 0xffe1
	KeyShiftRight
	KeyControlLeft
	KeyControlRight
	KeyCapsLock
	_
	_
	_
	KeyAltLeft
	KeyAltRight

	KeyDelete = 0xffff
)
<|endoftext|>


<commit_before>package main

import (
  "bufio"
  "io"
  "net/http"
  "strings"
  "errors"
)

func getDockerfile(imageId string) (io.Reader, string, error){
  url := ""
  repository := strings.Split(imageId, ":")
  if len(repository)>1 || strings.Index(imageId, "/")<0 {
    if strings.Index(imageId, "/")<0 {
      repository = append(repository, "latest")
    }
    resp, err := http.Get("https://raw.githubusercontent.com/docker-library/official-images/master/library/" + repository[0])
    if err==nil {
      scanner := bufio.NewScanner(resp.Body)
      scanner.Split(bufio.ScanLines)
      for scanner.Scan() {
        image := strings.Split(scanner.Text(), " ")
        if image[0]==repository[1]+":" {
          r := strings.NewReplacer("git://github.com", "https://raw.githubusercontent.com", "@", "/")
          url = r.Replace(image[1])
          if len(image)>2 {
            url += "/" + image[2] + "/Dockerfile"
          } else {
            url += "/Dockerfile"
          }
        }
      }
      if url == "" {
        return nil, url, errors.New("Failed to fetch " + imageId)
      }
    } else {
      return nil, url, errors.New("Failed to fetch " + imageId)
    }
  } else {
    url = "https://registry.hub.docker.com/u/" + imageId + "/dockerfile/raw"
  }
  resp, err := http.Get(url)
  if err==nil && strings.Index(resp.Header["Content-Type"][0], "text/plain")>=0 {
    return resp.Body, url, err
  } else {
    return nil, url, errors.New("Failed to fetch " + imageId)
  }
}
<commit_msg>Formatted client.go<commit_after>package main

import (
	"bufio"
	"errors"
	"io"
	"net/http"
	"strings"
)

func getDockerfile(imageId string) (io.Reader, string, error) {
	url := ""
	repository := strings.Split(imageId, ":")
	if len(repository) > 1 || strings.Index(imageId, "/") < 0 {
		if strings.Index(imageId, "/") < 0 {
			repository = append(repository, "latest")
		}
		resp, err := http.Get("https://raw.githubusercontent.com/docker-library/official-images/master/library/" + repository[0])
		if err == nil {
			scanner := bufio.NewScanner(resp.Body)
			scanner.Split(bufio.ScanLines)
			for scanner.Scan() {
				image := strings.Split(scanner.Text(), " ")
				if image[0] == repository[1]+":" {
					r := strings.NewReplacer("git://github.com", "https://raw.githubusercontent.com", "@", "/")
					url = r.Replace(image[1])
					if len(image) > 2 {
						url += "/" + image[2] + "/Dockerfile"
					} else {
						url += "/Dockerfile"
					}
				}
			}
			if url == "" {
				return nil, url, errors.New("Failed to fetch " + imageId)
			}
		} else {
			return nil, url, errors.New("Failed to fetch " + imageId)
		}
	} else {
		url = "https://registry.hub.docker.com/u/" + imageId + "/dockerfile/raw"
	}
	resp, err := http.Get(url)
	if err == nil && strings.Index(resp.Header["Content-Type"][0], "text/plain") >= 0 {
		return resp.Body, url, err
	} else {
		return nil, url, errors.New("Failed to fetch " + imageId)
	}
}
<|endoftext|>


<commit_before>package turnpike

import (
	"fmt"
	"time"
)

type Role int

const (
	// This client can publish events
	PUBLISHER Role = 1 << iota
	// This client can subscribe to events
	SUBSCRIBER
	// This client can register RPC functions
	CALLEE
	// This client can call RPC functions
	CALLER
	// This client can do all of the above
	ALLROLES = PUBLISHER | SUBSCRIBER | CALLEE | CALLER
)

var (
	abortUnexpectedMsg = &Abort{
		Details: map[string]interface{}{},
		Reason:  "turnpike.error.unexpected_message_type",
	}
	abortNoAuthHandler = &Abort{
		Details: map[string]interface{}{},
		Reason:  "turnpike.error.no_handler_for_authmethod",
	}
	abortAuthFailure = &Abort{
		Details: map[string]interface{}{},
		Reason:  "turnpike.error.authentication_failure",
	}
	goodbyeClient = &Goodbye{
		Details: map[string]interface{}{},
		Reason:  WAMP_ERROR_CLOSE_REALM,
	}
)

// A Client routes messages to/from a WAMP router.
type Client struct {
	Peer
	// ReceiveTimeout is the amount of time that the client will block waiting for a response from the router.
	ReceiveTimeout time.Duration
	// roles          int
	listeners    map[ID]chan Message
	events       map[ID]EventHandler
	procedures   map[ID]MethodHandler
	requestCount uint
}

// Creates a new websocket client.
func NewWebsocketClient(serialization Serialization, url string) (*Client, error) {
	p, err := NewWebsocketPeer(serialization, url, "")
	if err != nil {
		return nil, err
	}
	return NewClient(p), nil
}

// NewClient takes a connected Peer and returns a new Client
func NewClient(p Peer) *Client {
	c := &Client{
		Peer:           p,
		ReceiveTimeout: 5 * time.Second,
		// roles:          roles,
		listeners:    make(map[ID]chan Message),
		events:       make(map[ID]EventHandler),
		procedures:   make(map[ID]MethodHandler),
		requestCount: 0,
	}
	return c
}

// JoinRealm joins a WAMP realm, but does not handle challenge/response authentication.
func (c *Client) JoinRealm(realm string, roles Role, details map[string]interface{}) (map[string]interface{}, error) {
	if details == nil {
		details = map[string]interface{}{}
	}
	details["roles"] = createRolesMap(roles)
	if err := c.Send(&Hello{Realm: URI(realm), Details: details}); err != nil {
		c.Peer.Close()
		return nil, err
	}
	if msg, err := GetMessageTimeout(c.Peer, c.ReceiveTimeout); err != nil {
		c.Peer.Close()
		return nil, err
	} else if welcome, ok := msg.(*Welcome); !ok {
		c.Send(abortUnexpectedMsg)
		c.Peer.Close()
		return nil, fmt.Errorf(formatUnexpectedMessage(msg, WELCOME))
	} else {
		go c.Receive()
		return welcome.Details, nil
	}
}

// AuthFunc takes the HELLO details and CHALLENGE details and returns the
// signature string and a details map
type AuthFunc func(map[string]interface{}, map[string]interface{}) (string, map[string]interface{}, error)

// JoinRealmCRA joins a WAMP realm and handles challenge/response authentication.
func (c *Client) JoinRealmCRA(realm string, roles Role, details map[string]interface{}, auth map[string]AuthFunc) (map[string]interface{}, error) {
	if auth == nil || len(auth) == 0 {
		return nil, fmt.Errorf("no authentication methods provided")
	}
	if details == nil || len(details) == 0 {
		return nil, fmt.Errorf("no details map provided")
	}
	details["roles"] = createRolesMap(roles)
	if err := c.Send(&Hello{Realm: URI(realm), Details: details}); err != nil {
		c.Peer.Close()
		return nil, err
	}
	if msg, err := GetMessageTimeout(c.Peer, c.ReceiveTimeout); err != nil {
		c.Peer.Close()
		return nil, err
	} else if challenge, ok := msg.(*Challenge); !ok {
		c.Send(abortUnexpectedMsg)
		c.Peer.Close()
		return nil, fmt.Errorf(formatUnexpectedMessage(msg, CHALLENGE))
	} else if authFunc, ok := auth[challenge.AuthMethod]; !ok {
		c.Send(abortNoAuthHandler)
		c.Peer.Close()
		return nil, fmt.Errorf("no auth handler for method: %s", challenge.AuthMethod)
	} else if signature, authDetails, err := authFunc(details, challenge.Extra); err != nil {
		c.Send(abortAuthFailure)
		c.Peer.Close()
		return nil, err
	} else if err := c.Send(&Authenticate{Signature: signature, Extra: authDetails}); err != nil {
		c.Peer.Close()
		return nil, err
	}
	if msg, err := GetMessageTimeout(c.Peer, c.ReceiveTimeout); err != nil {
		c.Peer.Close()
		return nil, err
	} else if welcome, ok := msg.(*Welcome); !ok {
		c.Send(abortUnexpectedMsg)
		c.Peer.Close()
		return nil, fmt.Errorf(formatUnexpectedMessage(msg, WELCOME))
	} else {
		go c.Receive()
		return welcome.Details, nil
	}
}

func createRolesMap(roles Role) map[string]interface{} {
	rolesMap := make(map[string]interface{})
	if roles&PUBLISHER == PUBLISHER {
		rolesMap["publisher"] = make(map[string]interface{})
	}
	if roles&SUBSCRIBER == SUBSCRIBER {
		rolesMap["subscriber"] = make(map[string]interface{})
	}
	if roles&CALLEE == CALLEE {
		rolesMap["callee"] = make(map[string]interface{})
	}
	if roles&CALLER == CALLER {
		rolesMap["caller"] = make(map[string]interface{})
	}
	return rolesMap
}

func formatUnexpectedMessage(msg Message, expected MessageType) string {
	s := fmt.Sprintf("received unexpected %s message while waiting for %s", msg.MessageType(), expected)
	switch m := msg.(type) {
	case *Abort:
		s += ": " + string(m.Reason)
		s += formatUnknownMap(m.Details)
		return s
	case *Goodbye:
		s += ": " + string(m.Reason)
		s += formatUnknownMap(m.Details)
		return s
	}
	return s
}

func formatUnknownMap(m map[string]interface{}) string {
	s := ""
	for k, v := range m {
		// TODO: reflection to recursively check map
		s += fmt.Sprintf(" %s=%v", k, v)
	}
	return s
}

// LeaveRealm leaves the current realm without closing the connection to the server.
func (c *Client) LeaveRealm() {
	c.Send(goodbyeClient)
}

// Close closes the connection to the server.
func (c *Client) Close() {
	c.Send(goodbyeClient)
	c.Peer.Close()
}

func (c *Client) nextID() ID {
	c.requestCount++
	return ID(c.requestCount)
}

// Receive handles messages from the server until this client disconnects.
//
// This function blocks and is most commonly run in a goroutine.
func (c *Client) Receive() {
	for msg := range c.Peer.Receive() {

		switch msg := msg.(type) {

		case *Event:
			if fn, ok := c.events[msg.Subscription]; ok {
				go fn(msg.Arguments, msg.ArgumentsKw)
			} else {
				log.Println("no handler registered for subscription:", msg.Subscription)
			}

		case *Invocation:
			c.handleInvocation(msg)

		case *Registered:
			c.notifyListener(msg, msg.Request)

		case *Subscribed:
			c.notifyListener(msg, msg.Request)

		case *Result:
			c.notifyListener(msg, msg.Request)

		case *Error:
			c.notifyListener(msg, msg.Request)

		default:
			log.Println("unhandled message:", msg.MessageType(), msg)
		}
	}
	log.Fatal("Receive buffer closed")
}

func (c *Client) notifyListener(msg Message, requestId ID) {
	// pass in the request ID so we don't have to do any type assertion
	if l, ok := c.listeners[requestId]; ok {
		l <- msg
	} else {
		log.Println("no listener for message", msg.MessageType(), requestId)
	}
}

func (c *Client) handleInvocation(msg *Invocation) {
	if fn, ok := c.procedures[msg.Registration]; ok {
		go func() {
			result := fn(msg.Arguments, msg.ArgumentsKw)

			var tosend Message
			tosend = &Yield{
				Request:     msg.Request,
				Options:     make(map[string]interface{}),
				Arguments:   result.Args,
				ArgumentsKw: result.Kwargs,
			}

			if result.Err != "" {
				tosend = &Error{
					Type:        INVOCATION,
					Request:     msg.Request,
					Details:     make(map[string]interface{}),
					Arguments:   result.Args,
					ArgumentsKw: result.Kwargs,
					Error:       result.Err,
				}
			}

			if err := c.Send(tosend); err != nil {
				log.Fatal(err)
			}
		}()
	} else {
		log.Println("no handler registered for registration:", msg.Registration)
	}
}

func (c *Client) registerListener(id ID) {
	log.Println("register listener:", id)
	wait := make(chan Message, 1)
	c.listeners[id] = wait
}

func (c *Client) waitOnListener(id ID) (msg Message, err error) {
	log.Println("wait on listener:", id)
	if wait, ok := c.listeners[id]; !ok {
		return nil, fmt.Errorf("unknown listener ID: %v", id)
	} else {
		select {
		case msg = <-wait:
			return
		case <-time.After(c.ReceiveTimeout):
			err = fmt.Errorf("timeout while waiting for message")
			return
		}
	}
}

// EventHandler handles a publish event.
type EventHandler func(args []interface{}, kwargs map[string]interface{})

// Subscribe registers the EventHandler to be called for every message in the provided topic.
func (c *Client) Subscribe(topic string, fn EventHandler) error {
	id := c.nextID()
	c.registerListener(id)
	sub := &Subscribe{
		Request: id,
		Options: make(map[string]interface{}),
		Topic:   URI(topic),
	}
	if err := c.Send(sub); err != nil {
		return err
	}
	// wait to receive SUBSCRIBED message
	msg, err := c.waitOnListener(id)
	if err != nil {
		return err
	} else if e, ok := msg.(*Error); ok {
		return fmt.Errorf("error subscribing to topic '%v': %v", topic, e.Error)
	} else if subscribed, ok := msg.(*Subscribed); !ok {
		return fmt.Errorf(formatUnexpectedMessage(msg, SUBSCRIBED))
	} else {
		// register the event handler with this subscription
		c.events[subscribed.Subscription] = fn
	}
	return nil
}

// MethodHandler is an RPC endpoint.
type MethodHandler func(args []interface{}, kwargs map[string]interface{}) (result *CallResult)

// Register registers a procedure with the router.
func (c *Client) Register(procedure string, fn MethodHandler) error {
	id := c.nextID()
	c.registerListener(id)
	register := &Register{
		Request:   id,
		Options:   make(map[string]interface{}),
		Procedure: URI(procedure),
	}
	if err := c.Send(register); err != nil {
		return err
	}

	// wait to receive REGISTERED message
	msg, err := c.waitOnListener(id)
	if err != nil {
		return err
	} else if e, ok := msg.(*Error); ok {
		return fmt.Errorf("error registering to procedure '%v': %v", procedure, e.Error)
	} else if registered, ok := msg.(*Registered); !ok {
		return fmt.Errorf(formatUnexpectedMessage(msg, REGISTERED))
	} else {
		// register the event handler with this registration
		c.procedures[registered.Registration] = fn
	}
	return nil
}

// Publish publishes an EVENT to all subscribed peers.
func (c *Client) Publish(topic string, args []interface{}, kwargs map[string]interface{}) error {
	return c.Send(&Publish{
		Request:     c.nextID(),
		Options:     make(map[string]interface{}),
		Topic:       URI(topic),
		Arguments:   args,
		ArgumentsKw: kwargs,
	})
}

// Call calls a procedure given a URI.
func (c *Client) Call(procedure string, args []interface{}, kwargs map[string]interface{}) (Message, error) {
	id := c.nextID()
	c.registerListener(id)

	call := &Call{
		Request:     id,
		Procedure:   URI(procedure),
		Options:     make(map[string]interface{}),
		Arguments:   args,
		ArgumentsKw: kwargs,
	}
	if err := c.Send(call); err != nil {
		return nil, err
	}

	// wait to receive RESULT message
	msg, err := c.waitOnListener(id)
	if err != nil {
		return nil, err
	} else if e, ok := msg.(*Error); ok {
		return nil, fmt.Errorf("error registering to procedure '%v': %v", procedure, e.Error)
	} else if _, ok := msg.(*Result); !ok {
		return nil, fmt.Errorf(formatUnexpectedMessage(msg, RESULT))
	} else {
		return msg, nil
	}
}
<commit_msg>Add Client.Unsubscribe and Client.Unregister<commit_after>package turnpike

import (
	"fmt"
	"time"
)

type Role int

const (
	// This client can publish events
	PUBLISHER Role = 1 << iota
	// This client can subscribe to events
	SUBSCRIBER
	// This client can register RPC functions
	CALLEE
	// This client can call RPC functions
	CALLER
	// This client can do all of the above
	ALLROLES = PUBLISHER | SUBSCRIBER | CALLEE | CALLER
)

var (
	abortUnexpectedMsg = &Abort{
		Details: map[string]interface{}{},
		Reason:  "turnpike.error.unexpected_message_type",
	}
	abortNoAuthHandler = &Abort{
		Details: map[string]interface{}{},
		Reason:  "turnpike.error.no_handler_for_authmethod",
	}
	abortAuthFailure = &Abort{
		Details: map[string]interface{}{},
		Reason:  "turnpike.error.authentication_failure",
	}
	goodbyeClient = &Goodbye{
		Details: map[string]interface{}{},
		Reason:  WAMP_ERROR_CLOSE_REALM,
	}
)

// A Client routes messages to/from a WAMP router.
type Client struct {
	Peer
	// ReceiveTimeout is the amount of time that the client will block waiting for a response from the router.
	ReceiveTimeout time.Duration
	// roles          int
	listeners    map[ID]chan Message
	events       map[ID]*eventDesc
	procedures   map[ID]*procedureDesc
	requestCount uint
}

type procedureDesc struct {
	name    string
	handler MethodHandler
}

type eventDesc struct {
	topic   string
	handler EventHandler
}

// Creates a new websocket client.
func NewWebsocketClient(serialization Serialization, url string) (*Client, error) {
	p, err := NewWebsocketPeer(serialization, url, "")
	if err != nil {
		return nil, err
	}
	return NewClient(p), nil
}

// NewClient takes a connected Peer and returns a new Client
func NewClient(p Peer) *Client {
	c := &Client{
		Peer:           p,
		ReceiveTimeout: 5 * time.Second,
		// roles:          roles,
		listeners:    make(map[ID]chan Message),
		events:       make(map[ID]*eventDesc),
		procedures:   make(map[ID]*procedureDesc),
		requestCount: 0,
	}
	return c
}

// JoinRealm joins a WAMP realm, but does not handle challenge/response authentication.
func (c *Client) JoinRealm(realm string, roles Role, details map[string]interface{}) (map[string]interface{}, error) {
	if details == nil {
		details = map[string]interface{}{}
	}
	details["roles"] = createRolesMap(roles)
	if err := c.Send(&Hello{Realm: URI(realm), Details: details}); err != nil {
		c.Peer.Close()
		return nil, err
	}
	if msg, err := GetMessageTimeout(c.Peer, c.ReceiveTimeout); err != nil {
		c.Peer.Close()
		return nil, err
	} else if welcome, ok := msg.(*Welcome); !ok {
		c.Send(abortUnexpectedMsg)
		c.Peer.Close()
		return nil, fmt.Errorf(formatUnexpectedMessage(msg, WELCOME))
	} else {
		go c.Receive()
		return welcome.Details, nil
	}
}

// AuthFunc takes the HELLO details and CHALLENGE details and returns the
// signature string and a details map
type AuthFunc func(map[string]interface{}, map[string]interface{}) (string, map[string]interface{}, error)

// JoinRealmCRA joins a WAMP realm and handles challenge/response authentication.
func (c *Client) JoinRealmCRA(realm string, roles Role, details map[string]interface{}, auth map[string]AuthFunc) (map[string]interface{}, error) {
	if auth == nil || len(auth) == 0 {
		return nil, fmt.Errorf("no authentication methods provided")
	}
	if details == nil || len(details) == 0 {
		return nil, fmt.Errorf("no details map provided")
	}
	details["roles"] = createRolesMap(roles)
	if err := c.Send(&Hello{Realm: URI(realm), Details: details}); err != nil {
		c.Peer.Close()
		return nil, err
	}
	if msg, err := GetMessageTimeout(c.Peer, c.ReceiveTimeout); err != nil {
		c.Peer.Close()
		return nil, err
	} else if challenge, ok := msg.(*Challenge); !ok {
		c.Send(abortUnexpectedMsg)
		c.Peer.Close()
		return nil, fmt.Errorf(formatUnexpectedMessage(msg, CHALLENGE))
	} else if authFunc, ok := auth[challenge.AuthMethod]; !ok {
		c.Send(abortNoAuthHandler)
		c.Peer.Close()
		return nil, fmt.Errorf("no auth handler for method: %s", challenge.AuthMethod)
	} else if signature, authDetails, err := authFunc(details, challenge.Extra); err != nil {
		c.Send(abortAuthFailure)
		c.Peer.Close()
		return nil, err
	} else if err := c.Send(&Authenticate{Signature: signature, Extra: authDetails}); err != nil {
		c.Peer.Close()
		return nil, err
	}
	if msg, err := GetMessageTimeout(c.Peer, c.ReceiveTimeout); err != nil {
		c.Peer.Close()
		return nil, err
	} else if welcome, ok := msg.(*Welcome); !ok {
		c.Send(abortUnexpectedMsg)
		c.Peer.Close()
		return nil, fmt.Errorf(formatUnexpectedMessage(msg, WELCOME))
	} else {
		go c.Receive()
		return welcome.Details, nil
	}
}

func createRolesMap(roles Role) map[string]interface{} {
	rolesMap := make(map[string]interface{})
	if roles&PUBLISHER == PUBLISHER {
		rolesMap["publisher"] = make(map[string]interface{})
	}
	if roles&SUBSCRIBER == SUBSCRIBER {
		rolesMap["subscriber"] = make(map[string]interface{})
	}
	if roles&CALLEE == CALLEE {
		rolesMap["callee"] = make(map[string]interface{})
	}
	if roles&CALLER == CALLER {
		rolesMap["caller"] = make(map[string]interface{})
	}
	return rolesMap
}

func formatUnexpectedMessage(msg Message, expected MessageType) string {
	s := fmt.Sprintf("received unexpected %s message while waiting for %s", msg.MessageType(), expected)
	switch m := msg.(type) {
	case *Abort:
		s += ": " + string(m.Reason)
		s += formatUnknownMap(m.Details)
		return s
	case *Goodbye:
		s += ": " + string(m.Reason)
		s += formatUnknownMap(m.Details)
		return s
	}
	return s
}

func formatUnknownMap(m map[string]interface{}) string {
	s := ""
	for k, v := range m {
		// TODO: reflection to recursively check map
		s += fmt.Sprintf(" %s=%v", k, v)
	}
	return s
}

// LeaveRealm leaves the current realm without closing the connection to the server.
func (c *Client) LeaveRealm() {
	c.Send(goodbyeClient)
}

// Close closes the connection to the server.
func (c *Client) Close() {
	c.Send(goodbyeClient)
	c.Peer.Close()
}

func (c *Client) nextID() ID {
	c.requestCount++
	return ID(c.requestCount)
}

// Receive handles messages from the server until this client disconnects.
//
// This function blocks and is most commonly run in a goroutine.
func (c *Client) Receive() {
	for msg := range c.Peer.Receive() {

		switch msg := msg.(type) {

		case *Event:
			if event, ok := c.events[msg.Subscription]; ok {
				go event.handler(msg.Arguments, msg.ArgumentsKw)
			} else {
				log.Println("no handler registered for subscription:", msg.Subscription)
			}

		case *Invocation:
			c.handleInvocation(msg)

		case *Registered:
			c.notifyListener(msg, msg.Request)

		case *Subscribed:
			c.notifyListener(msg, msg.Request)

		case *Result:
			c.notifyListener(msg, msg.Request)

		case *Error:
			c.notifyListener(msg, msg.Request)

		default:
			log.Println("unhandled message:", msg.MessageType(), msg)
		}
	}
	log.Fatal("Receive buffer closed")
}

func (c *Client) notifyListener(msg Message, requestId ID) {
	// pass in the request ID so we don't have to do any type assertion
	if l, ok := c.listeners[requestId]; ok {
		l <- msg
	} else {
		log.Println("no listener for message", msg.MessageType(), requestId)
	}
}

func (c *Client) handleInvocation(msg *Invocation) {
	if proc, ok := c.procedures[msg.Registration]; ok {
		go func() {
			result := proc.handler(msg.Arguments, msg.ArgumentsKw)

			var tosend Message
			tosend = &Yield{
				Request:     msg.Request,
				Options:     make(map[string]interface{}),
				Arguments:   result.Args,
				ArgumentsKw: result.Kwargs,
			}

			if result.Err != "" {
				tosend = &Error{
					Type:        INVOCATION,
					Request:     msg.Request,
					Details:     make(map[string]interface{}),
					Arguments:   result.Args,
					ArgumentsKw: result.Kwargs,
					Error:       result.Err,
				}
			}

			if err := c.Send(tosend); err != nil {
				log.Fatal(err)
			}
		}()
	} else {
		log.Println("no handler registered for registration:", msg.Registration)
	}
}

func (c *Client) registerListener(id ID) {
	log.Println("register listener:", id)
	wait := make(chan Message, 1)
	c.listeners[id] = wait
}

func (c *Client) waitOnListener(id ID) (msg Message, err error) {
	log.Println("wait on listener:", id)
	if wait, ok := c.listeners[id]; !ok {
		return nil, fmt.Errorf("unknown listener ID: %v", id)
	} else {
		select {
		case msg = <-wait:
			return
		case <-time.After(c.ReceiveTimeout):
			err = fmt.Errorf("timeout while waiting for message")
			return
		}
	}
}

// EventHandler handles a publish event.
type EventHandler func(args []interface{}, kwargs map[string]interface{})

// Subscribe registers the EventHandler to be called for every message in the provided topic.
func (c *Client) Subscribe(topic string, fn EventHandler) error {
	id := c.nextID()
	c.registerListener(id)
	sub := &Subscribe{
		Request: id,
		Options: make(map[string]interface{}),
		Topic:   URI(topic),
	}
	if err := c.Send(sub); err != nil {
		return err
	}
	// wait to receive SUBSCRIBED message
	msg, err := c.waitOnListener(id)
	if err != nil {
		return err
	} else if e, ok := msg.(*Error); ok {
		return fmt.Errorf("error subscribing to topic '%v': %v", topic, e.Error)
	} else if subscribed, ok := msg.(*Subscribed); !ok {
		return fmt.Errorf(formatUnexpectedMessage(msg, SUBSCRIBED))
	} else {
		// register the event handler with this subscription
		c.events[subscribed.Subscription] = &eventDesc{topic, fn}
	}
	return nil
}

// Unsubscribe removes the registered EventHandler from the topic.
func (c *Client) Unsubscribe(topic string) error {
	var (
		subscriptionID ID
		found          bool
	)
	for id, desc := range c.events {
		if desc.topic == topic {
			subscriptionID = id
			found = true
		}
	}
	if !found {
		return fmt.Errorf("Event %s is not registered with this client.", topic)
	}

	id := c.nextID()
	c.registerListener(id)
	sub := &Unsubscribe{
		Request:      id,
		Subscription: subscriptionID,
	}
	if err := c.Send(sub); err != nil {
		return err
	}
	// wait to receive SUBSCRIBED message
	msg, err := c.waitOnListener(id)
	if err != nil {
		return err
	} else if e, ok := msg.(*Error); ok {
		return fmt.Errorf("error unsubscribing to topic '%v': %v", topic, e.Error)
	} else if _, ok := msg.(*Unsubscribed); !ok {
		return fmt.Errorf(formatUnexpectedMessage(msg, UNSUBSCRIBED))
	}
	delete(c.events, subscriptionID)
	return nil
}

// MethodHandler is an RPC endpoint.
type MethodHandler func(args []interface{}, kwargs map[string]interface{}) (result *CallResult)

// Register registers a procedure with the router.
func (c *Client) Register(procedure string, fn MethodHandler) error {
	id := c.nextID()
	c.registerListener(id)
	register := &Register{
		Request:   id,
		Options:   make(map[string]interface{}),
		Procedure: URI(procedure),
	}
	if err := c.Send(register); err != nil {
		return err
	}

	// wait to receive REGISTERED message
	msg, err := c.waitOnListener(id)
	if err != nil {
		return err
	} else if e, ok := msg.(*Error); ok {
		return fmt.Errorf("error registering to procedure '%v': %v", procedure, e.Error)
	} else if registered, ok := msg.(*Registered); !ok {
		return fmt.Errorf(formatUnexpectedMessage(msg, REGISTERED))
	} else {
		// register the event handler with this registration
		c.procedures[registered.Registration] = &procedureDesc{procedure, fn}
	}
	return nil
}

// Unregister removes a procedure with the router
func (c *Client) Unregister(procedure string) error {
	var (
		procedureID ID
		found       bool
	)
	for id, p := range c.procedures {
		if p.name == procedure {
			procedureID = id
			found = true
		}
	}
	if !found {
		return fmt.Errorf("Procedure %s is not registered with this client.", procedure)
	}
	id := c.nextID()
	c.registerListener(id)
	unregister := &Unregister{
		Request:      id,
		Registration: procedureID,
	}
	if err := c.Send(unregister); err != nil {
		return err
	}

	// wait to receive UNREGISTERED message
	msg, err := c.waitOnListener(id)
	if err != nil {
		return err
	} else if e, ok := msg.(*Error); ok {
		return fmt.Errorf("error unregister to procedure '%v': %v", procedure, e.Error)
	} else if _, ok := msg.(*Unregistered); !ok {
		return fmt.Errorf(formatUnexpectedMessage(msg, UNREGISTERED))
	}
	// register the event handler with this unregistration
	delete(c.procedures, procedureID)
	return nil
}

// Publish publishes an EVENT to all subscribed peers.
func (c *Client) Publish(topic string, args []interface{}, kwargs map[string]interface{}) error {
	return c.Send(&Publish{
		Request:     c.nextID(),
		Options:     make(map[string]interface{}),
		Topic:       URI(topic),
		Arguments:   args,
		ArgumentsKw: kwargs,
	})
}

// Call calls a procedure given a URI.
func (c *Client) Call(procedure string, args []interface{}, kwargs map[string]interface{}) (Message, error) {
	id := c.nextID()
	c.registerListener(id)

	call := &Call{
		Request:     id,
		Procedure:   URI(procedure),
		Options:     make(map[string]interface{}),
		Arguments:   args,
		ArgumentsKw: kwargs,
	}
	if err := c.Send(call); err != nil {
		return nil, err
	}

	// wait to receive RESULT message
	msg, err := c.waitOnListener(id)
	if err != nil {
		return nil, err
	} else if e, ok := msg.(*Error); ok {
		return nil, fmt.Errorf("error registering to procedure '%v': %v", procedure, e.Error)
	} else if _, ok := msg.(*Result); !ok {
		return nil, fmt.Errorf(formatUnexpectedMessage(msg, RESULT))
	} else {
		return msg, nil
	}
}
<|endoftext|>