<commit_before>// Package Bittrex is an implementation of the Biitrex API (public and private).
package bittrex

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
)

const (
	API_BASE                   = "https://bittrex.com/api/" // Bittrex API endpoint
	API_VERSION                = "v1.1"                     // Bittrex API version
	DEFAULT_HTTPCLIENT_TIMEOUT = 30                         // HTTP client timeout
)

// New return a instanciate bittrex struct
func New(apiKey, apiSecret string) *Bittrex {
	client := NewClient(apiKey, apiSecret)
	return &bittrex{client}
}

// handleErr gets JSON response from Bittrex API en deal with error
func handleErr(r jsonResponse) error {
	if !r.Success {
		return errors.New(r.Message)
	}
	return nil
}

// bittrex represent a bittrex client
type Bittrex struct {
	client *client
}

// GetMarkets is used to get the open and available trading markets at Bittrex along with other meta data.
func (b *Bittrex) GetMarkets() (markets []Market, err error) {
	r, err := b.client.do("GET", "public/getmarkets", "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &markets)
	return
}

// GetTicker is used to get the current ticker values for a market.
func (b *Bittrex) GetTicker(market string) (ticker Ticker, err error) {
	r, err := b.client.do("GET", "public/getticker?market="+strings.ToUpper(market), "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &ticker)
	return
}

// GetMarketSummaries is used to get the last 24 hour summary of all active exchanges
func (b *Bittrex) GetMarketSummaries() (marketSummaries []MarketSummary, err error) {
	r, err := b.client.do("GET", "public/getmarketsummaries", "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &marketSummaries)
	return
}

// GetOrderBook is used to get retrieve the orderbook for a given market
// market: a string literal for the market (ex: BTC-LTC)
// cat: buy, sell or both to identify the type of orderbook to return.
// depth: how deep of an order book to retrieve. Max is 100
func (b *Bittrex) GetOrderBook(market, cat string, depth int) (orderBook OrderBook, err error) {
	if cat != "buy" && cat != "sell" && cat != "both" {
		cat = "both"
	}
	if depth > 100 {
		depth = 100
	}
	if depth < 1 {
		depth = 1
	}
	r, err := b.client.do("GET", fmt.Sprintf("public/getorderbook?market=%s&type=%s&depth=%d", strings.ToUpper(market), cat, depth), "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &orderBook)
	return
}

// GetMarketHistory is used to retrieve the latest trades that have occured for a specific market.
// mark a string literal for the market (ex: BTC-LTC)
// count a number between 1-100 for the number of entries to return
func (b *Bittrex) GetMarketHistory(market string, count int) (trades []Trade, err error) {
	if count > 100 {
		count = 100
	}
	if count < 1 {
		count = 1
	}
	r, err := b.client.do("GET", fmt.Sprintf("public/getmarkethistory?market=%s&count=%d", strings.ToUpper(market), count), "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &trades)
	return
}

// Market

// BuyLimit is used to place a limited buy order in a specific market.
func (b *Bittrex) BuyLimit(market string, quantity, rate float64) (uuid string, err error) {
	r, err := b.client.do("GET", "market/buylimit?market="+market+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64)+"&rate="+strconv.FormatFloat(rate, 'f', 8, 64), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	uuid = u.Id
	return
}

// BuyMarket is used to place a market buy order in a spacific market.
func (b *Bittrex) BuyMarket(market string, quantity float64) (uuid string, err error) {
	r, err := b.client.do("GET", "market/buymarket?market="+market+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	uuid = u.Id
	return
}

// SellLimit is used to place a limited sell order in a specific market.
func (b *Bittrex) SellLimit(market string, quantity, rate float64) (uuid string, err error) {
	r, err := b.client.do("GET", "market/selllimit?market="+market+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64)+"&rate="+strconv.FormatFloat(rate, 'f', 8, 64), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	uuid = u.Id
	return
}

// SellMarket is used to place a market sell order in a specific market.
func (b *Bittrex) SellMarket(market string, quantity float64) (uuid string, err error) {
	r, err := b.client.do("GET", "market/selllimit?market="+market+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	uuid = u.Id
	return
}

// CancelOrder is used to cancel a buy or sell order.
func (b *Bittrex) CancelOrder(orderID string) (err error) {
	r, err := b.client.do("GET", "market/cancel?uuid="+orderID, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	err = handleErr(response)
	return
}

// GetOpenOrders returns orders that you currently have opened.
// If market is set to "all", GetOpenOrders return all orders
// If market is set to a specific order, GetOpenOrders return orders for this market
func (b *Bittrex) GetOpenOrders(market string) (openOrders []Order, err error) {
	ressource := "market/getopenorders"
	if market != "all" {
		ressource += "?market=" + strings.ToUpper(market)
	}
	r, err := b.client.do("GET", ressource, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &openOrders)
	return
}

// Account

// GetBalances is used to retrieve all balances from your account
func (b *Bittrex) GetBalances() (balances map[string]Balance, err error) {
	r, err := b.client.do("GET", "account/getbalances", "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &balances)
	return
}

// Getbalance is used to retrieve the balance from your account for a specific currency.
// currency: a string literal for the currency (ex: LTC)
func (b *Bittrex) GetBalance(currency string) (balance Balance, err error) {
	r, err := b.client.do("GET", "account/getbalance?currency="+strings.ToUpper(currency), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &balance)
	return
}

// GetDepositAddress is sed to generate or retrieve an address for a specific currency.
// currency a string literal for the currency (ie. BTC)
func (b *Bittrex) GetDepositAddress(currency string) (address Address, err error) {
	r, err := b.client.do("GET", "account/getdepositaddress?currency="+strings.ToUpper(currency), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &address)
	return
}

// Withdraw is used to withdraw funds from your account.
// address string the address where to send the funds.
// currency string literal for the currency (ie. BTC)
// quantity float the quantity of coins to withdraw
func (b *Bittrex) Withdraw(address, currency string, quantity float64) (withdrawUuid string, err error) {
	r, err := b.client.do("GET", "account/withdraw?currency="+strings.ToUpper(currency)+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64)+"&address="+address, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	withdrawUuid = u.Id
	return
}

// GetOrderHistory used to retrieve your order history.
// market string literal for the market (ie. BTC-LTC). If set to "all", will return for all market
// count int : 	the number of records to return. Is set to 0, will return max history
func (b *Bittrex) GetOrderHistory(market string, count int) (orders []Order, err error) {
	ressource := "account/getorderhistory"
	if count != 0 || market != "all" {
		ressource += "?"
	}
	if count != 0 {
		ressource += fmt.Sprintf("count=%d&", count)
	}
	if market != "all" {
		ressource += "market=" + market
	}
	r, err := b.client.do("GET", ressource, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &orders)
	return
}

// GetWithdrawalHistory is used to retrieve your withdrawal history
// currency string a string literal for the currency (ie. BTC). If set to "all", will return for all currencies
// count int the number of records to return. Is set to 0 will return the max set.
func (b *Bittrex) GetWithdrawalHistory(currency string, count int) (withdrawals []Withdrawal, err error) {
	ressource := "account/getwithdrawalhistory"
	if count != 0 || currency != "all" {
		ressource += "?"
	}
	if count != 0 {
		ressource += fmt.Sprintf("count=%d&", count)
	}
	if currency != "all" {
		ressource += "currency=" + currency
	}
	r, err := b.client.do("GET", ressource, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &withdrawals)
	return
}

// GetDepositHistory is used to retrieve your deposit history
// currency string a string literal for the currency (ie. BTC). If set to "all", will return for all currencies
// count int the number of records to return. Is set to 0 will return the max set.
func (b *Bittrex) GetDepositHistory(currency string, count int) (deposits []Deposit, err error) {
	ressource := "account/getdeposithistory"
	if count != 0 || currency != "all" {
		ressource += "?"
	}
	if count != 0 {
		ressource += fmt.Sprintf("count=%d&", count)
	}
	if currency != "all" {
		ressource += "currency=" + currency
	}
	r, err := b.client.do("GET", ressource, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &deposits)
	return
}
<commit_msg>Typo<commit_after>// Package Bittrex is an implementation of the Biitrex API (public and private).
package bittrex

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
)

const (
	API_BASE                   = "https://bittrex.com/api/" // Bittrex API endpoint
	API_VERSION                = "v1.1"                     // Bittrex API version
	DEFAULT_HTTPCLIENT_TIMEOUT = 30                         // HTTP client timeout
)

// New return a instanciate bittrex struct
func New(apiKey, apiSecret string) *Bittrex {
	client := NewClient(apiKey, apiSecret)
	return &Bittrex{client}
}

// handleErr gets JSON response from Bittrex API en deal with error
func handleErr(r jsonResponse) error {
	if !r.Success {
		return errors.New(r.Message)
	}
	return nil
}

// bittrex represent a bittrex client
type Bittrex struct {
	client *client
}

// GetMarkets is used to get the open and available trading markets at Bittrex along with other meta data.
func (b *Bittrex) GetMarkets() (markets []Market, err error) {
	r, err := b.client.do("GET", "public/getmarkets", "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &markets)
	return
}

// GetTicker is used to get the current ticker values for a market.
func (b *Bittrex) GetTicker(market string) (ticker Ticker, err error) {
	r, err := b.client.do("GET", "public/getticker?market="+strings.ToUpper(market), "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &ticker)
	return
}

// GetMarketSummaries is used to get the last 24 hour summary of all active exchanges
func (b *Bittrex) GetMarketSummaries() (marketSummaries []MarketSummary, err error) {
	r, err := b.client.do("GET", "public/getmarketsummaries", "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &marketSummaries)
	return
}

// GetOrderBook is used to get retrieve the orderbook for a given market
// market: a string literal for the market (ex: BTC-LTC)
// cat: buy, sell or both to identify the type of orderbook to return.
// depth: how deep of an order book to retrieve. Max is 100
func (b *Bittrex) GetOrderBook(market, cat string, depth int) (orderBook OrderBook, err error) {
	if cat != "buy" && cat != "sell" && cat != "both" {
		cat = "both"
	}
	if depth > 100 {
		depth = 100
	}
	if depth < 1 {
		depth = 1
	}
	r, err := b.client.do("GET", fmt.Sprintf("public/getorderbook?market=%s&type=%s&depth=%d", strings.ToUpper(market), cat, depth), "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &orderBook)
	return
}

// GetMarketHistory is used to retrieve the latest trades that have occured for a specific market.
// mark a string literal for the market (ex: BTC-LTC)
// count a number between 1-100 for the number of entries to return
func (b *Bittrex) GetMarketHistory(market string, count int) (trades []Trade, err error) {
	if count > 100 {
		count = 100
	}
	if count < 1 {
		count = 1
	}
	r, err := b.client.do("GET", fmt.Sprintf("public/getmarkethistory?market=%s&count=%d", strings.ToUpper(market), count), "", false)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &trades)
	return
}

// Market

// BuyLimit is used to place a limited buy order in a specific market.
func (b *Bittrex) BuyLimit(market string, quantity, rate float64) (uuid string, err error) {
	r, err := b.client.do("GET", "market/buylimit?market="+market+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64)+"&rate="+strconv.FormatFloat(rate, 'f', 8, 64), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	uuid = u.Id
	return
}

// BuyMarket is used to place a market buy order in a spacific market.
func (b *Bittrex) BuyMarket(market string, quantity float64) (uuid string, err error) {
	r, err := b.client.do("GET", "market/buymarket?market="+market+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	uuid = u.Id
	return
}

// SellLimit is used to place a limited sell order in a specific market.
func (b *Bittrex) SellLimit(market string, quantity, rate float64) (uuid string, err error) {
	r, err := b.client.do("GET", "market/selllimit?market="+market+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64)+"&rate="+strconv.FormatFloat(rate, 'f', 8, 64), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	uuid = u.Id
	return
}

// SellMarket is used to place a market sell order in a specific market.
func (b *Bittrex) SellMarket(market string, quantity float64) (uuid string, err error) {
	r, err := b.client.do("GET", "market/selllimit?market="+market+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	uuid = u.Id
	return
}

// CancelOrder is used to cancel a buy or sell order.
func (b *Bittrex) CancelOrder(orderID string) (err error) {
	r, err := b.client.do("GET", "market/cancel?uuid="+orderID, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	err = handleErr(response)
	return
}

// GetOpenOrders returns orders that you currently have opened.
// If market is set to "all", GetOpenOrders return all orders
// If market is set to a specific order, GetOpenOrders return orders for this market
func (b *Bittrex) GetOpenOrders(market string) (openOrders []Order, err error) {
	ressource := "market/getopenorders"
	if market != "all" {
		ressource += "?market=" + strings.ToUpper(market)
	}
	r, err := b.client.do("GET", ressource, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &openOrders)
	return
}

// Account

// GetBalances is used to retrieve all balances from your account
func (b *Bittrex) GetBalances() (balances map[string]Balance, err error) {
	r, err := b.client.do("GET", "account/getbalances", "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &balances)
	return
}

// Getbalance is used to retrieve the balance from your account for a specific currency.
// currency: a string literal for the currency (ex: LTC)
func (b *Bittrex) GetBalance(currency string) (balance Balance, err error) {
	r, err := b.client.do("GET", "account/getbalance?currency="+strings.ToUpper(currency), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &balance)
	return
}

// GetDepositAddress is sed to generate or retrieve an address for a specific currency.
// currency a string literal for the currency (ie. BTC)
func (b *Bittrex) GetDepositAddress(currency string) (address Address, err error) {
	r, err := b.client.do("GET", "account/getdepositaddress?currency="+strings.ToUpper(currency), "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &address)
	return
}

// Withdraw is used to withdraw funds from your account.
// address string the address where to send the funds.
// currency string literal for the currency (ie. BTC)
// quantity float the quantity of coins to withdraw
func (b *Bittrex) Withdraw(address, currency string, quantity float64) (withdrawUuid string, err error) {
	r, err := b.client.do("GET", "account/withdraw?currency="+strings.ToUpper(currency)+"&quantity="+strconv.FormatFloat(quantity, 'f', 8, 64)+"&address="+address, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	var u Uuid
	err = json.Unmarshal(response.Result, &u)
	withdrawUuid = u.Id
	return
}

// GetOrderHistory used to retrieve your order history.
// market string literal for the market (ie. BTC-LTC). If set to "all", will return for all market
// count int : 	the number of records to return. Is set to 0, will return max history
func (b *Bittrex) GetOrderHistory(market string, count int) (orders []Order, err error) {
	ressource := "account/getorderhistory"
	if count != 0 || market != "all" {
		ressource += "?"
	}
	if count != 0 {
		ressource += fmt.Sprintf("count=%d&", count)
	}
	if market != "all" {
		ressource += "market=" + market
	}
	r, err := b.client.do("GET", ressource, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &orders)
	return
}

// GetWithdrawalHistory is used to retrieve your withdrawal history
// currency string a string literal for the currency (ie. BTC). If set to "all", will return for all currencies
// count int the number of records to return. Is set to 0 will return the max set.
func (b *Bittrex) GetWithdrawalHistory(currency string, count int) (withdrawals []Withdrawal, err error) {
	ressource := "account/getwithdrawalhistory"
	if count != 0 || currency != "all" {
		ressource += "?"
	}
	if count != 0 {
		ressource += fmt.Sprintf("count=%d&", count)
	}
	if currency != "all" {
		ressource += "currency=" + currency
	}
	r, err := b.client.do("GET", ressource, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &withdrawals)
	return
}

// GetDepositHistory is used to retrieve your deposit history
// currency string a string literal for the currency (ie. BTC). If set to "all", will return for all currencies
// count int the number of records to return. Is set to 0 will return the max set.
func (b *Bittrex) GetDepositHistory(currency string, count int) (deposits []Deposit, err error) {
	ressource := "account/getdeposithistory"
	if count != 0 || currency != "all" {
		ressource += "?"
	}
	if count != 0 {
		ressource += fmt.Sprintf("count=%d&", count)
	}
	if currency != "all" {
		ressource += "currency=" + currency
	}
	r, err := b.client.do("GET", ressource, "", true)
	if err != nil {
		return
	}
	var response jsonResponse
	if err = json.Unmarshal(r, &response); err != nil {
		return
	}
	if err = handleErr(response); err != nil {
		return
	}
	err = json.Unmarshal(response.Result, &deposits)
	return
}
<|endoftext|>


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

// Package generator deals with creating projects from template files.
//
// A generator is a directory containing a definition file, template files,
// and partials.
//
// In addition to metadata, the definition file, which must be a JSON document
// named `generator.json` at the root of the generator, can define variables
// and user inputs that will be made available to the templates and partials.
// An input is read from the user only when its value is needed for the
// first time.
//
// The templates are files that should be placed in a `files` directory, and
// use the Go template syntax to produce project files. Every template will
// result in a file of the same name being created in the generated project
// directory. The path of the generated file within the generated project
// will be the path of the template relative to the `files` directory.
//
// Templates can include partials via the `partial` function. The partials
// should be placed in a `partials` directory. As opposed to templates,
// partials will not result in files being generated. The `partial` function
// expects the path of a partial relative to the `partials` directory, and
// optionally a variadic list of variable maps for the partial. The partials
// have access to the same functions and variables as the templates.
//
// By default templates are evaluated in alphabetical order. You can have
// more control over the order by adding a `priorities` array to the
// definition file. This array should contain a list of files relative
// to the `files` directory that will be evaluated first. That way it is
// possible to control the order which inputs will be read from the user.
//
// A basic definition file may look something like this:
//      {
//        "name": "basic",
//        "version": "0.1.0",
//        "description": "A basic generator",
//        "author": "Stratumn",
//        "license": "MIT",
//        "inputs": {
//          "name": {
//            "type": "string",
//            "prompt": "Project name:",
//            "default": "{{.dir}}",
//            "format": ".+"
//          }
//        }
//      }
//
// In this case, one input called `name` of type `string` is defined. Its
// default value is `{{.dir}}`, which should be a variable given to the
// definition file parser.
//
// A template file in the `template` directory can access the user input
// for `name` using the template function `input`. For instance it could be
// a Markdown file containing the following:
//      # {{input "name"}}
//      A basic project
//
// A project can be generated from the generator this way:
//      // Directory where the project will be generated.
//      dst := "path/to/generated/project"
//
//      // Add a `dir` variable for the definition file set the the name
//      // of the project directory.
//      opts := generator.Options{
//              DefVars: map[string]interface{}{
//                      "dir": filepath.Dir(dst),
//              },
//      }
//
//      // Load the generator.
//      gen, err := generator.NewFromDir("path/to/generator", &opts)
//      if err != nil {
//              panic(err)
//      }
//
//      // Generate the project.
//      if err := gen.Exec(dst); err != nil {
//              panic(err)
//      }
package generator
<commit_msg>Fix generator-doc typo<commit_after>// Copyright 2016 Stratumn SAS. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package generator deals with creating projects from template files.
//
// A generator is a directory containing a definition file, template files,
// and partials.
//
// In addition to metadata, the definition file, which must be a JSON document
// named `generator.json` at the root of the generator, can define variables
// and user inputs that will be made available to the templates and partials.
// An input is read from the user only when its value is needed for the
// first time.
//
// The templates are files that should be placed in a `files` directory, and
// use the Go template syntax to produce project files. Every template will
// result in a file of the same name being created in the generated project
// directory. The path of the generated file within the generated project
// will be the path of the template relative to the `files` directory.
//
// Templates can include partials via the `partial` function. The partials
// should be placed in a `partials` directory. As opposed to templates,
// partials will not result in files being generated. The `partial` function
// expects the path of a partial relative to the `partials` directory, and
// optionally a variadic list of variable maps for the partial. The partials
// have access to the same functions and variables as the templates.
//
// By default templates are evaluated in alphabetical order. You can have
// more control over the order by adding a `priorities` array to the
// definition file. This array should contain a list of files relative
// to the `files` directory that will be evaluated first. That way it is
// possible to control the order which inputs will be read from the user.
//
// A basic definition file may look something like this:
//      {
//        "name": "basic",
//        "version": "0.1.0",
//        "description": "A basic generator",
//        "author": "Stratumn",
//        "license": "MIT",
//        "inputs": {
//          "name": {
//            "type": "string",
//            "prompt": "Project name:",
//            "default": "{{.dir}}",
//            "format": ".+"
//          }
//        }
//      }
//
// In this case, one input called `name` of type `string` is defined. Its
// default value is `{{.dir}}`, which should be a variable given to the
// definition file parser.
//
// A template file in the `template` directory can access the user input
// for `name` using the template function `input`. For instance it could be
// a Markdown file containing the following:
//      # {{input "name"}}
//      A basic project
//
// A project can be generated from the generator this way:
//      // Directory where the project will be generated.
//      dst := "path/to/generated/project"
//
//      // Add a `dir` variable for the definition file set to the name
//      // of the project directory.
//      opts := generator.Options{
//              DefVars: map[string]interface{}{
//                      "dir": filepath.Dir(dst),
//              },
//      }
//
//      // Load the generator.
//      gen, err := generator.NewFromDir("path/to/generator", &opts)
//      if err != nil {
//              panic(err)
//      }
//
//      // Generate the project.
//      if err := gen.Exec(dst); err != nil {
//              panic(err)
//      }
package generator
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strconv"
	"time"

	"github.com/thinxer/ggfetch"

	"github.com/golang/groupcache"
	"gopkg.in/yaml.v1"
)

type Config struct {
	Listen      string   // API Listen Address
	Me          string   // as ip:port
	Peers       []string // as a list of ip:port
	CacheSize   int64    `yaml:"cache_size"`    // in MB
	MaxItemSize int64    `yaml:"max_item_size"` // in KB
}

var (
	flagConfigFile = flag.String("config", "config.yml", "Config file to use.")
)

func main() {
	flag.Parse()

	bytes, err := ioutil.ReadFile(*flagConfigFile)
	if err != nil {
		panic(err)
	}
	config := Config{}
	yaml.Unmarshal(bytes, &config)

	// Setup groupcache peers
	peers := groupcache.NewHTTPPool("http://" + config.Me)
	peersList := []string{}
	for _, peer := range config.Peers {
		peersList = append(peersList, "http://"+peer)
	}
	peers.Set(peersList...)
	go func() {
		panic(http.ListenAndServe(config.Me, peers))
	}()
	// Setup GGFetch
	timeout := 30 * time.Second
	timeoutDialer := func(netw, addr string) (net.Conn, error) {
		start := time.Now()
		conn, err := net.DialTimeout(netw, addr, timeout)
		if err != nil {
			return nil, err
		}
		conn.SetDeadline(start.Add(timeout))
		return conn, nil
	}
	fetcher := ggfetch.New("fetch", config.CacheSize<<20, config.MaxItemSize<<10, &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			Dial:  timeoutDialer,
		},
	})

	// Setup
	http.HandleFunc("/fetch", func(response http.ResponseWriter, request *http.Request) {
		request.ParseForm()
		url := request.FormValue("url")
		ttl_s := request.FormValue("ttl")
		ttl, err := strconv.ParseInt(ttl_s, 10, 64)
		if err != nil {
			ttl = 0
		}
		var buf []byte
		url, buf, err = fetcher.Fetch(url, ttl)
		if err != nil {
			log.Println("Error while fetching:", err)
		}
		response.Header().Set("X-Real-URL", url)
		_, err = response.Write(buf)
		if err != nil {
			log.Println("Error while writing response:", err)
		}
	})
	log.Println("Listening on", config.Listen)
	panic(http.ListenAndServe(config.Listen, nil))
}
<commit_msg>ggserver: cookiejar<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/cookiejar"
	"strconv"
	"time"

	"code.google.com/p/go.net/publicsuffix"
	"github.com/golang/groupcache"
	"gopkg.in/yaml.v1"

	"github.com/thinxer/ggfetch"
)

type Config struct {
	Listen      string   // API Listen Address
	Me          string   // as ip:port
	Peers       []string // as a list of ip:port
	CacheSize   int64    `yaml:"cache_size"`    // in MB
	MaxItemSize int64    `yaml:"max_item_size"` // in KB
}

var (
	flagConfigFile = flag.String("config", "config.yml", "Config file to use.")
)

func main() {
	flag.Parse()

	bytes, err := ioutil.ReadFile(*flagConfigFile)
	if err != nil {
		panic(err)
	}
	config := Config{}
	yaml.Unmarshal(bytes, &config)

	// Setup groupcache peers
	peers := groupcache.NewHTTPPool("http://" + config.Me)
	peersList := []string{}
	for _, peer := range config.Peers {
		peersList = append(peersList, "http://"+peer)
	}
	peers.Set(peersList...)
	go func() {
		panic(http.ListenAndServe(config.Me, peers))
	}()
	// Setup GGFetch
	timeout := 30 * time.Second
	timeoutDialer := func(netw, addr string) (net.Conn, error) {
		start := time.Now()
		conn, err := net.DialTimeout(netw, addr, timeout)
		if err != nil {
			return nil, err
		}
		conn.SetDeadline(start.Add(timeout))
		return conn, nil
	}
	jar, err := cookiejar.New(&cookiejar.Options{
		PublicSuffixList: publicsuffix.List,
	})
	if err != nil {
		panic(err)
	}
	fetcher := ggfetch.New("fetch", config.CacheSize<<20, config.MaxItemSize<<10, &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			Dial:  timeoutDialer,
		},
		Jar: jar,
	})

	// Setup
	http.HandleFunc("/fetch", func(response http.ResponseWriter, request *http.Request) {
		request.ParseForm()
		url := request.FormValue("url")
		ttl_s := request.FormValue("ttl")
		ttl, err := strconv.ParseInt(ttl_s, 10, 64)
		if err != nil {
			ttl = 0
		}
		var buf []byte
		url, buf, err = fetcher.Fetch(url, ttl)
		if err != nil {
			log.Println("Error while fetching:", err)
		}
		response.Header().Set("X-Real-URL", url)
		_, err = response.Write(buf)
		if err != nil {
			log.Println("Error while writing response:", err)
		}
	})
	log.Println("Listening on", config.Listen)
	panic(http.ListenAndServe(config.Listen, nil))
}
<|endoftext|>


<commit_before>package github

import (
	"fmt"
	"github.com/jingweno/octokat"
	"github.com/octokit/go-octokit"
)

const (
	GitHubHost  string = "github.com"
	OAuthAppURL string = "http://owenou.com/gh"
)

type GitHub struct {
	Project *Project
	Config  *Config
}

func (gh *GitHub) PullRequest(id string) (pr *octokit.PullRequest, err error) {
	client := gh.octokit()
	prService, err := client.PullRequests(&octokit.PullRequestsURL, octokit.M{"owner": gh.Project.Owner, "repo": gh.Project.Name, "number": id})
	if err != nil {
		return
	}

	pr, result := prService.Get()
	if result.HasError() {
		err = result.Err
	}

	return
}

func (gh *GitHub) CreatePullRequest(base, head, title, body string) (url string, err error) {
	client := gh.octokit()
	prService, err := client.PullRequests(&octokit.PullRequestsURL, octokit.M{"owner": gh.Project.Owner, "repo": gh.Project.Name})
	if err != nil {
		return
	}

	params := octokit.PullRequestParams{Base: base, Head: head, Title: title, Body: body}
	pr, result := prService.Create(params)
	if result.HasError() {
		err = result.Err
	}

	url = pr.HTMLURL

	return
}

func (gh *GitHub) CreatePullRequestForIssue(base, head, issue string) (url string, err error) {
	client := gh.octokit()
	prService, err := client.PullRequests(&octokit.PullRequestsURL, octokit.M{"owner": gh.Project.Owner, "repo": gh.Project.Name})
	if err != nil {
		return
	}

	params := octokit.PullRequestForIssueParams{Base: base, Head: head, Issue: issue}
	pr, result := prService.Create(params)
	if result.HasError() {
		err = result.Err
	}

	url = pr.HTMLURL

	return
}

func (gh *GitHub) Repository(project Project) (repo *octokit.Repository, err error) {
	client := gh.octokit()
	repoService, err := client.Repositories(&octokit.RepositoryURL, octokit.M{"owner": project.Owner, "repo": project.Name})
	if err != nil {
		return
	}

	repo, result := repoService.Get()
	if result.HasError() {
		err = result.Err
	}

	return
}

// TODO: detach GitHub from Project
func (gh *GitHub) IsRepositoryExist(project Project) bool {
	repo, err := gh.Repository(project)

	return err == nil && repo != nil
}

func (gh *GitHub) CreateRepository(project Project, description, homepage string, isPrivate bool) (repo *octokit.Repository, err error) {
	var repoURL octokit.Hyperlink
	if project.Owner != gh.Config.FetchUser() {
		repoURL = octokit.OrgRepositoriesURL
	} else {
		repoURL = octokit.UserRepositoriesURL
	}

	client := gh.octokit()
	repoService, err := client.Repositories(&repoURL, octokit.M{"org": project.Owner})
	if err != nil {
		return
	}

	params := octokat.Repository{Name: project.Name, Description: description, Homepage: homepage, Private: isPrivate}
	repo, result := repoService.Create(params)
	if result.HasError() {
		err = result.Err
	}

	return
}

func (gh *GitHub) Releases() ([]octokat.Release, error) {
	client := gh.client()
	releases, err := client.Releases(gh.repo(), nil)
	if err != nil {
		return nil, err
	}

	return releases, nil
}

func (gh *GitHub) CiStatus(sha string) (*octokat.Status, error) {
	client := gh.client()
	statuses, err := client.Statuses(gh.repo(), sha, nil)
	if err != nil {
		return nil, err
	}

	if len(statuses) == 0 {
		return nil, nil
	}

	return &statuses[0], nil
}

func (gh *GitHub) ForkRepository(name, owner string, noRemote bool) (repo *octokit.Repository, err error) {
	config := gh.Config
	project := Project{Name: name, Owner: config.User}
	r, err := gh.Repository(project)
	if err == nil && r != nil {
		err = fmt.Errorf("Error creating fork: %s exists on %s", r.FullName, GitHubHost)
		return
	}

	client := gh.octokit()
	repoService, err := client.Repositories(&octokit.ForksURL, octokit.M{"owner": owner, "repo": name})
	repo, result := repoService.Create(nil)
	if result.HasError() {
		err = result.Err
	}

	return
}

func (gh *GitHub) ExpandRemoteUrl(owner, name string, isSSH bool) (url string) {
	project := gh.Project
	if owner == "origin" {
		config := gh.Config
		owner = config.FetchUser()
	}

	return project.GitURL(name, owner, isSSH)
}

func (gh *GitHub) repo() octokat.Repo {
	project := gh.Project
	return octokat.Repo{Name: project.Name, UserName: project.Owner}
}

func findOrCreateToken(user, password, twoFactorCode string) (string, error) {
	client := octokat.NewClient().WithLogin(user, password)
	options := &octokat.Options{}
	if twoFactorCode != "" {
		headers := octokat.Headers{"X-GitHub-OTP": twoFactorCode}
		options.Headers = headers
	}

	auths, err := client.Authorizations(options)
	if err != nil {
		return "", err
	}

	var token string
	for _, auth := range auths {
		if auth.NoteURL == OAuthAppURL {
			token = auth.Token
			break
		}
	}

	if token == "" {
		authParam := octokat.AuthorizationParams{}
		authParam.Scopes = append(authParam.Scopes, "repo")
		authParam.Note = "gh"
		authParam.NoteURL = OAuthAppURL
		options.Params = authParam

		auth, err := client.CreateAuthorization(options)
		if err != nil {
			return "", err
		}

		token = auth.Token
	}

	return token, nil
}

func (gh *GitHub) client() *octokat.Client {
	config := gh.Config
	config.FetchCredentials()

	return octokat.NewClient().WithToken(config.Token)
}

func (gh *GitHub) octokit() *octokit.Client {
	config := gh.Config
	config.FetchCredentials()
	tokenAuth := octokit.TokenAuth{AccessToken: config.Token}

	return octokit.NewClient(tokenAuth)
}

func New() *GitHub {
	project := CurrentProject()
	c := CurrentConfig()
	c.FetchUser()

	return &GitHub{project, c}
}

// TODO: detach project from GitHub
func NewWithoutProject() *GitHub {
	c := CurrentConfig()
	c.FetchUser()

	return &GitHub{nil, c}
}

func (gh *GitHub) Issues() ([]octokat.Issue, error) {
	client := gh.client()
	issues, err := client.Issues(gh.repo(), nil)
	if err != nil {
		return nil, err
	}

	return issues, nil
}
<commit_msg>Fix wrong reference to octokit<commit_after>package github

import (
	"fmt"
	"github.com/jingweno/octokat"
	"github.com/octokit/go-octokit"
)

const (
	GitHubHost  string = "github.com"
	OAuthAppURL string = "http://owenou.com/gh"
)

type GitHub struct {
	Project *Project
	Config  *Config
}

func (gh *GitHub) PullRequest(id string) (pr *octokit.PullRequest, err error) {
	client := gh.octokit()
	prService, err := client.PullRequests(&octokit.PullRequestsURL, octokit.M{"owner": gh.Project.Owner, "repo": gh.Project.Name, "number": id})
	if err != nil {
		return
	}

	pr, result := prService.Get()
	if result.HasError() {
		err = result.Err
	}

	return
}

func (gh *GitHub) CreatePullRequest(base, head, title, body string) (url string, err error) {
	client := gh.octokit()
	prService, err := client.PullRequests(&octokit.PullRequestsURL, octokit.M{"owner": gh.Project.Owner, "repo": gh.Project.Name})
	if err != nil {
		return
	}

	params := octokit.PullRequestParams{Base: base, Head: head, Title: title, Body: body}
	pr, result := prService.Create(params)
	if result.HasError() {
		err = result.Err
	}

	url = pr.HTMLURL

	return
}

func (gh *GitHub) CreatePullRequestForIssue(base, head, issue string) (url string, err error) {
	client := gh.octokit()
	prService, err := client.PullRequests(&octokit.PullRequestsURL, octokit.M{"owner": gh.Project.Owner, "repo": gh.Project.Name})
	if err != nil {
		return
	}

	params := octokit.PullRequestForIssueParams{Base: base, Head: head, Issue: issue}
	pr, result := prService.Create(params)
	if result.HasError() {
		err = result.Err
	}

	url = pr.HTMLURL

	return
}

func (gh *GitHub) Repository(project Project) (repo *octokit.Repository, err error) {
	client := gh.octokit()
	repoService, err := client.Repositories(&octokit.RepositoryURL, octokit.M{"owner": project.Owner, "repo": project.Name})
	if err != nil {
		return
	}

	repo, result := repoService.Get()
	if result.HasError() {
		err = result.Err
	}

	return
}

// TODO: detach GitHub from Project
func (gh *GitHub) IsRepositoryExist(project Project) bool {
	repo, err := gh.Repository(project)

	return err == nil && repo != nil
}

func (gh *GitHub) CreateRepository(project Project, description, homepage string, isPrivate bool) (repo *octokit.Repository, err error) {
	var repoURL octokit.Hyperlink
	if project.Owner != gh.Config.FetchUser() {
		repoURL = octokit.OrgRepositoriesURL
	} else {
		repoURL = octokit.UserRepositoriesURL
	}

	client := gh.octokit()
	repoService, err := client.Repositories(&repoURL, octokit.M{"org": project.Owner})
	if err != nil {
		return
	}

	params := octokit.Repository{Name: project.Name, Description: description, Homepage: homepage, Private: isPrivate}
	repo, result := repoService.Create(params)
	if result.HasError() {
		err = result.Err
	}

	return
}

func (gh *GitHub) Releases() ([]octokat.Release, error) {
	client := gh.client()
	releases, err := client.Releases(gh.repo(), nil)
	if err != nil {
		return nil, err
	}

	return releases, nil
}

func (gh *GitHub) CiStatus(sha string) (*octokat.Status, error) {
	client := gh.client()
	statuses, err := client.Statuses(gh.repo(), sha, nil)
	if err != nil {
		return nil, err
	}

	if len(statuses) == 0 {
		return nil, nil
	}

	return &statuses[0], nil
}

func (gh *GitHub) ForkRepository(name, owner string, noRemote bool) (repo *octokit.Repository, err error) {
	config := gh.Config
	project := Project{Name: name, Owner: config.User}
	r, err := gh.Repository(project)
	if err == nil && r != nil {
		err = fmt.Errorf("Error creating fork: %s exists on %s", r.FullName, GitHubHost)
		return
	}

	client := gh.octokit()
	repoService, err := client.Repositories(&octokit.ForksURL, octokit.M{"owner": owner, "repo": name})
	repo, result := repoService.Create(nil)
	if result.HasError() {
		err = result.Err
	}

	return
}

func (gh *GitHub) ExpandRemoteUrl(owner, name string, isSSH bool) (url string) {
	project := gh.Project
	if owner == "origin" {
		config := gh.Config
		owner = config.FetchUser()
	}

	return project.GitURL(name, owner, isSSH)
}

func (gh *GitHub) repo() octokat.Repo {
	project := gh.Project
	return octokat.Repo{Name: project.Name, UserName: project.Owner}
}

func findOrCreateToken(user, password, twoFactorCode string) (string, error) {
	client := octokat.NewClient().WithLogin(user, password)
	options := &octokat.Options{}
	if twoFactorCode != "" {
		headers := octokat.Headers{"X-GitHub-OTP": twoFactorCode}
		options.Headers = headers
	}

	auths, err := client.Authorizations(options)
	if err != nil {
		return "", err
	}

	var token string
	for _, auth := range auths {
		if auth.NoteURL == OAuthAppURL {
			token = auth.Token
			break
		}
	}

	if token == "" {
		authParam := octokat.AuthorizationParams{}
		authParam.Scopes = append(authParam.Scopes, "repo")
		authParam.Note = "gh"
		authParam.NoteURL = OAuthAppURL
		options.Params = authParam

		auth, err := client.CreateAuthorization(options)
		if err != nil {
			return "", err
		}

		token = auth.Token
	}

	return token, nil
}

func (gh *GitHub) client() *octokat.Client {
	config := gh.Config
	config.FetchCredentials()

	return octokat.NewClient().WithToken(config.Token)
}

func (gh *GitHub) octokit() *octokit.Client {
	config := gh.Config
	config.FetchCredentials()
	tokenAuth := octokit.TokenAuth{AccessToken: config.Token}

	return octokit.NewClient(tokenAuth)
}

func New() *GitHub {
	project := CurrentProject()
	c := CurrentConfig()
	c.FetchUser()

	return &GitHub{project, c}
}

// TODO: detach project from GitHub
func NewWithoutProject() *GitHub {
	c := CurrentConfig()
	c.FetchUser()

	return &GitHub{nil, c}
}

func (gh *GitHub) Issues() ([]octokat.Issue, error) {
	client := gh.client()
	issues, err := client.Issues(gh.repo(), nil)
	if err != nil {
		return nil, err
	}

	return issues, nil
}
<|endoftext|>


<commit_before>package login

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/docker/api/errdefs"

	"github.com/Azure/go-autorest/autorest"
	"github.com/Azure/go-autorest/autorest/adal"
	"github.com/Azure/go-autorest/autorest/azure/cli"
	"github.com/Azure/go-autorest/autorest/date"
	"golang.org/x/oauth2"

	"github.com/pkg/errors"
)

func init() {
	rand.Seed(time.Now().Unix())
}

//go login process, derived from code sample provided by MS at https://github.com/devigned/go-az-cli-stuff
const (
	authorizeFormat = "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&state=%s&prompt=select_account&response_mode=query&scope=%s"
	tokenEndpoint   = "https://login.microsoftonline.com/%s/oauth2/v2.0/token"
	// scopes for a multi-tenant app works for openid, email, other common scopes, but fails when trying to add a token
	// v1 scope like "https://management.azure.com/.default" for ARM access
	scopes   = "offline_access https://management.azure.com/.default"
	clientID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" // Azure CLI client id
)

type (
	azureToken struct {
		Type         string `json:"token_type"`
		Scope        string `json:"scope"`
		ExpiresIn    int    `json:"expires_in"`
		ExtExpiresIn int    `json:"ext_expires_in"`
		AccessToken  string `json:"access_token"`
		RefreshToken string `json:"refresh_token"`
		Foci         string `json:"foci"`
	}

	tenantResult struct {
		Value []tenantValue `json:"value"`
	}
	tenantValue struct {
		TenantID string `json:"tenantId"`
	}
)

// AzureLoginService Service to log into azure and get authentifier for azure APIs
type AzureLoginService struct {
	tokenStore tokenStore
	apiHelper  apiHelper
}

const tokenFilename = "dockerAccessToken.json"

func getTokenStorePath() string {
	cliPath, _ := cli.AccessTokensPath()
	return filepath.Join(filepath.Dir(cliPath), tokenFilename)
}

// NewAzureLoginService creates a NewAzureLoginService
func NewAzureLoginService() AzureLoginService {
	return newAzureLoginServiceFromPath(getTokenStorePath(), azureAPIHelper{})
}

func newAzureLoginServiceFromPath(tokenStorePath string, helper apiHelper) AzureLoginService {
	return AzureLoginService{
		tokenStore: tokenStore{
			filePath: tokenStorePath,
		},
		apiHelper: helper,
	}
}

type apiHelper interface {
	queryToken(data url.Values, tenantID string) (azureToken, error)
}

type azureAPIHelper struct{}

//Login perform azure login through browser
func (login AzureLoginService) Login(ctx context.Context) error {
	queryCh := make(chan url.Values, 1)
	mux := http.NewServeMux()
	mux.HandleFunc("/", queryHandler(queryCh))
	server := &http.Server{Addr: ":8401", Handler: mux}
	go func() {
		if err := server.ListenAndServe(); err != nil {
			queryCh <- url.Values{
				"error": []string{fmt.Sprintf("error starting http server with: %v", err)},
			}
		}
	}()

	state := randomString("", 10)
	authURL := fmt.Sprintf(authorizeFormat, clientID, "http://localhost:8401", state, scopes)
	openbrowser(authURL)

	select {
	case <-ctx.Done():
		return nil
	case qsValues := <-queryCh:
		errorMsg, hasError := qsValues["error"]
		if hasError {
			return fmt.Errorf("login failed : %s", errorMsg)
		}
		code, hasCode := qsValues["code"]
		if !hasCode {
			return errdefs.ErrLoginFailed
		}
		data := url.Values{
			"grant_type":   []string{"authorization_code"},
			"client_id":    []string{clientID},
			"code":         code,
			"scope":        []string{scopes},
			"redirect_uri": []string{"http://localhost:8401"},
		}
		token, err := login.apiHelper.queryToken(data, "organizations")
		if err != nil {
			return errors.Wrap(err, "Access token request failed")
		}

		req, err := http.NewRequest(http.MethodGet, "https://management.azure.com/tenants?api-version=2019-11-01", nil)
		if err != nil {
			return err
		}

		req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			return errors.Wrap(err, "login failed")
		}

		bits, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return errors.Wrap(err, "login failed")
		}

		if res.StatusCode == 200 {
			var tenantResult tenantResult
			if err := json.Unmarshal(bits, &tenantResult); err != nil {
				return errors.Wrap(err, "login failed")
			}
			tenantID := tenantResult.Value[0].TenantID
			tenantToken, err := login.refreshToken(token.RefreshToken, tenantID)
			if err != nil {
				return errors.Wrap(err, "login failed")
			}
			loginInfo := TokenInfo{TenantID: tenantID, Token: tenantToken}

			err = login.tokenStore.writeLoginInfo(loginInfo)

			if err != nil {
				return errors.Wrap(err, "login failed")
			}
			fmt.Println("Login Succeeded")

			return nil
		}

		bits, err = httputil.DumpResponse(res, true)
		if err != nil {
			return errors.Wrap(err, "login failed")
		}

		return fmt.Errorf("login failed: \n" + string(bits))
	}
}

func queryHandler(queryCh chan url.Values) func(w http.ResponseWriter, r *http.Request) {
	queryHandler := func(w http.ResponseWriter, r *http.Request) {
		_, hasCode := r.URL.Query()["code"]
		if hasCode {
			_, err := w.Write([]byte(successfullLoginHTML))
			if err != nil {
				queryCh <- url.Values{
					"error": []string{err.Error()},
				}
			} else {
				queryCh <- r.URL.Query()
			}
		} else {
			_, err := w.Write([]byte(loginFailedHTML))
			if err != nil {
				queryCh <- url.Values{
					"error": []string{err.Error()},
				}
			} else {
				queryCh <- r.URL.Query()
			}
		}
	}
	return queryHandler
}

func (helper azureAPIHelper) queryToken(data url.Values, tenantID string) (token azureToken, err error) {
	res, err := http.Post(fmt.Sprintf(tokenEndpoint, tenantID), "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
	if err != nil {
		return token, err
	}
	if res.StatusCode != 200 {
		return token, errors.Errorf("error while renewing access token, status : %s", res.Status)
	}
	bits, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return token, err
	}
	if err := json.Unmarshal(bits, &token); err != nil {
		return token, err
	}
	return token, nil
}

func toOAuthToken(token azureToken) oauth2.Token {
	expireTime := time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
	oauthToken := oauth2.Token{
		RefreshToken: token.RefreshToken,
		AccessToken:  token.AccessToken,
		Expiry:       expireTime,
		TokenType:    token.Type,
	}
	return oauthToken
}

// NewAuthorizerFromLogin creates an authorizer based on login access token
func (login AzureLoginService) NewAuthorizerFromLogin() (autorest.Authorizer, error) {
	oauthToken, err := login.GetValidToken()
	if err != nil {
		return nil, err
	}

	token := adal.Token{
		AccessToken:  oauthToken.AccessToken,
		Type:         oauthToken.TokenType,
		ExpiresIn:    json.Number(strconv.Itoa(int(time.Until(oauthToken.Expiry).Seconds()))),
		ExpiresOn:    json.Number(strconv.Itoa(int(oauthToken.Expiry.Sub(date.UnixEpoch()).Seconds()))),
		RefreshToken: "",
		Resource:     "",
	}

	return autorest.NewBearerAuthorizer(&token), nil
}

// GetValidToken returns an access token. Refresh token if needed
func (login AzureLoginService) GetValidToken() (token oauth2.Token, err error) {
	loginInfo, err := login.tokenStore.readToken()
	if err != nil {
		return token, err
	}
	token = loginInfo.Token
	if token.Valid() {
		return token, nil
	}
	tenantID := loginInfo.TenantID
	token, err = login.refreshToken(token.RefreshToken, tenantID)
	if err != nil {
		return token, errors.Wrap(err, "access token request failed. Maybe you need to login to azure again.")
	}
	err = login.tokenStore.writeLoginInfo(TokenInfo{TenantID: tenantID, Token: token})
	if err != nil {
		return token, err
	}
	return token, nil
}

func (login AzureLoginService) refreshToken(currentRefreshToken string, tenantID string) (oauthToken oauth2.Token, err error) {
	data := url.Values{
		"grant_type":    []string{"refresh_token"},
		"client_id":     []string{clientID},
		"scope":         []string{scopes},
		"refresh_token": []string{currentRefreshToken},
	}
	token, err := login.apiHelper.queryToken(data, tenantID)
	if err != nil {
		return oauthToken, err
	}

	return toOAuthToken(token), nil
}

func openbrowser(url string) {
	var err error

	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", url).Start()
	case "windows":
		err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
	case "darwin":
		err = exec.Command("open", url).Start()
	default:
		err = fmt.Errorf("unsupported platform")
	}
	if err != nil {
		log.Fatal(err)
	}
}

var (
	letterRunes = []rune("abcdefghijklmnopqrstuvwxyz123456789")
)

func randomString(prefix string, length int) string {
	b := make([]rune, length)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return prefix + string(b)
}

const loginFailedHTML = `
	<!DOCTYPE html>

	    <meta charset="utf-8" />
	    <title>Login failed</title>

Some failures occurred during the authentication

You can log an issue at Azure CLI GitHub Repository and we will assist you in resolving it.

	`

const successfullLoginHTML = `
	<!DOCTYPE html>

	    <meta charset="utf-8" />
	    <meta http-equiv="refresh" content="10;url=https://docs.microsoft.com/cli/azure/">
	    <title>Login successfully</title>

You have logged into Microsoft Azure!

You can close this window, or we will redirect you to the Azure CLI documents in 10 seconds.

	`
<commit_msg>get an available port for login localhost server, instead of hardcoded port<commit_after>package login

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/docker/api/errdefs"

	"github.com/Azure/go-autorest/autorest"
	"github.com/Azure/go-autorest/autorest/adal"
	"github.com/Azure/go-autorest/autorest/azure/cli"
	"github.com/Azure/go-autorest/autorest/date"
	"golang.org/x/oauth2"

	"github.com/pkg/errors"
)

func init() {
	rand.Seed(time.Now().Unix())
}

//go login process, derived from code sample provided by MS at https://github.com/devigned/go-az-cli-stuff
const (
	authorizeFormat = "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&state=%s&prompt=select_account&response_mode=query&scope=%s"
	tokenEndpoint   = "https://login.microsoftonline.com/%s/oauth2/v2.0/token"
	// scopes for a multi-tenant app works for openid, email, other common scopes, but fails when trying to add a token
	// v1 scope like "https://management.azure.com/.default" for ARM access
	scopes   = "offline_access https://management.azure.com/.default"
	clientID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" // Azure CLI client id
)

type (
	azureToken struct {
		Type         string `json:"token_type"`
		Scope        string `json:"scope"`
		ExpiresIn    int    `json:"expires_in"`
		ExtExpiresIn int    `json:"ext_expires_in"`
		AccessToken  string `json:"access_token"`
		RefreshToken string `json:"refresh_token"`
		Foci         string `json:"foci"`
	}

	tenantResult struct {
		Value []tenantValue `json:"value"`
	}
	tenantValue struct {
		TenantID string `json:"tenantId"`
	}
)

// AzureLoginService Service to log into azure and get authentifier for azure APIs
type AzureLoginService struct {
	tokenStore tokenStore
	apiHelper  apiHelper
}

const tokenFilename = "dockerAccessToken.json"

func getTokenStorePath() string {
	cliPath, _ := cli.AccessTokensPath()
	return filepath.Join(filepath.Dir(cliPath), tokenFilename)
}

// NewAzureLoginService creates a NewAzureLoginService
func NewAzureLoginService() AzureLoginService {
	return newAzureLoginServiceFromPath(getTokenStorePath(), azureAPIHelper{})
}

func newAzureLoginServiceFromPath(tokenStorePath string, helper apiHelper) AzureLoginService {
	return AzureLoginService{
		tokenStore: tokenStore{
			filePath: tokenStorePath,
		},
		apiHelper: helper,
	}
}

type apiHelper interface {
	queryToken(data url.Values, tenantID string) (azureToken, error)
}

type azureAPIHelper struct{}

//Login perform azure login through browser
func (login AzureLoginService) Login(ctx context.Context) error {
	queryCh := make(chan url.Values, 1)
	serverPort, err := startLoginServer(queryCh)
	if err != nil {
		return err
	}

	redirectURL := "http://localhost:" + strconv.Itoa(serverPort)
	openAzureLoginPage(redirectURL)

	select {
	case <-ctx.Done():
		return nil
	case qsValues := <-queryCh:
		errorMsg, hasError := qsValues["error"]
		if hasError {
			return fmt.Errorf("login failed : %s", errorMsg)
		}
		code, hasCode := qsValues["code"]
		if !hasCode {
			return errdefs.ErrLoginFailed
		}
		data := url.Values{
			"grant_type":   []string{"authorization_code"},
			"client_id":    []string{clientID},
			"code":         code,
			"scope":        []string{scopes},
			"redirect_uri": []string{redirectURL},
		}
		token, err := login.apiHelper.queryToken(data, "organizations")
		if err != nil {
			return errors.Wrap(err, "Access token request failed")
		}

		req, err := http.NewRequest(http.MethodGet, "https://management.azure.com/tenants?api-version=2019-11-01", nil)
		if err != nil {
			return err
		}

		req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			return errors.Wrap(err, "login failed")
		}

		bits, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return errors.Wrap(err, "login failed")
		}

		if res.StatusCode == 200 {
			var tenantResult tenantResult
			if err := json.Unmarshal(bits, &tenantResult); err != nil {
				return errors.Wrap(err, "login failed")
			}
			tenantID := tenantResult.Value[0].TenantID
			tenantToken, err := login.refreshToken(token.RefreshToken, tenantID)
			if err != nil {
				return errors.Wrap(err, "login failed")
			}
			loginInfo := TokenInfo{TenantID: tenantID, Token: tenantToken}

			err = login.tokenStore.writeLoginInfo(loginInfo)

			if err != nil {
				return errors.Wrap(err, "login failed")
			}
			fmt.Println("Login Succeeded")

			return nil
		}

		bits, err = httputil.DumpResponse(res, true)
		if err != nil {
			return errors.Wrap(err, "login failed")
		}

		return fmt.Errorf("login failed: \n" + string(bits))
	}
}

func startLoginServer(queryCh chan url.Values) (int, error) {
	mux := http.NewServeMux()
	mux.HandleFunc("/", queryHandler(queryCh))
	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		return 0, err
	}

	availablePort := listener.Addr().(*net.TCPAddr).Port
	server := &http.Server{Handler: mux}
	go func() {
		if err := server.Serve(listener); err != nil {
			queryCh <- url.Values{
				"error": []string{fmt.Sprintf("error starting http server with: %v", err)},
			}
		}
	}()
	return availablePort, nil
}

func openAzureLoginPage(redirectURL string) {
	state := randomString("", 10)
	authURL := fmt.Sprintf(authorizeFormat, clientID, redirectURL, state, scopes)
	openbrowser(authURL)
}

func queryHandler(queryCh chan url.Values) func(w http.ResponseWriter, r *http.Request) {
	queryHandler := func(w http.ResponseWriter, r *http.Request) {
		_, hasCode := r.URL.Query()["code"]
		if hasCode {
			_, err := w.Write([]byte(successfullLoginHTML))
			if err != nil {
				queryCh <- url.Values{
					"error": []string{err.Error()},
				}
			} else {
				queryCh <- r.URL.Query()
			}
		} else {
			_, err := w.Write([]byte(loginFailedHTML))
			if err != nil {
				queryCh <- url.Values{
					"error": []string{err.Error()},
				}
			} else {
				queryCh <- r.URL.Query()
			}
		}
	}
	return queryHandler
}

func (helper azureAPIHelper) queryToken(data url.Values, tenantID string) (token azureToken, err error) {
	res, err := http.Post(fmt.Sprintf(tokenEndpoint, tenantID), "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
	if err != nil {
		return token, err
	}
	if res.StatusCode != 200 {
		return token, errors.Errorf("error while renewing access token, status : %s", res.Status)
	}
	bits, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return token, err
	}
	if err := json.Unmarshal(bits, &token); err != nil {
		return token, err
	}
	return token, nil
}

func toOAuthToken(token azureToken) oauth2.Token {
	expireTime := time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
	oauthToken := oauth2.Token{
		RefreshToken: token.RefreshToken,
		AccessToken:  token.AccessToken,
		Expiry:       expireTime,
		TokenType:    token.Type,
	}
	return oauthToken
}

// NewAuthorizerFromLogin creates an authorizer based on login access token
func (login AzureLoginService) NewAuthorizerFromLogin() (autorest.Authorizer, error) {
	oauthToken, err := login.GetValidToken()
	if err != nil {
		return nil, err
	}

	token := adal.Token{
		AccessToken:  oauthToken.AccessToken,
		Type:         oauthToken.TokenType,
		ExpiresIn:    json.Number(strconv.Itoa(int(time.Until(oauthToken.Expiry).Seconds()))),
		ExpiresOn:    json.Number(strconv.Itoa(int(oauthToken.Expiry.Sub(date.UnixEpoch()).Seconds()))),
		RefreshToken: "",
		Resource:     "",
	}

	return autorest.NewBearerAuthorizer(&token), nil
}

// GetValidToken returns an access token. Refresh token if needed
func (login AzureLoginService) GetValidToken() (token oauth2.Token, err error) {
	loginInfo, err := login.tokenStore.readToken()
	if err != nil {
		return token, err
	}
	token = loginInfo.Token
	if token.Valid() {
		return token, nil
	}
	tenantID := loginInfo.TenantID
	token, err = login.refreshToken(token.RefreshToken, tenantID)
	if err != nil {
		return token, errors.Wrap(err, "access token request failed. Maybe you need to login to azure again.")
	}
	err = login.tokenStore.writeLoginInfo(TokenInfo{TenantID: tenantID, Token: token})
	if err != nil {
		return token, err
	}
	return token, nil
}

func (login AzureLoginService) refreshToken(currentRefreshToken string, tenantID string) (oauthToken oauth2.Token, err error) {
	data := url.Values{
		"grant_type":    []string{"refresh_token"},
		"client_id":     []string{clientID},
		"scope":         []string{scopes},
		"refresh_token": []string{currentRefreshToken},
	}
	token, err := login.apiHelper.queryToken(data, tenantID)
	if err != nil {
		return oauthToken, err
	}

	return toOAuthToken(token), nil
}

func openbrowser(url string) {
	var err error

	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", url).Start()
	case "windows":
		err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
	case "darwin":
		err = exec.Command("open", url).Start()
	default:
		err = fmt.Errorf("unsupported platform")
	}
	if err != nil {
		log.Fatal(err)
	}
}

var (
	letterRunes = []rune("abcdefghijklmnopqrstuvwxyz123456789")
)

func randomString(prefix string, length int) string {
	b := make([]rune, length)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return prefix + string(b)
}

const loginFailedHTML = `
	<!DOCTYPE html>

	    <meta charset="utf-8" />
	    <title>Login failed</title>

Some failures occurred during the authentication

You can log an issue at Azure CLI GitHub Repository and we will assist you in resolving it.

	`

const successfullLoginHTML = `
	<!DOCTYPE html>

	    <meta charset="utf-8" />
	    <meta http-equiv="refresh" content="10;url=https://docs.microsoft.com/cli/azure/">
	    <title>Login successfully</title>

You have logged into Microsoft Azure!

You can close this window, or we will redirect you to the Azure CLI documents in 10 seconds.

	`
<|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 main

import (
	"strings"

	"v.io/x/ref/services/internal/acls"
	"v.io/x/ref/services/internal/fs"
	"v.io/x/ref/services/repository"

	"v.io/v23/context"
	"v.io/v23/naming"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/security/access"
	"v.io/v23/services/application"
	"v.io/v23/verror"
	"v.io/x/lib/vlog"
)

// appRepoService implements the Application repository interface.
type appRepoService struct {
	// store is the storage server used for storing application
	// metadata.
	// All objects share the same Memstore.
	store *fs.Memstore
	// storeRoot is a name in the directory under which all data will be
	// stored.
	storeRoot string
	// suffix is the name of the application object.
	suffix string
}

const pkgPath = "v.io/x/ref/services/application/applicationd/"

var (
	ErrInvalidSuffix   = verror.Register(pkgPath+".InvalidSuffix", verror.NoRetry, "{1:}{2:} invalid suffix{:_}")
	ErrOperationFailed = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
	ErrNotAuthorized   = verror.Register(pkgPath+".errNotAuthorized", verror.NoRetry, "{1:}{2:} none of the client's blessings are valid {:_}")
)

// NewApplicationService returns a new Application service implementation.
func NewApplicationService(store *fs.Memstore, storeRoot, suffix string) repository.ApplicationServerMethods {
	return &appRepoService{store: store, storeRoot: storeRoot, suffix: suffix}
}

func parse(ctx *context.T, suffix string) (string, string, error) {
	tokens := strings.Split(suffix, "/")
	switch len(tokens) {
	case 2:
		return tokens[0], tokens[1], nil
	case 1:
		return tokens[0], "", nil
	default:
		return "", "", verror.New(ErrInvalidSuffix, ctx)
	}
}

func (i *appRepoService) Match(ctx *context.T, call rpc.ServerCall, profiles []string) (application.Envelope, error) {
	vlog.VI(0).Infof("%v.Match(%v)", i.suffix, profiles)
	empty := application.Envelope{}
	name, version, err := parse(ctx, i.suffix)
	if err != nil {
		return empty, err
	}
	if version == "" {
		return empty, verror.New(ErrInvalidSuffix, ctx)
	}

	i.store.Lock()
	defer i.store.Unlock()

	for _, profile := range profiles {
		path := naming.Join("/applications", name, profile, version)
		entry, err := i.store.BindObject(path).Get(call)
		if err != nil {
			continue
		}
		envelope, ok := entry.Value.(application.Envelope)
		if !ok {
			continue
		}
		return envelope, nil
	}
	return empty, verror.New(verror.ErrNoExist, ctx)
}

func (i *appRepoService) Put(ctx *context.T, call rpc.ServerCall, profiles []string, envelope application.Envelope) error {
	vlog.VI(0).Infof("%v.Put(%v, %v)", i.suffix, profiles, envelope)
	name, version, err := parse(ctx, i.suffix)
	if err != nil {
		return err
	}
	if version == "" {
		return verror.New(ErrInvalidSuffix, ctx)
	}
	i.store.Lock()
	defer i.store.Unlock()
	// Transaction is rooted at "", so tname == tid.
	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
	if err != nil {
		return err
	}

	// Only add a Permission list value if there is not already one
	// present.
	apath := naming.Join("/acls", name, "data")
	aobj := i.store.BindObject(apath)
	if _, err := aobj.Get(call); verror.ErrorID(err) == fs.ErrNotInMemStore.ID {
		rb, _ := security.RemoteBlessingNames(ctx)
		if len(rb) == 0 {
			// None of the client's blessings are valid.
			return verror.New(ErrNotAuthorized, ctx)
		}
		newacls := acls.PermissionsForBlessings(rb)
		if _, err := aobj.Put(nil, newacls); err != nil {
			return err
		}
	}

	for _, profile := range profiles {
		path := naming.Join(tname, "/applications", name, profile, version)

		object := i.store.BindObject(path)
		_, err := object.Put(call, envelope)
		if err != nil {
			return verror.New(ErrOperationFailed, ctx)
		}
	}
	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
		return verror.New(ErrOperationFailed, ctx)
	}
	return nil
}

func (i *appRepoService) Remove(ctx *context.T, call rpc.ServerCall, profile string) error {
	vlog.VI(0).Infof("%v.Remove(%v)", i.suffix, profile)
	name, version, err := parse(ctx, i.suffix)
	if err != nil {
		return err
	}
	i.store.Lock()
	defer i.store.Unlock()
	// Transaction is rooted at "", so tname == tid.
	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
	if err != nil {
		return err
	}
	path := naming.Join(tname, "/applications", name, profile)
	if version != "" {
		path += "/" + version
	}
	object := i.store.BindObject(path)
	found, err := object.Exists(call)
	if err != nil {
		return verror.New(ErrOperationFailed, ctx)
	}
	if !found {
		return verror.New(verror.ErrNoExist, ctx)
	}
	if err := object.Remove(call); err != nil {
		return verror.New(ErrOperationFailed, ctx)
	}
	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
		return verror.New(ErrOperationFailed, ctx)
	}
	return nil
}

