<commit_before>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package client

import (
	"bytes"
	"errors"
	"fmt"
	"net/url"
	"strconv"

	"github.com/bogem/nehm/ui"
	"github.com/valyala/fasthttp"
)

const (
	apiURL   = "https://api.soundcloud.com"
	clientID = "11a37feb6ccc034d5975f3f803928a32"
)

var (
	ErrForbidden = errors.New("403 - Forbidden")
	ErrNotFound  = errors.New("404 - Not Found")

	uriBuffer = new(bytes.Buffer)
)

func addClientID(params *url.Values) {
	params.Set("client_id", clientID)
}

func resolve(params url.Values) ([]byte, error) {
	uri := formResolveURI(params)
	return get(uri)
}

func formResolveURI(params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/resolve?%v", apiURL, params.Encode())
	return uriBuffer.String()
}

func search(params url.Values) ([]byte, error) {
	uri := formSearchURI(params)
	return get(uri)
}

func formSearchURI(params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/tracks?%v", apiURL, params.Encode())
	return uriBuffer.String()
}

func getFavorites(uid string, params url.Values) ([]byte, error) {
	uri := formFavoritesURI(uid, params)
	return get(uri)
}

func formFavoritesURI(uid string, params url.Values) string {
	uriBuffer.Reset()
	addClientID(&params)
	fmt.Fprintf(uriBuffer, "%v/users/%v/favorites?%v", apiURL, uid, params.Encode())
	return uriBuffer.String()
}

func get(uri string) ([]byte, error) {
	statusCode, body, err := fasthttp.Get(nil, uri)
	if err != nil {
		return nil, err
	}
	if err := handleStatusCode(statusCode); err != nil {
		return nil, err
	}
	return body, nil
}

func handleStatusCode(statusCode int) error {
	switch {
	case statusCode == 403:
		return ErrForbidden
	case statusCode == 404:
		return ErrNotFound
	case statusCode >= 300 && statusCode < 500:
		ui.Term("invalid response from SoundCloud: "+strconv.Itoa(statusCode), nil)
	case statusCode >= 500:
		ui.Term("there is a problem by SoundCloud. Please wait a while", nil)
	}
	return nil
}
<commit_msg>client: Improve the performance of URI forming<commit_after>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package client

import (
	"bytes"
	"errors"
	"net/url"
	"strconv"

	"github.com/bogem/nehm/ui"
	"github.com/valyala/fasthttp"
)

const (
	apiURL   = "https://api.soundcloud.com"
	clientID = "11a37feb6ccc034d5975f3f803928a32"
)

var (
	ErrForbidden = errors.New("403 - Forbidden")
	ErrNotFound  = errors.New("404 - Not Found")

	uriBuffer = new(bytes.Buffer)
)

func resolve(params url.Values) ([]byte, error) {
	uri := formResolveURI(params)
	return get(uri)
}

func formResolveURI(params url.Values) string {
	params.Set("client_id", clientID)

	uriBuffer.Reset()
	uriBuffer.WriteString(apiURL)
	uriBuffer.WriteString("/resolve?")
	uriBuffer.WriteString(params.Encode())
	return uriBuffer.String()
}

func search(params url.Values) ([]byte, error) {
	uri := formSearchURI(params)
	return get(uri)
}

func formSearchURI(params url.Values) string {
	params.Set("client_id", clientID)

	uriBuffer.Reset()
	uriBuffer.WriteString(apiURL)
	uriBuffer.WriteString("/tracks?")
	uriBuffer.WriteString(params.Encode())
	return uriBuffer.String()
}

func getFavorites(uid string, params url.Values) ([]byte, error) {
	uri := formFavoritesURI(uid, params)
	return get(uri)
}

func formFavoritesURI(uid string, params url.Values) string {
	params.Set("client_id", clientID)

	uriBuffer.Reset()
	uriBuffer.WriteString(apiURL)
	uriBuffer.WriteString("/users/")
	uriBuffer.WriteString(uid)
	uriBuffer.WriteString("/favorites?")
	uriBuffer.WriteString(params.Encode())
	return uriBuffer.String()
}

func get(uri string) ([]byte, error) {
	statusCode, body, err := fasthttp.Get(nil, uri)
	if err != nil {
		return nil, err
	}
	if err := handleStatusCode(statusCode); err != nil {
		return nil, err
	}
	return body, nil
}

func handleStatusCode(statusCode int) error {
	switch {
	case statusCode == 403:
		return ErrForbidden
	case statusCode == 404:
		return ErrNotFound
	case statusCode >= 300 && statusCode < 500:
		ui.Term("invalid response from SoundCloud: "+strconv.Itoa(statusCode), nil)
	case statusCode >= 500:
		ui.Term("there is a problem by SoundCloud. Please wait a while", nil)
	}
	return nil
}
<|endoftext|>


<commit_before>// OKCoin exchange API

package okcoin

import (
	"bitfx/exchange"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/gorilla/websocket"
)

// Client contains all exchange information
type Client struct {
	key, secret, symbol, currency, websocketURL, restURL, name string
	priority                                                   int
	position, fee, maxPos, availShort, availFunds              float64
	currencyCode                                               byte
}

// Exchange request format
type request struct {
	Event      string            `json:"event"`      // Event to request
	Channel    string            `json:"channel"`    // Channel on which to make request
	Parameters map[string]string `json:"parameters"` // Additional parameters
}

// New returns a pointer to a Client instance
func New(key, secret, symbol, currency string, priority int, fee, availShort, availFunds float64) *Client {
	// URL depends on currency
	var websocketURL, restURL string
	var currencyCode byte
	if strings.ToLower(currency) == "usd" {
		websocketURL = "wss://real.okcoin.com:10440/websocket/okcoinapi"
		restURL = "https://www.okcoin.com/api/v1"
		currencyCode = 0
	} else if strings.ToLower(currency) == "cny" {
		websocketURL = "wss://real.okcoin.cn:10440/websocket/okcoinapi"
		restURL = "https://www.okcoin.cn/api/v1"
		currencyCode = 1
	} else {
		log.Fatal("Currency must be USD or CNY")
	}

	return &Client{
		key:          key,
		secret:       secret,
		symbol:       symbol,
		currency:     currency,
		websocketURL: websocketURL,
		restURL:      restURL,
		priority:     priority,
		fee:          fee,
		availShort:   availShort,
		availFunds:   availFunds,
		currencyCode: currencyCode,
		name:         fmt.Sprintf("OKCoin(%s)", currency),
	}
}

// String implements the Stringer interface
func (client *Client) String() string {
	return client.name
}

// Priority returns the exchange priority for order execution
func (client *Client) Priority() int {
	return client.priority
}

// Fee returns the exchange order fee
func (client *Client) Fee() float64 {
	return client.fee
}

// SetPosition sets the exchange position
func (client *Client) SetPosition(pos float64) {
	client.position = pos
}

// Position returns the exchange position
func (client *Client) Position() float64 {
	return client.position
}

// Currency returns the exchange currency
func (client *Client) Currency() string {
	return client.currency
}

// CurrencyCode returns the exchange currency code
func (client *Client) CurrencyCode() byte {
	return client.currencyCode
}

// SetMaxPos sets the exchange max position
func (client *Client) SetMaxPos(maxPos float64) {
	client.maxPos = maxPos
}

// MaxPos returns the exchange max position
func (client *Client) MaxPos() float64 {
	return client.maxPos
}

// AvailFunds returns the exchange available funds
func (client *Client) AvailFunds() float64 {
	return client.availFunds
}

// AvailShort returns the exchange quantity available for short selling
func (client *Client) AvailShort() float64 {
	return client.availShort
}

// HasCrytpoFee returns true if fee is taken in cryptocurrency on buys
func (client *Client) HasCryptoFee() bool {
	return true
}

// CommunicateBook sends the latest available book data on the supplied channel
func (client *Client) CommunicateBook(bookChan chan<- exchange.Book, doneChan <-chan bool) exchange.Book {
	// Connect to websocket
	ws, _, err := websocket.DefaultDialer.Dial(client.websocketURL, http.Header{})
	if err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}

	// Send request for book data
	channel := fmt.Sprintf("ok_%s%s_depth", client.symbol, client.currency)
	initMessage := request{Event: "addChannel", Channel: channel}
	if err = ws.WriteJSON(initMessage); err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}
	// Get an initial book to return
	_, data, err := ws.ReadMessage()
	if err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}
	book := client.convertToBook(data)

	// Run a read loop in new goroutine
	go client.runLoop(ws, initMessage, bookChan, doneChan)

	return book
}

// Websocket read loop
func (client *Client) runLoop(ws *websocket.Conn, initMessage request, bookChan chan<- exchange.Book, doneChan <-chan bool) {
	// Syncronize access to *websocket.Conn
	receiveWS := make(chan *websocket.Conn)
	reconnectWS := make(chan bool)
	closeWS := make(chan bool)
	go func() {
	LOOP:
		for {
			select {
			// Request to use websocket
			case receiveWS <- ws:
			// Request to reconnect websocket
			case <-reconnectWS:
				ws.Close()
				ws = client.reconnect(initMessage)
			// Request to close websocket
			case <-closeWS:
				ws.Close()
				break LOOP
			}
		}
	}()

	// Setup heartbeat
	pingInterval := 15 * time.Second
	ticker := time.NewTicker(pingInterval)
	ping := []byte(`{"event":"ping"}`)

	// Read from websocket
	dataChan := make(chan []byte)
	go func() {
		for {
			(<-receiveWS).SetReadDeadline(time.Now().Add(pingInterval + time.Second))
			_, data, err := (<-receiveWS).ReadMessage()
			if err != nil {
				// Reconnect on error
				log.Printf("%s WebSocket error: %s", client, err)
				reconnectWS <- true
			} else if string(data) != `{"event":"pong"}` {
				// If not a pong, send for processing
				dataChan <- data
			}
		}
	}()

	for {
		select {
		case <-doneChan:
			// End if notified
			ticker.Stop()
			closeWS <- true
			return
		case <-ticker.C:
			// Send ping (true type-9 pings not supported by server)
			if err := (<-receiveWS).WriteMessage(1, ping); err != nil {
				// Reconnect on error
				log.Printf("%s WebSocket error: %s", client, err)
				reconnectWS <- true
			}
		case data := <-dataChan:
			// Process data and send out to user
			bookChan <- client.convertToBook(data)
		}
	}
}

// Reconnect websocket
func (client *Client) reconnect(initMessage request) *websocket.Conn {
	log.Println("Reconnecting...")

	// Try reconnecting
	ws, _, err := websocket.DefaultDialer.Dial(client.websocketURL, http.Header{})
	if err == nil {
		err = ws.WriteJSON(initMessage)
	}
	// Keep trying on error
	for err != nil {
		log.Printf("%s WebSocket error: %s", client, err)
		time.Sleep(1 * time.Second)
		ws, _, err = websocket.DefaultDialer.Dial(client.websocketURL, http.Header{})
		if err == nil {
			err = ws.WriteJSON(initMessage)
		}
	}

	log.Println("Successful reconnect")

	return ws
}

// Convert websocket data to an exchange.Book
func (client *Client) convertToBook(data []byte) exchange.Book {
	// Unmarshal
	var response []struct {
		Channel   string `json:"channel"`          // Channel name
		ErrorCode int64  `json:"errorcode,string"` // Error code if not successful
		Data      struct {
			Bids       [][2]float64 `json:"bids"`             // Slice of bid data items
			Asks       [][2]float64 `json:"asks"`             // Slice of ask data items
			Timestamp  int64        `json:"timestamp,string"` // Timestamp
			UnitAmount int          `json:"unit_amount"`      // Unit amount for futures

		} `json:"data"` // Data specific to channel
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return exchange.Book{Error: fmt.Errorf("%s book error: %s", client, err)}
	}

	// Return error if there is an exchange error code
	if response[0].ErrorCode != 0 {
		return exchange.Book{Error: fmt.Errorf("%s book error code: %d", client, response[0].ErrorCode)}
	}

	// Translate into exchange.Book structure
	bids := make(exchange.BidItems, 20)
	asks := make(exchange.AskItems, 20)
	for i := 0; i < 20; i++ {
		bids[i].Price = response[0].Data.Bids[i][0]
		bids[i].Amount = response[0].Data.Bids[i][1]
		asks[i].Price = response[0].Data.Asks[i][0]
		asks[i].Amount = response[0].Data.Asks[i][1]
	}
	sort.Sort(bids)
	sort.Sort(asks)

	// Return book
	return exchange.Book{
		Exg:   client,
		Time:  time.Now(),
		Bids:  bids,
		Asks:  asks,
		Error: nil,
	}
}

