<commit_before>package signalr

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

	"github.com/gorilla/websocket"
)

type negotiationResponse struct {
	Url                     string
	ConnectionToken         string
	ConnectionId            string
	KeepAliveTimeout        float32
	DisconnectTimeout       float32
	ConnectionTimeout       float32
	TryWebSockets           bool
	ProtocolVersion         string
	TransportConnectTimeout float32
	LogPollDelay            float32
}

type Client struct {
	OnMessageError func(err error)
	OnClientMethod func(hub, method string, arguments []json.RawMessage)
	params         negotiationResponse
	socket         *websocket.Conn
	nextId         int

	// Futures for server call responses and a guarding mutex.
	responseFutures map[string]chan *serverMessage
	mutex           sync.Mutex
	dispatchRunning bool
}

type serverMessage struct {
	Cursor     string            `json:"C"`
	Data       []json.RawMessage `json:"M"`
	Result     json.RawMessage   `json:"R"`
	Identifier string            `json:"I"`
	Error      string            `json:"E"`
}

func negotiate(scheme, address string) (negotiationResponse, error) {
	var response negotiationResponse

	var negotiationUrl = url.URL{Scheme: scheme, Host: address, Path: "/signalr/negotiate"}

	client := &http.Client{}

	reply, err := client.Get(negotiationUrl.String())
	if err != nil {
		return response, err
	}

	defer reply.Body.Close()

	if body, err := ioutil.ReadAll(reply.Body); err != nil {
		return response, err
	} else if err := json.Unmarshal(body, &response); err != nil {
		return response, err
	} else {
		return response, nil
	}
}

func connectWebsocket(address string, params negotiationResponse, hubs []string) (*websocket.Conn, error) {
	var connectionData = make([]struct {
		Name string `json:"Name"`
	}, len(hubs))
	for i, h := range hubs {
		connectionData[i].Name = h
	}
	connectionDataBytes, err := json.Marshal(connectionData)
	if err != nil {
		return nil, err
	}

	var connectionParameters = url.Values{}
	connectionParameters.Set("transport", "webSockets")
	connectionParameters.Set("clientProtocol", "1.5")
	connectionParameters.Set("connectionToken", params.ConnectionToken)
	connectionParameters.Set("connectionData", string(connectionDataBytes))

	var connectionUrl = url.URL{Scheme: "wss", Host: address, Path: "signalr/connect"}
	connectionUrl.RawQuery = connectionParameters.Encode()

	if conn, _, err := websocket.DefaultDialer.Dial(connectionUrl.String(), nil); err != nil {
		return nil, err
	} else {
		return conn, nil
	}
}

func (self *Client) routeResponse(response *serverMessage) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if c, ok := self.responseFutures[response.Identifier]; ok {
		c <- response
		close(c)
		delete(self.responseFutures, response.Identifier)
	}
}

func (self *Client) createResponseFuture(identifier string) (chan *serverMessage, error) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if !self.dispatchRunning {
		return nil, fmt.Errorf("Dispatch is not running")
	}

	var c = make(chan *serverMessage)
	self.responseFutures[identifier] = c

	return c, nil
}

func (self *Client) deleteResponseFuture(identifier string) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	delete(self.responseFutures, identifier)
}

func (self *Client) tryStartDispatch() error {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if self.dispatchRunning {
		return fmt.Errorf("Another Dispatch() is running")
	}
	self.dispatchRunning = true

	return nil
}

func (self *Client) endDispatch() {
	// Close all the waiting response futures.
	self.mutex.Lock()
	defer self.mutex.Unlock()
	self.dispatchRunning = false
	for _, c := range self.responseFutures {
		close(c)
	}
	self.responseFutures = make(map[string]chan *serverMessage)
}

// Start dispatch loop. This function will return when error occurs. When this
// happens, all the connections are closed and user can run Connect()
// and Dispatch() again on the same client.
func (self *Client) Dispatch() error {
	if err := self.tryStartDispatch(); err != nil {
		return err
	}

	defer self.endDispatch()

	for {
		var message serverMessage

		var hubCall struct {
			HubName   string            `json:"H"`
			Method    string            `json:"M"`
			Arguments []json.RawMessage `json:"A"`
		}

		_, data, err := self.socket.ReadMessage()
		if err != nil {
			self.socket.Close()
			return err
		} else if err := json.Unmarshal(data, &message); err != nil {
			if self.OnMessageError != nil {
				self.OnMessageError(err)
			}
		} else {
			if len(message.Identifier) > 0 {
				// This is a response to a hub call.
				self.routeResponse(&message)
			} else if len(message.Data) == 1 {
				if err := json.Unmarshal(message.Data[0], &hubCall); err == nil && len(hubCall.HubName) > 0 && len(hubCall.Method) > 0 {
					// This is a client Hub method call from server.
					if self.OnClientMethod != nil {
						self.OnClientMethod(hubCall.HubName, hubCall.Method, hubCall.Arguments)
					}
				}
			}
		}
	}
}

// Call server hub method. Dispatch() function must be running, otherwise this method will never return.
func (self *Client) CallHub(hub, method string, params ...interface{}) (json.RawMessage, error) {
	var request = struct {
		Hub        string        `json:"H"`
		Method     string        `json:"M"`
		Arguments  []interface{} `json:"A"`
		Identifier int           `json:"I"`
	}{
		Hub:        hub,
		Method:     method,
		Arguments:  params,
		Identifier: self.nextId,
	}

	self.nextId++

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

	var responseKey = fmt.Sprintf("%d", request.Identifier)
	responseChannel, err := self.createResponseFuture(responseKey)
	if err != nil {
		return nil, err
	}

	if err := self.socket.WriteMessage(websocket.TextMessage, data); err != nil {
		return nil, err
	}

	defer self.deleteResponseFuture(responseKey)

	if response, ok := <-responseChannel; !ok {
		return nil, fmt.Errorf("Call to server returned no result")
	} else if len(response.Error) > 0 {
		return nil, fmt.Errorf("%s", response.Error)
	} else {
		return response.Result, nil
	}
}

func (self *Client) Connect(scheme, host string, hubs []string) error {
	// Negotiate parameters.
	if params, err := negotiate(scheme, host); err != nil {
		return err
	} else {
		self.params = params
	}

	// Connect Websocket.
	if ws, err := connectWebsocket(host, self.params, hubs); err != nil {
		return err
	} else {
		self.socket = ws
	}

	return nil
}

func (self *Client) Close() {
	self.socket.Close()
}

func NewWebsocketClient() *Client {
	return &Client{
		nextId:          1,
		responseFutures: make(map[string]chan *serverMessage),
	}
}
<commit_msg>Changed API<commit_after>package signalr

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

	"github.com/gorilla/websocket"
)

type negotiationResponse struct {
	Url                     string
	ConnectionToken         string
	ConnectionId            string
	KeepAliveTimeout        float32
	DisconnectTimeout       float32
	ConnectionTimeout       float32
	TryWebSockets           bool
	ProtocolVersion         string
	TransportConnectTimeout float32
	LogPollDelay            float32
}

type Client struct {
	OnMessageError func(err error)
	OnClientMethod func(hub, method string, arguments []json.RawMessage)
	// When client disconnects, the causing error is sent to this channel. Valid only after Connect().
	DisconnectedChannel chan bool
	params              negotiationResponse
	socket              *websocket.Conn
	nextId              int

	// Futures for server call responses and a guarding mutex.
	responseFutures map[string]chan *serverMessage
	mutex           sync.Mutex
	dispatchRunning bool
}

type serverMessage struct {
	Cursor     string            `json:"C"`
	Data       []json.RawMessage `json:"M"`
	Result     json.RawMessage   `json:"R"`
	Identifier string            `json:"I"`
	Error      string            `json:"E"`
}

func negotiate(scheme, address string) (negotiationResponse, error) {
	var response negotiationResponse

	var negotiationUrl = url.URL{Scheme: scheme, Host: address, Path: "/signalr/negotiate"}

	client := &http.Client{}

	reply, err := client.Get(negotiationUrl.String())
	if err != nil {
		return response, err
	}

	defer reply.Body.Close()

	if body, err := ioutil.ReadAll(reply.Body); err != nil {
		return response, err
	} else if err := json.Unmarshal(body, &response); err != nil {
		return response, err
	} else {
		return response, nil
	}
}

func connectWebsocket(address string, params negotiationResponse, hubs []string) (*websocket.Conn, error) {
	var connectionData = make([]struct {
		Name string `json:"Name"`
	}, len(hubs))
	for i, h := range hubs {
		connectionData[i].Name = h
	}
	connectionDataBytes, err := json.Marshal(connectionData)
	if err != nil {
		return nil, err
	}

	var connectionParameters = url.Values{}
	connectionParameters.Set("transport", "webSockets")
	connectionParameters.Set("clientProtocol", "1.5")
	connectionParameters.Set("connectionToken", params.ConnectionToken)
	connectionParameters.Set("connectionData", string(connectionDataBytes))

	var connectionUrl = url.URL{Scheme: "wss", Host: address, Path: "signalr/connect"}
	connectionUrl.RawQuery = connectionParameters.Encode()

	if conn, _, err := websocket.DefaultDialer.Dial(connectionUrl.String(), nil); err != nil {
		return nil, err
	} else {
		return conn, nil
	}
}

func (self *Client) routeResponse(response *serverMessage) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if c, ok := self.responseFutures[response.Identifier]; ok {
		c <- response
		close(c)
		delete(self.responseFutures, response.Identifier)
	}
}

func (self *Client) createResponseFuture(identifier string) (chan *serverMessage, error) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if !self.dispatchRunning {
		return nil, fmt.Errorf("Dispatch is not running")
	}

	var c = make(chan *serverMessage)
	self.responseFutures[identifier] = c

	return c, nil
}

func (self *Client) deleteResponseFuture(identifier string) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	delete(self.responseFutures, identifier)
}

func (self *Client) tryStartDispatch() error {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if self.dispatchRunning {
		return fmt.Errorf("Another Dispatch() is running")
	}
	self.DisconnectedChannel = make(chan bool)
	self.dispatchRunning = true

	return nil
}

func (self *Client) endDispatch() {
	// Close all the waiting response futures.
	self.mutex.Lock()
	defer self.mutex.Unlock()
	self.dispatchRunning = false
	for _, c := range self.responseFutures {
		close(c)
	}
	self.responseFutures = make(map[string]chan *serverMessage)
	close(self.DisconnectedChannel)
}

// Start dispatch loop. This function will return when error occurs. When this
// happens, all the connections are closed and user can run Connect()
// and Dispatch() again on the same client.
func (self *Client) dispatch(connectedChannel chan bool) {
	if err := self.tryStartDispatch(); err != nil {
		panic("Dispatch is already running")
	}

	defer self.endDispatch()

	close(connectedChannel)

	for {
		var message serverMessage

		var hubCall struct {
			HubName   string            `json:"H"`
			Method    string            `json:"M"`
			Arguments []json.RawMessage `json:"A"`
		}

		_, data, err := self.socket.ReadMessage()
		if err != nil {
			self.socket.Close()
			break
		} else if err := json.Unmarshal(data, &message); err != nil {
			if self.OnMessageError != nil {
				self.OnMessageError(err)
			}
		} else {
			if len(message.Identifier) > 0 {
				// This is a response to a hub call.
				self.routeResponse(&message)
			} else if len(message.Data) == 1 {
				if err := json.Unmarshal(message.Data[0], &hubCall); err == nil && len(hubCall.HubName) > 0 && len(hubCall.Method) > 0 {
					// This is a client Hub method call from server.
					if self.OnClientMethod != nil {
						self.OnClientMethod(hubCall.HubName, hubCall.Method, hubCall.Arguments)
					}
				}
			}
		}
	}
}

// Call server hub method. Dispatch() function must be running, otherwise this method will never return.
func (self *Client) CallHub(hub, method string, params ...interface{}) (json.RawMessage, error) {
	var request = struct {
		Hub        string        `json:"H"`
		Method     string        `json:"M"`
		Arguments  []interface{} `json:"A"`
		Identifier int           `json:"I"`
	}{
		Hub:        hub,
		Method:     method,
		Arguments:  params,
		Identifier: self.nextId,
	}

	self.nextId++

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

	var responseKey = fmt.Sprintf("%d", request.Identifier)
	responseChannel, err := self.createResponseFuture(responseKey)
	if err != nil {
		return nil, err
	}

	if err := self.socket.WriteMessage(websocket.TextMessage, data); err != nil {
		return nil, err
	}

	defer self.deleteResponseFuture(responseKey)

	if response, ok := <-responseChannel; !ok {
		return nil, fmt.Errorf("Call to server returned no result")
	} else if len(response.Error) > 0 {
		return nil, fmt.Errorf("%s", response.Error)
	} else {
		return response.Result, nil
	}
}