func (i *appRepoService) allApplications() ([]string, error) {
	apps, err := i.store.BindObject("/applications").Children()
	if err != nil {
		return nil, err
	}
	return apps, nil
}

func (i *appRepoService) allAppVersions(appName string) ([]string, error) {
	profiles, err := i.store.BindObject(naming.Join("/applications", appName)).Children()
	if err != nil {
		return nil, err
	}
	uniqueVersions := make(map[string]struct{})
	for _, profile := range profiles {
		versions, err := i.store.BindObject(naming.Join("/applications", appName, profile)).Children()
		if err != nil {
			return nil, err
		}
		for _, v := range versions {
			uniqueVersions[v] = struct{}{}
		}
	}
	versions := make([]string, len(uniqueVersions))
	index := 0
	for v, _ := range uniqueVersions {
		versions[index] = v
		index++
	}
	return versions, nil
}

func (i *appRepoService) GlobChildren__(*context.T, rpc.ServerCall) (<-chan string, error) {
	vlog.VI(0).Infof("%v.GlobChildren__()", i.suffix)
	i.store.Lock()
	defer i.store.Unlock()

	var elems []string
	if i.suffix != "" {
		elems = strings.Split(i.suffix, "/")
	}

	var results []string
	var err error
	switch len(elems) {
	case 0:
		results, err = i.allApplications()
		if err != nil {
			return nil, err
		}
	case 1:
		results, err = i.allAppVersions(elems[0])
		if err != nil {
			return nil, err
		}
	case 2:
		versions, err := i.allAppVersions(elems[0])
		if err != nil {
			return nil, err
		}
		for _, v := range versions {
			if v == elems[1] {
				return nil, nil
			}
		}
		return nil, verror.New(verror.ErrNoExist, nil)
	default:
		return nil, verror.New(verror.ErrNoExist, nil)
	}

	ch := make(chan string, len(results))
	for _, r := range results {
		ch <- r
	}
	close(ch)
	return ch, nil
}