// SendOrder sends an order to the exchange
func (client *Client) SendOrder(action, otype string, amount, price float64) (int64, error) {
	// Create parameter map for signing
	params := make(map[string]string)
	params["symbol"] = fmt.Sprintf("%s_%s", client.symbol, client.currency)
	if otype == "limit" {
		params["type"] = action
	} else if otype == "market" {
		params["type"] = fmt.Sprintf("%s_%s", action, otype)
	}
	params["price"] = fmt.Sprintf("%f", price)
	params["amount"] = fmt.Sprintf("%f", amount)

	// Send POST request
	data, err := client.post(client.restURL+"/trade.do", params)
	if err != nil {
		return 0, fmt.Errorf("%s SendOrder error: %s", client, err)
	}

	// Unmarshal response
	var response struct {
		ID        int64 `json:"order_id"`
		ErrorCode int64 `json:"error_code"`
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return 0, fmt.Errorf("%s SendOrder error: %s", client, err)
	}
	if response.ErrorCode != 0 {
		return 0, fmt.Errorf("%s SendOrder error code: %d", client, response.ErrorCode)
	}

	return response.ID, nil
}

// CancelOrder cancels an order on the exchange
func (client *Client) CancelOrder(id int64) (bool, error) {
	// Create parameter map for signing
	params := make(map[string]string)
	params["symbol"] = fmt.Sprintf("%s_%s", client.symbol, client.currency)
	params["order_id"] = fmt.Sprintf("%d", id)

	// Send POST request
	data, err := client.post(client.restURL+"/cancel_order.do", params)
	if err != nil {
		return false, fmt.Errorf("%s CancelOrder error: %s", client, err)
	}

	// Unmarshal response
	var response struct {
		Result    bool  `json:"result"`
		ErrorCode int64 `json:"error_code"`
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return false, fmt.Errorf("%s CancelOrder error: %s", client, err)
	}
	if response.ErrorCode != 0 {
		return false, fmt.Errorf("%s CancelOrder error code: %d", client, response.ErrorCode)
	}

	return response.Result, nil
}

// GetOrderStatus gets the status of an order on the exchange
func (client *Client) GetOrderStatus(id int64) (exchange.Order, error) {
	// Create parameter map for signing
	params := make(map[string]string)
	params["symbol"] = fmt.Sprintf("%s_%s", client.symbol, client.currency)
	params["order_id"] = fmt.Sprintf("%d", id)

	// Create order to be returned
	var order exchange.Order

	// Send POST request
	data, err := client.post(client.restURL+"/order_info.do", params)
	if err != nil {
		return order, fmt.Errorf("%s GetOrderStatus error: %s", client, err)
	}

	// Unmarshal response
	var response struct {
		Orders []struct {
			Status     int     `json:"status"`
			DealAmount float64 `json:"deal_amount"`
		} `json:"orders"`
		ErrorCode int64 `json:"error_code"`
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return order, fmt.Errorf("%s GetOrderStatus error: %s", client, err)
	}
	if response.ErrorCode != 0 {
		return order, fmt.Errorf("%s GetOrderStatus error code: %d", client, response.ErrorCode)
	}

	if response.Orders[0].Status == -1 || response.Orders[0].Status == 2 {
		order.Status = "dead"
	} else if response.Orders[0].Status == 4 || response.Orders[0].Status == 5 {
		order.Status = ""
	} else {
		order.Status = "live"
	}
	order.FilledAmount = math.Abs(response.Orders[0].DealAmount)
	return order, nil

}

// Authenticated POST
func (client *Client) post(stringrestURL string, params map[string]string) ([]byte, error) {
	// Make url.Values from params
	values := url.Values{}
	for param, value := range params {
		values.Set(param, value)
	}
	// Add authorization key to url.Values
	values.Set("api_key", client.key)
	// Prepare string to sign with MD5
	stringParams := values.Encode()
	// Add the authorization secret to the end
	stringParams += fmt.Sprintf("&secret_key=%s", client.secret)
	// Sign with MD5
	sum := md5.Sum([]byte(stringParams))
	// Add sign to url.Values
	values.Set("sign", strings.ToUpper(fmt.Sprintf("%x", sum)))

	// Send POST
	resp, err := http.PostForm(stringrestURL, values)
	if err != nil {
		return []byte{}, err
	}
	if resp.StatusCode != 200 {
		return []byte{}, fmt.Errorf(resp.Status)
	}
	defer resp.Body.Close()

	return ioutil.ReadAll(resp.Body)

}
<commit_msg>separate out newWS and reconnectWS<commit_after>// OKCoin exchange API

package okcoin

import (
	"bitfx/exchange"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/gorilla/websocket"
)

// Client contains all exchange information
type Client struct {
	key, secret, symbol, currency, websocketURL, restURL, name string
	priority                                                   int
	position, fee, maxPos, availShort, availFunds              float64
	currencyCode                                               byte
	orderWS                                                    *websocket.Conn
}

// Exchange request format
type request struct {
	Event      string            `json:"event"`      // Event to request
	Channel    string            `json:"channel"`    // Channel on which to make request
	Parameters map[string]string `json:"parameters"` // Additional parameters
}

// New returns a pointer to a Client instance
func New(key, secret, symbol, currency string, priority int, fee, availShort, availFunds float64) *Client {
	// URL depends on currency
	var websocketURL, restURL string
	var currencyCode byte
	if strings.ToLower(currency) == "usd" {
		websocketURL = "wss://real.okcoin.com:10440/websocket/okcoinapi"
		restURL = "https://www.okcoin.com/api/v1"
		currencyCode = 0
	} else if strings.ToLower(currency) == "cny" {
		websocketURL = "wss://real.okcoin.cn:10440/websocket/okcoinapi"
		restURL = "https://www.okcoin.cn/api/v1"
		currencyCode = 1
	} else {
		log.Fatal("Currency must be USD or CNY")
	}

	return &Client{
		key:          key,
		secret:       secret,
		symbol:       symbol,
		currency:     currency,
		websocketURL: websocketURL,
		restURL:      restURL,
		priority:     priority,
		fee:          fee,
		availShort:   availShort,
		availFunds:   availFunds,
		currencyCode: currencyCode,
		name:         fmt.Sprintf("OKCoin(%s)", currency),
	}
}

// String implements the Stringer interface
func (client *Client) String() string {
	return client.name
}

// Priority returns the exchange priority for order execution
func (client *Client) Priority() int {
	return client.priority
}

// Fee returns the exchange order fee
func (client *Client) Fee() float64 {
	return client.fee
}

// SetPosition sets the exchange position
func (client *Client) SetPosition(pos float64) {
	client.position = pos
}

// Position returns the exchange position
func (client *Client) Position() float64 {
	return client.position
}

// Currency returns the exchange currency
func (client *Client) Currency() string {
	return client.currency
}

// CurrencyCode returns the exchange currency code
func (client *Client) CurrencyCode() byte {
	return client.currencyCode
}

// SetMaxPos sets the exchange max position
func (client *Client) SetMaxPos(maxPos float64) {
	client.maxPos = maxPos
}

// MaxPos returns the exchange max position
func (client *Client) MaxPos() float64 {
	return client.maxPos
}

// AvailFunds returns the exchange available funds
func (client *Client) AvailFunds() float64 {
	return client.availFunds
}

// AvailShort returns the exchange quantity available for short selling
func (client *Client) AvailShort() float64 {
	return client.availShort
}

// HasCrytpoFee returns true if fee is taken in cryptocurrency on buys
func (client *Client) HasCryptoFee() bool {
	return true
}

// CommunicateBook sends the latest available book data on the supplied channel
func (client *Client) CommunicateBook(bookChan chan<- exchange.Book, doneChan <-chan bool) exchange.Book {
	// Connect to WebSocket
	channel := fmt.Sprintf("ok_%s%s_depth", client.symbol, client.currency)
	initMessage := request{Event: "addChannel", Channel: channel}
	ws, err := client.newWS(initMessage)
	if err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}

	// Get an initial book to return
	_, data, err := ws.ReadMessage()
	if err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}
	book := client.convertToBook(data)

	// Run a read loop in new goroutine
	go client.runLoop(ws, initMessage, bookChan, doneChan)

	return book
}

// Get a new WebSocket connection subscribed to specified channel
func (client *Client) newWS(initMessage request) (*websocket.Conn, error) {
	// Get WebSocket connection
	ws, _, err := websocket.DefaultDialer.Dial(client.websocketURL, http.Header{})
	if err != nil {
		return nil, err
	}

	// Subscribe to channel
	if err = ws.WriteJSON(initMessage); err != nil {
		return nil, err
	}

	return ws, nil
}

// Reconnect websocket
func (client *Client) reconnectWS(initMessage request) *websocket.Conn {
	log.Println("Reconnecting...")

	// Try reconnecting
	ws, err := client.newWS(initMessage)
	// Keep trying on error
	for err != nil {
		log.Printf("%s WebSocket error: %s", client, err)
		time.Sleep(1 * time.Second)
		ws, err = client.newWS(initMessage)
	}

	log.Println("Successful reconnect")

	return ws
}

// Websocket read loop
func (client *Client) runLoop(ws *websocket.Conn, initMessage request, bookChan chan<- exchange.Book, doneChan <-chan bool) {
	// Syncronize access to *websocket.Conn
	receiveWS := make(chan *websocket.Conn)
	reconnectWS := make(chan bool)
	closeWS := make(chan bool)
	go func() {
	LOOP:
		for {
			select {
			// Request to use websocket
			case receiveWS <- ws:
			// Request to reconnect websocket
			case <-reconnectWS:
				ws.Close()
				ws = client.reconnectWS(initMessage)
			// Request to close websocket
			case <-closeWS:
				ws.Close()
				break LOOP
			}
		}
	}()

	// Setup heartbeat
	pingInterval := 15 * time.Second
	ticker := time.NewTicker(pingInterval)
	ping := []byte(`{"event":"ping"}`)

	// Read from websocket
	dataChan := make(chan []byte)
	go func() {
		for {
			(<-receiveWS).SetReadDeadline(time.Now().Add(pingInterval + time.Second))
			_, data, err := (<-receiveWS).ReadMessage()
			if err != nil {
				// Reconnect on error
				log.Printf("%s WebSocket error: %s", client, err)
				reconnectWS <- true
			} else if string(data) != `{"event":"pong"}` {
				// If not a pong, send for processing
				dataChan <- data
			}
		}
	}()

	for {
		select {
		case <-doneChan:
			// End if notified
			ticker.Stop()
			closeWS <- true
			return
		case <-ticker.C:
			// Send ping (true type-9 pings not supported by server)
			if err := (<-receiveWS).WriteMessage(1, ping); err != nil {
				// Reconnect on error
				log.Printf("%s WebSocket error: %s", client, err)
				reconnectWS <- true
			}
		case data := <-dataChan:
			// Process data and send out to user
			bookChan <- client.convertToBook(data)
		}
	}
}

// Convert websocket data to an exchange.Book
func (client *Client) convertToBook(data []byte) exchange.Book {
	// Unmarshal
	var response []struct {
		Channel   string `json:"channel"`          // Channel name
		ErrorCode int64  `json:"errorcode,string"` // Error code if not successful
		Data      struct {
			Bids       [][2]float64 `json:"bids"`             // Slice of bid data items
			Asks       [][2]float64 `json:"asks"`             // Slice of ask data items
			Timestamp  int64        `json:"timestamp,string"` // Timestamp
			UnitAmount int          `json:"unit_amount"`      // Unit amount for futures

		} `json:"data"` // Data specific to channel
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return exchange.Book{Error: fmt.Errorf("%s book error: %s", client, err)}
	}

	// Return error if there is an exchange error code
	if response[0].ErrorCode != 0 {
		return exchange.Book{Error: fmt.Errorf("%s book error code: %d", client, response[0].ErrorCode)}
	}

	// Translate into exchange.Book structure
	bids := make(exchange.BidItems, 20)
	asks := make(exchange.AskItems, 20)
	for i := 0; i < 20; i++ {
		bids[i].Price = response[0].Data.Bids[i][0]
		bids[i].Amount = response[0].Data.Bids[i][1]
		asks[i].Price = response[0].Data.Asks[i][0]
		asks[i].Amount = response[0].Data.Asks[i][1]
	}
	sort.Sort(bids)
	sort.Sort(asks)

	// Return book
	return exchange.Book{
		Exg:   client,
		Time:  time.Now(),
		Bids:  bids,
		Asks:  asks,
		Error: nil,
	}
}