func (self *Client) Connect(scheme, host string, hubs []string) error {
	// Negotiate parameters.
	if params, err := negotiate(scheme, host); err != nil {
		return err
	} else {
		self.params = params
	}

	// Connect Websocket.
	if ws, err := connectWebsocket(host, self.params, hubs); err != nil {
		return err
	} else {
		self.socket = ws
	}

	var connectedChannel = make(chan bool)
	go self.dispatch(connectedChannel)
	<-connectedChannel

	return nil
}

func (self *Client) Close() {
	self.socket.Close()
}

func NewWebsocketClient() *Client {
	return &Client{
		nextId:          1,
		responseFutures: make(map[string]chan *serverMessage),
	}
}
<|endoftext|>


<commit_before>/*
A smart client for go.

Usage:

 client, err := couchbase.Connect("http://myserver:8091/")
 handleError(err)
 pool, err := client.GetPool("default")
 handleError(err)
 bucket, err := pool.getBucket("MyAwesomeBucket")
 handleError(err)
 ...

or a shortcut for the bucket directly

 bucket, err := couchbase.GetBucket("http://myserver:8091/", "default", "default")
*/
package couchbase

import (
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"net/url"
	"sync"
	"sync/atomic"

	"github.com/dustin/gomemcached"
	"github.com/dustin/gomemcached/client"
)

type connectionPool struct {
	host        string
	name        string
	connections []*memcached.Client
	mutex       sync.Mutex
}

func (cp *connectionPool) Close() error {
	cp.mutex.Lock()
	defer cp.mutex.Unlock()
	for _, c := range cp.connections {
		c.Close()
	}
	cp.connections = []*memcached.Client{}
	return nil
}

func (cp *connectionPool) Get() (*memcached.Client, error) {
	cp.mutex.Lock()
	defer cp.mutex.Unlock()

	if len(cp.connections) == 0 {
		conn, err := memcached.Connect("tcp", cp.host)
		if err != nil {
			return nil, err
		}
		if cp.name != "default" {
			conn.Auth(cp.name, "")
		}

		cp.connections = append(cp.connections, conn)
	}

	rv := cp.connections[0]
	cp.connections = cp.connections[1:]

	return rv, nil
}

func (cp *connectionPool) Return(c *memcached.Client) {
	cp.mutex.Lock()
	defer cp.mutex.Unlock()

	if c != nil {
		if c.IsHealthy() {
			cp.connections = append(cp.connections, c)
		} else {
			c.Close()
		}
	}
}

// Execute a function on a memcached connection to the node owning key "k"
//
// Note that this automatically handles transient errors by replaying
// your function on a "not-my-vbucket" error, so don't assume
// your command will only be executed only once.
func (b *Bucket) Do(k string, f func(mc *memcached.Client, vb uint16) error) error {
	vb := b.VBHash(k)
	for {
		masterId := b.VBucketServerMap.VBucketMap[vb][0]
		conn, err := b.connections[masterId].Get()
		defer b.connections[masterId].Return(conn)
		if err != nil {
			return err
		}

		err = f(conn, uint16(vb))
		switch err.(type) {
		default:
			return err
		case gomemcached.MCResponse:
			st := err.(gomemcached.MCResponse).Status
			atomic.AddUint64(&b.pool.client.Statuses[st], 1)
			if st == gomemcached.NOT_MY_VBUCKET {
				b.refresh()
			} else {
				return err
			}
		}
	}
	panic("Unreachable.")
}

type gathered_stats struct {
	sn   string
	vals map[string]string
}

func getStatsParallel(b *Bucket, offset int, which string, ch chan<- gathered_stats) {
	sn := b.VBucketServerMap.ServerList[offset]

	results := map[string]string{}
	conn, err := b.connections[offset].Get()
	defer b.connections[offset].Return(conn)
	if err != nil {
		ch <- gathered_stats{sn, results}
	} else {
		st, err := conn.StatsMap(which)
		if err == nil {
			ch <- gathered_stats{sn, st}
		} else {
			ch <- gathered_stats{sn, results}
		}
	}
}

// Get a set of stats from all servers.
//
// Returns a map of server ID -> map of stat key to map value.
func (b *Bucket) GetStats(which string) map[string]map[string]string {
	rv := map[string]map[string]string{}

	if b.VBucketServerMap.ServerList == nil {
		return rv
	}
	// Go grab all the things at once.
	todo := len(b.VBucketServerMap.ServerList)
	ch := make(chan gathered_stats, todo)

	for offset, _ := range b.VBucketServerMap.ServerList {
		go getStatsParallel(b, offset, which, ch)
	}

	// Gather the results
	for i := 0; i < len(b.VBucketServerMap.ServerList); i++ {
		g := <-ch
		if len(g.vals) > 0 {
			rv[g.sn] = g.vals
		}
	}

	return rv
}

func (b *Bucket) doBulkGet(vb uint16, keys []string,
	ch chan<- map[string]*gomemcached.MCResponse) {

	masterId := b.VBucketServerMap.VBucketMap[vb][0]
	conn, err := b.connections[masterId].Get()
	if err != nil {
		ch <- map[string]*gomemcached.MCResponse{}
	}
	defer b.connections[masterId].Return(conn)

	m, err := conn.GetBulk(vb, keys)
	switch err.(type) {
	default:
		ch <- m
	case *gomemcached.MCResponse:
		fmt.Printf("Got a memcached error")
		st := err.(gomemcached.MCResponse).Status
		atomic.AddUint64(&b.pool.client.Statuses[st], 1)
		if st == gomemcached.NOT_MY_VBUCKET {
			b.refresh()
		}
		ch <- map[string]*gomemcached.MCResponse{}
	}
}

func (b *Bucket) processBulkGet(kdm map[uint16][]string,
	ch chan map[string]*gomemcached.MCResponse) {

	wch := make(chan uint16)

	worker := func() {
		for k := range wch {
			b.doBulkGet(k, kdm[k], ch)
		}
	}

	for i := 0; i < 4; i++ {
		go worker()
	}

	for k := range kdm {
		wch <- k
	}
	close(wch)

}
func (b *Bucket) GetBulk(keys []string) map[string]*gomemcached.MCResponse {
	// Organize by vbucket
	kdm := map[uint16][]string{}
	for _, k := range keys {
		vb := uint16(b.VBHash(k))
		a, ok := kdm[vb]
		if !ok {
			a = []string{}
		}
		kdm[vb] = append(a, k)
	}

	ch := make(chan map[string]*gomemcached.MCResponse)
	defer close(ch)

	go b.processBulkGet(kdm, ch)

	rv := map[string]*gomemcached.MCResponse{}
	for _ = range kdm {
		m := <-ch
		for k, v := range m {
			rv[k] = v
		}
	}

	return rv
}

// Set a value in this bucket.
// The value will be serialized into a JSON document.
func (b *Bucket) Set(k string, exp int, v interface{}) error {
	data, err := json.Marshal(v)
	if err != nil {
		return err
	}
	return b.SetRaw(k, exp, data)
}

// Set a value in this bucket.
// The value will be stored as raw bytes.
func (b *Bucket) SetRaw(k string, exp int, v []byte) error {
	return b.Do(k, func(mc *memcached.Client, vb uint16) error {
		res, err := mc.Set(vb, k, 0, exp, v)
		if err != nil {
			return err
		}
		if res.Status != gomemcached.SUCCESS {
			return res
		}
		return nil
	})
}

// Adds a value to this bucket; like Set except that nothing happens if the key exists.
// The value will be serialized into a JSON document.
func (b *Bucket) Add(k string, exp int, v interface{}) (added bool, err error) {
	data, err := json.Marshal(v)
	if err != nil {
		return false, err
	}
	return b.AddRaw(k, exp, data)
}

// Adds a value to this bucket; like SetRaw except that nothing happens if the key exists.
// The value will be stored as raw bytes.
func (b *Bucket) AddRaw(k string, exp int, v []byte) (added bool, err error) {
	err = b.Do(k, func(mc *memcached.Client, vb uint16) error {
		switch res, err := mc.Add(vb, k, 0, exp, v) ; {
		case err != nil:
			return err
        case res.Status == gomemcached.KEY_EEXISTS:
		case res.Status != gomemcached.SUCCESS:
			return res
		}
        added = true
		return nil
	})
    return
}

// Get a raw value from this bucket, including its CAS counter.
func (b *Bucket) GetsRaw(k string, cas *uint64) ([]byte, error) {
    var data []byte
	err := b.Do(k, func(mc *memcached.Client, vb uint16) error {
		res, err := mc.Get(vb, k)
		if err != nil {
			return err
		}
		if res.Status != gomemcached.SUCCESS {
			return res
		}
		if cas != nil {
			*cas = res.Cas
		}
        data = res.Body
		return nil
	})
    return data, err
}

// Get a value from this bucket, including its CAS counter.
// The value is expected to be a JSON stream and will be deserialized
// into rv.
func (b *Bucket) Gets(k string, rv interface{}, cas *uint64) error {
    data, err := b.GetsRaw(k, cas)
    if err != nil {
        return err
    }
	return json.Unmarshal(data, rv)
}

// Get a value from this bucket.
// The value is expected to be a JSON stream and will be deserialized
// into rv.
func (b *Bucket) Get(k string, rv interface{}) error {
	return b.Gets(k, rv, nil)
}

// Get a raw value from this bucket.
func (b *Bucket) GetRaw(k string) ([]byte, error) {
	return b.GetsRaw(k, nil)
}

// Delete a key from this bucket.
func (b *Bucket) Delete(k string) error {
	return b.Do(k, func(mc *memcached.Client, vb uint16) error {
		res, err := mc.Del(vb, k)
		if err != nil {
			return err
		}
		if res.Status != gomemcached.SUCCESS {
			return res
		}
		return nil
	})
}

// Increment a key
func (b *Bucket) Incr(k string, amt, def uint64, exp int) (uint64, error) {
	var rv uint64
	err := b.Do(k, func(mc *memcached.Client, vb uint16) error {
		res, err := mc.Incr(vb, k, amt, def, exp)
		if err != nil {
			return err
		}
		rv = res
		return nil
	})
	return rv, err
}

type ViewRow struct {
	ID    string
	Key   interface{}
	Value interface{}
	Doc   *interface{}
}

type ViewError struct {
	From   string
	Reason string
}

func (ve ViewError) Error() string {
	return fmt.Sprintf("Node: %v, reason: %v", ve.From, ve.Reason)
}

type ViewResult struct {
	TotalRows int `json:"total_rows"`
	Rows      []ViewRow
	Errors    []ViewError
}

// Document ID type for the startkey_docid parameter in views.
type DocId string

// Perform a view request that can map row values to a custom type.
//
// See the source to View for an example usage.
func (b *Bucket) ViewCustom(ddoc, name string, params map[string]interface{},
	vres interface{}) error {

	// Pick a random node to service our request.
	node := b.Nodes[rand.Intn(len(b.Nodes))]
	u, err := url.Parse(node.CouchAPIBase)
	if err != nil {
		return err
	}

	values := url.Values{}
	for k, v := range params {
		switch t := v.(type) {
		case DocId:
			values[k] = []string{string(t)}
		case string:
			values[k] = []string{fmt.Sprintf(`"%s"`, t)}
		case int:
			values[k] = []string{fmt.Sprintf(`%d`, t)}
		case bool:
			values[k] = []string{fmt.Sprintf(`%v`, t)}
		default:
			b, err := json.Marshal(v)
			if err != nil {
				panic(fmt.Sprintf("unsupported value-type %T in Query, json encoder said %v", t, err))
			}
			values[k] = []string{fmt.Sprintf(`%v`, string(b))}
		}
	}

	u.Path = fmt.Sprintf("/%s/_design/%s/_view/%s", b.Name, ddoc, name)
	u.RawQuery = values.Encode()

	res, err := HttpClient.Get(u.String())
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		return errors.New(res.Status)
	}

	d := json.NewDecoder(res.Body)
	if err := d.Decode(vres); err != nil {
		return err
	}
	return nil
}