func (i *appRepoService) GetPermissions(ctx *context.T, call rpc.ServerCall) (acl access.Permissions, version string, err error) {
	name, _, err := parse(ctx, i.suffix)
	if err != nil {
		return nil, "", err
	}
	i.store.Lock()
	defer i.store.Unlock()
	path := naming.Join("/acls", name, "data")

	acl, version, err = getAccessList(i.store, path)
	if verror.ErrorID(err) == verror.ErrNoExist.ID {
		return acls.NilAuthPermissions(ctx), "", nil
	}

	return acl, version, err
}

func (i *appRepoService) SetPermissions(ctx *context.T, _ rpc.ServerCall, acl access.Permissions, version string) error {
	name, _, err := parse(ctx, i.suffix)
	if err != nil {
		return err
	}
	i.store.Lock()
	defer i.store.Unlock()
	path := naming.Join("/acls", name, "data")
	return setAccessList(i.store, path, acl, version)
}

// getAccessList fetches a Permissions out of the Memstore at the provided path.
// path is expected to already have been cleaned by naming.Join or its ilk.
func getAccessList(store *fs.Memstore, path string) (access.Permissions, string, error) {
	entry, err := store.BindObject(path).Get(nil)

	if verror.ErrorID(err) == fs.ErrNotInMemStore.ID {
		// No AccessList exists
		return nil, "", verror.New(verror.ErrNoExist, nil)
	} else if err != nil {
		vlog.Errorf("getAccessList: internal failure in fs.Memstore")
		return nil, "", err
	}

	acl, ok := entry.Value.(access.Permissions)
	if !ok {
		return nil, "", err
	}

	version, err := acls.ComputeVersion(acl)
	if err != nil {
		return nil, "", err
	}
	return acl, version, nil
}