// SendOrder sends an order to the exchange
func (client *Client) SendOrder(action, otype string, amount, price float64) (int64, error) {
	// Create parameter map for signing
	params := make(map[string]string)
	params["symbol"] = fmt.Sprintf("%s_%s", client.symbol, client.currency)
	if otype == "limit" {
		params["type"] = action
	} else if otype == "market" {
		params["type"] = fmt.Sprintf("%s_%s", action, otype)
	}
	params["price"] = fmt.Sprintf("%f", price)
	params["amount"] = fmt.Sprintf("%f", amount)

	// Send POST request
	data, err := client.post(client.restURL+"/trade.do", params)
	if err != nil {
		return 0, fmt.Errorf("%s SendOrder error: %s", client, err)
	}

	// Unmarshal response
	var response struct {
		ID        int64 `json:"order_id"`
		ErrorCode int64 `json:"error_code"`
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return 0, fmt.Errorf("%s SendOrder error: %s", client, err)
	}
	if response.ErrorCode != 0 {
		return 0, fmt.Errorf("%s SendOrder error code: %d", client, response.ErrorCode)
	}

	return response.ID, nil
}

// CancelOrder cancels an order on the exchange
func (client *Client) CancelOrder(id int64) (bool, error) {
	// Create parameter map for signing
	params := make(map[string]string)
	params["symbol"] = fmt.Sprintf("%s_%s", client.symbol, client.currency)
	params["order_id"] = fmt.Sprintf("%d", id)

	// Send POST request
	data, err := client.post(client.restURL+"/cancel_order.do", params)
	if err != nil {
		return false, fmt.Errorf("%s CancelOrder error: %s", client, err)
	}

	// Unmarshal response
	var response struct {
		Result    bool  `json:"result"`
		ErrorCode int64 `json:"error_code"`
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return false, fmt.Errorf("%s CancelOrder error: %s", client, err)
	}
	if response.ErrorCode != 0 {
		return false, fmt.Errorf("%s CancelOrder error code: %d", client, response.ErrorCode)
	}

	return response.Result, nil
}

// GetOrderStatus gets the status of an order on the exchange
func (client *Client) GetOrderStatus(id int64) (exchange.Order, error) {
	// Create parameter map for signing
	params := make(map[string]string)
	params["symbol"] = fmt.Sprintf("%s_%s", client.symbol, client.currency)
	params["order_id"] = fmt.Sprintf("%d", id)

	// Create order to be returned
	var order exchange.Order

	// Send POST request
	data, err := client.post(client.restURL+"/order_info.do", params)
	if err != nil {
		return order, fmt.Errorf("%s GetOrderStatus error: %s", client, err)
	}

	// Unmarshal response
	var response struct {
		Orders []struct {
			Status     int     `json:"status"`
			DealAmount float64 `json:"deal_amount"`
		} `json:"orders"`
		ErrorCode int64 `json:"error_code"`
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return order, fmt.Errorf("%s GetOrderStatus error: %s", client, err)
	}
	if response.ErrorCode != 0 {
		return order, fmt.Errorf("%s GetOrderStatus error code: %d", client, response.ErrorCode)
	}

	if response.Orders[0].Status == -1 || response.Orders[0].Status == 2 {
		order.Status = "dead"
	} else if response.Orders[0].Status == 4 || response.Orders[0].Status == 5 {
		order.Status = ""
	} else {
		order.Status = "live"
	}
	order.FilledAmount = math.Abs(response.Orders[0].DealAmount)
	return order, nil

}

// Authenticated POST
func (client *Client) post(stringrestURL string, params map[string]string) ([]byte, error) {
	// Make url.Values from params
	values := url.Values{}
	for param, value := range params {
		values.Set(param, value)
	}
	// Add authorization key to url.Values
	values.Set("api_key", client.key)
	// Prepare string to sign with MD5
	stringParams := values.Encode()
	// Add the authorization secret to the end
	stringParams += fmt.Sprintf("&secret_key=%s", client.secret)
	// Sign with MD5
	sum := md5.Sum([]byte(stringParams))
	// Add sign to url.Values
	values.Set("sign", strings.ToUpper(fmt.Sprintf("%x", sum)))

	// Send POST
	resp, err := http.PostForm(stringrestURL, values)
	if err != nil {
		return []byte{}, err
	}
	if resp.StatusCode != 200 {
		return []byte{}, fmt.Errorf(resp.Status)
	}
	defer resp.Body.Close()

	return ioutil.ReadAll(resp.Body)

}
<|endoftext|>


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

package profiles

import (
	"encoding/xml"
	"fmt"
	"os"
	"sort"
	"sync"
	"time"

	"v.io/jiri/jiri"
)

const (
	defaultFileMode = os.FileMode(0644)
)

type Version int

const (
	// Original, old-style profiles without a version #
	Original Version = 0
	// First version of new-style profiles.
	V2 Version = 2
	// V3 added support for recording the options that were used to install profiles.
	V3 Version = 3
	// V4 adds support for relative path names in profiles and environment variables.
	V4 Version = 4
)

// Profile represents a suite of software that is managed by an implementation
// of profiles.Manager.
type Profile struct {
	Name    string
	Root    string
	targets OrderedTargets
}

func (p *Profile) Targets() OrderedTargets {
	r := make(OrderedTargets, len(p.targets), len(p.targets))
	for i, t := range p.targets {
		tmp := *t
		r[i] = &tmp
	}
	return r
}

type profilesSchema struct {
	XMLName  xml.Name         `xml:"profiles"`
	Version  Version          `xml:"version,attr"`
	Profiles []*profileSchema `xml:"profile"`
}

type profileSchema struct {
	XMLName xml.Name        `xml:"profile"`
	Name    string          `xml:"name,attr"`
	Root    string          `xml:"root,attr"`
	Targets []*targetSchema `xml:"target"`
}

type targetSchema struct {
	XMLName xml.Name `xml:"target"`
	// TODO(cnicolaou): remove this after this CL is checked in and no-one
	// is using Tags.
	Tag             string      `xml:"tag,attr"`
	Arch            string      `xml:"arch,attr"`
	OS              string      `xml:"os,attr"`
	InstallationDir string      `xml:"installation-directory,attr"`
	Version         string      `xml:"version,attr"`
	UpdateTime      time.Time   `xml:"date,attr"`
	Env             Environment `xml:"envvars"`
	CommandLineEnv  Environment `xml:"command-line"`
}

type profileDB struct {
	sync.Mutex
	version Version
	db      map[string]*Profile
}

func newDB() *profileDB {
	return &profileDB{db: make(map[string]*Profile), version: V4}
}

var (
	db = newDB()
)

// Profiles returns the names, in lexicographic order, of all of the currently
// available profiles as read or stored in the manifest. A profile name may
// be used to lookup a profile manager or the current state of a profile.
func Profiles() []string {
	return db.profiles()
}

func SchemaVersion() Version {
	return db.schemaVersion()
}

// LookupProfile returns the profile for the name profile or nil if one is
// not found.
func LookupProfile(name string) *Profile {
	return db.profile(name)
}

// LookupProfileTarget returns the target information stored for the name
// profile.
func LookupProfileTarget(name string, target Target) *Target {
	mgr := db.profile(name)
	if mgr == nil {
		return nil
	}
	return FindTarget(mgr.targets, &target)
}

// InstallProfile will create a new profile and store in the profiles manifest,
// it has no effect if the profile already exists.
func InstallProfile(name, root string) {
	db.installProfile(name, root)
}

// AddProfileTarget adds the specified target to the named profile.
// The UpdateTime of the newly installed target will be set to time.Now()
func AddProfileTarget(name string, target Target) error {
	return db.addProfileTarget(name, &target)
}

// RemoveProfileTarget removes the specified target from the named profile.
// If this is the last target for the profile then the profile will be deleted
// from the manifest. It returns true if the profile was so deleted or did
// not originally exist.
func RemoveProfileTarget(name string, target Target) bool {
	return db.removeProfileTarget(name, &target)
}

// UpdateProfileTarget updates the specified target from the named profile.
// The UpdateTime of the updated target will be set to time.Now()
func UpdateProfileTarget(name string, target Target) error {
	return db.updateProfileTarget(name, &target)
}

// Read reads the specified manifest file to obtain the current set of
// installed profiles.
func Read(jirix *jiri.X, filename string) error {
	return db.read(jirix, filename)
}

// Write writes the current set of installed profiles to the specified manifest
// file.
func Write(jirix *jiri.X, filename string) error {
	return db.write(jirix, filename)
}

func (pdb *profileDB) installProfile(name, root string) {
	pdb.Lock()
	defer pdb.Unlock()
	if p := pdb.db[name]; p == nil {
		pdb.db[name] = &Profile{Name: name, Root: root}
	}
}

func (pdb *profileDB) addProfileTarget(name string, target *Target) error {
	pdb.Lock()
	defer pdb.Unlock()
	target.UpdateTime = time.Now()
	if pi, present := pdb.db[name]; present {
		for _, t := range pi.Targets() {
			if target.Match(t) {
				return fmt.Errorf("%s is already used by profile %s %s", target, name, pi.Targets())
			}
		}
		pi.targets = InsertTarget(pi.targets, target)
		return nil
	}
	pdb.db[name] = &Profile{Name: name}
	pdb.db[name].targets = InsertTarget(nil, target)
	return nil
}

func (pdb *profileDB) updateProfileTarget(name string, target *Target) error {
	pdb.Lock()
	defer pdb.Unlock()
	target.UpdateTime = time.Now()
	pi, present := pdb.db[name]
	if !present {
		return fmt.Errorf("profile %v is not installed", name)
	}
	for _, t := range pi.targets {
		if target.Match(t) {
			*t = *target
			t.UpdateTime = time.Now()
			return nil
		}
	}
	return fmt.Errorf("profile %v does not have target: %v", name, target)
}

func (pdb *profileDB) removeProfileTarget(name string, target *Target) bool {
	pdb.Lock()
	defer pdb.Unlock()

	pi, present := pdb.db[name]
	if !present {
		return true
	}
	pi.targets = RemoveTarget(pi.targets, target)
	if len(pi.targets) == 0 {
		delete(pdb.db, name)
		return true
	}
	return false
}

func (pdb *profileDB) profiles() []string {
	pdb.Lock()
	defer pdb.Unlock()
	return pdb.profilesUnlocked()

}

func (pdb *profileDB) profilesUnlocked() []string {
	names := make([]string, 0, len(pdb.db))
	for name := range pdb.db {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}

func (pdb *profileDB) profile(name string) *Profile {
	pdb.Lock()
	defer pdb.Unlock()
	return pdb.db[name]
}

func (pdb *profileDB) read(jirix *jiri.X, filename string) error {
	pdb.Lock()
	defer pdb.Unlock()
	pdb.db = make(map[string]*Profile)

	data, err := jirix.Run().ReadFile(filename)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Fprintf(jirix.Stderr(), "WARNING: %v doesn't exist\n", filename)
			return nil
		}
		return err
	}

	var schema profilesSchema
	if err := xml.Unmarshal(data, &schema); err != nil {
		return fmt.Errorf("Unmarshal(%v) failed: %v", string(data), err)
	}
	pdb.version = schema.Version
	for _, profile := range schema.Profiles {
		name := profile.Name
		pdb.db[name] = &Profile{
			Name: name,
			Root: profile.Root,
		}
		for _, target := range profile.Targets {
			pdb.db[name].targets = append(pdb.db[name].targets, &Target{
				arch:            target.Arch,
				opsys:           target.OS,
				Env:             target.Env,
				commandLineEnv:  target.CommandLineEnv,
				version:         target.Version,
				UpdateTime:      target.UpdateTime,
				InstallationDir: target.InstallationDir,
				isSet:           true,
			})
		}
	}
	return nil
}

func (pdb *profileDB) write(jirix *jiri.X, filename string) error {
	pdb.Lock()
	defer pdb.Unlock()

	var schema profilesSchema
	schema.Version = V4
	for i, name := range pdb.profilesUnlocked() {
		profile := pdb.db[name]
		schema.Profiles = append(schema.Profiles, &profileSchema{
			Name: name,
			Root: profile.Root,
		})

		for _, target := range profile.targets {
			sort.Strings(target.Env.Vars)
			if len(target.version) == 0 {
				return fmt.Errorf("missing version for profile %s target: %s", name, target)
			}
			schema.Profiles[i].Targets = append(schema.Profiles[i].Targets,
				&targetSchema{
					Tag:             "",
					Arch:            target.arch,
					OS:              target.opsys,
					Env:             target.Env,
					CommandLineEnv:  target.commandLineEnv,
					Version:         target.version,
					InstallationDir: target.InstallationDir,
					UpdateTime:      target.UpdateTime,
				})
		}
	}

	data, err := xml.MarshalIndent(schema, "", "  ")
	if err != nil {
		return fmt.Errorf("MarshalIndent() failed: %v", err)
	}

	oldName := filename + ".prev"
	newName := filename + fmt.Sprintf(".%d", time.Now().UnixNano())

	if err := jirix.Run().WriteFile(newName, data, defaultFileMode); err != nil {
		return err
	}

	if jirix.Run().FileExists(filename) {
		if err := jirix.Run().Rename(filename, oldName); err != nil {
			return err
		}
	}

	if err := jirix.Run().Rename(newName, filename); err != nil {
		return err
	}

	return nil
}