// Execute a view
func (b *Bucket) View(ddoc, name string, params map[string]interface{}) (ViewResult, error) {
	vres := ViewResult{}
	return vres, b.ViewCustom(ddoc, name, params, &vres)
}
<commit_msg>go fmt<commit_after>/*
A smart client for go.

Usage:

 client, err := couchbase.Connect("http://myserver:8091/")
 handleError(err)
 pool, err := client.GetPool("default")
 handleError(err)
 bucket, err := pool.getBucket("MyAwesomeBucket")
 handleError(err)
 ...

or a shortcut for the bucket directly

 bucket, err := couchbase.GetBucket("http://myserver:8091/", "default", "default")
*/
package couchbase

import (
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"net/url"
	"sync"
	"sync/atomic"

	"github.com/dustin/gomemcached"
	"github.com/dustin/gomemcached/client"
)

type connectionPool struct {
	host        string
	name        string
	connections []*memcached.Client
	mutex       sync.Mutex
}

func (cp *connectionPool) Close() error {
	cp.mutex.Lock()
	defer cp.mutex.Unlock()
	for _, c := range cp.connections {
		c.Close()
	}
	cp.connections = []*memcached.Client{}
	return nil
}

func (cp *connectionPool) Get() (*memcached.Client, error) {
	cp.mutex.Lock()
	defer cp.mutex.Unlock()

	if len(cp.connections) == 0 {
		conn, err := memcached.Connect("tcp", cp.host)
		if err != nil {
			return nil, err
		}
		if cp.name != "default" {
			conn.Auth(cp.name, "")
		}

		cp.connections = append(cp.connections, conn)
	}

	rv := cp.connections[0]
	cp.connections = cp.connections[1:]

	return rv, nil
}

func (cp *connectionPool) Return(c *memcached.Client) {
	cp.mutex.Lock()
	defer cp.mutex.Unlock()

	if c != nil {
		if c.IsHealthy() {
			cp.connections = append(cp.connections, c)
		} else {
			c.Close()
		}
	}
}

// Execute a function on a memcached connection to the node owning key "k"
//
// Note that this automatically handles transient errors by replaying
// your function on a "not-my-vbucket" error, so don't assume
// your command will only be executed only once.
func (b *Bucket) Do(k string, f func(mc *memcached.Client, vb uint16) error) error {
	vb := b.VBHash(k)
	for {
		masterId := b.VBucketServerMap.VBucketMap[vb][0]
		conn, err := b.connections[masterId].Get()
		defer b.connections[masterId].Return(conn)
		if err != nil {
			return err
		}

		err = f(conn, uint16(vb))
		switch err.(type) {
		default:
			return err
		case gomemcached.MCResponse:
			st := err.(gomemcached.MCResponse).Status
			atomic.AddUint64(&b.pool.client.Statuses[st], 1)
			if st == gomemcached.NOT_MY_VBUCKET {
				b.refresh()
			} else {
				return err
			}
		}
	}
	panic("Unreachable.")
}

type gathered_stats struct {
	sn   string
	vals map[string]string
}

func getStatsParallel(b *Bucket, offset int, which string, ch chan<- gathered_stats) {
	sn := b.VBucketServerMap.ServerList[offset]

	results := map[string]string{}
	conn, err := b.connections[offset].Get()
	defer b.connections[offset].Return(conn)
	if err != nil {
		ch <- gathered_stats{sn, results}
	} else {
		st, err := conn.StatsMap(which)
		if err == nil {
			ch <- gathered_stats{sn, st}
		} else {
			ch <- gathered_stats{sn, results}
		}
	}
}

// Get a set of stats from all servers.
//
// Returns a map of server ID -> map of stat key to map value.
func (b *Bucket) GetStats(which string) map[string]map[string]string {
	rv := map[string]map[string]string{}

	if b.VBucketServerMap.ServerList == nil {
		return rv
	}
	// Go grab all the things at once.
	todo := len(b.VBucketServerMap.ServerList)
	ch := make(chan gathered_stats, todo)

	for offset, _ := range b.VBucketServerMap.ServerList {
		go getStatsParallel(b, offset, which, ch)
	}

	// Gather the results
	for i := 0; i < len(b.VBucketServerMap.ServerList); i++ {
		g := <-ch
		if len(g.vals) > 0 {
			rv[g.sn] = g.vals
		}
	}

	return rv
}

func (b *Bucket) doBulkGet(vb uint16, keys []string,
	ch chan<- map[string]*gomemcached.MCResponse) {

	masterId := b.VBucketServerMap.VBucketMap[vb][0]
	conn, err := b.connections[masterId].Get()
	if err != nil {
		ch <- map[string]*gomemcached.MCResponse{}
	}
	defer b.connections[masterId].Return(conn)

	m, err := conn.GetBulk(vb, keys)
	switch err.(type) {
	default:
		ch <- m
	case *gomemcached.MCResponse:
		fmt.Printf("Got a memcached error")
		st := err.(gomemcached.MCResponse).Status
		atomic.AddUint64(&b.pool.client.Statuses[st], 1)
		if st == gomemcached.NOT_MY_VBUCKET {
			b.refresh()
		}
		ch <- map[string]*gomemcached.MCResponse{}
	}
}

func (b *Bucket) processBulkGet(kdm map[uint16][]string,
	ch chan map[string]*gomemcached.MCResponse) {

	wch := make(chan uint16)

	worker := func() {
		for k := range wch {
			b.doBulkGet(k, kdm[k], ch)
		}
	}

	for i := 0; i < 4; i++ {
		go worker()
	}

	for k := range kdm {
		wch <- k
	}
	close(wch)

}
func (b *Bucket) GetBulk(keys []string) map[string]*gomemcached.MCResponse {
	// Organize by vbucket
	kdm := map[uint16][]string{}
	for _, k := range keys {
		vb := uint16(b.VBHash(k))
		a, ok := kdm[vb]
		if !ok {
			a = []string{}
		}
		kdm[vb] = append(a, k)
	}

	ch := make(chan map[string]*gomemcached.MCResponse)
	defer close(ch)

	go b.processBulkGet(kdm, ch)

	rv := map[string]*gomemcached.MCResponse{}
	for _ = range kdm {
		m := <-ch
		for k, v := range m {
			rv[k] = v
		}
	}

	return rv
}

// Set a value in this bucket.
// The value will be serialized into a JSON document.
func (b *Bucket) Set(k string, exp int, v interface{}) error {
	data, err := json.Marshal(v)
	if err != nil {
		return err
	}
	return b.SetRaw(k, exp, data)
}

// Set a value in this bucket.
// The value will be stored as raw bytes.
func (b *Bucket) SetRaw(k string, exp int, v []byte) error {
	return b.Do(k, func(mc *memcached.Client, vb uint16) error {
		res, err := mc.Set(vb, k, 0, exp, v)
		if err != nil {
			return err
		}
		if res.Status != gomemcached.SUCCESS {
			return res
		}
		return nil
	})
}

// Adds a value to this bucket; like Set except that nothing happens if the key exists.
// The value will be serialized into a JSON document.
func (b *Bucket) Add(k string, exp int, v interface{}) (added bool, err error) {
	data, err := json.Marshal(v)
	if err != nil {
		return false, err
	}
	return b.AddRaw(k, exp, data)
}

// Adds a value to this bucket; like SetRaw except that nothing happens if the key exists.
// The value will be stored as raw bytes.
func (b *Bucket) AddRaw(k string, exp int, v []byte) (added bool, err error) {
	err = b.Do(k, func(mc *memcached.Client, vb uint16) error {
		switch res, err := mc.Add(vb, k, 0, exp, v); {
		case err != nil:
			return err
		case res.Status == gomemcached.KEY_EEXISTS:
		case res.Status != gomemcached.SUCCESS:
			return res
		}
		added = true
		return nil
	})
	return
}

// Get a raw value from this bucket, including its CAS counter.
func (b *Bucket) GetsRaw(k string, cas *uint64) ([]byte, error) {
	var data []byte
	err := b.Do(k, func(mc *memcached.Client, vb uint16) error {
		res, err := mc.Get(vb, k)
		if err != nil {
			return err
		}
		if res.Status != gomemcached.SUCCESS {
			return res
		}
		if cas != nil {
			*cas = res.Cas
		}
		data = res.Body
		return nil
	})
	return data, err
}

// Get a value from this bucket, including its CAS counter.
// The value is expected to be a JSON stream and will be deserialized
// into rv.
func (b *Bucket) Gets(k string, rv interface{}, cas *uint64) error {
	data, err := b.GetsRaw(k, cas)
	if err != nil {
		return err
	}
	return json.Unmarshal(data, rv)
}

// Get a value from this bucket.
// The value is expected to be a JSON stream and will be deserialized
// into rv.
func (b *Bucket) Get(k string, rv interface{}) error {
	return b.Gets(k, rv, nil)
}

// Get a raw value from this bucket.
func (b *Bucket) GetRaw(k string) ([]byte, error) {
	return b.GetsRaw(k, nil)
}

// Delete a key from this bucket.
func (b *Bucket) Delete(k string) error {
	return b.Do(k, func(mc *memcached.Client, vb uint16) error {
		res, err := mc.Del(vb, k)
		if err != nil {
			return err
		}
		if res.Status != gomemcached.SUCCESS {
			return res
		}
		return nil
	})
}

// Increment a key
func (b *Bucket) Incr(k string, amt, def uint64, exp int) (uint64, error) {
	var rv uint64
	err := b.Do(k, func(mc *memcached.Client, vb uint16) error {
		res, err := mc.Incr(vb, k, amt, def, exp)
		if err != nil {
			return err
		}
		rv = res
		return nil
	})
	return rv, err
}

type ViewRow struct {
	ID    string
	Key   interface{}
	Value interface{}
	Doc   *interface{}
}

type ViewError struct {
	From   string
	Reason string
}

func (ve ViewError) Error() string {
	return fmt.Sprintf("Node: %v, reason: %v", ve.From, ve.Reason)
}

type ViewResult struct {
	TotalRows int `json:"total_rows"`
	Rows      []ViewRow
	Errors    []ViewError
}

// Document ID type for the startkey_docid parameter in views.
type DocId string

// Perform a view request that can map row values to a custom type.
//
// See the source to View for an example usage.
func (b *Bucket) ViewCustom(ddoc, name string, params map[string]interface{},
	vres interface{}) error {

	// Pick a random node to service our request.
	node := b.Nodes[rand.Intn(len(b.Nodes))]
	u, err := url.Parse(node.CouchAPIBase)
	if err != nil {
		return err
	}

	values := url.Values{}
	for k, v := range params {
		switch t := v.(type) {
		case DocId:
			values[k] = []string{string(t)}
		case string:
			values[k] = []string{fmt.Sprintf(`"%s"`, t)}
		case int:
			values[k] = []string{fmt.Sprintf(`%d`, t)}
		case bool:
			values[k] = []string{fmt.Sprintf(`%v`, t)}
		default:
			b, err := json.Marshal(v)
			if err != nil {
				panic(fmt.Sprintf("unsupported value-type %T in Query, json encoder said %v", t, err))
			}
			values[k] = []string{fmt.Sprintf(`%v`, string(b))}
		}
	}

	u.Path = fmt.Sprintf("/%s/_design/%s/_view/%s", b.Name, ddoc, name)
	u.RawQuery = values.Encode()

	res, err := HttpClient.Get(u.String())
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		return errors.New(res.Status)
	}

	d := json.NewDecoder(res.Body)
	if err := d.Decode(vres); err != nil {
		return err
	}
	return nil
}

// Execute a view
func (b *Bucket) View(ddoc, name string, params map[string]interface{}) (ViewResult, error) {
	vres := ViewResult{}
	return vres, b.ViewCustom(ddoc, name, params, &vres)
}
<|endoftext|>


<commit_before>// Package oauth1 implements an OAuth 1.0a client per RFC 5849.
package oauth1

import (
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
)

// Token contains an end-user's tokens.
// This is the data you must store to persist authentication.
type Token struct {
	Key    string
	Secret string
}