// setAccessList writes a Permissions into the Memstore at the provided path.
// where path is expected to have already been cleaned by naming.Join.
func setAccessList(store *fs.Memstore, path string, acl access.Permissions, version string) error {
	_, oversion, err := getAccessList(store, path)
	if verror.ErrorID(err) == verror.ErrNoExist.ID {
		oversion = version
	} else if err != nil {
		return err
	}

	if oversion != version {
		return verror.NewErrBadVersion(nil)
	}

	tname, err := store.BindTransactionRoot("").CreateTransaction(nil)
	if err != nil {
		return err
	}

	object := store.BindObject(path)

	if _, err := object.Put(nil, acl); err != nil {
		return err
	}
	if err := store.BindTransaction(tname).Commit(nil); err != nil {
		return verror.New(ErrOperationFailed, nil)
	}
	return nil
}
<commit_msg>services/application/applicationd: Allow set -f at anytime<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 main

import (
	"strings"

	"v.io/x/ref/services/internal/acls"
	"v.io/x/ref/services/internal/fs"
	"v.io/x/ref/services/repository"

	"v.io/v23/context"
	"v.io/v23/naming"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/security/access"
	"v.io/v23/services/application"
	"v.io/v23/verror"
	"v.io/x/lib/vlog"
)

// appRepoService implements the Application repository interface.
type appRepoService struct {
	// store is the storage server used for storing application
	// metadata.
	// All objects share the same Memstore.
	store *fs.Memstore
	// storeRoot is a name in the directory under which all data will be
	// stored.
	storeRoot string
	// suffix is the name of the application object.
	suffix string
}