func (pdb *profileDB) schemaVersion() Version {
	pdb.Lock()
	defer pdb.Unlock()
	return pdb.version
}
<commit_msg>TBR: Remove scary "WARNING" text, since the behavior is expected.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package profiles

import (
	"encoding/xml"
	"fmt"
	"os"
	"sort"
	"sync"
	"time"

	"v.io/jiri/jiri"
)

const (
	defaultFileMode = os.FileMode(0644)
)

type Version int

const (
	// Original, old-style profiles without a version #
	Original Version = 0
	// First version of new-style profiles.
	V2 Version = 2
	// V3 added support for recording the options that were used to install profiles.
	V3 Version = 3
	// V4 adds support for relative path names in profiles and environment variables.
	V4 Version = 4
)

// Profile represents a suite of software that is managed by an implementation
// of profiles.Manager.
type Profile struct {
	Name    string
	Root    string
	targets OrderedTargets
}

func (p *Profile) Targets() OrderedTargets {
	r := make(OrderedTargets, len(p.targets), len(p.targets))
	for i, t := range p.targets {
		tmp := *t
		r[i] = &tmp
	}
	return r
}

type profilesSchema struct {
	XMLName  xml.Name         `xml:"profiles"`
	Version  Version          `xml:"version,attr"`
	Profiles []*profileSchema `xml:"profile"`
}

type profileSchema struct {
	XMLName xml.Name        `xml:"profile"`
	Name    string          `xml:"name,attr"`
	Root    string          `xml:"root,attr"`
	Targets []*targetSchema `xml:"target"`
}

type targetSchema struct {
	XMLName xml.Name `xml:"target"`
	// TODO(cnicolaou): remove this after this CL is checked in and no-one
	// is using Tags.
	Tag             string      `xml:"tag,attr"`
	Arch            string      `xml:"arch,attr"`
	OS              string      `xml:"os,attr"`
	InstallationDir string      `xml:"installation-directory,attr"`
	Version         string      `xml:"version,attr"`
	UpdateTime      time.Time   `xml:"date,attr"`
	Env             Environment `xml:"envvars"`
	CommandLineEnv  Environment `xml:"command-line"`
}

type profileDB struct {
	sync.Mutex
	version Version
	db      map[string]*Profile
}

func newDB() *profileDB {
	return &profileDB{db: make(map[string]*Profile), version: V4}
}

var (
	db = newDB()
)

// Profiles returns the names, in lexicographic order, of all of the currently
// available profiles as read or stored in the manifest. A profile name may
// be used to lookup a profile manager or the current state of a profile.
func Profiles() []string {
	return db.profiles()
}

func SchemaVersion() Version {
	return db.schemaVersion()
}

// LookupProfile returns the profile for the name profile or nil if one is
// not found.
func LookupProfile(name string) *Profile {
	return db.profile(name)
}

// LookupProfileTarget returns the target information stored for the name
// profile.
func LookupProfileTarget(name string, target Target) *Target {
	mgr := db.profile(name)
	if mgr == nil {
		return nil
	}
	return FindTarget(mgr.targets, &target)
}

// InstallProfile will create a new profile and store in the profiles manifest,
// it has no effect if the profile already exists.
func InstallProfile(name, root string) {
	db.installProfile(name, root)
}

// AddProfileTarget adds the specified target to the named profile.
// The UpdateTime of the newly installed target will be set to time.Now()
func AddProfileTarget(name string, target Target) error {
	return db.addProfileTarget(name, &target)
}

// RemoveProfileTarget removes the specified target from the named profile.
// If this is the last target for the profile then the profile will be deleted
// from the manifest. It returns true if the profile was so deleted or did
// not originally exist.
func RemoveProfileTarget(name string, target Target) bool {
	return db.removeProfileTarget(name, &target)
}

// UpdateProfileTarget updates the specified target from the named profile.
// The UpdateTime of the updated target will be set to time.Now()
func UpdateProfileTarget(name string, target Target) error {
	return db.updateProfileTarget(name, &target)
}

// Read reads the specified manifest file to obtain the current set of
// installed profiles.
func Read(jirix *jiri.X, filename string) error {
	return db.read(jirix, filename)
}

// Write writes the current set of installed profiles to the specified manifest
// file.
func Write(jirix *jiri.X, filename string) error {
	return db.write(jirix, filename)
}

func (pdb *profileDB) installProfile(name, root string) {
	pdb.Lock()
	defer pdb.Unlock()
	if p := pdb.db[name]; p == nil {
		pdb.db[name] = &Profile{Name: name, Root: root}
	}
}

func (pdb *profileDB) addProfileTarget(name string, target *Target) error {
	pdb.Lock()
	defer pdb.Unlock()
	target.UpdateTime = time.Now()
	if pi, present := pdb.db[name]; present {
		for _, t := range pi.Targets() {
			if target.Match(t) {
				return fmt.Errorf("%s is already used by profile %s %s", target, name, pi.Targets())
			}
		}
		pi.targets = InsertTarget(pi.targets, target)
		return nil
	}
	pdb.db[name] = &Profile{Name: name}
	pdb.db[name].targets = InsertTarget(nil, target)
	return nil
}

func (pdb *profileDB) updateProfileTarget(name string, target *Target) error {
	pdb.Lock()
	defer pdb.Unlock()
	target.UpdateTime = time.Now()
	pi, present := pdb.db[name]
	if !present {
		return fmt.Errorf("profile %v is not installed", name)
	}
	for _, t := range pi.targets {
		if target.Match(t) {
			*t = *target
			t.UpdateTime = time.Now()
			return nil
		}
	}
	return fmt.Errorf("profile %v does not have target: %v", name, target)
}

func (pdb *profileDB) removeProfileTarget(name string, target *Target) bool {
	pdb.Lock()
	defer pdb.Unlock()

	pi, present := pdb.db[name]
	if !present {
		return true
	}
	pi.targets = RemoveTarget(pi.targets, target)
	if len(pi.targets) == 0 {
		delete(pdb.db, name)
		return true
	}
	return false
}

func (pdb *profileDB) profiles() []string {
	pdb.Lock()
	defer pdb.Unlock()
	return pdb.profilesUnlocked()

}

func (pdb *profileDB) profilesUnlocked() []string {
	names := make([]string, 0, len(pdb.db))
	for name := range pdb.db {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}

func (pdb *profileDB) profile(name string) *Profile {
	pdb.Lock()
	defer pdb.Unlock()
	return pdb.db[name]
}

func (pdb *profileDB) read(jirix *jiri.X, filename string) error {
	pdb.Lock()
	defer pdb.Unlock()
	pdb.db = make(map[string]*Profile)

	data, err := jirix.Run().ReadFile(filename)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Fprintf(jirix.Stderr(), "creating %v\n", filename)
			return nil
		}
		return err
	}

	var schema profilesSchema
	if err := xml.Unmarshal(data, &schema); err != nil {
		return fmt.Errorf("Unmarshal(%v) failed: %v", string(data), err)
	}
	pdb.version = schema.Version
	for _, profile := range schema.Profiles {
		name := profile.Name
		pdb.db[name] = &Profile{
			Name: name,
			Root: profile.Root,
		}
		for _, target := range profile.Targets {
			pdb.db[name].targets = append(pdb.db[name].targets, &Target{
				arch:            target.Arch,
				opsys:           target.OS,
				Env:             target.Env,
				commandLineEnv:  target.CommandLineEnv,
				version:         target.Version,
				UpdateTime:      target.UpdateTime,
				InstallationDir: target.InstallationDir,
				isSet:           true,
			})
		}
	}
	return nil
}

func (pdb *profileDB) write(jirix *jiri.X, filename string) error {
	pdb.Lock()
	defer pdb.Unlock()

	var schema profilesSchema
	schema.Version = V4
	for i, name := range pdb.profilesUnlocked() {
		profile := pdb.db[name]
		schema.Profiles = append(schema.Profiles, &profileSchema{
			Name: name,
			Root: profile.Root,
		})

		for _, target := range profile.targets {
			sort.Strings(target.Env.Vars)
			if len(target.version) == 0 {
				return fmt.Errorf("missing version for profile %s target: %s", name, target)
			}
			schema.Profiles[i].Targets = append(schema.Profiles[i].Targets,
				&targetSchema{
					Tag:             "",
					Arch:            target.arch,
					OS:              target.opsys,
					Env:             target.Env,
					CommandLineEnv:  target.commandLineEnv,
					Version:         target.version,
					InstallationDir: target.InstallationDir,
					UpdateTime:      target.UpdateTime,
				})
		}
	}

	data, err := xml.MarshalIndent(schema, "", "  ")
	if err != nil {
		return fmt.Errorf("MarshalIndent() failed: %v", err)
	}

	oldName := filename + ".prev"
	newName := filename + fmt.Sprintf(".%d", time.Now().UnixNano())

	if err := jirix.Run().WriteFile(newName, data, defaultFileMode); err != nil {
		return err
	}

	if jirix.Run().FileExists(filename) {
		if err := jirix.Run().Rename(filename, oldName); err != nil {
			return err
		}
	}

	if err := jirix.Run().Rename(newName, filename); err != nil {
		return err
	}

	return nil
}

func (pdb *profileDB) schemaVersion() Version {
	pdb.Lock()
	defer pdb.Unlock()
	return pdb.version
}
<|endoftext|>


<commit_before>// Copyright (c) 2013, 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package bloom

import (
	"github.com/btcsuite/btcchain"
	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcwire"
)

// merkleBlock is used to house intermediate information needed to generate a
// btcwire.MsgMerkleBlock according to a filter.
type merkleBlock struct {
	numTx       uint32
	allHashes   []*btcwire.ShaHash
	finalHashes []*btcwire.ShaHash
	matchedBits []byte
	bits        []byte
}

// calcTreeWidth calculates and returns the the number of nodes (width) or a
// merkle tree at the given depth-first height.
func (m *merkleBlock) calcTreeWidth(height uint32) uint32 {
	return (m.numTx + (1 << height) - 1) >> height
}

// calcHash returns the hash for a sub-tree given a depth-first height and
// node position.
func (m *merkleBlock) calcHash(height, pos uint32) *btcwire.ShaHash {
	if height == 0 {
		return m.allHashes[pos]
	}

	var right *btcwire.ShaHash
	left := m.calcHash(height-1, pos*2)
	if pos*2+1 < m.calcTreeWidth(height-1) {
		right = m.calcHash(height-1, pos*2+1)
	} else {
		right = left
	}
	return btcchain.HashMerkleBranches(left, right)
}

// traverseAndBuild builds a partial merkle tree using a recursive depth-first
// approach.  As it calculates the hashes, it also saves whether or not each
// node is a parent node and a list of final hashes to be included in the
// merkle block.
func (m *merkleBlock) traverseAndBuild(height, pos uint32) {
	// Determine whether this node is a parent of a matched node.
	var isParent byte
	for i := pos << height; i < (pos+1)<<height && i < m.numTx; i++ {
		isParent |= m.matchedBits[i]
	}
	m.bits = append(m.bits, isParent)

	// When the node is a leaf node or not a parent of a matched node,
	// append the hash to the list that will be part of the final merkle
	// block.
	if height == 0 || isParent == 0x00 {
		m.finalHashes = append(m.finalHashes, m.calcHash(height, pos))
		return
	}

	// At this point, the node is an internal node and it is the parent of
	// of an included leaf node.

	// Descend into the left child and process its sub-tree.
	m.traverseAndBuild(height-1, pos*2)

	// Descend into the right child and process its sub-tree if
	// there is one.
	if pos*2+1 < m.calcTreeWidth(height-1) {
		m.traverseAndBuild(height-1, pos*2+1)
	}
}