// Transport implements http.RoundTripper. When configured, it can be
// used to make authenticated HTTP requests.
type Transport struct {
	// Key is the identifier string generated by the API for the handshake.
	// The key can be treated as a username.
	Key string

	// Secret is the unique token generated for signing requests.
	// The secret must be kept private.
	Secret string

	// CallbackURI is an absolute URI back to which the server will redirect the
	// resource owner when the Resource Owner Authorization step is completed.
	CallbackURI string

	// TemporaryCredentialsURI is an absolute URI used to obtain a set of
	// temporary credentials that can be used to identify the access request
	// throughout the authorization process.
	TemporaryCredentialsURI string

	// AuthorizationURI is an absolute URI used to grant the access request
	// identified by the temporary credentials.
	AuthorizationURI string

	// TokenRequestURI is an absolute URI used to request a set of token
	// credentials using the set of temporary credentials.
	TokenRequestURI string

	// Token contains an end-user's tokens.
	// This may be a set of temporary credentials.
	Token *Token

	// Transport is the HTTP transport to use when making requests.
	// It will default to http.DefaultTransport if nil.
	// It should never be an oauth1.Transport.
	Transport http.RoundTripper
}

var (
	errResponseToken       = errors.New("missing oauth_token")
	errResponseTokenSecret = errors.New("missing oauth_token_secret")
)

// Client returns an *http.Client that makes authenticated requests.
func (t *Transport) Client() *http.Client {
	return &http.Client{Transport: t}
}

// RequestTemporaryCredentials obtains a set of temporary credentials by making
// an authenticated request to the Temporary Credential Request endpoint. The
// AuthorizationURI configured with the required oauth_token query parameter
// is returned if successful.
//
// See RFC 5849 Section 2.1 and 2.2.
func (t *Transport) RequestTemporaryCredentials() (string, error) {
	params := url.Values{"oauth_callback": {t.CallbackURI}}
	_, err := t.request(t.TemporaryCredentialsURI, params)
	if err != nil {
		return "", err
	}

	u, err := url.Parse(t.AuthorizationURI)
	if err != nil {
		return "", err
	}

	q := u.Query()
	q.Set("oauth_token", t.Token.Key)
	u.RawQuery = q.Encode()

	return u.String(), nil
}

// RequestToken obtains a set of token credentials from the server by making an
// authenticated request to the Token Request endpoint.
//
// See RFC 5849 Section 2.3.
func (t *Transport) RequestToken(verifier string) (url.Values, error) {
	params := url.Values{"oauth_verifier": {verifier}}
	return t.request(t.TokenRequestURI, params)
}

// RoundTrip executes a single HTTP transaction using the Transport's Token as
// authorization headers.
//
// See RFC 5849 Section 3.1.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Build the Authorization header.
	header, err := t.authenticate(req, url.Values{})
	if err != nil {
		return nil, err
	}

	// This is so that we don't modify the original request as specified
	// in the documentation for http.RoundTripper.
	req = cloneRequest(req)
	req.Header.Set("Authorization", header)

	// Make the HTTP request.
	return t.transport().RoundTrip(req)
}

// authenticate returns a signed Authorization header for the given request.
//
// See RFC 5849 Section 3.1.
func (t *Transport) authenticate(req *http.Request, params url.Values) (string, error) {
	nonce, err := generateNonce()
	if err != nil {
		return "", err
	}

	// Authenticated requests include several protocol parameters.
	params.Add("oauth_consumer_key", t.Key)
	params.Add("oauth_signature_method", "HMAC-SHA1")
	params.Add("oauth_timestamp", generateTimestamp())
	params.Add("oauth_nonce", nonce)
	params.Add("oauth_version", "1.0")

	// Add the token, if present.
	if t.Token != nil {
		params.Set("oauth_token", t.Token.Key)
	}

	// Build the HMAC key.
	key := encode(t.Secret) + "&"
	if t.Token != nil {
		key += encode(t.Token.Secret)
	}

	// Build the Authorization header.
	header, err := authenticate(req, params, key)
	if err != nil {
		return "", err
	}

	return header, nil
}

// request makes an HTTP POST request to the uri with some extra OAuth
// parameters. The Transport Token is updated from the response.
func (t *Transport) request(uri string, params url.Values) (url.Values, error) {
	c := &http.Client{Transport: t.transport()}
	req, err := http.NewRequest("POST", uri, nil)
	if err != nil {
		return nil, err
	}

	header, err := t.authenticate(req, params)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", header)
	response, err := c.Do(req)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	if response.StatusCode != 200 {
		return nil, err
	}

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

	form, err := url.ParseQuery(string(body))
	if err != nil {
		return nil, err
	}

	key := form.Get("oauth_token")
	if key == "" {
		return nil, errResponseToken
	}

	secret := form.Get("oauth_token_secret")
	if secret == "" {
		return nil, errResponseTokenSecret
	}

	t.Token = &Token{
		Key:    key,
		Secret: secret,
	}

	return form, nil
}

// transport returns the configured Transport, or the http.DefaultTransport.
func (t *Transport) transport() http.RoundTripper {
	if t.Transport != nil {
		return t.Transport
	}

	return http.DefaultTransport
}

// cloneRequest returns a copy of the given request.
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return r2
}
<commit_msg>Add client example.<commit_after>// Package oauth1 implements an OAuth 1.0a client per RFC 5849.
package oauth1

import (
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
)

// Token contains an end-user's tokens.
// This is the data you must store to persist authentication.
//
// Example usage:
//	// Configure a new Transport.
//	t := oauth1.Transport{
//		Key:                     os.Getenv("FITBIT_CLIENT_KEY"),
//		Secret:                  os.Getenv("FITBIT_CLIENT_SECRET"),
//		CallbackURI:             os.Getenv("FITBIT_CALLBACK_URI"),
//		TemporaryCredentialsURI: "https://api.fitbit.com/oauth/request_token",
//		AuthorizationURI:        "https://www.fitbit.com/oauth/authorize",
//		TokenRequestURI:         "https://api.fitbit.com/oauth/access_token",
//	}
//
//	// Request a set of temporary credentials.
//	auth, err := t.RequestTemporaryCredentials()
//	if err != nil {
//		return
//	}
//
//	// Most flows would redirect the user to the auth URI.
//	fmt.Println("Enter the following URI in your browser.")
//	fmt.Println(auth)
//
//	var verifier string
//	fmt.Printf("Paste the verifier here: ")
//	fmt.Scanf("%s", &verifier)
//
//	// Exchange temporary credentials for proper request token.
//	form, err := t.RequestToken(verifier)
//	if err != nil {
//		return
//	}
//
//	fmt.Println(t.Token.Key)
//	fmt.Println(t.Token.Secret)
//
//	// Some providers return additional data with the token.
//	fmt.Println(form.Get("encoded_user_id"))
//
//	// Perform authenticated requests.
//	client := t.Client()
//	response, err := client.Get("https://api.fitbit.com/1/user/-/profile.json")
//	if err != nil {
//		fmt.Println(err)
//		return
//	}
//
//	defer response.Body.Close()
//
//	activities, err := ioutil.ReadAll(response.Body)
//	if err != nil {
//		return
//	}
//
//	fmt.Println(string(activities))
//
type Token struct {
	Key    string
	Secret string
}

// Transport implements http.RoundTripper. When configured, it can be
// used to make authenticated HTTP requests.
type Transport struct {
	// Key is the identifier string generated by the API for the handshake.
	// The key can be treated as a username.
	Key string

	// Secret is the unique token generated for signing requests.
	// The secret must be kept private.
	Secret string

	// CallbackURI is an absolute URI back to which the server will redirect the
	// resource owner when the Resource Owner Authorization step is completed.
	CallbackURI string

	// TemporaryCredentialsURI is an absolute URI used to obtain a set of
	// temporary credentials that can be used to identify the access request
	// throughout the authorization process.
	TemporaryCredentialsURI string

	// AuthorizationURI is an absolute URI used to grant the access request
	// identified by the temporary credentials.
	AuthorizationURI string

	// TokenRequestURI is an absolute URI used to request a set of token
	// credentials using the set of temporary credentials.
	TokenRequestURI string

	// Token contains an end-user's tokens.
	// This may be a set of temporary credentials.
	Token *Token

	// Transport is the HTTP transport to use when making requests.
	// It will default to http.DefaultTransport if nil.
	// It should never be an oauth1.Transport.
	Transport http.RoundTripper
}

var (
	errResponseToken       = errors.New("missing oauth_token")
	errResponseTokenSecret = errors.New("missing oauth_token_secret")
)

// Client returns an *http.Client that makes authenticated requests.
func (t *Transport) Client() *http.Client {
	return &http.Client{Transport: t}
}

// RequestTemporaryCredentials obtains a set of temporary credentials by making
// an authenticated request to the Temporary Credential Request endpoint. The
// AuthorizationURI configured with the required oauth_token query parameter
// is returned if successful.
//
// See RFC 5849 Section 2.1 and 2.2.
func (t *Transport) RequestTemporaryCredentials() (string, error) {
	params := url.Values{"oauth_callback": {t.CallbackURI}}
	_, err := t.request(t.TemporaryCredentialsURI, params)
	if err != nil {
		return "", err
	}

	u, err := url.Parse(t.AuthorizationURI)
	if err != nil {
		return "", err
	}

	q := u.Query()
	q.Set("oauth_token", t.Token.Key)
	u.RawQuery = q.Encode()

	return u.String(), nil
}

// RequestToken obtains a set of token credentials from the server by making an
// authenticated request to the Token Request endpoint.
//
// See RFC 5849 Section 2.3.
func (t *Transport) RequestToken(verifier string) (url.Values, error) {
	params := url.Values{"oauth_verifier": {verifier}}
	return t.request(t.TokenRequestURI, params)
}

// RoundTrip executes a single HTTP transaction using the Transport's Token as
// authorization headers.
//
// See RFC 5849 Section 3.1.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Build the Authorization header.
	header, err := t.authenticate(req, url.Values{})
	if err != nil {
		return nil, err
	}

	// This is so that we don't modify the original request as specified
	// in the documentation for http.RoundTripper.
	req = cloneRequest(req)
	req.Header.Set("Authorization", header)

	// Make the HTTP request.
	return t.transport().RoundTrip(req)
}

// authenticate returns a signed Authorization header for the given request.
//
// See RFC 5849 Section 3.1.
func (t *Transport) authenticate(req *http.Request, params url.Values) (string, error) {
	nonce, err := generateNonce()
	if err != nil {
		return "", err
	}

	// Authenticated requests include several protocol parameters.
	params.Add("oauth_consumer_key", t.Key)
	params.Add("oauth_signature_method", "HMAC-SHA1")
	params.Add("oauth_timestamp", generateTimestamp())
	params.Add("oauth_nonce", nonce)
	params.Add("oauth_version", "1.0")

	// Add the token, if present.
	if t.Token != nil {
		params.Set("oauth_token", t.Token.Key)
	}

	// Build the HMAC key.
	key := encode(t.Secret) + "&"
	if t.Token != nil {
		key += encode(t.Token.Secret)
	}

	// Build the Authorization header.
	header, err := authenticate(req, params, key)
	if err != nil {
		return "", err
	}

	return header, nil
}

// request makes an HTTP POST request to the uri with some extra OAuth
// parameters. The Transport Token is updated from the response.
func (t *Transport) request(uri string, params url.Values) (url.Values, error) {
	c := &http.Client{Transport: t.transport()}
	req, err := http.NewRequest("POST", uri, nil)
	if err != nil {
		return nil, err
	}

	header, err := t.authenticate(req, params)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", header)
	response, err := c.Do(req)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	if response.StatusCode != 200 {
		return nil, err
	}

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

	form, err := url.ParseQuery(string(body))
	if err != nil {
		return nil, err
	}

	key := form.Get("oauth_token")
	if key == "" {
		return nil, errResponseToken
	}

	secret := form.Get("oauth_token_secret")
	if secret == "" {
		return nil, errResponseTokenSecret
	}

	t.Token = &Token{
		Key:    key,
		Secret: secret,
	}

	return form, nil
}

// transport returns the configured Transport, or the http.DefaultTransport.
func (t *Transport) transport() http.RoundTripper {
	if t.Transport != nil {
		return t.Transport
	}

	return http.DefaultTransport
}

// cloneRequest returns a copy of the given request.
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return r2
}
<|endoftext|>


<commit_before>package recurly

import (
	"bytes"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
)

const defaultBaseURL = "https://%s.recurly.com/"

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

	// subdomain is your account's sub domain used for authentication.
	subDomain string

	// apiKey is your account's API key used for authentication.
	apiKey string

	// BaseURL is the base url for api requests.
	BaseURL string

	// Services used for talking with different parts of the Recurly API
	Accounts      AccountsService
	Adjustments   AdjustmentsService
	Billing       BillingService
	Coupons       CouponsService
	Redemptions   RedemptionsService
	Invoices      InvoicesService
	Plans         PlansService
	AddOns        AddOnsService
	Subscriptions SubscriptionsService
	Transactions  TransactionsService
}