const pkgPath = "v.io/x/ref/services/application/applicationd/"

var (
	ErrInvalidSuffix   = verror.Register(pkgPath+".InvalidSuffix", verror.NoRetry, "{1:}{2:} invalid suffix{:_}")
	ErrOperationFailed = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
	ErrNotAuthorized   = verror.Register(pkgPath+".errNotAuthorized", verror.NoRetry, "{1:}{2:} none of the client's blessings are valid {:_}")
)

// NewApplicationService returns a new Application service implementation.
func NewApplicationService(store *fs.Memstore, storeRoot, suffix string) repository.ApplicationServerMethods {
	return &appRepoService{store: store, storeRoot: storeRoot, suffix: suffix}
}

func parse(ctx *context.T, suffix string) (string, string, error) {
	tokens := strings.Split(suffix, "/")
	switch len(tokens) {
	case 2:
		return tokens[0], tokens[1], nil
	case 1:
		return tokens[0], "", nil
	default:
		return "", "", verror.New(ErrInvalidSuffix, ctx)
	}
}

func (i *appRepoService) Match(ctx *context.T, call rpc.ServerCall, profiles []string) (application.Envelope, error) {
	vlog.VI(0).Infof("%v.Match(%v)", i.suffix, profiles)
	empty := application.Envelope{}
	name, version, err := parse(ctx, i.suffix)
	if err != nil {
		return empty, err
	}
	if version == "" {
		return empty, verror.New(ErrInvalidSuffix, ctx)
	}

	i.store.Lock()
	defer i.store.Unlock()

	for _, profile := range profiles {
		path := naming.Join("/applications", name, profile, version)
		entry, err := i.store.BindObject(path).Get(call)
		if err != nil {
			continue
		}
		envelope, ok := entry.Value.(application.Envelope)
		if !ok {
			continue
		}
		return envelope, nil
	}
	return empty, verror.New(verror.ErrNoExist, ctx)
}