// NewMerkleBlock returns a new *btcwire.MsgMerkleBlock and an array of the matched
// transaction hashes based on the passed block and filter.
func NewMerkleBlock(block *btcutil.Block, filter *Filter) (*btcwire.MsgMerkleBlock, []*btcwire.ShaHash) {
	numTx := uint32(len(block.Transactions()))
	mBlock := merkleBlock{
		numTx:       numTx,
		allHashes:   make([]*btcwire.ShaHash, 0, numTx),
		matchedBits: make([]byte, 0, numTx),
	}

	// Find and keep track of any transactions that match the filter.
	var matchedHashes []*btcwire.ShaHash
	for _, tx := range block.Transactions() {
		if filter.MatchTxAndUpdate(tx) {
			mBlock.matchedBits = append(mBlock.matchedBits, 0x01)
			matchedHashes = append(matchedHashes, tx.Sha())
		} else {
			mBlock.matchedBits = append(mBlock.matchedBits, 0x00)
		}
		mBlock.allHashes = append(mBlock.allHashes, tx.Sha())
	}

	// Calculate the number of merkle branches (height) in the tree.
	height := uint32(0)
	for mBlock.calcTreeWidth(height) > 1 {
		height++
	}

	// Build the depth-first partial merkle tree.
	mBlock.traverseAndBuild(height, 0)

	// Create and return the merkle block.
	msgMerkleBlock := btcwire.MsgMerkleBlock{
		Header:       block.MsgBlock().Header,
		Transactions: uint32(mBlock.numTx),
		Hashes:       make([]*btcwire.ShaHash, 0, len(mBlock.finalHashes)),
		Flags:        make([]byte, (len(mBlock.bits)+7)/8),
	}
	for _, sha := range mBlock.finalHashes {
		msgMerkleBlock.AddTxHash(sha)
	}
	for i := uint32(0); i < uint32(len(mBlock.bits)); i++ {
		msgMerkleBlock.Flags[i/8] |= mBlock.bits[i] << (i % 8)
	}
	return &msgMerkleBlock, matchedHashes
}
<commit_msg>Update btcchain import paths to new location.<commit_after>// Copyright (c) 2013, 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package bloom

import (
	"github.com/btcsuite/btcd/blockchain"
	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcwire"
)

// merkleBlock is used to house intermediate information needed to generate a
// btcwire.MsgMerkleBlock according to a filter.
type merkleBlock struct {
	numTx       uint32
	allHashes   []*btcwire.ShaHash
	finalHashes []*btcwire.ShaHash
	matchedBits []byte
	bits        []byte
}

// calcTreeWidth calculates and returns the the number of nodes (width) or a
// merkle tree at the given depth-first height.
func (m *merkleBlock) calcTreeWidth(height uint32) uint32 {
	return (m.numTx + (1 << height) - 1) >> height
}

// calcHash returns the hash for a sub-tree given a depth-first height and
// node position.
func (m *merkleBlock) calcHash(height, pos uint32) *btcwire.ShaHash {
	if height == 0 {
		return m.allHashes[pos]
	}

	var right *btcwire.ShaHash
	left := m.calcHash(height-1, pos*2)
	if pos*2+1 < m.calcTreeWidth(height-1) {
		right = m.calcHash(height-1, pos*2+1)
	} else {
		right = left
	}
	return blockchain.HashMerkleBranches(left, right)
}

// traverseAndBuild builds a partial merkle tree using a recursive depth-first
// approach.  As it calculates the hashes, it also saves whether or not each
// node is a parent node and a list of final hashes to be included in the
// merkle block.
func (m *merkleBlock) traverseAndBuild(height, pos uint32) {
	// Determine whether this node is a parent of a matched node.
	var isParent byte
	for i := pos << height; i < (pos+1)<<height && i < m.numTx; i++ {
		isParent |= m.matchedBits[i]
	}
	m.bits = append(m.bits, isParent)

	// When the node is a leaf node or not a parent of a matched node,
	// append the hash to the list that will be part of the final merkle
	// block.
	if height == 0 || isParent == 0x00 {
		m.finalHashes = append(m.finalHashes, m.calcHash(height, pos))
		return
	}

	// At this point, the node is an internal node and it is the parent of
	// of an included leaf node.

	// Descend into the left child and process its sub-tree.
	m.traverseAndBuild(height-1, pos*2)

	// Descend into the right child and process its sub-tree if
	// there is one.
	if pos*2+1 < m.calcTreeWidth(height-1) {
		m.traverseAndBuild(height-1, pos*2+1)
	}
}

// NewMerkleBlock returns a new *btcwire.MsgMerkleBlock and an array of the matched
// transaction hashes based on the passed block and filter.
func NewMerkleBlock(block *btcutil.Block, filter *Filter) (*btcwire.MsgMerkleBlock, []*btcwire.ShaHash) {
	numTx := uint32(len(block.Transactions()))
	mBlock := merkleBlock{
		numTx:       numTx,
		allHashes:   make([]*btcwire.ShaHash, 0, numTx),
		matchedBits: make([]byte, 0, numTx),
	}

	// Find and keep track of any transactions that match the filter.
	var matchedHashes []*btcwire.ShaHash
	for _, tx := range block.Transactions() {
		if filter.MatchTxAndUpdate(tx) {
			mBlock.matchedBits = append(mBlock.matchedBits, 0x01)
			matchedHashes = append(matchedHashes, tx.Sha())
		} else {
			mBlock.matchedBits = append(mBlock.matchedBits, 0x00)
		}
		mBlock.allHashes = append(mBlock.allHashes, tx.Sha())
	}

	// Calculate the number of merkle branches (height) in the tree.
	height := uint32(0)
	for mBlock.calcTreeWidth(height) > 1 {
		height++
	}

	// Build the depth-first partial merkle tree.
	mBlock.traverseAndBuild(height, 0)

	// Create and return the merkle block.
	msgMerkleBlock := btcwire.MsgMerkleBlock{
		Header:       block.MsgBlock().Header,
		Transactions: uint32(mBlock.numTx),
		Hashes:       make([]*btcwire.ShaHash, 0, len(mBlock.finalHashes)),
		Flags:        make([]byte, (len(mBlock.bits)+7)/8),
	}
	for _, sha := range mBlock.finalHashes {
		msgMerkleBlock.AddTxHash(sha)
	}
	for i := uint32(0); i < uint32(len(mBlock.bits)); i++ {
		msgMerkleBlock.Flags[i/8] |= mBlock.bits[i] << (i % 8)
	}
	return &msgMerkleBlock, matchedHashes
}
<|endoftext|>


<commit_before>package dockerclient

import (
	"io"
)

type Callback func(*Event, chan error, ...interface{})

type StatCallback func(string, *Stats, chan error, ...interface{})

type Client interface {
	Info() (*Info, error)
	ListContainers(all, size bool, filters string) ([]Container, error)
	InspectContainer(id string) (*ContainerInfo, error)
	CreateContainer(config *ContainerConfig, name string) (string, error)
	ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error)
	ContainerChanges(id string) ([]*ContainerChanges, error)
	Exec(config *ExecConfig) (string, error)
	StartContainer(id string, config *HostConfig) error
	StopContainer(id string, timeout int) error
	RestartContainer(id string, timeout int) error
	KillContainer(id, signal string) error
	// MonitorEvents returns an EventOrError channel and a close channel. If an
	// error is ever sent, then no more eventswill be sent. Users must
	// always close the close channel when they are done reading events, even
	// if an error was sent.
	MonitorEvents(options *MonitorEventsOptions) (<-chan EventOrError, chan<- struct{}, error)
	StartMonitorEvents(cb Callback, ec chan error, args ...interface{})
	StopAllMonitorEvents()
	StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{})
	StopAllMonitorStats()
	Version() (*Version, error)
	PullImage(name string, auth *AuthConfig) error
	RemoveContainer(id string, force, volumes bool) error
	ListImages() ([]*Image, error)
	RemoveImage(name string) ([]*ImageDelete, error)
	PauseContainer(name string) error
	UnpauseContainer(name string) error
}
<commit_msg>fix comments again<commit_after>package dockerclient

import (
	"io"
)

type Callback func(*Event, chan error, ...interface{})

type StatCallback func(string, *Stats, chan error, ...interface{})

type Client interface {
	Info() (*Info, error)
	ListContainers(all, size bool, filters string) ([]Container, error)
	InspectContainer(id string) (*ContainerInfo, error)
	CreateContainer(config *ContainerConfig, name string) (string, error)
	ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error)
	ContainerChanges(id string) ([]*ContainerChanges, error)
	Exec(config *ExecConfig) (string, error)
	StartContainer(id string, config *HostConfig) error
	StopContainer(id string, timeout int) error
	RestartContainer(id string, timeout int) error
	KillContainer(id, signal string) error
	// MonitorEvents returns an EventOrError channel and a close channel. If
	// an error is ever sent, then no more events will be sent. Users must
	// always close the close channel when they are done reading events,
	// even if an error was sent.
	MonitorEvents(options *MonitorEventsOptions) (<-chan EventOrError, chan<- struct{}, error)
	StartMonitorEvents(cb Callback, ec chan error, args ...interface{})
	StopAllMonitorEvents()
	StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{})
	StopAllMonitorStats()
	Version() (*Version, error)
	PullImage(name string, auth *AuthConfig) error
	RemoveContainer(id string, force, volumes bool) error
	ListImages() ([]*Image, error)
	RemoveImage(name string) ([]*ImageDelete, error)
	PauseContainer(name string) error
	UnpauseContainer(name string) error
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"
	"strings"
	"time"

	"github.com/NebulousLabs/Sia/build"
	"github.com/julienschmidt/httprouter"
)