// NewClient returns a new instance of *Client.
// apiKey should be everything after "Basic ".
func NewClient(subDomain, apiKey string, httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}

	client := &Client{
		client:    httpClient,
		subDomain: subDomain,
		apiKey:    base64.StdEncoding.EncodeToString([]byte(apiKey)),
		BaseURL:   fmt.Sprintf(defaultBaseURL, subDomain),
	}

	client.Accounts = &accountsImpl{client: client}
	client.Adjustments = &adjustmentsImpl{client: client}
	client.Billing = &billingImpl{client: client}
	client.Coupons = &couponsImpl{client: client}
	client.Redemptions = &redemptionsImpl{client: client}
	client.Invoices = &invoicesImpl{client: client}
	client.Plans = &plansImpl{client: client}
	client.AddOns = &addOnsImpl{client: client}
	client.Subscriptions = &subscriptionsImpl{client: client}
	client.Transactions = &transactionsImpl{client: client}

	return client
}

// newRequest creates an authenticated API request that is ready to send.
func (c *Client) newRequest(method string, action string, params Params, body interface{}) (*http.Request, error) {
	method = strings.ToUpper(method)
	endpoint := fmt.Sprintf("%sv2/%s", c.BaseURL, action)

	// Query String
	qs := url.Values{}
	for k, v := range params {
		qs.Add(k, fmt.Sprintf("%v", v))
	}

	if len(qs) > 0 {
		endpoint += "?" + qs.Encode()
	}

	// Request body
	var buf bytes.Buffer
	if body != nil {
		err := xml.NewEncoder(&buf).Encode(body)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, endpoint, &buf)

	req.Header.Set("Authorization", fmt.Sprintf("Basic %s", c.apiKey))
	req.Header.Set("Accept", "application/xml")
	req.Header.Set("X-Api-Version", "2.5")
	if req.Method == "POST" || req.Method == "PUT" {
		req.Header.Set("Content-Type", "application/xml; charset=utf-8")
	}

	return req, err
}

// do takes a prepared API request and makes the API call to Recurly.
// It will decode the XML into a destination struct you provide as well
// as parse any validation errors that may have occurred.
// It returns a Response object that provides a wrapper around http.Response
// with some convenience methods.
func (c *Client) do(req *http.Request, v interface{}) (*Response, error) {
	req.Close = true
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	response := &Response{Response: resp}
	decoder := xml.NewDecoder(resp.Body)
	if response.IsError() { // Parse validation errors
		if response.StatusCode == http.StatusUnprocessableEntity {
			var ve struct {
				XMLName     xml.Name     `xml:"errors"`
				Errors      []Error      `xml:"error"`
				Transaction *Transaction `xml:"transaction,omitempty"`
			}

			if err = decoder.Decode(&ve); err != nil {
				return response, err
			}

			response.Errors = ve.Errors

			// If the response object includes a TransactionError, set the
			// transaction field on the response object and the TransactionError field.
			if ve.Transaction != nil {
				response.transaction = ve.Transaction
			}
		} else if response.IsClientError() { // Parse possible individual error message
			var ve struct {
				XMLName     xml.Name `xml:"error"`
				Symbol      string   `xml:"symbol"`
				Description string   `xml:"description"`
			}
			if err = decoder.Decode(&ve); err == io.EOF {
				return response, nil
			} else if err != nil {
				return response, err
			}

			response.Errors = []Error{
				{
					Symbol:  ve.Symbol,
					Message: ve.Description,
				},
			}
		}

		return response, nil
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			io.Copy(w, resp.Body)
		} else {
			err = decoder.Decode(&v)
		}
	}

	return response, err
}
<commit_msg>Add User-Agent header. Closes #41<commit_after>package recurly

import (
	"bytes"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"runtime"
	"strings"
)

const defaultBaseURL = "https://%s.recurly.com/"

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

	// subdomain is your account's sub domain used for authentication.
	subDomain string

	// apiKey is your account's API key used for authentication.
	apiKey string

	// BaseURL is the base url for api requests.
	BaseURL string

	// Services used for talking with different parts of the Recurly API
	Accounts      AccountsService
	Adjustments   AdjustmentsService
	Billing       BillingService
	Coupons       CouponsService
	Redemptions   RedemptionsService
	Invoices      InvoicesService
	Plans         PlansService
	AddOns        AddOnsService
	Subscriptions SubscriptionsService
	Transactions  TransactionsService
}

// NewClient returns a new instance of *Client.
// apiKey should be everything after "Basic ".
func NewClient(subDomain, apiKey string, httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}

	client := &Client{
		client:    httpClient,
		subDomain: subDomain,
		apiKey:    base64.StdEncoding.EncodeToString([]byte(apiKey)),
		BaseURL:   fmt.Sprintf(defaultBaseURL, subDomain),
	}

	client.Accounts = &accountsImpl{client: client}
	client.Adjustments = &adjustmentsImpl{client: client}
	client.Billing = &billingImpl{client: client}
	client.Coupons = &couponsImpl{client: client}
	client.Redemptions = &redemptionsImpl{client: client}
	client.Invoices = &invoicesImpl{client: client}
	client.Plans = &plansImpl{client: client}
	client.AddOns = &addOnsImpl{client: client}
	client.Subscriptions = &subscriptionsImpl{client: client}
	client.Transactions = &transactionsImpl{client: client}

	return client
}

// newRequest creates an authenticated API request that is ready to send.
func (c *Client) newRequest(method string, action string, params Params, body interface{}) (*http.Request, error) {
	method = strings.ToUpper(method)
	endpoint := fmt.Sprintf("%sv2/%s", c.BaseURL, action)

	// Query String
	qs := url.Values{}
	for k, v := range params {
		qs.Add(k, fmt.Sprintf("%v", v))
	}

	if len(qs) > 0 {
		endpoint += "?" + qs.Encode()
	}

	// Request body
	var buf bytes.Buffer
	if body != nil {
		err := xml.NewEncoder(&buf).Encode(body)
		if err != nil {
			return nil, err
		}
	}

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

	// Add User-Agent tracking for Recurly statistics and potentially
	// identifying bugs or updates needed in the library.
	// https://github.com/blacklightcms/recurly/issues/41
	req.Header.Set("User-Agent", fmt.Sprintf(
		"Blacklight/2017-09-09; Go (%s) [%s-%s]",
		runtime.Version(),
		runtime.GOARCH,
		runtime.GOOS,
	))
	req.Header.Set("Authorization", fmt.Sprintf("Basic %s", c.apiKey))
	req.Header.Set("Accept", "application/xml")
	req.Header.Set("X-Api-Version", "2.5")
	if req.Method == "POST" || req.Method == "PUT" {
		req.Header.Set("Content-Type", "application/xml; charset=utf-8")
	}

	return req, err
}

// do takes a prepared API request and makes the API call to Recurly.
// It will decode the XML into a destination struct you provide as well
// as parse any validation errors that may have occurred.
// It returns a Response object that provides a wrapper around http.Response
// with some convenience methods.
func (c *Client) do(req *http.Request, v interface{}) (*Response, error) {
	req.Close = true
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	response := &Response{Response: resp}
	decoder := xml.NewDecoder(resp.Body)
	if response.IsError() { // Parse validation errors
		if response.StatusCode == http.StatusUnprocessableEntity {
			var ve struct {
				XMLName     xml.Name     `xml:"errors"`
				Errors      []Error      `xml:"error"`
				Transaction *Transaction `xml:"transaction,omitempty"`
			}

			if err = decoder.Decode(&ve); err != nil {
				return response, err
			}

			response.Errors = ve.Errors

			// If the response object includes a TransactionError, set the
			// transaction field on the response object and the TransactionError field.
			if ve.Transaction != nil {
				response.transaction = ve.Transaction
			}
		} else if response.IsClientError() { // Parse possible individual error message
			var ve struct {
				XMLName     xml.Name `xml:"error"`
				Symbol      string   `xml:"symbol"`
				Description string   `xml:"description"`
			}
			if err = decoder.Decode(&ve); err == io.EOF {
				return response, nil
			} else if err != nil {
				return response, err
			}

			response.Errors = []Error{
				{
					Symbol:  ve.Symbol,
					Message: ve.Description,
				},
			}
		}

		return response, nil
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			io.Copy(w, resp.Body)
		} else {
			err = decoder.Decode(&v)
		}
	}

	return response, err
}
<|endoftext|>


<commit_before>package arn

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/aerogo/http/client"
	"github.com/animenotifier/arn/autocorrect"
	"github.com/animenotifier/arn/validate"
	"github.com/animenotifier/osu"
	gravatar "github.com/ungerik/go-gravatar"
)

var setNickMutex sync.Mutex
var setEmailMutex sync.Mutex

// User is a registered person.
type User struct {
	ID           string       `json:"id"`
	Nick         string       `json:"nick" editable:"true"`
	FirstName    string       `json:"firstName" private:"true"`
	LastName     string       `json:"lastName" private:"true"`
	Email        string       `json:"email" editable:"true" private:"true"`
	Role         string       `json:"role"`
	Registered   string       `json:"registered"`
	LastLogin    string       `json:"lastLogin" private:"true"`
	LastSeen     string       `json:"lastSeen" private:"true"`
	ProExpires   string       `json:"proExpires" editable:"true"`
	Gender       string       `json:"gender" private:"true"`
	Language     string       `json:"language"`
	Tagline      string       `json:"tagline" editable:"true"`
	Introduction string       `json:"introduction" editable:"true" type:"textarea"`
	Website      string       `json:"website" editable:"true"`
	IP           string       `json:"ip" private:"true"`
	UserAgent    string       `json:"agent" private:"true"`
	Balance      int          `json:"balance" private:"true"`
	Avatar       UserAvatar   `json:"avatar"`
	Cover        UserCover    `json:"cover"`
	AgeRange     UserAgeRange `json:"ageRange" private:"true"`
	Accounts     UserAccounts `json:"accounts" private:"true"`
	Browser      UserBrowser  `json:"browser" private:"true"`
	OS           UserOS       `json:"os" private:"true"`
	Location     *Location    `json:"location" private:"true"`

	// user.Email = ""
	// user.Gender = ""
	// user.FirstName = ""
	// user.LastName = ""
	// user.IP = ""
	// user.UserAgent = ""
	// user.LastLogin = ""
	// user.LastSeen = ""
	// user.Accounts.Facebook.ID = ""
	// user.Accounts.Google.ID = ""
	// user.Accounts.Twitter.ID = ""
	// user.AgeRange = UserAgeRange{}
	// user.Location = &Location{}
	// user.Browser = UserBrowser{}
	// user.OS = UserOS{}
}

// NewUser creates an empty user object with a unique ID.
func NewUser() *User {
	user := &User{
		ID: GenerateID("User"),

		// Avoid nil value fields
		Location: &Location{},
	}

	return user
}

// RegisterUser registers a new user in the database and sets up all the required references.
func RegisterUser(user *User) {
	user.Registered = DateTimeUTC()
	user.LastLogin = user.Registered
	user.LastSeen = user.Registered

	// Save nick in NickToUser table
	DB.Set("NickToUser", user.Nick, &NickToUser{
		Nick:   user.Nick,
		UserID: user.ID,
	})

	// Save email in EmailToUser table
	if user.Email != "" {
		DB.Set("EmailToUser", user.Email, &EmailToUser{
			Email:  user.Email,
			UserID: user.ID,
		})
	}

	// Create default settings
	NewSettings(user).Save()

	// Add empty anime list
	DB.Set("AnimeList", user.ID, &AnimeList{
		UserID: user.ID,
		Items:  []*AnimeListItem{},
	})

	// Add empty inventory
	NewInventory(user.ID).Save()

	// Add draft index
	NewDraftIndex(user.ID).Save()

	// Add empty push subscriptions
	DB.Set("PushSubscriptions", user.ID, &PushSubscriptions{
		UserID: user.ID,
		Items:  []*PushSubscription{},
	})

	// Add empty follow list
	NewUserFollows(user.ID).Save()

	// Add empty notifications list
	NewUserNotifications(user.ID).Save()

	// Fetch gravatar
	if user.Email != "" {
		gravatarURL := gravatar.Url(user.Email) + "?s=" + fmt.Sprint(AvatarMaxSize) + "&d=404&r=pg"
		gravatarURL = strings.Replace(gravatarURL, "http://", "https://", 1)

		response, err := client.Get(gravatarURL).End()

		if err == nil && response.StatusCode() == http.StatusOK {
			data := response.Bytes()
			user.SetAvatarBytes(data)
		}
	}
}