func (i *appRepoService) Put(ctx *context.T, call rpc.ServerCall, profiles []string, envelope application.Envelope) error {
	vlog.VI(0).Infof("%v.Put(%v, %v)", i.suffix, profiles, envelope)
	name, version, err := parse(ctx, i.suffix)
	if err != nil {
		return err
	}
	if version == "" {
		return verror.New(ErrInvalidSuffix, ctx)
	}
	i.store.Lock()
	defer i.store.Unlock()
	// Transaction is rooted at "", so tname == tid.
	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
	if err != nil {
		return err
	}

	// Only add a Permission list value if there is not already one
	// present.
	apath := naming.Join("/acls", name, "data")
	aobj := i.store.BindObject(apath)
	if _, err := aobj.Get(call); verror.ErrorID(err) == fs.ErrNotInMemStore.ID {
		rb, _ := security.RemoteBlessingNames(ctx)
		if len(rb) == 0 {
			// None of the client's blessings are valid.
			return verror.New(ErrNotAuthorized, ctx)
		}
		newacls := acls.PermissionsForBlessings(rb)
		if _, err := aobj.Put(nil, newacls); err != nil {
			return err
		}
	}

	for _, profile := range profiles {
		path := naming.Join(tname, "/applications", name, profile, version)

		object := i.store.BindObject(path)
		_, err := object.Put(call, envelope)
		if err != nil {
			return verror.New(ErrOperationFailed, ctx)
		}
	}
	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
		return verror.New(ErrOperationFailed, ctx)
	}
	return nil
}