// buildHttpRoutes sets up and returns an * httprouter.Router.
// it connected the Router to the given api using the required
// parameters: requiredUserAgent and requiredPassword
func (api *API) buildHTTPRoutes(requiredUserAgent string, requiredPassword string) {
	router := httprouter.New()

	router.NotFound = http.HandlerFunc(UnrecognizedCallHandler)
	router.RedirectTrailingSlash = false

	// Consensus API Calls
	if api.cs != nil {
		router.GET("/consensus", api.consensusHandler)
		router.GET("/consensus/blocks", api.consensusBlocksHandler)
		router.POST("/consensus/validate/transactionset", api.consensusValidateTransactionsetHandler)
	}

	// Explorer API Calls
	if api.explorer != nil {
		router.GET("/explorer", api.explorerHandler)
		router.GET("/explorer/blocks/:height", api.explorerBlocksHandler)
		router.GET("/explorer/hashes/:hash", api.explorerHashHandler)
	}

	// Gateway API Calls
	if api.gateway != nil {
		router.GET("/gateway", api.gatewayHandler)
		router.POST("/gateway/connect/:netaddress", RequirePassword(api.gatewayConnectHandler, requiredPassword))
		router.POST("/gateway/disconnect/:netaddress", RequirePassword(api.gatewayDisconnectHandler, requiredPassword))
	}

	// Host API Calls
	if api.host != nil {
		// Calls directly pertaining to the host.
		router.GET("/host", api.hostHandlerGET)                                                   // Get the host status.
		router.POST("/host", RequirePassword(api.hostHandlerPOST, requiredPassword))              // Change the settings of the host.
		router.POST("/host/announce", RequirePassword(api.hostAnnounceHandler, requiredPassword)) // Announce the host to the network.
		router.GET("/host/estimatescore", api.hostEstimateScoreGET)

		// Calls pertaining to the storage manager that the host uses.
		router.GET("/host/storage", api.storageHandler)
		router.POST("/host/storage/folders/add", RequirePassword(api.storageFoldersAddHandler, requiredPassword))
		router.POST("/host/storage/folders/remove", RequirePassword(api.storageFoldersRemoveHandler, requiredPassword))
		router.POST("/host/storage/folders/resize", RequirePassword(api.storageFoldersResizeHandler, requiredPassword))
		router.POST("/host/storage/sectors/delete/:merkleroot", RequirePassword(api.storageSectorsDeleteHandler, requiredPassword))
	}

	// Miner API Calls
	if api.miner != nil {
		router.GET("/miner", api.minerHandler)
		router.GET("/miner/header", RequirePassword(api.minerHeaderHandlerGET, requiredPassword))
		router.POST("/miner/header", RequirePassword(api.minerHeaderHandlerPOST, requiredPassword))
		router.GET("/miner/start", RequirePassword(api.minerStartHandler, requiredPassword))
		router.GET("/miner/stop", RequirePassword(api.minerStopHandler, requiredPassword))
	}

	// Renter API Calls
	if api.renter != nil {
		router.GET("/renter", api.renterHandlerGET)
		router.POST("/renter", RequirePassword(api.renterHandlerPOST, requiredPassword))
		router.GET("/renter/contracts", api.renterContractsHandler)
		router.GET("/renter/downloads", api.renterDownloadsHandler)
		router.GET("/renter/files", api.renterFilesHandler)
		router.GET("/renter/prices", api.renterPricesHandler)

		// TODO: re-enable these routes once the new .sia format has been
		// standardized and implemented.
		// router.POST("/renter/load", RequirePassword(api.renterLoadHandler, requiredPassword))
		// router.POST("/renter/loadascii", RequirePassword(api.renterLoadAsciiHandler, requiredPassword))
		// router.GET("/renter/share", RequirePassword(api.renterShareHandler, requiredPassword))
		// router.GET("/renter/shareascii", RequirePassword(api.renterShareAsciiHandler, requiredPassword))

		router.POST("/renter/delete/*siapath", RequirePassword(api.renterDeleteHandler, requiredPassword))
		router.GET("/renter/download/*siapath", RequirePassword(api.renterDownloadHandler, requiredPassword))
		router.GET("/renter/downloadasync/*siapath", RequirePassword(api.renterDownloadAsyncHandler, requiredPassword))
		router.POST("/renter/rename/*siapath", RequirePassword(api.renterRenameHandler, requiredPassword))
		router.POST("/renter/upload/*siapath", RequirePassword(api.renterUploadHandler, requiredPassword))

		// HostDB endpoints.
		router.GET("/hostdb/active", api.hostdbActiveHandler)
		router.GET("/hostdb/all", api.hostdbAllHandler)
		router.GET("/hostdb/hosts/:pubkey", api.hostdbHostsHandler)
	}

	// Transaction pool API Calls
	if api.tpool != nil {
		router.GET("/tpool/fee", api.tpoolFeeHandlerGET)
		router.GET("/tpool/raw/:id", api.tpoolRawHandlerGET)
		router.POST("/tpool/raw", api.tpoolRawHandlerPOST)
		router.GET("/tpool/confirmed/:id", api.tpoolConfirmedGET)

		// TODO: re-enable this route once the transaction pool API has been finalized
		//router.GET("/transactionpool/transactions", api.transactionpoolTransactionsHandler)
	}

	// Wallet API Calls
	if api.wallet != nil {
		router.GET("/wallet", api.walletHandler)
		router.POST("/wallet/033x", RequirePassword(api.wallet033xHandler, requiredPassword))
		router.GET("/wallet/address", RequirePassword(api.walletAddressHandler, requiredPassword))
		router.GET("/wallet/addresses", api.walletAddressesHandler)
		router.GET("/wallet/backup", RequirePassword(api.walletBackupHandler, requiredPassword))
		router.POST("/wallet/init", RequirePassword(api.walletInitHandler, requiredPassword))
		router.POST("/wallet/init/seed", RequirePassword(api.walletInitSeedHandler, requiredPassword))
		router.POST("/wallet/lock", RequirePassword(api.walletLockHandler, requiredPassword))
		router.POST("/wallet/seed", RequirePassword(api.walletSeedHandler, requiredPassword))
		router.GET("/wallet/seeds", RequirePassword(api.walletSeedsHandler, requiredPassword))
		router.POST("/wallet/siacoins", RequirePassword(api.walletSiacoinsHandler, requiredPassword))
		router.POST("/wallet/siafunds", RequirePassword(api.walletSiafundsHandler, requiredPassword))
		router.POST("/wallet/siagkey", RequirePassword(api.walletSiagkeyHandler, requiredPassword))
		router.POST("/wallet/sweep/seed", RequirePassword(api.walletSweepSeedHandler, requiredPassword))
		router.GET("/wallet/transaction/:id", api.walletTransactionHandler)
		router.GET("/wallet/transactions", api.walletTransactionsHandler)
		router.GET("/wallet/transactions/:addr", api.walletTransactionsAddrHandler)
		router.GET("/wallet/verify/address/:addr", api.walletVerifyAddressHandler)
		router.POST("/wallet/unlock", RequirePassword(api.walletUnlockHandler, requiredPassword))
		router.POST("/wallet/changepassword", RequirePassword(api.walletChangePasswordHandler, requiredPassword))
	}

	// Apply UserAgent middleware and return the Router
	api.router = cleanCloseHandler(RequireUserAgent(router, requiredUserAgent))
	return
}

// cleanCloseHandler wraps the entire API, ensuring that underlying conns are
// not leaked if the remote end closes the connection before the underlying
// handler finishes.
func cleanCloseHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Close this file handle either when the function completes or when the
		// connection is done.
		done := make(chan struct{})
		go func(w http.ResponseWriter, r *http.Request) {
			defer close(done)
			next.ServeHTTP(w, r)
		}(w, r)
		select {
		case <-done:
		}

		// Sanity check - thread should not take more than an hour to return. This
		// must be done in a goroutine, otherwise the server will not close the
		// underlying socket for this API call.
		timer := time.NewTimer(time.Minute * 60)
		go func() {
			select {
			case <-done:
				timer.Stop()
			case <-timer.C:
				build.Severe("api call is taking more than 60 minutes to return:", r.URL.Path)
			}
		}()
	})
}

// RequireUserAgent is middleware that requires all requests to set a
// UserAgent that contains the specified string.
func RequireUserAgent(h http.Handler, ua string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if !strings.Contains(req.UserAgent(), ua) {
			WriteError(w, Error{"Browser access disabled due to security vulnerability. Use Sia-UI or siac."}, http.StatusBadRequest)
			return
		}
		h.ServeHTTP(w, req)
	})
}

// RequirePassword is middleware that requires a request to authenticate with a
// password using HTTP basic auth. Usernames are ignored. Empty passwords
// indicate no authentication is required.
func RequirePassword(h httprouter.Handle, password string) httprouter.Handle {
	// An empty password is equivalent to no password.
	if password == "" {
		return h
	}
	return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
		_, pass, ok := req.BasicAuth()
		if !ok || pass != password {
			w.Header().Set("WWW-Authenticate", "Basic realm=\"SiaAPI\"")
			WriteError(w, Error{"API authentication failed."}, http.StatusUnauthorized)
			return
		}
		h(w, req, ps)
	}
}
<commit_msg>Add route for /host/contracts<commit_after>package api

import (
	"net/http"
	"strings"
	"time"

	"github.com/NebulousLabs/Sia/build"
	"github.com/julienschmidt/httprouter"
)

// buildHttpRoutes sets up and returns an * httprouter.Router.
// it connected the Router to the given api using the required
// parameters: requiredUserAgent and requiredPassword
func (api *API) buildHTTPRoutes(requiredUserAgent string, requiredPassword string) {
	router := httprouter.New()

	router.NotFound = http.HandlerFunc(UnrecognizedCallHandler)
	router.RedirectTrailingSlash = false

	// Consensus API Calls
	if api.cs != nil {
		router.GET("/consensus", api.consensusHandler)
		router.GET("/consensus/blocks", api.consensusBlocksHandler)
		router.POST("/consensus/validate/transactionset", api.consensusValidateTransactionsetHandler)
	}

	// Explorer API Calls
	if api.explorer != nil {
		router.GET("/explorer", api.explorerHandler)
		router.GET("/explorer/blocks/:height", api.explorerBlocksHandler)
		router.GET("/explorer/hashes/:hash", api.explorerHashHandler)
	}

	// Gateway API Calls
	if api.gateway != nil {
		router.GET("/gateway", api.gatewayHandler)
		router.POST("/gateway/connect/:netaddress", RequirePassword(api.gatewayConnectHandler, requiredPassword))
		router.POST("/gateway/disconnect/:netaddress", RequirePassword(api.gatewayDisconnectHandler, requiredPassword))
	}

	// Host API Calls
	if api.host != nil {
		// Calls directly pertaining to the host.
		router.GET("/host", api.hostHandlerGET)                                                   // Get the host status.
		router.POST("/host", RequirePassword(api.hostHandlerPOST, requiredPassword))              // Change the settings of the host.
		router.POST("/host/announce", RequirePassword(api.hostAnnounceHandler, requiredPassword)) // Announce the host to the network.
		router.GET("/host/contracts", api.hostContractInfoHandler)                                // Get info about contracts.
		router.GET("/host/estimatescore", api.hostEstimateScoreGET)

		// Calls pertaining to the storage manager that the host uses.
		router.GET("/host/storage", api.storageHandler)
		router.POST("/host/storage/folders/add", RequirePassword(api.storageFoldersAddHandler, requiredPassword))
		router.POST("/host/storage/folders/remove", RequirePassword(api.storageFoldersRemoveHandler, requiredPassword))
		router.POST("/host/storage/folders/resize", RequirePassword(api.storageFoldersResizeHandler, requiredPassword))
		router.POST("/host/storage/sectors/delete/:merkleroot", RequirePassword(api.storageSectorsDeleteHandler, requiredPassword))
	}

	// Miner API Calls
	if api.miner != nil {
		router.GET("/miner", api.minerHandler)
		router.GET("/miner/header", RequirePassword(api.minerHeaderHandlerGET, requiredPassword))
		router.POST("/miner/header", RequirePassword(api.minerHeaderHandlerPOST, requiredPassword))
		router.GET("/miner/start", RequirePassword(api.minerStartHandler, requiredPassword))
		router.GET("/miner/stop", RequirePassword(api.minerStopHandler, requiredPassword))
	}

	// Renter API Calls
	if api.renter != nil {
		router.GET("/renter", api.renterHandlerGET)
		router.POST("/renter", RequirePassword(api.renterHandlerPOST, requiredPassword))
		router.GET("/renter/contracts", api.renterContractsHandler)
		router.GET("/renter/downloads", api.renterDownloadsHandler)
		router.GET("/renter/files", api.renterFilesHandler)
		router.GET("/renter/prices", api.renterPricesHandler)

		// TODO: re-enable these routes once the new .sia format has been
		// standardized and implemented.
		// router.POST("/renter/load", RequirePassword(api.renterLoadHandler, requiredPassword))
		// router.POST("/renter/loadascii", RequirePassword(api.renterLoadAsciiHandler, requiredPassword))
		// router.GET("/renter/share", RequirePassword(api.renterShareHandler, requiredPassword))
		// router.GET("/renter/shareascii", RequirePassword(api.renterShareAsciiHandler, requiredPassword))

		router.POST("/renter/delete/*siapath", RequirePassword(api.renterDeleteHandler, requiredPassword))
		router.GET("/renter/download/*siapath", RequirePassword(api.renterDownloadHandler, requiredPassword))
		router.GET("/renter/downloadasync/*siapath", RequirePassword(api.renterDownloadAsyncHandler, requiredPassword))
		router.POST("/renter/rename/*siapath", RequirePassword(api.renterRenameHandler, requiredPassword))
		router.POST("/renter/upload/*siapath", RequirePassword(api.renterUploadHandler, requiredPassword))

		// HostDB endpoints.
		router.GET("/hostdb/active", api.hostdbActiveHandler)
		router.GET("/hostdb/all", api.hostdbAllHandler)
		router.GET("/hostdb/hosts/:pubkey", api.hostdbHostsHandler)
	}

	// Transaction pool API Calls
	if api.tpool != nil {
		router.GET("/tpool/fee", api.tpoolFeeHandlerGET)
		router.GET("/tpool/raw/:id", api.tpoolRawHandlerGET)
		router.POST("/tpool/raw", api.tpoolRawHandlerPOST)
		router.GET("/tpool/confirmed/:id", api.tpoolConfirmedGET)

		// TODO: re-enable this route once the transaction pool API has been finalized
		//router.GET("/transactionpool/transactions", api.transactionpoolTransactionsHandler)
	}

	// Wallet API Calls
	if api.wallet != nil {
		router.GET("/wallet", api.walletHandler)
		router.POST("/wallet/033x", RequirePassword(api.wallet033xHandler, requiredPassword))
		router.GET("/wallet/address", RequirePassword(api.walletAddressHandler, requiredPassword))
		router.GET("/wallet/addresses", api.walletAddressesHandler)
		router.GET("/wallet/backup", RequirePassword(api.walletBackupHandler, requiredPassword))
		router.POST("/wallet/init", RequirePassword(api.walletInitHandler, requiredPassword))
		router.POST("/wallet/init/seed", RequirePassword(api.walletInitSeedHandler, requiredPassword))
		router.POST("/wallet/lock", RequirePassword(api.walletLockHandler, requiredPassword))
		router.POST("/wallet/seed", RequirePassword(api.walletSeedHandler, requiredPassword))
		router.GET("/wallet/seeds", RequirePassword(api.walletSeedsHandler, requiredPassword))
		router.POST("/wallet/siacoins", RequirePassword(api.walletSiacoinsHandler, requiredPassword))
		router.POST("/wallet/siafunds", RequirePassword(api.walletSiafundsHandler, requiredPassword))
		router.POST("/wallet/siagkey", RequirePassword(api.walletSiagkeyHandler, requiredPassword))
		router.POST("/wallet/sweep/seed", RequirePassword(api.walletSweepSeedHandler, requiredPassword))
		router.GET("/wallet/transaction/:id", api.walletTransactionHandler)
		router.GET("/wallet/transactions", api.walletTransactionsHandler)
		router.GET("/wallet/transactions/:addr", api.walletTransactionsAddrHandler)
		router.GET("/wallet/verify/address/:addr", api.walletVerifyAddressHandler)
		router.POST("/wallet/unlock", RequirePassword(api.walletUnlockHandler, requiredPassword))
		router.POST("/wallet/changepassword", RequirePassword(api.walletChangePasswordHandler, requiredPassword))
	}

	// Apply UserAgent middleware and return the Router
	api.router = cleanCloseHandler(RequireUserAgent(router, requiredUserAgent))
	return
}