// SendNotification accepts a PushNotification and generates a new Notification object.
// The notification is then sent to all registered push devices.
func (user *User) SendNotification(pushNotification *PushNotification) {
	// Don't ever send notifications in development mode
	if IsDevelopment() && user.ID != "4J6qpK1ve" {
		return
	}

	// Save notification in database
	notification := NewNotification(user.ID, pushNotification)
	notification.Save()

	userNotifications := user.Notifications()
	userNotifications.Add(notification.ID)
	userNotifications.Save()

	// Send push notification
	subs := user.PushSubscriptions()
	expired := []*PushSubscription{}

	for _, sub := range subs.Items {
		resp, err := sub.SendNotification(pushNotification)

		if resp != nil && resp.StatusCode == http.StatusGone {
			expired = append(expired, sub)
			continue
		}

		// Print errors
		if err != nil {
			fmt.Println(err)
			continue
		}

		// Print bad status codes
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
			body, _ := ioutil.ReadAll(resp.Body)
			fmt.Println(resp.StatusCode, string(body))
			continue
		}

		sub.LastSuccess = DateTimeUTC()
	}

	// Remove expired items
	if len(expired) > 0 {
		for _, sub := range expired {
			subs.Remove(sub.ID())
		}
	}

	// Save changes
	subs.Save()
}

// RealName returns the real name of the user.
func (user *User) RealName() string {
	if user.LastName == "" {
		return user.FirstName
	}

	if user.FirstName == "" {
		return user.LastName
	}

	return user.FirstName + " " + user.LastName
}

// RegisteredTime returns the time the user registered his account.
func (user *User) RegisteredTime() time.Time {
	reg, _ := time.Parse(time.RFC3339, user.Registered)
	return reg
}

// LastSeenTime returns the time the user was last seen on the site.
func (user *User) LastSeenTime() time.Time {
	lastSeen, _ := time.Parse(time.RFC3339, user.LastSeen)
	return lastSeen
}

// IsActive tells you whether the user is active.
func (user *User) IsActive() bool {
	lastSeen, _ := time.Parse(time.RFC3339, user.LastSeen)
	twoWeeksAgo := time.Now().Add(-14 * 24 * time.Hour)

	if lastSeen.Unix() < twoWeeksAgo.Unix() {
		return false
	}

	if !user.AnimeList().HasItemsWithStatus(AnimeListStatusWatching) {
		return false
	}

	return true
}

// IsPro returns whether the user is a PRO user or not.
func (user *User) IsPro() bool {
	if user.ProExpires == "" {
		return false
	}

	return DateTimeUTC() < user.ProExpires
}

// ExtendProDuration extends the PRO account duration by the given duration.
func (user *User) ExtendProDuration(duration time.Duration) {
	now := time.Now().UTC()
	expires, _ := time.Parse(time.RFC3339, user.ProExpires)

	// If the user never had a PRO account yet,
	// or if it already expired,
	// use the current time as the start time.
	if user.ProExpires == "" || now.Unix() > expires.Unix() {
		expires = now
	}

	user.ProExpires = expires.Add(duration).Format(time.RFC3339)
}

// TimeSinceRegistered returns the duration since the user registered his account.
func (user *User) TimeSinceRegistered() time.Duration {
	registered, _ := time.Parse(time.RFC3339, user.Registered)
	return time.Since(registered)
}

// HasNick returns whether the user has a custom nickname.
func (user *User) HasNick() bool {
	return !strings.HasPrefix(user.Nick, "g") && !strings.HasPrefix(user.Nick, "fb") && !strings.HasPrefix(user.Nick, "t") && user.Nick != ""
}

// WebsiteURL adds https:// to the URL.
func (user *User) WebsiteURL() string {
	return "https://" + user.WebsiteShortURL()
}

// WebsiteShortURL returns the user website without the protocol.
func (user *User) WebsiteShortURL() string {
	return strings.Replace(strings.Replace(user.Website, "https://", "", 1), "http://", "", 1)
}

// Link returns the URI to the user page.
func (user *User) Link() string {
	return "/+" + user.Nick
}

// HasAvatar tells you whether the user has an avatar or not.
func (user *User) HasAvatar() bool {
	return user.Avatar.Extension != ""
}

// AvatarLink returns the URL to the user avatar.
// Expects "small" (50 x 50) or "large" (560 x 560) for the size parameter.
func (user *User) AvatarLink(size string) string {
	if user.HasAvatar() {
		return fmt.Sprintf("//%s/images/avatars/%s/%s%s?%v", MediaHost, size, user.ID, user.Avatar.Extension, user.Avatar.LastModified)
	}

	return fmt.Sprintf("//%s/images/elements/no-avatar.svg", MediaHost)
}

// CoverLink ...
func (user *User) CoverLink(size string) string {
	if user.Cover.Extension != "" {
		return fmt.Sprintf("//%s/images/covers/%s/%s%s?%v", MediaHost, size, user.ID, user.Cover.Extension, user.Cover.LastModified)
	}

	return "/images/elements/default-cover.jpg"
}

// Gravatar returns the URL to the gravatar if an email has been registered.
func (user *User) Gravatar() string {
	if user.Email == "" {
		return ""
	}

	return gravatar.SecureUrl(user.Email) + "?s=" + fmt.Sprint(AvatarMaxSize)
}

// EditorScore returns the editor score.
func (user *User) EditorScore() int {
	ignoreDifferences := FilterIgnoreAnimeDifferences(func(entry *IgnoreAnimeDifference) bool {
		return entry.CreatedBy == user.ID
	})

	score := len(ignoreDifferences) * IgnoreAnimeDifferenceEditorScore

	logEntries := FilterEditLogEntries(func(entry *EditLogEntry) bool {
		return entry.UserID == user.ID
	})

	for _, entry := range logEntries {
		score += entry.EditorScore()
	}

	return score
}

// ActivateItemEffect activates an item in the user inventory by the given item ID.
func (user *User) ActivateItemEffect(itemID string) error {
	month := 30 * 24 * time.Hour

	switch itemID {
	case "pro-account-1":
		user.ExtendProDuration(1 * month)
		user.Save()
		return nil

	case "pro-account-3":
		user.ExtendProDuration(3 * month)
		user.Save()
		return nil

	case "pro-account-6":
		user.ExtendProDuration(6 * month)
		user.Save()
		return nil

	case "pro-account-12":
		user.ExtendProDuration(12 * month)
		user.Save()
		return nil

	case "pro-account-24":
		user.ExtendProDuration(24 * month)
		user.Save()
		return nil

	default:
		return errors.New("Can't activate unknown item: " + itemID)
	}
}

// SetNick changes the user's nickname safely.
func (user *User) SetNick(newName string) error {
	setNickMutex.Lock()
	defer setNickMutex.Unlock()

	newName = autocorrect.UserNick(newName)

	if !validate.Nick(newName) {
		return errors.New("Invalid nickname")
	}

	if newName == user.Nick {
		return nil
	}

	// Make sure the nickname doesn't exist already
	_, err := GetUserByNick(newName)

	// If there was no error: the username exists.
	// If "not found" is not included in the error message it's a different error type.
	if err == nil || !strings.Contains(err.Error(), "not found") {
		return errors.New("Username '" + newName + "' is taken already")
	}

	user.ForceSetNick(newName)
	return nil
}

// ForceSetNick forces a nickname overwrite.
func (user *User) ForceSetNick(newName string) {
	// Delete old nick reference
	DB.Delete("NickToUser", user.Nick)

	// Set new nick
	user.Nick = newName

	DB.Set("NickToUser", user.Nick, &NickToUser{
		Nick:   user.Nick,
		UserID: user.ID,
	})
}

// SetEmail changes the user's email safely.
func (user *User) SetEmail(newName string) error {
	setEmailMutex.Lock()
	defer setEmailMutex.Unlock()

	if !validate.Email(user.Email) {
		return errors.New("Invalid email address")
	}

	// Delete old email reference
	DB.Delete("EmailToUser", user.Email)

	// Set new email
	user.Email = newName

	DB.Set("EmailToUser", user.Email, &EmailToUser{
		Email:  user.Email,
		UserID: user.ID,
	})

	return nil
}

// RefreshOsuInfo refreshes a user's Osu information.
func (user *User) RefreshOsuInfo() error {
	if user.Accounts.Osu.Nick == "" {
		return nil
	}

	osu, err := osu.GetUser(user.Accounts.Osu.Nick)

	if err != nil {
		return err
	}

	user.Accounts.Osu.PP, _ = strconv.ParseFloat(osu.PPRaw, 64)
	user.Accounts.Osu.Level, _ = strconv.ParseFloat(osu.Level, 64)
	user.Accounts.Osu.Accuracy, _ = strconv.ParseFloat(osu.Accuracy, 64)

	return nil
}
<commit_msg>Fixed SetEmail validation<commit_after>package arn

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/aerogo/http/client"
	"github.com/animenotifier/arn/autocorrect"
	"github.com/animenotifier/arn/validate"
	"github.com/animenotifier/osu"
	gravatar "github.com/ungerik/go-gravatar"
)

var setNickMutex sync.Mutex
var setEmailMutex sync.Mutex

// User is a registered person.
type User struct {
	ID           string       `json:"id"`
	Nick         string       `json:"nick" editable:"true"`
	FirstName    string       `json:"firstName" private:"true"`
	LastName     string       `json:"lastName" private:"true"`
	Email        string       `json:"email" editable:"true" private:"true"`
	Role         string       `json:"role"`
	Registered   string       `json:"registered"`
	LastLogin    string       `json:"lastLogin" private:"true"`
	LastSeen     string       `json:"lastSeen" private:"true"`
	ProExpires   string       `json:"proExpires" editable:"true"`
	Gender       string       `json:"gender" private:"true"`
	Language     string       `json:"language"`
	Tagline      string       `json:"tagline" editable:"true"`
	Introduction string       `json:"introduction" editable:"true" type:"textarea"`
	Website      string       `json:"website" editable:"true"`
	IP           string       `json:"ip" private:"true"`
	UserAgent    string       `json:"agent" private:"true"`
	Balance      int          `json:"balance" private:"true"`
	Avatar       UserAvatar   `json:"avatar"`
	Cover        UserCover    `json:"cover"`
	AgeRange     UserAgeRange `json:"ageRange" private:"true"`
	Accounts     UserAccounts `json:"accounts" private:"true"`
	Browser      UserBrowser  `json:"browser" private:"true"`
	OS           UserOS       `json:"os" private:"true"`
	Location     *Location    `json:"location" private:"true"`

	// user.Email = ""
	// user.Gender = ""
	// user.FirstName = ""
	// user.LastName = ""
	// user.IP = ""
	// user.UserAgent = ""
	// user.LastLogin = ""
	// user.LastSeen = ""
	// user.Accounts.Facebook.ID = ""
	// user.Accounts.Google.ID = ""
	// user.Accounts.Twitter.ID = ""
	// user.AgeRange = UserAgeRange{}
	// user.Location = &Location{}
	// user.Browser = UserBrowser{}
	// user.OS = UserOS{}
}

// NewUser creates an empty user object with a unique ID.
func NewUser() *User {
	user := &User{
		ID: GenerateID("User"),

		// Avoid nil value fields
		Location: &Location{},
	}

	return user
}

// RegisterUser registers a new user in the database and sets up all the required references.
func RegisterUser(user *User) {
	user.Registered = DateTimeUTC()
	user.LastLogin = user.Registered
	user.LastSeen = user.Registered

	// Save nick in NickToUser table
	DB.Set("NickToUser", user.Nick, &NickToUser{
		Nick:   user.Nick,
		UserID: user.ID,
	})

	// Save email in EmailToUser table
	if user.Email != "" {
		DB.Set("EmailToUser", user.Email, &EmailToUser{
			Email:  user.Email,
			UserID: user.ID,
		})
	}

	// Create default settings
	NewSettings(user).Save()

	// Add empty anime list
	DB.Set("AnimeList", user.ID, &AnimeList{
		UserID: user.ID,
		Items:  []*AnimeListItem{},
	})

	// Add empty inventory
	NewInventory(user.ID).Save()

	// Add draft index
	NewDraftIndex(user.ID).Save()

	// Add empty push subscriptions
	DB.Set("PushSubscriptions", user.ID, &PushSubscriptions{
		UserID: user.ID,
		Items:  []*PushSubscription{},
	})

	// Add empty follow list
	NewUserFollows(user.ID).Save()

	// Add empty notifications list
	NewUserNotifications(user.ID).Save()

	// Fetch gravatar
	if user.Email != "" {
		gravatarURL := gravatar.Url(user.Email) + "?s=" + fmt.Sprint(AvatarMaxSize) + "&d=404&r=pg"
		gravatarURL = strings.Replace(gravatarURL, "http://", "https://", 1)

		response, err := client.Get(gravatarURL).End()

		if err == nil && response.StatusCode() == http.StatusOK {
			data := response.Bytes()
			user.SetAvatarBytes(data)
		}
	}
}