func (i *appRepoService) Remove(ctx *context.T, call rpc.ServerCall, profile string) error {
	vlog.VI(0).Infof("%v.Remove(%v)", i.suffix, profile)
	name, version, err := parse(ctx, i.suffix)
	if err != nil {
		return err
	}
	i.store.Lock()
	defer i.store.Unlock()
	// Transaction is rooted at "", so tname == tid.
	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
	if err != nil {
		return err
	}
	path := naming.Join(tname, "/applications", name, profile)
	if version != "" {
		path += "/" + version
	}
	object := i.store.BindObject(path)
	found, err := object.Exists(call)
	if err != nil {
		return verror.New(ErrOperationFailed, ctx)
	}
	if !found {
		return verror.New(verror.ErrNoExist, ctx)
	}
	if err := object.Remove(call); err != nil {
		return verror.New(ErrOperationFailed, ctx)
	}
	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
		return verror.New(ErrOperationFailed, ctx)
	}
	return nil
}

func (i *appRepoService) allApplications() ([]string, error) {
	apps, err := i.store.BindObject("/applications").Children()
	if err != nil {
		return nil, err
	}
	return apps, nil
}

func (i *appRepoService) allAppVersions(appName string) ([]string, error) {
	profiles, err := i.store.BindObject(naming.Join("/applications", appName)).Children()
	if err != nil {
		return nil, err
	}
	uniqueVersions := make(map[string]struct{})
	for _, profile := range profiles {
		versions, err := i.store.BindObject(naming.Join("/applications", appName, profile)).Children()
		if err != nil {
			return nil, err
		}
		for _, v := range versions {
			uniqueVersions[v] = struct{}{}
		}
	}
	versions := make([]string, len(uniqueVersions))
	index := 0
	for v, _ := range uniqueVersions {
		versions[index] = v
		index++
	}
	return versions, nil
}