// cleanCloseHandler wraps the entire API, ensuring that underlying conns are
// not leaked if the remote end closes the connection before the underlying
// handler finishes.
func cleanCloseHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Close this file handle either when the function completes or when the
		// connection is done.
		done := make(chan struct{})
		go func(w http.ResponseWriter, r *http.Request) {
			defer close(done)
			next.ServeHTTP(w, r)
		}(w, r)
		select {
		case <-done:
		}

		// Sanity check - thread should not take more than an hour to return. This
		// must be done in a goroutine, otherwise the server will not close the
		// underlying socket for this API call.
		timer := time.NewTimer(time.Minute * 60)
		go func() {
			select {
			case <-done:
				timer.Stop()
			case <-timer.C:
				build.Severe("api call is taking more than 60 minutes to return:", r.URL.Path)
			}
		}()
	})
}

// RequireUserAgent is middleware that requires all requests to set a
// UserAgent that contains the specified string.
func RequireUserAgent(h http.Handler, ua string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if !strings.Contains(req.UserAgent(), ua) {
			WriteError(w, Error{"Browser access disabled due to security vulnerability. Use Sia-UI or siac."}, http.StatusBadRequest)
			return
		}
		h.ServeHTTP(w, req)
	})
}

// RequirePassword is middleware that requires a request to authenticate with a
// password using HTTP basic auth. Usernames are ignored. Empty passwords
// indicate no authentication is required.
func RequirePassword(h httprouter.Handle, password string) httprouter.Handle {
	// An empty password is equivalent to no password.
	if password == "" {
		return h
	}
	return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
		_, pass, ok := req.BasicAuth()
		if !ok || pass != password {
			w.Header().Set("WWW-Authenticate", "Basic realm=\"SiaAPI\"")
			WriteError(w, Error{"API authentication failed."}, http.StatusUnauthorized)
			return
		}
		h(w, req, ps)
	}
}
<|endoftext|>


<commit_before>package pop

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"text/tabwriter"
	"time"

	"github.com/pkg/errors"
)

var mrx = regexp.MustCompile(`(\d+)_([^\.]+)(\.[a-z0-9]+)?\.(up|down)\.(sql|fizz)$`)

// NewMigrator returns a new "blank" migrator. It is recommended
// to use something like MigrationBox or FileMigrator. A "blank"
// Migrator should only be used as the basis for a new type of
// migration system.
func NewMigrator(c *Connection) Migrator {
	return Migrator{
		Connection: c,
		Migrations: map[string]Migrations{
			"up":   Migrations{},
			"down": Migrations{},
		},
	}
}

// Migrator forms the basis of all migrations systems.
// It does the actual heavy lifting of running migrations.
// When building a new migration system, you should embed this
// type into your migrator.
type Migrator struct {
	Connection *Connection
	SchemaPath string
	Migrations map[string]Migrations
}

// UpLogOnly insert pending "up" migrations logs only, without applying the patch.
// It's used when loading the schema dump, instead of the migrations.
func (m Migrator) UpLogOnly() error {
	c := m.Connection
	return m.exec(func() error {
		mtn := c.MigrationTableName()
		mfs := m.Migrations["up"]
		sort.Sort(mfs)
		return c.Transaction(func(tx *Connection) error {
			for _, mi := range mfs {
				if mi.DBType != "all" && mi.DBType != c.Dialect.Name() {
					// Skip migration for non-matching dialect
					continue
				}
				exists, err := c.Where("version = ?", mi.Version).Exists(mtn)
				if err != nil {
					return errors.Wrapf(err, "problem checking for migration version %s", mi.Version)
				}
				if exists {
					continue
				}
				_, err = tx.Store.Exec(fmt.Sprintf("insert into %s (version) values ('%s')", mtn, mi.Version))
				if err != nil {
					return errors.Wrapf(err, "problem inserting migration version %s", mi.Version)
				}
			}
			return nil
		})
	})
}

// Up runs pending "up" migrations and applies them to the database.
func (m Migrator) Up() error {
	c := m.Connection
	return m.exec(func() error {
		mtn := c.MigrationTableName()
		mfs := m.Migrations["up"]
		sort.Sort(mfs)
		for _, mi := range mfs {
			if mi.DBType != "all" && mi.DBType != c.Dialect.Name() {
				// Skip migration for non-matching dialect
				continue
			}
			exists, err := c.Where("version = ?", mi.Version).Exists(mtn)
			if err != nil {
				return errors.Wrapf(err, "problem checking for migration version %s", mi.Version)
			}
			if exists {
				continue
			}
			err = c.Transaction(func(tx *Connection) error {
				err := mi.Run(tx)
				if err != nil {
					return err
				}
				_, err = tx.Store.Exec(fmt.Sprintf("insert into %s (version) values ('%s')", mtn, mi.Version))
				return errors.Wrapf(err, "problem inserting migration version %s", mi.Version)
			})
			if err != nil {
				return errors.WithStack(err)
			}
			fmt.Printf("> %s\n", mi.Name)
		}
		return nil
	})
}

// Down runs pending "down" migrations and rolls back the
// database by the specified number of steps.
func (m Migrator) Down(step int) error {
	c := m.Connection
	return m.exec(func() error {
		mtn := c.MigrationTableName()
		count, err := c.Count(mtn)
		if err != nil {
			return errors.Wrap(err, "migration down: unable count existing migration")
		}
		mfs := m.Migrations["down"]
		sort.Sort(sort.Reverse(mfs))
		// skip all runned migration
		if len(mfs) > count {
			mfs = mfs[len(mfs)-count:]
		}
		// run only required steps
		if step > 0 && len(mfs) >= step {
			mfs = mfs[:step]
		}
		for _, mi := range mfs {
			exists, err := c.Where("version = ?", mi.Version).Exists(mtn)
			if err != nil || !exists {
				return errors.Wrapf(err, "problem checking for migration version %s", mi.Version)
			}
			err = c.Transaction(func(tx *Connection) error {
				err := mi.Run(tx)
				if err != nil {
					return err
				}
				err = tx.RawQuery(fmt.Sprintf("delete from %s where version = ?", mtn), mi.Version).Exec()
				return errors.Wrapf(err, "problem deleting migration version %s", mi.Version)
			})
			if err != nil {
				return err
			}

			fmt.Printf("< %s\n", mi.Name)
		}
		return nil
	})
}

// Reset the database by running the down migrations followed by the up migrations.
func (m Migrator) Reset() error {
	err := m.Down(-1)
	if err != nil {
		return errors.WithStack(err)
	}
	return m.Up()
}

// CreateSchemaMigrations sets up a table to track migrations. This is an idempotent
// operation.
func (m Migrator) CreateSchemaMigrations() error {
	c := m.Connection
	mtn := c.MigrationTableName()
	err := c.Open()
	if err != nil {
		return errors.Wrap(err, "could not open connection")
	}
	_, err = c.Store.Exec(fmt.Sprintf("select * from %s", mtn))
	if err == nil {
		return nil
	}

	return c.Transaction(func(tx *Connection) error {
		schemaMigrations := newSchemaMigrations(mtn)
		smSQL, err := c.Dialect.FizzTranslator().CreateTable(schemaMigrations)
		if err != nil {
			return errors.Wrap(err, "could not build SQL for schema migration table")
		}
		err = tx.RawQuery(smSQL).Exec()
		if err != nil {
			return errors.WithStack(errors.Wrap(err, smSQL))
		}
		return nil
	})
}

// Status prints out the status of applied/pending migrations.
func (m Migrator) Status() error {
	err := m.CreateSchemaMigrations()
	if err != nil {
		return errors.WithStack(err)
	}
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.TabIndent)
	fmt.Fprintln(w, "Version\tName\tStatus\t")
	for _, mf := range m.Migrations["up"] {
		exists, err := m.Connection.Where("version = ?", mf.Version).Exists(m.Connection.MigrationTableName())
		if err != nil {
			return errors.Wrapf(err, "problem with migration")
		}
		state := "Pending"
		if exists {
			state = "Applied"
		}
		fmt.Fprintf(w, "%s\t%s\t%s\t\n", mf.Version, mf.Name, state)
	}
	return w.Flush()
}

// DumpMigrationSchema will generate a file of the current database schema
// based on the value of Migrator.SchemaPath
func (m Migrator) DumpMigrationSchema() error {
	if m.SchemaPath == "" {
		return nil
	}
	c := m.Connection
	f, err := os.Create(filepath.Join(m.SchemaPath, "schema.sql"))
	if err != nil {
		return errors.WithStack(err)
	}
	err = c.Dialect.DumpSchema(f)
	if err != nil {

		return errors.WithStack(err)
	}
	return nil
}

func (m Migrator) exec(fn func() error) error {
	now := time.Now()
	defer m.DumpMigrationSchema()
	defer printTimer(now)

	err := m.CreateSchemaMigrations()
	if err != nil {
		return errors.Wrap(err, "Migrator: problem creating schema migrations")
	}
	return fn()
}

func printTimer(timerStart time.Time) {
	diff := time.Since(timerStart).Seconds()
	if diff > 60 {
		fmt.Printf("\n%.4f minutes\n", diff/60)
	} else {
		fmt.Printf("\n%.4f seconds\n", diff)
	}
}
<commit_msg>Fix #151: ensure the migration file name starts with a number (#152)<commit_after>package pop

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"text/tabwriter"
	"time"

	"github.com/pkg/errors"
)

var mrx = regexp.MustCompile(`^(\d+)_([^\.]+)(\.[a-z0-9]+)?\.(up|down)\.(sql|fizz)$`)

// NewMigrator returns a new "blank" migrator. It is recommended
// to use something like MigrationBox or FileMigrator. A "blank"
// Migrator should only be used as the basis for a new type of
// migration system.
func NewMigrator(c *Connection) Migrator {
	return Migrator{
		Connection: c,
		Migrations: map[string]Migrations{
			"up":   Migrations{},
			"down": Migrations{},
		},
	}
}

// Migrator forms the basis of all migrations systems.
// It does the actual heavy lifting of running migrations.
// When building a new migration system, you should embed this
// type into your migrator.
type Migrator struct {
	Connection *Connection
	SchemaPath string
	Migrations map[string]Migrations
}

// UpLogOnly insert pending "up" migrations logs only, without applying the patch.
// It's used when loading the schema dump, instead of the migrations.
func (m Migrator) UpLogOnly() error {
	c := m.Connection
	return m.exec(func() error {
		mtn := c.MigrationTableName()
		mfs := m.Migrations["up"]
		sort.Sort(mfs)
		return c.Transaction(func(tx *Connection) error {
			for _, mi := range mfs {
				if mi.DBType != "all" && mi.DBType != c.Dialect.Name() {
					// Skip migration for non-matching dialect
					continue
				}
				exists, err := c.Where("version = ?", mi.Version).Exists(mtn)
				if err != nil {
					return errors.Wrapf(err, "problem checking for migration version %s", mi.Version)
				}
				if exists {
					continue
				}
				_, err = tx.Store.Exec(fmt.Sprintf("insert into %s (version) values ('%s')", mtn, mi.Version))
				if err != nil {
					return errors.Wrapf(err, "problem inserting migration version %s", mi.Version)
				}
			}
			return nil
		})
	})
}

// Up runs pending "up" migrations and applies them to the database.
func (m Migrator) Up() error {
	c := m.Connection
	return m.exec(func() error {
		mtn := c.MigrationTableName()
		mfs := m.Migrations["up"]
		sort.Sort(mfs)
		for _, mi := range mfs {
			if mi.DBType != "all" && mi.DBType != c.Dialect.Name() {
				// Skip migration for non-matching dialect
				continue
			}
			exists, err := c.Where("version = ?", mi.Version).Exists(mtn)
			if err != nil {
				return errors.Wrapf(err, "problem checking for migration version %s", mi.Version)
			}
			if exists {
				continue
			}
			err = c.Transaction(func(tx *Connection) error {
				err := mi.Run(tx)
				if err != nil {
					return err
				}
				_, err = tx.Store.Exec(fmt.Sprintf("insert into %s (version) values ('%s')", mtn, mi.Version))
				return errors.Wrapf(err, "problem inserting migration version %s", mi.Version)
			})
			if err != nil {
				return errors.WithStack(err)
			}
			fmt.Printf("> %s\n", mi.Name)
		}
		return nil
	})
}