// SendNotification accepts a PushNotification and generates a new Notification object.
// The notification is then sent to all registered push devices.
func (user *User) SendNotification(pushNotification *PushNotification) {
	// Don't ever send notifications in development mode
	if IsDevelopment() && user.ID != "4J6qpK1ve" {
		return
	}

	// Save notification in database
	notification := NewNotification(user.ID, pushNotification)
	notification.Save()

	userNotifications := user.Notifications()
	userNotifications.Add(notification.ID)
	userNotifications.Save()

	// Send push notification
	subs := user.PushSubscriptions()
	expired := []*PushSubscription{}

	for _, sub := range subs.Items {
		resp, err := sub.SendNotification(pushNotification)

		if resp != nil && resp.StatusCode == http.StatusGone {
			expired = append(expired, sub)
			continue
		}

		// Print errors
		if err != nil {
			fmt.Println(err)
			continue
		}

		// Print bad status codes
		if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
			body, _ := ioutil.ReadAll(resp.Body)
			fmt.Println(resp.StatusCode, string(body))
			continue
		}

		sub.LastSuccess = DateTimeUTC()
	}

	// Remove expired items
	if len(expired) > 0 {
		for _, sub := range expired {
			subs.Remove(sub.ID())
		}
	}

	// Save changes
	subs.Save()
}

// RealName returns the real name of the user.
func (user *User) RealName() string {
	if user.LastName == "" {
		return user.FirstName
	}

	if user.FirstName == "" {
		return user.LastName
	}

	return user.FirstName + " " + user.LastName
}

// RegisteredTime returns the time the user registered his account.
func (user *User) RegisteredTime() time.Time {
	reg, _ := time.Parse(time.RFC3339, user.Registered)
	return reg
}

// LastSeenTime returns the time the user was last seen on the site.
func (user *User) LastSeenTime() time.Time {
	lastSeen, _ := time.Parse(time.RFC3339, user.LastSeen)
	return lastSeen
}

// IsActive tells you whether the user is active.
func (user *User) IsActive() bool {
	lastSeen, _ := time.Parse(time.RFC3339, user.LastSeen)
	twoWeeksAgo := time.Now().Add(-14 * 24 * time.Hour)

	if lastSeen.Unix() < twoWeeksAgo.Unix() {
		return false
	}

	if !user.AnimeList().HasItemsWithStatus(AnimeListStatusWatching) {
		return false
	}

	return true
}

// IsPro returns whether the user is a PRO user or not.
func (user *User) IsPro() bool {
	if user.ProExpires == "" {
		return false
	}

	return DateTimeUTC() < user.ProExpires
}

// ExtendProDuration extends the PRO account duration by the given duration.
func (user *User) ExtendProDuration(duration time.Duration) {
	now := time.Now().UTC()
	expires, _ := time.Parse(time.RFC3339, user.ProExpires)

	// If the user never had a PRO account yet,
	// or if it already expired,
	// use the current time as the start time.
	if user.ProExpires == "" || now.Unix() > expires.Unix() {
		expires = now
	}

	user.ProExpires = expires.Add(duration).Format(time.RFC3339)
}

// TimeSinceRegistered returns the duration since the user registered his account.
func (user *User) TimeSinceRegistered() time.Duration {
	registered, _ := time.Parse(time.RFC3339, user.Registered)
	return time.Since(registered)
}

// HasNick returns whether the user has a custom nickname.
func (user *User) HasNick() bool {
	return !strings.HasPrefix(user.Nick, "g") && !strings.HasPrefix(user.Nick, "fb") && !strings.HasPrefix(user.Nick, "t") && user.Nick != ""
}

// WebsiteURL adds https:// to the URL.
func (user *User) WebsiteURL() string {
	return "https://" + user.WebsiteShortURL()
}

// WebsiteShortURL returns the user website without the protocol.
func (user *User) WebsiteShortURL() string {
	return strings.Replace(strings.Replace(user.Website, "https://", "", 1), "http://", "", 1)
}

// Link returns the URI to the user page.
func (user *User) Link() string {
	return "/+" + user.Nick
}

// HasAvatar tells you whether the user has an avatar or not.
func (user *User) HasAvatar() bool {
	return user.Avatar.Extension != ""
}

// AvatarLink returns the URL to the user avatar.
// Expects "small" (50 x 50) or "large" (560 x 560) for the size parameter.
func (user *User) AvatarLink(size string) string {
	if user.HasAvatar() {
		return fmt.Sprintf("//%s/images/avatars/%s/%s%s?%v", MediaHost, size, user.ID, user.Avatar.Extension, user.Avatar.LastModified)
	}

	return fmt.Sprintf("//%s/images/elements/no-avatar.svg", MediaHost)
}

// CoverLink ...
func (user *User) CoverLink(size string) string {
	if user.Cover.Extension != "" {
		return fmt.Sprintf("//%s/images/covers/%s/%s%s?%v", MediaHost, size, user.ID, user.Cover.Extension, user.Cover.LastModified)
	}

	return "/images/elements/default-cover.jpg"
}

// Gravatar returns the URL to the gravatar if an email has been registered.
func (user *User) Gravatar() string {
	if user.Email == "" {
		return ""
	}

	return gravatar.SecureUrl(user.Email) + "?s=" + fmt.Sprint(AvatarMaxSize)
}

// EditorScore returns the editor score.
func (user *User) EditorScore() int {
	ignoreDifferences := FilterIgnoreAnimeDifferences(func(entry *IgnoreAnimeDifference) bool {
		return entry.CreatedBy == user.ID
	})

	score := len(ignoreDifferences) * IgnoreAnimeDifferenceEditorScore

	logEntries := FilterEditLogEntries(func(entry *EditLogEntry) bool {
		return entry.UserID == user.ID
	})

	for _, entry := range logEntries {
		score += entry.EditorScore()
	}

	return score
}

// ActivateItemEffect activates an item in the user inventory by the given item ID.
func (user *User) ActivateItemEffect(itemID string) error {
	month := 30 * 24 * time.Hour

	switch itemID {
	case "pro-account-1":
		user.ExtendProDuration(1 * month)
		user.Save()
		return nil

	case "pro-account-3":
		user.ExtendProDuration(3 * month)
		user.Save()
		return nil

	case "pro-account-6":
		user.ExtendProDuration(6 * month)
		user.Save()
		return nil

	case "pro-account-12":
		user.ExtendProDuration(12 * month)
		user.Save()
		return nil

	case "pro-account-24":
		user.ExtendProDuration(24 * month)
		user.Save()
		return nil

	default:
		return errors.New("Can't activate unknown item: " + itemID)
	}
}

// SetNick changes the user's nickname safely.
func (user *User) SetNick(newName string) error {
	setNickMutex.Lock()
	defer setNickMutex.Unlock()

	newName = autocorrect.UserNick(newName)

	if !validate.Nick(newName) {
		return errors.New("Invalid nickname")
	}

	if newName == user.Nick {
		return nil
	}

	// Make sure the nickname doesn't exist already
	_, err := GetUserByNick(newName)

	// If there was no error: the username exists.
	// If "not found" is not included in the error message it's a different error type.
	if err == nil || !strings.Contains(err.Error(), "not found") {
		return errors.New("Username '" + newName + "' is taken already")
	}

	user.ForceSetNick(newName)
	return nil
}

// ForceSetNick forces a nickname overwrite.
func (user *User) ForceSetNick(newName string) {
	// Delete old nick reference
	DB.Delete("NickToUser", user.Nick)

	// Set new nick
	user.Nick = newName

	DB.Set("NickToUser", user.Nick, &NickToUser{
		Nick:   user.Nick,
		UserID: user.ID,
	})
}

// SetEmail changes the user's email safely.
func (user *User) SetEmail(newEmail string) error {
	setEmailMutex.Lock()
	defer setEmailMutex.Unlock()

	if !validate.Email(newEmail) {
		return errors.New("Invalid email address")
	}

	// Delete old email reference
	DB.Delete("EmailToUser", user.Email)

	// Set new email
	user.Email = newEmail

	DB.Set("EmailToUser", user.Email, &EmailToUser{
		Email:  user.Email,
		UserID: user.ID,
	})

	return nil
}

// RefreshOsuInfo refreshes a user's Osu information.
func (user *User) RefreshOsuInfo() error {
	if user.Accounts.Osu.Nick == "" {
		return nil
	}

	osu, err := osu.GetUser(user.Accounts.Osu.Nick)

	if err != nil {
		return err
	}

	user.Accounts.Osu.PP, _ = strconv.ParseFloat(osu.PPRaw, 64)
	user.Accounts.Osu.Level, _ = strconv.ParseFloat(osu.Level, 64)
	user.Accounts.Osu.Accuracy, _ = strconv.ParseFloat(osu.Accuracy, 64)

	return nil
}
<|endoftext|>


<commit_before>package getter

import (
	"context"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

// S3Getter is a Getter implementation that will download a module from
// a S3 bucket.
type S3Getter struct {
	getter
}

func (g *S3Getter) ClientMode(u *url.URL) (ClientMode, error) {
	// Parse URL
	region, bucket, path, _, creds, err := g.parseUrl(u)
	if err != nil {
		return 0, err
	}

	// Create client config
	config := g.getAWSConfig(region, u, creds)
	sess := session.New(config)
	client := s3.New(sess)

	// List the object(s) at the given prefix
	req := &s3.ListObjectsInput{
		Bucket: aws.String(bucket),
		Prefix: aws.String(path),
	}
	resp, err := client.ListObjects(req)
	if err != nil {
		return 0, err
	}

	for _, o := range resp.Contents {
		// Use file mode on exact match.
		if *o.Key == path {
			return ClientModeFile, nil
		}

		// Use dir mode if child keys are found.
		if strings.HasPrefix(*o.Key, path+"/") {
			return ClientModeDir, nil
		}
	}

	// There was no match, so just return file mode. The download is going
	// to fail but we will let S3 return the proper error later.
	return ClientModeFile, nil
}

func (g *S3Getter) Get(dst string, u *url.URL) error {
	ctx := g.Context()

	// Parse URL
	region, bucket, path, _, creds, err := g.parseUrl(u)
	if err != nil {
		return err
	}

	// Remove destination if it already exists
	_, err = os.Stat(dst)
	if err != nil && !os.IsNotExist(err) {
		return err
	}

	if err == nil {
		// Remove the destination
		if err := os.RemoveAll(dst); err != nil {
			return err
		}
	}

	// Create all the parent directories
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return err
	}

	config := g.getAWSConfig(region, u, creds)
	sess := session.New(config)
	client := s3.New(sess)

	// List files in path, keep listing until no more objects are found
	lastMarker := ""
	hasMore := true
	for hasMore {
		req := &s3.ListObjectsInput{
			Bucket: aws.String(bucket),
			Prefix: aws.String(path),
		}
		if lastMarker != "" {
			req.Marker = aws.String(lastMarker)
		}

		resp, err := client.ListObjects(req)
		if err != nil {
			return err
		}

		hasMore = aws.BoolValue(resp.IsTruncated)

		// Get each object storing each file relative to the destination path
		for _, object := range resp.Contents {
			lastMarker = aws.StringValue(object.Key)
			objPath := aws.StringValue(object.Key)

			// If the key ends with a backslash assume it is a directory and ignore
			if strings.HasSuffix(objPath, "/") {
				continue
			}

			// Get the object destination path
			objDst, err := filepath.Rel(path, objPath)
			if err != nil {
				return err
			}
			objDst = filepath.Join(dst, objDst)

			if err := g.getObject(ctx, client, objDst, bucket, objPath, ""); err != nil {
				return err
			}
		}
	}

	return nil
}