func (i *appRepoService) GlobChildren__(*context.T, rpc.ServerCall) (<-chan string, error) {
	vlog.VI(0).Infof("%v.GlobChildren__()", i.suffix)
	i.store.Lock()
	defer i.store.Unlock()

	var elems []string
	if i.suffix != "" {
		elems = strings.Split(i.suffix, "/")
	}

	var results []string
	var err error
	switch len(elems) {
	case 0:
		results, err = i.allApplications()
		if err != nil {
			return nil, err
		}
	case 1:
		results, err = i.allAppVersions(elems[0])
		if err != nil {
			return nil, err
		}
	case 2:
		versions, err := i.allAppVersions(elems[0])
		if err != nil {
			return nil, err
		}
		for _, v := range versions {
			if v == elems[1] {
				return nil, nil
			}
		}
		return nil, verror.New(verror.ErrNoExist, nil)
	default:
		return nil, verror.New(verror.ErrNoExist, nil)
	}

	ch := make(chan string, len(results))
	for _, r := range results {
		ch <- r
	}
	close(ch)
	return ch, nil
}

func (i *appRepoService) GetPermissions(ctx *context.T, call rpc.ServerCall) (acl access.Permissions, version string, err error) {
	name, _, err := parse(ctx, i.suffix)
	if err != nil {
		return nil, "", err
	}
	i.store.Lock()
	defer i.store.Unlock()
	path := naming.Join("/acls", name, "data")

	acl, version, err = getAccessList(i.store, path)
	if verror.ErrorID(err) == verror.ErrNoExist.ID {
		return acls.NilAuthPermissions(ctx), "", nil
	}

	return acl, version, err
}

func (i *appRepoService) SetPermissions(ctx *context.T, _ rpc.ServerCall, acl access.Permissions, version string) error {
	name, _, err := parse(ctx, i.suffix)
	if err != nil {
		return err
	}
	i.store.Lock()
	defer i.store.Unlock()
	path := naming.Join("/acls", name, "data")
	return setAccessList(i.store, path, acl, version)
}

// getAccessList fetches a Permissions out of the Memstore at the provided path.
// path is expected to already have been cleaned by naming.Join or its ilk.
func getAccessList(store *fs.Memstore, path string) (access.Permissions, string, error) {
	entry, err := store.BindObject(path).Get(nil)

	if verror.ErrorID(err) == fs.ErrNotInMemStore.ID {
		// No AccessList exists
		return nil, "", verror.New(verror.ErrNoExist, nil)
	} else if err != nil {
		vlog.Errorf("getAccessList: internal failure in fs.Memstore")
		return nil, "", err
	}

	acl, ok := entry.Value.(access.Permissions)
	if !ok {
		return nil, "", err
	}

	version, err := acls.ComputeVersion(acl)
	if err != nil {
		return nil, "", err
	}
	return acl, version, nil
}

// setAccessList writes a Permissions into the Memstore at the provided path.
// where path is expected to have already been cleaned by naming.Join.
func setAccessList(store *fs.Memstore, path string, acl access.Permissions, version string) error {
	if version != "" {
		_, oversion, err := getAccessList(store, path)
		if verror.ErrorID(err) == verror.ErrNoExist.ID {
			oversion = version
		} else if err != nil {
			return err
		}

		if oversion != version {
			return verror.NewErrBadVersion(nil)
		}
	}

	tname, err := store.BindTransactionRoot("").CreateTransaction(nil)
	if err != nil {
		return err
	}

	object := store.BindObject(path)

	if _, err := object.Put(nil, acl); err != nil {
		return err
	}
	if err := store.BindTransaction(tname).Commit(nil); err != nil {
		return verror.New(ErrOperationFailed, nil)
	}
	return nil
}
<|endoftext|>