// Down runs pending "down" migrations and rolls back the
// database by the specified number of steps.
func (m Migrator) Down(step int) error {
	c := m.Connection
	return m.exec(func() error {
		mtn := c.MigrationTableName()
		count, err := c.Count(mtn)
		if err != nil {
			return errors.Wrap(err, "migration down: unable count existing migration")
		}
		mfs := m.Migrations["down"]
		sort.Sort(sort.Reverse(mfs))
		// skip all runned migration
		if len(mfs) > count {
			mfs = mfs[len(mfs)-count:]
		}
		// run only required steps
		if step > 0 && len(mfs) >= step {
			mfs = mfs[:step]
		}
		for _, mi := range mfs {
			exists, err := c.Where("version = ?", mi.Version).Exists(mtn)
			if err != nil || !exists {
				return errors.Wrapf(err, "problem checking for migration version %s", mi.Version)
			}
			err = c.Transaction(func(tx *Connection) error {
				err := mi.Run(tx)
				if err != nil {
					return err
				}
				err = tx.RawQuery(fmt.Sprintf("delete from %s where version = ?", mtn), mi.Version).Exec()
				return errors.Wrapf(err, "problem deleting migration version %s", mi.Version)
			})
			if err != nil {
				return err
			}

			fmt.Printf("< %s\n", mi.Name)
		}
		return nil
	})
}

// Reset the database by running the down migrations followed by the up migrations.
func (m Migrator) Reset() error {
	err := m.Down(-1)
	if err != nil {
		return errors.WithStack(err)
	}
	return m.Up()
}

// CreateSchemaMigrations sets up a table to track migrations. This is an idempotent
// operation.
func (m Migrator) CreateSchemaMigrations() error {
	c := m.Connection
	mtn := c.MigrationTableName()
	err := c.Open()
	if err != nil {
		return errors.Wrap(err, "could not open connection")
	}
	_, err = c.Store.Exec(fmt.Sprintf("select * from %s", mtn))
	if err == nil {
		return nil
	}

	return c.Transaction(func(tx *Connection) error {
		schemaMigrations := newSchemaMigrations(mtn)
		smSQL, err := c.Dialect.FizzTranslator().CreateTable(schemaMigrations)
		if err != nil {
			return errors.Wrap(err, "could not build SQL for schema migration table")
		}
		err = tx.RawQuery(smSQL).Exec()
		if err != nil {
			return errors.WithStack(errors.Wrap(err, smSQL))
		}
		return nil
	})
}

// Status prints out the status of applied/pending migrations.
func (m Migrator) Status() error {
	err := m.CreateSchemaMigrations()
	if err != nil {
		return errors.WithStack(err)
	}
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.TabIndent)
	fmt.Fprintln(w, "Version\tName\tStatus\t")
	for _, mf := range m.Migrations["up"] {
		exists, err := m.Connection.Where("version = ?", mf.Version).Exists(m.Connection.MigrationTableName())
		if err != nil {
			return errors.Wrapf(err, "problem with migration")
		}
		state := "Pending"
		if exists {
			state = "Applied"
		}
		fmt.Fprintf(w, "%s\t%s\t%s\t\n", mf.Version, mf.Name, state)
	}
	return w.Flush()
}

// DumpMigrationSchema will generate a file of the current database schema
// based on the value of Migrator.SchemaPath
func (m Migrator) DumpMigrationSchema() error {
	if m.SchemaPath == "" {
		return nil
	}
	c := m.Connection
	f, err := os.Create(filepath.Join(m.SchemaPath, "schema.sql"))
	if err != nil {
		return errors.WithStack(err)
	}
	err = c.Dialect.DumpSchema(f)
	if err != nil {

		return errors.WithStack(err)
	}
	return nil
}

func (m Migrator) exec(fn func() error) error {
	now := time.Now()
	defer m.DumpMigrationSchema()
	defer printTimer(now)

	err := m.CreateSchemaMigrations()
	if err != nil {
		return errors.Wrap(err, "Migrator: problem creating schema migrations")
	}
	return fn()
}

func printTimer(timerStart time.Time) {
	diff := time.Since(timerStart).Seconds()
	if diff > 60 {
		fmt.Printf("\n%.4f minutes\n", diff/60)
	} else {
		fmt.Printf("\n%.4f seconds\n", diff)
	}
}
<|endoftext|>


<commit_before>package images

import (
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"

	"github.com/racker/perigee"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToImageListQuery() (string, error)
}

// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
type ListOpts struct {
	// When the image last changed status (in date-time format).
	ChangesSince string `q:"changes-since"`
	// The number of Images to return.
	Limit int `q:"limit"`
	// UUID of the Image at which to set a marker.
	Marker string `q:"marker"`
	// The name of the Image.
	Name string `q:"name:"`
	// The name of the Server (in URL format).
	Server string `q:"server"`
	// The current status of the Image.
	Status string `q:"status"`
	// The value of the type of image (e.g. BASE, SERVER, ALL)
	Type string `q:"type"`
}

// ToImageListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToImageListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// ListDetail enumerates the available images.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listDetailURL(client)
	if opts != nil {
		query, err := opts.ToImageListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	createPage := func(r pagination.PageResult) pagination.Page {
		return ImagePage{pagination.LinkedPageBase{PageResult: r}}
	}

	return pagination.NewPager(client, url, createPage)
}

// Get acquires additional detail about a specific image by ID.
// Use ExtractImage() to interpret the result as an openstack Image.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
	var result GetResult
	_, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
		MoreHeaders: client.AuthenticatedHeaders(),
		Results:     &result.Body,
		OkCodes:     []int{200},
	})
	return result
}
<commit_msg>Fixed typo in image query requests<commit_after>package images

import (
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"

	"github.com/racker/perigee"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToImageListQuery() (string, error)
}

// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
type ListOpts struct {
	// When the image last changed status (in date-time format).
	ChangesSince string `q:"changes-since"`
	// The number of Images to return.
	Limit int `q:"limit"`
	// UUID of the Image at which to set a marker.
	Marker string `q:"marker"`
	// The name of the Image.
	Name string `q:"name"`
	// The name of the Server (in URL format).
	Server string `q:"server"`
	// The current status of the Image.
	Status string `q:"status"`
	// The value of the type of image (e.g. BASE, SERVER, ALL)
	Type string `q:"type"`
}

// ToImageListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToImageListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// ListDetail enumerates the available images.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listDetailURL(client)
	if opts != nil {
		query, err := opts.ToImageListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	createPage := func(r pagination.PageResult) pagination.Page {
		return ImagePage{pagination.LinkedPageBase{PageResult: r}}
	}

	return pagination.NewPager(client, url, createPage)
}

// Get acquires additional detail about a specific image by ID.
// Use ExtractImage() to interpret the result as an openstack Image.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
	var result GetResult
	_, result.Err = perigee.Request("GET", getURL(client, id), perigee.Options{
		MoreHeaders: client.AuthenticatedHeaders(),
		Results:     &result.Body,
		OkCodes:     []int{200},
	})
	return result
}
<|endoftext|>


<commit_before>/**
 * Copyright 2015 @ z3q.net.
 * name : person_finance
 * author : jarryliu
 * date : 2016-03-31 10:46
 * description :
 * history :
 */
package personfinance

import "go2o/src/core/domain/interface/member"

type (
	// 在此聚合下, 会员抽象为Person, PersonId 对应 MemberId
	IPersonFinance interface {
		// 获取聚合根
		GetAggregateRootId() int

		// 获取账号
		GetMemberAccount() *member.IAccount

		// 转入
		TransferIn(amount float32) error

		// 转出
		TransferOut(amount float32) error

		// 获取增利账户信息(类:余额宝)
		GetRiseInfo() *RiseInfo

		// 结算增利信息
		RiseSettleForToday() error

		// 获取时间段内的增利信息
		GetRiseByTime(begin, end int64) []*RiseDayInfo
	}

	// 收益总记录
	RiseInfo struct {
		//Id  int `db:"id" pk:"yes" auto:"no"`
		PersonId    int     `db:"person_id" pk:"yes" auto:"no"` //人员编号
		Balance     float32 `db:"base_balance"`                 //本金及收益的余额
		TransferIn  float32 `db:"transfer_in"`                  //今日转入
		TotalAmount float32 `db:"total_amount"`                 //总金额
		TotalRise   float32 `db:"total_rise"`                   //总收益
		UpdateTime  int64   `db:"update_time"`
	}

	// 收益每日结算数据
	RiseDayInfo struct {
		Id         int     `db:"id" pk:"yes" auto:"yes"`
		PersonId   int     `db:"person_id"`
		Date       string  `db:"date"`
		BaseAmount float32 `db:"base_amount"` //本金
		RiseAmount string  `db:"rise_amount"` //增加金额
		IntDate    int64   `db:"unix_date"`
		UpdateTime int64   `db:"update_time"`
	}
)
<commit_msg>person finance<commit_after>/**
 * Copyright 2015 @ z3q.net.
 * name : person_finance
 * author : jarryliu
 * date : 2016-03-31 10:46
 * description :
 * history :
 */
package personfinance

import (
	"go2o/src/core/domain/interface/member"
	"go2o/src/core/domain/personfinance"
	"go2o/src/core/infrastructure/domain"
)

const (
	MinRiseTransferInAmount  float32 = 100.00 //最低转入金额为100
	MinRiseTransferOutAmount float32 = 0.00   //最低转出金额
)

type (
	// 在此聚合下, 会员抽象为Person, PersonId 对应 MemberId
	IPersonFinance interface {
		// 获取聚合根
		GetAggregateRootId() int
		// 获取账号
		GetMemberAccount() *member.IAccount
		// 获取增利账户信息(类:余额宝)
		GetRiseInfo() *IRiseInfo
	}

	// 现金增利
	IRiseInfo interface {
		GetDomainId() int

		// 将Transfer_in的金额到余额,用于计算收益
		TransferToBalance(amount float32) error

		// 获取值
		Value() (RiseInfoValue, error)

		// 设置值
		//Set(*RiseInfoValue)error

		// 转入
		TransferIn(amount float32) error

		// 转出
		TransferOut(amount float32) error

		// 结算增利信息,dayRatio 为每天的收益比率
		RiseSettleForToday(dayRatio float32) error

		// 获取时间段内的增利信息
		GetRiseByTime(begin, end int64) []*RiseDayInfo

		// 保存
		Save() error
	}

	// 收益总记录
	RiseInfoValue struct {
		//Id  int `db:"id" pk:"yes" auto:"no"`
		PersonId    int     `db:"person_id" pk:"yes" auto:"no"` //人员编号
		Balance     float32 `db:"base_balance"`                 //本金及收益的余额
		TransferIn  float32 `db:"transfer_in"`                  //今日转入
		TotalAmount float32 `db:"total_amount"`                 //总金额
		TotalRise   float32 `db:"total_rise"`                   //总收益
		UpdateTime  int64   `db:"update_time"`
	}

	// 收益每日结算数据
	RiseDayInfo struct {
		Id         int     `db:"id" pk:"yes" auto:"yes"`
		PersonId   int     `db:"person_id"`
		Date       string  `db:"date"`
		BaseAmount float32 `db:"base_amount"` //本金
		RiseAmount string  `db:"rise_amount"` //增加金额
		IntDate    int64   `db:"unix_date"`
		UpdateTime int64   `db:"update_time"`
	}

	IPersonFinanceRepository interface {
		GetRiseByTime(begin, end int64) []*RiseDayInfo
		GetRiseValueByPersonId(id int) (v *RiseInfoValue, err error)
		SaveRiseInfo(*RiseInfoValue) (id int, err error)
	}
)

var (
	ErrIncorrectAmount *domain.DomainError = domain.NewDomainError(
		"err_balance_amount", "金额错误!")
	ErrNoSuchRiseInfo *domain.DomainError = domain.NewDomainError(
		"err_no_such_rise_info", "未开通该功能!")

	ErrHasSettled *domain.DomainError = domain.NewDomainError(
		"err_has_settled", "已经结算!")
	ErrRatio *domain.DomainError = domain.NewDomainError(
		"err_ratio", "利率不正确!")

	ErrLessThanMinTransferIn *domain.DomainError = domain.NewDomainError(
		"err_less_than_min_transfer_in", "转入金额最低%d!")

	ErrLessThanMinTransferOut *domain.DomainError = domain.NewDomainError(
		"err_less_than_min_transfer_out", "转出金额最低%d!")

	ErrOutOfBalance *domain.DomainError = domain.NewDomainError(
		"err_out_of_balance", "超出帐户最大金额!")
)
<|endoftext|>