func (g *S3Getter) GetFile(dst string, u *url.URL) error {
	ctx := g.Context()
	region, bucket, path, version, creds, err := g.parseUrl(u)
	if err != nil {
		return err
	}

	config := g.getAWSConfig(region, u, creds)
	sess := session.New(config)
	client := s3.New(sess)
	return g.getObject(ctx, client, dst, bucket, path, version)
}

func (g *S3Getter) getObject(ctx context.Context, client *s3.S3, dst, bucket, key, version string) error {
	req := &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	}
	if version != "" {
		req.VersionId = aws.String(version)
	}

	resp, err := client.GetObject(req)
	if err != nil {
		return err
	}

	// Create all the parent directories
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return err
	}

	f, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = Copy(ctx, f, resp.Body)
	return err
}

func (g *S3Getter) getAWSConfig(region string, url *url.URL, creds *credentials.Credentials) *aws.Config {
	conf := &aws.Config{}
	if creds == nil {
		// Grab the metadata URL
		metadataURL := os.Getenv("AWS_METADATA_URL")
		if metadataURL == "" {
			metadataURL = "http://169.254.169.254:80/latest"
		}

		creds = credentials.NewChainCredentials(
			[]credentials.Provider{
				&credentials.EnvProvider{},
				&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
				&ec2rolecreds.EC2RoleProvider{
					Client: ec2metadata.New(session.New(&aws.Config{
						Endpoint: aws.String(metadataURL),
					})),
				},
			})
	}

	if creds != nil {
		conf.Endpoint = &url.Host
		conf.S3ForcePathStyle = aws.Bool(true)
		if url.Scheme == "http" {
			conf.DisableSSL = aws.Bool(true)
		}
	}

	conf.Credentials = creds
	if region != "" {
		conf.Region = aws.String(region)
	}

	return conf
}

func (g *S3Getter) parseUrl(u *url.URL) (region, bucket, path, version string, creds *credentials.Credentials, err error) {
	// This just check whether we are dealing with S3 or
	// any other S3 compliant service. S3 has a predictable
	// url as others do not
	if strings.Contains(u.Host, "amazonaws.com") {
		// Expected host style: s3.amazonaws.com. They always have 3 parts,
		// although the first may differ if we're accessing a specific region.
		hostParts := strings.Split(u.Host, ".")
		if len(hostParts) != 3 {
			err = fmt.Errorf("URL is not a valid S3 URL")
			return
		}

		// Parse the region out of the first part of the host
		region = strings.TrimPrefix(strings.TrimPrefix(hostParts[0], "s3-"), "s3")
		if region == "" {
			region = "us-east-1"
		}

		pathParts := strings.SplitN(u.Path, "/", 3)
		if len(pathParts) != 3 {
			err = fmt.Errorf("URL is not a valid S3 URL")
			return
		}

		bucket = pathParts[1]
		path = pathParts[2]
		version = u.Query().Get("version")

	} else {
		pathParts := strings.SplitN(u.Path, "/", 3)
		if len(pathParts) != 3 {
			err = fmt.Errorf("URL is not a valid S3 complaint URL")
			return
		}
		bucket = pathParts[1]
		path = pathParts[2]
		version = u.Query().Get("version")
		region = u.Query().Get("region")
		if region == "" {
			region = "us-east-1"
		}
	}

	_, hasAwsId := u.Query()["aws_access_key_id"]
	_, hasAwsSecret := u.Query()["aws_access_key_secret"]
	_, hasAwsToken := u.Query()["aws_access_token"]
	if hasAwsId || hasAwsSecret || hasAwsToken {
		creds = credentials.NewStaticCredentials(
			u.Query().Get("aws_access_key_id"),
			u.Query().Get("aws_access_key_secret"),
			u.Query().Get("aws_access_token"),
		)
	}

	return
}
<commit_msg>Use default AWS credential chain under normal circumstances (#218)<commit_after>package getter

import (
	"context"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

// S3Getter is a Getter implementation that will download a module from
// a S3 bucket.
type S3Getter struct {
	getter
}

func (g *S3Getter) ClientMode(u *url.URL) (ClientMode, error) {
	// Parse URL
	region, bucket, path, _, creds, err := g.parseUrl(u)
	if err != nil {
		return 0, err
	}

	// Create client config
	config := g.getAWSConfig(region, u, creds)
	sess := session.New(config)
	client := s3.New(sess)

	// List the object(s) at the given prefix
	req := &s3.ListObjectsInput{
		Bucket: aws.String(bucket),
		Prefix: aws.String(path),
	}
	resp, err := client.ListObjects(req)
	if err != nil {
		return 0, err
	}

	for _, o := range resp.Contents {
		// Use file mode on exact match.
		if *o.Key == path {
			return ClientModeFile, nil
		}

		// Use dir mode if child keys are found.
		if strings.HasPrefix(*o.Key, path+"/") {
			return ClientModeDir, nil
		}
	}

	// There was no match, so just return file mode. The download is going
	// to fail but we will let S3 return the proper error later.
	return ClientModeFile, nil
}

func (g *S3Getter) Get(dst string, u *url.URL) error {
	ctx := g.Context()

	// Parse URL
	region, bucket, path, _, creds, err := g.parseUrl(u)
	if err != nil {
		return err
	}

	// Remove destination if it already exists
	_, err = os.Stat(dst)
	if err != nil && !os.IsNotExist(err) {
		return err
	}

	if err == nil {
		// Remove the destination
		if err := os.RemoveAll(dst); err != nil {
			return err
		}
	}

	// Create all the parent directories
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return err
	}

	config := g.getAWSConfig(region, u, creds)
	sess := session.New(config)
	client := s3.New(sess)

	// List files in path, keep listing until no more objects are found
	lastMarker := ""
	hasMore := true
	for hasMore {
		req := &s3.ListObjectsInput{
			Bucket: aws.String(bucket),
			Prefix: aws.String(path),
		}
		if lastMarker != "" {
			req.Marker = aws.String(lastMarker)
		}

		resp, err := client.ListObjects(req)
		if err != nil {
			return err
		}

		hasMore = aws.BoolValue(resp.IsTruncated)

		// Get each object storing each file relative to the destination path
		for _, object := range resp.Contents {
			lastMarker = aws.StringValue(object.Key)
			objPath := aws.StringValue(object.Key)

			// If the key ends with a backslash assume it is a directory and ignore
			if strings.HasSuffix(objPath, "/") {
				continue
			}

			// Get the object destination path
			objDst, err := filepath.Rel(path, objPath)
			if err != nil {
				return err
			}
			objDst = filepath.Join(dst, objDst)

			if err := g.getObject(ctx, client, objDst, bucket, objPath, ""); err != nil {
				return err
			}
		}
	}

	return nil
}

func (g *S3Getter) GetFile(dst string, u *url.URL) error {
	ctx := g.Context()
	region, bucket, path, version, creds, err := g.parseUrl(u)
	if err != nil {
		return err
	}

	config := g.getAWSConfig(region, u, creds)
	sess := session.New(config)
	client := s3.New(sess)
	return g.getObject(ctx, client, dst, bucket, path, version)
}

func (g *S3Getter) getObject(ctx context.Context, client *s3.S3, dst, bucket, key, version string) error {
	req := &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	}
	if version != "" {
		req.VersionId = aws.String(version)
	}

	resp, err := client.GetObject(req)
	if err != nil {
		return err
	}

	// Create all the parent directories
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return err
	}

	f, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = Copy(ctx, f, resp.Body)
	return err
}

func (g *S3Getter) getAWSConfig(region string, url *url.URL, creds *credentials.Credentials) *aws.Config {
	conf := &aws.Config{}
	metadataURLOverride := os.Getenv("AWS_METADATA_URL")
	if creds == nil && metadataURLOverride != "" {
		creds = credentials.NewChainCredentials(
			[]credentials.Provider{
				&credentials.EnvProvider{},
				&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
				&ec2rolecreds.EC2RoleProvider{
					Client: ec2metadata.New(session.New(&aws.Config{
						Endpoint: aws.String(metadataURLOverride),
					})),
				},
			})
	}

	if creds != nil {
		conf.Endpoint = &url.Host
		conf.S3ForcePathStyle = aws.Bool(true)
		if url.Scheme == "http" {
			conf.DisableSSL = aws.Bool(true)
		}
	}

	conf.Credentials = creds
	if region != "" {
		conf.Region = aws.String(region)
	}

	return conf.WithCredentialsChainVerboseErrors(true)
}

func (g *S3Getter) parseUrl(u *url.URL) (region, bucket, path, version string, creds *credentials.Credentials, err error) {
	// This just check whether we are dealing with S3 or
	// any other S3 compliant service. S3 has a predictable
	// url as others do not
	if strings.Contains(u.Host, "amazonaws.com") {
		// Expected host style: s3.amazonaws.com. They always have 3 parts,
		// although the first may differ if we're accessing a specific region.
		hostParts := strings.Split(u.Host, ".")
		if len(hostParts) != 3 {
			err = fmt.Errorf("URL is not a valid S3 URL")
			return
		}

		// Parse the region out of the first part of the host
		region = strings.TrimPrefix(strings.TrimPrefix(hostParts[0], "s3-"), "s3")
		if region == "" {
			region = "us-east-1"
		}

		pathParts := strings.SplitN(u.Path, "/", 3)
		if len(pathParts) != 3 {
			err = fmt.Errorf("URL is not a valid S3 URL")
			return
		}

		bucket = pathParts[1]
		path = pathParts[2]
		version = u.Query().Get("version")

	} else {
		pathParts := strings.SplitN(u.Path, "/", 3)
		if len(pathParts) != 3 {
			err = fmt.Errorf("URL is not a valid S3 complaint URL")
			return
		}
		bucket = pathParts[1]
		path = pathParts[2]
		version = u.Query().Get("version")
		region = u.Query().Get("region")
		if region == "" {
			region = "us-east-1"
		}
	}

	_, hasAwsId := u.Query()["aws_access_key_id"]
	_, hasAwsSecret := u.Query()["aws_access_key_secret"]
	_, hasAwsToken := u.Query()["aws_access_token"]
	if hasAwsId || hasAwsSecret || hasAwsToken {
		creds = credentials.NewStaticCredentials(
			u.Query().Get("aws_access_key_id"),
			u.Query().Get("aws_access_key_secret"),
			u.Query().Get("aws_access_token"),
		)
	}

	return
}
<|endoftext|>


<commit_before>package kafka

import (
	"encoding/binary"
	"net"
)

type Client struct {
	addr string
	conn net.Conn
}

func NewClient(addr string) (client *Client, err error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	client = &Client{addr, conn}
	return client, err
}

func (client *Client) write(buf []byte) (err error) {
	var size [4]byte
	binary.BigEndian.PutUint32(size[:], uint32(len(buf)))
	_, err = client.conn.Write(size[:])
	if err != nil {
		return err
	}
	_, err = client.conn.Write(buf)
	if err != nil {
		return err
	}
	return nil
}

func (client *Client) read() (buf []byte, err error) {
	var size [4]byte
	n, err := client.conn.Read(size[:])
	if err != nil {
		return nil, err
	}
	if n != 4 {
		return nil, nil
	}
	s := binary.BigEndian.Uint32(size[:])
	buf = make([]byte, s)
	n, err = client.conn.Read(buf)
	if err != nil {
		return nil, err
	}
	if uint32(n) != s {
		return nil, nil
	}
	return buf, nil
}
<commit_msg>Build a slice right away, instead of creating them for every call that takes a slice not an array<commit_after>package kafka

import (
	"encoding/binary"
	"net"
)

type Client struct {
	addr string
	conn net.Conn
}

func NewClient(addr string) (client *Client, err error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	client = &Client{addr, conn}
	return client, err
}

func (client *Client) write(buf []byte) (err error) {
	size := make([]byte, 4)
	binary.BigEndian.PutUint32(size, uint32(len(buf)))
	_, err = client.conn.Write(size)
	if err != nil {
		return err
	}
	_, err = client.conn.Write(buf)
	if err != nil {
		return err
	}
	return nil
}

func (client *Client) read() (buf []byte, err error) {
	size := make([]byte, 4)
	n, err := client.conn.Read(size)
	if err != nil {
		return nil, err
	}
	if n != 4 {
		return nil, nil
	}
	s := binary.BigEndian.Uint32(size)
	buf = make([]byte, s)
	n, err = client.conn.Read(buf)
	if err != nil {
		return nil, err
	}
	if uint32(n) != s {
		return nil, nil
	}
	return buf, nil
}
<|endoftext|>